/*-------------------------------------------------------------------------
 *
 * cjoin.c
 *	  Test-only custom scan provider that replaces a join.
 *
 * No in-core code produces a CustomScan that stands for a whole join, so
 * this module does: it replaces the inner join of two tables named cj_a and
 * cj_b with one CustomScan node.
 *
 * Only the planner's side is implemented, because only the shape of the plan
 * matters here.  The node keeps no join clauses and cannot be executed: it
 * raises an error if the executor asks it for a row.  Use EXPLAIN, or a
 * query that is planned but never runs the node.
 *
 * To build it with PGXS, put this file next to a Makefile that contains
 *
 *		MODULES = cjoin
 *		PG_CONFIG = pg_config
 *		PGXS := $(shell $(PG_CONFIG) --pgxs)
 *		include $(PGXS)
 *
 * run "make", and then LOAD the resulting library by its full path.
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "executor/executor.h"
#include "fmgr.h"
#include "nodes/extensible.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"

PG_MODULE_MAGIC;

#define CJ_NAME "CustomJoin"

/* ExecInitCustomScan() always calls this, but there is nothing to set up. */
static void
cj_begin(CustomScanState *node, EState *estate, int eflags)
{
}

/*
 * Running the join would need real executor code.  Refuse, so that nobody
 * gets a wrong result from a node that has none.
 */
static TupleTableSlot *
cj_exec(CustomScanState *node)
{
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("cjoin cannot execute a join"),
			 errhint("Use EXPLAIN to look at the plan.")));
	return NULL;				/* keep compiler quiet */
}

/* EndCustomScan and ReScanCustomScan: there is no state to free or reset. */
static void
cj_noop(CustomScanState *node)
{
}

static const CustomExecMethods cj_exec_methods =
{
	.CustomName = CJ_NAME,
	.BeginCustomScan = cj_begin,
	.ExecCustomScan = cj_exec,
	.EndCustomScan = cj_noop,
	.ReScanCustomScan = cj_noop,
};

static Node *
cj_create_state(CustomScan *cscan)
{
	CustomScanState *css = makeNode(CustomScanState);

	css->methods = &cj_exec_methods;

	/*
	 * ExecInitCustomScan() reads slotOps before it makes the scan slot.  A
	 * join has no single table from which to take the slot type, so use
	 * virtual tuples.
	 */
	css->slotOps = &TTSOpsVirtual;

	return (Node *) css;
}

static const CustomScanMethods cj_scan_methods =
{
	.CustomName = CJ_NAME,
	.CreateCustomScanState = cj_create_state,
};

static Plan *
cj_plan(PlannerInfo *root, RelOptInfo *rel, struct CustomPath *best_path,
		List *tlist, List *clauses, List *custom_plans)
{
	CustomScan *cscan = makeNode(CustomScan);

	/*
	 * scanrelid = 0 is how a CustomScan says that it stands for a join and
	 * not for one base relation.  setrefs.c and the executor then resolve
	 * Vars through custom_scan_tlist, because there is no scan relation to
	 * resolve them against, so that list must describe the join's output.
	 *
	 * create_customscan_plan() sets custom_relids to the relids of the join
	 * after this function returns.
	 */
	cscan->scan.scanrelid = 0;
	cscan->scan.plan.targetlist = tlist;
	cscan->custom_scan_tlist = copyObject(tlist);
	cscan->methods = &cj_scan_methods;

	return (Plan *) cscan;
}

static const CustomPathMethods cj_path_methods =
{
	.CustomName = CJ_NAME,
	.PlanCustomPath = cj_plan,
};

static bool
cj_is_table(PlannerInfo *root, RelOptInfo *rel, const char *name)
{
	char	   *relname;

	if (rel->reloptkind != RELOPT_BASEREL || rel->rtekind != RTE_RELATION)
		return false;
	relname = get_rel_name(planner_rt_fetch(rel->relid, root)->relid);
	return relname != NULL && strcmp(relname, name) == 0;
}

static set_join_pathlist_hook_type prev_join_pathlist_hook = NULL;

static void
cj_join_pathlist(PlannerInfo *root, RelOptInfo *joinrel,
				 RelOptInfo *outerrel, RelOptInfo *innerrel,
				 JoinType jointype, JoinPathExtraData *extra)
{
	CustomPath *cpath;

	if (prev_join_pathlist_hook)
		prev_join_pathlist_hook(root, joinrel, outerrel, innerrel,
								jointype, extra);

	/*
	 * The planner calls this hook for both orders of an inner join, so it is
	 * enough to act on one of them.  A join that has lateral references would
	 * need a parameterized path, which this module does not build.
	 */
	if (jointype != JOIN_INNER ||
		!cj_is_table(root, outerrel, "cj_a") ||
		!cj_is_table(root, innerrel, "cj_b") ||
		joinrel->lateral_relids != NULL)
		return;

	cpath = makeNode(CustomPath);
	cpath->path.pathtype = T_CustomScan;
	cpath->path.parent = joinrel;
	cpath->path.pathtarget = joinrel->reltarget;
	cpath->path.rows = joinrel->rows;

	/*
	 * A cost of zero makes the planner choose this path every time, whatever
	 * the table sizes are.  A real provider must give an honest estimate.
	 */
	cpath->path.startup_cost = 0;
	cpath->path.total_cost = 0;
	cpath->methods = &cj_path_methods;

	add_path(joinrel, (Path *) cpath);
}

void
_PG_init(void)
{
	RegisterCustomScanMethods(&cj_scan_methods);

	prev_join_pathlist_hook = set_join_pathlist_hook;
	set_join_pathlist_hook = cj_join_pathlist;
}
