*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.143 2000/09/12 04:49:06 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.144 2000/09/12 21:06:46 tgl Exp $
*
*
* INTERFACE ROUTINES
*/
rte = makeNode(RangeTblEntry);
rte->relname = RelationGetRelationName(rel);
-#ifndef DISABLE_EREF
- rte->ref = makeNode(Attr);
- rte->ref->relname = RelationGetRelationName(rel);
-#endif
rte->relid = RelationGetRelid(rel);
+ rte->eref = makeNode(Attr);
+ rte->eref->relname = RelationGetRelationName(rel);
rte->inh = false;
rte->inFromCl = true;
rte->skipAcl = false;
*/
rte = makeNode(RangeTblEntry);
rte->relname = RelationGetRelationName(rel);
-#ifndef DISABLE_EREF
- rte->ref = makeNode(Attr);
- rte->ref->relname = RelationGetRelationName(rel);
-#endif
rte->relid = RelationGetRelid(rel);
+ rte->eref = makeNode(Attr);
+ rte->eref->relname = RelationGetRelationName(rel);
rte->inh = false;
rte->inFromCl = true;
rte->skipAcl = false;
int numoldchecks;
ConstrCheck *oldchecks;
ParseState *pstate;
+ RangeTblEntry *rte;
int numchecks;
List *listptr;
Relation relrel;
*/
pstate = make_parsestate(NULL);
makeRangeTable(pstate, NULL);
- addRangeTableEntry(pstate, relname, makeAttr(relname, NULL), false, true, true);
+ rte = addRangeTableEntry(pstate, relname, NULL, false, true);
+ addRTEtoJoinTree(pstate, rte);
/*
* Process column default expressions.
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.102 2000/09/12 05:09:43 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.103 2000/09/12 21:06:47 tgl Exp $
*
* NOTES
* The PerformAddAttribute() code, like most of the relation
static bool is_view(Relation rel);
-
-
/* --------------------------------
* PortalCleanup
* --------------------------------
rel = heap_openr(relationName, AccessExclusiveLock);
if ( rel->rd_rel->relkind == RELKIND_VIEW )
elog(ERROR, "ALTER TABLE: %s is a view", relationName);
-
myrelid = RelationGetRelid(rel);
heap_close(rel, NoLock);
* find a specified attribute in a node entry
*/
static bool
-find_attribute_walker(Node *node, int attnum)
+find_attribute_walker(Node *node, int *attnump)
{
if (node == NULL)
return false;
Var *var = (Var *) node;
if (var->varlevelsup == 0 && var->varno == 1 &&
- var->varattno == attnum)
+ var->varattno == *attnump)
return true;
}
- return expression_tree_walker(node, find_attribute_walker, (void *) attnum);
+ return expression_tree_walker(node, find_attribute_walker,
+ (void *) attnump);
}
static bool
find_attribute_in_node(Node *node, int attnum)
{
- return expression_tree_walker(node, find_attribute_walker, (void *) attnum);
+ return find_attribute_walker(node, &attnum);
}
/*
AlterTableAddConstraint(char *relationName,
bool inh, Node *newConstraint)
{
-
if (newConstraint == NULL)
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT passed invalid constraint.");
/* check to see if the table to be constrained is a view. */
if (is_viewr(relationName))
elog(ERROR, "ALTER TABLE: Cannot add constraints to views.");
-
+
switch (nodeTag(newConstraint))
{
case T_Constraint:
+ {
+ Constraint *constr = (Constraint *) newConstraint;
+
+ switch (constr->contype)
{
- Constraint *constr=(Constraint *)newConstraint;
- switch (constr->contype) {
- case CONSTR_CHECK:
+ case CONSTR_CHECK:
+ {
+ ParseState *pstate;
+ bool successful = TRUE;
+ HeapScanDesc scan;
+ ExprContext *econtext;
+ TupleTableSlot *slot = makeNode(TupleTableSlot);
+ HeapTuple tuple;
+ RangeTblEntry *rte;
+ List *rtlist;
+ List *qual;
+ List *constlist;
+ Relation rel;
+ Node *expr;
+ char *name;
+
+ if (constr->name)
+ name=constr->name;
+ else
+ name="<unnamed>";
+
+ constlist=lcons(constr, NIL);
+
+ rel = heap_openr(relationName, AccessExclusiveLock);
+
+ /* make sure it is not a view */
+ if (rel->rd_rel->relkind == RELKIND_VIEW)
+ elog(ERROR, "ALTER TABLE: cannot add constraint to a view");
+
+ /*
+ * Scan all of the rows, looking for a false match
+ */
+ scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+ AssertState(scan != NULL);
+
+ /*
+ * We need to make a parse state and range table to allow
+ * us to transformExpr and fix_opids to get a version of
+ * the expression we can pass to ExecQual
+ */
+ pstate = make_parsestate(NULL);
+ makeRangeTable(pstate, NULL);
+ rte = addRangeTableEntry(pstate, relationName, NULL,
+ false, true);
+ addRTEtoJoinTree(pstate, rte);
+
+ /* Convert the A_EXPR in raw_expr into an EXPR */
+ expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST);
+
+ /*
+ * Make sure it yields a boolean result.
+ */
+ if (exprType(expr) != BOOLOID)
+ elog(ERROR, "CHECK '%s' does not yield boolean result",
+ name);
+
+ /*
+ * Make sure no outside relations are referred to.
+ */
+ if (length(pstate->p_rtable) != 1)
+ elog(ERROR, "Only relation '%s' can be referenced in CHECK",
+ relationName);
+
+ /*
+ * Might as well try to reduce any constant expressions.
+ */
+ expr = eval_const_expressions(expr);
+
+ /* And fix the opids */
+ fix_opids(expr);
+
+ qual = lcons(expr, NIL);
+
+ rte = makeNode(RangeTblEntry);
+ rte->relname = relationName;
+ rte->relid = RelationGetRelid(rel);
+ rte->eref = makeNode(Attr);
+ rte->eref->relname = relationName;
+ rtlist = lcons(rte, NIL);
+
+ /*
+ * Scan through the rows now, making the necessary things
+ * for ExecQual, and then call it to evaluate the
+ * expression.
+ */
+ while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
{
- ParseState *pstate;
- bool successful=TRUE;
- HeapScanDesc scan;
- ExprContext *econtext;
- TupleTableSlot *slot = makeNode(TupleTableSlot);
- HeapTuple tuple;
- RangeTblEntry *rte = makeNode(RangeTblEntry);
- List *rtlist;
- List *qual;
- List *constlist;
- Relation rel;
- Node *expr;
- char *name;
- if (constr->name)
- name=constr->name;
- else
- name="<unnamed>";
-
- rel = heap_openr(relationName, AccessExclusiveLock);
-
- /* make sure it is not a view */
- if (rel->rd_rel->relkind == RELKIND_VIEW)
- elog(ERROR, "ALTER TABLE: cannot add constraint to a view");
-
- /*
- * Scan all of the rows, looking for a false match
- */
- scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
- AssertState(scan != NULL);
-
- /*
- *We need to make a parse state and range table to allow us
- * to transformExpr and fix_opids to get a version of the
- * expression we can pass to ExecQual
- */
- pstate = make_parsestate(NULL);
- makeRangeTable(pstate, NULL);
- addRangeTableEntry(pstate, relationName,
- makeAttr(relationName, NULL), false, true,true);
- constlist=lcons(constr, NIL);
-
- /* Convert the A_EXPR in raw_expr into an EXPR */
- expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST);
-
- /*
- * Make sure it yields a boolean result.
- */
- if (exprType(expr) != BOOLOID)
- elog(ERROR, "CHECK '%s' does not yield boolean result",
- name);
-
- /*
- * Make sure no outside relations are referred to.
- */
- if (length(pstate->p_rtable) != 1)
- elog(ERROR, "Only relation '%s' can be referenced in CHECK",
- relationName);
-
- /*
- * Might as well try to reduce any constant expressions.
- */
- expr = eval_const_expressions(expr);
-
- /* And fix the opids */
- fix_opids(expr);
-
- qual = lcons(expr, NIL);
- rte->relname = relationName;
- rte->ref = makeNode(Attr);
- rte->ref->relname = rte->relname;
- rte->relid = RelationGetRelid(rel);
- rtlist = lcons(rte, NIL);
-
- /*
- * Scan through the rows now, making the necessary things for
- * ExecQual, and then call it to evaluate the expression.
- */
- while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+ slot->val = tuple;
+ slot->ttc_shouldFree = false;
+ slot->ttc_descIsNew = true;
+ slot->ttc_tupleDescriptor = rel->rd_att;
+ slot->ttc_buffer = InvalidBuffer;
+ slot->ttc_whichplan = -1;
+
+ econtext = MakeExprContext(slot, CurrentMemoryContext);
+ econtext->ecxt_range_table = rtlist; /* range table */
+ if (!ExecQual(qual, econtext, true))
{
- slot->val = tuple;
- slot->ttc_shouldFree = false;
- slot->ttc_descIsNew = true;
- slot->ttc_tupleDescriptor = rel->rd_att;
- slot->ttc_buffer = InvalidBuffer;
- slot->ttc_whichplan = -1;
-
- econtext = MakeExprContext(slot, CurrentMemoryContext);
- econtext->ecxt_range_table = rtlist; /* range table */
- if (!ExecQual(qual, econtext, true)) {
- successful=false;
- break;
- }
- FreeExprContext(econtext);
+ successful=false;
+ break;
}
+ FreeExprContext(econtext);
+ }
- pfree(slot);
- pfree(rtlist);
- pfree(rte);
+ pfree(slot);
+ pfree(rtlist);
+ pfree(rte);
- heap_endscan(scan);
- heap_close(rel, NoLock);
+ heap_endscan(scan);
+ heap_close(rel, NoLock);
- if (!successful)
- {
- elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
- }
- /*
- * Call AddRelationRawConstraints to do the real adding -- It duplicates some
- * of the above, but does not check the validity of the constraint against
- * tuples already in the table.
- */
- AddRelationRawConstraints(rel, NIL, constlist);
- pfree(constlist);
-
- break;
+ if (!successful)
+ {
+ elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
}
- default:
- elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
+ /*
+ * Call AddRelationRawConstraints to do the real adding --
+ * It duplicates some of the above, but does not check the
+ * validity of the constraint against tuples already in
+ * the table.
+ */
+ AddRelationRawConstraints(rel, NIL, constlist);
+ pfree(constlist);
+
+ break;
}
+ default:
+ elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
}
break;
+ }
case T_FkConstraint:
- {
- FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
- Relation rel, pkrel;
- HeapScanDesc scan;
- HeapTuple tuple;
- Trigger trig;
- List *list;
- int count;
- List *indexoidlist,
- *indexoidscan;
- Form_pg_index indexStruct = NULL;
- Form_pg_attribute *rel_attrs = NULL;
- int i;
- int found=0;
-
- if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
- get_temp_rel_by_username(relationName)==NULL) {
- elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
- }
+ {
+ FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
+ Relation rel, pkrel;
+ HeapScanDesc scan;
+ HeapTuple tuple;
+ Trigger trig;
+ List *list;
+ int count;
+ List *indexoidlist,
+ *indexoidscan;
+ Form_pg_index indexStruct = NULL;
+ Form_pg_attribute *rel_attrs = NULL;
+ int i;
+ int found=0;
+
+ if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
+ get_temp_rel_by_username(relationName)==NULL) {
+ elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+ }
+
+ /*
+ * Grab an exclusive lock on the pk table, so that someone
+ * doesn't delete rows out from under us.
+ */
+
+ pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
+ if (pkrel->rd_rel->relkind != RELKIND_RELATION)
+ elog(ERROR, "referenced table \"%s\" not a relation",
+ fkconstraint->pktable_name);
+
+ /*
+ * Grab an exclusive lock on the fk table, and then scan
+ * through each tuple, calling the RI_FKey_Match_Ins
+ * (insert trigger) as if that tuple had just been
+ * inserted. If any of those fail, it should elog(ERROR)
+ * and that's that.
+ */
+ rel = heap_openr(relationName, AccessExclusiveLock);
+ if (rel->rd_rel->relkind != RELKIND_RELATION)
+ elog(ERROR, "referencing table \"%s\" not a relation",
+ relationName);
+
+ /* First we check for limited correctness of the constraint */
+
+ rel_attrs = pkrel->rd_att->attrs;
+ indexoidlist = RelationGetIndexList(pkrel);
- /*
- * Grab an exclusive lock on the pk table, so that someone
- * doesn't delete rows out from under us.
- */
-
- pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
- if (pkrel == NULL)
- elog(ERROR, "referenced table \"%s\" not found",
- fkconstraint->pktable_name);
-
- if (pkrel->rd_rel->relkind != RELKIND_RELATION)
- elog(ERROR, "referenced table \"%s\" not a relation",
- fkconstraint->pktable_name);
-
-
- /*
- * Grab an exclusive lock on the fk table, and then scan
- * through each tuple, calling the RI_FKey_Match_Ins
- * (insert trigger) as if that tuple had just been
- * inserted. If any of those fail, it should elog(ERROR)
- * and that's that.
- */
- rel = heap_openr(relationName, AccessExclusiveLock);
- if (rel == NULL)
- elog(ERROR, "table \"%s\" not found",
- relationName);
-
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- elog(ERROR, "referencing table \"%s\" not a relation", relationName);
-
- /* First we check for limited correctness of the constraint */
-
- rel_attrs = pkrel->rd_att->attrs;
- indexoidlist = RelationGetIndexList(pkrel);
-
- foreach(indexoidscan, indexoidlist)
- {
- Oid indexoid = lfirsti(indexoidscan);
- HeapTuple indexTuple;
- List *attrl;
- indexTuple = SearchSysCacheTuple(INDEXRELID,
- ObjectIdGetDatum(indexoid),
- 0, 0, 0);
- if (!HeapTupleIsValid(indexTuple))
- elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found",
- indexoid);
- indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
-
- if (indexStruct->indisunique) {
- /* go through the fkconstraint->pk_attrs list */
- foreach(attrl, fkconstraint->pk_attrs) {
- Ident *attr=lfirst(attrl);
- found=0;
- for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++)
- {
- int pkattno = indexStruct->indkey[i];
+ foreach(indexoidscan, indexoidlist)
+ {
+ Oid indexoid = lfirsti(indexoidscan);
+ HeapTuple indexTuple;
+ List *attrl;
+ indexTuple = SearchSysCacheTuple(INDEXRELID,
+ ObjectIdGetDatum(indexoid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(indexTuple))
+ elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found",
+ indexoid);
+ indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
+
+ if (indexStruct->indisunique) {
+ /* go through the fkconstraint->pk_attrs list */
+ foreach(attrl, fkconstraint->pk_attrs) {
+ Ident *attr=lfirst(attrl);
+ found=0;
+ for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++)
+ {
+ int pkattno = indexStruct->indkey[i];
if (pkattno>0) {
char *name = NameStr(rel_attrs[pkattno-1]->attname);
- if (strcmp(name, attr->name)==0) {
- found=1;
- break;
- }
+ if (strcmp(name, attr->name)==0) {
+ found=1;
+ break;
+ }
}
- }
- if (!found)
- break;
- }
- }
- if (found)
- break;
- indexStruct = NULL;
- }
- if (!found)
- elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found",
- fkconstraint->pktable_name);
-
- freeList(indexoidlist);
- heap_close(pkrel, NoLock);
-
- rel_attrs = rel->rd_att->attrs;
- if (fkconstraint->fk_attrs!=NIL) {
- int found=0;
- List *fkattrs;
- Ident *fkattr;
- foreach(fkattrs, fkconstraint->fk_attrs) {
- int count=0;
- found=0;
- fkattr=lfirst(fkattrs);
- for (; count < rel->rd_att->natts; count++) {
- char *name = NameStr(rel->rd_att->attrs[count]->attname);
- if (strcmp(name, fkattr->name)==0) {
- found=1;
- break;
- }
- }
- if (!found)
- break;
- }
- if (!found)
- elog(ERROR, "columns referenced in foreign key constraint not found.");
- }
-
- trig.tgoid = 0;
- if (fkconstraint->constr_name)
- trig.tgname = fkconstraint->constr_name;
- else
- trig.tgname = "<unknown>";
- trig.tgfoid = 0;
- trig.tgtype = 0;
- trig.tgenabled = TRUE;
- trig.tgisconstraint = TRUE;
- trig.tginitdeferred = FALSE;
- trig.tgdeferrable = FALSE;
-
- trig.tgargs = (char **) palloc(
- sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
- + length(fkconstraint->pk_attrs)));
-
- if (fkconstraint->constr_name)
- trig.tgargs[0] = fkconstraint->constr_name;
- else
- trig.tgargs[0] = "<unknown>";
- trig.tgargs[1] = (char *) relationName;
- trig.tgargs[2] = fkconstraint->pktable_name;
- trig.tgargs[3] = fkconstraint->match_type;
- count = 4;
- foreach(list, fkconstraint->fk_attrs)
+ }
+ if (!found)
+ break;
+ }
+ }
+ if (found)
+ break;
+ indexStruct = NULL;
+ }
+ if (!found)
+ elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found",
+ fkconstraint->pktable_name);
+
+ freeList(indexoidlist);
+ heap_close(pkrel, NoLock);
+
+ rel_attrs = rel->rd_att->attrs;
+ if (fkconstraint->fk_attrs!=NIL) {
+ int found=0;
+ List *fkattrs;
+ Ident *fkattr;
+ foreach(fkattrs, fkconstraint->fk_attrs) {
+ int count=0;
+ found=0;
+ fkattr=lfirst(fkattrs);
+ for (; count < rel->rd_att->natts; count++) {
+ char *name = NameStr(rel->rd_att->attrs[count]->attname);
+ if (strcmp(name, fkattr->name)==0) {
+ found=1;
+ break;
+ }
+ }
+ if (!found)
+ break;
+ }
+ if (!found)
+ elog(ERROR, "columns referenced in foreign key constraint not found.");
+ }
+
+ trig.tgoid = 0;
+ if (fkconstraint->constr_name)
+ trig.tgname = fkconstraint->constr_name;
+ else
+ trig.tgname = "<unknown>";
+ trig.tgfoid = 0;
+ trig.tgtype = 0;
+ trig.tgenabled = TRUE;
+ trig.tgisconstraint = TRUE;
+ trig.tginitdeferred = FALSE;
+ trig.tgdeferrable = FALSE;
+
+ trig.tgargs = (char **) palloc(
+ sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
+ + length(fkconstraint->pk_attrs)));
+
+ if (fkconstraint->constr_name)
+ trig.tgargs[0] = fkconstraint->constr_name;
+ else
+ trig.tgargs[0] = "<unknown>";
+ trig.tgargs[1] = (char *) relationName;
+ trig.tgargs[2] = fkconstraint->pktable_name;
+ trig.tgargs[3] = fkconstraint->match_type;
+ count = 4;
+ foreach(list, fkconstraint->fk_attrs)
{
Ident *fk_at = lfirst(list);
trig.tgargs[count++] = fk_at->name;
}
- foreach(list, fkconstraint->pk_attrs)
+ foreach(list, fkconstraint->pk_attrs)
{
Ident *pk_at = lfirst(list);
trig.tgargs[count++] = pk_at->name;
}
- trig.tgnargs = count;
+ trig.tgnargs = count;
- scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
- AssertState(scan != NULL);
+ scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+ AssertState(scan != NULL);
- while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
- {
- /* Make a call to the check function */
- /* No parameters are passed, but we do set a context */
- FunctionCallInfoData fcinfo;
- TriggerData trigdata;
-
- MemSet(&fcinfo, 0, sizeof(fcinfo));
- /* We assume RI_FKey_check_ins won't look at flinfo... */
+ while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+ {
+ /* Make a call to the check function */
+ /* No parameters are passed, but we do set a context */
+ FunctionCallInfoData fcinfo;
+ TriggerData trigdata;
- trigdata.type = T_TriggerData;
- trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
- trigdata.tg_relation = rel;
- trigdata.tg_trigtuple = tuple;
- trigdata.tg_newtuple = NULL;
- trigdata.tg_trigger = &trig;
+ MemSet(&fcinfo, 0, sizeof(fcinfo));
+ /* We assume RI_FKey_check_ins won't look at flinfo... */
- fcinfo.context = (Node *) &trigdata;
+ trigdata.type = T_TriggerData;
+ trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
+ trigdata.tg_relation = rel;
+ trigdata.tg_trigtuple = tuple;
+ trigdata.tg_newtuple = NULL;
+ trigdata.tg_trigger = &trig;
- RI_FKey_check_ins(&fcinfo);
- }
- heap_endscan(scan);
- heap_close(rel, NoLock); /* close rel but keep
- * lock! */
+ fcinfo.context = (Node *) &trigdata;
- pfree(trig.tgargs);
+ RI_FKey_check_ins(&fcinfo);
}
+ heap_endscan(scan);
+ heap_close(rel, NoLock); /* close rel but keep
+ * lock! */
+
+ pfree(trig.tgargs);
break;
+ }
default:
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed");
}
}
-
/*
* ALTER TABLE OWNER
*/
/*
* first check that we are a superuser
*/
- if (! superuser() )
+ if (! superuser())
elog(ERROR, "ALTER TABLE: permission denied");
/*
* look up the new owner in pg_shadow and get the sysid
*/
tuple = SearchSysCacheTuple(SHADOWNAME, PointerGetDatum(newOwnerName),
- 0, 0, 0);
+ 0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "ALTER TABLE: user \"%s\" not found", newOwnerName);
*/
heap_freetuple(tuple);
heap_close(class_rel, RowExclusiveLock);
-
- return;
}
+
/*
* ALTER TABLE CREATE TOAST TABLE
*/
* allow to create TOAST tables for views. But why not - someone
* can insert into a view, so it shouldn't be impossible to hide
* huge data there :-)
+ *
* Not any more.
*/
if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION)
}
-static
-bool
+static bool
is_viewr(char *name)
{
Relation rel = heap_openr(name, NoLock);
return retval;
}
-static
-bool
-is_view (Relation rel)
+static bool
+is_view(Relation rel)
{
Relation RewriteRelation;
HeapScanDesc scanDesc;
ScanKeyData scanKeyData;
HeapTuple tuple;
Form_pg_rewrite data;
-
-
- bool retval = 0;
+ bool retval = false;
/*
* Open the pg_rewrite relation.
data = (Form_pg_rewrite) GETSTRUCT(tuple);
if (data->ev_type == '1')
{
- retval = 1;
+ retval = true;
break;
}
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.63 2000/08/04 06:12:11 inoue Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.64 2000/09/12 21:06:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
Var *var = (Var *) node;
- Assert(newattno != NULL);
if (var->varlevelsup == 0 && var->varno == 1)
{
/*
*/
Assert(newattno[var->varattno - 1] > 0);
var->varattno = newattno[var->varattno - 1];
- return true;
}
- else
- return false;
+ return false;
}
- return expression_tree_walker(node, change_varattnos_walker, (void *)newattno);
+ return expression_tree_walker(node, change_varattnos_walker,
+ (void *) newattno);
}
+
static bool
change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
{
- return expression_tree_walker(node, change_varattnos_walker, (void *)newattno);
+ return change_varattnos_walker(node, newattno);
}
+
/*
* MergeAttributes
* Returns new schema given initial schema and supers.
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.57 2000/06/18 22:43:58 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.58 2000/09/12 21:06:47 tgl Exp $
*
*/
appendStringInfo(str, " on %s",
stringStringInfo(rte->relname));
- if (rte->ref != NULL)
+ if (rte->alias != NULL)
{
- if ((strcmp(rte->ref->relname, rte->relname) != 0)
- || (length(rte->ref->attrs) > 0))
+ if ((strcmp(rte->alias->relname, rte->relname) != 0)
+ || (length(rte->alias->attrs) > 0))
{
appendStringInfo(str, " %s",
- stringStringInfo(rte->ref->relname));
+ stringStringInfo(rte->alias->relname));
- if (length(rte->ref->attrs) > 0)
+ if (length(rte->alias->attrs) > 0)
{
List *c;
int firstEntry = true;
appendStringInfo(str, " (");
- foreach(c, rte->ref->attrs)
+ foreach(c, rte->alias->attrs)
{
if (!firstEntry)
{
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: view.c,v 1.47 2000/09/12 04:49:07 momjian Exp $
+ * $Id: view.c,v 1.48 2000/09/12 21:06:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
MakeRetrieveViewRuleName(char *viewName)
{
char *buf;
+#ifdef MULTIBYTE
+ int len;
+#endif
buf = palloc(strlen(viewName) + 5);
snprintf(buf, strlen(viewName) + 5, "_RET%s", viewName);
#ifdef MULTIBYTE
- int len;
len = pg_mbcliplen(buf,strlen(buf),NAMEDATALEN-1);
buf[len] = '\0';
#else
* Of course we must also increase the 'varnos' of all the Var nodes
* by 2...
*
+ * These extra RT entries are not actually used in the query, obviously.
+ * We add them so that views look the same as ON SELECT rules ---
+ * the rule rewriter assumes that ALL rules have OLD and NEW RTEs.
+ *
* NOTE: these are destructive changes. It would be difficult to
* make a complete copy of the parse tree and make the changes
* in the copy.
static void
UpdateRangeTableOfViewParse(char *viewName, Query *viewParse)
{
- List *old_rt;
List *new_rt;
RangeTblEntry *rt_entry1,
*rt_entry2;
- /*
- * first offset all var nodes by 2
- */
- OffsetVarNodes((Node *) viewParse->targetList, 2, 0);
- OffsetVarNodes(viewParse->qual, 2, 0);
-
- OffsetVarNodes(viewParse->havingQual, 2, 0);
-
-
- /*
- * find the old range table...
- */
- old_rt = viewParse->rtable;
-
/*
* create the 2 new range table entries and form the new range
* table... OLD first, then NEW....
*/
- rt_entry1 = addRangeTableEntry(NULL, (char *) viewName,
+ rt_entry1 = addRangeTableEntry(NULL, viewName,
makeAttr("*OLD*", NULL),
- FALSE, FALSE, FALSE);
- rt_entry2 = addRangeTableEntry(NULL, (char *) viewName,
+ false, false);
+ rt_entry2 = addRangeTableEntry(NULL, viewName,
makeAttr("*NEW*", NULL),
- FALSE, FALSE, FALSE);
- new_rt = lcons(rt_entry2, old_rt);
- new_rt = lcons(rt_entry1, new_rt);
+ false, false);
+ new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
/*
* Now the tricky part.... Update the range table in place... Be
* careful here, or hell breaks loooooooooooooOOOOOOOOOOOOOOOOOOSE!
*/
viewParse->rtable = new_rt;
+
+ /*
+ * now offset all var nodes by 2, and jointree RT indexes too.
+ */
+ OffsetVarNodes((Node *) viewParse, 2, 0);
}
/*-------------------------------------------------------------------
viewTlist = viewParse->targetList;
/*
- * Create the "view" relation NOTE: if it already exists, the xaxt
+ * Create the "view" relation NOTE: if it already exists, the xact
* will be aborted.
*/
DefineVirtualRelation(viewName, viewTlist);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.126 2000/09/12 04:49:08 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.127 2000/09/12 21:06:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* If we have a result relation, determine whether the result rel is
* scanned or merely written. If scanned, we will insist on read
* permission as well as modify permission.
+ *
+ * Note: it might look faster to apply rangeTableEntry_used(), but
+ * that's not correct since it will trigger on jointree references
+ * to the RTE. We only want to know about actual Var nodes.
*/
if (resultRelation > 0)
{
- List *qvars = pull_varnos(parseTree->qual);
- List *tvars = pull_varnos((Node *) parseTree->targetList);
+ List *qvars = pull_varnos((Node *) parseTree);
- resultIsScanned = (intMember(resultRelation, qvars) ||
- intMember(resultRelation, tvars));
+ resultIsScanned = intMember(resultRelation, qvars);
freeList(qvars);
- freeList(tvars);
}
/*
bool isResultRelation, bool resultIsScanned)
{
char *relName;
+ Oid userid;
int32 aclcheck_result;
- Oid userid;
if (rte->skipAcl)
{
*/
RelationInfo *resultRelationInfo;
Index resultRelationIndex;
- RangeTblEntry *rtentry;
Oid resultRelationOid;
Relation resultRelationDesc;
resultRelationIndex = resultRelation;
- rtentry = rt_fetch(resultRelationIndex, rangeTable);
- resultRelationOid = rtentry->relid;
+ resultRelationOid = getrelid(resultRelationIndex, rangeTable);
resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock);
if (resultRelationDesc->rd_rel->relkind == RELKIND_SEQUENCE)
if (!(rm->info & ROW_MARK_FOR_UPDATE))
continue;
- relid = rt_fetch(rm->rti, rangeTable)->relid;
+ relid = getrelid(rm->rti, rangeTable);
relation = heap_open(relid, RowShareLock);
erm = (execRowMark *) palloc(sizeof(execRowMark));
erm->relation = relation;
rte = makeNode(RangeTblEntry);
rte->relname = RelationGetRelationName(rel);
- rte->ref = makeNode(Attr);
- rte->ref->relname = rte->relname;
rte->relid = RelationGetRelid(rel);
- /* inh, inFromCl, inJoinSet, skipAcl won't be used, leave them zero */
+ rte->eref = makeNode(Attr);
+ rte->eref->relname = rte->relname;
+ /* inh, inFromCl, skipAcl won't be used, leave them zero */
/* Set up single-entry range table */
econtext->ecxt_range_table = lcons(rte, NIL);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.38 2000/07/12 02:37:02 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.39 2000/09/12 21:06:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* type of tuple in a slot
*
* CONVENIENCE INITIALIZATION ROUTINES
- * ExecInitResultTupleSlot \ convience routines to initialize
+ * ExecInitResultTupleSlot \ convenience routines to initialize
* ExecInitScanTupleSlot \ the various tuple slots for nodes
- * ExecInitMarkedTupleSlot / which store copies of tuples.
- * ExecInitOuterTupleSlot /
- * ExecInitHashTupleSlot /
+ * ExecInitExtraTupleSlot / which store copies of tuples.
+ * ExecInitNullTupleSlot /
*
* old routines:
* ExecGetTupType - get type of tuple returned by this node
* ----------------------------------------------------------------
*/
/* --------------------------------
- * ExecInit{Result,Scan,Raw,Marked,Outer,Hash}TupleSlot
+ * ExecInit{Result,Scan,Extra}TupleSlot
*
- * These are convenience routines to initialize the specfied slot
- * in nodes inheriting the appropriate state.
+ * These are convenience routines to initialize the specified slot
+ * in nodes inheriting the appropriate state. ExecInitExtraTupleSlot
+ * is used for initializing special-purpose slots.
* --------------------------------
*/
#define INIT_SLOT_DEFS \
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
- commonstate->cs_ResultTupleSlot = (TupleTableSlot *) slot;
+ commonstate->cs_ResultTupleSlot = slot;
}
/* ----------------
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
- commonscanstate->css_ScanTupleSlot = (TupleTableSlot *) slot;
+ commonscanstate->css_ScanTupleSlot = slot;
}
-#ifdef NOT_USED
/* ----------------
- * ExecInitMarkedTupleSlot
+ * ExecInitExtraTupleSlot
* ----------------
*/
-void
-ExecInitMarkedTupleSlot(EState *estate, MergeJoinState *mergestate)
+TupleTableSlot *
+ExecInitExtraTupleSlot(EState *estate)
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
- mergestate->mj_MarkedTupleSlot = (TupleTableSlot *) slot;
+ return slot;
}
-#endif
-
/* ----------------
- * ExecInitOuterTupleSlot
+ * ExecInitNullTupleSlot
+ *
+ * Build a slot containing an all-nulls tuple of the given type.
+ * This is used as a substitute for an input tuple when performing an
+ * outer join.
* ----------------
*/
-void
-ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate)
+TupleTableSlot *
+ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
{
- INIT_SLOT_DEFS;
- INIT_SLOT_ALLOC;
- hashstate->hj_OuterTupleSlot = slot;
-}
+ TupleTableSlot* slot = ExecInitExtraTupleSlot(estate);
+ /*
+ * Since heap_getattr() will treat attributes beyond a tuple's t_natts
+ * as being NULL, we can make an all-nulls tuple just by making it be of
+ * zero length. However, the slot descriptor must match the real tupType.
+ */
+ HeapTuple nullTuple;
+ Datum values[1];
+ char nulls[1];
+ static struct tupleDesc NullTupleDesc; /* we assume this inits to
+ * zeroes */
-/* ----------------
- * ExecInitHashTupleSlot
- * ----------------
- */
-#ifdef NOT_USED
-void
-ExecInitHashTupleSlot(EState *estate, HashJoinState *hashstate)
-{
- INIT_SLOT_DEFS;
- INIT_SLOT_ALLOC;
- hashstate->hj_HashTupleSlot = slot;
+ ExecSetSlotDescriptor(slot, tupType);
+
+ nullTuple = heap_formtuple(&NullTupleDesc, values, nulls);
+
+ return ExecStoreTuple(nullTuple, slot, InvalidBuffer, true);
}
-#endif
static TupleTableSlot *
NodeGetResultTupleSlot(Plan *node)
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.65 2000/08/22 04:06:19 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.66 2000/09/12 21:06:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
ExecAssignResultTypeFromTL(Plan *node, CommonState *commonstate)
{
List *targetList;
- int i;
+ TupleDesc tupDesc;
int len;
- List *tl;
- TargetEntry *tle;
- List *fjtl;
- TupleDesc origTupDesc;
targetList = node->targetlist;
- origTupDesc = ExecTypeFromTL(targetList);
+ tupDesc = ExecTypeFromTL(targetList);
len = ExecTargetListLength(targetList);
- fjtl = NIL;
- tl = targetList;
- i = 0;
- while (tl != NIL || fjtl != NIL)
- {
- if (fjtl != NIL)
- {
- tle = lfirst(fjtl);
- fjtl = lnext(fjtl);
- }
- else
- {
- tle = lfirst(tl);
- tl = lnext(tl);
- }
-#ifdef SETS_FIXED
- if (!tl_is_resdom(tle))
- {
- Fjoin *fj = (Fjoin *) lfirst(tle);
-
- /* it is a FJoin */
- fjtl = lnext(tle);
- tle = fj->fj_innerNode;
- }
-#endif
- i++;
- }
-
if (len > 0)
- {
- ExecAssignResultType(commonstate,
- origTupDesc);
- }
+ ExecAssignResultType(commonstate, tupDesc);
else
- ExecAssignResultType(commonstate,
- (TupleDesc) NULL);
+ ExecAssignResultType(commonstate, (TupleDesc) NULL);
}
/* ----------------
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.33 2000/08/24 03:29:03 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.34 2000/09/12 21:06:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Hash *hashNode;
List *hjclauses;
Expr *clause;
- List *qual;
+ List *joinqual;
+ List *otherqual;
ScanDirection dir;
TupleTableSlot *inntuple;
Node *outerVar;
hjstate = node->hashjoinstate;
hjclauses = node->hashclauses;
clause = lfirst(hjclauses);
- estate = node->join.state;
- qual = node->join.qual;
+ estate = node->join.plan.state;
+ joinqual = node->join.joinqual;
+ otherqual = node->join.plan.qual;
hashNode = (Hash *) innerPlan(node);
outerNode = outerPlan(node);
- hashPhaseDone = node->hashdone;
+ hashPhaseDone = hjstate->hj_hashdone;
dir = estate->es_direction;
/* -----------------
hashNode->hashstate->hashtable = hashtable;
innerTupleSlot = ExecProcNode((Plan *) hashNode, (Plan *) node);
}
- node->hashdone = true;
+ hjstate->hj_hashdone = true;
/* ----------------
* Open temp files for outer batches, if needed.
* Note that file buffers are palloc'd in regular executor context.
for (;;)
{
-
/*
- * if the current outer tuple is nil, get a new one
+ * If we don't have an outer tuple, get the next one
*/
- if (TupIsNull(outerTupleSlot))
+ if (hjstate->hj_NeedNewOuter)
{
outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode,
(Plan *) node,
return NULL;
}
+ hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ hjstate->hj_NeedNewOuter = false;
+ hjstate->hj_MatchedOuter = false;
+
/*
* now we have an outer tuple, find the corresponding bucket
* for this tuple from the hash table
*/
- econtext->ecxt_outertuple = outerTupleSlot;
hjstate->hj_CurBucketNo = ExecHashGetBucket(hashtable, econtext,
outerVar);
hjstate->hj_CurTuple = NULL;
hashtable->outerBatchSize[batchno]++;
ExecHashJoinSaveTuple(outerTupleSlot->val,
hashtable->outerBatchFile[batchno]);
- ExecClearTuple(outerTupleSlot);
+ hjstate->hj_NeedNewOuter = true;
continue; /* loop around for a new outer tuple */
}
}
break; /* out of matches */
/*
- * we've got a match, but still need to test qpqual
+ * we've got a match, but still need to test non-hashed quals
*/
inntuple = ExecStoreTuple(curtuple,
hjstate->hj_HashTupleSlot,
false); /* don't pfree this tuple */
econtext->ecxt_innertuple = inntuple;
- /* reset temp memory each time to avoid leaks from qpqual */
+ /* reset temp memory each time to avoid leaks from qual expr */
ResetExprContext(econtext);
/* ----------------
* if we pass the qual, then save state for next call and
* have ExecProject form the projection, store it
* in the tuple table, and return the slot.
+ *
+ * Only the joinquals determine MatchedOuter status,
+ * but all quals must pass to actually return the tuple.
* ----------------
*/
- if (ExecQual(qual, econtext, false))
+ if (ExecQual(joinqual, econtext, false))
{
- TupleTableSlot *result;
+ hjstate->hj_MatchedOuter = true;
- hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
- result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
- if (isDone != ExprEndResult)
+ if (otherqual == NIL || ExecQual(otherqual, econtext, false))
{
- hjstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
- return result;
+ TupleTableSlot *result;
+
+ result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ hjstate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
}
}
}
/* ----------------
* Now the current outer tuple has run out of matches,
- * so we free it and loop around to get a new outer tuple.
+ * so check whether to emit a dummy outer-join tuple.
+ * If not, loop around to get a new outer tuple.
* ----------------
*/
- ExecClearTuple(outerTupleSlot);
+ hjstate->hj_NeedNewOuter = true;
+
+ if (! hjstate->hj_MatchedOuter &&
+ node->join.jointype == JOIN_LEFT)
+ {
+ /*
+ * We are doing an outer join and there were no join matches
+ * for this outer tuple. Generate a fake join tuple with
+ * nulls for the inner tuple, and return it if it passes
+ * the non-join quals.
+ */
+ econtext->ecxt_innertuple = hjstate->hj_NullInnerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification was satisfied so we project and
+ * return the slot containing the result tuple
+ * using ExecProject().
+ * ----------------
+ */
+ TupleTableSlot *result;
+
+ result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ hjstate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
}
}
* assign the node's execution state
* ----------------
*/
- node->join.state = estate;
+ node->join.plan.state = estate;
/* ----------------
* create state structure
* ----------------
*/
hjstate = makeNode(HashJoinState);
-
node->hashjoinstate = hjstate;
/* ----------------
*/
ExecAssignExprContext(estate, &hjstate->jstate);
-#define HASHJOIN_NSLOTS 2
- /* ----------------
- * tuple table initialization
- * ----------------
- */
- ExecInitResultTupleSlot(estate, &hjstate->jstate);
- ExecInitOuterTupleSlot(estate, hjstate);
-
/* ----------------
* initializes child nodes
* ----------------
ExecInitNode(outerNode, estate, (Plan *) node);
ExecInitNode((Plan *) hashNode, estate, (Plan *) node);
+#define HASHJOIN_NSLOTS 3
+ /* ----------------
+ * tuple table initialization
+ * ----------------
+ */
+ ExecInitResultTupleSlot(estate, &hjstate->jstate);
+ hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate);
+
+ switch (node->join.jointype)
+ {
+ case JOIN_INNER:
+ break;
+ case JOIN_LEFT:
+ hjstate->hj_NullInnerTupleSlot =
+ ExecInitNullTupleSlot(estate,
+ ExecGetTupType((Plan *) hashNode));
+ break;
+ default:
+ elog(ERROR, "ExecInitHashJoin: unsupported join type %d",
+ (int) node->join.jointype);
+ }
+
/* ----------------
* now for some voodoo. our temporary tuple slot
* is actually the result tuple slot of the Hash node
hjstate->hj_HashTupleSlot = slot;
}
- hjstate->hj_OuterTupleSlot->ttc_tupleDescriptor = ExecGetTupType(outerNode);
-
-/*
- hjstate->hj_OuterTupleSlot->ttc_execTupDescriptor = ExecGetExecTupDesc(outerNode);
-*/
/* ----------------
* initialize tuple type and projection info
ExecAssignResultTypeFromTL((Plan *) node, &hjstate->jstate);
ExecAssignProjectionInfo((Plan *) node, &hjstate->jstate);
+ ExecSetSlotDescriptor(hjstate->hj_OuterTupleSlot,
+ ExecGetTupType(outerNode));
+
/* ----------------
* initialize hash-specific info
* ----------------
*/
- node->hashdone = false;
+ hjstate->hj_hashdone = false;
hjstate->hj_HashTable = (HashJoinTable) NULL;
hjstate->hj_CurBucketNo = 0;
hjstate->hj_CurTuple = (HashJoinTuple) NULL;
hjstate->hj_InnerHashKey = (Node *) NULL;
- hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
+ hjstate->jstate.cs_OuterTupleSlot = NULL;
hjstate->jstate.cs_TupFromTlist = false;
+ hjstate->hj_NeedNewOuter = true;
+ hjstate->hj_MatchedOuter = false;
return TRUE;
}
{
HashJoinState *hjstate = node->hashjoinstate;
- if (!node->hashdone)
+ if (!hjstate->hj_hashdone)
return;
- node->hashdone = false;
+ hjstate->hj_hashdone = false;
/*
* Unfortunately, currently we have to destroy hashtable in all
hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
hjstate->jstate.cs_TupFromTlist = false;
+ hjstate->hj_NeedNewOuter = true;
+ hjstate->hj_MatchedOuter = false;
/*
* if chgParam of subnodes is not null then plans will be re-scanned
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.37 2000/08/24 03:29:03 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.38 2000/09/12 21:06:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* INTERFACE ROUTINES
* ExecMergeJoin mergejoin outer and inner relations.
* ExecInitMergeJoin creates and initializes run time states
- * ExecEndMergeJoin cleand up the node.
+ * ExecEndMergeJoin cleans up the node.
*
* NOTES
* Essential operation of the merge join algorithm is as follows:
- * (** indicates the tuples satisfy the merge clause).
*
* Join { -
* get initial outer and inner tuples INITIALIZE
* } -
* } -
*
- * Skip Outer { SKIPOUTER
+ * Skip Outer { SKIPOUTER_BEGIN
* if (inner == outer) Join Tuples JOINTUPLES
- * while (outer < inner) SKIPOUTER
- * advance outer SKIPOUTER
- * if (outer > inner) SKIPOUTER
+ * while (outer < inner) SKIPOUTER_TEST
+ * advance outer SKIPOUTER_ADVANCE
+ * if (outer > inner) SKIPOUTER_TEST
* Skip Inner SKIPINNER
* } -
*
- * Skip Inner { SKIPINNER
+ * Skip Inner { SKIPINNER_BEGIN
* if (inner == outer) Join Tuples JOINTUPLES
- * while (outer > inner) SKIPINNER
- * advance inner SKIPINNER
- * if (outer < inner) SKIPINNER
+ * while (outer > inner) SKIPINNER_TEST
+ * advance inner SKIPINNER_ADVANCE
+ * if (outer < inner) SKIPINNER_TEST
* Skip Outer SKIPOUTER
* } -
*
#include "postgres.h"
#include "access/heapam.h"
+#include "access/printtup.h"
#include "catalog/pg_operator.h"
#include "executor/execdebug.h"
#include "executor/execdefs.h"
* ----------------------------------------------------------------
*/
#ifdef EXEC_MERGEJOINDEBUG
-void
- ExecMergeTupleDumpInner(ExprContext *econtext);
-void
-ExecMergeTupleDumpInner(ExprContext *econtext)
+static void
+ExecMergeTupleDumpOuter(MergeJoinState *mergestate)
{
- TupleTableSlot *innerSlot;
+ TupleTableSlot *outerSlot = mergestate->mj_OuterTupleSlot;
- printf("==== inner tuple ====\n");
- innerSlot = econtext->ecxt_innertuple;
- if (TupIsNull(innerSlot))
+ printf("==== outer tuple ====\n");
+ if (TupIsNull(outerSlot))
printf("(nil)\n");
else
- MJ_debugtup(innerSlot->val,
- innerSlot->ttc_tupleDescriptor);
+ MJ_debugtup(outerSlot->val,
+ outerSlot->ttc_tupleDescriptor);
}
-void
- ExecMergeTupleDumpOuter(ExprContext *econtext);
-
-void
-ExecMergeTupleDumpOuter(ExprContext *econtext)
+static void
+ExecMergeTupleDumpInner(MergeJoinState *mergestate)
{
- TupleTableSlot *outerSlot;
+ TupleTableSlot *innerSlot = mergestate->mj_InnerTupleSlot;
- printf("==== outer tuple ====\n");
- outerSlot = econtext->ecxt_outertuple;
- if (TupIsNull(outerSlot))
+ printf("==== inner tuple ====\n");
+ if (TupIsNull(innerSlot))
printf("(nil)\n");
else
- MJ_debugtup(outerSlot->val,
- outerSlot->ttc_tupleDescriptor);
+ MJ_debugtup(innerSlot->val,
+ innerSlot->ttc_tupleDescriptor);
}
-void ExecMergeTupleDumpMarked(ExprContext *econtext,
- MergeJoinState *mergestate);
-
-void
-ExecMergeTupleDumpMarked(ExprContext *econtext,
- MergeJoinState *mergestate)
+static void
+ExecMergeTupleDumpMarked(MergeJoinState *mergestate)
{
- TupleTableSlot *markedSlot;
+ TupleTableSlot *markedSlot = mergestate->mj_MarkedTupleSlot;
printf("==== marked tuple ====\n");
- markedSlot = mergestate->mj_MarkedTupleSlot;
-
if (TupIsNull(markedSlot))
printf("(nil)\n");
else
markedSlot->ttc_tupleDescriptor);
}
-void
- ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate);
-
-void
-ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate)
+static void
+ExecMergeTupleDump(MergeJoinState *mergestate)
{
printf("******** ExecMergeTupleDump ********\n");
- ExecMergeTupleDumpInner(econtext);
- ExecMergeTupleDumpOuter(econtext);
- ExecMergeTupleDumpMarked(econtext, mergestate);
+ ExecMergeTupleDumpOuter(mergestate);
+ ExecMergeTupleDumpInner(mergestate);
+ ExecMergeTupleDumpMarked(mergestate);
printf("******** \n");
}
List *innerSkipQual;
List *outerSkipQual;
List *mergeclauses;
- List *qual;
+ List *joinqual;
+ List *otherqual;
bool qualResult;
bool compareResult;
Plan *innerPlan;
Plan *outerPlan;
TupleTableSlot *outerTupleSlot;
ExprContext *econtext;
-#ifdef ENABLE_OUTER_JOINS
- /*
- * These should be set from the expression context! - thomas
- * 1999-02-20
- */
- static bool isLeftJoin = true;
- static bool isRightJoin = false;
-#endif
+ bool doFillOuter;
+ bool doFillInner;
/* ----------------
* get information from node
* ----------------
*/
mergestate = node->mergestate;
- estate = node->join.state;
+ estate = node->join.plan.state;
direction = estate->es_direction;
innerPlan = innerPlan((Plan *) node);
outerPlan = outerPlan((Plan *) node);
econtext = mergestate->jstate.cs_ExprContext;
mergeclauses = node->mergeclauses;
- qual = node->join.qual;
+ joinqual = node->join.joinqual;
+ otherqual = node->join.plan.qual;
+
+ switch (node->join.jointype)
+ {
+ case JOIN_INNER:
+ doFillOuter = false;
+ doFillInner = false;
+ break;
+ case JOIN_LEFT:
+ doFillOuter = true;
+ doFillInner = false;
+ break;
+ case JOIN_FULL:
+ doFillOuter = true;
+ doFillInner = true;
+ break;
+ case JOIN_RIGHT:
+ doFillOuter = false;
+ doFillInner = true;
+ break;
+ default:
+ elog(ERROR, "ExecMergeJoin: unsupported join type %d",
+ (int) node->join.jointype);
+ doFillOuter = false; /* keep compiler quiet */
+ doFillInner = false;
+ break;
+ }
if (ScanDirectionIsForward(direction))
{
* improved readability.
* ----------------
*/
- MJ_dump(econtext, mergestate);
+ MJ_dump(mergestate);
switch (mergestate->mj_JoinState)
{
/*
* EXEC_MJ_INITIALIZE means that this is the first time
* ExecMergeJoin() has been called and so we have to
- * initialize the inner, outer and marked tuples as well
- * as various stuff in the expression context.
+ * fetch the first tuple for both outer and inner subplans.
+ * If we fail to get a tuple here, then that subplan is
+ * empty, and we either end the join or go to one of the
+ * fill-remaining-tuples states.
*/
case EXEC_MJ_INITIALIZE:
MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE\n");
- /*
- * Note: at this point, if either of our inner or outer
- * tuples are nil, then the join ends immediately because
- * we know one of the subplans is empty.
- */
- innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
- if (TupIsNull(innerTupleSlot))
+ outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+ mergestate->mj_OuterTupleSlot = outerTupleSlot;
+ if (TupIsNull(outerTupleSlot))
{
- MJ_printf("ExecMergeJoin: **** inner tuple is nil ****\n");
+ MJ_printf("ExecMergeJoin: outer subplan is empty\n");
+ if (doFillInner)
+ {
+ /*
+ * Need to emit right-join tuples for remaining
+ * inner tuples. We set MatchedInner = true to
+ * force the ENDOUTER state to advance inner.
+ */
+ mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+ mergestate->mj_MatchedInner = true;
+ break;
+ }
+ /* Otherwise we're done. */
return NULL;
}
- outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
- if (TupIsNull(outerTupleSlot))
+ innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+ mergestate->mj_InnerTupleSlot = innerTupleSlot;
+ if (TupIsNull(innerTupleSlot))
{
- MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n");
+ MJ_printf("ExecMergeJoin: inner subplan is empty\n");
+ if (doFillOuter)
+ {
+ /*
+ * Need to emit left-join tuples for remaining
+ * outer tuples. We set MatchedOuter = true to
+ * force the ENDINNER state to advance outer.
+ */
+ mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
+ mergestate->mj_MatchedOuter = true;
+ break;
+ }
+ /* Otherwise we're done. */
return NULL;
}
/* ----------------
- * store the inner and outer tuple in the merge state
+ * OK, we have the initial tuples. Begin by skipping
+ * unmatched inner tuples.
* ----------------
*/
- econtext->ecxt_innertuple = innerTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
-
- mergestate->mj_MarkedTupleSlot->ttc_tupleDescriptor =
- innerTupleSlot->ttc_tupleDescriptor;
-
- /* ----------------
- * initialize merge join state to skip inner tuples.
- * ----------------
- */
- mergestate->mj_JoinState = EXEC_MJ_SKIPINNER;
+ mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
break;
/*
*/
case EXEC_MJ_JOINMARK:
MJ_printf("ExecMergeJoin: EXEC_MJ_JOINMARK\n");
+
ExecMarkPos(innerPlan);
- MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+ MarkInnerTuple(mergestate->mj_InnerTupleSlot, mergestate);
mergestate->mj_JoinState = EXEC_MJ_JOINTEST;
break;
ResetExprContext(econtext);
- qualResult = ExecQual((List *) mergeclauses, econtext, false);
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ qualResult = ExecQual(mergeclauses, econtext, false);
MJ_DEBUG_QUAL(mergeclauses, qualResult);
if (qualResult)
*/
case EXEC_MJ_JOINTUPLES:
MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");
+
mergestate->mj_JoinState = EXEC_MJ_NEXTINNER;
/*
- * Check the qpqual to see if we actually want to return
- * this join tuple. If not, can proceed with merge.
+ * Check the extra qual conditions to see if we actually
+ * want to return this join tuple. If not, can proceed with
+ * merge. We must distinguish the additional joinquals
+ * (which must pass to consider the tuples "matched" for
+ * outer-join logic) from the otherquals (which must pass
+ * before we actually return the tuple).
*
- * (We don't bother with a ResetExprContext here, on the
+ * We don't bother with a ResetExprContext here, on the
* assumption that we just did one before checking the merge
- * qual. One per tuple should be sufficient.)
+ * qual. One per tuple should be sufficient. Also, the
+ * econtext's tuple pointers were set up before checking
+ * the merge qual, so we needn't do it again.
*/
- qualResult = ExecQual((List *) qual, econtext, false);
- MJ_DEBUG_QUAL(qual, qualResult);
+ qualResult = (joinqual == NIL ||
+ ExecQual(joinqual, econtext, false));
+ MJ_DEBUG_QUAL(joinqual, qualResult);
if (qualResult)
{
- /* ----------------
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing it.
- * ----------------
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
+ mergestate->mj_MatchedOuter = true;
+ mergestate->mj_MatchedInner = true;
- MJ_printf("ExecMergeJoin: **** returning tuple ****\n");
+ qualResult = (otherqual == NIL ||
+ ExecQual(otherqual, econtext, false));
+ MJ_DEBUG_QUAL(otherqual, qualResult);
- result = ExecProject(mergestate->jstate.cs_ProjInfo,
- &isDone);
-
- if (isDone != ExprEndResult)
+ if (qualResult)
{
- mergestate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
- return result;
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
}
}
break;
* EXEC_MJ_NEXTINNER means advance the inner scan to the
* next tuple. If the tuple is not nil, we then proceed to
* test it against the join qualification.
+ *
+ * Before advancing, we check to see if we must emit an
+ * outer-join fill tuple for this inner tuple.
*/
case EXEC_MJ_NEXTINNER:
MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTINNER\n");
+ if (doFillInner && !mergestate->mj_MatchedInner)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the outer
+ * tuple, and return it if it passes the non-join quals.
+ */
+ mergestate->mj_MatchedInner = true; /* do it only once */
+
+ ResetExprContext(econtext);
+
+ outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
+
/* ----------------
* now we get the next inner tuple, if any
* ----------------
*/
innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+ mergestate->mj_InnerTupleSlot = innerTupleSlot;
MJ_DEBUG_PROC_NODE(innerTupleSlot);
- econtext->ecxt_innertuple = innerTupleSlot;
+ mergestate->mj_MatchedInner = false;
if (TupIsNull(innerTupleSlot))
mergestate->mj_JoinState = EXEC_MJ_NEXTOUTER;
* so get a new outer tuple and then
* proceed to test it against the marked tuple
* (EXEC_MJ_TESTOUTER)
+ *
+ * Before advancing, we check to see if we must emit an
+ * outer-join fill tuple for this outer tuple.
*------------------------------------------------
*/
case EXEC_MJ_NEXTOUTER:
MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTOUTER\n");
+ if (doFillOuter && !mergestate->mj_MatchedOuter)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the inner
+ * tuple, and return it if it passes the non-join quals.
+ */
+ mergestate->mj_MatchedOuter = true; /* do it only once */
+
+ ResetExprContext(econtext);
+
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
+
+ /* ----------------
+ * now we get the next outer tuple, if any
+ * ----------------
+ */
outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+ mergestate->mj_OuterTupleSlot = outerTupleSlot;
MJ_DEBUG_PROC_NODE(outerTupleSlot);
- econtext->ecxt_outertuple = outerTupleSlot;
+ mergestate->mj_MatchedOuter = false;
/* ----------------
- * if the outer tuple is null then we know
- * we are done with the join
+ * if the outer tuple is null then we are done with the
+ * join, unless we have inner tuples we need to null-fill.
* ----------------
*/
if (TupIsNull(outerTupleSlot))
{
- MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n");
+ MJ_printf("ExecMergeJoin: end of outer subplan\n");
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ if (doFillInner && !TupIsNull(innerTupleSlot))
+ {
+ /*
+ * Need to emit right-join tuples for remaining
+ * inner tuples.
+ */
+ mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+ break;
+ }
+ /* Otherwise we're done. */
return NULL;
}
/* ----------------
* here we compare the outer tuple with the marked inner tuple
- * by using the marked tuple in place of the inner tuple.
* ----------------
*/
- innerTupleSlot = econtext->ecxt_innertuple;
- econtext->ecxt_innertuple = mergestate->mj_MarkedTupleSlot;
-
ResetExprContext(econtext);
- qualResult = ExecQual((List *) mergeclauses, econtext, false);
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_MarkedTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ qualResult = ExecQual(mergeclauses, econtext, false);
MJ_DEBUG_QUAL(mergeclauses, qualResult);
if (qualResult)
{
/*
- * the merge clause matched so now we juggle the slots
- * back the way they were and proceed to JOINTEST.
+ * the merge clause matched so now we restore the inner
+ * scan position to the first mark, and loop back to
+ * JOINTEST. Actually, since we know the mergeclause
+ * matches, we can skip JOINTEST and go straight to
+ * JOINTUPLES.
*
- * I can't understand why we have to go to JOINTEST and
- * compare outer tuple with the same inner one again
- * -> go to JOINTUPLES... - vadim 02/27/98
+ * NOTE: we do not need to worry about the MatchedInner
+ * state for the rescanned inner tuples. We know all
+ * of them will match this new outer tuple and therefore
+ * won't be emitted as fill tuples. This works *only*
+ * because we require the extra joinquals to be nil when
+ * doing a right or full join --- otherwise some of the
+ * rescanned tuples might fail the extra joinquals.
*/
-
ExecRestrPos(innerPlan);
mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
}
else
{
- econtext->ecxt_innertuple = innerTupleSlot;
/* ----------------
* if the inner tuple was nil and the new outer
* tuple didn't match the marked outer tuple then
- * we may have the case:
+ * we have the case:
*
* outer inner
* 4 4 - marked tuple
* 7
*
* which means that all subsequent outer tuples will be
- * larger than our inner tuples.
+ * larger than our marked inner tuples. So we're done.
* ----------------
*/
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
if (TupIsNull(innerTupleSlot))
{
-#ifdef ENABLE_OUTER_JOINS
- if (isLeftJoin)
+ if (doFillOuter)
{
- /* continue on to null fill outer tuples */
- mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
+ /*
+ * Need to emit left-join tuples for remaining
+ * outer tuples.
+ */
+ mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
break;
}
-#endif
- MJ_printf("ExecMergeJoin: **** weird case 1 ****\n");
+ /* Otherwise we're done. */
return NULL;
}
/* continue on to skip outer tuples */
- mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER;
+ mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
}
break;
/*----------------------------------------------------------
* EXEC_MJ_SKIPOUTER means skip over tuples in the outer plan
- * until we find an outer tuple > current inner tuple.
+ * until we find an outer tuple >= current inner tuple.
*
* For example:
*
*
* we have to advance the outer scan
* until we find the outer 8.
+ *
+ * To avoid redundant tests, we divide this into three
+ * sub-states: BEGIN, TEST, ADVANCE.
*----------------------------------------------------------
*/
- case EXEC_MJ_SKIPOUTER:
- MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER\n");
+ case EXEC_MJ_SKIPOUTER_BEGIN:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_BEGIN\n");
+
/* ----------------
* before we advance, make sure the current tuples
* do not satisfy the mergeclauses. If they do, then
*/
ResetExprContext(econtext);
- qualResult = ExecQual((List *) mergeclauses, econtext, false);
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ qualResult = ExecQual(mergeclauses, econtext, false);
MJ_DEBUG_QUAL(mergeclauses, qualResult);
if (qualResult)
{
ExecMarkPos(innerPlan);
- MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+ MarkInnerTuple(innerTupleSlot, mergestate);
mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
break;
}
+ mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
+ break;
+
+ case EXEC_MJ_SKIPOUTER_TEST:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_TEST\n");
+
/* ----------------
* ok, now test the skip qualification
* ----------------
*/
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
compareResult = MergeCompare(mergeclauses,
outerSkipQual,
econtext);
/* ----------------
* compareResult is true as long as we should
- * continue skipping tuples.
+ * continue skipping outer tuples.
* ----------------
*/
if (compareResult)
{
-#ifdef ENABLE_OUTER_JOINS
- /* ----------------
- * if this is a left or full outer join, then fill
- * ----------------
- */
- if (isLeftJoin)
- {
- mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
- break;
- }
-#endif
-
- outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
- MJ_DEBUG_PROC_NODE(outerTupleSlot);
- econtext->ecxt_outertuple = outerTupleSlot;
-
- /* ----------------
- * if the outer tuple is null then we know
- * we are done with the join
- * ----------------
- */
- if (TupIsNull(outerTupleSlot))
- {
- MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n");
- return NULL;
- }
- /* ----------------
- * otherwise test the new tuple against the skip qual.
- * (we remain in the EXEC_MJ_SKIPOUTER state)
- * ----------------
- */
+ mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
break;
}
MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult);
if (compareResult)
- mergestate->mj_JoinState = EXEC_MJ_SKIPINNER;
+ mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
else
mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
break;
+ /*------------------------------------------------
+ * Before advancing, we check to see if we must emit an
+ * outer-join fill tuple for this outer tuple.
+ *------------------------------------------------
+ */
+ case EXEC_MJ_SKIPOUTER_ADVANCE:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_ADVANCE\n");
+
+ if (doFillOuter && !mergestate->mj_MatchedOuter)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the inner
+ * tuple, and return it if it passes the non-join quals.
+ */
+ mergestate->mj_MatchedOuter = true; /* do it only once */
+
+ ResetExprContext(econtext);
+
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
+
+ /* ----------------
+ * now we get the next outer tuple, if any
+ * ----------------
+ */
+ outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+ mergestate->mj_OuterTupleSlot = outerTupleSlot;
+ MJ_DEBUG_PROC_NODE(outerTupleSlot);
+ mergestate->mj_MatchedOuter = false;
+
+ /* ----------------
+ * if the outer tuple is null then we are done with the
+ * join, unless we have inner tuples we need to null-fill.
+ * ----------------
+ */
+ if (TupIsNull(outerTupleSlot))
+ {
+ MJ_printf("ExecMergeJoin: end of outer subplan\n");
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ if (doFillInner && !TupIsNull(innerTupleSlot))
+ {
+ /*
+ * Need to emit right-join tuples for remaining
+ * inner tuples.
+ */
+ mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
+ }
+
+ /* ----------------
+ * otherwise test the new tuple against the skip qual.
+ * ----------------
+ */
+ mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
+ break;
+
/*-----------------------------------------------------------
* EXEC_MJ_SKIPINNER means skip over tuples in the inner plan
- * until we find an inner tuple > current outer tuple.
+ * until we find an inner tuple >= current outer tuple.
*
* For example:
*
* we have to advance the inner scan
* until we find the inner 12.
*
+ * To avoid redundant tests, we divide this into three
+ * sub-states: BEGIN, TEST, ADVANCE.
*-------------------------------------------------------
*/
- case EXEC_MJ_SKIPINNER:
- MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER\n");
+ case EXEC_MJ_SKIPINNER_BEGIN:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_BEGIN\n");
+
/* ----------------
* before we advance, make sure the current tuples
* do not satisfy the mergeclauses. If they do, then
*/
ResetExprContext(econtext);
- qualResult = ExecQual((List *) mergeclauses, econtext, false);
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ qualResult = ExecQual(mergeclauses, econtext, false);
MJ_DEBUG_QUAL(mergeclauses, qualResult);
if (qualResult)
{
ExecMarkPos(innerPlan);
- MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+ MarkInnerTuple(innerTupleSlot, mergestate);
mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
break;
}
+ mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
+ break;
+
+ case EXEC_MJ_SKIPINNER_TEST:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_TEST\n");
+
/* ----------------
* ok, now test the skip qualification
* ----------------
*/
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
compareResult = MergeCompare(mergeclauses,
innerSkipQual,
econtext);
/* ----------------
* compareResult is true as long as we should
- * continue skipping tuples.
+ * continue skipping inner tuples.
* ----------------
*/
if (compareResult)
{
-#ifdef ENABLE_OUTER_JOINS
- /* ----------------
- * if this is a right or full outer join, then fill
- * ----------------
- */
- if (isRightJoin)
- {
- mergestate->mj_JoinState = EXEC_MJ_FILLINNER;
- break;
- }
-#endif
-
- /* ----------------
- * now try and get a new inner tuple
- * ----------------
- */
- innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
- MJ_DEBUG_PROC_NODE(innerTupleSlot);
- econtext->ecxt_innertuple = innerTupleSlot;
-
- /* ----------------
- * if the inner tuple is null then we know
- * we have to restore the inner scan
- * and advance to the next outer tuple
- * ----------------
- */
- if (TupIsNull(innerTupleSlot))
- {
- /* ----------------
- * this is an interesting case.. all our
- * inner tuples are smaller then our outer
- * tuples so we never found an inner tuple
- * to mark.
- *
- * outer inner
- * outer tuple - 5 4
- * 5 4
- * 6 nil - inner tuple
- * 7
- *
- * This means the join should end.
- * ----------------
- */
- MJ_printf("ExecMergeJoin: **** weird case 2 ****\n");
- return NULL;
- }
-
- /* ----------------
- * otherwise test the new tuple against the skip qual.
- * (we remain in the EXEC_MJ_SKIPINNER state)
- * ----------------
- */
+ mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
break;
}
/* ----------------
- * compare finally failed and we have stopped skipping
- * inner tuples so now check the outer skip qual
- * to see if we should now skip outer tuples...
+ * now check the outer skip qual to see if we
+ * should now skip outer tuples... if we fail the
+ * outer skip qual, then we know we have a new pair
+ * of matching tuples.
* ----------------
*/
compareResult = MergeCompare(mergeclauses,
MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult);
if (compareResult)
- mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER;
+ mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
else
mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
-
break;
-#ifdef ENABLE_OUTER_JOINS
-
- /*
- * EXEC_MJ_FILLINNER means we have an unmatched inner
- * tuple which must be null-expanded into the projection
- * tuple. get the next inner tuple and reset markers
- * (EXEC_MJ_JOINMARK).
+ /*------------------------------------------------
+ * Before advancing, we check to see if we must emit an
+ * outer-join fill tuple for this inner tuple.
+ *------------------------------------------------
*/
- case EXEC_MJ_FILLINNER:
- MJ_printf("ExecMergeJoin: EXEC_MJ_FILLINNER\n");
- mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
+ case EXEC_MJ_SKIPINNER_ADVANCE:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_ADVANCE\n");
- /* ----------------
- * project the inner tuple into the result
- * ----------------
- */
- MJ_printf("ExecMergeJoin: project inner tuple into the result (not yet implemented)\n");
+ if (doFillInner && !mergestate->mj_MatchedInner)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the outer
+ * tuple, and return it if it passes the non-join quals.
+ */
+ mergestate->mj_MatchedInner = true; /* do it only once */
+
+ ResetExprContext(econtext);
+
+ outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
/* ----------------
- * now skip this inner tuple
+ * now we get the next inner tuple, if any
* ----------------
*/
innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+ mergestate->mj_InnerTupleSlot = innerTupleSlot;
MJ_DEBUG_PROC_NODE(innerTupleSlot);
- econtext->ecxt_innertuple = innerTupleSlot;
+ mergestate->mj_MatchedInner = false;
/* ----------------
- * if the inner tuple is null then we know
- * we have to restore the inner scan
- * and advance to the next outer tuple
+ * if the inner tuple is null then we are done with the
+ * join, unless we have outer tuples we need to null-fill.
* ----------------
*/
if (TupIsNull(innerTupleSlot))
{
- if (isLeftJoin && !TupIsNull(outerTupleSlot))
+ MJ_printf("ExecMergeJoin: end of inner subplan\n");
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ if (doFillOuter && !TupIsNull(outerTupleSlot))
{
- mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
- MJ_printf("ExecMergeJoin: try to complete outer fill\n");
+ /*
+ * Need to emit left-join tuples for remaining
+ * outer tuples.
+ */
+ mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
break;
}
-
- MJ_printf("ExecMergeJoin: **** weird case 2 ****\n");
+ /* Otherwise we're done. */
return NULL;
}
/* ----------------
* otherwise test the new tuple against the skip qual.
- * (we move to the EXEC_MJ_JOINMARK state)
* ----------------
*/
+ mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
break;
/*
- * EXEC_MJ_FILLOUTER means we have an unmatched outer
- * tuple which must be null-expanded into the projection
- * tuple. get the next outer tuple and reset markers
- * (EXEC_MJ_JOINMARK).
+ * EXEC_MJ_ENDOUTER means we have run out of outer tuples,
+ * but are doing a right/full join and therefore must null-
+ * fill any remaing unmatched inner tuples.
*/
- case EXEC_MJ_FILLOUTER:
- MJ_printf("ExecMergeJoin: EXEC_MJ_FILLOUTER\n");
- mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
+ case EXEC_MJ_ENDOUTER:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");
+
+ Assert(doFillInner);
+
+ if (!mergestate->mj_MatchedInner)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the outer
+ * tuple, and return it if it passes the non-join quals.
+ */
+ mergestate->mj_MatchedInner = true; /* do it only once */
+
+ ResetExprContext(econtext);
+
+ outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_InnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
/* ----------------
- * project the outer tuple into the result
+ * now we get the next inner tuple, if any
* ----------------
*/
- MJ_printf("ExecMergeJoin: project outer tuple into the result (not yet implemented)\n");
+ innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+ mergestate->mj_InnerTupleSlot = innerTupleSlot;
+ MJ_DEBUG_PROC_NODE(innerTupleSlot);
+ mergestate->mj_MatchedInner = false;
+
+ if (TupIsNull(innerTupleSlot))
+ {
+ MJ_printf("ExecMergeJoin: end of inner subplan\n");
+ return NULL;
+ }
+
+ /* Else remain in ENDOUTER state and process next tuple. */
+ break;
+
+ /*
+ * EXEC_MJ_ENDINNER means we have run out of inner tuples,
+ * but are doing a left/full join and therefore must null-
+ * fill any remaing unmatched outer tuples.
+ */
+ case EXEC_MJ_ENDINNER:
+ MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n");
+
+ Assert(doFillOuter);
+
+ if (!mergestate->mj_MatchedOuter)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the inner
+ * tuple, and return it if it passes the non-join quals.
+ */
+ mergestate->mj_MatchedOuter = true; /* do it only once */
+
+ ResetExprContext(econtext);
+
+ outerTupleSlot = mergestate->mj_OuterTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+ econtext->ecxt_innertuple = innerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification succeeded. now form the desired
+ * projection tuple and return the slot containing it.
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+ result = ExecProject(mergestate->jstate.cs_ProjInfo,
+ &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ mergestate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ }
/* ----------------
- * now skip this outer tuple
+ * now we get the next outer tuple, if any
* ----------------
*/
outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+ mergestate->mj_OuterTupleSlot = outerTupleSlot;
MJ_DEBUG_PROC_NODE(outerTupleSlot);
- econtext->ecxt_outertuple = outerTupleSlot;
+ mergestate->mj_MatchedOuter = false;
- /* ----------------
- * if the outer tuple is null then we know
- * we are done with the left half of the join
- * ----------------
- */
if (TupIsNull(outerTupleSlot))
{
- if (isRightJoin && !TupIsNull(innerTupleSlot))
- {
- mergestate->mj_JoinState = EXEC_MJ_FILLINNER;
- MJ_printf("ExecMergeJoin: try to complete inner fill\n");
- break;
- }
-
- MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n");
+ MJ_printf("ExecMergeJoin: end of outer subplan\n");
return NULL;
}
- /* ----------------
- * otherwise test the new tuple against the skip qual.
- * (we move to the EXEC_MJ_JOINMARK state)
- * ----------------
- */
+ /* Else remain in ENDINNER state and process next tuple. */
break;
-#endif
/*
* if we get here it means our code is fouled up and so we
* just end the join prematurely.
*/
default:
- elog(NOTICE, "ExecMergeJoin: invalid join state. aborting");
+ elog(NOTICE, "ExecMergeJoin: invalid join state %d, aborting",
+ mergestate->mj_JoinState);
return NULL;
}
}
{
MergeJoinState *mergestate;
List *joinclauses;
- TupleTableSlot *mjSlot;
MJ1_printf("ExecInitMergeJoin: %s\n",
"initializing node");
* get the range table and direction from it
* ----------------
*/
- node->join.state = estate;
+ node->join.plan.state = estate;
/* ----------------
* create new merge state for node
* ----------------
*/
mergestate = makeNode(MergeJoinState);
- mergestate->mj_OuterSkipQual = NIL;
- mergestate->mj_InnerSkipQual = NIL;
- mergestate->mj_JoinState = 0;
- mergestate->mj_MarkedTupleSlot = NULL;
node->mergestate = mergestate;
/* ----------------
*/
ExecAssignExprContext(estate, &mergestate->jstate);
-#define MERGEJOIN_NSLOTS 2
+ /* ----------------
+ * initialize subplans
+ * ----------------
+ */
+ ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
+ ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+
+#define MERGEJOIN_NSLOTS 4
/* ----------------
* tuple table initialization
- *
- * XXX why aren't we getting a tuple table slot in the normal way?
* ----------------
*/
ExecInitResultTupleSlot(estate, &mergestate->jstate);
- mjSlot = makeNode(TupleTableSlot);
- mjSlot->val = NULL;
- mjSlot->ttc_shouldFree = true;
- mjSlot->ttc_descIsNew = true;
- mjSlot->ttc_tupleDescriptor = NULL;
- mjSlot->ttc_buffer = InvalidBuffer;
- mjSlot->ttc_whichplan = -1;
- mergestate->mj_MarkedTupleSlot = mjSlot;
+
+ mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate);
+ ExecSetSlotDescriptor(mergestate->mj_MarkedTupleSlot,
+ ExecGetTupType(innerPlan((Plan *) node)));
+
+ switch (node->join.jointype)
+ {
+ case JOIN_INNER:
+ break;
+ case JOIN_LEFT:
+ mergestate->mj_NullInnerTupleSlot =
+ ExecInitNullTupleSlot(estate,
+ ExecGetTupType(innerPlan((Plan*) node)));
+ break;
+ case JOIN_RIGHT:
+ mergestate->mj_NullOuterTupleSlot =
+ ExecInitNullTupleSlot(estate,
+ ExecGetTupType(outerPlan((Plan*) node)));
+ /*
+ * Can't handle right or full join with non-nil extra joinclauses.
+ */
+ if (node->join.joinqual != NIL)
+ elog(ERROR, "RIGHT JOIN is only supported with mergejoinable join conditions");
+ break;
+ case JOIN_FULL:
+ mergestate->mj_NullOuterTupleSlot =
+ ExecInitNullTupleSlot(estate,
+ ExecGetTupType(outerPlan((Plan*) node)));
+ mergestate->mj_NullInnerTupleSlot =
+ ExecInitNullTupleSlot(estate,
+ ExecGetTupType(innerPlan((Plan*) node)));
+ /*
+ * Can't handle right or full join with non-nil extra joinclauses.
+ */
+ if (node->join.joinqual != NIL)
+ elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
+ break;
+ default:
+ elog(ERROR, "ExecInitMergeJoin: unsupported join type %d",
+ (int) node->join.jointype);
+ }
+
+ /* ----------------
+ * initialize tuple type and projection info
+ * ----------------
+ */
+ ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate);
+ ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate);
/* ----------------
* form merge skip qualifications
* ----------------
*/
mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
-
- /* ----------------
- * initialize subplans
- * ----------------
- */
- ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
- ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
-
- /* ----------------
- * initialize tuple type and projection info
- * ----------------
- */
- ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate);
- ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate);
-
mergestate->jstate.cs_TupFromTlist = false;
+ mergestate->mj_MatchedOuter = false;
+ mergestate->mj_MatchedInner = false;
+ mergestate->mj_OuterTupleSlot = NULL;
+ mergestate->mj_InnerTupleSlot = NULL;
+
/* ----------------
* initialization successful
* ----------------
ExecEndNode((Plan *) outerPlan((Plan *) node), (Plan *) node);
/* ----------------
- * clean out the tuple table so that we don't try and
- * pfree the marked tuples.. see HACK ALERT at the top of
- * this file.
+ * clean out the tuple table
* ----------------
*/
ExecClearTuple(mergestate->jstate.cs_ResultTupleSlot);
ExecClearTuple(mergestate->mj_MarkedTupleSlot);
- pfree(mergestate->mj_MarkedTupleSlot);
- mergestate->mj_MarkedTupleSlot = NULL;
MJ1_printf("ExecEndMergeJoin: %s\n",
"node processing ended");
ExecReScanMergeJoin(MergeJoin *node, ExprContext *exprCtxt, Plan *parent)
{
MergeJoinState *mergestate = node->mergestate;
- TupleTableSlot *mjSlot = mergestate->mj_MarkedTupleSlot;
- ExecClearTuple(mjSlot);
- mjSlot->ttc_tupleDescriptor = NULL;
- mjSlot->ttc_descIsNew = true;
- mjSlot->ttc_whichplan = -1;
+ ExecClearTuple(mergestate->mj_MarkedTupleSlot);
mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
+ mergestate->jstate.cs_TupFromTlist = false;
+ mergestate->mj_MatchedOuter = false;
+ mergestate->mj_MatchedInner = false;
+ mergestate->mj_OuterTupleSlot = NULL;
+ mergestate->mj_InnerTupleSlot = NULL;
/*
* if chgParam of subnodes is not null then plans will be re-scanned
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.20 2000/08/24 03:29:03 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.21 2000/09/12 21:06:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
NestLoopState *nlstate;
Plan *innerPlan;
Plan *outerPlan;
- bool needNewOuterTuple;
TupleTableSlot *outerTupleSlot;
TupleTableSlot *innerTupleSlot;
- List *qual;
+ List *joinqual;
+ List *otherqual;
ExprContext *econtext;
/* ----------------
ENL1_printf("getting info from node");
nlstate = node->nlstate;
- qual = node->join.qual;
- outerPlan = outerPlan(&node->join);
- innerPlan = innerPlan(&node->join);
+ joinqual = node->join.joinqual;
+ otherqual = node->join.plan.qual;
+ outerPlan = outerPlan((Plan *) node);
+ innerPlan = innerPlan((Plan *) node);
econtext = nlstate->jstate.cs_ExprContext;
/* ----------------
/* ----------------
* Ok, everything is setup for the join so now loop until
- * we return a qualifying join tuple..
+ * we return a qualifying join tuple.
* ----------------
*/
ENL1_printf("entering main loop");
for (;;)
{
/* ----------------
- * The essential idea now is to get the next inner tuple
- * and join it with the current outer tuple.
+ * If we don't have an outer tuple, get the next one and
+ * reset the inner scan.
* ----------------
*/
- needNewOuterTuple = TupIsNull(outerTupleSlot);
-
- /* ----------------
- * if we have an outerTuple, try to get the next inner tuple.
- * ----------------
- */
- if (!needNewOuterTuple)
- {
- ENL1_printf("getting new inner tuple");
-
- innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
- econtext->ecxt_innertuple = innerTupleSlot;
-
- if (TupIsNull(innerTupleSlot))
- {
- ENL1_printf("no inner tuple, need new outer tuple");
- needNewOuterTuple = true;
- }
- }
-
- /* ----------------
- * loop until we have a new outer tuple and a new
- * inner tuple.
- * ----------------
- */
- while (needNewOuterTuple)
+ if (nlstate->nl_NeedNewOuter)
{
- /* ----------------
- * now try to get the next outer tuple
- * ----------------
- */
ENL1_printf("getting new outer tuple");
outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
- econtext->ecxt_outertuple = outerTupleSlot;
/* ----------------
* if there are no more outer tuples, then the join
ENL1_printf("saving new outer tuple information");
nlstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
+ econtext->ecxt_outertuple = outerTupleSlot;
+ nlstate->nl_NeedNewOuter = false;
+ nlstate->nl_MatchedOuter = false;
/* ----------------
- * now rescan the inner plan and get a new inner tuple
+ * now rescan the inner plan
* ----------------
*/
-
ENL1_printf("rescanning inner plan");
/*
* expr context.
*/
ExecReScan(innerPlan, econtext, (Plan *) node);
+ }
+
+ /* ----------------
+ * we have an outerTuple, try to get the next inner tuple.
+ * ----------------
+ */
+ ENL1_printf("getting new inner tuple");
+
+ innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+ econtext->ecxt_innertuple = innerTupleSlot;
- ENL1_printf("getting new inner tuple");
+ if (TupIsNull(innerTupleSlot))
+ {
+ ENL1_printf("no inner tuple, need new outer tuple");
- innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
- econtext->ecxt_innertuple = innerTupleSlot;
+ nlstate->nl_NeedNewOuter = true;
- if (TupIsNull(innerTupleSlot))
- ENL1_printf("couldn't get inner tuple - need new outer tuple");
- else
+ if (! nlstate->nl_MatchedOuter &&
+ node->join.jointype == JOIN_LEFT)
{
- ENL1_printf("got inner and outer tuples");
- needNewOuterTuple = false;
+ /*
+ * We are doing an outer join and there were no join matches
+ * for this outer tuple. Generate a fake join tuple with
+ * nulls for the inner tuple, and return it if it passes
+ * the non-join quals.
+ */
+ econtext->ecxt_innertuple = nlstate->nl_NullInnerTupleSlot;
+
+ ENL1_printf("testing qualification for outer-join tuple");
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /* ----------------
+ * qualification was satisfied so we project and
+ * return the slot containing the result tuple
+ * using ExecProject().
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ ENL1_printf("qualification succeeded, projecting tuple");
+
+ result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ nlstate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
}
- } /* while (needNewOuterTuple) */
+ /*
+ * Otherwise just return to top of loop for a new outer tuple.
+ */
+ continue;
+ }
/* ----------------
* at this point we have a new pair of inner and outer
* tuples so we test the inner and outer tuples to see
- * if they satisify the node's qualification.
+ * if they satisfy the node's qualification.
+ *
+ * Only the joinquals determine MatchedOuter status,
+ * but all quals must pass to actually return the tuple.
* ----------------
*/
ENL1_printf("testing qualification");
- if (ExecQual((List *) qual, econtext, false))
+ if (ExecQual(joinqual, econtext, false))
{
- /* ----------------
- * qualification was satisified so we project and
- * return the slot containing the result tuple
- * using ExecProject().
- * ----------------
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
+ nlstate->nl_MatchedOuter = true;
- ENL1_printf("qualification succeeded, projecting tuple");
-
- result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
-
- if (isDone != ExprEndResult)
+ if (otherqual == NIL || ExecQual(otherqual, econtext, false))
{
- nlstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
- return result;
+ /* ----------------
+ * qualification was satisfied so we project and
+ * return the slot containing the result tuple
+ * using ExecProject().
+ * ----------------
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ ENL1_printf("qualification succeeded, projecting tuple");
+
+ result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ nlstate->jstate.cs_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
}
}
* assign execution state to node
* ----------------
*/
- node->join.state = estate;
+ node->join.plan.state = estate;
/* ----------------
* create new nest loop state
*/
ExecAssignExprContext(estate, &nlstate->jstate);
-#define NESTLOOP_NSLOTS 1
/* ----------------
- * tuple table initialization
+ * now initialize children
* ----------------
*/
- ExecInitResultTupleSlot(estate, &nlstate->jstate);
+ ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
+ ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+#define NESTLOOP_NSLOTS 2
/* ----------------
- * now initialize children
+ * tuple table initialization
* ----------------
*/
- ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
- ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+ ExecInitResultTupleSlot(estate, &nlstate->jstate);
+
+ switch (node->join.jointype)
+ {
+ case JOIN_INNER:
+ break;
+ case JOIN_LEFT:
+ nlstate->nl_NullInnerTupleSlot =
+ ExecInitNullTupleSlot(estate,
+ ExecGetTupType(innerPlan((Plan*) node)));
+ break;
+ default:
+ elog(ERROR, "ExecInitNestLoop: unsupported join type %d",
+ (int) node->join.jointype);
+ }
/* ----------------
* initialize tuple type and projection info
*/
nlstate->jstate.cs_OuterTupleSlot = NULL;
nlstate->jstate.cs_TupFromTlist = false;
+ nlstate->nl_NeedNewOuter = true;
+ nlstate->nl_MatchedOuter = false;
NL1_printf("ExecInitNestLoop: %s\n",
"node initialized");
/* let outerPlan to free its result tuple ... */
nlstate->jstate.cs_OuterTupleSlot = NULL;
nlstate->jstate.cs_TupFromTlist = false;
+ nlstate->nl_NeedNewOuter = true;
+ nlstate->nl_MatchedOuter = false;
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.120 2000/08/11 23:45:31 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.121 2000/09/12 21:06:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static void
CopyJoinFields(Join *from, Join *newnode)
{
- /* nothing extra */
- return;
+ newnode->jointype = from->jointype;
+ Node_Copy(from, newnode, joinqual);
+ /* subPlan list must point to subplans in the new subtree, not the old */
+ if (from->plan.subPlan != NIL)
+ newnode->plan.subPlan = nconc(newnode->plan.subPlan,
+ pull_subplans((Node *) newnode->joinqual));
}
/*
* We must add subplans in mergeclauses to the new plan's subPlan list
*/
- if (from->join.subPlan != NIL)
- newnode->join.subPlan = nconc(newnode->join.subPlan,
+ if (from->join.plan.subPlan != NIL)
+ newnode->join.plan.subPlan = nconc(newnode->join.plan.subPlan,
pull_subplans((Node *) newnode->mergeclauses));
return newnode;
/*
* We must add subplans in hashclauses to the new plan's subPlan list
*/
- if (from->join.subPlan != NIL)
- newnode->join.subPlan = nconc(newnode->join.subPlan,
+ if (from->join.plan.subPlan != NIL)
+ newnode->join.plan.subPlan = nconc(newnode->join.plan.subPlan,
pull_subplans((Node *) newnode->hashclauses));
return newnode;
return newnode;
}
-static JoinExpr *
-_copyJoinExpr(JoinExpr *from)
-{
- JoinExpr *newnode = makeNode(JoinExpr);
-
- newnode->jointype = from->jointype;
- newnode->isNatural = from->isNatural;
- Node_Copy(from, newnode, larg);
- Node_Copy(from, newnode, rarg);
- Node_Copy(from, newnode, alias);
- Node_Copy(from, newnode, quals);
-
- return newnode;
-}
-
/* ----------------
* _copyUnique
* ----------------
return newnode;
}
+static RangeTblRef *
+_copyRangeTblRef(RangeTblRef *from)
+{
+ RangeTblRef *newnode = makeNode(RangeTblRef);
+
+ newnode->rtindex = from->rtindex;
+
+ return newnode;
+}
+
+static JoinExpr *
+_copyJoinExpr(JoinExpr *from)
+{
+ JoinExpr *newnode = makeNode(JoinExpr);
+
+ newnode->jointype = from->jointype;
+ newnode->isNatural = from->isNatural;
+ Node_Copy(from, newnode, larg);
+ Node_Copy(from, newnode, rarg);
+ Node_Copy(from, newnode, using);
+ Node_Copy(from, newnode, quals);
+ Node_Copy(from, newnode, alias);
+ Node_Copy(from, newnode, colnames);
+ Node_Copy(from, newnode, colvars);
+
+ return newnode;
+}
+
/* ----------------
* _copyCaseExpr
* ----------------
Node_Copy(from, newnode, baserestrictinfo);
newnode->baserestrictcost = from->baserestrictcost;
+ newnode->outerjoinset = listCopy(from->outerjoinset);
Node_Copy(from, newnode, joininfo);
Node_Copy(from, newnode, innerjoin);
Node_Copy(from, newnode, indexqual);
newnode->indexscandir = from->indexscandir;
newnode->joinrelids = listCopy(from->joinrelids);
+ newnode->alljoinquals = from->alljoinquals;
newnode->rows = from->rows;
return newnode;
static void
CopyJoinPathFields(JoinPath *from, JoinPath *newnode)
{
+ newnode->jointype = from->jointype;
Node_Copy(from, newnode, outerjoinpath);
Node_Copy(from, newnode, innerjoinpath);
Node_Copy(from, newnode, joinrestrictinfo);
* ----------------
*/
Node_Copy(from, newnode, clause);
+ newnode->isjoinqual = from->isjoinqual;
Node_Copy(from, newnode, subclauseindices);
newnode->mergejoinoperator = from->mergejoinoperator;
newnode->left_sortop = from->left_sortop;
if (from->relname)
newnode->relname = pstrdup(from->relname);
- Node_Copy(from, newnode, ref);
- Node_Copy(from, newnode, eref);
newnode->relid = from->relid;
+ Node_Copy(from, newnode, alias);
+ Node_Copy(from, newnode, eref);
newnode->inh = from->inh;
newnode->inFromCl = from->inFromCl;
- newnode->inJoinSet = from->inJoinSet;
newnode->skipAcl = from->skipAcl;
return newnode;
return newnode;
}
-static RelExpr *
-_copyRelExpr(RelExpr *from)
-{
- RelExpr *newnode = makeNode(RelExpr);
-
- if (from->relname)
- newnode->relname = pstrdup(from->relname);
- newnode->inh = from->inh;
-
- return newnode;
-}
-
static SortGroupBy *
_copySortGroupBy(SortGroupBy *from)
{
{
RangeVar *newnode = makeNode(RangeVar);
- Node_Copy(from, newnode, relExpr);
+ if (from->relname)
+ newnode->relname = pstrdup(from->relname);
+ newnode->inh = from->inh;
+ Node_Copy(from, newnode, name);
+
+ return newnode;
+}
+
+static RangeSubselect *
+_copyRangeSubselect(RangeSubselect *from)
+{
+ RangeSubselect *newnode = makeNode(RangeSubselect);
+
+ Node_Copy(from, newnode, subquery);
Node_Copy(from, newnode, name);
return newnode;
newnode->hasSubLinks = from->hasSubLinks;
Node_Copy(from, newnode, rtable);
+ Node_Copy(from, newnode, jointree);
+
Node_Copy(from, newnode, targetList);
Node_Copy(from, newnode, qual);
Node_Copy(from, newnode, rowMark);
case T_RelabelType:
retval = _copyRelabelType(from);
break;
+ case T_RangeTblRef:
+ retval = _copyRangeTblRef(from);
+ break;
+ case T_JoinExpr:
+ retval = _copyJoinExpr(from);
+ break;
/*
* RELATION NODES
case T_TypeCast:
retval = _copyTypeCast(from);
break;
- case T_RelExpr:
- retval = _copyRelExpr(from);
- break;
case T_SortGroupBy:
retval = _copySortGroupBy(from);
break;
case T_RangeVar:
retval = _copyRangeVar(from);
break;
+ case T_RangeSubselect:
+ retval = _copyRangeSubselect(from);
+ break;
case T_TypeName:
retval = _copyTypeName(from);
break;
case T_GroupClause:
retval = _copyGroupClause(from);
break;
- case T_JoinExpr:
- retval = _copyJoinExpr(from);
- break;
case T_CaseExpr:
retval = _copyCaseExpr(from);
break;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.72 2000/08/11 23:45:31 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.73 2000/09/12 21:06:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
return true;
}
+static bool
+_equalArrayRef(ArrayRef *a, ArrayRef *b)
+{
+ if (a->refelemtype != b->refelemtype)
+ return false;
+ if (a->refattrlength != b->refattrlength)
+ return false;
+ if (a->refelemlength != b->refelemlength)
+ return false;
+ if (a->refelembyval != b->refelembyval)
+ return false;
+ if (!equal(a->refupperindexpr, b->refupperindexpr))
+ return false;
+ if (!equal(a->reflowerindexpr, b->reflowerindexpr))
+ return false;
+ if (!equal(a->refexpr, b->refexpr))
+ return false;
+ return equal(a->refassgnexpr, b->refassgnexpr);
+}
+
static bool
_equalFieldSelect(FieldSelect *a, FieldSelect *b)
{
}
static bool
-_equalArrayRef(ArrayRef *a, ArrayRef *b)
+_equalRangeTblRef(RangeTblRef *a, RangeTblRef *b)
{
- if (a->refelemtype != b->refelemtype)
+ if (a->rtindex != b->rtindex)
return false;
- if (a->refattrlength != b->refattrlength)
+
+ return true;
+}
+
+static bool
+_equalJoinExpr(JoinExpr *a, JoinExpr *b)
+{
+ if (a->jointype != b->jointype)
return false;
- if (a->refelemlength != b->refelemlength)
+ if (a->isNatural != b->isNatural)
return false;
- if (a->refelembyval != b->refelembyval)
+ if (!equal(a->larg, b->larg))
return false;
- if (!equal(a->refupperindexpr, b->refupperindexpr))
+ if (!equal(a->rarg, b->rarg))
return false;
- if (!equal(a->reflowerindexpr, b->reflowerindexpr))
+ if (!equal(a->using, b->using))
return false;
- if (!equal(a->refexpr, b->refexpr))
+ if (!equal(a->quals, b->quals))
return false;
- return equal(a->refassgnexpr, b->refassgnexpr);
+ if (!equal(a->alias, b->alias))
+ return false;
+ if (!equal(a->colnames, b->colnames))
+ return false;
+ if (!equal(a->colvars, b->colvars))
+ return false;
+
+ return true;
}
/*
return false;
if (!equali(a->joinrelids, b->joinrelids))
return false;
+ if (a->alljoinquals != b->alljoinquals)
+ return false;
/*
* Skip 'rows' because of possibility of floating-point roundoff
{
if (!_equalPath((Path *) a, (Path *) b))
return false;
+ if (a->jointype != b->jointype)
+ return false;
if (!equal(a->outerjoinpath, b->outerjoinpath))
return false;
if (!equal(a->innerjoinpath, b->innerjoinpath))
{
if (!equal(a->clause, b->clause))
return false;
+ if (a->isjoinqual != b->isjoinqual)
+ return false;
if (!equal(a->subclauseindices, b->subclauseindices))
return false;
if (a->mergejoinoperator != b->mergejoinoperator)
return false;
if (!equal(a->rtable, b->rtable))
return false;
+ if (!equal(a->jointree, b->jointree))
+ return false;
if (!equal(a->targetList, b->targetList))
return false;
if (!equal(a->qual, b->qual))
}
static bool
-_equalRelExpr(RelExpr *a, RelExpr *b)
+_equalSortGroupBy(SortGroupBy *a, SortGroupBy *b)
{
- if (!equalstr(a->relname, b->relname))
+ if (!equalstr(a->useOp, b->useOp))
return false;
- if (a->inh != b->inh)
+ if (!equal(a->node, b->node))
return false;
return true;
}
static bool
-_equalSortGroupBy(SortGroupBy *a, SortGroupBy *b)
+_equalRangeVar(RangeVar *a, RangeVar *b)
{
- if (!equalstr(a->useOp, b->useOp))
+ if (!equalstr(a->relname, b->relname))
return false;
- if (!equal(a->node, b->node))
+ if (a->inh != b->inh)
+ return false;
+ if (!equal(a->name, b->name))
return false;
return true;
}
static bool
-_equalRangeVar(RangeVar *a, RangeVar *b)
+_equalRangeSubselect(RangeSubselect *a, RangeSubselect *b)
{
- if (!equal(a->relExpr, b->relExpr))
+ if (!equal(a->subquery, b->subquery))
return false;
if (!equal(a->name, b->name))
return false;
{
if (!equalstr(a->relname, b->relname))
return false;
- if (!equal(a->ref, b->ref))
- return false;
- /* XXX what about eref? */
if (a->relid != b->relid)
return false;
+ if (!equal(a->alias, b->alias))
+ return false;
+ if (!equal(a->eref, b->eref))
+ return false;
if (a->inh != b->inh)
return false;
if (a->inFromCl != b->inFromCl)
return false;
- if (a->inJoinSet != b->inJoinSet)
- return false;
if (a->skipAcl != b->skipAcl)
return false;
return true;
}
-static bool
-_equalJoinExpr(JoinExpr *a, JoinExpr *b)
-{
- if (a->jointype != b->jointype)
- return false;
- if (a->isNatural != b->isNatural)
- return false;
- if (!equal(a->larg, b->larg))
- return false;
- if (!equal(a->rarg, b->rarg))
- return false;
- if (!equal(a->alias, b->alias))
- return false;
- if (!equal(a->quals, b->quals))
- return false;
-
- return true;
-}
-
static bool
_equalFkConstraint(FkConstraint *a, FkConstraint *b)
{
case T_RelabelType:
retval = _equalRelabelType(a, b);
break;
+ case T_RangeTblRef:
+ retval = _equalRangeTblRef(a, b);
+ break;
+ case T_JoinExpr:
+ retval = _equalJoinExpr(a, b);
+ break;
case T_RelOptInfo:
retval = _equalRelOptInfo(a, b);
case T_TypeCast:
retval = _equalTypeCast(a, b);
break;
- case T_RelExpr:
- retval = _equalRelExpr(a, b);
- break;
case T_SortGroupBy:
retval = _equalSortGroupBy(a, b);
break;
case T_RangeVar:
retval = _equalRangeVar(a, b);
break;
+ case T_RangeSubselect:
+ retval = _equalRangeSubselect(a, b);
+ break;
case T_TypeName:
retval = _equalTypeName(a, b);
break;
/* GroupClause is equivalent to SortClause */
retval = _equalSortClause(a, b);
break;
- case T_JoinExpr:
- retval = _equalJoinExpr(a, b);
- break;
case T_CaseExpr:
retval = _equalCaseExpr(a, b);
break;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.32 2000/06/09 01:44:12 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.33 2000/09/12 21:06:49 tgl Exp $
*
* NOTES
* XXX a few of the following functions are duplicated to handle
return false;
}
+/*
+ * like member(), but use when pointer-equality comparison is sufficient
+ */
+bool
+ptrMember(void *l1, List *l2)
+{
+ List *i;
+
+ foreach(i, l2)
+ {
+ if (l1 == ((void *) lfirst(i)))
+ return true;
+ }
+ return false;
+}
+
+/*
+ * membership test for integer lists
+ */
bool
intMember(int l1, List *l2)
{
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.125 2000/08/08 15:41:26 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.126 2000/09/12 21:06:49 tgl Exp $
*
* NOTES
* Every (plan) node in POSTGRES has an associated "out" routine which
appendStringInfo(str, " :rtable ");
_outNode(str, node->rtable);
+ appendStringInfo(str, " :jointree ");
+ _outNode(str, node->jointree);
+
appendStringInfo(str, " :targetlist ");
_outNode(str, node->targetList);
" :inheritrelid %u :inheritrtable ",
node->inheritrelid);
_outNode(str, node->inheritrtable);
-
}
/*
{
appendStringInfo(str, " JOIN ");
_outPlanInfo(str, (Plan *) node);
-
+ appendStringInfo(str, " :jointype %d :joinqual ",
+ (int) node->jointype);
+ _outNode(str, node->joinqual);
}
/*
{
appendStringInfo(str, " NESTLOOP ");
_outPlanInfo(str, (Plan *) node);
+ appendStringInfo(str, " :jointype %d :joinqual ",
+ (int) node->join.jointype);
+ _outNode(str, node->join.joinqual);
}
/*
{
appendStringInfo(str, " MERGEJOIN ");
_outPlanInfo(str, (Plan *) node);
+ appendStringInfo(str, " :jointype %d :joinqual ",
+ (int) node->join.jointype);
+ _outNode(str, node->join.joinqual);
appendStringInfo(str, " :mergeclauses ");
_outNode(str, node->mergeclauses);
{
appendStringInfo(str, " HASHJOIN ");
_outPlanInfo(str, (Plan *) node);
+ appendStringInfo(str, " :jointype %d :joinqual ",
+ (int) node->join.jointype);
+ _outNode(str, node->join.joinqual);
appendStringInfo(str, " :hashclauses ");
_outNode(str, node->hashclauses);
-
- appendStringInfo(str,
- " :hashjoinop %u ",
+ appendStringInfo(str, " :hashjoinop %u ",
node->hashjoinop);
-
- appendStringInfo(str,
- " :hashdone %d ",
- node->hashdone);
}
static void
_outNode(str, node->subselect);
}
-/*
- * FieldSelect
- */
-static void
-_outFieldSelect(StringInfo str, FieldSelect *node)
-{
- appendStringInfo(str, " FIELDSELECT :arg ");
- _outNode(str, node->arg);
-
- appendStringInfo(str, " :fieldnum %d :resulttype %u :resulttypmod %d ",
- node->fieldnum, node->resulttype, node->resulttypmod);
-}
-
-/*
- * RelabelType
- */
-static void
-_outRelabelType(StringInfo str, RelabelType *node)
-{
- appendStringInfo(str, " RELABELTYPE :arg ");
- _outNode(str, node->arg);
-
- appendStringInfo(str, " :resulttype %u :resulttypmod %d ",
- node->resulttype, node->resulttypmod);
-}
-
/*
* ArrayRef is a subclass of Expr
*/
appendStringInfo(str, " :paramtype %u ", node->paramtype);
}
+/*
+ * FieldSelect
+ */
+static void
+_outFieldSelect(StringInfo str, FieldSelect *node)
+{
+ appendStringInfo(str, " FIELDSELECT :arg ");
+ _outNode(str, node->arg);
+
+ appendStringInfo(str, " :fieldnum %d :resulttype %u :resulttypmod %d ",
+ node->fieldnum, node->resulttype, node->resulttypmod);
+}
+
+/*
+ * RelabelType
+ */
+static void
+_outRelabelType(StringInfo str, RelabelType *node)
+{
+ appendStringInfo(str, " RELABELTYPE :arg ");
+ _outNode(str, node->arg);
+
+ appendStringInfo(str, " :resulttype %u :resulttypmod %d ",
+ node->resulttype, node->resulttypmod);
+}
+
+/*
+ * RangeTblRef
+ */
+static void
+_outRangeTblRef(StringInfo str, RangeTblRef *node)
+{
+ appendStringInfo(str, " RANGETBLREF %d ",
+ node->rtindex);
+}
+
+/*
+ * JoinExpr
+ */
+static void
+_outJoinExpr(StringInfo str, JoinExpr *node)
+{
+ appendStringInfo(str, " JOINEXPR :jointype %d :isNatural %s :larg ",
+ (int) node->jointype,
+ node->isNatural ? "true" : "false");
+ _outNode(str, node->larg);
+ appendStringInfo(str, " :rarg ");
+ _outNode(str, node->rarg);
+ appendStringInfo(str, " :using ");
+ _outNode(str, node->using);
+ appendStringInfo(str, " :quals ");
+ _outNode(str, node->quals);
+ appendStringInfo(str, " :alias ");
+ _outNode(str, node->alias);
+ appendStringInfo(str, " :colnames ");
+ _outNode(str, node->colnames);
+ appendStringInfo(str, " :colvars ");
+ _outNode(str, node->colvars);
+}
+
/*
* Stuff from execnodes.h
*/
node->pruneable ? "true" : "false");
_outNode(str, node->baserestrictinfo);
+ appendStringInfo(str,
+ " :baserestrictcost %.2f :outerjoinset ",
+ node->baserestrictcost);
+ _outIntList(str, node->outerjoinset);
+
appendStringInfo(str, " :joininfo ");
_outNode(str, node->joininfo);
{
appendStringInfo(str, " RTE :relname ");
_outToken(str, node->relname);
- appendStringInfo(str, " :ref ");
- _outNode(str, node->ref);
- appendStringInfo(str,
- " :relid %u :inh %s :inFromCl %s :inJoinSet %s :skipAcl %s",
- node->relid,
+ appendStringInfo(str, " :relid %u :alias ",
+ node->relid);
+ _outNode(str, node->alias);
+ appendStringInfo(str, " :eref ");
+ _outNode(str, node->eref);
+ appendStringInfo(str, " :inh %s :inFromCl %s :skipAcl %s",
node->inh ? "true" : "false",
node->inFromCl ? "true" : "false",
- node->inJoinSet ? "true" : "false",
node->skipAcl ? "true" : "false");
}
(int) node->indexscandir);
_outIntList(str, node->joinrelids);
- appendStringInfo(str, " :rows %.2f ",
+ appendStringInfo(str, " :alljoinquals %s :rows %.2f ",
+ node->alljoinquals ? "true" : "false",
node->rows);
}
node->path.startup_cost,
node->path.total_cost);
_outNode(str, node->path.pathkeys);
- appendStringInfo(str, " :outerjoinpath ");
+ appendStringInfo(str, " :jointype %d :outerjoinpath ",
+ (int) node->jointype);
_outNode(str, node->outerjoinpath);
appendStringInfo(str, " :innerjoinpath ");
_outNode(str, node->innerjoinpath);
node->jpath.path.startup_cost,
node->jpath.path.total_cost);
_outNode(str, node->jpath.path.pathkeys);
- appendStringInfo(str, " :outerjoinpath ");
+ appendStringInfo(str, " :jointype %d :outerjoinpath ",
+ (int) node->jpath.jointype);
_outNode(str, node->jpath.outerjoinpath);
appendStringInfo(str, " :innerjoinpath ");
_outNode(str, node->jpath.innerjoinpath);
node->jpath.path.startup_cost,
node->jpath.path.total_cost);
_outNode(str, node->jpath.path.pathkeys);
- appendStringInfo(str, " :outerjoinpath ");
+ appendStringInfo(str, " :jointype %d :outerjoinpath ",
+ (int) node->jpath.jointype);
_outNode(str, node->jpath.outerjoinpath);
appendStringInfo(str, " :innerjoinpath ");
_outNode(str, node->jpath.innerjoinpath);
appendStringInfo(str, " RESTRICTINFO :clause ");
_outNode(str, node->clause);
- appendStringInfo(str, " :subclauseindices ");
+ appendStringInfo(str, " :isjoinqual %s :subclauseindices ",
+ node->isjoinqual ? "true" : "false");
_outNode(str, node->subclauseindices);
appendStringInfo(str, " :mergejoinoperator %u ", node->mergejoinoperator);
case T_SubLink:
_outSubLink(str, obj);
break;
- case T_FieldSelect:
- _outFieldSelect(str, obj);
- break;
- case T_RelabelType:
- _outRelabelType(str, obj);
- break;
case T_ArrayRef:
_outArrayRef(str, obj);
break;
case T_Param:
_outParam(str, obj);
break;
+ case T_FieldSelect:
+ _outFieldSelect(str, obj);
+ break;
+ case T_RelabelType:
+ _outRelabelType(str, obj);
+ break;
+ case T_RangeTblRef:
+ _outRangeTblRef(str, obj);
+ break;
+ case T_JoinExpr:
+ _outJoinExpr(str, obj);
+ break;
case T_EState:
_outEState(str, obj);
break;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.39 2000/06/18 22:44:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.40 2000/09/12 21:06:49 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
RangeTblEntry *rte = lfirst(l);
printf("%d\t%s(%s)\t%u\t%d\t%s\n",
- i, rte->relname, rte->ref->relname, rte->relid,
+ i, rte->relname, rte->eref->relname, rte->relid,
rte->inFromCl,
(rte->inh ? "inh" : ""));
i++;
if (IsA(expr, Var))
{
Var *var = (Var *) expr;
- RangeTblEntry *rt;
char *relname,
*attname;
break;
default:
{
+ RangeTblEntry *rt;
+
rt = rt_fetch(var->varno, rtable);
- relname = rt->relname;
- if (rt->ref && rt->ref->relname)
- relname = rt->ref->relname; /* table renamed */
+ relname = rt->eref->relname;
attname = get_attname(rt->relid, var->varattno);
}
break;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.95 2000/08/08 15:41:27 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.96 2000/09/12 21:06:49 tgl Exp $
*
* NOTES
* Most of the read functions for plan nodes are tested. (In fact, they
token = lsptok(NULL, &length); /* skip :rtable */
local_node->rtable = nodeRead(true);
+ token = lsptok(NULL, &length); /* skip :jointree */
+ local_node->jointree = nodeRead(true);
+
token = lsptok(NULL, &length); /* skip :targetlist */
local_node->targetList = nodeRead(true);
/* ----------------
* _getJoin
- *
- * In case Join is not the same structure as Plan someday.
* ----------------
*/
static void
_getJoin(Join *node)
{
+ char *token;
+ int length;
+
_getPlan((Plan *) node);
+
+ token = lsptok(NULL, &length); /* skip the :jointype */
+ token = lsptok(NULL, &length); /* get the jointype */
+ node->jointype = (JoinType) atoi(token);
+
+ token = lsptok(NULL, &length); /* skip the :joinqual */
+ node->joinqual = nodeRead(true); /* get the joinqual */
}
local_node = makeNode(MergeJoin);
_getJoin((Join *) local_node);
+
token = lsptok(NULL, &length); /* eat :mergeclauses */
local_node->mergeclauses = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get hashjoinop */
local_node->hashjoinop = strtoul(token, NULL, 10);
- token = lsptok(NULL, &length); /* eat :hashdone */
- token = lsptok(NULL, &length); /* eat hashdone */
- local_node->hashdone = false;
-
return local_node;
}
/* ----------------
* _getScan
*
- * Scan is a subclass of Node
- * (Actually, according to the plannodes.h include file, it is a
- * subclass of Plan. This is why _getPlan is used here.)
+ * Scan is a subclass of Plan.
*
* Scan gets its own get function since stuff inherits it.
* ----------------
/* ----------------
* _readScan
*
- * Scan is a subclass of Plan (Not Node, see above).
+ * Scan is a subclass of Plan.
* ----------------
*/
static Scan *
return local_node;
}
+/* ----------------
+ * _readRangeTblRef
+ *
+ * RangeTblRef is a subclass of Node
+ * ----------------
+ */
+static RangeTblRef *
+_readRangeTblRef()
+{
+ RangeTblRef *local_node;
+ char *token;
+ int length;
+
+ local_node = makeNode(RangeTblRef);
+
+ token = lsptok(NULL, &length); /* get rtindex */
+ local_node->rtindex = atoi(token);
+
+ return local_node;
+}
+
+/* ----------------
+ * _readJoinExpr
+ *
+ * JoinExpr is a subclass of Node
+ * ----------------
+ */
+static JoinExpr *
+_readJoinExpr()
+{
+ JoinExpr *local_node;
+ char *token;
+ int length;
+
+ local_node = makeNode(JoinExpr);
+
+ token = lsptok(NULL, &length); /* eat :jointype */
+ token = lsptok(NULL, &length); /* get jointype */
+ local_node->jointype = (JoinType) atoi(token);
+
+ token = lsptok(NULL, &length); /* eat :isNatural */
+ token = lsptok(NULL, &length); /* get :isNatural */
+ local_node->isNatural = (token[0] == 't') ? true : false;
+
+ token = lsptok(NULL, &length); /* eat :larg */
+ local_node->larg = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :rarg */
+ local_node->rarg = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :using */
+ local_node->using = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :quals */
+ local_node->quals = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :alias */
+ local_node->alias = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :colnames */
+ local_node->colnames = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :colvars */
+ local_node->colvars = nodeRead(true); /* now read it */
+
+ return local_node;
+}
+
/*
* Stuff from execnodes.h
*/
local_node->pruneable = (token[0] == 't') ? true : false;
token = lsptok(NULL, &length); /* get :baserestrictinfo */
- local_node->baserestrictinfo = nodeRead(true); /* now read it */
+ local_node->baserestrictinfo = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* get :baserestrictcost */
+ token = lsptok(NULL, &length); /* now read it */
+ local_node->baserestrictcost = (Cost) atof(token);
+
+ token = lsptok(NULL, &length); /* get :outerjoinset */
+ local_node->outerjoinset = toIntList(nodeRead(true)); /* now read it */
token = lsptok(NULL, &length); /* get :joininfo */
local_node->joininfo = nodeRead(true); /* now read it */
else
local_node->relname = debackslash(token, length);
- token = lsptok(NULL, &length); /* eat :ref */
- local_node->ref = nodeRead(true); /* now read it */
-
token = lsptok(NULL, &length); /* eat :relid */
token = lsptok(NULL, &length); /* get :relid */
local_node->relid = strtoul(token, NULL, 10);
+ token = lsptok(NULL, &length); /* eat :alias */
+ local_node->alias = nodeRead(true); /* now read it */
+
+ token = lsptok(NULL, &length); /* eat :eref */
+ local_node->eref = nodeRead(true); /* now read it */
+
token = lsptok(NULL, &length); /* eat :inh */
token = lsptok(NULL, &length); /* get :inh */
local_node->inh = (token[0] == 't') ? true : false;
token = lsptok(NULL, &length); /* get :inFromCl */
local_node->inFromCl = (token[0] == 't') ? true : false;
- token = lsptok(NULL, &length); /* eat :inJoinSet */
- token = lsptok(NULL, &length); /* get :inJoinSet */
- local_node->inJoinSet = (token[0] == 't') ? true : false;
-
token = lsptok(NULL, &length); /* eat :skipAcl */
token = lsptok(NULL, &length); /* get :skipAcl */
local_node->skipAcl = (token[0] == 't') ? true : false;
token = lsptok(NULL, &length); /* get :joinrelids */
local_node->joinrelids = toIntList(nodeRead(true));
+ token = lsptok(NULL, &length); /* get :alljoinquals */
+ token = lsptok(NULL, &length); /* now read it */
+ local_node->alljoinquals = (token[0] == 't') ? true : false;
+
token = lsptok(NULL, &length); /* get :rows */
token = lsptok(NULL, &length); /* now read it */
local_node->rows = atof(token);
token = lsptok(NULL, &length); /* get :pathkeys */
local_node->path.pathkeys = nodeRead(true); /* now read it */
+ token = lsptok(NULL, &length); /* get :jointype */
+ token = lsptok(NULL, &length); /* now read it */
+ local_node->jointype = (JoinType) atoi(token);
+
token = lsptok(NULL, &length); /* get :outerjoinpath */
local_node->outerjoinpath = nodeRead(true); /* now read it */
local_node->innerjoinpath = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :joinrestrictinfo */
- local_node->joinrestrictinfo = nodeRead(true); /* now read it */
+ local_node->joinrestrictinfo = nodeRead(true); /* now read it */
return local_node;
}
token = lsptok(NULL, &length); /* get :pathkeys */
local_node->jpath.path.pathkeys = nodeRead(true); /* now read it */
+ token = lsptok(NULL, &length); /* get :jointype */
+ token = lsptok(NULL, &length); /* now read it */
+ local_node->jpath.jointype = (JoinType) atoi(token);
+
token = lsptok(NULL, &length); /* get :outerjoinpath */
local_node->jpath.outerjoinpath = nodeRead(true); /* now read it */
local_node->jpath.innerjoinpath = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :joinrestrictinfo */
- local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
+ local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :path_mergeclauses */
local_node->path_mergeclauses = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :pathkeys */
local_node->jpath.path.pathkeys = nodeRead(true); /* now read it */
+ token = lsptok(NULL, &length); /* get :jointype */
+ token = lsptok(NULL, &length); /* now read it */
+ local_node->jpath.jointype = (JoinType) atoi(token);
+
token = lsptok(NULL, &length); /* get :outerjoinpath */
local_node->jpath.outerjoinpath = nodeRead(true); /* now read it */
local_node->jpath.innerjoinpath = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :joinrestrictinfo */
- local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
+ local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :path_hashclauses */
local_node->path_hashclauses = nodeRead(true); /* now read it */
token = lsptok(NULL, &length); /* get :clause */
local_node->clause = nodeRead(true); /* now read it */
+ token = lsptok(NULL, &length); /* get :isjoinqual */
+ token = lsptok(NULL, &length); /* now read it */
+ local_node->isjoinqual = (token[0] == 't') ? true : false;
+
token = lsptok(NULL, &length); /* get :subclauseindices */
local_node->subclauseindices = nodeRead(true); /* now read it */
return_value = _readFieldSelect();
else if (length == 11 && strncmp(token, "RELABELTYPE", length) == 0)
return_value = _readRelabelType();
+ else if (length == 11 && strncmp(token, "RANGETBLREF", length) == 0)
+ return_value = _readRangeTblRef();
+ else if (length == 8 && strncmp(token, "JOINEXPR", length) == 0)
+ return_value = _readJoinExpr();
else if (length == 3 && strncmp(token, "AGG", length) == 0)
return_value = _readAgg();
else if (length == 4 && strncmp(token, "HASH", length) == 0)
inferior alternatives before they ever get into the pathlist --- what
ends up in the pathlist is the cheapest way of generating each potentially
useful sort ordering of the relation.) Also create RelOptInfo.joininfo
-nodes that list all the joins that involve this relation. For example,
-the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo for tab1
-listing tab2 as an unjoined relation, and also one for tab2 showing tab1
-as an unjoined relation.
+nodes that list all the join clauses that involve this relation. For
+example, the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo
+for tab1 listing tab2 as an unjoined relation, and also one for tab2
+showing tab1 as an unjoined relation.
If we have only a single base relation in the query, we are done now.
Otherwise we have to figure out how to join the base relations into a
for it or the cheapest path with the desired ordering (if that's cheaper
than applying a sort to the cheapest other path).
+The above dynamic-programming search is only conducted for simple cross
+joins (ie, SELECT FROM tab1, tab2, ...). When the FROM clause contains
+explicit JOIN clauses, we join rels in exactly the order implied by the
+join tree. Searching for the best possible join order is done only at
+the top implicit-cross-join level. For example, in
+ SELECT FROM tab1, tab2, (tab3 NATURAL JOIN tab4)
+we will always join tab3 to tab4 and then consider all ways to join that
+result to tab1 and tab2. Note that the JOIN syntax only constrains the
+order of joining --- we will still consider all available Paths and
+join methods for each JOIN operator. We also consider both sides of
+the JOIN operator as inner or outer (so that we can transform RIGHT JOIN
+into LEFT JOIN).
+
Optimizer Functions
-------------------
get a target list that only contains column names, no expressions
if none, then return
---subplanner()
- make list of relations in target
- make list of relations in where clause
+ make list of base relations used in query
split up the qual into restrictions (a=1) and joins (b=c)
- find relation clauses can do merge sort and hash joins
+ find relation clauses that can do merge sort and hash joins
----make_one_rel()
set_base_rel_pathlist()
- find scan and all index paths for each relation
+ find scan and all index paths for each base relation
find selectivity of columns used in joins
-----make_one_rel_by_joins()
jump to geqo if needed
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: geqo_eval.c,v 1.53 2000/07/28 02:13:16 tgl Exp $
+ * $Id: geqo_eval.c,v 1.54 2000/09/12 21:06:50 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Returns a new join relation incorporating all joins in a left-sided tree.
*/
RelOptInfo *
-gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene, RelOptInfo *old_rel)
+gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene,
+ RelOptInfo *old_rel)
{
RelOptInfo *inner_rel; /* current relation */
int base_rel_index;
- RelOptInfo *new_rel;
if (rel_count < num_gene)
{ /* tree not yet finished */
else
{ /* tree main part */
List *acceptable_rels = lcons(inner_rel, NIL);
-
- new_rel = make_rels_by_clause_joins(root, old_rel,
- acceptable_rels);
- if (!new_rel)
+ List *new_rels;
+ RelOptInfo *new_rel;
+
+ new_rels = make_rels_by_clause_joins(root, old_rel,
+ acceptable_rels);
+ /* Shouldn't get more than one result */
+ Assert(length(new_rels) <= 1);
+ if (new_rels == NIL)
{
- new_rel = make_rels_by_clauseless_joins(root, old_rel,
- acceptable_rels);
- if (!new_rel)
+ new_rels = make_rels_by_clauseless_joins(root, old_rel,
+ acceptable_rels);
+ Assert(length(new_rels) <= 1);
+ if (new_rels == NIL)
elog(ERROR, "gimme_tree: failed to construct join rel");
}
+ new_rel = (RelOptInfo *) lfirst(new_rels);
rel_count++;
Assert(length(new_rel->relids) == rel_count);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.62 2000/05/31 00:28:22 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.63 2000/09/12 21:06:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static void set_base_rel_pathlist(Query *root);
-static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed);
+static List *build_jointree_rels(Query *root);
+static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed,
+ List *initial_rels);
#ifdef OPTIMIZER_DEBUG
static void debug_print_rel(Query *root, RelOptInfo *rel);
make_one_rel(Query *root)
{
int levels_needed;
+ List *initial_rels;
/*
- * Set the number of join (not nesting) levels yet to be processed.
+ * Count the number of top-level jointree nodes. This is the depth
+ * of the dynamic-programming algorithm we must employ to consider
+ * all ways of joining the top-level nodes. Currently, we build
+ * JoinExpr joins in exactly the order implied by the join expression,
+ * so no dynamic-programming search is needed within a JoinExpr.
*/
- levels_needed = length(root->base_rel_list);
+ levels_needed = length(root->jointree);
if (levels_needed <= 0)
- return NULL;
+ return NULL; /* nothing to do? */
/*
* Generate access paths for the base rels.
*/
set_base_rel_pathlist(root);
+ /*
+ * Construct a list of rels corresponding to the toplevel jointree nodes.
+ * This may contain both base rels and rels constructed according to
+ * explicit JOIN directives.
+ */
+ initial_rels = build_jointree_rels(root);
+
if (levels_needed == 1)
{
-
/*
- * Single relation, no more processing is required.
+ * Single jointree node, so we're done.
*/
- return (RelOptInfo *) lfirst(root->base_rel_list);
+ return (RelOptInfo *) lfirst(initial_rels);
}
else
{
/*
* Generate join tree.
*/
- return make_one_rel_by_joins(root, levels_needed);
+ return make_one_rel_by_joins(root, levels_needed, initial_rels);
}
}
}
}
+/*
+ * build_jointree_rels
+ * Construct a RelOptInfo for each item in the query's jointree.
+ *
+ * At present, we handle explicit joins in the FROM clause exactly as
+ * specified, with no search for other join orders. Only the cross-product
+ * joins at the top level are involved in the dynamic-programming search.
+ */
+static List *
+build_jointree_rels(Query *root)
+{
+ List *rels = NIL;
+ List *jt;
+
+ foreach(jt, root->jointree)
+ {
+ Node *jtnode = (Node *) lfirst(jt);
+
+ rels = lappend(rels, make_rel_from_jointree(root, jtnode));
+ }
+ return rels;
+}
+
/*
* make_one_rel_by_joins
* Find all possible joinpaths for a query by successively finding ways
* to join component relations into join relations.
*
* 'levels_needed' is the number of iterations needed, ie, the number of
- * base relations present in the query
+ * independent jointree items in the query. This is > 1.
+ *
+ * 'initial_rels' is a list of RelOptInfo nodes for each independent
+ * jointree item. These are the components to be joined together.
*
* Returns the final level of join relations, i.e., the relation that is
* the result of joining all the original relations together.
*/
static RelOptInfo *
-make_one_rel_by_joins(Query *root, int levels_needed)
+make_one_rel_by_joins(Query *root, int levels_needed, List *initial_rels)
{
+ List **joinitems;
int lev;
RelOptInfo *rel;
/*
* We employ a simple "dynamic programming" algorithm: we first find
- * all ways to build joins of two base relations, then all ways to
- * build joins of three base relations (from two-base-rel joins and
- * other base rels), then four-base-rel joins, and so on until we have
- * considered all ways to join all N relations into one rel.
+ * all ways to build joins of two jointree items, then all ways to
+ * build joins of three items (from two-item joins and single items),
+ * then four-item joins, and so on until we have considered all ways
+ * to join all the items into one rel.
+ *
+ * joinitems[j] is a list of all the j-item rels. Initially we set
+ * joinitems[1] to represent all the single-jointree-item relations.
*/
+ joinitems = (List **) palloc((levels_needed+1) * sizeof(List *));
+ MemSet(joinitems, 0, (levels_needed+1) * sizeof(List *));
+
+ joinitems[1] = initial_rels;
for (lev = 2; lev <= levels_needed; lev++)
{
- List *first_old_rel = root->join_rel_list;
List *x;
/*
* Determine all possible pairs of relations to be joined at this
* level, and build paths for making each one from every available
- * pair of lower-level relations. Results are prepended to
- * root->join_rel_list.
+ * pair of lower-level relations.
*/
- make_rels_by_joins(root, lev);
+ joinitems[lev] = make_rels_by_joins(root, lev, joinitems);
/*
- * The relations created at the current level will appear at the
- * front of root->join_rel_list.
+ * Do cleanup work on each just-processed rel.
*/
- foreach(x, root->join_rel_list)
+ foreach(x, joinitems[lev])
{
- if (x == first_old_rel)
- break; /* no more rels added at this level */
-
rel = (RelOptInfo *) lfirst(x);
#ifdef NOT_USED
}
/*
- * Now, the front of the join_rel_list should be the single rel
+ * We should have a single rel at the final level,
* representing the join of all the base rels.
*/
- Assert(length(root->join_rel_list) > 0);
- rel = (RelOptInfo *) lfirst(root->join_rel_list);
- Assert(length(rel->relids) == levels_needed);
- Assert(length(root->join_rel_list) == 1 ||
- length(((RelOptInfo *) lsecond(root->join_rel_list))->relids) < levels_needed);
+ Assert(length(joinitems[levels_needed]) == 1);
+ rel = (RelOptInfo *) lfirst(joinitems[levels_needed]);
+ Assert(length(rel->relids) == length(root->base_rel_list));
return rel;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.94 2000/08/24 03:29:04 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.95 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
List *clausegroup = lfirst(i);
IndexPath *pathnode = makeNode(IndexPath);
- List *indexquals;
+ List *indexquals = NIL;
+ bool alljoinquals = true;
+ List *temp;
/* XXX this code ought to be merged with create_index_path? */
*/
pathnode->path.pathkeys = NIL;
- indexquals = get_actual_clauses(clausegroup);
+ /* extract bare indexqual clauses, check whether all from JOIN/ON */
+ foreach(temp, clausegroup)
+ {
+ RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
+
+ indexquals = lappend(indexquals, clause->clause);
+ if (! clause->isjoinqual)
+ alljoinquals = false;
+ }
+
/* expand special operators to indexquals the executor can handle */
indexquals = expand_indexqual_conditions(indexquals);
/* joinrelids saves the rels needed on the outer side of the join */
pathnode->joinrelids = lfirst(outerrelids_list);
+ pathnode->alljoinquals = alljoinquals;
+
/*
* We must compute the estimated number of output rows for the
* indexscan. This is less than rel->rows because of the
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.55 2000/05/30 00:49:47 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.56 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "utils/lsyscache.h"
static void sort_inner_and_outer(Query *root, RelOptInfo *joinrel,
- RelOptInfo *outerrel, RelOptInfo *innerrel,
- List *restrictlist, List *mergeclause_list);
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, List *mergeclause_list,
+ JoinType jointype);
static void match_unsorted_outer(Query *root, RelOptInfo *joinrel,
- RelOptInfo *outerrel, RelOptInfo *innerrel,
- List *restrictlist, List *mergeclause_list);
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, List *mergeclause_list,
+ JoinType jointype);
#ifdef NOT_USED
static void match_unsorted_inner(Query *root, RelOptInfo *joinrel,
- RelOptInfo *outerrel, RelOptInfo *innerrel,
- List *restrictlist, List *mergeclause_list);
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, List *mergeclause_list,
+ JoinType jointype);
#endif
static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel,
- RelOptInfo *outerrel, RelOptInfo *innerrel,
- List *restrictlist);
-static Path *best_innerjoin(List *join_paths, List *outer_relid);
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype);
+static Path *best_innerjoin(List *join_paths, List *outer_relid,
+ JoinType jointype);
static Selectivity estimate_disbursion(Query *root, Var *var);
static List *select_mergejoin_clauses(RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist);
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype);
/*
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
+ JoinType jointype,
List *restrictlist)
{
List *mergeclause_list = NIL;
mergeclause_list = select_mergejoin_clauses(joinrel,
outerrel,
innerrel,
- restrictlist);
+ restrictlist,
+ jointype);
/*
* 1. Consider mergejoin paths where both relations must be explicitly
* sorted.
*/
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
- restrictlist, mergeclause_list);
+ restrictlist, mergeclause_list, jointype);
/*
* 2. Consider paths where the outer relation need not be explicitly
* path is already ordered.
*/
match_unsorted_outer(root, joinrel, outerrel, innerrel,
- restrictlist, mergeclause_list);
+ restrictlist, mergeclause_list, jointype);
#ifdef NOT_USED
* other order.
*/
match_unsorted_inner(root, joinrel, outerrel, innerrel,
- restrictlist, mergeclause_list);
+ restrictlist, mergeclause_list, jointype);
#endif
/*
*/
if (enable_hashjoin)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
- restrictlist);
+ restrictlist, jointype);
}
/*
* clauses that apply to this join
* 'mergeclause_list' is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
+ * 'jointype' is the type of join to do
*/
static void
sort_inner_and_outer(Query *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
- List *mergeclause_list)
+ List *mergeclause_list,
+ JoinType jointype)
{
List *i;
*/
outerkeys = make_pathkeys_for_mergeclauses(root,
curclause_list,
- outerrel->targetlist);
+ outerrel);
innerkeys = make_pathkeys_for_mergeclauses(root,
curclause_list,
- innerrel->targetlist);
+ innerrel);
/* Build pathkeys representing output sort order. */
merge_pathkeys = build_join_pathkeys(outerkeys,
joinrel->targetlist,
*/
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
outerrel->cheapest_total_path,
innerrel->cheapest_total_path,
restrictlist,
* clauses that apply to this join
* 'mergeclause_list' is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
+ * 'jointype' is the type of join to do
*/
static void
match_unsorted_outer(Query *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
- List *mergeclause_list)
+ List *mergeclause_list,
+ JoinType jointype)
{
+ bool nestjoinOK;
Path *bestinnerjoin;
List *i;
+ /*
+ * Nestloop only supports inner and left joins.
+ */
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ case JOIN_LEFT:
+ nestjoinOK = true;
+ break;
+ default:
+ nestjoinOK = false;
+ break;
+ }
+
/*
* Get the best innerjoin indexpath (if any) for this outer rel. It's
* the same for all outer paths.
*/
- bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids);
+ bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids,
+ jointype);
foreach(i, outerrel->pathlist)
{
joinrel->targetlist,
root->equi_key_list);
- /*
- * Always consider a nestloop join with this outer and cheapest-
- * total-cost inner. Consider nestloops using the cheapest-
- * startup-cost inner as well, and the best innerjoin indexpath.
- */
- add_path(joinrel, (Path *)
- create_nestloop_path(joinrel,
- outerpath,
- innerrel->cheapest_total_path,
- restrictlist,
- merge_pathkeys));
- if (innerrel->cheapest_startup_path != innerrel->cheapest_total_path)
- add_path(joinrel, (Path *)
- create_nestloop_path(joinrel,
- outerpath,
- innerrel->cheapest_startup_path,
- restrictlist,
- merge_pathkeys));
- if (bestinnerjoin != NULL)
+ if (nestjoinOK)
+ {
+ /*
+ * Always consider a nestloop join with this outer and cheapest-
+ * total-cost inner. Consider nestloops using the cheapest-
+ * startup-cost inner as well, and the best innerjoin indexpath.
+ */
add_path(joinrel, (Path *)
create_nestloop_path(joinrel,
+ jointype,
outerpath,
- bestinnerjoin,
+ innerrel->cheapest_total_path,
restrictlist,
merge_pathkeys));
+ if (innerrel->cheapest_startup_path !=
+ innerrel->cheapest_total_path)
+ add_path(joinrel, (Path *)
+ create_nestloop_path(joinrel,
+ jointype,
+ outerpath,
+ innerrel->cheapest_startup_path,
+ restrictlist,
+ merge_pathkeys));
+ if (bestinnerjoin != NULL)
+ add_path(joinrel, (Path *)
+ create_nestloop_path(joinrel,
+ jointype,
+ outerpath,
+ bestinnerjoin,
+ restrictlist,
+ merge_pathkeys));
+ }
/* Look for useful mergeclauses (if any) */
mergeclauses = find_mergeclauses_for_pathkeys(outerpath->pathkeys,
/* Compute the required ordering of the inner path */
innersortkeys = make_pathkeys_for_mergeclauses(root,
mergeclauses,
- innerrel->targetlist);
+ innerrel);
/*
* Generate a mergejoin on the basis of sorting the cheapest
*/
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
outerpath,
innerrel->cheapest_total_path,
restrictlist,
newclauses = mergeclauses;
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
outerpath,
innerpath,
restrictlist,
}
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
outerpath,
innerpath,
restrictlist,
* clauses that apply to this join
* 'mergeclause_list' is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
+ * 'jointype' is the type of join to do
*/
static void
match_unsorted_inner(Query *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
- List *mergeclause_list)
+ List *mergeclause_list,
+ JoinType jointype)
{
List *i;
/* Compute the required ordering of the outer path */
outersortkeys = make_pathkeys_for_mergeclauses(root,
mergeclauses,
- outerrel->targetlist);
+ outerrel);
/*
* Generate a mergejoin on the basis of sorting the cheapest
root->equi_key_list);
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
outerrel->cheapest_total_path,
innerpath,
restrictlist,
root->equi_key_list);
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
totalouterpath,
innerpath,
restrictlist,
root->equi_key_list);
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
+ jointype,
startupouterpath,
innerpath,
restrictlist,
* 'innerrel' is the inner join relation
* 'restrictlist' contains all of the RestrictInfo nodes for restriction
* clauses that apply to this join
+ * 'jointype' is the type of join to do
*/
static void
hash_inner_and_outer(Query *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
- List *restrictlist)
+ List *restrictlist,
+ JoinType jointype)
{
Relids outerrelids = outerrel->relids;
Relids innerrelids = innerrel->relids;
+ bool isouterjoin;
List *i;
+ /*
+ * Hashjoin only supports inner and left joins.
+ */
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ isouterjoin = false;
+ break;
+ case JOIN_LEFT:
+ isouterjoin = true;
+ break;
+ default:
+ return;
+ }
+
/*
* Scan the join's restrictinfo list to find hashjoinable clauses that
* are usable with this pair of sub-relations. Since we currently
if (restrictinfo->hashjoinoperator == InvalidOid)
continue; /* not hashjoinable */
+ /*
+ * If processing an outer join, only use explicit join clauses for
+ * hashing. For inner joins we need not be so picky.
+ */
+ if (isouterjoin && !restrictinfo->isjoinqual)
+ continue;
+
clause = restrictinfo->clause;
/* these must be OK, since check_hashjoinable accepted the clause */
left = get_leftop(clause);
*/
add_path(joinrel, (Path *)
create_hashjoin_path(joinrel,
+ jointype,
outerrel->cheapest_total_path,
innerrel->cheapest_total_path,
restrictlist,
if (outerrel->cheapest_startup_path != outerrel->cheapest_total_path)
add_path(joinrel, (Path *)
create_hashjoin_path(joinrel,
+ jointype,
outerrel->cheapest_startup_path,
innerrel->cheapest_total_path,
restrictlist,
* usable path.
*/
static Path *
-best_innerjoin(List *join_paths, Relids outer_relids)
+best_innerjoin(List *join_paths, Relids outer_relids, JoinType jointype)
{
Path *cheapest = (Path *) NULL;
+ bool isouterjoin;
List *join_path;
+ /*
+ * Nestloop only supports inner and left joins.
+ */
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ isouterjoin = false;
+ break;
+ case JOIN_LEFT:
+ isouterjoin = true;
+ break;
+ default:
+ return NULL;
+ }
+
foreach(join_path, join_paths)
{
- Path *path = (Path *) lfirst(join_path);
+ IndexPath *path = (IndexPath *) lfirst(join_path);
Assert(IsA(path, IndexPath));
+ /*
+ * If processing an outer join, only use explicit join clauses in the
+ * inner indexscan. For inner joins we need not be so picky.
+ */
+ if (isouterjoin && !path->alljoinquals)
+ continue;
+
/*
* path->joinrelids is the set of base rels that must be part of
* outer_relids in order to use this inner path, because those
* rels are used in the index join quals of this inner path.
*/
- if (is_subseti(((IndexPath *) path)->joinrelids, outer_relids) &&
+ if (is_subseti(path->joinrelids, outer_relids) &&
(cheapest == NULL ||
- compare_path_costs(path, cheapest, TOTAL_COST) < 0))
- cheapest = path;
+ compare_path_costs((Path *) path, cheapest, TOTAL_COST) < 0))
+ cheapest = (Path *) path;
}
return cheapest;
}
relid = getrelid(var->varno, root->rtable);
+ if (relid == InvalidOid)
+ return 0.1;
+
return (Selectivity) get_attdisbursion(relid, var->varattno, 0.1);
}
select_mergejoin_clauses(RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
- List *restrictlist)
+ List *restrictlist,
+ JoinType jointype)
{
List *result_list = NIL;
Relids outerrelids = outerrel->relids;
Relids innerrelids = innerrel->relids;
+ bool isouterjoin = IS_OUTER_JOIN(jointype);
List *i;
foreach(i, restrictlist)
Var *left,
*right;
+ /*
+ * If processing an outer join, only use explicit join clauses in the
+ * merge. For inner joins we need not be so picky.
+ *
+ * Furthermore, if it is a right/full join then *all* the explicit
+ * join clauses must be mergejoinable, else the executor will fail.
+ * If we are asked for a right join then just return NIL to indicate
+ * no mergejoin is possible (we can handle it as a left join instead).
+ * If we are asked for a full join then emit an error, because there
+ * is no fallback.
+ */
+ if (isouterjoin)
+ {
+ if (!restrictinfo->isjoinqual)
+ continue;
+ switch (jointype)
+ {
+ case JOIN_RIGHT:
+ if (restrictinfo->mergejoinoperator == InvalidOid)
+ return NIL; /* not mergejoinable */
+ break;
+ case JOIN_FULL:
+ if (restrictinfo->mergejoinoperator == InvalidOid)
+ elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
+ break;
+ default:
+ /* otherwise, it's OK to have nonmergeable join quals */
+ break;
+ }
+ }
+
if (restrictinfo->mergejoinoperator == InvalidOid)
continue; /* not mergejoinable */
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.46 2000/05/30 00:49:47 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.47 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static RelOptInfo *make_join_rel(Query *root, RelOptInfo *rel1,
- RelOptInfo *rel2);
+ RelOptInfo *rel2, JoinType jointype);
/*
* make_rels_by_joins
* Consider ways to produce join relations containing exactly 'level'
- * base relations. (This is one step of the dynamic-programming method
+ * jointree items. (This is one step of the dynamic-programming method
* embodied in make_one_rel_by_joins.) Join rel nodes for each feasible
- * combination of base rels are created and added to the front of the
- * query's join_rel_list. Implementation paths are created for each
- * such joinrel, too.
+ * combination of lower-level rels are created and returned in a list.
+ * Implementation paths are created for each such joinrel, too.
*
- * Returns nothing, but adds entries to root->join_rel_list.
+ * level: level of rels we want to make this time.
+ * joinrels[j], 1 <= j < level, is a list of rels containing j items.
*/
-void
-make_rels_by_joins(Query *root, int level)
+List *
+make_rels_by_joins(Query *root, int level, List **joinrels)
{
- List *first_old_rel = root->join_rel_list;
+ List *result_rels = NIL;
+ List *new_rels;
+ List *nr;
List *r;
+ int k;
/*
* First, consider left-sided and right-sided plans, in which rels of
- * exactly level-1 member relations are joined against base relations.
- * We prefer to join using join clauses, but if we find a rel of
- * level-1 members that has no join clauses, we will generate
- * Cartesian-product joins against all base rels not already contained
- * in it.
+ * exactly level-1 member relations are joined against initial relations.
+ * We prefer to join using join clauses, but if we find a rel of level-1
+ * members that has no join clauses, we will generate Cartesian-product
+ * joins against all initial rels not already contained in it.
*
- * In the first pass (level == 2), we try to join each base rel to each
- * base rel that appears later in base_rel_list. (The mirror-image
+ * In the first pass (level == 2), we try to join each initial rel to each
+ * initial rel that appears later in joinrels[1]. (The mirror-image
* joins are handled automatically by make_join_rel.) In later
- * passes, we try to join rels of size level-1 from join_rel_list to
- * each base rel in base_rel_list.
- *
- * We assume that the rels already present in join_rel_list appear in
- * decreasing order of level (number of members). This should be true
- * since we always add new higher-level rels to the front of the list.
+ * passes, we try to join rels of size level-1 from joinrels[level-1]
+ * to each initial rel in joinrels[1].
*/
- if (level == 2)
- r = root->base_rel_list;/* level-1 is base rels */
- else
- r = root->join_rel_list;
- for (; r != NIL; r = lnext(r))
+ foreach(r, joinrels[level-1])
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
- int old_level = length(old_rel->relids);
List *other_rels;
- if (old_level != level - 1)
- break;
-
if (level == 2)
- other_rels = lnext(r); /* only consider remaining base
+ other_rels = lnext(r); /* only consider remaining initial
* rels */
else
- other_rels = root->base_rel_list; /* consider all base rels */
+ other_rels = joinrels[1]; /* consider all initial rels */
if (old_rel->joininfo != NIL)
{
* have those other rels collected into a join rel. See also
* the last-ditch case below.
*/
- make_rels_by_clause_joins(root,
- old_rel,
- other_rels);
+ new_rels = make_rels_by_clause_joins(root,
+ old_rel,
+ other_rels);
}
else
{
* Oops, we have a relation that is not joined to any other
* relation. Cartesian product time.
*/
- make_rels_by_clauseless_joins(root,
- old_rel,
- other_rels);
+ new_rels = make_rels_by_clauseless_joins(root,
+ old_rel,
+ other_rels);
+ }
+
+ /*
+ * At levels above 2 we will generate the same joined relation
+ * in multiple ways --- for example (a join b) join c is the same
+ * RelOptInfo as (b join c) join a, though the second case will
+ * add a different set of Paths to it. To avoid making extra work
+ * for subsequent passes, do not enter the same RelOptInfo into our
+ * output list multiple times.
+ */
+ foreach(nr, new_rels)
+ {
+ RelOptInfo *jrel = (RelOptInfo *) lfirst(nr);
+
+ if (!ptrMember(jrel, result_rels))
+ result_rels = lcons(jrel, result_rels);
}
}
/*
- * Now, consider "bushy plans" in which relations of k base rels are
- * joined to relations of level-k base rels, for 2 <= k <= level-2.
- * The previous loop left r pointing to the first rel of level
- * level-2.
+ * Now, consider "bushy plans" in which relations of k initial rels are
+ * joined to relations of level-k initial rels, for 2 <= k <= level-2.
*
* We only consider bushy-plan joins for pairs of rels where there is a
* suitable join clause, in order to avoid unreasonable growth of
* planning time.
*/
- for (; r != NIL; r = lnext(r))
+ for (k = 2; ; k++)
{
- RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
- int old_level = length(old_rel->relids);
- List *r2;
+ int other_level = level - k;
/*
- * We can quit once past the halfway point (make_join_rel took
- * care of making the opposite-direction joins)
+ * Since make_join_rel(x, y) handles both x,y and y,x cases,
+ * we only need to go as far as the halfway point.
*/
- if (old_level * 2 < level)
+ if (k > other_level)
break;
- if (old_rel->joininfo == NIL)
- continue; /* we ignore clauseless joins here */
-
- foreach(r2, lnext(r))
+ foreach(r, joinrels[k])
{
- RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
- int new_level = length(new_rel->relids);
-
- if (old_level + new_level > level)
- continue; /* scan down to new_rels of right size */
- if (old_level + new_level < level)
- break; /* no more new_rels of right size */
- if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
+ RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+ List *other_rels;
+ List *r2;
+
+ if (old_rel->joininfo == NIL)
+ continue; /* we ignore clauseless joins here */
+
+ if (k == other_level)
+ other_rels = lnext(r); /* only consider remaining rels */
+ else
+ other_rels = joinrels[other_level];
+
+ foreach(r2, other_rels)
{
- List *i;
-
- /*
- * OK, we can build a rel of the right level from this
- * pair of rels. Do so if there is at least one usable
- * join clause.
- */
- foreach(i, old_rel->joininfo)
- {
- JoinInfo *joininfo = (JoinInfo *) lfirst(i);
+ RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
- if (is_subseti(joininfo->unjoined_relids, new_rel->relids))
+ if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
+ {
+ List *i;
+
+ /*
+ * OK, we can build a rel of the right level from this
+ * pair of rels. Do so if there is at least one usable
+ * join clause.
+ */
+ foreach(i, old_rel->joininfo)
{
- make_join_rel(root, old_rel, new_rel);
- break;
+ JoinInfo *joininfo = (JoinInfo *) lfirst(i);
+
+ if (is_subseti(joininfo->unjoined_relids,
+ new_rel->relids))
+ {
+ RelOptInfo *jrel;
+
+ jrel = make_join_rel(root, old_rel, new_rel,
+ JOIN_INNER);
+ /* Avoid making duplicate entries ... */
+ if (!ptrMember(jrel, result_rels))
+ result_rels = lcons(jrel, result_rels);
+ break; /* need not consider more joininfos */
+ }
}
}
}
* no choice but to make cartesian joins. We consider only left-sided
* and right-sided cartesian joins in this case (no bushy).
*/
- if (root->join_rel_list == first_old_rel)
+ if (result_rels == NIL)
{
/* This loop is just like the first one, except we always call
* make_rels_by_clauseless_joins().
*/
- if (level == 2)
- r = root->base_rel_list; /* level-1 is base rels */
- else
- r = root->join_rel_list;
- for (; r != NIL; r = lnext(r))
+ foreach(r, joinrels[level-1])
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
- int old_level = length(old_rel->relids);
List *other_rels;
- if (old_level != level - 1)
- break;
-
if (level == 2)
- other_rels = lnext(r); /* only consider remaining base
+ other_rels = lnext(r); /* only consider remaining initial
* rels */
else
- other_rels = root->base_rel_list; /* consider all base rels */
+ other_rels = joinrels[1]; /* consider all initial rels */
+
+ new_rels = make_rels_by_clauseless_joins(root,
+ old_rel,
+ other_rels);
+
+ foreach(nr, new_rels)
+ {
+ RelOptInfo *jrel = (RelOptInfo *) lfirst(nr);
- make_rels_by_clauseless_joins(root,
- old_rel,
- other_rels);
+ if (!ptrMember(jrel, result_rels))
+ result_rels = lcons(jrel, result_rels);
+ }
}
- if (root->join_rel_list == first_old_rel)
+ if (result_rels == NIL)
elog(ERROR, "make_rels_by_joins: failed to build any %d-way joins",
level);
}
+
+ return result_rels;
}
/*
* Build joins between the given relation 'old_rel' and other relations
* that are mentioned within old_rel's joininfo nodes (i.e., relations
* that participate in join clauses that 'old_rel' also participates in).
- * The join rel nodes are added to root->join_rel_list.
+ * The join rel nodes are returned in a list.
*
* 'old_rel' is the relation entry for the relation to be joined
* 'other_rels': other rels to be considered for joining
*
- * Currently, this is only used with base rels in other_rels, but it would
- * work for joining to joinrels too, if the caller ensures there is no
+ * Currently, this is only used with initial rels in other_rels, but it
+ * will work for joining to joinrels too, if the caller ensures there is no
* membership overlap between old_rel and the rels in other_rels. (We need
- * no extra test for overlap for base rels, since the is_subset test can
+ * no extra test for overlap for initial rels, since the is_subset test can
* only succeed when other_rel is not already part of old_rel.)
- *
- * Returns NULL if no suitable joins were found, else the last suitable
- * joinrel processed. (The only caller who checks the return value is
- * geqo_eval.c, and it sets things up so there can be no more than one
- * "suitable" joinrel; so we don't bother with returning a list.)
*/
-RelOptInfo *
+List *
make_rels_by_clause_joins(Query *root,
RelOptInfo *old_rel,
List *other_rels)
{
- RelOptInfo *result = NULL;
+ List *result = NIL;
List *i,
*j;
RelOptInfo *other_rel = (RelOptInfo *) lfirst(j);
if (is_subseti(unjoined_relids, other_rel->relids))
- result = make_join_rel(root, old_rel, other_rel);
+ result = lcons(make_join_rel(root, old_rel, other_rel,
+ JOIN_INNER),
+ result);
}
}
* Given a relation 'old_rel' and a list of other relations
* 'other_rels', create a join relation between 'old_rel' and each
* member of 'other_rels' that isn't already included in 'old_rel'.
+ * The join rel nodes are returned in a list.
*
* 'old_rel' is the relation entry for the relation to be joined
* 'other_rels': other rels to be considered for joining
*
- * Currently, this is only used with base rels in other_rels, but it would
+ * Currently, this is only used with initial rels in other_rels, but it would
* work for joining to joinrels too.
- *
- * Returns NULL if no suitable joins were found, else the last suitable
- * joinrel processed. (The only caller who checks the return value is
- * geqo_eval.c, and it sets things up so there can be no more than one
- * "suitable" joinrel; so we don't bother with returning a list.)
*/
-RelOptInfo *
+List *
make_rels_by_clauseless_joins(Query *root,
RelOptInfo *old_rel,
List *other_rels)
{
- RelOptInfo *result = NULL;
+ List *result = NIL;
List *i;
foreach(i, other_rels)
RelOptInfo *other_rel = (RelOptInfo *) lfirst(i);
if (nonoverlap_setsi(other_rel->relids, old_rel->relids))
- result = make_join_rel(root, old_rel, other_rel);
+ result = lcons(make_join_rel(root, old_rel, other_rel,
+ JOIN_INNER),
+ result);
}
return result;
}
+/*
+ * make_rel_from_jointree
+ * Find or build a RelOptInfojoin rel representing a specific
+ * jointree item. For JoinExprs, we only consider the construction
+ * path that corresponds exactly to what the user wrote.
+ */
+RelOptInfo *
+make_rel_from_jointree(Query *root, Node *jtnode)
+{
+ if (IsA(jtnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+
+ return get_base_rel(root, varno);
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+ RelOptInfo *rel,
+ *lrel,
+ *rrel;
+
+ /* Recurse */
+ lrel = make_rel_from_jointree(root, j->larg);
+ rrel = make_rel_from_jointree(root, j->rarg);
+
+ /* Make this join rel */
+ rel = make_join_rel(root, lrel, rrel, j->jointype);
+
+ /*
+ * Since we are only going to consider this one way to do it,
+ * we're done generating Paths for this joinrel and can now select
+ * the cheapest. In fact we *must* do so now, since next level up
+ * will need it!
+ */
+ set_cheapest(rel);
+
+ return rel;
+ }
+ else
+ elog(ERROR, "make_rel_from_jointree: unexpected node type %d",
+ nodeTag(jtnode));
+ return NULL; /* keep compiler quiet */
+}
+
+
/*
* make_join_rel
* Find or create a join RelOptInfo that represents the join of
* created with the two rels as outer and inner rel.
* (The join rel may already contain paths generated from other
* pairs of rels that add up to the same set of base rels.)
- * The join rel is stored in the query's join_rel_list.
*/
static RelOptInfo *
-make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
+make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2,
+ JoinType jointype)
{
RelOptInfo *joinrel;
List *restrictlist;
joinrel = get_join_rel(root, rel1, rel2, &restrictlist);
/*
- * We consider paths using each rel as both outer and inner.
+ * Consider paths using each rel as both outer and inner.
*/
- add_paths_to_joinrel(root, joinrel, rel1, rel2, restrictlist);
- add_paths_to_joinrel(root, joinrel, rel2, rel1, restrictlist);
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER,
+ restrictlist);
+ add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER,
+ restrictlist);
+ break;
+ case JOIN_LEFT:
+ add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT,
+ restrictlist);
+ add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT,
+ restrictlist);
+ break;
+ case JOIN_FULL:
+ add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL,
+ restrictlist);
+ add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL,
+ restrictlist);
+ break;
+ case JOIN_RIGHT:
+ add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_RIGHT,
+ restrictlist);
+ add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_LEFT,
+ restrictlist);
+ break;
+ default:
+ elog(ERROR, "make_join_rel: unsupported join type %d",
+ (int) jointype);
+ break;
+ }
return joinrel;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.40 2000/05/30 00:49:47 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.41 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* This isn't a nestloop innerjoin, so: */
pathnode->joinrelids = NIL; /* no join clauses here */
+ pathnode->alljoinquals = false;
pathnode->rows = rel->rows;
best_or_subclause_indices(root,
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.24 2000/08/08 15:41:31 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.25 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*
* 'mergeclauses' is a list of RestrictInfos for mergejoin clauses
* that will be used in a merge join.
- * 'tlist' is a relation target list for either the inner or outer
- * side of the proposed join rel. (Not actually needed anymore)
+ * 'rel' is the relation the pathkeys will apply to (ie, either the inner
+ * or outer side of the proposed join rel).
*
* Returns a pathkeys list that can be applied to the indicated relation.
*
List *
make_pathkeys_for_mergeclauses(Query *root,
List *mergeclauses,
- List *tlist)
+ RelOptInfo *rel)
{
List *pathkeys = NIL;
List *i;
Assert(restrictinfo->mergejoinoperator != InvalidOid);
/*
- * Find the key and sortop needed for this mergeclause.
- *
- * Both sides of the mergeclause should appear in one of the query's
- * pathkey equivalence classes, so it doesn't matter which one we
- * use here.
+ * Which key and sortop is needed for this relation?
*/
key = (Node *) get_leftop(restrictinfo->clause);
sortop = restrictinfo->left_sortop;
+ if (!IsA(key, Var) ||
+ !intMember(((Var *) key)->varno, rel->relids))
+ {
+ key = (Node *) get_rightop(restrictinfo->clause);
+ sortop = restrictinfo->right_sortop;
+ if (!IsA(key, Var) ||
+ !intMember(((Var *) key)->varno, rel->relids))
+ elog(ERROR, "make_pathkeys_for_mergeclauses: can't identify which side of mergeclause to use");
+ }
/*
- * Find pathkey sublist for this sort item. We expect to find the
- * canonical set including the mergeclause's left and right sides;
- * if we get back just the one item, something is rotten.
+ * Find or create canonical pathkey sublist for this sort item.
*/
item = makePathKeyItem(key, sortop);
pathkey = make_canonical_pathkey(root, item);
- Assert(length(pathkey) > 1);
/*
- * Since the item we just made is not in the returned canonical
- * set, we can free it --- this saves a useful amount of storage
- * in a big join tree.
+ * Most of the time we will get back a canonical pathkey set
+ * including both the mergeclause's left and right sides (the only
+ * case where we don't is if the mergeclause appeared in an OUTER
+ * JOIN, which causes us not to generate an equijoin set from it).
+ * Therefore, most of the time the item we just made is not part
+ * of the returned structure, and we can free it. This check
+ * saves a useful amount of storage in a big join tree.
*/
- pfree(item);
+ if (item != (PathKeyItem *) lfirst(pathkey))
+ pfree(item);
pathkeys = lappend(pathkeys, pathkey);
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.95 2000/08/13 02:50:06 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.96 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static TidScan *create_tidscan_node(TidPath *best_path, List *tlist,
List *scan_clauses);
static NestLoop *create_nestloop_node(NestPath *best_path, List *tlist,
- List *clauses, Plan *outer_node, List *outer_tlist,
- Plan *inner_node, List *inner_tlist);
+ List *joinclauses, List *otherclauses,
+ Plan *outer_node, List *outer_tlist,
+ Plan *inner_node, List *inner_tlist);
static MergeJoin *create_mergejoin_node(MergePath *best_path, List *tlist,
- List *clauses, Plan *outer_node, List *outer_tlist,
- Plan *inner_node, List *inner_tlist);
+ List *joinclauses, List *otherclauses,
+ Plan *outer_node, List *outer_tlist,
+ Plan *inner_node, List *inner_tlist);
static HashJoin *create_hashjoin_node(HashPath *best_path, List *tlist,
- List *clauses, Plan *outer_node, List *outer_tlist,
- Plan *inner_node, List *inner_tlist);
+ List *joinclauses, List *otherclauses,
+ Plan *outer_node, List *outer_tlist,
+ Plan *inner_node, List *inner_tlist);
static List *fix_indxqual_references(List *indexquals, IndexPath *index_path);
static List *fix_indxqual_sublist(List *indexqual, int baserelid, Oid relam,
Form_pg_index index);
static Node *fix_indxqual_operand(Node *node, int baserelid,
Form_pg_index index,
Oid *opclass);
+static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
List *indxid, List *indxqual,
List *indxqualorig,
ScanDirection indexscandir);
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tideval);
-static NestLoop *make_nestloop(List *qptlist, List *qpqual, Plan *lefttree,
- Plan *righttree);
-static HashJoin *make_hashjoin(List *tlist, List *qpqual,
- List *hashclauses, Plan *lefttree, Plan *righttree);
+static NestLoop *make_nestloop(List *tlist,
+ List *joinclauses, List *otherclauses,
+ Plan *lefttree, Plan *righttree,
+ JoinType jointype);
+static HashJoin *make_hashjoin(List *tlist,
+ List *joinclauses, List *otherclauses,
+ List *hashclauses,
+ Plan *lefttree, Plan *righttree,
+ JoinType jointype);
static Hash *make_hash(List *tlist, Node *hashkey, Plan *lefttree);
-static MergeJoin *make_mergejoin(List *tlist, List *qpqual,
- List *mergeclauses, Plan *righttree, Plan *lefttree);
+static MergeJoin *make_mergejoin(List *tlist,
+ List *joinclauses, List *otherclauses,
+ List *mergeclauses,
+ Plan *lefttree, Plan *righttree,
+ JoinType jointype);
static void copy_path_costsize(Plan *dest, Path *src);
static void copy_plan_costsize(Plan *dest, Plan *src);
-static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
/*
* create_plan
List *outer_tlist;
Plan *inner_node;
List *inner_tlist;
- List *clauses;
+ List *joinclauses;
+ List *otherclauses;
Join *retval = NULL;
outer_node = create_plan(root, best_path->outerjoinpath);
inner_node = create_plan(root, best_path->innerjoinpath);
inner_tlist = inner_node->targetlist;
- clauses = get_actual_clauses(best_path->joinrestrictinfo);
+ if (IS_OUTER_JOIN(best_path->jointype))
+ {
+ get_actual_join_clauses(best_path->joinrestrictinfo,
+ &joinclauses, &otherclauses);
+ }
+ else
+ {
+ /* We can treat all clauses alike for an inner join */
+ joinclauses = get_actual_clauses(best_path->joinrestrictinfo);
+ otherclauses = NIL;
+ }
switch (best_path->path.pathtype)
{
case T_MergeJoin:
retval = (Join *) create_mergejoin_node((MergePath *) best_path,
tlist,
- clauses,
+ joinclauses,
+ otherclauses,
outer_node,
outer_tlist,
inner_node,
case T_HashJoin:
retval = (Join *) create_hashjoin_node((HashPath *) best_path,
tlist,
- clauses,
+ joinclauses,
+ otherclauses,
outer_node,
outer_tlist,
inner_node,
case T_NestLoop:
retval = (Join *) create_nestloop_node((NestPath *) best_path,
tlist,
- clauses,
+ joinclauses,
+ otherclauses,
outer_node,
outer_tlist,
inner_node,
return scan_node;
}
-static TidScan *
-make_tidscan(List *qptlist,
- List *qpqual,
- Index scanrelid,
- List *tideval)
-{
- TidScan *node = makeNode(TidScan);
- Plan *plan = &node->scan.plan;
-
- /* cost should be inserted by caller */
- plan->state = (EState *) NULL;
- plan->targetlist = qptlist;
- plan->qual = qpqual;
- plan->lefttree = NULL;
- plan->righttree = NULL;
- node->scan.scanrelid = scanrelid;
- node->tideval = copyObject(tideval); /* XXX do we really need a
- * copy? */
- node->needRescan = false;
- node->scan.scanstate = (CommonScanState *) NULL;
-
- return node;
-}
-
/*
* create_tidscan_node
* Returns a tidscan node for the base relation scanned by 'best_path'
static NestLoop *
create_nestloop_node(NestPath *best_path,
List *tlist,
- List *clauses,
+ List *joinclauses,
+ List *otherclauses,
Plan *outer_node,
List *outer_tlist,
Plan *inner_node,
* attnos, and may have been commuted as well).
*/
if (length(indxqualorig) == 1) /* single indexscan? */
- clauses = set_difference(clauses, lfirst(indxqualorig));
+ joinclauses = set_difference(joinclauses,
+ lfirst(indxqualorig));
/* only refs to outer vars get changed in the inner indexqual */
innerscan->indxqualorig = join_references(indxqualorig,
inner_node);
}
+ /*
+ * Set quals to contain INNER/OUTER var references.
+ */
+ joinclauses = join_references(joinclauses,
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
+ otherclauses = join_references(otherclauses,
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
+
join_node = make_nestloop(tlist,
- join_references(clauses,
- outer_tlist,
- inner_tlist,
- (Index) 0),
+ joinclauses,
+ otherclauses,
outer_node,
- inner_node);
+ inner_node,
+ best_path->jointype);
- copy_path_costsize(&join_node->join, &best_path->path);
+ copy_path_costsize(&join_node->join.plan, &best_path->path);
return join_node;
}
static MergeJoin *
create_mergejoin_node(MergePath *best_path,
List *tlist,
- List *clauses,
+ List *joinclauses,
+ List *otherclauses,
Plan *outer_node,
List *outer_tlist,
Plan *inner_node,
List *inner_tlist)
{
- List *qpqual,
- *mergeclauses;
+ List *mergeclauses;
MergeJoin *join_node;
mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
* the list of quals that must be checked as qpquals. Set those
* clauses to contain INNER/OUTER var references.
*/
- qpqual = join_references(set_difference(clauses, mergeclauses),
- outer_tlist,
- inner_tlist,
- (Index) 0);
+ joinclauses = join_references(set_difference(joinclauses, mergeclauses),
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
+
+ /*
+ * Fix the additional qpquals too.
+ */
+ otherclauses = join_references(otherclauses,
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
/*
* Now set the references in the mergeclauses and rearrange them so
inner_node,
best_path->innersortkeys);
+ /*
+ * The executor requires the inner side of a mergejoin to support "mark"
+ * and "restore" operations. Not all plan types do, so we must be careful
+ * not to generate an invalid plan. If necessary, an invalid inner plan
+ * can be handled by inserting a Materialize node.
+ *
+ * Since the inner side must be ordered, and only Sorts and IndexScans can
+ * create order to begin with, you might think there's no problem --- but
+ * you'd be wrong. Nestloop and merge joins can *preserve* the order of
+ * their inputs, so they can be selected as the input of a mergejoin,
+ * and that won't work in the present executor.
+ *
+ * Doing this here is a bit of a kluge since the cost of the Materialize
+ * wasn't taken into account in our earlier decisions. But Materialize
+ * is hard to estimate a cost for, and the above consideration shows that
+ * this is a rare case anyway, so this seems an acceptable way to proceed.
+ *
+ * This check must agree with ExecMarkPos/ExecRestrPos in
+ * executor/execAmi.c!
+ */
+ switch (nodeTag(inner_node))
+ {
+ case T_SeqScan:
+ case T_IndexScan:
+ case T_Material:
+ case T_Sort:
+ /* OK, these inner plans support mark/restore */
+ break;
+
+ default:
+ /* Ooops, need to materialize the inner plan */
+ inner_node = (Plan *) make_material(inner_tlist,
+ inner_node);
+ break;
+ }
+
+ /*
+ * Now we can build the mergejoin node.
+ */
join_node = make_mergejoin(tlist,
- qpqual,
+ joinclauses,
+ otherclauses,
mergeclauses,
+ outer_node,
inner_node,
- outer_node);
+ best_path->jpath.jointype);
- copy_path_costsize(&join_node->join, &best_path->jpath.path);
+ copy_path_costsize(&join_node->join.plan, &best_path->jpath.path);
return join_node;
}
static HashJoin *
create_hashjoin_node(HashPath *best_path,
List *tlist,
- List *clauses,
+ List *joinclauses,
+ List *otherclauses,
Plan *outer_node,
List *outer_tlist,
Plan *inner_node,
List *inner_tlist)
{
- List *qpqual;
List *hashclauses;
HashJoin *join_node;
Hash *hash_node;
* the list of quals that must be checked as qpquals. Set those
* clauses to contain INNER/OUTER var references.
*/
- qpqual = join_references(set_difference(clauses, hashclauses),
- outer_tlist,
- inner_tlist,
- (Index) 0);
+ joinclauses = join_references(set_difference(joinclauses, hashclauses),
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
+
+ /*
+ * Fix the additional qpquals too.
+ */
+ otherclauses = join_references(otherclauses,
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
/*
* Now set the references in the hashclauses and rearrange them so
*/
hash_node = make_hash(inner_tlist, innerhashkey, inner_node);
join_node = make_hashjoin(tlist,
- qpqual,
+ joinclauses,
+ otherclauses,
hashclauses,
outer_node,
- (Plan *) hash_node);
+ (Plan *) hash_node,
+ best_path->jpath.jointype);
- copy_path_costsize(&join_node->join, &best_path->jpath.path);
+ copy_path_costsize(&join_node->join.plan, &best_path->jpath.path);
return join_node;
}
return node;
}
+static TidScan *
+make_tidscan(List *qptlist,
+ List *qpqual,
+ Index scanrelid,
+ List *tideval)
+{
+ TidScan *node = makeNode(TidScan);
+ Plan *plan = &node->scan.plan;
+
+ /* cost should be inserted by caller */
+ plan->state = (EState *) NULL;
+ plan->targetlist = qptlist;
+ plan->qual = qpqual;
+ plan->lefttree = NULL;
+ plan->righttree = NULL;
+ node->scan.scanrelid = scanrelid;
+ node->tideval = copyObject(tideval); /* XXX do we really need a
+ * copy? */
+ node->needRescan = false;
+ node->scan.scanstate = (CommonScanState *) NULL;
+
+ return node;
+}
+
static NestLoop *
-make_nestloop(List *qptlist,
- List *qpqual,
+make_nestloop(List *tlist,
+ List *joinclauses,
+ List *otherclauses,
Plan *lefttree,
- Plan *righttree)
+ Plan *righttree,
+ JoinType jointype)
{
NestLoop *node = makeNode(NestLoop);
- Plan *plan = &node->join;
+ Plan *plan = &node->join.plan;
/* cost should be inserted by caller */
plan->state = (EState *) NULL;
- plan->targetlist = qptlist;
- plan->qual = qpqual;
+ plan->targetlist = tlist;
+ plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
- node->nlstate = (NestLoopState *) NULL;
+ node->join.jointype = jointype;
+ node->join.joinqual = joinclauses;
return node;
}
static HashJoin *
make_hashjoin(List *tlist,
- List *qpqual,
+ List *joinclauses,
+ List *otherclauses,
List *hashclauses,
Plan *lefttree,
- Plan *righttree)
+ Plan *righttree,
+ JoinType jointype)
{
HashJoin *node = makeNode(HashJoin);
- Plan *plan = &node->join;
+ Plan *plan = &node->join.plan;
/* cost should be inserted by caller */
plan->state = (EState *) NULL;
plan->targetlist = tlist;
- plan->qual = qpqual;
+ plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->hashclauses = hashclauses;
- node->hashdone = false;
+ node->join.jointype = jointype;
+ node->join.joinqual = joinclauses;
return node;
}
static MergeJoin *
make_mergejoin(List *tlist,
- List *qpqual,
+ List *joinclauses,
+ List *otherclauses,
List *mergeclauses,
+ Plan *lefttree,
Plan *righttree,
- Plan *lefttree)
+ JoinType jointype)
{
MergeJoin *node = makeNode(MergeJoin);
- Plan *plan = &node->join;
+ Plan *plan = &node->join.plan;
/* cost should be inserted by caller */
plan->state = (EState *) NULL;
plan->targetlist = tlist;
- plan->qual = qpqual;
+ plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->mergeclauses = mergeclauses;
+ node->join.jointype = jointype;
+ node->join.joinqual = joinclauses;
return node;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.49 2000/08/13 02:50:07 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.50 2000/09/12 21:06:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
+#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "utils/lsyscache.h"
-static void add_restrict_and_join_to_rel(Query *root, Node *clause);
+static void mark_baserels_for_outer_join(Query *root, Relids rels,
+ Relids outerrels);
+static void add_restrict_and_join_to_rel(Query *root, Node *clause,
+ bool isjoinqual,
+ Relids outerjoinrelids);
static void add_join_info_to_rels(Query *root, RestrictInfo *restrictinfo,
Relids join_relids);
static void add_vars_to_targetlist(Query *root, List *vars);
*****************************************************************************/
/*
- * make_var_only_tlist
+ * build_base_rel_tlists
* Creates rel nodes for every relation mentioned in the target list
* 'tlist' (if a node hasn't already been created) and adds them to
- * *query_relation_list*. Creates targetlist entries for each member of
- * 'tlist' and adds them to the tlist field of the appropriate rel node.
+ * root->base_rel_list. Creates targetlist entries for each var seen
+ * in 'tlist' and adds them to the tlist of the appropriate rel node.
*/
void
-make_var_only_tlist(Query *root, List *tlist)
+build_base_rel_tlists(Query *root, List *tlist)
{
List *tlist_vars = pull_var_clause((Node *) tlist, false);
}
}
-/*
+/*----------
* add_missing_rels_to_query
*
- * If we have a range variable in the FROM clause that does not appear
+ * If we have a relation listed in the join tree that does not appear
* in the target list nor qualifications, we must add it to the base
- * relation list so that it will be joined. For instance, "select f.x
- * from foo f, foo f2" is a join of f and f2. Note that if we have
- * "select foo.x from foo f", it also gets turned into a join (between
- * foo as foo and foo as f).
+ * relation list so that it can be processed. For instance,
+ * select f.x from foo f, foo f2
+ * is a join of f and f2. Note that if we have
+ * select foo.x from foo f
+ * this also gets turned into a join (between foo as foo and foo as f).
*
* To avoid putting useless entries into the per-relation targetlists,
* this should only be called after all the variables in the targetlist
* and quals have been processed by the routines above.
+ *
+ * Returns a list of all the base relations (RelOptInfo nodes) that appear
+ * in the join tree. This list can be used for cross-checking in the
+ * reverse direction, ie, that we have a join tree entry for every
+ * relation used in the query.
+ *----------
*/
-void
-add_missing_rels_to_query(Query *root)
+List *
+add_missing_rels_to_query(Query *root, Node *jtnode)
{
- int varno = 1;
- List *l;
+ List *result = NIL;
- foreach(l, root->rtable)
+ if (jtnode == NULL)
+ return NIL;
+ if (IsA(jtnode, List))
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ List *l;
- if (rte->inJoinSet)
+ foreach(l, (List *) jtnode)
{
- RelOptInfo *rel = get_base_rel(root, varno);
+ result = nconc(result,
+ add_missing_rels_to_query(root, lfirst(l)));
+ }
+ }
+ else if (IsA(jtnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+ RelOptInfo *rel = get_base_rel(root, varno);
- /*
- * If the rel isn't otherwise referenced, give it a dummy
- * targetlist consisting of its own OID.
- */
- if (rel->targetlist == NIL)
- {
- Var *var = makeVar(varno, ObjectIdAttributeNumber,
- OIDOID, -1, 0);
+ /*
+ * If the rel isn't otherwise referenced, give it a dummy
+ * targetlist consisting of its own OID.
+ */
+ if (rel->targetlist == NIL)
+ {
+ Var *var = makeVar(varno, ObjectIdAttributeNumber,
+ OIDOID, -1, 0);
- add_var_to_tlist(rel, var);
- }
+ add_var_to_tlist(rel, var);
}
- varno++;
+
+ result = lcons(rel, NIL);
}
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ result = add_missing_rels_to_query(root, j->larg);
+ result = nconc(result,
+ add_missing_rels_to_query(root, j->rarg));
+ }
+ else
+ elog(ERROR, "add_missing_rels_to_query: unexpected node type %d",
+ nodeTag(jtnode));
+ return result;
}
*****************************************************************************/
+/*
+ * add_join_quals_to_rels
+ * Recursively scan the join tree for JOIN/ON (and JOIN/USING) qual
+ * clauses, and add these to the appropriate JoinInfo lists. Also,
+ * mark base RelOptInfos with outerjoinset information, which will
+ * be needed for proper placement of WHERE clauses during
+ * add_restrict_and_join_to_rels().
+ *
+ * NOTE: when dealing with inner joins, it is appropriate to let a qual clause
+ * be evaluated at the lowest level where all the variables it mentions are
+ * available. However, we cannot do this within an outer join since the qual
+ * might eliminate matching rows and cause a NULL row to be added improperly.
+ * Therefore, rels appearing within (the nullable side of) an outer join
+ * are marked with outerjoinset = list of Relids used at the outer join node.
+ * This list will be added to the list of rels referenced by quals using
+ * such a rel, thereby forcing them up the join tree to the right level.
+ *
+ * To ease the calculation of these values, add_join_quals_to_rels() returns
+ * the list of Relids involved in its own level of join. This is just an
+ * internal convenience; no outside callers pay attention to the result.
+ */
+Relids
+add_join_quals_to_rels(Query *root, Node *jtnode)
+{
+ Relids result = NIL;
+
+ if (jtnode == NULL)
+ return result;
+ if (IsA(jtnode, List))
+ {
+ List *l;
+
+ /*
+ * Note: we assume it's impossible to see same RT index from more
+ * than one subtree, so nconc() is OK rather than LispUnioni().
+ */
+ foreach(l, (List *) jtnode)
+ result = nconc(result,
+ add_join_quals_to_rels(root, lfirst(l)));
+ }
+ else if (IsA(jtnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+
+ /* No quals to deal with, just return correct result */
+ result = lconsi(varno, NIL);
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+ Relids leftids,
+ rightids,
+ outerjoinids;
+ List *qual;
+
+ /*
+ * Order of operations here is subtle and critical. First we recurse
+ * to handle sub-JOINs. Their join quals will be placed without
+ * regard for whether this level is an outer join, which is correct.
+ * Then, if we are an outer join, we mark baserels contained within
+ * the nullable side(s) with our own rel list; this will restrict
+ * placement of subsequent quals using those rels, including our own
+ * quals, quals above us in the join tree, and WHERE quals.
+ * Finally we place our own join quals.
+ */
+ leftids = add_join_quals_to_rels(root, j->larg);
+ rightids = add_join_quals_to_rels(root, j->rarg);
+
+ result = nconc(listCopy(leftids), rightids);
+
+ outerjoinids = NIL;
+ switch (j->jointype)
+ {
+ case JOIN_INNER:
+ /* Inner join adds no restrictions for quals */
+ break;
+ case JOIN_LEFT:
+ mark_baserels_for_outer_join(root, rightids, result);
+ outerjoinids = result;
+ break;
+ case JOIN_FULL:
+ mark_baserels_for_outer_join(root, result, result);
+ outerjoinids = result;
+ break;
+ case JOIN_RIGHT:
+ mark_baserels_for_outer_join(root, leftids, result);
+ outerjoinids = result;
+ break;
+ case JOIN_UNION:
+ /*
+ * This is where we fail if upper levels of planner haven't
+ * rewritten UNION JOIN as an Append ...
+ */
+ elog(ERROR, "UNION JOIN is not implemented yet");
+ break;
+ default:
+ elog(ERROR, "add_join_quals_to_rels: unsupported join type %d",
+ (int) j->jointype);
+ break;
+ }
+
+ foreach(qual, (List *) j->quals)
+ add_restrict_and_join_to_rel(root, (Node *) lfirst(qual),
+ true, outerjoinids);
+ }
+ else
+ elog(ERROR, "add_join_quals_to_rels: unexpected node type %d",
+ nodeTag(jtnode));
+ return result;
+}
+
+/*
+ * mark_baserels_for_outer_join
+ * Mark all base rels listed in 'rels' as having the given outerjoinset.
+ */
+static void
+mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
+{
+ List *relid;
+
+ foreach(relid, rels)
+ {
+ RelOptInfo *rel = get_base_rel(root, lfirsti(relid));
+
+ /*
+ * Since we do this bottom-up, any outer-rels previously marked
+ * should be within the new outer join set.
+ */
+ Assert(is_subseti(rel->outerjoinset, outerrels));
+
+ rel->outerjoinset = outerrels;
+ }
+}
+
/*
* add_restrict_and_join_to_rels
* Fill RestrictInfo and JoinInfo lists of relation entries for all
* relations appearing within clauses. Creates new relation entries if
- * necessary, adding them to *query_relation_list*.
+ * necessary, adding them to root->base_rel_list.
*
* 'clauses': the list of clauses in the cnfify'd query qualification.
*/
List *clause;
foreach(clause, clauses)
- add_restrict_and_join_to_rel(root, (Node *) lfirst(clause));
+ add_restrict_and_join_to_rel(root, (Node *) lfirst(clause),
+ false, NIL);
}
/*
* (depending on whether the clause is a join) of each base relation
* mentioned in the clause. A RestrictInfo node is created and added to
* the appropriate list for each rel. Also, if the clause uses a
- * mergejoinable operator, enter the left- and right-side expressions
- * into the query's lists of equijoined vars.
+ * mergejoinable operator and is not an outer-join qual, enter the left-
+ * and right-side expressions into the query's lists of equijoined vars.
+ *
+ * isjoinqual is true if the clause came from JOIN/ON or JOIN/USING;
+ * we have to mark the created RestrictInfo accordingly. If the JOIN
+ * is an OUTER join, the caller must set outerjoinrelids = all relids of join,
+ * which will override the joinrel identifiers extracted from the clause
+ * itself. For inner join quals and WHERE clauses, set outerjoinrelids = NIL.
+ * (Passing the whole list, and not just an "isouterjoin" boolean, is simply
+ * a speed optimization: we could extract the same list from the base rels'
+ * outerjoinsets, but since add_join_quals_to_rels() already knows what we
+ * should use, might as well pass it in instead of recalculating it.)
*/
static void
-add_restrict_and_join_to_rel(Query *root, Node *clause)
+add_restrict_and_join_to_rel(Query *root, Node *clause,
+ bool isjoinqual,
+ Relids outerjoinrelids)
{
RestrictInfo *restrictinfo = makeNode(RestrictInfo);
Relids relids;
List *vars;
+ bool can_be_equijoin;
restrictinfo->clause = (Expr *) clause;
+ restrictinfo->isjoinqual = isjoinqual;
restrictinfo->subclauseindices = NIL;
restrictinfo->mergejoinoperator = InvalidOid;
restrictinfo->left_sortop = InvalidOid;
*/
clause_get_relids_vars(clause, &relids, &vars);
+ /*
+ * If caller has given us a join relid list, use it; otherwise, we must
+ * scan the referenced base rels and add in any outer-join rel lists.
+ * This prevents the clause from being applied at a lower level of joining
+ * than any OUTER JOIN that should be evaluated before it.
+ */
+ if (outerjoinrelids)
+ {
+ /* Safety check: parser should have enforced this to start with */
+ if (! is_subseti(relids, outerjoinrelids))
+ elog(ERROR, "JOIN qualification may not refer to other relations");
+ relids = outerjoinrelids;
+ can_be_equijoin = false;
+ }
+ else
+ {
+ Relids newrelids = relids;
+ List *relid;
+
+ /* We rely on LispUnioni to be nondestructive of its input lists... */
+ can_be_equijoin = true;
+ foreach(relid, relids)
+ {
+ RelOptInfo *rel = get_base_rel(root, lfirsti(relid));
+
+ if (rel->outerjoinset)
+ {
+ newrelids = LispUnioni(newrelids, rel->outerjoinset);
+ /*
+ * Because application of the qual will be delayed by outer
+ * join, we mustn't assume its vars are equal everywhere.
+ */
+ can_be_equijoin = false;
+ }
+ }
+ relids = newrelids;
+ }
+
if (length(relids) == 1)
{
* that "a.x = a.y AND a.x = b.z AND a.y = c.q" allows us to
* consider z and q equal after their rels are joined.
*/
- check_mergejoinable(restrictinfo);
+ if (can_be_equijoin)
+ check_mergejoinable(restrictinfo);
}
else if (relids != NIL)
{
* the relid list. Set additional RestrictInfo fields for
* joining.
*
- * We need the merge info whether or not mergejoin is enabled (for
- * constructing equijoined-var lists), but we don't bother setting
- * hash info if hashjoin is disabled.
+ * We don't bother setting the merge/hashjoin info if we're not
+ * going to need it.
*/
- check_mergejoinable(restrictinfo);
+ if (enable_mergejoin || can_be_equijoin)
+ check_mergejoinable(restrictinfo);
if (enable_hashjoin)
check_hashjoinable(restrictinfo);