*
*
* 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);
add_join_info_to_rels(root, restrictinfo, relids);
/*
- * Add vars used in the join clause to targetlists of member
+ * Add vars used in the join clause to targetlists of their
* relations, so that they will be emitted by the plan nodes that
* scan those relations (else they won't be available at the join
* node!).
}
/*
- * If the clause has a mergejoinable operator, then the two sides
+ * If the clause has a mergejoinable operator, and is not an outer-join
+ * qualification nor bubbled up due to an outer join, then the two sides
* represent equivalent PathKeyItems for path keys: any path that is
- * sorted by one side will also be sorted by the other (after joining,
- * that is). Record the key equivalence for future use.
+ * sorted by one side will also be sorted by the other (as soon as the
+ * two rels are joined, that is). Record the key equivalence for future
+ * use.
*/
- if (restrictinfo->mergejoinoperator != InvalidOid)
+ if (can_be_equijoin && restrictinfo->mergejoinoperator != InvalidOid)
add_equijoined_keys(root, restrictinfo);
}
BOOLOID); /* operator result type */
clause->args = lcons(item1, lcons(item2, NIL));
- add_restrict_and_join_to_rel(root, (Node *) clause);
+ add_restrict_and_join_to_rel(root, (Node *) clause,
+ false, NIL);
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.58 2000/08/13 02:50:07 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.59 2000/09/12 21:06:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
+#include "parser/parsetree.h"
#include "utils/memutils.h"
* not any fancier features.
*
* tlist is the target list the query should produce (NOT root->targetList!)
- * qual is the qualification of the query (likewise!)
* tuple_fraction is the fraction of tuples we expect will be retrieved
*
- * qual must already have been converted to implicit-AND form.
- *
* Note: the Query node now also includes a query_pathkeys field, which
* is both an input and an output of query_planner(). The input value
* signals query_planner that the indicated sort order is wanted in the
Plan *
query_planner(Query *root,
List *tlist,
- List *qual,
double tuple_fraction)
{
+ List *normal_qual;
List *noncachable_qual;
List *constant_qual;
List *var_only_tlist;
root->query_pathkeys = NIL; /* signal unordered result */
/* Make childless Result node to evaluate given tlist. */
- return (Plan *) make_result(tlist, (Node *) qual, (Plan *) NULL);
+ return (Plan *) make_result(tlist, root->qual, (Plan *) NULL);
}
/*
* noncachable functions but no vars, such as "WHERE random() < 0.5".
* These cannot be treated as normal restriction or join quals, but
* they're not constants either. Instead, attach them to the qpqual
- * of the top-level plan, so that they get evaluated once per potential
+ * of the top plan, so that they get evaluated once per potential
* output tuple.
*/
- qual = pull_constant_clauses(qual, &noncachable_qual, &constant_qual);
+ normal_qual = pull_constant_clauses((List *) root->qual,
+ &noncachable_qual,
+ &constant_qual);
/*
* Create a target list that consists solely of (resdom var) target
/*
* Choose the best access path and build a plan for it.
*/
- subplan = subplanner(root, var_only_tlist, qual, tuple_fraction);
+ subplan = subplanner(root, var_only_tlist, normal_qual, tuple_fraction);
/*
* Handle the noncachable quals.
List *qual,
double tuple_fraction)
{
+ List *joined_rels;
+ List *brel;
RelOptInfo *final_rel;
Plan *resultplan;
MemoryContext mycontext;
Path *presortedpath;
/*
- * Initialize the targetlist and qualification, adding entries to
+ * Examine the targetlist and qualifications, adding entries to
* base_rel_list as relation references are found (e.g., in the
* qualification, the targetlist, etc.). Restrict and join clauses
* are added to appropriate lists belonging to the mentioned
root->join_rel_list = NIL;
root->equi_key_list = NIL;
- make_var_only_tlist(root, flat_tlist);
+ build_base_rel_tlists(root, flat_tlist);
+ (void) add_join_quals_to_rels(root, (Node *) root->jointree);
+ /* this must happen after add_join_quals_to_rels: */
add_restrict_and_join_to_rels(root, qual);
/*
- * Make sure we have RelOptInfo nodes for all relations used.
+ * Make sure we have RelOptInfo nodes for all relations to be joined.
+ */
+ joined_rels = add_missing_rels_to_query(root, (Node *) root->jointree);
+
+ /*
+ * Check that the join tree includes all the base relations used in
+ * the query --- otherwise, the parser or rewriter messed up.
*/
- add_missing_rels_to_query(root);
+ foreach(brel, root->base_rel_list)
+ {
+ RelOptInfo *baserel = (RelOptInfo *) lfirst(brel);
+ int relid = lfirsti(baserel->relids);
+
+ if (! ptrMember(baserel, joined_rels))
+ elog(ERROR, "Internal error: no jointree entry for rel %s (%d)",
+ rt_fetch(relid, root->rtable)->eref->relname, relid);
+ }
/*
* Use the completed lists of equijoined keys to deduce any implied
* We expect to end up here for a trivial INSERT ... VALUES query
* (which will have a target relation, so it gets past
* query_planner's check for empty range table; but the target rel
- * is unreferenced and not marked inJoinSet, so we find there is
- * nothing to join).
+ * is not in the join tree, so we find there is nothing to join).
*
* It's also possible to get here if the query was rewritten by the
- * rule processor (creating rangetable entries not marked
- * inJoinSet) but the rules either did nothing or were simplified
+ * rule processor (creating dummy rangetable entries that are not in
+ * the join tree) but the rules either did nothing or were simplified
* to nothing by constant-expression folding. So, don't complain.
*/
root->query_pathkeys = NIL; /* signal unordered result */
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.88 2000/08/21 20:55:29 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.89 2000/09/12 21:06:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "utils/lsyscache.h"
+static void preprocess_join_conditions(Query *parse, Node *jtnode);
static List *make_subplanTargetList(Query *parse, List *tlist,
AttrNumber **groupColIdx);
static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup,
* canonicalize_qual?
*/
parse->qual = (Node *) canonicalize_qual((Expr *) parse->qual, true);
+
#ifdef OPTIMIZER_DEBUG
printf("After canonicalize_qual()\n");
pprint(parse->qual);
parse->havingQual = SS_replace_correlation_vars(parse->havingQual);
}
+ /* Do all the above for each qual condition (ON clause) in the join tree */
+ preprocess_join_conditions(parse, (Node *) parse->jointree);
+
/* Do the main planning (potentially recursive) */
return union_planner(parse, tuple_fraction);
*/
}
+/*
+ * preprocess_join_conditions
+ * Recursively scan the query's jointree and do subquery_planner's
+ * qual preprocessing work on each ON condition found therein.
+ */
+static void
+preprocess_join_conditions(Query *parse, Node *jtnode)
+{
+ if (jtnode == NULL)
+ return;
+ if (IsA(jtnode, List))
+ {
+ List *l;
+
+ foreach(l, (List *) jtnode)
+ preprocess_join_conditions(parse, lfirst(l));
+ }
+ else if (IsA(jtnode, RangeTblRef))
+ {
+ /* nothing to do here */
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ preprocess_join_conditions(parse, j->larg);
+ preprocess_join_conditions(parse, j->rarg);
+
+ /* Simplify constant expressions */
+ j->quals = eval_const_expressions(j->quals);
+
+ /* Canonicalize the qual, and convert it to implicit-AND format */
+ j->quals = (Node *) canonicalize_qual((Expr *) j->quals, true);
+
+ /* Expand SubLinks to SubPlans */
+ if (parse->hasSubLinks)
+ {
+ j->quals = SS_process_sublinks(j->quals);
+ /*
+ * ON conditions, like WHERE clauses, are evaluated pre-GROUP;
+ * so we allow ungrouped vars in them.
+ */
+ }
+
+ /* Replace uplevel vars with Param nodes */
+ if (PlannerQueryLevel > 1)
+ j->quals = SS_replace_correlation_vars(j->quals);
+ }
+ else
+ elog(ERROR, "preprocess_join_conditions: unexpected node type %d",
+ nodeTag(jtnode));
+}
/*--------------------
* union_planner
/* Generate the (sub) plan */
result_plan = query_planner(parse,
sub_tlist,
- (List *) parse->qual,
tuple_fraction);
/*
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.64 2000/06/04 20:50:50 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.65 2000/09/12 21:06:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
set_join_references((Join *) plan);
fix_expr_references(plan, (Node *) plan->targetlist);
fix_expr_references(plan, (Node *) plan->qual);
+ fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
break;
case T_MergeJoin:
set_join_references((Join *) plan);
fix_expr_references(plan, (Node *) plan->targetlist);
fix_expr_references(plan, (Node *) plan->qual);
+ fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
fix_expr_references(plan,
(Node *) ((MergeJoin *) plan)->mergeclauses);
break;
set_join_references((Join *) plan);
fix_expr_references(plan, (Node *) plan->targetlist);
fix_expr_references(plan, (Node *) plan->qual);
+ fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
fix_expr_references(plan,
(Node *) ((HashJoin *) plan)->hashclauses);
break;
static void
set_join_references(Join *join)
{
- Plan *outer = join->lefttree;
- Plan *inner = join->righttree;
+ Plan *outer = join->plan.lefttree;
+ Plan *inner = join->plan.righttree;
List *outer_tlist = ((outer == NULL) ? NIL : outer->targetlist);
List *inner_tlist = ((inner == NULL) ? NIL : inner->targetlist);
- join->targetlist = join_references(join->targetlist,
- outer_tlist,
- inner_tlist,
- (Index) 0);
+ join->plan.targetlist = join_references(join->plan.targetlist,
+ outer_tlist,
+ inner_tlist,
+ (Index) 0);
}
/*
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.40 2000/08/06 04:13:22 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.41 2000/09/12 21:06:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
break;
+ case T_NestLoop:
+ finalize_primnode((Node *) ((Join *) plan)->joinqual,
+ &results);
+ break;
+
case T_MergeJoin:
+ finalize_primnode((Node *) ((Join *) plan)->joinqual,
+ &results);
finalize_primnode((Node *) ((MergeJoin *) plan)->mergeclauses,
&results);
break;
case T_HashJoin:
+ finalize_primnode((Node *) ((Join *) plan)->joinqual,
+ &results);
finalize_primnode((Node *) ((HashJoin *) plan)->hashclauses,
&results);
break;
case T_Agg:
case T_SeqScan:
- case T_NestLoop:
case T_Material:
case T_Sort:
case T_Unique:
Node_Copy(origNode, unionNode, distinctClause);
Node_Copy(origNode, unionNode, sortClause);
Node_Copy(origNode, unionNode, rtable);
+ Node_Copy(origNode, unionNode, jointree);
Node_Copy(origNode, unionNode, targetList);
origNode->unionClause = lappend(origNode->unionClause, unionNode);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.51 2000/06/20 04:22:16 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.52 2000/09/12 21:06:57 tgl Exp $
*
*-------------------------------------------------------------------------
*/
context.new_relid = new_relid;
context.sublevels_up = 0;
- /*
- * We must scan both the targetlist and qual, but we know the
- * havingQual is empty, so we can ignore it.
- */
- fix_parsetree_attnums_walker((Node *) parsetree->targetList, &context);
- fix_parsetree_attnums_walker((Node *) parsetree->qual, &context);
+ query_tree_walker(parsetree,
+ fix_parsetree_attnums_walker,
+ (void *) &context);
}
/*
}
return false;
}
- if (IsA(node, SubLink))
+ if (IsA(node, Query))
{
+ /* Recurse into subselects */
+ bool result;
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (fix_parsetree_attnums_walker((Node *) (sub->lefthand), context))
- return true;
context->sublevels_up++;
- if (fix_parsetree_attnums_walker((Node *) (sub->subselect), context))
- {
- context->sublevels_up--;
- return true;
- }
+ result = query_tree_walker((Query *) node,
+ fix_parsetree_attnums_walker,
+ (void *) context);
context->sublevels_up--;
- return false;
- }
- if (IsA(node, Query))
- {
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
-
- if (fix_parsetree_attnums_walker((Node *) (qry->targetList), context))
- return true;
- if (fix_parsetree_attnums_walker((Node *) (qry->qual), context))
- return true;
- if (fix_parsetree_attnums_walker((Node *) (qry->havingQual), context))
- return true;
- return false;
+ return result;
}
return expression_tree_walker(node, fix_parsetree_attnums_walker,
(void *) context);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.73 2000/08/24 03:29:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.74 2000/09/12 21:06:58 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
elog(ERROR, "cache lookup of attribute %d in relation %u failed",
var->varattno, rte->relid);
elog(ERROR, "Sub-SELECT uses un-GROUPed attribute %s.%s from outer query",
- rte->ref->relname, attname);
+ rte->eref->relname, attname);
}
}
}
* will have List structure at the top level, and it handles TargetEntry nodes
* so that a scan of a target list can be handled without additional code.
* (But only the "expr" part of a TargetEntry is examined, unless the walker
- * chooses to process TargetEntry nodes specially.)
+ * chooses to process TargetEntry nodes specially.) Also, RangeTblRef and
+ * JoinExpr nodes are handled, so that qual expressions in a jointree can be
+ * processed without additional code.
+ *
+ * expression_tree_walker will handle SubLink and SubPlan nodes by recursing
+ * normally into the "lefthand" arguments (which belong to the outer plan).
+ * It will also call the walker on the sub-Query node; however, when
+ * expression_tree_walker itself is called on a Query node, it does nothing
+ * and returns "false". The net effect is that unless the walker does
+ * something special at a Query node, sub-selects will not be visited
+ * during an expression tree walk. This is exactly the behavior wanted
+ * in many cases --- and for those walkers that do want to recurse into
+ * sub-selects, special behavior is typically needed anyway at the entry
+ * to a sub-select (such as incrementing a depth counter). A walker that
+ * wants to examine sub-selects should include code along the lines of:
+ *
+ * if (IsA(node, Query))
+ * {
+ * adjust context for subquery;
+ * result = query_tree_walker((Query *) node, my_walker, context);
+ * restore context if needed;
+ * return result;
+ * }
*
- * expression_tree_walker will handle a SUBPLAN_EXPR node by recursing into
- * the args and slink->oper lists (which belong to the outer plan), but it
- * will *not* visit the inner plan, since that's typically what expression
- * tree walkers want. A walker that wants to visit the subplan can force
- * appropriate behavior by recognizing subplan expression nodes and doing
- * the right thing.
+ * query_tree_walker is a convenience routine (see below) that calls the
+ * walker on all the expression subtrees of the given Query node.
*
- * Bare SubLink nodes (without a SUBPLAN_EXPR) are handled by recursing into
- * the "lefthand" argument list only. (A bare SubLink should be seen only if
- * the tree has not yet been processed by subselect.c.) Again, this can be
- * overridden by the walker, but it seems to be the most useful default
- * behavior.
+ * NOTE: currently, because make_subplan() clears the subselect link in
+ * a SubLink node, it is not actually possible to recurse into subselects
+ * of an already-planned expression tree. This is OK for current uses,
+ * but ought to be cleaned up when we redesign querytree processing.
*--------------------
*/
bool
- expression_tree_walker(Node *node, bool (*walker) (), void *context)
+expression_tree_walker(Node *node,
+ bool (*walker) (),
+ void *context)
{
List *temp;
case T_Const:
case T_Var:
case T_Param:
+ case T_RangeTblRef:
/* primitive node types with no subnodes */
break;
case T_Expr:
/*
* If the SubLink has already been processed by
- * subselect.c, it will have lefthand=NIL, and we only
- * need to look at the oper list. Otherwise we only need
- * to look at lefthand (the Oper nodes in the oper list
- * are deemed uninteresting).
+ * subselect.c, it will have lefthand=NIL, and we need to
+ * scan the oper list. Otherwise we only need to look at
+ * the lefthand list (the incomplete Oper nodes in the oper
+ * list are deemed uninteresting, perhaps even confusing).
*/
if (sublink->lefthand)
- return walker((Node *) sublink->lefthand, context);
+ {
+ if (walker((Node *) sublink->lefthand, context))
+ return true;
+ }
else
- return walker((Node *) sublink->oper, context);
+ {
+ if (walker((Node *) sublink->oper, context))
+ return true;
+ }
+ /*
+ * Also invoke the walker on the sublink's Query node,
+ * so it can recurse into the sub-query if it wants to.
+ */
+ return walker(sublink->subselect, context);
}
break;
+ case T_Query:
+ /* Do nothing with a sub-Query, per discussion above */
+ break;
case T_List:
foreach(temp, (List *) node)
{
break;
case T_TargetEntry:
return walker(((TargetEntry *) node)->expr, context);
+ case T_JoinExpr:
+ {
+ JoinExpr *join = (JoinExpr *) node;
+
+ if (walker(join->larg, context))
+ return true;
+ if (walker(join->rarg, context))
+ return true;
+ if (walker(join->quals, context))
+ return true;
+ if (walker((Node *) join->colvars, context))
+ return true;
+ /* alias clause, using list, colnames list are deemed
+ * uninteresting.
+ */
+ }
+ break;
default:
elog(ERROR, "expression_tree_walker: Unexpected node type %d",
nodeTag(node));
return false;
}
+/*
+ * query_tree_walker --- initiate a walk of a Query's expressions
+ *
+ * This routine exists just to reduce the number of places that need to know
+ * where all the expression subtrees of a Query are. Note it can be used
+ * for starting a walk at top level of a Query regardless of whether the
+ * walker intends to descend into subqueries. It is also useful for
+ * descending into subqueries within a walker.
+ */
+bool
+query_tree_walker(Query *query,
+ bool (*walker) (),
+ void *context)
+{
+ Assert(query != NULL && IsA(query, Query));
+
+ if (walker((Node *) query->targetList, context))
+ return true;
+ if (walker(query->qual, context))
+ return true;
+ if (walker(query->havingQual, context))
+ return true;
+ if (walker((Node *) query->jointree, context))
+ return true;
+ /*
+ * XXX for subselect-in-FROM, may need to examine rtable as well
+ */
+ return false;
+}
+
+
/*--------------------
* expression_tree_mutator() is designed to support routines that make a
* modified copy of an expression tree, with some nodes being added,
*/
Node *
- expression_tree_mutator(Node *node, Node *(*mutator) (), void *context)
+expression_tree_mutator(Node *node,
+ Node *(*mutator) (),
+ void *context)
{
/*
case T_Const:
case T_Var:
case T_Param:
+ case T_RangeTblRef:
/* primitive node types with no subnodes */
return (Node *) copyObject(node);
case T_Expr:
return (Node *) newnode;
}
break;
+ case T_JoinExpr:
+ {
+ JoinExpr *join = (JoinExpr *) node;
+ JoinExpr *newnode;
+
+ FLATCOPY(newnode, join, JoinExpr);
+ MUTATE(newnode->larg, join->larg, Node *);
+ MUTATE(newnode->rarg, join->rarg, Node *);
+ MUTATE(newnode->quals, join->quals, Node *);
+ MUTATE(newnode->colvars, join->colvars, List *);
+ /* We do not mutate alias, using, or colnames by default */
+ return (Node *) newnode;
+ }
+ break;
default:
elog(ERROR, "expression_tree_mutator: Unexpected node type %d",
nodeTag(node));
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.64 2000/05/30 00:49:49 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.65 2000/09/12 21:06:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Path *cheapest_total_path;
Assert(IsA(parent_rel, RelOptInfo));
- Assert(pathlist != NIL);
+
+ if (pathlist == NIL)
+ elog(ERROR, "Unable to devise a query plan for the given query");
cheapest_startup_path = cheapest_total_path = (Path *) lfirst(pathlist);
* number of rows is the same as the parent rel's estimate.
*/
pathnode->joinrelids = NIL; /* no join clauses here */
+ pathnode->alljoinquals = false;
pathnode->rows = rel->rows;
cost_index(&pathnode->path, root, rel, index, indexquals, false);
* relations.
*
* 'joinrel' is the join relation.
+ * 'jointype' is the type of join required
* 'outer_path' is the outer path
* 'inner_path' is the inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
*/
NestPath *
create_nestloop_path(RelOptInfo *joinrel,
+ JoinType jointype,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
+ pathnode->jointype = jointype;
pathnode->outerjoinpath = outer_path;
pathnode->innerjoinpath = inner_path;
pathnode->joinrestrictinfo = restrict_clauses;
* two relations
*
* 'joinrel' is the join relation
+ * 'jointype' is the type of join required
* 'outer_path' is the outer path
* 'inner_path' is the inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
*/
MergePath *
create_mergejoin_path(RelOptInfo *joinrel,
+ JoinType jointype,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
+ pathnode->jpath.jointype = jointype;
pathnode->jpath.outerjoinpath = outer_path;
pathnode->jpath.innerjoinpath = inner_path;
pathnode->jpath.joinrestrictinfo = restrict_clauses;
* Creates a pathnode corresponding to a hash join between two relations.
*
* 'joinrel' is the join relation
+ * 'jointype' is the type of join required
* 'outer_path' is the cheapest outer path
* 'inner_path' is the cheapest inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
*/
HashPath *
create_hashjoin_path(RelOptInfo *joinrel,
+ JoinType jointype,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
+ pathnode->jpath.jointype = jointype;
pathnode->jpath.outerjoinpath = outer_path;
pathnode->jpath.innerjoinpath = inner_path;
pathnode->jpath.joinrestrictinfo = restrict_clauses;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.27 2000/06/18 22:44:12 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.28 2000/09/12 21:06:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
rel->tuples = 0;
rel->baserestrictinfo = NIL;
rel->baserestrictcost = 0;
+ rel->outerjoinset = NIL;
rel->joininfo = NIL;
rel->innerjoin = NIL;
joinrel->tuples = 0;
joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost = 0;
+ joinrel->outerjoinset = NIL;
joinrel->joininfo = NIL;
joinrel->innerjoin = NIL;
restrictlist);
/*
- * Add the joinrel to the front of the query's joinrel list.
- * (allpaths.c depends on this ordering!)
+ * Add the joinrel to the query's joinrel list.
*/
root->join_rel_list = lcons(joinrel, root->join_rel_list);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.10 2000/05/30 00:49:49 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.11 2000/09/12 21:06:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
}
return result;
}
+
+/*
+ * get_actual_join_clauses
+ *
+ * Extract clauses from 'restrictinfo_list', separating those that
+ * came from JOIN/ON conditions from those that didn't.
+ */
+void
+get_actual_join_clauses(List *restrictinfo_list,
+ List **joinquals, List **otherquals)
+{
+ List *temp;
+
+ *joinquals = NIL;
+ *otherquals = NIL;
+
+ foreach(temp, restrictinfo_list)
+ {
+ RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
+
+ if (clause->isjoinqual)
+ *joinquals = lappend(*joinquals, clause->clause);
+ else
+ *otherquals = lappend(*otherquals, clause->clause);
+ }
+}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.26 2000/04/12 17:15:24 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.27 2000/09/12 21:06:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "nodes/plannodes.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
+typedef struct
+{
+ List *varlist;
+ int sublevels_up;
+} pull_varnos_context;
+
typedef struct
{
List *varlist;
bool includeUpperVars;
} pull_var_clause_context;
-static bool pull_varnos_walker(Node *node, List **listptr);
+static bool pull_varnos_walker(Node *node,
+ pull_varnos_context *context);
static bool contain_var_clause_walker(Node *node, void *context);
static bool pull_var_clause_walker(Node *node,
pull_var_clause_context *context);
/*
* pull_varnos
*
- * Create a list of all the distinct varnos present in a parsetree
- * (tlist or qual). Note that only varnos attached to level-zero
- * Vars are considered --- upper Vars refer to some other rtable!
+ * Create a list of all the distinct varnos present in a parsetree.
+ * Only varnos that reference level-zero rtable entries are considered.
+ *
+ * NOTE: unlike other routines in this file, pull_varnos() is used on
+ * not-yet-planned expressions. It may therefore find bare SubLinks,
+ * and if so it needs to recurse into them to look for uplevel references
+ * to the desired rtable level! But when we find a completed SubPlan,
+ * we only need to look at the parameters passed to the subplan.
*/
List *
pull_varnos(Node *node)
{
- List *result = NIL;
+ pull_varnos_context context;
+
+ context.varlist = NIL;
+ context.sublevels_up = 0;
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree;
+ * if it's a Query, go straight to query_tree_walker to make sure that
+ * sublevels_up doesn't get incremented prematurely.
+ */
+ if (node && IsA(node, Query))
+ query_tree_walker((Query *) node, pull_varnos_walker,
+ (void *) &context);
+ else
+ pull_varnos_walker(node, &context);
- pull_varnos_walker(node, &result);
- return result;
+ return context.varlist;
}
static bool
-pull_varnos_walker(Node *node, List **listptr)
+pull_varnos_walker(Node *node, pull_varnos_context *context)
{
if (node == NULL)
return false;
{
Var *var = (Var *) node;
- if (var->varlevelsup == 0 && !intMember(var->varno, *listptr))
- *listptr = lconsi(var->varno, *listptr);
+ if (var->varlevelsup == context->sublevels_up &&
+ !intMember(var->varno, context->varlist))
+ context->varlist = lconsi(var->varno, context->varlist);
+ return false;
+ }
+ if (is_subplan(node))
+ {
+ /*
+ * Already-planned subquery. Examine the args list (parameters
+ * to be passed to subquery), as well as the "oper" list which
+ * is executed by the outer query. But short-circuit recursion into
+ * the subquery itself, which would be a waste of effort.
+ */
+ Expr *expr = (Expr *) node;
+
+ if (pull_varnos_walker((Node*) ((SubPlan*) expr->oper)->sublink->oper,
+ context))
+ return true;
+ if (pull_varnos_walker((Node *) expr->args,
+ context))
+ return true;
return false;
}
- return expression_tree_walker(node, pull_varnos_walker, (void *) listptr);
+ if (IsA(node, Query))
+ {
+ /* Recurse into not-yet-planned subquery */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node, pull_varnos_walker,
+ (void *) context);
+ context->sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node, pull_varnos_walker,
+ (void *) context);
}
/*
#
# Makefile for parser
#
-# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.29 2000/08/28 11:53:19 petere Exp $
+# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.30 2000/09/12 21:07:00 tgl Exp $
#
#-------------------------------------------------------------------------
$(srcdir)/scan.c: scan.l
ifdef FLEX
- $(FLEX) $(FLEXFLAGS) -o'$@' $<
+ $(FLEX) $(FLEXFLAGS) -Pbase_yy -o'$@' $<
else
@$(missing) flex $< $@
endif
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: analyze.c,v 1.156 2000/08/29 04:20:44 momjian Exp $
+ * $Id: analyze.c,v 1.157 2000/09/12 21:07:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
+#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/relcache.h"
static void transformColumnType(ParseState *pstate, ColumnDef *column);
static void transformFkeyCheckAttrs(FkConstraint *fkconstraint);
+static void release_pstate_resources(ParseState *pstate);
+
/* kluge to return extra info from transformCreateStmt() */
static List *extras_before;
static List *extras_after;
parse_analyze(List *pl, ParseState *parentParseState)
{
List *result = NIL;
- ParseState *pstate;
- Query *parsetree;
while (pl != NIL)
{
+ ParseState *pstate = make_parsestate(parentParseState);
+ Query *parsetree;
+
extras_before = extras_after = NIL;
- pstate = make_parsestate(parentParseState);
parsetree = transformStmt(pstate, lfirst(pl));
- if (pstate->p_target_relation != NULL)
- heap_close(pstate->p_target_relation, AccessShareLock);
- pstate->p_target_relation = NULL;
- pstate->p_target_rangetblentry = NULL;
+ release_pstate_resources(pstate);
while (extras_before != NIL)
{
result = lappend(result,
- transformStmt(pstate, lfirst(extras_before)));
- if (pstate->p_target_relation != NULL)
- heap_close(pstate->p_target_relation, AccessShareLock);
- pstate->p_target_relation = NULL;
- pstate->p_target_rangetblentry = NULL;
+ transformStmt(pstate, lfirst(extras_before)));
+ release_pstate_resources(pstate);
extras_before = lnext(extras_before);
}
{
result = lappend(result,
transformStmt(pstate, lfirst(extras_after)));
- if (pstate->p_target_relation != NULL)
- heap_close(pstate->p_target_relation, AccessShareLock);
- pstate->p_target_relation = NULL;
- pstate->p_target_rangetblentry = NULL;
+ release_pstate_resources(pstate);
extras_after = lnext(extras_after);
}
return result;
}
+static void
+release_pstate_resources(ParseState *pstate)
+{
+ if (pstate->p_target_relation != NULL)
+ heap_close(pstate->p_target_relation, AccessShareLock);
+ pstate->p_target_relation = NULL;
+ pstate->p_target_rangetblentry = NULL;
+}
+
/*
* transformStmt -
* transform a Parse tree. If it is an optimizable statement, turn it
Resdom *rd;
id = nth(i, n->aliases);
- Assert(nodeTag(id) == T_Ident);
+ Assert(IsA(id, Ident));
te = nth(i, targetList);
- Assert(nodeTag(te) == T_TargetEntry);
+ Assert(IsA(te, TargetEntry));
rd = te->resdom;
- Assert(nodeTag(rd) == T_Resdom);
+ Assert(IsA(rd, Resdom));
rd->resname = pstrdup(id->name);
}
}
qry->commandType = CMD_DELETE;
/* set up a range table */
- makeRangeTable(pstate, NULL);
- setTargetTable(pstate, stmt->relname, stmt->inh);
+ makeRangeTable(pstate, NIL);
+ setTargetTable(pstate, stmt->relname, stmt->inh, true);
qry->distinctClause = NIL;
/* fix where clause */
qry->qual = transformWhereClause(pstate, stmt->whereClause);
+ /* done building the rtable */
qry->rtable = pstate->p_rtable;
+ qry->jointree = pstate->p_jointree;
qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
*
* In particular, it's time to add the INSERT target to the rangetable.
* (We didn't want it there until now since it shouldn't be visible in
- * the SELECT part.)
+ * the SELECT part.) Note that the INSERT target is NOT added to the
+ * join tree, since we don't want to join over it.
*/
- setTargetTable(pstate, stmt->relname, FALSE);
+ setTargetTable(pstate, stmt->relname, false, false);
/* now the range table will not change */
qry->rtable = pstate->p_rtable;
+ qry->jointree = pstate->p_jointree;
qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
/* Prepare to assign non-conflicting resnos to resjunk attributes */
while (dlist != NIL)
{
constraint = lfirst(dlist);
- Assert(nodeTag(constraint) == T_Constraint);
+ Assert(IsA(constraint, Constraint));
Assert((constraint->contype == CONSTR_PRIMARY)
|| (constraint->contype == CONSTR_UNIQUE));
transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
{
Query *qry;
- Query *action;
- List *actions;
+ RangeTblEntry *oldrte;
+ RangeTblEntry *newrte;
qry = makeNode(Query);
qry->commandType = CMD_UTILITY;
+ qry->utilityStmt = (Node *) stmt;
+
+ /*
+ * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
+ * equal to 2. Set up their RTEs in the main pstate for use
+ * in parsing the rule qualification.
+ */
+ Assert(pstate->p_rtable == NIL);
+ oldrte = addRangeTableEntry(pstate, stmt->object->relname,
+ makeAttr("*OLD*", NULL),
+ false, true);
+ newrte = addRangeTableEntry(pstate, stmt->object->relname,
+ makeAttr("*NEW*", NULL),
+ false, true);
+ /*
+ * They must be in the jointree too for lookup purposes, but only add
+ * the one(s) that are relevant for the current kind of rule. In an
+ * UPDATE rule, quals must refer to OLD.field or NEW.field to be
+ * unambiguous, but there's no need to be so picky for INSERT & DELETE.
+ * (Note we marked the RTEs "inFromCl = true" above to allow unqualified
+ * references to their fields.)
+ */
+ switch (stmt->event)
+ {
+ case CMD_SELECT:
+ addRTEtoJoinTree(pstate, oldrte);
+ break;
+ case CMD_UPDATE:
+ addRTEtoJoinTree(pstate, oldrte);
+ addRTEtoJoinTree(pstate, newrte);
+ break;
+ case CMD_INSERT:
+ addRTEtoJoinTree(pstate, newrte);
+ break;
+ case CMD_DELETE:
+ addRTEtoJoinTree(pstate, oldrte);
+ break;
+ default:
+ elog(ERROR, "transformRuleStmt: unexpected event type %d",
+ (int) stmt->event);
+ break;
+ }
+
+ /* take care of the where clause */
+ stmt->whereClause = transformWhereClause(pstate, stmt->whereClause);
+
+ if (length(pstate->p_rtable) != 2) /* naughty, naughty... */
+ elog(ERROR, "Rule WHERE condition may not contain references to other relations");
+
+ /* save info about sublinks in where clause */
+ qry->hasSubLinks = pstate->p_hasSubLinks;
/*
- * 'instead nothing' rules with a qualification need a query a
+ * 'instead nothing' rules with a qualification need a query
* rangetable so the rewrite handler can add the negated rule
* qualification to the original query. We create a query with the new
- * command type CMD_NOTHING here that is treated special by the
+ * command type CMD_NOTHING here that is treated specially by the
* rewrite system.
*/
if (stmt->actions == NIL)
Query *nothing_qry = makeNode(Query);
nothing_qry->commandType = CMD_NOTHING;
-
- addRangeTableEntry(pstate, stmt->object->relname,
- makeAttr("*OLD*", NULL),
- FALSE, FALSE, FALSE);
- addRangeTableEntry(pstate, stmt->object->relname,
- makeAttr("*NEW*", NULL),
- FALSE, FALSE, FALSE);
-
nothing_qry->rtable = pstate->p_rtable;
+ nothing_qry->jointree = NIL; /* no join actually wanted */
stmt->actions = lappend(NIL, nothing_qry);
}
-
- actions = stmt->actions;
-
- /*
- * transform each statment, like parse_analyze()
- */
- while (actions != NIL)
+ else
{
+ List *actions;
/*
- * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
- * equal to 2.
+ * transform each statement, like parse_analyze()
*/
- addRangeTableEntry(pstate, stmt->object->relname,
- makeAttr("*OLD*", NULL),
- FALSE, FALSE, FALSE);
- addRangeTableEntry(pstate, stmt->object->relname,
- makeAttr("*NEW*", NULL),
- FALSE, FALSE, FALSE);
-
- pstate->p_last_resno = 1;
- pstate->p_is_rule = true; /* for expand all */
- pstate->p_hasAggs = false;
-
- action = (Query *) lfirst(actions);
- if (action->commandType != CMD_NOTHING)
- lfirst(actions) = transformStmt(pstate, lfirst(actions));
- actions = lnext(actions);
- }
+ foreach(actions, stmt->actions)
+ {
+ ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
+ Query *sub_qry;
+ bool has_old,
+ has_new;
- /* take care of the where clause */
- stmt->whereClause = transformWhereClause(pstate, stmt->whereClause);
+ /*
+ * Set up OLD/NEW in the rtable for this statement. The entries
+ * are marked not inFromCl because we don't want them to be
+ * referred to by unqualified field names nor "*" in the rule
+ * actions. We don't need to add them to the jointree for
+ * qualified-name lookup, either (see qualifiedNameToVar()).
+ */
+ oldrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
+ makeAttr("*OLD*", NULL),
+ false, false);
+ newrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
+ makeAttr("*NEW*", NULL),
+ false, false);
- qry->hasSubLinks = pstate->p_hasSubLinks;
+ /* Transform the rule action statement */
+ sub_qry = transformStmt(sub_pstate, lfirst(actions));
+
+ /*
+ * Validate action's use of OLD/NEW, qual too
+ */
+ has_old =
+ rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
+ rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
+ has_new =
+ rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
+ rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);
+
+ switch (stmt->event)
+ {
+ case CMD_SELECT:
+ if (has_old)
+ elog(ERROR, "ON SELECT rule may not use OLD");
+ if (has_new)
+ elog(ERROR, "ON SELECT rule may not use NEW");
+ break;
+ case CMD_UPDATE:
+ /* both are OK */
+ break;
+ case CMD_INSERT:
+ if (has_old)
+ elog(ERROR, "ON INSERT rule may not use OLD");
+ break;
+ case CMD_DELETE:
+ if (has_new)
+ elog(ERROR, "ON DELETE rule may not use NEW");
+ break;
+ default:
+ elog(ERROR, "transformRuleStmt: unexpected event type %d",
+ (int) stmt->event);
+ break;
+ }
+
+ /*
+ * For efficiency's sake, add OLD to the rule action's jointree
+ * only if it was actually referenced in the statement or qual.
+ * NEW is not really a relation and should never be added.
+ */
+ if (has_old)
+ {
+ addRTEtoJoinTree(sub_pstate, oldrte);
+ sub_qry->jointree = sub_pstate->p_jointree;
+ }
+
+ lfirst(actions) = sub_qry;
+
+ release_pstate_resources(sub_pstate);
+ pfree(sub_pstate);
+ }
+ }
- qry->utilityStmt = (Node *) stmt;
return qry;
}
qry->intersectClause = stmt->intersectClause;
qry->rtable = pstate->p_rtable;
+ qry->jointree = pstate->p_jointree;
if (stmt->forUpdate != NULL)
transformForUpdate(qry, stmt->forUpdate);
* do this with REPLACE in POSTQUEL so we keep the feature.
*/
makeRangeTable(pstate, stmt->fromClause);
- setTargetTable(pstate, stmt->relname, stmt->inh);
+ setTargetTable(pstate, stmt->relname, stmt->inh, true);
qry->targetList = transformTargetList(pstate, stmt->targetList);
qry->qual = transformWhereClause(pstate, stmt->whereClause);
- qry->hasSubLinks = pstate->p_hasSubLinks;
-
qry->rtable = pstate->p_rtable;
+ qry->jointree = pstate->p_jointree;
qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs)
parseCheckAggregates(pstate, qry);
transformColumnType(pstate, (ColumnDef *) stmt->def);
break;
case 'C':
- if (stmt->def && nodeTag(stmt->def) == T_FkConstraint)
+ if (stmt->def && IsA(stmt->def, FkConstraint))
{
CreateTrigStmt *fk_trigger;
List *fk_attr;
i++;
}
if (l2 == NULL)
- elog(ERROR, "FOR UPDATE: relation '%s' not found in FROM clause",
+ elog(ERROR, "FOR UPDATE: relation \"%s\" not found in FROM clause",
relname);
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.188 2000/09/12 05:09:44 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.189 2000/09/12 21:07:01 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
#include <ctype.h>
#include "postgres.h"
+
#include "access/htup.h"
#include "access/xact.h"
#include "catalog/catname.h"
static Node *makeTypeCast(Node *arg, TypeName *typename);
static Node *makeRowExpr(char *opr, List *largs, List *rargs);
static void mapTargetColumns(List *source, List *target);
-static void param_type_init(Oid *typev, int nargs);
static bool exprIsNullConstant(Node *arg);
static Node *doNegate(Node *n);
static void doNegateFloat(Value *v);
-/* old versions of flex define this as a macro */
-#if defined(yywrap)
-#undef yywrap
-#endif /* yywrap */
%}
char chr;
char *str;
bool boolean;
+ JoinType jtype;
List *list;
Node *node;
Value *value;
JoinExpr *jexpr;
IndexElem *ielem;
RangeVar *range;
- RelExpr *relexp;
A_Indices *aind;
ResTarget *target;
ParamNo *paramno;
%type <boolean> opt_table
%type <boolean> opt_chain, opt_trans
-%type <jexpr> from_expr, join_clause, join_expr
-%type <jexpr> join_clause_with_union, join_expr_with_union
%type <node> join_outer, join_qual
-%type <ival> join_type
-%type <list> using_list
-%type <ident> using_expr
-/***
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-%type <list> oracle_list
-%type <jexpr> oracle_expr
-%type <boolean> oracle_outer
-#endif
-***/
+%type <jtype> join_type
%type <list> extract_list, position_list
%type <list> substr_list, substr_from, substr_for, trim_list
%type <attr> event_object, attr, alias_clause
%type <sortgroupby> sortby
%type <ielem> index_elem, func_index
-%type <range> table_expr
-%type <relexp> relation_expr
+%type <node> table_ref
+%type <jexpr> joined_table
+%type <range> relation_expr
%type <target> target_el, update_target_el
%type <paramno> ParamNo
TEMP, TOAST, TRUNCATE, TRUSTED,
UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION
+/* The grammar thinks these are keywords, but they are not in the keywords.c
+ * list and so can never be entered directly. The filter in parser.c
+ * creates these tokens when required.
+ */
+%token UNIONJOIN
+
/* Special keywords, not in the query language - see the "lex" file */
%token <str> IDENT, FCONST, SCONST, Op
%token <ival> ICONST, PARAM
%token OP
/* precedence: lowest to highest */
-%left UNION INTERSECT EXCEPT
+%left UNION EXCEPT
+%left INTERSECT
+%left JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
%left OR
%left AND
%right NOT
n->value = $3;
$$ = (Node *) n;
#else
- elog(ERROR, "SET NAMES is not supported.");
+ elog(ERROR, "SET NAMES is not supported");
#endif
}
;
n->relname = $3;
$$ = (Node *)n;
}
-
/* ALTER TABLE <name> OWNER TO UserId */
| ALTER TABLE relation_name OWNER TO UserId
{
CreatedbStmt *n;
if ($5 == NULL && $6 == -1)
- elog(ERROR, "CREATE DATABASE WITH requires at least one option.");
+ elog(ERROR, "CREATE DATABASE WITH requires at least one option");
n = makeNode(CreatedbStmt);
n->dbname = $3;
/* This rule parses Select statements that can appear within set operations,
* including UNION, INTERSECT and EXCEPT. '(' and ')' can be used to specify
* the ordering of the set operations. Without '(' and ')' we want the
- * operations to be left associative.
+ * operations to be ordered per the precedence specs at the head of this file.
*
* Note that sort clauses cannot be included at this level --- a sort clause
* can only appear at the end of the complete Select, and it will be handled
{
$$ = $1;
}
- | select_clause EXCEPT select_clause
+ | select_clause EXCEPT opt_all select_clause
{
$$ = (Node *)makeA_Expr(AND,NULL,$1,
- makeA_Expr(NOT,NULL,NULL,$3));
+ makeA_Expr(NOT,NULL,NULL,$4));
+ if ($3)
+ elog(ERROR, "EXCEPT ALL is not implemented yet");
}
| select_clause UNION opt_all select_clause
{
}
$$ = (Node *)makeA_Expr(OR,NULL,$1,$4);
}
- | select_clause INTERSECT select_clause
+ | select_clause INTERSECT opt_all select_clause
{
- $$ = (Node *)makeA_Expr(AND,NULL,$1,$3);
+ $$ = (Node *)makeA_Expr(AND,NULL,$1,$4);
+ if ($3)
+ elog(ERROR, "INTERSECT ALL is not implemented yet");
}
;
*****************************************************************************/
from_clause: FROM from_list { $$ = $2; }
-/***
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
- | FROM oracle_list { $$ = $2; }
-#endif
-***/
- | FROM from_expr { $$ = lcons($2, NIL); }
| /*EMPTY*/ { $$ = NIL; }
;
-from_list: from_list ',' table_expr { $$ = lappend($1, $3); }
- | table_expr { $$ = lcons($1, NIL); }
- ;
-
-/***********
- * This results in one shift/reduce conflict, presumably due to the trailing "(+)"
- * - Thomas 1999-09-20
- *
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-oracle_list: oracle_expr { $$ = lcons($1, NIL); }
- ;
-
-oracle_expr: ColId ',' ColId oracle_outer
- {
- elog(ERROR,"Oracle OUTER JOIN not yet supported");
- $$ = NULL;
- }
- | oracle_outer ColId ',' ColId
- {
- elog(ERROR,"Oracle OUTER JOIN not yet supported");
- $$ = NULL;
- }
- ;
-
-oracle_outer: '(' '+' ')' { $$ = TRUE; }
- ;
-#endif
-***********/
-
-from_expr: '(' join_clause_with_union ')' alias_clause
- {
- JoinExpr *j = $2;
- j->alias = $4;
- $$ = j;
- }
- | join_clause
- { $$ = $1; }
- ;
-
-table_expr: relation_expr alias_clause
- {
- $$ = makeNode(RangeVar);
- $$->relExpr = $1;
- $$->name = $2;
-
-#ifdef DISABLE_JOIN_SYNTAX
- if (($2 != NULL) && ($2->attrs != NULL))
- elog(ERROR, "Column aliases in table expressions not yet supported");
-#endif
- }
+from_list: from_list ',' table_ref { $$ = lappend($1, $3); }
+ | table_ref { $$ = lcons($1, NIL); }
;
-alias_clause: AS ColId '(' name_list ')'
- {
- $$ = makeNode(Attr);
- $$->relname = $2;
- $$->attrs = $4;
- }
- | AS ColId
+/*
+ * table_ref is where an alias clause can be attached. Note we cannot make
+ * alias_clause have an empty production because that causes parse conflicts
+ * between table_ref := '(' joined_table ')' alias_clause
+ * and joined_table := '(' joined_table ')'. So, we must have the
+ * redundant-looking productions here instead.
+ */
+table_ref: relation_expr
{
- $$ = makeNode(Attr);
- $$->relname = $2;
+ $$ = (Node *) $1;
}
- | ColId '(' name_list ')'
+ | relation_expr alias_clause
{
- $$ = makeNode(Attr);
- $$->relname = $1;
- $$->attrs = $3;
+ $1->name = $2;
+ $$ = (Node *) $1;
}
- | ColId
+ | '(' select_clause ')'
{
- $$ = makeNode(Attr);
- $$->relname = $1;
+ RangeSubselect *n = makeNode(RangeSubselect);
+ n->subquery = $2;
+ n->name = NULL;
+ $$ = (Node *) n;
}
- | /*EMPTY*/
+ | '(' select_clause ')' alias_clause
{
- $$ = NULL; /* no qualifiers */
+ RangeSubselect *n = makeNode(RangeSubselect);
+ n->subquery = $2;
+ n->name = $4;
+ $$ = (Node *) n;
}
- ;
-
-/* A UNION JOIN is the same as a FULL OUTER JOIN which *omits*
- * all result rows which would have matched on an INNER JOIN.
- * Syntactically, must enclose the UNION JOIN in parens to avoid
- * conflicts with SELECT/UNION.
- */
-join_clause: join_clause join_expr
+ | joined_table
{
- $2->larg = (Node *)$1;
- $$ = $2;
+ $$ = (Node *) $1;
}
- | table_expr join_expr
+ | '(' joined_table ')' alias_clause
{
- $2->larg = (Node *)$1;
- $$ = $2;
+ $2->alias = $4;
+ $$ = (Node *) $2;
}
;
-/* This is everything but the left side of a join.
+/*
+ * It may seem silly to separate joined_table from table_ref, but there is
+ * method in SQL92's madness: if you don't do it this way you get reduce-
+ * reduce conflicts, because it's not clear to the parser generator whether
+ * to expect alias_clause after ')' or not. For the same reason we must
+ * treat 'JOIN' and 'join_type JOIN' separately, rather than allowing
+ * join_type to expand to empty; if we try it, the parser generator can't
+ * figure out when to reduce an empty join_type right after table_ref.
+ *
* Note that a CROSS JOIN is the same as an unqualified
* INNER JOIN, and an INNER JOIN/ON has the same shape
* but a qualification expression to limit membership.
* tables and the shape is determined by which columns are
* in common. We'll collect columns during the later transformations.
*/
-join_expr: join_type JOIN table_expr join_qual
+
+joined_table: '(' joined_table ')'
+ {
+ $$ = $2;
+ }
+ | table_ref CROSS JOIN table_ref
{
+ /* CROSS JOIN is same as unqualified inner join */
JoinExpr *n = makeNode(JoinExpr);
- n->jointype = $1;
- n->rarg = (Node *)$3;
- n->quals = (List *)$4;
+ n->jointype = JOIN_INNER;
+ n->isNatural = FALSE;
+ n->larg = $1;
+ n->rarg = $4;
+ n->using = NIL;
+ n->quals = NULL;
$$ = n;
}
- | NATURAL join_type JOIN table_expr
+ | table_ref UNIONJOIN table_ref
{
+ /* UNION JOIN is made into 1 token to avoid shift/reduce
+ * conflict against regular UNION keyword.
+ */
JoinExpr *n = makeNode(JoinExpr);
- n->jointype = $2;
- n->isNatural = TRUE;
- n->rarg = (Node *)$4;
- n->quals = NULL; /* figure out which columns later... */
+ n->jointype = JOIN_UNION;
+ n->isNatural = FALSE;
+ n->larg = $1;
+ n->rarg = $3;
+ n->using = NIL;
+ n->quals = NULL;
$$ = n;
}
- | CROSS JOIN table_expr
+ | table_ref join_type JOIN table_ref join_qual
{
JoinExpr *n = makeNode(JoinExpr);
- n->jointype = INNER_P;
+ n->jointype = $2;
n->isNatural = FALSE;
- n->rarg = (Node *)$3;
- n->quals = NULL;
+ n->larg = $1;
+ n->rarg = $4;
+ if ($5 != NULL && IsA($5, List))
+ n->using = (List *) $5; /* USING clause */
+ else
+ n->quals = $5; /* ON clause */
$$ = n;
}
- ;
-
-join_clause_with_union: join_clause_with_union join_expr_with_union
+ | table_ref JOIN table_ref join_qual
{
- $2->larg = (Node *)$1;
- $$ = $2;
+ /* letting join_type reduce to empty doesn't work */
+ JoinExpr *n = makeNode(JoinExpr);
+ n->jointype = JOIN_INNER;
+ n->isNatural = FALSE;
+ n->larg = $1;
+ n->rarg = $3;
+ if ($4 != NULL && IsA($4, List))
+ n->using = (List *) $4; /* USING clause */
+ else
+ n->quals = $4; /* ON clause */
+ $$ = n;
}
- | table_expr join_expr_with_union
+ | table_ref NATURAL join_type JOIN table_ref
{
- $2->larg = (Node *)$1;
- $$ = $2;
+ JoinExpr *n = makeNode(JoinExpr);
+ n->jointype = $3;
+ n->isNatural = TRUE;
+ n->larg = $1;
+ n->rarg = $5;
+ n->using = NIL; /* figure out which columns later... */
+ n->quals = NULL; /* fill later */
+ $$ = n;
}
- ;
-
-join_expr_with_union: join_expr
- { $$ = $1; }
- | UNION JOIN table_expr
+ | table_ref NATURAL JOIN table_ref
{
+ /* letting join_type reduce to empty doesn't work */
JoinExpr *n = makeNode(JoinExpr);
- n->jointype = UNION;
- n->rarg = (Node *)$3;
- n->quals = NULL;
+ n->jointype = JOIN_INNER;
+ n->isNatural = TRUE;
+ n->larg = $1;
+ n->rarg = $4;
+ n->using = NIL; /* figure out which columns later... */
+ n->quals = NULL; /* fill later */
$$ = n;
+ }
+ ;
- elog(ERROR,"UNION JOIN not yet implemented");
+alias_clause: AS ColId '(' name_list ')'
+ {
+ $$ = makeNode(Attr);
+ $$->relname = $2;
+ $$->attrs = $4;
+ }
+ | AS ColId
+ {
+ $$ = makeNode(Attr);
+ $$->relname = $2;
+ }
+ | ColId '(' name_list ')'
+ {
+ $$ = makeNode(Attr);
+ $$->relname = $1;
+ $$->attrs = $3;
+ }
+ | ColId
+ {
+ $$ = makeNode(Attr);
+ $$->relname = $1;
}
;
-/* OUTER is just noise... */
-join_type: FULL join_outer { $$ = FULL; }
- | LEFT join_outer { $$ = LEFT; }
- | RIGHT join_outer { $$ = RIGHT; }
- | OUTER_P { $$ = LEFT; }
- | INNER_P { $$ = INNER_P; }
- | /*EMPTY*/ { $$ = INNER_P; }
+join_type: FULL join_outer { $$ = JOIN_FULL; }
+ | LEFT join_outer { $$ = JOIN_LEFT; }
+ | RIGHT join_outer { $$ = JOIN_RIGHT; }
+ | INNER_P { $$ = JOIN_INNER; }
;
+/* OUTER is just noise... */
join_outer: OUTER_P { $$ = NULL; }
- | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
+ | /*EMPTY*/ { $$ = NULL; }
;
/* JOIN qualification clauses
* USING ( column list ) allows only unqualified column names,
* which must match between tables.
* ON expr allows more general qualifications.
- * - thomas 1999-01-07
+ *
+ * We return USING as a List node, while an ON-expr will not be a List.
*/
-join_qual: USING '(' using_list ')' { $$ = (Node *)$3; }
- | ON a_expr { $$ = (Node *)$2; }
+join_qual: USING '(' name_list ')' { $$ = (Node *) $3; }
+ | ON a_expr { $$ = $2; }
;
-using_list: using_list ',' using_expr { $$ = lappend($1, $3); }
- | using_expr { $$ = lcons($1, NIL); }
- ;
-
-using_expr: ColId
- {
- /* could be a column name or a relation_name */
- Ident *n = makeNode(Ident);
- n->name = $1;
- n->indirection = NULL;
- $$ = n;
- }
- ;
-
-where_clause: WHERE a_expr { $$ = $2; }
- | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
- ;
relation_expr: relation_name
{
/* default inheritance */
- $$ = makeNode(RelExpr);
+ $$ = makeNode(RangeVar);
$$->relname = $1;
$$->inh = SQL_inheritance;
+ $$->name = NULL;
}
| relation_name '*' %prec '='
{
/* inheritance query */
- $$ = makeNode(RelExpr);
+ $$ = makeNode(RangeVar);
$$->relname = $1;
$$->inh = TRUE;
+ $$->name = NULL;
}
| ONLY relation_name %prec '='
{
/* no inheritance */
- $$ = makeNode(RelExpr);
+ $$ = makeNode(RangeVar);
$$->relname = $2;
$$->inh = FALSE;
+ $$->name = NULL;
}
;
-opt_array_bounds: '[' ']' opt_array_bounds
- { $$ = lcons(makeInteger(-1), $3); }
- | '[' Iconst ']' opt_array_bounds
- { $$ = lcons(makeInteger($2), $4); }
- | /*EMPTY*/
- { $$ = NIL; }
+where_clause: WHERE a_expr { $$ = $2; }
+ | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ }
;
}
;
+opt_array_bounds: '[' ']' opt_array_bounds
+ { $$ = lcons(makeInteger(-1), $3); }
+ | '[' Iconst ']' opt_array_bounds
+ { $$ = lcons(makeInteger($2), $4); }
+ | /*EMPTY*/
+ { $$ = NIL; }
+ ;
+
SimpleTypename: ConstTypename
| ConstInterval
;
void parser_init(Oid *typev, int nargs)
{
+ saved_relname[0] = '\0';
QueryIsRule = FALSE;
- saved_relname[0]= '\0';
-
- param_type_init(typev, nargs);
-}
-
-
-/*
- * param_type_init()
- *
- * Keep enough information around to fill out the type of param nodes
- * used in postquel functions
- */
-static void
-param_type_init(Oid *typev, int nargs)
-{
- pfunc_num_args = nargs;
+ /*
+ * Keep enough information around to fill out the type of param nodes
+ * used in postquel functions
+ */
param_type_info = typev;
+ pfunc_num_args = nargs;
}
Oid param_type(int t)
{
- if ((t > pfunc_num_args) || (t == 0))
+ if ((t > pfunc_num_args) || (t <= 0))
return InvalidOid;
return param_type_info[t - 1];
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.39 2000/07/17 03:05:02 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.40 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
if (contain_agg_clause(qry->qual))
elog(ERROR, "Aggregates not allowed in WHERE clause");
+ /*
+ * ON-conditions in JOIN expressions are like WHERE clauses.
+ */
+ if (contain_agg_clause((Node *) qry->jointree))
+ elog(ERROR, "Aggregates not allowed in JOIN conditions");
/*
* No aggregates allowed in GROUP BY clauses, either.
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.65 2000/06/15 03:32:19 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.66 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "access/heapam.h"
#include "optimizer/tlist.h"
#include "nodes/makefuncs.h"
+#include "parser/analyze.h"
#include "parser/parse.h"
+#include "parser/parsetree.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
+static void extractUniqueColumns(List *common_colnames,
+ List *src_colnames, List *src_colvars,
+ List **res_colnames, List **res_colvars);
+static Node *transformUsingClause(ParseState *pstate,
+ List *leftVars, List *rightVars);
+static RangeTblRef *transformTableEntry(ParseState *pstate, RangeVar *r);
+static RangeTblRef *transformRangeSubselect(ParseState *pstate,
+ RangeSubselect *r);
+static Node *transformFromClauseItem(ParseState *pstate, Node *n);
static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node,
List *tlist, int clause);
-static void parseFromClause(ParseState *pstate, List *frmList);
-static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
static List *addTargetToSortList(TargetEntry *tle, List *sortlist,
List *targetlist, char *opname);
static bool exprIsInSortList(Node *expr, List *sortList, List *targetList);
-#ifndef DISABLE_OUTER_JOINS
-static List *transformUsingClause(ParseState *pstate, List *using,
- List *left, List *right);
-#endif
-
/*
* makeRangeTable -
* Build the initial range table from the FROM clause.
+ *
+ * The range table constructed here may grow as we transform the expressions
+ * in the query's quals and target list. (Note that this happens because in
+ * POSTQUEL, we allow references to relations not specified in the
+ * from-clause. PostgreSQL keeps this extension to standard SQL.)
+ *
+ * Note: we assume that pstate's p_rtable and p_jointree lists were
+ * initialized to NIL when the pstate was created. We will add onto
+ * any entries already present --- this is needed for rule processing!
*/
void
makeRangeTable(ParseState *pstate, List *frmList)
{
- /* Currently, nothing to do except this: */
- parseFromClause(pstate, frmList);
+ List *fl;
+
+ /*
+ * The grammar will have produced a list of RangeVars, RangeSubselects,
+ * and/or JoinExprs. Transform each one, and then add it to the join tree.
+ */
+ foreach(fl, frmList)
+ {
+ Node *n = lfirst(fl);
+
+ n = transformFromClauseItem(pstate, n);
+ pstate->p_jointree = lappend(pstate->p_jointree, n);
+ }
}
/*
* setTargetTable
- * Add the target relation of INSERT or UPDATE to the range table,
+ * Add the target relation of INSERT/UPDATE/DELETE to the range table,
* and make the special links to it in the ParseState.
*
- * Note that the target is not marked as either inFromCl or inJoinSet.
+ * inJoinSet says whether to add the target to the join tree.
* For INSERT, we don't want the target to be joined to; it's a
* destination of tuples, not a source. For UPDATE/DELETE, we do
- * need to scan or join the target. This will happen without the
- * inJoinSet flag because the planner's preprocess_targetlist()
- * adds the destination's CTID attribute to the targetlist, and
- * therefore the destination will be a referenced table even if
- * there is no other use of any of its attributes. Tricky, eh?
+ * need to scan or join the target.
*/
void
-setTargetTable(ParseState *pstate, char *relname, bool inh)
+setTargetTable(ParseState *pstate, char *relname, bool inh, bool inJoinSet)
{
RangeTblEntry *rte;
/* look for relname only at current nesting level... */
if (refnameRangeTablePosn(pstate, relname, NULL) == 0)
- rte = addRangeTableEntry(pstate, relname,
- makeAttr(relname, NULL),
- inh, FALSE, FALSE);
+ {
+ rte = addRangeTableEntry(pstate, relname, NULL, inh, false);
+ }
else
+ {
rte = refnameRangeTableEntry(pstate, relname);
+ /* XXX what if pre-existing entry has wrong inh setting? */
+ }
+
+ if (inJoinSet)
+ addRTEtoJoinTree(pstate, rte);
/* This could only happen for multi-action rules */
if (pstate->p_target_relation != NULL)
}
-static Node *
-mergeInnerJoinQuals(ParseState *pstate, Node *clause)
-{
- List *jquals;
-
- foreach(jquals, pstate->p_join_quals)
- {
- Node *jqual = (Node *) lfirst(jquals);
-
- if (clause == NULL)
- clause = jqual;
- else
- {
- A_Expr *a = makeNode(A_Expr);
-
- a->oper = AND;
- a->opname = NULL;
- a->lexpr = clause;
- a->rexpr = jqual;
- clause = (Node *) a;
- }
- }
-
- /* Make sure that we don't add same quals twice... */
- pstate->p_join_quals = NIL;
-
- return clause;
-} /* mergeInnerJoinQuals() */
-
/*
- * transformWhereClause -
- * transforms the qualification and make sure it is of type Boolean
+ * Extract all not-in-common columns from column lists of a source table
*/
-Node *
-transformWhereClause(ParseState *pstate, Node *clause)
-{
- Node *qual;
-
- if (pstate->p_join_quals != NIL)
- clause = mergeInnerJoinQuals(pstate, clause);
-
- if (clause == NULL)
- return NULL;
-
- pstate->p_in_where_clause = true;
- qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
- pstate->p_in_where_clause = false;
-
- if (exprType(qual) != BOOLOID)
- {
- elog(ERROR, "WHERE clause must return type bool, not type %s",
- typeidTypeName(exprType(qual)));
- }
- return qual;
-}
-
-#ifndef DISABLE_JOIN_SYNTAX
-char *
- AttrString(Attr *attr);
-
-char *
-AttrString(Attr *attr)
-{
- Value *val;
-
- Assert(length(attr->attrs) == 1);
-
- val = lfirst(attr->attrs);
-
- Assert(IsA(val, String));
-
- return strVal(val);
-}
-
-List *
- ListTableAsAttrs(ParseState *pstate, char *table);
-List *
-ListTableAsAttrs(ParseState *pstate, char *table)
-{
- Attr *attr = expandTable(pstate, table, TRUE);
- List *rlist = NIL;
- List *col;
-
- foreach(col, attr->attrs)
- {
- Attr *a = makeAttr(table, strVal((Value *) lfirst(col)));
-
- rlist = lappend(rlist, a);
- }
-
- return rlist;
-}
-
-List *
- makeUniqueAttrList(List *candidates, List *idents);
-List *
-makeUniqueAttrList(List *attrs, List *filter)
+static void
+extractUniqueColumns(List *common_colnames,
+ List *src_colnames, List *src_colvars,
+ List **res_colnames, List **res_colvars)
{
- List *result = NULL;
- List *candidate;
+ List *new_colnames = NIL;
+ List *new_colvars = NIL;
+ List *lnames,
+ *lvars = src_colvars;
- foreach(candidate, attrs)
+ foreach(lnames, src_colnames)
{
- List *fmember;
- bool match = FALSE;
- Attr *cattr = lfirst(candidate);
+ char *colname = strVal(lfirst(lnames));
+ bool match = false;
+ List *cnames;
- Assert(IsA(cattr, Attr));
- Assert(length(cattr->attrs) == 1);
-
- foreach(fmember, filter)
+ foreach(cnames, common_colnames)
{
- Attr *fattr = lfirst(fmember);
-
- Assert(IsA(fattr, Attr));
- Assert(length(fattr->attrs) == 1);
+ char *ccolname = strVal(lfirst(cnames));
- if (strcmp(strVal(lfirst(cattr->attrs)), strVal(lfirst(fattr->attrs))) == 0)
+ if (strcmp(colname, ccolname) == 0)
{
- match = TRUE;
+ match = true;
break;
}
}
if (!match)
- result = lappend(result, cattr);
- }
-
- return result;
-}
-
-List *
- makeAttrList(Attr *attr);
-
-List *
-makeAttrList(Attr *attr)
-{
- List *result = NULL;
-
- char *name = attr->relname;
- List *col;
-
- foreach(col, attr->attrs)
- {
- Attr *newattr = makeAttr(name, strVal((Value *) lfirst(col)));
-
- result = lappend(result, newattr);
- }
-
- return result;
-}
-#ifdef NOT_USED
-/* ExpandAttrs()
- * Take an existing attribute node and return a list of attribute nodes
- * with one attribute name per node.
- */
-List *
-ExpandAttrs(Attr *attr)
-{
- List *col;
- char *relname = attr->relname;
- List *rlist = NULL;
-
- Assert(attr != NULL);
-
- if ((attr->attrs == NULL) || (length(attr->attrs) <= 1))
- return lcons(attr, NIL);
-
- foreach(col, attr->attrs)
- {
- Attr *attr = lfirst(col);
+ {
+ new_colnames = lappend(new_colnames, lfirst(lnames));
+ new_colvars = lappend(new_colvars, lfirst(lvars));
+ }
- rlist = lappend(rlist, makeAttr(relname, AttrString(attr)));
+ lvars = lnext(lvars);
}
- return rlist;
+ *res_colnames = new_colnames;
+ *res_colvars = new_colvars;
}
-#endif
/* transformUsingClause()
- * Take an ON or USING clause from a join expression and expand if necessary.
- * Result is an implicitly-ANDed list of untransformed qualification clauses.
+ * Build a complete ON clause from a partially-transformed USING list.
+ * We are given lists of Var nodes representing left and right match columns.
+ * Result is a transformed qualification expression.
*/
-static List *
-transformUsingClause(ParseState *pstate, List *usingList,
- List *leftList, List *rightList)
+static Node *
+transformUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
{
- List *result = NIL;
- List *using;
+ Node *result = NULL;
+ List *lvars,
+ *rvars = rightVars;
- foreach(using, usingList)
+ /*
+ * We cheat a little bit here by building an untransformed operator
+ * tree whose leaves are the already-transformed Vars. This is OK
+ * because transformExpr() won't complain about already-transformed
+ * subnodes.
+ */
+ foreach(lvars, leftVars)
{
- Attr *uattr = lfirst(using);
- Attr *lattr = NULL,
- *rattr = NULL;
- List *col;
+ Node *lvar = (Node *) lfirst(lvars);
+ Node *rvar = (Node *) lfirst(rvars);
A_Expr *e;
- /*
- * find the first instances of this column in the shape list and
- * the last table in the shape list...
- */
- foreach(col, leftList)
- {
- Attr *attr = lfirst(col);
+ e = makeNode(A_Expr);
+ e->oper = OP;
+ e->opname = "=";
+ e->lexpr = copyObject(lvar);
+ e->rexpr = copyObject(rvar);
- if (strcmp(AttrString(attr), AttrString(uattr)) == 0)
- {
- lattr = attr;
- break;
- }
- }
- foreach(col, rightList)
+ if (result == NULL)
+ result = (Node *) e;
+ else
{
- Attr *attr = lfirst(col);
+ A_Expr *a = makeNode(A_Expr);
- if (strcmp(AttrString(attr), AttrString(uattr)) == 0)
- {
- rattr = attr;
- break;
- }
+ a->oper = AND;
+ a->opname = NULL;
+ a->lexpr = result;
+ a->rexpr = (Node *) e;
+ result = (Node *) a;
}
- Assert((lattr != NULL) && (rattr != NULL));
+ rvars = lnext(rvars);
+ }
- e = makeNode(A_Expr);
- e->oper = OP;
- e->opname = "=";
- e->lexpr = (Node *) lattr;
- e->rexpr = (Node *) rattr;
+ result = transformExpr(pstate, result, EXPR_COLUMN_FIRST);
- result = lappend(result, e);
+ if (exprType(result) != BOOLOID)
+ {
+ /* This could only happen if someone defines a funny version of '=' */
+ elog(ERROR, "USING clause must return type bool, not type %s",
+ typeidTypeName(exprType(result)));
}
return result;
} /* transformUsingClause() */
-#endif
-
-static RangeTblEntry *
+/*
+ * transformTableEntry --- transform a RangeVar (simple relation reference)
+ */
+static RangeTblRef *
transformTableEntry(ParseState *pstate, RangeVar *r)
{
- RelExpr *baserel = r->relExpr;
- char *relname = baserel->relname;
-
-#if 0
- char *refname;
- List *columns;
-
-#endif
+ char *relname = r->relname;
RangeTblEntry *rte;
-
-#if 0
- if (r->name != NULL)
- refname = r->name->relname;
- else
- refname = NULL;
-
- columns = ListTableAsAttrs(pstate, relname);
-
- /* alias might be specified... */
- if (r->name != NULL)
- {
-#ifndef DISABLE_JOIN_SYNTAX
- if (length(columns) > 0)
- {
- if (length(r->name->attrs) > 0)
- {
- if (length(columns) != length(r->name->attrs))
- elog(ERROR, "'%s' has %d columns but %d %s specified",
- relname, length(columns), length(r->name->attrs),
- ((length(r->name->attrs) != 1) ? "aliases" : "alias"));
-
- aliasList = nconc(aliasList, r->name->attrs);
- }
- else
- {
- r->name->attrs = columns;
-
- aliasList = nconc(aliasList, r->name->attrs);
- }
- }
- else
- elog(NOTICE, "transformTableEntry: column aliases not handled (internal error)");
-#else
- elog(ERROR, "Column aliases not yet supported");
-#endif
- }
- else
- {
- refname = relname;
- aliasList = nconc(aliasList, columns);
- }
-#endif
-
- if (r->name == NULL)
- r->name = makeAttr(relname, NULL);
+ RangeTblRef *rtr;
/*
- * marks this entry to indicate it comes from the FROM clause. In SQL,
+ * mark this entry to indicate it comes from the FROM clause. In SQL,
* the target list can only refer to range variables specified in the
* from clause but we follow the more powerful POSTQUEL semantics and
* automatically generate the range variable if not specified. However
* there are times we need to know whether the entries are legitimate.
- *
- * eg. select * from foo f where f.x = 1; will generate wrong answer if
- * we expand * to foo.x.
*/
+ rte = addRangeTableEntry(pstate, relname, r->name, r->inh, true);
- rte = addRangeTableEntry(pstate, relname, r->name,
- baserel->inh, TRUE, TRUE);
+ /*
+ * We create a RangeTblRef, but we do not add it to the jointree here.
+ * makeRangeTable will do so, if we are at top level of the FROM clause.
+ */
+ rtr = makeNode(RangeTblRef);
+ /* assume new rte is at end */
+ rtr->rtindex = length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
- return rte;
-} /* transformTableEntry() */
+ return rtr;
+}
/*
- * parseFromClause -
- * turns the table references specified in the from-clause into a
- * range table. The range table may grow as we transform the expressions
- * in the target list. (Note that this happens because in POSTQUEL, we
- * allow references to relations not specified in the from-clause. We
- * also allow now as an extension.)
- *
- * The FROM clause can now contain JoinExpr nodes, which contain parsing info
- * for inner and outer joins. The USING clause must be expanded into a qualification
- * for an inner join at least, since that is compatible with the old syntax.
- * Not sure yet how to handle outer joins, but it will become clear eventually?
- * - thomas 1998-12-16
+ * transformRangeSubselect --- transform a sub-SELECT appearing in FROM
*/
-static void
-parseFromClause(ParseState *pstate, List *frmList)
+static RangeTblRef *
+transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
{
- List *fl;
+ SelectStmt *subquery = (SelectStmt *) r->subquery;
+ List *parsetrees;
+ Query *query;
- foreach(fl, frmList)
- {
- Node *n = lfirst(fl);
+ /*
+ * subquery node might not be SelectStmt if user wrote something like
+ * FROM (SELECT ... UNION SELECT ...). Our current implementation of
+ * UNION/INTERSECT/EXCEPT is too messy to deal with here, so punt until
+ * we redesign querytrees to make it more reasonable.
+ */
+ if (subquery == NULL || !IsA(subquery, SelectStmt))
+ elog(ERROR, "Set operations not yet supported in subselects in FROM");
- /*
- * marks this entry to indicate it comes from the FROM clause. In
- * SQL, the target list can only refer to range variables
- * specified in the from clause but we follow the more powerful
- * POSTQUEL semantics and automatically generate the range
- * variable if not specified. However there are times we need to
- * know whether the entries are legitimate.
- *
- * eg. select * from foo f where f.x = 1; will generate wrong answer
- * if we expand * to foo.x.
- */
+ /*
+ * Analyze and transform the subquery as if it were an independent
+ * statement (we do NOT want it to see the outer query as a parent).
+ */
+ parsetrees = parse_analyze(lcons(subquery, NIL), NULL);
- /* Plain vanilla inner join, just like we've always had? */
- if (IsA(n, RangeVar))
- transformTableEntry(pstate, (RangeVar *) n);
+ /*
+ * Check that we got something reasonable. Some of these conditions
+ * are probably impossible given restrictions of the grammar, but
+ * check 'em anyway.
+ */
+ if (length(parsetrees) != 1)
+ elog(ERROR, "Unexpected parse analysis result for subselect in FROM");
+ query = (Query *) lfirst(parsetrees);
+ if (query == NULL || !IsA(query, Query))
+ elog(ERROR, "Unexpected parse analysis result for subselect in FROM");
- /* A newfangled join expression? */
- else if (IsA(n, JoinExpr))
- {
-#ifndef DISABLE_JOIN_SYNTAX
- RangeTblEntry *l_rte,
- *r_rte;
- Attr *l_name,
- *r_name = NULL;
- JoinExpr *j = (JoinExpr *) n;
-
- if (j->alias != NULL)
- elog(ERROR, "JOIN table aliases are not supported");
-
- /* nested join? then handle the left one first... */
- if (IsA(j->larg, JoinExpr))
- {
- parseFromClause(pstate, lcons(j->larg, NIL));
- l_name = ((JoinExpr *) j->larg)->alias;
- }
- else
- {
- Assert(IsA(j->larg, RangeVar));
- l_rte = transformTableEntry(pstate, (RangeVar *) j->larg);
- l_name = expandTable(pstate, l_rte->eref->relname, TRUE);
- }
+ if (query->commandType != CMD_SELECT)
+ elog(ERROR, "Expected SELECT query from subselect in FROM");
+ if (query->resultRelation != 0 || query->into != NULL)
+ elog(ERROR, "Subselect in FROM may not have SELECT INTO");
- if (IsA(j->rarg, JoinExpr))
- {
- parseFromClause(pstate, lcons(j->rarg, NIL));
- l_name = ((JoinExpr *) j->larg)->alias;
- }
- else
- {
- Assert(IsA(j->rarg, RangeVar));
- r_rte = transformTableEntry(pstate, (RangeVar *) j->rarg);
- r_name = expandTable(pstate, r_rte->eref->relname, TRUE);
- }
- /*
- * Natural join does not explicitly specify columns; must
- * generate columns to join. Need to run through the list of
- * columns from each table or join result and match up the
- * column names. Use the first table, and check every column
- * in the second table for a match.
- */
- if (j->isNatural)
- {
- List *lx,
- *rx;
- List *rlist = NULL;
+ elog(ERROR, "Subselect in FROM not done yet");
- foreach(lx, l_name->attrs)
- {
- Ident *id = NULL;
- Value *l_col = lfirst(lx);
+ return NULL;
+}
- Assert(IsA(l_col, String));
- foreach(rx, r_name->attrs)
- {
- Value *r_col = lfirst(rx);
+/*
+ * transformFromClauseItem -
+ * Transform a FROM-clause item, adding any required entries to the
+ * range table list being built in the ParseState, and return the
+ * transformed item ready to include in the jointree list.
+ * This routine can recurse to handle SQL92 JOIN expressions.
+ */
+static Node *
+transformFromClauseItem(ParseState *pstate, Node *n)
+{
+ if (IsA(n, RangeVar))
+ {
+ /* Plain relation reference */
+ return (Node *) transformTableEntry(pstate, (RangeVar *) n);
+ }
+ else if (IsA(n, RangeSubselect))
+ {
+ /* Plain relation reference */
+ return (Node *) transformRangeSubselect(pstate, (RangeSubselect *) n);
+ }
+ else if (IsA(n, JoinExpr))
+ {
+ /* A newfangled join expression */
+ JoinExpr *j = (JoinExpr *) n;
+ List *l_colnames,
+ *r_colnames,
+ *res_colnames,
+ *l_colvars,
+ *r_colvars,
+ *res_colvars;
- Assert(IsA(r_col, String));
+ /*
+ * Recursively process the left and right subtrees
+ */
+ j->larg = transformFromClauseItem(pstate, j->larg);
+ j->rarg = transformFromClauseItem(pstate, j->rarg);
- if (strcmp(strVal(l_col), strVal(r_col)) == 0)
- {
- id = (Ident *) makeNode(Ident);
- id->name = strVal(l_col);
- break;
- }
- }
+ /*
+ * Extract column name and var lists from both subtrees
+ */
+ if (IsA(j->larg, JoinExpr))
+ {
+ /* Make a copy of the subtree's lists so we can modify! */
+ l_colnames = copyObject(((JoinExpr *) j->larg)->colnames);
+ l_colvars = copyObject(((JoinExpr *) j->larg)->colvars);
+ }
+ else
+ {
+ RangeTblEntry *rte;
- /* right column matched? then keep as join column... */
- if (id != NULL)
- rlist = lappend(rlist, id);
- }
- j->quals = rlist;
+ Assert(IsA(j->larg, RangeTblRef));
+ rte = rt_fetch(((RangeTblRef *) j->larg)->rtindex,
+ pstate->p_rtable);
+ expandRTE(pstate, rte, &l_colnames, &l_colvars);
+ /* expandRTE returns new lists, so no need for copyObject */
+ }
+ if (IsA(j->rarg, JoinExpr))
+ {
+ /* Make a copy of the subtree's lists so we can modify! */
+ r_colnames = copyObject(((JoinExpr *) j->rarg)->colnames);
+ r_colvars = copyObject(((JoinExpr *) j->rarg)->colvars);
+ }
+ else
+ {
+ RangeTblEntry *rte;
- printf("NATURAL JOIN columns are %s\n", nodeToString(rlist));
- }
+ Assert(IsA(j->rarg, RangeTblRef));
+ rte = rt_fetch(((RangeTblRef *) j->rarg)->rtindex,
+ pstate->p_rtable);
+ expandRTE(pstate, rte, &r_colnames, &r_colvars);
+ /* expandRTE returns new lists, so no need for copyObject */
+ }
- if (j->jointype == INNER_P)
+ /*
+ * Natural join does not explicitly specify columns; must
+ * generate columns to join. Need to run through the list of
+ * columns from each table or join result and match up the
+ * column names. Use the first table, and check every column
+ * in the second table for a match. (We'll check that the
+ * matches were unique later on.)
+ * The result of this step is a list of column names just like an
+ * explicitly-written USING list.
+ */
+ if (j->isNatural)
+ {
+ List *rlist = NIL;
+ List *lx,
+ *rx;
+
+ Assert(j->using == NIL); /* shouldn't have USING() too */
+
+ foreach(lx, l_colnames)
{
- /* CROSS JOIN */
- if (j->quals == NULL)
- printf("CROSS JOIN...\n");
+ char *l_colname = strVal(lfirst(lx));
+ Value *m_name = NULL;
- /*
- * JOIN/USING This is an inner join, so rip apart the join
- * node and transform into a traditional FROM list.
- * NATURAL JOIN and JOIN USING both change the shape of
- * the result. Need to generate a list of result columns
- * to use for target list expansion and validation.
- */
- else if (IsA(j->quals, List))
+ foreach(rx, r_colnames)
{
+ char *r_colname = strVal(lfirst(rx));
- /*
- * List of Ident nodes means column names from a real
- * USING clause. Determine the shape of the joined
- * table.
- */
- List *ucols,
- *ucol;
- List *shape = NULL;
- List *alias = NULL;
- List *l_shape,
- *r_shape;
-
- List *l_cols = makeAttrList(l_name);
- List *r_cols = makeAttrList(r_name);
-
- printf("USING input tables are:\n %s\n %s\n",
- nodeToString(l_name), nodeToString(r_name));
-
- printf("USING expanded tables are:\n %s\n %s\n",
- nodeToString(l_cols), nodeToString(r_cols));
-
- /* Columns from the USING clause... */
- ucols = (List *) j->quals;
- foreach(ucol, ucols)
+ if (strcmp(l_colname, r_colname) == 0)
{
- List *col;
- Attr *l_attr = NULL,
- *r_attr = NULL;
- Ident *id = lfirst(ucol);
-
- Attr *attr = makeAttr("", id->name);
-
- foreach(col, l_cols)
- {
- attr = lfirst(col);
- if (strcmp(AttrString(attr), id->name) == 0)
- {
- l_attr = attr;
- break;
- }
- }
-
- foreach(col, r_cols)
- {
- attr = lfirst(col);
- if (strcmp(AttrString(attr), id->name) == 0)
- {
- r_attr = attr;
- break;
- }
- }
-
- if (l_attr == NULL)
- elog(ERROR, "USING column '%s' not found in table '%s'",
- id->name, l_name->relname);
- if (r_attr == NULL)
- elog(ERROR, "USING column '%s' not found in table '%s'",
- id->name, r_name->relname);
-
- shape = lappend(shape, l_attr);
- alias = lappend(alias, makeAttr("", AttrString(l_attr)));
+ m_name = makeString(l_colname);
+ break;
}
- printf("JOIN/USING join columns are %s\n", nodeToString(shape));
-
- /* Remaining columns from the left side... */
- l_shape = makeUniqueAttrList(makeAttrList(l_name), shape);
-
- printf("JOIN/USING left columns are %s\n", nodeToString(l_shape));
-
- r_shape = makeUniqueAttrList(makeAttrList(r_name), shape);
+ }
- printf("JOIN/USING right columns are %s\n", nodeToString(r_shape));
+ /* matched a right column? then keep as join column... */
+ if (m_name != NULL)
+ rlist = lappend(rlist, m_name);
+ }
- printf("JOIN/USING input quals are %s\n", nodeToString(j->quals));
+ j->using = rlist;
+ }
- j->quals = transformUsingClause(pstate, shape, l_cols, r_cols);
+ /*
+ * Now transform the join qualifications, if any.
+ */
+ res_colnames = NIL;
+ res_colvars = NIL;
- printf("JOIN/USING transformed quals are %s\n", nodeToString(j->quals));
+ if (j->using)
+ {
+ /*
+ * JOIN/USING (or NATURAL JOIN, as transformed above).
+ * Transform the list into an explicit ON-condition,
+ * and generate a list of result columns.
+ */
+ List *ucols = j->using;
+ List *l_usingvars = NIL;
+ List *r_usingvars = NIL;
+ List *ucol;
- alias = nconc(nconc(alias, listCopy(l_shape)), listCopy(r_shape));
- shape = nconc(nconc(shape, l_shape), r_shape);
+ Assert(j->quals == NULL); /* shouldn't have ON() too */
- printf("JOIN/USING shaped table is %s\n", nodeToString(shape));
- printf("JOIN/USING alias list is %s\n", nodeToString(alias));
+ foreach(ucol, ucols)
+ {
+ char *u_colname = strVal(lfirst(ucol));
+ List *col;
+ Node *l_colvar,
+ *r_colvar,
+ *colvar;
+ int ndx;
+ int l_index = -1;
+ int r_index = -1;
+
+ ndx = 0;
+ foreach(col, l_colnames)
+ {
+ char *l_colname = strVal(lfirst(col));
- pstate->p_shape = shape;
- pstate->p_alias = alias;
+ if (strcmp(l_colname, u_colname) == 0)
+ {
+ if (l_index >= 0)
+ elog(ERROR, "Common column name \"%s\" appears more than once in left table", u_colname);
+ l_index = ndx;
+ }
+ ndx++;
}
+ if (l_index < 0)
+ elog(ERROR, "USING column \"%s\" not found in left table",
+ u_colname);
- /* otherwise, must be an expression from an ON clause... */
- else
- j->quals = (List *) lcons(j->quals, NIL);
-
- /* listCopy may not be needed here --- will j->quals list
- * be used again anywhere? The #ifdef'd code below may need
- * it, if it ever gets used...
- */
- pstate->p_join_quals = nconc(pstate->p_join_quals,
- listCopy(j->quals));
-
-#if 0
- if (qual == NULL)
- elog(ERROR, "JOIN/ON not supported in this context");
-
- printf("Table aliases are %s\n", nodeToString(*aliasList));
-#endif
-
-#if 0
- /* XXX this code is WRONG because j->quals is a List
- * not a simple expression. Perhaps *qual
- * ought also to be a List and we append to it,
- * similarly to the way p_join_quals is handled above?
- */
- if (*qual == NULL)
+ ndx = 0;
+ foreach(col, r_colnames)
{
- /* merge qualified join clauses... */
- if (j->quals != NULL)
+ char *r_colname = strVal(lfirst(col));
+
+ if (strcmp(r_colname, u_colname) == 0)
{
- if (*qual != NULL)
- {
- A_Expr *a = makeNode(A_Expr);
-
- a->oper = AND;
- a->opname = NULL;
- a->lexpr = (Node *) *qual;
- a->rexpr = (Node *) j->quals;
-
- *qual = (Node *) a;
- }
- else
- *qual = (Node *) j->quals;
+ if (r_index >= 0)
+ elog(ERROR, "Common column name \"%s\" appears more than once in right table", u_colname);
+ r_index = ndx;
}
+ ndx++;
}
- else
+ if (r_index < 0)
+ elog(ERROR, "USING column \"%s\" not found in right table",
+ u_colname);
+
+ l_colvar = nth(l_index, l_colvars);
+ l_usingvars = lappend(l_usingvars, l_colvar);
+ r_colvar = nth(r_index, r_colvars);
+ r_usingvars = lappend(r_usingvars, r_colvar);
+
+ res_colnames = lappend(res_colnames,
+ nth(l_index, l_colnames));
+ switch (j->jointype)
{
- elog(ERROR, "Multiple JOIN/ON clauses not handled (internal error)");
- *qual = lappend(*qual, j->quals);
+ case JOIN_INNER:
+ case JOIN_LEFT:
+ colvar = l_colvar;
+ break;
+ case JOIN_RIGHT:
+ colvar = r_colvar;
+ break;
+ default:
+ {
+ /* Need COALESCE(l_colvar, r_colvar) */
+ CaseExpr *c = makeNode(CaseExpr);
+ CaseWhen *w = makeNode(CaseWhen);
+ A_Expr *a = makeNode(A_Expr);
+
+ a->oper = NOTNULL;
+ a->lexpr = l_colvar;
+ w->expr = (Node *) a;
+ w->result = l_colvar;
+ c->args = lcons(w, NIL);
+ c->defresult = r_colvar;
+ colvar = transformExpr(pstate, (Node *) c,
+ EXPR_COLUMN_FIRST);
+ break;
+ }
}
-#endif
-
- /*
- * if we are transforming this node back into a FROM list,
- * then we will need to replace the node with two nodes.
- * Will need access to the previous list item to change
- * the link pointer to reference these new nodes. Try
- * accumulating and returning a new list. - thomas
- * 1999-01-08 Not doing this yet though!
- */
+ res_colvars = lappend(res_colvars, colvar);
+ }
+ j->quals = transformUsingClause(pstate, l_usingvars, r_usingvars);
+ }
+ else if (j->quals)
+ {
+ /* User-written ON-condition; transform it */
+ j->quals = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST);
+ if (exprType(j->quals) != BOOLOID)
+ {
+ elog(ERROR, "ON clause must return type bool, not type %s",
+ typeidTypeName(exprType(j->quals)));
}
- else if ((j->jointype == LEFT)
- || (j->jointype == RIGHT)
- || (j->jointype == FULL))
- elog(ERROR, "OUTER JOIN is not yet supported");
- else
- elog(ERROR, "Unrecognized JOIN clause; tag is %d (internal error)",
- j->jointype);
-#else
- elog(ERROR, "JOIN expressions are not yet implemented");
-#endif
+ /* XXX should check that ON clause refers only to joined tbls */
}
else
- elog(ERROR, "parseFromClause: unexpected FROM clause node (internal error)"
- "\n\t%s", nodeToString(n));
+ {
+ /* CROSS JOIN: no quals */
+ }
+
+ /* Add remaining columns from each side to the output columns */
+ extractUniqueColumns(res_colnames,
+ l_colnames, l_colvars,
+ &l_colnames, &l_colvars);
+ extractUniqueColumns(res_colnames,
+ r_colnames, r_colvars,
+ &r_colnames, &r_colvars);
+ res_colnames = nconc(res_colnames, l_colnames);
+ res_colvars = nconc(res_colvars, l_colvars);
+ res_colnames = nconc(res_colnames, r_colnames);
+ res_colvars = nconc(res_colvars, r_colvars);
+
+ /*
+ * Process alias (AS clause), if any.
+ *
+ * The given table alias must be unique in the current nesting level,
+ * ie it cannot match any RTE refname or jointable alias. This is
+ * a bit painful to check because my own child joins are not yet in
+ * the pstate's jointree, so they have to be scanned separately.
+ */
+ if (j->alias)
+ {
+ /* Check against previously created RTEs and jointree entries */
+ if (refnameRangeOrJoinEntry(pstate, j->alias->relname, NULL))
+ elog(ERROR, "Table name \"%s\" specified more than once",
+ j->alias->relname);
+ /* Check children */
+ if (scanJoinTreeForRefname(j->larg, j->alias->relname) ||
+ scanJoinTreeForRefname(j->rarg, j->alias->relname))
+ elog(ERROR, "Table name \"%s\" specified more than once",
+ j->alias->relname);
+ /*
+ * If a column alias list is specified, substitute the alias
+ * names into my output-column list
+ */
+ if (j->alias->attrs != NIL)
+ {
+ if (length(j->alias->attrs) != length(res_colnames))
+ elog(ERROR, "Column alias list for \"%s\" has wrong number of entries (need %d)",
+ j->alias->relname, length(res_colnames));
+ res_colnames = j->alias->attrs;
+ }
+ }
+
+ j->colnames = res_colnames;
+ j->colvars = res_colvars;
+
+ return (Node *) j;
+ }
+ else
+ elog(ERROR, "transformFromClauseItem: unexpected node (internal error)"
+ "\n\t%s", nodeToString(n));
+ return NULL; /* can't get here, just keep compiler quiet */
+}
+
+
+/*
+ * transformWhereClause -
+ * transforms the qualification and make sure it is of type Boolean
+ */
+Node *
+transformWhereClause(ParseState *pstate, Node *clause)
+{
+ Node *qual;
+
+ if (clause == NULL)
+ return NULL;
+
+ qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
+
+ if (exprType(qual) != BOOLOID)
+ {
+ elog(ERROR, "WHERE clause must return type bool, not type %s",
+ typeidTypeName(exprType(qual)));
}
-} /* parseFromClause() */
+ return qual;
+}
/*
* is a matching column. If so, fall through to let
* transformExpr() do the rest. NOTE: if name could refer
* ambiguously to more than one column name exposed by FROM,
- * colnameRangeTableEntry will elog(ERROR). That's just what
+ * colnameToVar will elog(ERROR). That's just what
* we want here.
*/
- if (colnameRangeTableEntry(pstate, name) != NULL)
+ if (colnameToVar(pstate, name) != NULL)
name = NULL;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.82 2000/08/08 15:42:03 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.83 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
case OP:
{
- Node *lexpr = transformExpr(pstate, a->lexpr, precedence);
- Node *rexpr = transformExpr(pstate, a->rexpr, precedence);
+ Node *lexpr = transformExpr(pstate,
+ a->lexpr,
+ precedence);
+ Node *rexpr = transformExpr(pstate,
+ a->rexpr,
+ precedence);
result = (Node *) make_op(a->opname, lexpr, rexpr);
}
break;
case ISNULL:
{
- Node *lexpr = transformExpr(pstate, a->lexpr, precedence);
+ Node *lexpr = transformExpr(pstate,
+ a->lexpr,
+ precedence);
result = ParseFuncOrColumn(pstate,
"nullvalue",
lcons(lexpr, NIL),
false, false,
- &pstate->p_last_resno,
precedence);
}
break;
case NOTNULL:
{
- Node *lexpr = transformExpr(pstate, a->lexpr, precedence);
+ Node *lexpr = transformExpr(pstate,
+ a->lexpr,
+ precedence);
result = ParseFuncOrColumn(pstate,
"nonnullvalue",
lcons(lexpr, NIL),
false, false,
- &pstate->p_last_resno,
precedence);
}
break;
case AND:
{
+ Node *lexpr = transformExpr(pstate,
+ a->lexpr,
+ precedence);
+ Node *rexpr = transformExpr(pstate,
+ a->rexpr,
+ precedence);
Expr *expr = makeNode(Expr);
- Node *lexpr = transformExpr(pstate, a->lexpr, precedence);
- Node *rexpr = transformExpr(pstate, a->rexpr, precedence);
if (exprType(lexpr) != BOOLOID)
elog(ERROR, "left-hand side of AND is type '%s', not '%s'",
break;
case OR:
{
+ Node *lexpr = transformExpr(pstate,
+ a->lexpr,
+ precedence);
+ Node *rexpr = transformExpr(pstate,
+ a->rexpr,
+ precedence);
Expr *expr = makeNode(Expr);
- Node *lexpr = transformExpr(pstate, a->lexpr, precedence);
- Node *rexpr = transformExpr(pstate, a->rexpr, precedence);
if (exprType(lexpr) != BOOLOID)
elog(ERROR, "left-hand side of OR is type '%s', not '%s'",
break;
case NOT:
{
+ Node *rexpr = transformExpr(pstate,
+ a->rexpr,
+ precedence);
Expr *expr = makeNode(Expr);
- Node *rexpr = transformExpr(pstate, a->rexpr, precedence);
if (exprType(rexpr) != BOOLOID)
elog(ERROR, "argument to NOT is type '%s', not '%s'",
/* transform the list of arguments */
foreach(args, fn->args)
- lfirst(args) = transformExpr(pstate, (Node *) lfirst(args), precedence);
+ lfirst(args) = transformExpr(pstate,
+ (Node *) lfirst(args),
+ precedence);
result = ParseFuncOrColumn(pstate,
fn->funcname,
fn->args,
fn->agg_star,
fn->agg_distinct,
- &pstate->p_last_resno,
precedence);
break;
}
{
Node *basenode;
- basenode = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno,
- precedence);
+ basenode = ParseNestedFuncOrColumn(pstate, att, precedence);
return transformIndirection(pstate, basenode, att->indirection);
}
transformIdent(ParseState *pstate, Ident *ident, int precedence)
{
Node *result = NULL;
- RangeTblEntry *rte;
/*
* try to find the ident as a relation ... but not if subscripts
if (result == NULL || precedence == EXPR_COLUMN_FIRST)
{
/* try to find the ident as a column */
- if ((rte = colnameRangeTableEntry(pstate, ident->name)) != NULL)
- {
- /* Convert it to a fully qualified Attr, and transform that */
- Attr *att = makeAttr(rte->eref->relname, ident->name);
-
- att->indirection = ident->indirection;
- return transformAttr(pstate, att, precedence);
- }
+ Node *var = colnameToVar(pstate, ident->name);
+
+ if (var != NULL)
+ result = transformIndirection(pstate, var, ident->indirection);
}
if (result == NULL)
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.89 2000/08/24 03:29:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.90 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
** a tree with of Iter and Func nodes.
*/
Node *
-ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int precedence)
+ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int precedence)
{
List *mutator_iter;
Node *retval = NULL;
if (attr->paramNo != NULL)
{
- Param *param = (Param *) transformExpr(pstate, (Node *) attr->paramNo, EXPR_RELATION_FIRST);
+ Param *param = (Param *) transformExpr(pstate,
+ (Node *) attr->paramNo,
+ EXPR_RELATION_FIRST);
retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)),
lcons(param, NIL),
false, false,
- curr_resno,
precedence);
}
else
retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)),
lcons(ident, NIL),
false, false,
- curr_resno,
precedence);
}
retval = ParseFuncOrColumn(pstate, strVal(lfirst(mutator_iter)),
lcons(retval, NIL),
false, false,
- curr_resno,
precedence);
}
Node *
ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
bool agg_star, bool agg_distinct,
- int *curr_resno, int precedence)
+ int precedence)
{
Oid rettype = InvalidOid;
Oid argrelid = InvalidOid;
Oid funcid = InvalidOid;
List *i = NIL;
Node *first_arg = NULL;
- char *relname = NULL;
- char *refname = NULL;
+ char *refname;
Relation rd;
- Oid relid;
int nargs = length(fargs);
Func *funcnode;
Oid oid_array[FUNC_MAX_ARGS];
if (IsA(first_arg, Ident) && ((Ident *) first_arg)->isRel)
{
Ident *ident = (Ident *) first_arg;
- RangeTblEntry *rte;
- AttrNumber attnum;
/*
* first arg is a relation. This could be a projection.
*/
refname = ident->name;
- rte = refnameRangeTableEntry(pstate, refname);
- if (rte == NULL)
- {
- rte = addRangeTableEntry(pstate, refname,
- makeAttr(refname, NULL),
- FALSE, FALSE, TRUE);
- warnAutoRange(pstate, refname);
- }
-
- relname = rte->relname;
- relid = rte->relid;
- attnum = InvalidAttrNumber;
-
- /*
- * If the attr isn't a set, just make a var for it. If it is
- * a set, treat it like a function and drop through. Look
- * through the explicit column list first, since we now allow
- * column aliases. - thomas 2000-02-07
- */
- if (rte->eref->attrs != NULL)
- {
- List *c;
-
- /*
- * start counting attributes/columns from one. zero is
- * reserved for InvalidAttrNumber. - thomas 2000-01-27
- */
- int i = 1;
-
- foreach(c, rte->eref->attrs)
- {
- char *colname = strVal(lfirst(c));
-
- /* found a match? */
- if (strcmp(colname, funcname) == 0)
- {
- char *basename = get_attname(relid, i);
-
- if (basename != NULL)
- {
- funcname = basename;
- attnum = i;
- }
-
- /*
- * attnum was initialized to InvalidAttrNumber
- * earlier, so no need to reset it if the above
- * test fails. - thomas 2000-02-07
- */
- break;
- }
- i++;
- }
- if (attnum == InvalidAttrNumber)
- attnum = specialAttNum(funcname);
- }
- else
- attnum = get_attnum(relid, funcname);
+ retval = qualifiedNameToVar(pstate, refname, funcname, true);
+ if (retval)
+ return retval;
- if (attnum != InvalidAttrNumber)
- {
- return (Node *) make_var(pstate,
- relid,
- refname,
- funcname);
- }
- /* else drop through - attr is a set */
+ /* else drop through - attr is a set or function */
}
else if (ISCOMPLEX(exprType(first_arg)))
{
toid = exprType(first_arg);
rd = heap_openr_nofail(typeidTypeName(toid));
if (RelationIsValid(rd))
- {
- relname = RelationGetRelationName(rd);
heap_close(rd, NoLock);
- }
else
elog(ERROR, "Type '%s' is not a relation type",
typeidTypeName(toid));
rte = refnameRangeTableEntry(pstate, refname);
if (rte == NULL)
- {
- rte = addRangeTableEntry(pstate, refname,
- makeAttr(refname, NULL),
- FALSE, FALSE, TRUE);
- warnAutoRange(pstate, refname);
- }
-
- relname = rte->relname;
+ rte = addImplicitRTE(pstate, refname);
- vnum = refnameRangeTablePosn(pstate, rte->eref->relname,
- &sublevels_up);
+ vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
/*
* for func(relname), the param to the function is the tuple
* but has varattno == 0 to signal that the whole tuple is the
* argument.
*/
- toid = typeTypeId(typenameType(relname));
+ toid = typeTypeId(typenameType(rte->relname));
+
/* replace it in the arg list */
lfirst(i) = makeVar(vnum, 0, toid, -1, sublevels_up);
}
/* perform the necessary typecasting of arguments */
make_arguments(pstate, nargs, fargs, oid_array, true_oid_array);
- /*
- * Special checks to disallow sequence functions with side-effects
- * in WHERE clauses. This is pretty much of a hack; why disallow these
- * when we have no way to check for side-effects of user-defined fns?
- */
- if (funcid == F_NEXTVAL && pstate->p_in_where_clause)
- elog(ERROR, "Sequence function nextval is not allowed in WHERE clauses");
- if (funcid == F_SETVAL && pstate->p_in_where_clause)
- elog(ERROR, "Sequence function setval is not allowed in WHERE clauses");
-
expr = makeNode(Expr);
expr->typeOid = rettype;
expr->opType = FUNC_EXPR;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.45 2000/08/24 03:29:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.46 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
pstate = palloc(sizeof(ParseState));
MemSet(pstate, 0, sizeof(ParseState));
- pstate->p_last_resno = 1;
pstate->parentParseState = parentParseState;
+ pstate->p_last_resno = 1;
return pstate;
}
/*
* make_var
- * Build a Var node for an attribute identified by name
+ * Build a Var node for an attribute identified by RTE and attrno
*/
Var *
-make_var(ParseState *pstate, Oid relid, char *refname,
- char *attrname)
+make_var(ParseState *pstate, RangeTblEntry *rte, int attrno)
{
- HeapTuple tp;
- Form_pg_attribute att_tup;
int vnum,
- attid;
+ sublevels_up;
Oid vartypeid;
int32 type_mod;
- int sublevels_up;
-
- vnum = refnameRangeTablePosn(pstate, refname, &sublevels_up);
-
- tp = SearchSysCacheTuple(ATTNAME,
- ObjectIdGetDatum(relid),
- PointerGetDatum(attrname),
- 0, 0);
- if (!HeapTupleIsValid(tp))
- elog(ERROR, "Relation %s does not have attribute %s",
- refname, attrname);
- att_tup = (Form_pg_attribute) GETSTRUCT(tp);
- attid = att_tup->attnum;
- vartypeid = att_tup->atttypid;
- type_mod = att_tup->atttypmod;
-
- return makeVar(vnum, attid, vartypeid, type_mod, sublevels_up);
+
+ vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
+
+ if (rte->relid != InvalidOid)
+ {
+ /* Plain relation RTE --- get the attribute's type info */
+ HeapTuple tp;
+ Form_pg_attribute att_tup;
+
+ tp = SearchSysCacheTuple(ATTNUM,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(attrno),
+ 0, 0);
+ /* this shouldn't happen... */
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "Relation %s does not have attribute %d",
+ rte->relname, attrno);
+ att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+ vartypeid = att_tup->atttypid;
+ type_mod = att_tup->atttypmod;
+ }
+ else
+ {
+ /* Subselect RTE --- get type info from subselect's tlist */
+ elog(ERROR, "make_var: subselect in FROM not implemented yet");
+ vartypeid = type_mod = 0;
+ }
+
+ return makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up);
}
/*
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.46 2000/08/08 15:42:04 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.47 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "access/htup.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
+#include "rewrite/rewriteManip.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
+ char *colname);
+static Node *scanJoinForColumn(JoinExpr *join, char *colname,
+ int sublevels_up);
+static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
+static void warnAutoRange(ParseState *pstate, char *refname);
+
+
/*
* Information defining the "system" attributes of every relation.
*/
#define SPECIALS ((int) (sizeof(special_attr)/sizeof(special_attr[0])))
-#ifdef NOT_USED
-/* refnameRangeTableEntries()
- * Given refname, return a list of range table entries
- * This is possible with JOIN syntax, where tables in a join
- * acquire the same reference name.
- * - thomas 2000-01-20
- * But at the moment we aren't carrying along a full list of
- * table/column aliases, so we don't have the full mechanism
- * to support outer joins in place yet.
- * - thomas 2000-03-04
+/*
+ * refnameRangeOrJoinEntry
+ * Given a refname, look to see if it matches any RTE or join table.
+ * If so, return a pointer to the RangeTblEntry or JoinExpr.
+ * Optionally get its nesting depth (0 = current). If sublevels_up
+ * is NULL, only consider items at the current nesting level.
*/
-
-static List *
-refnameRangeTableEntries(ParseState *pstate, char *refname)
+Node *
+refnameRangeOrJoinEntry(ParseState *pstate,
+ char *refname,
+ int *sublevels_up)
{
- List *rteList = NULL;
- List *temp;
+ if (sublevels_up)
+ *sublevels_up = 0;
while (pstate != NULL)
{
+ List *temp;
+ JoinExpr *join;
+
+ /*
+ * Check the rangetable for RTEs; if no match, recursively scan
+ * the jointree for join tables. We assume that no duplicate
+ * entries have been made in any one nesting level.
+ */
foreach(temp, pstate->p_rtable)
{
RangeTblEntry *rte = lfirst(temp);
if (strcmp(rte->eref->relname, refname) == 0)
- rteList = lappend(rteList, rte);
+ return (Node *) rte;
}
+
+ join = scanJoinTreeForRefname((Node *) pstate->p_jointree, refname);
+ if (join)
+ return (Node *) join;
+
pstate = pstate->parentParseState;
+ if (sublevels_up)
+ (*sublevels_up)++;
+ else
+ break;
}
- return rteList;
+ return NULL;
}
-#endif
-/* given refname, return a pointer to the range table entry */
+/* Recursively search a jointree for a joinexpr with given refname */
+JoinExpr *
+scanJoinTreeForRefname(Node *jtnode, char *refname)
+{
+ JoinExpr *result = NULL;
+
+ if (jtnode == NULL)
+ return NULL;
+ if (IsA(jtnode, List))
+ {
+ List *l;
+
+ foreach(l, (List *) jtnode)
+ {
+ result = scanJoinTreeForRefname(lfirst(l), refname);
+ if (result)
+ break;
+ }
+ }
+ else if (IsA(jtnode, RangeTblRef))
+ {
+ /* ignore ... */
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ if (j->alias && strcmp(j->alias->relname, refname) == 0)
+ return j;
+ result = scanJoinTreeForRefname(j->larg, refname);
+ if (! result)
+ result = scanJoinTreeForRefname(j->rarg, refname);
+ }
+ else
+ elog(ERROR, "scanJoinTreeForRefname: unexpected node type %d",
+ nodeTag(jtnode));
+ return result;
+}
+
+/*
+ * given refname, return a pointer to the range table entry.
+ *
+ * NOTE that this routine will ONLY find RTEs, not join tables.
+ */
RangeTblEntry *
refnameRangeTableEntry(ParseState *pstate, char *refname)
{
return NULL;
}
-/* given refname, return RT index (starting with 1) of the relation,
+/*
+ * given refname, return RT index (starting with 1) of the relation,
* and optionally get its nesting depth (0 = current). If sublevels_up
* is NULL, only consider rels at the current nesting level.
+ * A zero result means name not found.
+ *
+ * NOTE that this routine will ONLY find RTEs, not join tables.
*/
int
refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
}
/*
- * returns range entry if found, else NULL
+ * given an RTE, return RT index (starting with 1) of the entry,
+ * and optionally get its nesting depth (0 = current). If sublevels_up
+ * is NULL, only consider rels at the current nesting level.
+ * Raises error if RTE not found.
*/
-RangeTblEntry *
-colnameRangeTableEntry(ParseState *pstate, char *colname)
+int
+RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
{
- List *et;
- List *rtable;
- RangeTblEntry *rte_result = NULL;
+ int index;
+ List *temp;
+
+ if (sublevels_up)
+ *sublevels_up = 0;
while (pstate != NULL)
{
- if (pstate->p_is_rule)
- rtable = lnext(lnext(pstate->p_rtable));
+ index = 1;
+ foreach(temp, pstate->p_rtable)
+ {
+ if (rte == (RangeTblEntry *) lfirst(temp))
+ return index;
+ index++;
+ }
+ pstate = pstate->parentParseState;
+ if (sublevels_up)
+ (*sublevels_up)++;
else
- rtable = pstate->p_rtable;
+ break;
+ }
+ elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)");
+ return 0; /* keep compiler quiet */
+}
+
+/*
+ * scanRTEForColumn
+ * Search the column names of a single RTE for the given name.
+ * If found, return an appropriate Var node, else return NULL.
+ * If the name proves ambiguous within this RTE, raise error.
+ */
+static Node *
+scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
+{
+ Node *result = NULL;
+ int attnum = 0;
+ List *c;
- foreach(et, rtable)
+ /*
+ * Scan the user column names (or aliases) for a match.
+ * Complain if multiple matches.
+ */
+ foreach(c, rte->eref->attrs)
+ {
+ attnum++;
+ if (strcmp(strVal(lfirst(c)), colname) == 0)
{
- RangeTblEntry *rte_candidate = NULL;
- RangeTblEntry *rte = lfirst(et);
+ if (result)
+ elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
+ result = (Node *) make_var(pstate, rte, attnum);
+ }
+ }
- /* only consider RTEs mentioned in FROM or UPDATE/DELETE */
- if (!rte->inFromCl && rte != pstate->p_target_rangetblentry)
- continue;
+ /*
+ * If we have a unique match, return it. Note that this allows a user
+ * alias to override a system column name (such as OID) without error.
+ */
+ if (result)
+ return result;
- if (rte->eref->attrs != NULL)
- {
- List *c;
-
- foreach(c, rte->ref->attrs)
- {
- if (strcmp(strVal(lfirst(c)), colname) == 0)
- {
- if (rte_candidate != NULL)
- elog(ERROR, "Column '%s' is ambiguous"
- " (internal error)", colname);
- rte_candidate = rte;
- }
- }
- }
+ /*
+ * If the RTE represents a table (not a sub-select), consider system
+ * column names.
+ */
+ if (rte->relid != InvalidOid)
+ {
+ attnum = specialAttNum(colname);
+ if (attnum != InvalidAttrNumber)
+ result = (Node *) make_var(pstate, rte, attnum);
+ }
+
+ return result;
+}
+/*
+ * scanJoinForColumn
+ * Search the column names of a single join table for the given name.
+ * If found, return an appropriate Var node or expression, else return NULL.
+ * If the name proves ambiguous within this jointable, raise error.
+ */
+static Node *
+scanJoinForColumn(JoinExpr *join, char *colname, int sublevels_up)
+{
+ Node *result = NULL;
+ int attnum = 0;
+ List *c;
+
+ foreach(c, join->colnames)
+ {
+ attnum++;
+ if (strcmp(strVal(lfirst(c)), colname) == 0)
+ {
+ if (result)
+ elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
+ result = copyObject(nth(attnum-1, join->colvars));
/*
- * Even if we have an attribute list in the RTE, look for the
- * column here anyway. This is the only way we will find
- * implicit columns like "oid". - thomas 2000-02-07
+ * If referencing an uplevel join item, we must adjust
+ * sublevels settings in the copied expression.
*/
- if ((rte_candidate == NULL)
- && (get_attnum(rte->relid, colname) != InvalidAttrNumber))
- rte_candidate = rte;
+ if (sublevels_up > 0)
+ IncrementVarSublevelsUp(result, sublevels_up, 0);
+ }
+ }
+ return result;
+}
+
+/*
+ * colnameToVar
+ * Search for an unqualified column name.
+ * If found, return the appropriate Var node (or expression).
+ * If not found, return NULL. If the name proves ambiguous, raise error.
+ */
+Node *
+colnameToVar(ParseState *pstate, char *colname)
+{
+ Node *result = NULL;
+ ParseState *orig_pstate = pstate;
+ int levels_up = 0;
- if (rte_candidate == NULL)
- continue;
+ while (pstate != NULL)
+ {
+ List *jt;
- if (rte_result != NULL)
+ /*
+ * We want to look only at top-level jointree items, and even for
+ * those, ignore RTEs that are marked as not inFromCl and not
+ * the query's target relation.
+ */
+ foreach(jt, pstate->p_jointree)
+ {
+ Node *jtnode = (Node *) lfirst(jt);
+ Node *newresult = NULL;
+
+ if (IsA(jtnode, RangeTblRef))
{
- if (!pstate->p_is_insert ||
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+ RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
+
+ if (! rte->inFromCl &&
rte != pstate->p_target_rangetblentry)
- elog(ERROR, "Column '%s' is ambiguous", colname);
+ continue;
+
+ /* use orig_pstate here to get the right sublevels_up */
+ newresult = scanRTEForColumn(orig_pstate, rte, colname);
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ newresult = scanJoinForColumn(j, colname, levels_up);
}
else
- rte_result = rte;
+ elog(ERROR, "colnameToVar: unexpected node type %d",
+ nodeTag(jtnode));
+
+ if (newresult)
+ {
+ if (result)
+ elog(ERROR, "Column reference \"%s\" is ambiguous",
+ colname);
+ result = newresult;
+ }
}
- if (rte_result != NULL)
+ if (result != NULL)
break; /* found */
pstate = pstate->parentParseState;
+ levels_up++;
}
- return rte_result;
+
+ return result;
}
/*
- * put new entry in pstate p_rtable structure, or return pointer
- * if pstate null
+ * qualifiedNameToVar
+ * Search for a qualified column name (refname + column name).
+ * If found, return the appropriate Var node (or expression).
+ * If not found, return NULL. If the name proves ambiguous, raise error.
+ */
+Node *
+qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
+ bool implicitRTEOK)
+{
+ Node *result;
+ Node *rteorjoin;
+ int sublevels_up;
+
+ rteorjoin = refnameRangeOrJoinEntry(pstate, refname, &sublevels_up);
+
+ if (rteorjoin == NULL)
+ {
+ if (! implicitRTEOK)
+ return NULL;
+ rteorjoin = (Node *) addImplicitRTE(pstate, refname);
+ sublevels_up = 0;
+ }
+
+ if (IsA(rteorjoin, RangeTblEntry))
+ result = scanRTEForColumn(pstate, (RangeTblEntry *) rteorjoin,
+ colname);
+ else if (IsA(rteorjoin, JoinExpr))
+ result = scanJoinForColumn((JoinExpr *) rteorjoin,
+ colname, sublevels_up);
+ else
+ {
+ elog(ERROR, "qualifiedNameToVar: unexpected node type %d",
+ nodeTag(rteorjoin));
+ result = NULL; /* keep compiler quiet */
+ }
+
+ return result;
+}
+
+/*
+ * Add an entry to the pstate's range table (p_rtable), unless the
+ * specified refname is already present, in which case raise error.
+ *
+ * If pstate is NULL, we just build an RTE and return it without worrying
+ * about membership in an rtable list.
*/
RangeTblEntry *
addRangeTableEntry(ParseState *pstate,
char *relname,
- Attr *ref,
+ Attr *alias,
bool inh,
- bool inFromCl,
- bool inJoinSet)
+ bool inFromCl)
{
+ char *refname = alias ? alias->relname : relname;
Relation rel;
RangeTblEntry *rte;
Attr *eref;
int maxattrs;
- int sublevels_up;
+ int numaliases;
int varattno;
- /* Look for an existing rte, if available... */
+ /* Check for conflicting RTE or jointable alias (at level 0 only) */
if (pstate != NULL)
{
- int rt_index = refnameRangeTablePosn(pstate, ref->relname,
- &sublevels_up);
+ Node *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
- if (rt_index != 0 && (!inFromCl || sublevels_up == 0))
- {
- if (!strcmp(ref->relname, "*OLD*") || !strcmp(ref->relname, "*NEW*"))
- return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable);
- elog(ERROR, "Table name '%s' specified more than once", ref->relname);
- }
+ if (rteorjoin)
+ elog(ERROR, "Table name \"%s\" specified more than once",
+ refname);
}
rte = makeNode(RangeTblEntry);
rte->relname = relname;
- rte->ref = ref;
+ rte->alias = alias;
/*
* Get the rel's OID. This access also ensures that we have an
rte->relid = RelationGetRelid(rel);
maxattrs = RelationGetNumberOfAttributes(rel);
- eref = copyObject(ref);
- if (maxattrs < length(eref->attrs))
- elog(ERROR, "Table '%s' has %d columns available but %d columns specified",
- relname, maxattrs, length(eref->attrs));
+ eref = alias ? copyObject(alias) : makeAttr(refname, NULL);
+ numaliases = length(eref->attrs);
+
+ if (maxattrs < numaliases)
+ elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
+ refname, maxattrs, numaliases);
/* fill in any unspecified alias columns */
- for (varattno = length(eref->attrs); varattno < maxattrs; varattno++)
+ for (varattno = numaliases; varattno < maxattrs; varattno++)
{
char *attrname;
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
eref->attrs = lappend(eref->attrs, makeString(attrname));
}
- heap_close(rel, AccessShareLock);
rte->eref = eref;
- /*
- * Flags: - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause, - this RTE should be included in
- * the planner's final join.
+ heap_close(rel, AccessShareLock);
+
+ /*----------
+ * Flags:
+ * - this RTE should be expanded to include descendant tables,
+ * - this RTE is in the FROM clause,
+ * - this RTE should not be checked for access rights.
+ *----------
*/
rte->inh = inh;
rte->inFromCl = inFromCl;
- rte->inJoinSet = inJoinSet;
rte->skipAcl = false; /* always starts out false */
/*
return rte;
}
-/* expandTable()
- * Populates an Attr with table name and column names
- * This is similar to expandAll(), but does not create an RTE
- * if it does not already exist.
- * - thomas 2000-01-19
+/*
+ * Add the given RTE as a top-level entry in the pstate's join tree,
+ * unless there already is an entry for it.
*/
-Attr *
-expandTable(ParseState *pstate, char *refname, bool getaliases)
+void
+addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte)
+{
+ int rtindex = RTERangeTablePosn(pstate, rte, NULL);
+ List *jt;
+ RangeTblRef *rtr;
+
+ foreach(jt, pstate->p_jointree)
+ {
+ Node *n = (Node *) lfirst(jt);
+
+ if (IsA(n, RangeTblRef))
+ {
+ if (rtindex == ((RangeTblRef *) n)->rtindex)
+ return; /* it's already being joined to */
+ }
+ }
+
+ /* Not present, so add it */
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = rtindex;
+ pstate->p_jointree = lappend(pstate->p_jointree, rtr);
+}
+
+/*
+ * Add a POSTQUEL-style implicit RTE.
+ *
+ * We assume caller has already checked that there is no such RTE now.
+ */
+RangeTblEntry *
+addImplicitRTE(ParseState *pstate, char *relname)
{
- Attr *attr;
RangeTblEntry *rte;
+
+ rte = addRangeTableEntry(pstate, relname, NULL, false, false);
+ addRTEtoJoinTree(pstate, rte);
+ warnAutoRange(pstate, relname);
+
+ return rte;
+}
+
+/* expandRTE()
+ *
+ * Given a rangetable entry, create lists of its column names (aliases if
+ * provided, else real names) and Vars for each column. Only user columns
+ * are considered, since this is primarily used to expand '*' and determine
+ * the contents of JOIN tables.
+ *
+ * If only one of the two kinds of output list is needed, pass NULL for the
+ * output pointer for the unwanted one.
+ */
+void
+expandRTE(ParseState *pstate, RangeTblEntry *rte,
+ List **colnames, List **colvars)
+{
Relation rel;
int varattno,
- maxattrs;
+ maxattrs,
+ rtindex,
+ sublevels_up;
- rte = refnameRangeTableEntry(pstate, refname);
+ if (colnames)
+ *colnames = NIL;
+ if (colvars)
+ *colvars = NIL;
- if (getaliases && (rte != NULL))
- return rte->eref;
+ /* Need the RT index of the entry for creating Vars */
+ rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
- if (rte != NULL)
- rel = heap_open(rte->relid, AccessShareLock);
- else
- rel = heap_openr(refname, AccessShareLock);
-
- if (rel == NULL)
- elog(ERROR, "Relation '%s' not found", refname);
+ rel = heap_open(rte->relid, AccessShareLock);
maxattrs = RelationGetNumberOfAttributes(rel);
- attr = makeAttr(refname, NULL);
-
for (varattno = 0; varattno < maxattrs; varattno++)
{
- char *attrname;
+ Form_pg_attribute attr = rel->rd_att->attrs[varattno];
#ifdef _DROP_COLUMN_HACK__
- if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
+ if (COLUMN_IS_DROPPED(attr))
continue;
#endif /* _DROP_COLUMN_HACK__ */
- attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
- attr->attrs = lappend(attr->attrs, makeString(attrname));
+
+ if (colnames)
+ {
+ char *label;
+
+ if (varattno < length(rte->eref->attrs))
+ label = strVal(nth(varattno, rte->eref->attrs));
+ else
+ label = NameStr(attr->attname);
+ *colnames = lappend(*colnames, makeString(pstrdup(label)));
+ }
+
+ if (colvars)
+ {
+ Var *varnode;
+
+ varnode = makeVar(rtindex, attr->attnum,
+ attr->atttypid, attr->atttypmod,
+ sublevels_up);
+
+ *colvars = lappend(*colvars, varnode);
+ }
}
heap_close(rel, AccessShareLock);
-
- return attr;
}
/*
- * expandAll -
- * makes a list of attributes
+ * expandRelAttrs -
+ * makes a list of TargetEntry nodes for the attributes of the rel
*/
List *
-expandAll(ParseState *pstate, char *relname, Attr *ref, int *this_resno)
+expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
{
- List *te_list = NIL;
- RangeTblEntry *rte;
- Relation rel;
- int varattno,
- maxattrs;
+ List *name_list,
+ *var_list;
- rte = refnameRangeTableEntry(pstate, ref->relname);
- if (rte == NULL)
- {
- rte = addRangeTableEntry(pstate, relname, ref,
- FALSE, FALSE, TRUE);
- warnAutoRange(pstate, ref->relname);
- }
+ expandRTE(pstate, rte, &name_list, &var_list);
- rel = heap_open(rte->relid, AccessShareLock);
+ return expandNamesVars(pstate, name_list, var_list);
+}
- maxattrs = RelationGetNumberOfAttributes(rel);
+/*
+ * expandJoinAttrs -
+ * makes a list of TargetEntry nodes for the attributes of the join
+ */
+List *
+expandJoinAttrs(ParseState *pstate, JoinExpr *join, int sublevels_up)
+{
+ List *vars;
- for (varattno = 0; varattno < maxattrs; varattno++)
- {
- char *attrname;
- char *label;
- Var *varnode;
- TargetEntry *te = makeNode(TargetEntry);
+ vars = copyObject(join->colvars);
+ /*
+ * If referencing an uplevel join item, we must adjust
+ * sublevels settings in the copied expression.
+ */
+ if (sublevels_up > 0)
+ IncrementVarSublevelsUp((Node *) vars, sublevels_up, 0);
-#ifdef _DROP_COLUMN_HACK__
- if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
- continue;
-#endif /* _DROP_COLUMN_HACK__ */
- attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
+ return expandNamesVars(pstate,
+ copyObject(join->colnames),
+ vars);
+}
- /*
- * varattno is zero-based, so check that length() is always
- * greater
- */
- if (length(rte->eref->attrs) > varattno)
- label = pstrdup(strVal(nth(varattno, rte->eref->attrs)));
- else
- label = attrname;
- varnode = make_var(pstate, rte->relid, relname, attrname);
+/*
+ * expandNamesVars -
+ * Workhorse for "*" expansion: produce a list of targetentries
+ * given lists of column names (as String nodes) and var references.
+ */
+static List *
+expandNamesVars(ParseState *pstate, List *names, List *vars)
+{
+ List *te_list = NIL;
- /*
- * Even if the elements making up a set are complex, the set
- * itself is not.
- */
+ while (names)
+ {
+ char *label = strVal(lfirst(names));
+ Node *varnode = (Node *) lfirst(vars);
+ TargetEntry *te = makeNode(TargetEntry);
- te->resdom = makeResdom((AttrNumber) (*this_resno)++,
- varnode->vartype,
- varnode->vartypmod,
+ te->resdom = makeResdom((AttrNumber) (pstate->p_last_resno)++,
+ exprType(varnode),
+ exprTypmod(varnode),
label,
false);
- te->expr = (Node *) varnode;
+ te->expr = varnode;
te_list = lappend(te_list, te);
+
+ names = lnext(names);
+ vars = lnext(vars);
}
- heap_close(rel, AccessShareLock);
+ Assert(vars == NIL); /* lists not same length? */
return te_list;
}
return rd->rd_att->attrs[attid - 1]->atttypid;
}
-void
+/*
+ * Generate a warning about an implicit RTE, if appropriate.
+ *
+ * Our current theory on this is that we should allow "SELECT foo.*"
+ * but warn about a mixture of explicit and implicit RTEs.
+ */
+static void
warnAutoRange(ParseState *pstate, char *refname)
{
- List *temp;
bool foundInFromCl = false;
+ List *temp;
foreach(temp, pstate->p_rtable)
{
}
}
if (foundInFromCl)
- elog(NOTICE, "Adding missing FROM-clause entry%s for table %s",
- pstate->parentParseState != NULL ? " in subquery" : "",
- refname);
+ elog(NOTICE, "Adding missing FROM-clause entry%s for table \"%s\"",
+ pstate->parentParseState != NULL ? " in subquery" : "",
+ refname);
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.61 2000/08/08 15:42:04 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.62 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
* Target item is a single '*', expand all tables (eg.
* SELECT * FROM emp)
*/
- if (pstate->p_shape != NULL)
- {
- List *s,
- *a;
- int i;
-
- Assert(length(pstate->p_shape) == length(pstate->p_alias));
-
- s = pstate->p_shape;
- a = pstate->p_alias;
- for (i = 0; i < length(pstate->p_shape); i++)
- {
- TargetEntry *te;
- char *colname;
- Attr *shape = lfirst(s);
- Attr *alias = lfirst(a);
-
- Assert(IsA(shape, Attr) &&IsA(alias, Attr));
-
- colname = strVal(lfirst(alias->attrs));
- te = transformTargetEntry(pstate, (Node *) shape,
- NULL, colname, false);
- p_target = lappend(p_target, te);
- s = lnext(s);
- a = lnext(a);
- }
- }
- else
- p_target = nconc(p_target,
- ExpandAllTables(pstate));
+ p_target = nconc(p_target,
+ ExpandAllTables(pstate));
}
else if (att->attrs != NIL &&
strcmp(strVal(lfirst(att->attrs)), "*") == 0)
* Target item is relation.*, expand that table (eg.
* SELECT emp.*, dname FROM emp, dept)
*/
- p_target = nconc(p_target,
- expandAll(pstate, att->relname,
- makeAttr(att->relname, NULL),
- &pstate->p_last_resno));
+ Node *rteorjoin;
+ int sublevels_up;
+
+ rteorjoin = refnameRangeOrJoinEntry(pstate, att->relname,
+ &sublevels_up);
+
+ if (rteorjoin == NULL)
+ {
+ rteorjoin = (Node *) addImplicitRTE(pstate, att->relname);
+ sublevels_up = 0;
+ }
+
+ if (IsA(rteorjoin, RangeTblEntry))
+ p_target = nconc(p_target,
+ expandRelAttrs(pstate,
+ (RangeTblEntry *) rteorjoin));
+ else if (IsA(rteorjoin, JoinExpr))
+ p_target = nconc(p_target,
+ expandJoinAttrs(pstate,
+ (JoinExpr *) rteorjoin,
+ sublevels_up));
+ else
+ elog(ERROR, "transformTargetList: unexpected node type %d",
+ nodeTag(rteorjoin));
}
else
{
*/
if (indirection)
{
-#ifndef DISABLE_JOIN_SYNTAX
- Attr *att = makeAttr(pstrdup(RelationGetRelationName(rd)), colname);
-
-#else
- Attr *att = makeNode(Attr);
-
-#endif
+ Attr *att = makeAttr(pstrdup(RelationGetRelationName(rd)),
+ colname);
Node *arrayBase;
ArrayRef *aref;
-#ifdef DISABLE_JOIN_SYNTAX
- att->relname = pstrdup(RelationGetRelationName(rd));
- att->attrs = lcons(makeString(colname), NIL);
-#endif
- arrayBase = ParseNestedFuncOrColumn(pstate, att,
- &pstate->p_last_resno,
- EXPR_COLUMN_FIRST);
+ arrayBase = ParseNestedFuncOrColumn(pstate, att, EXPR_COLUMN_FIRST);
aref = transformArraySubscripts(pstate, arrayBase,
indirection,
pstate->p_is_insert,
}
/* ExpandAllTables()
- * Turns '*' (in the target list) into a list of attributes
- * (of all relations in the range table)
+ * Turns '*' (in the target list) into a list of targetlist entries.
+ *
+ * tlist entries are generated for each relation appearing in the FROM list,
+ * which by now has been expanded into a join tree.
*/
static List *
ExpandAllTables(ParseState *pstate)
{
List *target = NIL;
- List *rt,
- *rtable;
-
- rtable = pstate->p_rtable;
- if (pstate->p_is_rule)
- {
-
- /*
- * skip first two entries, "*new*" and "*current*"
- */
- rtable = lnext(lnext(rtable));
- }
+ List *jt;
/* SELECT *; */
- if (rtable == NIL)
+ if (pstate->p_jointree == NIL)
elog(ERROR, "Wildcard with no tables specified not allowed");
- foreach(rt, rtable)
+ foreach(jt, pstate->p_jointree)
{
- RangeTblEntry *rte = lfirst(rt);
+ Node *n = (Node *) lfirst(jt);
- /*
- * we only expand those listed in the from clause. (This will also
- * prevent us from using the wrong table in inserts: eg. tenk2 in
- * "insert into tenk2 select * from tenk1;")
- */
- if (!rte->inFromCl)
- continue;
+ if (IsA(n, RangeTblRef))
+ {
+ RangeTblEntry *rte;
- target = nconc(target,
- expandAll(pstate, rte->eref->relname, rte->eref,
- &pstate->p_last_resno));
+ rte = rt_fetch(((RangeTblRef *) n)->rtindex,
+ pstate->p_rtable);
+
+ /*
+ * Ignore added-on relations that were not listed in the FROM
+ * clause.
+ */
+ if (!rte->inFromCl)
+ continue;
+
+ target = nconc(target, expandRelAttrs(pstate, rte));
+ }
+ else if (IsA(n, JoinExpr))
+ {
+ /* A newfangled join expression */
+ JoinExpr *j = (JoinExpr *) n;
+
+ /* Currently, a join expr could only have come from FROM. */
+ target = nconc(target, expandJoinAttrs(pstate, j, 0));
+ }
+ else
+ elog(ERROR, "ExpandAllTables: unexpected node (internal error)"
+ "\n\t%s", nodeToString(n));
}
+
return target;
}
/*-------------------------------------------------------------------------
*
* parser.c
+ * Main entry point/driver for PostgreSQL parser
+ *
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- *
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.45 2000/04/12 17:15:27 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.46 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
#include "parser/analyze.h"
#include "parser/gramparse.h"
+#include "parser/parse.h"
#include "parser/parser.h"
#include "parser/parse_expr.h"
+
#if defined(FLEX_SCANNER)
extern void DeleteBuffer(void);
-
#endif /* FLEX_SCANNER */
char *parseString; /* the char* which holds the string to be
* parsed */
List *parsetree; /* result of parsing is left here */
+static int lookahead_token; /* one-token lookahead */
+static bool have_lookahead; /* lookahead_token set? */
+
#ifdef SETS_FIXED
static void fixupsets();
static void define_sets();
List *queryList;
int yyresult;
- init_io();
-
- parseString = pstrdup(str);
+ parseString = str;
parsetree = NIL; /* in case parser forgets to set it */
+ have_lookahead = false;
+ scanner_init();
parser_init(typev, nargs);
parse_expr_init();
return queryList;
}
+
+/*
+ * Intermediate filter between parser and base lexer (base_yylex in scan.l).
+ *
+ * The filter is needed because in some cases SQL92 requires more than one
+ * token lookahead. We reduce these cases to one-token lookahead by combining
+ * tokens here, in order to keep the grammar LR(1).
+ *
+ * Using a filter is simpler than trying to recognize multiword tokens
+ * directly in scan.l, because we'd have to allow for comments between the
+ * words ...
+ */
+int
+yylex(void)
+{
+ int cur_token;
+
+ /* Get next token --- we might already have it */
+ if (have_lookahead)
+ {
+ cur_token = lookahead_token;
+ have_lookahead = false;
+ }
+ else
+ cur_token = base_yylex();
+
+ /* Do we need to look ahead for a possible multiword token? */
+ switch (cur_token)
+ {
+ case UNION:
+ /* UNION JOIN must be reduced to a single UNIONJOIN token */
+ lookahead_token = base_yylex();
+ if (lookahead_token == JOIN)
+ cur_token = UNIONJOIN;
+ else
+ have_lookahead = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return cur_token;
+}
+
+
#ifdef SETS_FIXED
static void
fixupsets(Query *parse)
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.76 2000/08/22 13:01:20 ishii Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.77 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static int literallen; /* actual current length */
static int literalalloc; /* current allocated buffer size */
-static int xcdepth = 0;
+static int xcdepth = 0; /* depth of nesting in slash-star comments */
#define startlit() (literalbuf[0] = '\0', literallen = 0)
static void addlit(char *ytext, int yleng);
%%
-void yyerror(const char * message)
+void
+yyerror(const char *message)
{
elog(ERROR, "parser: %s at or near \"%s\"", message, yytext);
}
-int yywrap()
+int
+yywrap(void)
{
return(1);
}
/*
- init_io:
+ scanner_init:
called by postgres before any actual parsing is done
*/
void
-init_io()
+scanner_init(void)
{
/* it's important to set this to NULL
because input()/myinput() checks the non-nullness of parseCh
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.31 2000/09/06 14:15:20 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.32 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
return true;
return false;
}
- if (IsA(node, SubLink))
+ if (IsA(node, Query))
{
+ /* Recurse into subselects */
+ bool result;
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (thisLockWasTriggered_walker((Node *) (sub->lefthand), context))
- return true;
context->sublevels_up++;
- if (thisLockWasTriggered_walker((Node *) (sub->subselect), context))
- {
- context->sublevels_up--; /* not really necessary */
- return true;
- }
+ result = query_tree_walker((Query *) node, thisLockWasTriggered_walker,
+ (void *) context);
context->sublevels_up--;
- return false;
- }
- if (IsA(node, Query))
- {
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
-
- if (thisLockWasTriggered_walker((Node *) (qry->targetList), context))
- return true;
- if (thisLockWasTriggered_walker((Node *) (qry->qual), context))
- return true;
- if (thisLockWasTriggered_walker((Node *) (qry->havingQual), context))
- return true;
- return false;
+ return result;
}
return expression_tree_walker(node, thisLockWasTriggered_walker,
(void *) context);
typedef struct
{
- Oid evowner;
+ Oid evowner;
} checkLockPerms_context;
static bool
{
if (node == NULL)
return false;
- if (IsA(node, SubLink))
- {
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (checkLockPerms_walker((Node *) (sub->lefthand), context))
- return true;
- if (checkLockPerms_walker((Node *) (sub->subselect), context))
- return true;
- return false;
- }
if (IsA(node, Query))
{
- /* Reach here after recursing down into subselect above... */
Query *qry = (Query *) node;
int rtablength = length(qry->rtable);
int i;
int32 reqperm;
int32 aclcheck_res;
- if (rte->ref != NULL)
- {
- if (strcmp(rte->ref->relname, "*NEW*") == 0)
- continue;
- if (strcmp(rte->ref->relname, "*OLD*") == 0)
- continue;
- }
+ if (strcmp(rte->eref->relname, "*NEW*") == 0)
+ continue;
+ if (strcmp(rte->eref->relname, "*OLD*") == 0)
+ continue;
if (i == qry->resultRelation)
switch (qry->commandType)
/* If there are sublinks, search for them and check their RTEs */
if (qry->hasSubLinks)
- {
- if (checkLockPerms_walker((Node *) (qry->targetList), context))
- return true;
- if (checkLockPerms_walker((Node *) (qry->qual), context))
- return true;
- if (checkLockPerms_walker((Node *) (qry->havingQual), context))
- return true;
- }
+ return query_tree_walker(qry, checkLockPerms_walker,
+ (void *) context);
return false;
}
return expression_tree_walker(node, checkLockPerms_walker,
return; /* nothing to check */
/*
- * Get the usename of the rule's event relation owner
+ * Get the userid of the rule's event relation owner
*/
rte = rt_fetch(rt_index, parsetree->rtable);
ev_rel = heap_openr(rte->relname, AccessShareLock);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.79 2000/09/06 14:15:20 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.80 2000/09/12 21:07:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Node *rule_qual,
int rt_index,
CmdType event,
- bool *instead_flag);
-static bool rangeTableEntry_used(Node *node, int rt_index, int sublevels_up);
-static bool attribute_used(Node *node, int rt_index, int attno,
- int sublevels_up);
-static bool modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
- int sublevels_up, int new_sublevels_up);
+ bool instead_flag);
+static List *adjustJoinTree(Query *parsetree, int rt_index, bool *found);
+static bool modifyAggrefChangeVarnodes(Query *query,
+ int rt_index, int new_index,
+ int sublevels_up, int new_sublevels_up);
static Node *modifyAggrefDropQual(Node *node, Node *targetNode);
static SubLink *modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree);
static Node *modifyAggrefQual(Node *node, Query *parsetree);
Node *rule_qual,
int rt_index,
CmdType event,
- bool *instead_flag)
+ bool instead_flag)
{
RewriteInfo *info;
int rt_length;
- int result_reln;
info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
info->rt_index = rt_index;
info->event = event;
- info->instead_flag = *instead_flag;
+ info->instead_flag = instead_flag;
info->rule_action = (Query *) copyObject(rule_action);
info->rule_qual = (Node *) copyObject(rule_qual);
if (info->rule_action == NULL)
info->nothing = FALSE;
info->action = info->rule_action->commandType;
info->current_varno = rt_index;
- info->rt = parsetree->rtable;
- rt_length = length(info->rt);
- info->rt = nconc(info->rt, copyObject(info->rule_action->rtable));
+ rt_length = length(parsetree->rtable);
+ /* Adjust rule action and qual to offset its varnos */
info->new_varno = PRS2_NEW_VARNO + rt_length;
- OffsetVarNodes(info->rule_action->qual, rt_length, 0);
- OffsetVarNodes((Node *) info->rule_action->targetList, rt_length, 0);
+ OffsetVarNodes((Node *) info->rule_action, rt_length, 0);
OffsetVarNodes(info->rule_qual, rt_length, 0);
- ChangeVarNodes((Node *) info->rule_action->qual,
- PRS2_OLD_VARNO + rt_length, rt_index, 0);
- ChangeVarNodes((Node *) info->rule_action->targetList,
+ /* but its references to *OLD* should point at original rt_index */
+ ChangeVarNodes((Node *) info->rule_action,
PRS2_OLD_VARNO + rt_length, rt_index, 0);
ChangeVarNodes(info->rule_qual,
PRS2_OLD_VARNO + rt_length, rt_index, 0);
+ /*
+ * We want the main parsetree's rtable to end up as the concatenation
+ * of its original contents plus those of all the relevant rule
+ * actions. Also store same into all the rule_action rtables.
+ * Some of the entries may be unused after we finish rewriting, but
+ * if we tried to clean those out we'd have a much harder job to
+ * adjust RT indexes in the query's Vars. It's OK to have unused
+ * RT entries, since planner will ignore them.
+ *
+ * NOTE KLUGY HACK: we assume the parsetree rtable had at least one
+ * entry to begin with (OK enough, else where'd the rule come from?).
+ * Because of this, if multiple rules nconc() their rtable additions
+ * onto parsetree->rtable, they'll all see the same rtable because
+ * they all have the same list head pointer.
+ */
+ parsetree->rtable = nconc(parsetree->rtable,
+ info->rule_action->rtable);
+ info->rule_action->rtable = parsetree->rtable;
+
+ /*
+ * Each rule action's jointree should be the main parsetree's jointree
+ * plus that rule's jointree, but *without* the original rtindex
+ * that we're replacing (if present, which it won't be for INSERT).
+ * Note that if the rule refers to OLD, its jointree will add back
+ * a reference to rt_index.
+ *
+ * XXX This might be wrong for subselect-in-FROM?
+ */
+ {
+ bool found;
+ List *newjointree = adjustJoinTree(parsetree, rt_index, &found);
+
+ info->rule_action->jointree = nconc(newjointree,
+ info->rule_action->jointree);
+ }
+
/*
* bug here about replace CURRENT -- sort of replace current is
* deprecated now so this code shouldn't really need to be so
*/
if (info->action != CMD_SELECT)
{ /* i.e update XXXXX */
- int new_result_reln = 0;
+ int result_reln;
+ int new_result_reln;
result_reln = info->rule_action->resultRelation;
switch (result_reln)
return info;
}
-
-/*
- * rangeTableEntry_used -
- * we need to process a RTE for RIR rules only if it is
- * referenced somewhere in var nodes of the query.
- */
-
-typedef struct
-{
- int rt_index;
- int sublevels_up;
-} rangeTableEntry_used_context;
-
-static bool
-rangeTableEntry_used_walker(Node *node,
- rangeTableEntry_used_context *context)
-{
- if (node == NULL)
- return false;
- if (IsA(node, Var))
- {
- Var *var = (Var *) node;
-
- if (var->varlevelsup == context->sublevels_up &&
- var->varno == context->rt_index)
- return true;
- return false;
- }
- if (IsA(node, SubLink))
- {
-
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (rangeTableEntry_used_walker((Node *) (sub->lefthand), context))
- return true;
- if (rangeTableEntry_used((Node *) (sub->subselect),
- context->rt_index,
- context->sublevels_up + 1))
- return true;
- return false;
- }
- if (IsA(node, Query))
- {
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
-
- if (rangeTableEntry_used_walker((Node *) (qry->targetList), context))
- return true;
- if (rangeTableEntry_used_walker((Node *) (qry->qual), context))
- return true;
- if (rangeTableEntry_used_walker((Node *) (qry->havingQual), context))
- return true;
- return false;
- }
- return expression_tree_walker(node, rangeTableEntry_used_walker,
- (void *) context);
-}
-
-static bool
-rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
-{
- rangeTableEntry_used_context context;
-
- context.rt_index = rt_index;
- context.sublevels_up = sublevels_up;
- return rangeTableEntry_used_walker(node, &context);
-}
-
-
/*
- * attribute_used -
- * Check if a specific attribute number of a RTE is used
- * somewhere in the query
+ * Copy the query's jointree list, and attempt to remove any occurrence
+ * of the given rt_index as a top-level join item (we do not look for it
+ * within JoinExprs). Returns modified jointree list --- original list
+ * is not changed. *found is set to indicate if we found the rt_index.
*/
-
-typedef struct
-{
- int rt_index;
- int attno;
- int sublevels_up;
-} attribute_used_context;
-
-static bool
-attribute_used_walker(Node *node,
- attribute_used_context *context)
+static List *
+adjustJoinTree(Query *parsetree, int rt_index, bool *found)
{
- if (node == NULL)
- return false;
- if (IsA(node, Var))
- {
- Var *var = (Var *) node;
+ List *newjointree = listCopy(parsetree->jointree);
+ List *jjt;
- if (var->varlevelsup == context->sublevels_up &&
- var->varno == context->rt_index &&
- var->varattno == context->attno)
- return true;
- return false;
- }
- if (IsA(node, SubLink))
+ *found = false;
+ foreach(jjt, newjointree)
{
+ RangeTblRef *rtr = lfirst(jjt);
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (attribute_used_walker((Node *) (sub->lefthand), context))
- return true;
- if (attribute_used((Node *) (sub->subselect),
- context->rt_index,
- context->attno,
- context->sublevels_up + 1))
- return true;
- return false;
- }
- if (IsA(node, Query))
- {
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
-
- if (attribute_used_walker((Node *) (qry->targetList), context))
- return true;
- if (attribute_used_walker((Node *) (qry->qual), context))
- return true;
- if (attribute_used_walker((Node *) (qry->havingQual), context))
- return true;
- return false;
+ if (IsA(rtr, RangeTblRef) && rtr->rtindex == rt_index)
+ {
+ newjointree = lremove(rtr, newjointree);
+ *found = true;
+ break;
+ }
}
- return expression_tree_walker(node, attribute_used_walker,
- (void *) context);
-}
-
-static bool
-attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
-{
- attribute_used_context context;
-
- context.rt_index = rt_index;
- context.attno = attno;
- context.sublevels_up = sublevels_up;
- return attribute_used_walker(node, &context);
+ return newjointree;
}
}
return false;
}
- if (IsA(node, SubLink))
- {
-
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (modifyAggrefChangeVarnodes_walker((Node *) (sub->lefthand),
- context))
- return true;
- if (modifyAggrefChangeVarnodes((Node *) (sub->subselect),
- context->rt_index,
- context->new_index,
- context->sublevels_up + 1,
- context->new_sublevels_up + 1))
- return true;
- return false;
- }
if (IsA(node, Query))
{
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
-
- if (modifyAggrefChangeVarnodes_walker((Node *) (qry->targetList),
- context))
- return true;
- if (modifyAggrefChangeVarnodes_walker((Node *) (qry->qual),
- context))
- return true;
- if (modifyAggrefChangeVarnodes_walker((Node *) (qry->havingQual),
- context))
- return true;
- return false;
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ context->new_sublevels_up++;
+ result = query_tree_walker((Query *) node,
+ modifyAggrefChangeVarnodes_walker,
+ (void *) context);
+ context->sublevels_up--;
+ context->new_sublevels_up--;
+ return result;
}
return expression_tree_walker(node, modifyAggrefChangeVarnodes_walker,
(void *) context);
}
static bool
-modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
+modifyAggrefChangeVarnodes(Query *query, int rt_index, int new_index,
int sublevels_up, int new_sublevels_up)
{
modifyAggrefChangeVarnodes_context context;
context.new_index = new_index;
context.sublevels_up = sublevels_up;
context.new_sublevels_up = new_sublevels_up;
- return modifyAggrefChangeVarnodes_walker(node, &context);
+ return query_tree_walker(query, modifyAggrefChangeVarnodes_walker,
+ (void *) &context);
}
SubLink *sublink;
TargetEntry *tle;
Resdom *resdom;
+ RangeTblRef *rtr;
aggVarNos = pull_varnos(aggref->target);
if (length(aggVarNos) != 1)
subquery->distinctClause = NIL;
subquery->sortClause = NIL;
subquery->rtable = lcons(copyObject(rte), NIL);
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = 1;
+ subquery->jointree = lcons(rtr, NIL);
subquery->targetList = lcons(tle, NIL);
subquery->qual = modifyAggrefDropQual((Node *) parsetree->qual,
(Node *) aggref);
* Note that because of previous line, these references have
* varlevelsup = 1, which must be changed to 0.
*/
- modifyAggrefChangeVarnodes((Node *) subquery,
+ modifyAggrefChangeVarnodes(subquery,
lfirsti(aggVarNos), 1,
1, 0);
apply_RIR_view_mutator, context);
MUTATE(newnode->havingQual, query->havingQual, Node *,
apply_RIR_view_mutator, context);
+ MUTATE(newnode->jointree, query->jointree, List *,
+ apply_RIR_view_mutator, context);
return (Node *) newnode;
}
return expression_tree_mutator(node, apply_RIR_view_mutator,
int rt_index,
int relation_level,
Relation relation,
- bool relWasInJoinSet)
+ bool relIsUsed)
{
Query *rule_action = NULL;
Node *rule_qual;
addedrtable = copyObject(rule_action->rtable);
/*
- * If the original rel wasn't in the join set, none of its spawn is.
- * If it was, then leave the spawn's flags as they are.
+ * If the original rel wasn't in the join set (which'd be the case
+ * for the target of an INSERT, for example), none of its spawn is.
+ * If it was, then the spawn has to be added to the join set.
*/
- if (!relWasInJoinSet)
+ if (relIsUsed)
{
- foreach(l, addedrtable)
- {
- RangeTblEntry *rte = lfirst(l);
-
- rte->inJoinSet = false;
- }
+ /*
+ * QUICK HACK: this all needs to be replaced, but for now, find
+ * the original rel in the jointree, remove it, and add the rule
+ * action's jointree. This will not work for views referenced
+ * in JoinExprs!!
+ *
+ * Note: it is possible that the old rel is referenced in the query
+ * but isn't present in the jointree; this should only happen for
+ * *OLD* and *NEW*. We must not fail if so, but add the rule's
+ * jointree anyway. (This is a major crock ... should fix rule
+ * representation ...)
+ */
+ bool found;
+ List *newjointree = adjustJoinTree(parsetree, rt_index, &found);
+ List *addedjointree = (List *) copyObject(rule_action->jointree);
+
+ if (!found)
+ elog(DEBUG, "ApplyRetrieveRule: can't find old rel %s (%d) in jointree",
+ rt_fetch(rt_index, rtable)->eref->relname, rt_index);
+ OffsetVarNodes((Node *) addedjointree, rt_length, 0);
+ newjointree = nconc(newjointree, addedjointree);
+ parsetree->jointree = newjointree;
}
rtable = nconc(rtable, addedrtable);
* NOTE: although this has the form of a walker, we cheat and modify the
* SubLink nodes in-place. It is caller's responsibility to ensure that
* no unwanted side-effects occur!
+ *
+ * This is unlike most of the other routines that recurse into subselects,
+ * because we must take control at the SubLink node in order to replace
+ * the SubLink's subselect link with the possibly-rewritten subquery.
*/
static bool
fireRIRonSubselect(Node *node, void *context)
if (IsA(node, SubLink))
{
SubLink *sub = (SubLink *) node;
- Query *qry;
- /* Process lefthand args */
- if (fireRIRonSubselect((Node *) (sub->lefthand), context))
- return true;
/* Do what we came for */
- qry = fireRIRrules((Query *) (sub->subselect));
- sub->subselect = (Node *) qry;
- /* Need not recurse into subselect, because fireRIRrules did it */
- return false;
- }
- if (IsA(node, Query))
- {
- /* Reach here when called from fireRIRrules */
- Query *qry = (Query *) node;
-
- if (fireRIRonSubselect((Node *) (qry->targetList), context))
- return true;
- if (fireRIRonSubselect((Node *) (qry->qual), context))
- return true;
- if (fireRIRonSubselect((Node *) (qry->havingQual), context))
- return true;
- return false;
+ sub->subselect = (Node *) fireRIRrules((Query *) (sub->subselect));
+ /* Fall through to process lefthand args of SubLink */
}
+ /*
+ * Do NOT recurse into Query nodes, because fireRIRrules already
+ * processed subselects for us.
+ */
return expression_tree_walker(node, fireRIRonSubselect,
(void *) context);
}
RuleLock *rules;
RewriteRule *rule;
RewriteRule RIRonly;
- bool relWasInJoinSet;
+ bool relIsUsed;
int i;
List *l;
* If the table is not referenced in the query, then we ignore it.
* This prevents infinite expansion loop due to new rtable entries
* inserted by expansion of a rule. A table is referenced if it is
- * part of the join set (a source table), or is the result table,
- * or is referenced by any Var nodes.
+ * part of the join set (a source table), or is referenced by any
+ * Var nodes, or is the result table.
*/
- if (!rte->inJoinSet && rt_index != parsetree->resultRelation &&
- !rangeTableEntry_used((Node *) parsetree, rt_index, 0))
+ relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0);
+
+ if (!relIsUsed && rt_index != parsetree->resultRelation)
continue;
rel = heap_openr(rte->relname, AccessShareLock);
continue;
}
- relWasInJoinSet = rte->inJoinSet; /* save before possibly
- * clearing */
-
/*
* Collect the RIR rules that we must apply
*/
if (rule->attrno > 0)
{
/* per-attr rule; do we need it? */
- if (!attribute_used((Node *) parsetree,
- rt_index,
+ if (!attribute_used((Node *) parsetree, rt_index,
rule->attrno, 0))
continue;
}
- else
- {
-
- /*
- * Rel-wide ON SELECT DO INSTEAD means this is a view.
- * Remove the view from the planner's join target set, or
- * we'll get no rows out because view itself is empty!
- */
- if (rule->isInstead)
- rte->inJoinSet = false;
- }
locks = lappend(locks, rule);
}
rt_index,
RIRonly.attrno == -1,
rel,
- relWasInJoinSet);
+ relIsUsed);
}
heap_close(rel, AccessShareLock);
parsetree->qual = modifyAggrefQual(parsetree->qual, parsetree);
if (parsetree->hasSubLinks)
- fireRIRonSubselect((Node *) parsetree, NULL);
+ query_tree_walker(parsetree, fireRIRonSubselect, NULL);
return parsetree;
}
{
List *rtable;
int rt_length;
+ List *jointree;
rtable = new_tree->rtable;
rt_length = length(rtable);
rtable = nconc(rtable, copyObject(rule_action->rtable));
+ /* XXX above possibly wrong for subselect-in-FROM */
new_tree->rtable = rtable;
OffsetVarNodes(new_qual, rt_length, 0);
ChangeVarNodes(new_qual, PRS2_OLD_VARNO + rt_length, rt_index, 0);
+ jointree = copyObject(rule_action->jointree);
+ OffsetVarNodes((Node *) jointree, rt_length, 0);
+ ChangeVarNodes((Node *) jointree, PRS2_OLD_VARNO + rt_length,
+ rt_index, 0);
+ new_tree->jointree = nconc(new_tree->jointree, jointree);
}
/* XXX -- where current doesn't work for instead nothing.... yet */
AddNotQual(new_tree, new_qual);
foreach(i, locks)
{
RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
- Node *qual,
- *event_qual;
+ Node *event_qual;
List *actions;
List *r;
*--------------------------------------------------
*/
info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
- rt_index, event, instead_flag);
+ rt_index, event, *instead_flag);
/* handle escapable cases, or those handled by other code */
if (info->nothing)
* splitting into two queries one w/rule_qual, one w/NOT
* rule_qual. Also add user query qual onto rule action
*/
- qual = parsetree->qual;
- AddQual(info->rule_action, qual);
+ AddQual(info->rule_action, parsetree->qual);
- if (info->rule_qual != NULL)
- AddQual(info->rule_action, info->rule_qual);
+ AddQual(info->rule_action, info->rule_qual);
/*--------------------------------------------------
* Step 2:
/*--------------------------------------------------
* Step 3:
- * rewriting due to retrieve rules
- *--------------------------------------------------
- */
- info->rule_action->rtable = info->rt;
-
- /*
- * ProcessRetrieveQuery(info->rule_action, info->rt,
- * &orig_instead_flag, TRUE);
- */
-
- /*--------------------------------------------------
- * Step 4
* Simplify? hey, no algorithm for simplification... let
* the planner do it.
*--------------------------------------------------
rewritten = nconc(rewritten, qual_products);
/* ----------
- * The original query is appended last if not instead
+ * The original query is appended last (if no "instead" rule)
* because update and delete rule actions might not do
* anything if they are invoked after the update or
* delete is performed. The command counter increment
*/
if (query->hasAggs)
{
- query->hasAggs =
- checkExprHasAggs((Node *) (query->targetList)) ||
- checkExprHasAggs((Node *) (query->havingQual));
- if (checkExprHasAggs((Node *) (query->qual)))
- elog(ERROR, "BasicQueryRewrite: failed to remove aggs from qual");
+ query->hasAggs = checkExprHasAggs((Node *) query);
+ if (query->hasAggs)
+ if (checkExprHasAggs(query->qual))
+ elog(ERROR, "BasicQueryRewrite: failed to remove aggs from qual");
}
if (query->hasSubLinks)
- query->hasSubLinks =
- checkExprHasSubLink((Node *) (query->targetList)) ||
- checkExprHasSubLink((Node *) (query->qual)) ||
- checkExprHasSubLink((Node *) (query->havingQual));
+ {
+ query->hasSubLinks = checkExprHasSubLink((Node *) query);
+ }
results = lappend(results, query);
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.47 2000/05/30 00:49:51 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.48 2000/09/12 21:07:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
bool
checkExprHasAggs(Node *node)
{
- return checkExprHasAggs_walker(node, NULL);
+ /*
+ * If a Query is passed, examine it --- but we will not recurse
+ * into sub-Queries.
+ */
+ if (node && IsA(node, Query))
+ return query_tree_walker((Query *) node, checkExprHasAggs_walker,
+ NULL);
+ else
+ return checkExprHasAggs_walker(node, NULL);
}
static bool
bool
checkExprHasSubLink(Node *node)
{
- return checkExprHasSubLink_walker(node, NULL);
+ /*
+ * If a Query is passed, examine it --- but we will not recurse
+ * into sub-Queries.
+ */
+ if (node && IsA(node, Query))
+ return query_tree_walker((Query *) node, checkExprHasSubLink_walker,
+ NULL);
+ else
+ return checkExprHasSubLink_walker(node, NULL);
}
static bool
*
* Find all Var nodes in the given tree with varlevelsup == sublevels_up,
* and increment their varno fields (rangetable indexes) by 'offset'.
- * The varnoold fields are adjusted similarly.
+ * The varnoold fields are adjusted similarly. Also, RangeTblRef nodes
+ * in join trees are adjusted.
*
* NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place. The given expression tree should have been copied
+ * nodes in-place. The given expression tree should have been copied
* earlier to ensure that no unwanted side-effects occur!
*/
}
return false;
}
- if (IsA(node, SubLink))
+ if (IsA(node, RangeTblRef))
{
+ RangeTblRef *rtr = (RangeTblRef *) node;
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (OffsetVarNodes_walker((Node *) (sub->lefthand),
- context))
- return true;
- OffsetVarNodes((Node *) (sub->subselect),
- context->offset,
- context->sublevels_up + 1);
+ if (context->sublevels_up == 0)
+ rtr->rtindex += context->offset;
return false;
}
if (IsA(node, Query))
{
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
+ /* Recurse into subselects */
+ bool result;
- if (OffsetVarNodes_walker((Node *) (qry->targetList),
- context))
- return true;
- if (OffsetVarNodes_walker((Node *) (qry->qual),
- context))
- return true;
- if (OffsetVarNodes_walker((Node *) (qry->havingQual),
- context))
- return true;
- return false;
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node, OffsetVarNodes_walker,
+ (void *) context);
+ context->sublevels_up--;
+ return result;
}
return expression_tree_walker(node, OffsetVarNodes_walker,
(void *) context);
context.offset = offset;
context.sublevels_up = sublevels_up;
- OffsetVarNodes_walker(node, &context);
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree;
+ * if it's a Query, go straight to query_tree_walker to make sure that
+ * sublevels_up doesn't get incremented prematurely.
+ */
+ if (node && IsA(node, Query))
+ query_tree_walker((Query *) node, OffsetVarNodes_walker,
+ (void *) &context);
+ else
+ OffsetVarNodes_walker(node, &context);
}
/*
*
* Find all Var nodes in the given tree belonging to a specific relation
* (identified by sublevels_up and rt_index), and change their varno fields
- * to 'new_index'. The varnoold fields are changed too.
+ * to 'new_index'. The varnoold fields are changed too. Also, RangeTblRef
+ * nodes in join trees are adjusted.
*
* NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place. The given expression tree should have been copied
+ * nodes in-place. The given expression tree should have been copied
* earlier to ensure that no unwanted side-effects occur!
*/
}
return false;
}
- if (IsA(node, SubLink))
+ if (IsA(node, RangeTblRef))
{
+ RangeTblRef *rtr = (RangeTblRef *) node;
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
-
- if (ChangeVarNodes_walker((Node *) (sub->lefthand),
- context))
- return true;
- ChangeVarNodes((Node *) (sub->subselect),
- context->rt_index,
- context->new_index,
- context->sublevels_up + 1);
+ if (context->sublevels_up == 0 &&
+ rtr->rtindex == context->rt_index)
+ rtr->rtindex = context->new_index;
return false;
}
if (IsA(node, Query))
{
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
+ /* Recurse into subselects */
+ bool result;
- if (ChangeVarNodes_walker((Node *) (qry->targetList),
- context))
- return true;
- if (ChangeVarNodes_walker((Node *) (qry->qual),
- context))
- return true;
- if (ChangeVarNodes_walker((Node *) (qry->havingQual),
- context))
- return true;
- return false;
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node, ChangeVarNodes_walker,
+ (void *) context);
+ context->sublevels_up--;
+ return result;
}
return expression_tree_walker(node, ChangeVarNodes_walker,
(void *) context);
context.rt_index = rt_index;
context.new_index = new_index;
context.sublevels_up = sublevels_up;
- ChangeVarNodes_walker(node, &context);
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree;
+ * if it's a Query, go straight to query_tree_walker to make sure that
+ * sublevels_up doesn't get incremented prematurely.
+ */
+ if (node && IsA(node, Query))
+ query_tree_walker((Query *) node, ChangeVarNodes_walker,
+ (void *) &context);
+ else
+ ChangeVarNodes_walker(node, &context);
}
/*
var->varlevelsup += context->delta_sublevels_up;
return false;
}
- if (IsA(node, SubLink))
+ if (IsA(node, Query))
{
+ /* Recurse into subselects */
+ bool result;
- /*
- * Standard expression_tree_walker will not recurse into
- * subselect, but here we must do so.
- */
- SubLink *sub = (SubLink *) node;
+ context->min_sublevels_up++;
+ result = query_tree_walker((Query *) node,
+ IncrementVarSublevelsUp_walker,
+ (void *) context);
+ context->min_sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node, IncrementVarSublevelsUp_walker,
+ (void *) context);
+}
+
+void
+IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
+ int min_sublevels_up)
+{
+ IncrementVarSublevelsUp_context context;
+
+ context.delta_sublevels_up = delta_sublevels_up;
+ context.min_sublevels_up = min_sublevels_up;
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree;
+ * if it's a Query, go straight to query_tree_walker to make sure that
+ * sublevels_up doesn't get incremented prematurely.
+ */
+ if (node && IsA(node, Query))
+ query_tree_walker((Query *) node, IncrementVarSublevelsUp_walker,
+ (void *) &context);
+ else
+ IncrementVarSublevelsUp_walker(node, &context);
+}
+
+
+/*
+ * rangeTableEntry_used - detect whether an RTE is referenced somewhere
+ * in var nodes or jointree nodes of a query or expression.
+ */
+
+typedef struct
+{
+ int rt_index;
+ int sublevels_up;
+} rangeTableEntry_used_context;
+
+static bool
+rangeTableEntry_used_walker(Node *node,
+ rangeTableEntry_used_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
- if (IncrementVarSublevelsUp_walker((Node *) (sub->lefthand),
- context))
+ if (var->varlevelsup == context->sublevels_up &&
+ var->varno == context->rt_index)
return true;
- IncrementVarSublevelsUp((Node *) (sub->subselect),
- context->delta_sublevels_up,
- context->min_sublevels_up + 1);
return false;
}
- if (IsA(node, Query))
+ if (IsA(node, RangeTblRef))
{
- /* Reach here after recursing down into subselect above... */
- Query *qry = (Query *) node;
+ RangeTblRef *rtr = (RangeTblRef *) node;
- if (IncrementVarSublevelsUp_walker((Node *) (qry->targetList),
- context))
+ if (rtr->rtindex == context->rt_index &&
+ context->sublevels_up == 0)
return true;
- if (IncrementVarSublevelsUp_walker((Node *) (qry->qual),
- context))
- return true;
- if (IncrementVarSublevelsUp_walker((Node *) (qry->havingQual),
- context))
+ return false;
+ }
+ if (IsA(node, Query))
+ {
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node, rangeTableEntry_used_walker,
+ (void *) context);
+ context->sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node, rangeTableEntry_used_walker,
+ (void *) context);
+}
+
+bool
+rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
+{
+ rangeTableEntry_used_context context;
+
+ context.rt_index = rt_index;
+ context.sublevels_up = sublevels_up;
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree;
+ * if it's a Query, go straight to query_tree_walker to make sure that
+ * sublevels_up doesn't get incremented prematurely.
+ */
+ if (node && IsA(node, Query))
+ return query_tree_walker((Query *) node, rangeTableEntry_used_walker,
+ (void *) &context);
+ else
+ return rangeTableEntry_used_walker(node, &context);
+}
+
+
+/*
+ * attribute_used -
+ * Check if a specific attribute number of a RTE is used
+ * somewhere in the query or expression.
+ */
+
+typedef struct
+{
+ int rt_index;
+ int attno;
+ int sublevels_up;
+} attribute_used_context;
+
+static bool
+attribute_used_walker(Node *node,
+ attribute_used_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (var->varlevelsup == context->sublevels_up &&
+ var->varno == context->rt_index &&
+ var->varattno == context->attno)
return true;
return false;
}
- return expression_tree_walker(node, IncrementVarSublevelsUp_walker,
+ if (IsA(node, Query))
+ {
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node, attribute_used_walker,
+ (void *) context);
+ context->sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node, attribute_used_walker,
(void *) context);
}
-void
-IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
- int min_sublevels_up)
+bool
+attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
{
- IncrementVarSublevelsUp_context context;
+ attribute_used_context context;
- context.delta_sublevels_up = delta_sublevels_up;
- context.min_sublevels_up = min_sublevels_up;
- IncrementVarSublevelsUp_walker(node, &context);
+ context.rt_index = rt_index;
+ context.attno = attno;
+ context.sublevels_up = sublevels_up;
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree;
+ * if it's a Query, go straight to query_tree_walker to make sure that
+ * sublevels_up doesn't get incremented prematurely.
+ */
+ if (node && IsA(node, Query))
+ return query_tree_walker((Query *) node, attribute_used_walker,
+ (void *) &context);
+ else
+ return attribute_used_walker(node, &context);
}
+
/*
* Add the given qualifier condition to the query's WHERE clause
*/
Query *query = (Query *) node;
Query *newnode;
- /*
- * XXX original code for ResolveNew only recursed into qual field
- * of subquery. I'm assuming that was an oversight ... tgl 9/99
- */
-
FLATCOPY(newnode, query, Query);
MUTATE(newnode->targetList, query->targetList, List *,
ResolveNew_mutator, context);
ResolveNew_mutator, context);
MUTATE(newnode->havingQual, query->havingQual, Node *,
ResolveNew_mutator, context);
+ MUTATE(newnode->jointree, query->jointree, List *,
+ ResolveNew_mutator, context);
return (Node *) newnode;
}
return expression_tree_mutator(node, ResolveNew_mutator,
FixNew(RewriteInfo *info, Query *parsetree)
{
info->rule_action->targetList = (List *)
- ResolveNew((Node *) info->rule_action->targetList,
- info, parsetree->targetList, 0);
+ ResolveNew((Node *) info->rule_action->targetList,
+ info, parsetree->targetList, 0);
info->rule_action->qual = ResolveNew(info->rule_action->qual,
info, parsetree->targetList, 0);
- /* XXX original code didn't fix havingQual; presumably an oversight? */
info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
- info, parsetree->targetList, 0);
+ info, parsetree->targetList, 0);
+ info->rule_action->jointree = (List *)
+ ResolveNew((Node *) info->rule_action->jointree,
+ info, parsetree->targetList, 0);
}
/*
Query *query = (Query *) node;
Query *newnode;
- /*
- * XXX original code for HandleRIRAttributeRule only recursed into
- * qual field of subquery. I'm assuming that was an oversight ...
- */
-
FLATCOPY(newnode, query, Query);
MUTATE(newnode->targetList, query->targetList, List *,
HandleRIRAttributeRule_mutator, context);
HandleRIRAttributeRule_mutator, context);
MUTATE(newnode->havingQual, query->havingQual, Node *,
HandleRIRAttributeRule_mutator, context);
+ MUTATE(newnode->jointree, query->jointree, List *,
+ HandleRIRAttributeRule_mutator, context);
return (Node *) newnode;
}
return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
parsetree->targetList = (List *)
HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
&context);
- parsetree->qual = HandleRIRAttributeRule_mutator(parsetree->qual,
- &context);
- /* XXX original code did not fix havingQual ... oversight? */
- parsetree->havingQual = HandleRIRAttributeRule_mutator(parsetree->havingQual,
- &context);
+ parsetree->qual =
+ HandleRIRAttributeRule_mutator(parsetree->qual,
+ &context);
+ parsetree->havingQual =
+ HandleRIRAttributeRule_mutator(parsetree->havingQual,
+ &context);
+ parsetree->jointree = (List *)
+ HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
+ &context);
}
/**********************************************************************
- * get_ruledef.c - Function to get a rules definition text
- * out of its tuple
+ * ruleutils.c - Functions to convert stored expressions/querytrees
+ * back to source text
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.60 2000/09/12 04:15:58 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.61 2000/09/12 21:07:05 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
#include "catalog/pg_index.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_shadow.h"
+#include "commands/view.h"
#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "optimizer/clauses.h"
#include "parser/keywords.h"
#include "parser/parse_expr.h"
#include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
-#include "commands/view.h"
/* ----------
bool varprefix; /* TRUE to print prefixes on Vars */
} deparse_context;
-typedef struct
-{
- Index rt_index;
- int levelsup;
-} check_if_rte_used_context;
-
/* ----------
* Global data
static void get_tle_expr(TargetEntry *tle, deparse_context *context);
static void get_const_expr(Const *constval, deparse_context *context);
static void get_sublink_expr(Node *node, deparse_context *context);
+static void get_from_clause(Query *query, deparse_context *context);
+static void get_from_clause_item(Node *jtnode, Query *query,
+ deparse_context *context);
static bool tleIsArrayAssign(TargetEntry *tle);
static char *quote_identifier(char *ident);
static char *get_relation_name(Oid relid);
static char *get_attribute_name(Oid relid, int2 attnum);
-static bool check_if_rte_used(Node *node, Index rt_index, int levelsup);
-static bool check_if_rte_used_walker(Node *node,
- check_if_rte_used_context *context);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
Name vname = PG_GETARG_NAME(0);
text *ruledef;
Datum args[1];
- char nulls[2];
+ char nulls[1];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
int len;
- char *name;
+ char *name;
/* ----------
* We need the view name somewhere deep down
name = MakeRetrieveViewRuleName(rulename);
args[0] = PointerGetDatum(name);
nulls[0] = ' ';
- nulls[1] = '\0';
spirc = SPI_execp(plan_getview, args, nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename);
{
StringInfo buf = context->buf;
char *sep;
- TargetEntry *tle;
- RangeTblEntry *rte;
- bool *rt_used;
- int rt_length;
- int rt_numused = 0;
- bool rt_constonly = TRUE;
- int i;
List *l;
- /* ----------
- * First we need to know which and how many of the
- * range table entries in the query are used in the target list
- * or queries qualification
- * ----------
- */
- rt_length = length(query->rtable);
- rt_used = palloc(sizeof(bool) * rt_length);
- for (i = 0; i < rt_length; i++)
- {
- if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
- check_if_rte_used(query->qual, i + 1, 0) ||
- check_if_rte_used(query->havingQual, i + 1, 0))
- {
- rt_used[i] = TRUE;
- rt_numused++;
- }
- else
- rt_used[i] = FALSE;
- }
-
- /* ----------
- * Now check if any of the used rangetable entries is different
- * from *NEW* and *OLD*. If so we must provide the FROM clause
- * later.
- * ----------
- */
- i = 0;
- foreach(l, query->rtable)
- {
- if (!rt_used[i++])
- continue;
-
- rte = (RangeTblEntry *) lfirst(l);
- if (rte->ref == NULL)
- continue;
- if (strcmp(rte->ref->relname, "*NEW*") == 0)
- continue;
- if (strcmp(rte->ref->relname, "*OLD*") == 0)
- continue;
-
- rt_constonly = FALSE;
- break;
- }
-
/* ----------
* Build up the query string - first we say SELECT
* ----------
sep = " ";
foreach(l, query->targetList)
{
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
bool tell_as = false;
- tle = (TargetEntry *) lfirst(l);
appendStringInfo(buf, sep);
sep = ", ";
else
{
Var *var = (Var *) (tle->expr);
+ RangeTblEntry *rte;
char *attname;
rte = get_rte_for_var(var, context);
quote_identifier(tle->resdom->resname));
}
- /* If we need other tables than *NEW* or *OLD* add the FROM clause */
- if (!rt_constonly && rt_numused > 0)
- {
- sep = " FROM ";
- i = 0;
- foreach(l, query->rtable)
- {
- if (rt_used[i++])
- {
- rte = (RangeTblEntry *) lfirst(l);
-
- if (rte->ref == NULL)
- continue;
- if (strcmp(rte->ref->relname, "*NEW*") == 0)
- continue;
- if (strcmp(rte->ref->relname, "*OLD*") == 0)
- continue;
-
- appendStringInfo(buf, sep);
- sep = ", ";
- appendStringInfo(buf, "%s%s",
- only_marker(rte),
- quote_identifier(rte->relname));
-
- /*
- * NOTE: SQL92 says you can't write column aliases unless
- * you write a table alias --- so, if there's an alias
- * list, make sure we emit a table alias even if it's the
- * same as the table's real name.
- */
- if ((rte->ref != NULL)
- && ((strcmp(rte->relname, rte->ref->relname) != 0)
- || (rte->ref->attrs != NIL)))
- {
- appendStringInfo(buf, " %s",
- quote_identifier(rte->ref->relname));
- if (rte->ref->attrs != NIL)
- {
- List *col;
-
- appendStringInfo(buf, " (");
- foreach(col, rte->ref->attrs)
- {
- if (col != rte->ref->attrs)
- appendStringInfo(buf, ", ");
- appendStringInfo(buf, "%s",
- quote_identifier(strVal(lfirst(col))));
- }
- appendStringInfoChar(buf, ')');
- }
- }
- }
- }
- }
+ /* Add the FROM clause if needed */
+ get_from_clause(query, context);
/* Add the WHERE clause if given */
if (query->qual != NULL)
{
StringInfo buf = context->buf;
char *sep;
- TargetEntry *tle;
- RangeTblEntry *rte;
- bool *rt_used;
- int rt_length;
- int rt_numused = 0;
bool rt_constonly = TRUE;
+ RangeTblEntry *rte;
int i;
List *l;
/* ----------
* We need to know if other tables than *NEW* or *OLD*
* are used in the query. If not, it's an INSERT ... VALUES,
- * otherwise an INSERT ... SELECT.
+ * otherwise an INSERT ... SELECT. (Pretty klugy ... fix this
+ * when we redesign querytrees!)
* ----------
*/
- rt_length = length(query->rtable);
- rt_used = palloc(sizeof(bool) * rt_length);
- for (i = 0; i < rt_length; i++)
- {
- if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
- check_if_rte_used(query->qual, i + 1, 0) ||
- check_if_rte_used(query->havingQual, i + 1, 0))
- {
- rt_used[i] = TRUE;
- rt_numused++;
- }
- else
- rt_used[i] = FALSE;
- }
-
i = 0;
foreach(l, query->rtable)
{
- if (!rt_used[i++])
- continue;
-
rte = (RangeTblEntry *) lfirst(l);
- if (rte->ref == NULL)
- continue;
- if (strcmp(rte->ref->relname, "*NEW*") == 0)
+ i++;
+ if (strcmp(rte->eref->relname, "*NEW*") == 0)
continue;
- if (strcmp(rte->ref->relname, "*OLD*") == 0)
+ if (strcmp(rte->eref->relname, "*OLD*") == 0)
continue;
-
- rt_constonly = FALSE;
- break;
+ if (rangeTableEntry_used((Node *) query, i, 0))
+ {
+ rt_constonly = FALSE;
+ break;
+ }
}
/* ----------
appendStringInfo(buf, "INSERT INTO %s",
quote_identifier(rte->relname));
- /* Add the target list */
+ /* Add the insert-column-names list */
sep = " (";
foreach(l, query->targetList)
{
- tle = (TargetEntry *) lfirst(l);
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
appendStringInfo(buf, sep);
sep = ", ";
sep = "";
foreach(l, query->targetList)
{
- tle = (TargetEntry *) lfirst(l);
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
appendStringInfo(buf, sep);
sep = ", ";
get_tle_expr(tle, context);
}
+ /* Add the FROM clause if needed */
+ get_from_clause(query, context);
+
/* Finally add a WHERE clause if given */
if (query->qual != NULL)
{
if (context->varprefix)
{
- if (rte->ref == NULL)
- appendStringInfo(buf, "%s.",
- quote_identifier(rte->relname));
- else if (strcmp(rte->ref->relname, "*NEW*") == 0)
+ if (strcmp(rte->eref->relname, "*NEW*") == 0)
appendStringInfo(buf, "new.");
- else if (strcmp(rte->ref->relname, "*OLD*") == 0)
+ else if (strcmp(rte->eref->relname, "*OLD*") == 0)
appendStringInfo(buf, "old.");
else
appendStringInfo(buf, "%s.",
- quote_identifier(rte->ref->relname));
+ quote_identifier(rte->eref->relname));
}
appendStringInfo(buf, "%s",
quote_identifier(get_attribute_name(rte->relid,
appendStringInfoChar(buf, ')');
}
+
+/* ----------
+ * get_from_clause - Parse back a FROM clause
+ * ----------
+ */
+static void
+get_from_clause(Query *query, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ char *sep;
+ List *l;
+
+ /*
+ * We use the query's jointree as a guide to what to print. However,
+ * we must ignore auto-added RTEs that are marked not inFromCl.
+ * Also ignore the rule pseudo-RTEs for NEW and OLD.
+ */
+ sep = " FROM ";
+
+ foreach(l, query->jointree)
+ {
+ Node *jtnode = (Node *) lfirst(l);
+
+ if (IsA(jtnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+ RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+ if (!rte->inFromCl)
+ continue;
+ if (strcmp(rte->eref->relname, "*NEW*") == 0)
+ continue;
+ if (strcmp(rte->eref->relname, "*OLD*") == 0)
+ continue;
+ }
+
+ appendStringInfo(buf, sep);
+ get_from_clause_item(jtnode, query, context);
+ sep = ", ";
+ }
+}
+
+static void
+get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+
+ if (IsA(jtnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+ RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+ appendStringInfo(buf, "%s%s",
+ only_marker(rte),
+ quote_identifier(rte->relname));
+ if (rte->alias != NULL)
+ {
+ appendStringInfo(buf, " %s",
+ quote_identifier(rte->alias->relname));
+ if (rte->alias->attrs != NIL)
+ {
+ List *col;
+
+ appendStringInfo(buf, " (");
+ foreach(col, rte->alias->attrs)
+ {
+ if (col != rte->alias->attrs)
+ appendStringInfo(buf, ", ");
+ appendStringInfo(buf, "%s",
+ quote_identifier(strVal(lfirst(col))));
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ }
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ appendStringInfoChar(buf, '(');
+ get_from_clause_item(j->larg, query, context);
+ if (j->isNatural)
+ appendStringInfo(buf, " NATURAL");
+ switch (j->jointype)
+ {
+ case JOIN_INNER:
+ if (j->quals)
+ appendStringInfo(buf, " JOIN ");
+ else
+ appendStringInfo(buf, " CROSS JOIN ");
+ break;
+ case JOIN_LEFT:
+ appendStringInfo(buf, " LEFT JOIN ");
+ break;
+ case JOIN_FULL:
+ appendStringInfo(buf, " FULL JOIN ");
+ break;
+ case JOIN_RIGHT:
+ appendStringInfo(buf, " RIGHT JOIN ");
+ break;
+ case JOIN_UNION:
+ appendStringInfo(buf, " UNION JOIN ");
+ break;
+ default:
+ elog(ERROR, "get_from_clause_item: unknown join type %d",
+ (int) j->jointype);
+ }
+ get_from_clause_item(j->rarg, query, context);
+ if (! j->isNatural)
+ {
+ if (j->using)
+ {
+ List *col;
+
+ appendStringInfo(buf, " USING (");
+ foreach(col, j->using)
+ {
+ if (col != j->using)
+ appendStringInfo(buf, ", ");
+ appendStringInfo(buf, "%s",
+ quote_identifier(strVal(lfirst(col))));
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ else if (j->quals)
+ {
+ appendStringInfo(buf, " ON (");
+ get_rule_expr(j->quals, context);
+ appendStringInfoChar(buf, ')');
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ /* Yes, it's correct to put alias after the right paren ... */
+ if (j->alias != NULL)
+ {
+ appendStringInfo(buf, " %s",
+ quote_identifier(j->alias->relname));
+ if (j->alias->attrs != NIL)
+ {
+ List *col;
+
+ appendStringInfo(buf, " (");
+ foreach(col, j->alias->attrs)
+ {
+ if (col != j->alias->attrs)
+ appendStringInfo(buf, ", ");
+ appendStringInfo(buf, "%s",
+ quote_identifier(strVal(lfirst(col))));
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ }
+ }
+ else
+ elog(ERROR, "get_from_clause_item: unexpected node type %d",
+ nodeTag(jtnode));
+}
+
+
/* ----------
* tleIsArrayAssign - check for array assignment
* ----------
attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
return pstrdup(NameStr(attStruct->attname));
}
-
-
-/* ----------
- * check_if_rte_used
- * Check a targetlist or qual to see if a given rangetable entry
- * is used in it
- * ----------
- */
-static bool
-check_if_rte_used(Node *node, Index rt_index, int levelsup)
-{
- check_if_rte_used_context context;
-
- context.rt_index = rt_index;
- context.levelsup = levelsup;
- return check_if_rte_used_walker(node, &context);
-}
-
-static bool
-check_if_rte_used_walker(Node *node,
- check_if_rte_used_context *context)
-{
- if (node == NULL)
- return false;
- if (IsA(node, Var))
- {
- Var *var = (Var *) node;
-
- return var->varno == context->rt_index &&
- var->varlevelsup == context->levelsup;
- }
- if (IsA(node, SubLink))
- {
- SubLink *sublink = (SubLink *) node;
- Query *query = (Query *) sublink->subselect;
-
- /* Recurse into subquery; expression_tree_walker will not */
- if (check_if_rte_used((Node *) (query->targetList),
- context->rt_index, context->levelsup + 1) ||
- check_if_rte_used(query->qual,
- context->rt_index, context->levelsup + 1) ||
- check_if_rte_used(query->havingQual,
- context->rt_index, context->levelsup + 1))
- return true;
-
- /*
- * fall through to let expression_tree_walker examine lefthand
- * args
- */
- }
- return expression_tree_walker(node, check_if_rte_used_walker,
- (void *) context);
-}
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: catversion.h,v 1.44 2000/09/12 04:49:15 momjian Exp $
+ * $Id: catversion.h,v 1.45 2000/09/12 21:07:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200009111
+#define CATALOG_VERSION_NO 200009121
#endif
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: execdebug.h,v 1.13 2000/06/15 00:52:07 momjian Exp $
+ * $Id: execdebug.h,v 1.14 2000/09/12 21:07:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#undef EXEC_MERGEJOINDEBUG
*/
-/* ----------------
- * EXEC_MERGEJOINPFREE is a flag which causes merge joins
- * to pfree intermittant tuples (which is the proper thing)
- * Not defining this means we avoid menory management problems
- * at the cost of doing deallocation of stuff only at the
- * end of the transaction
- * ----------------
-#undef EXEC_MERGEJOINPFREE
- */
-
/* ----------------
* EXEC_DEBUGINTERACTIVE is a flag which enables the
* user to issue "DEBUG" commands from an interactive
* only as necessary -cim 10/26/89
* ----------------------------------------------------------------
*/
-#define T_OR_F(b) (b ? "true" : "false")
+#define T_OR_F(b) ((b) ? "true" : "false")
#define NULL_OR_TUPLE(slot) (TupIsNull(slot) ? "null" : "a tuple")
-/* #define EXEC_TUPLECOUNT - XXX take out for now for executor stubbing -- jolly*/
/* ----------------
* tuple count debugging defines
* ----------------
#define MJ1_printf(s, p) printf(s, p)
#define MJ2_printf(s, p1, p2) printf(s, p1, p2)
#define MJ_debugtup(tuple, type) debugtup(tuple, type, NULL)
-#define MJ_dump(context, state) ExecMergeTupleDump(econtext, state)
+#define MJ_dump(state) ExecMergeTupleDump(state)
#define MJ_DEBUG_QUAL(clause, res) \
MJ2_printf(" ExecQual(%s, econtext) returns %s\n", \
CppAsString(clause), T_OR_F(res));
#define MJ_DEBUG_MERGE_COMPARE(qual, res) \
- MJ2_printf(" MergeCompare(mergeclauses, %s, ..) returns %s\n", \
+ MJ2_printf(" MergeCompare(mergeclauses, %s, ...) returns %s\n", \
CppAsString(qual), T_OR_F(res));
#define MJ_DEBUG_PROC_NODE(slot) \
- MJ2_printf(" %s = ExecProcNode(innerPlan) returns %s\n", \
+ MJ2_printf(" %s = ExecProcNode(...) returns %s\n", \
CppAsString(slot), NULL_OR_TUPLE(slot));
+
#else
+
#define MJ_nodeDisplay(l)
#define MJ_printf(s)
#define MJ1_printf(s, p)
#define MJ2_printf(s, p1, p2)
#define MJ_debugtup(tuple, type)
-#define MJ_dump(context, state)
+#define MJ_dump(state)
#define MJ_DEBUG_QUAL(clause, res)
#define MJ_DEBUG_MERGE_COMPARE(qual, res)
#define MJ_DEBUG_PROC_NODE(slot)
+
#endif /* EXEC_MERGEJOINDEBUG */
/* ----------------------------------------------------------------
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: execdefs.h,v 1.6 2000/01/26 05:58:05 momjian Exp $
+ * $Id: execdefs.h,v 1.7 2000/09/12 21:07:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#define EXEC_MJ_NEXTOUTER 5
#define EXEC_MJ_TESTOUTER 6
#define EXEC_MJ_NEXTINNER 7
-#define EXEC_MJ_SKIPINNER 8
-#define EXEC_MJ_SKIPOUTER 9
-#define EXEC_MJ_FILLINNER 10
-#define EXEC_MJ_FILLOUTER 11
+#define EXEC_MJ_SKIPOUTER_BEGIN 8
+#define EXEC_MJ_SKIPOUTER_TEST 9
+#define EXEC_MJ_SKIPOUTER_ADVANCE 10
+#define EXEC_MJ_SKIPINNER_BEGIN 11
+#define EXEC_MJ_SKIPINNER_TEST 12
+#define EXEC_MJ_SKIPINNER_ADVANCE 13
+#define EXEC_MJ_ENDOUTER 14
+#define EXEC_MJ_ENDINNER 15
#endif /* EXECDEFS_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: executor.h,v 1.50 2000/08/24 23:34:09 tgl Exp $
+ * $Id: executor.h,v 1.51 2000/09/12 21:07:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern void ExecInitResultTupleSlot(EState *estate, CommonState *commonstate);
extern void ExecInitScanTupleSlot(EState *estate,
CommonScanState *commonscanstate);
-extern void ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate);
+extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate);
+extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
+ TupleDesc tupType);
extern TupleDesc ExecGetTupType(Plan *node);
extern TupleDesc ExecTypeFromTL(List *targetList);
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: execnodes.h,v 1.48 2000/08/24 03:29:13 tgl Exp $
+ * $Id: execnodes.h,v 1.49 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* ----------------
* NestLoopState information
+ *
+ * NeedNewOuter true if need new outer tuple on next call
+ * MatchedOuter true if found a join match for current outer tuple
+ * NullInnerTupleSlot prepared null tuple for left outer joins
* ----------------
*/
typedef struct NestLoopState
{
JoinState jstate; /* its first field is NodeTag */
+ bool nl_NeedNewOuter;
+ bool nl_MatchedOuter;
+ TupleTableSlot *nl_NullInnerTupleSlot;
} NestLoopState;
/* ----------------
* OuterSkipQual outerKey1 < innerKey1 ...
* InnerSkipQual outerKey1 > innerKey1 ...
* JoinState current "state" of join. see executor.h
+ * MatchedOuter true if found a join match for current outer tuple
+ * MatchedInner true if found a join match for current inner tuple
+ * OuterTupleSlot pointer to slot in tuple table for cur outer tuple
+ * InnerTupleSlot pointer to slot in tuple table for cur inner tuple
* MarkedTupleSlot pointer to slot in tuple table for marked tuple
+ * NullOuterTupleSlot prepared null tuple for right outer joins
+ * NullInnerTupleSlot prepared null tuple for left outer joins
* ----------------
*/
typedef struct MergeJoinState
List *mj_OuterSkipQual;
List *mj_InnerSkipQual;
int mj_JoinState;
+ bool mj_MatchedOuter;
+ bool mj_MatchedInner;
+ TupleTableSlot *mj_OuterTupleSlot;
+ TupleTableSlot *mj_InnerTupleSlot;
TupleTableSlot *mj_MarkedTupleSlot;
+ TupleTableSlot *mj_NullOuterTupleSlot;
+ TupleTableSlot *mj_NullInnerTupleSlot;
} MergeJoinState;
/* ----------------
* hj_InnerHashKey the inner hash key in the hashjoin condition
* hj_OuterTupleSlot tuple slot for outer tuples
* hj_HashTupleSlot tuple slot for hashed tuples
+ * hj_NullInnerTupleSlot prepared null tuple for left outer joins
+ * hj_NeedNewOuter true if need new outer tuple on next call
+ * hj_MatchedOuter true if found a join match for current outer
+ * hj_hashdone true if hash-table-build phase is done
* ----------------
*/
typedef struct HashJoinState
Node *hj_InnerHashKey;
TupleTableSlot *hj_OuterTupleSlot;
TupleTableSlot *hj_HashTupleSlot;
+ TupleTableSlot *hj_NullInnerTupleSlot;
+ bool hj_NeedNewOuter;
+ bool hj_MatchedOuter;
+ bool hj_hashdone;
} HashJoinState;
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: nodes.h,v 1.75 2000/08/24 03:29:13 tgl Exp $
+ * $Id: nodes.h,v 1.76 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
T_ArrayRef,
T_Iter,
T_RelabelType,
+ T_RangeTblRef,
+ T_JoinExpr,
/*---------------------
* TAGS FOR PLANNER NODES (relation.h)
T_A_Indices,
T_ResTarget,
T_TypeCast,
- T_RelExpr,
+ T_RangeSubselect,
T_SortGroupBy,
T_RangeVar,
T_TypeName,
T_SortClause,
T_GroupClause,
T_SubSelectXXX, /* not used anymore; this tag# is available */
- T_JoinExpr,
+ T_oldJoinExprXXX, /* not used anymore; this tag# is available */
T_CaseExpr,
T_CaseWhen,
T_RowMark,
T_FkConstraint,
/*---------------------
- * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (cf. fmgr.h)
+ * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (see fmgr.h)
*---------------------
*/
T_TriggerData = 800, /* in commands/trigger.h */
/*
* CmdType -
- * enums for type of operation to aid debugging
+ * enums for type of operation represented by a Query
*
* ??? could have put this in parsenodes.h but many files not in the
* optimizer also need this...
} CmdType;
+/*
+ * JoinType -
+ * enums for types of relation joins
+ *
+ * JoinType determines the exact semantics of joining two relations using
+ * a matching qualification. For example, it tells what to do with a tuple
+ * that has no match in the other relation.
+ *
+ * This is needed in both parsenodes.h and plannodes.h, so put it here...
+ */
+typedef enum JoinType
+{
+ /*
+ * The canonical kinds of joins
+ */
+ JOIN_INNER, /* matching tuple pairs only */
+ JOIN_LEFT, /* pairs + unmatched outer tuples */
+ JOIN_FULL, /* pairs + unmatched outer + unmatched inner */
+ JOIN_RIGHT, /* pairs + unmatched inner tuples */
+ /*
+ * SQL92 considers UNION JOIN to be a kind of join, so list it here for
+ * parser convenience, even though it's not implemented like a join in
+ * the executor. (The planner must convert it to an Append plan.)
+ */
+ JOIN_UNION
+ /*
+ * Eventually we will have some additional join types for efficient
+ * support of queries like WHERE foo IN (SELECT bar FROM ...).
+ */
+} JoinType;
+
+#define IS_OUTER_JOIN(jointype) \
+ ((jointype) == JOIN_LEFT || \
+ (jointype) == JOIN_FULL || \
+ (jointype) == JOIN_RIGHT)
+
#endif /* NODES_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parsenodes.h,v 1.112 2000/09/12 05:09:50 momjian Exp $
+ * $Id: parsenodes.h,v 1.113 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Node *utilityStmt; /* non-null if this is a non-optimizable
* statement */
- int resultRelation; /* target relation (index to rtable) */
+ int resultRelation; /* target relation (index into rtable) */
char *into; /* portal (cursor) name */
bool isPortal; /* is this a retrieve into portal? */
bool isBinary; /* binary portal? */
bool hasSubLinks; /* has subquery SubLink */
List *rtable; /* list of range table entries */
+ List *jointree; /* table join tree (from the FROM clause) */
+
List *targetList; /* target list (of TargetEntry) */
Node *qual; /* qualifications applied to tuples */
List *rowMark; /* list of RowMark entries */
* assign */
} ResTarget;
-/*
- * RelExpr - relation expressions
- */
-typedef struct RelExpr
-{
- NodeTag type;
- char *relname; /* the relation name */
- bool inh; /* inheritance query */
-} RelExpr;
-
/*
* SortGroupBy - for ORDER BY clause
*/
typedef struct RangeVar
{
NodeTag type;
- RelExpr *relExpr; /* the relation expression */
- Attr *name; /* the name to be referenced (optional) */
+ char *relname; /* the relation name */
+ bool inh; /* expand rel by inheritance? */
+ Attr *name; /* optional table alias & column aliases */
} RangeVar;
+/*
+ * RangeSubselect - subquery appearing in a FROM clause
+ */
+typedef struct RangeSubselect
+{
+ NodeTag type;
+ Node *subquery; /* the untransformed sub-select clause */
+ Attr *name; /* optional table alias & column aliases */
+} RangeSubselect;
+
/*
* IndexElem - index parameters (used in CREATE INDEX)
*
Node *arg; /* a (Value *) or a (TypeName *) */
} DefElem;
-/*
- * JoinExpr - for JOIN expressions
- */
-typedef struct JoinExpr
-{
- NodeTag type;
- int jointype;
- bool isNatural; /* Natural join? Will need to shape table */
- Node *larg; /* RangeVar or join expression */
- Node *rarg; /* RangeVar or join expression */
- Attr *alias; /* table and column aliases, if any */
- List *quals; /* qualifiers on join, if any */
-} JoinExpr;
-
/****************************************************************************
* Nodes for a Query tree
* Some of the following are only used in one of
* the parsing, optimizing, execution stages.
*
- * eref is the expanded table name and columns for the underlying
- * relation. Note that for outer join syntax, allowed reference names
- * could be modified as one evaluates the nested clauses (e.g.
- * "SELECT ... FROM t1 NATURAL JOIN t2 WHERE ..." forbids explicit mention
- * of a table name in any reference to the join column.
+ * alias is an Attr node representing the AS alias-clause attached to the
+ * FROM expression, or NULL if no clause.
+ *
+ * eref is the table reference name and column reference names (either
+ * real or aliases). This is filled in during parse analysis. Note that
+ * system columns (OID etc) are not included in the column list.
*
* inFromCl marks those range variables that are listed in the FROM clause.
* In SQL, the query can only refer to range variables listed in the
* implicitly-added RTE shouldn't change the namespace for unqualified
* column names processed later, and it also shouldn't affect the
* expansion of '*'.
- *
- * inJoinSet marks those range variables that the planner should join
- * over even if they aren't explicitly referred to in the query. For
- * example, "SELECT COUNT(1) FROM tx" should produce the number of rows
- * in tx. A more subtle example uses a POSTQUEL implicit RTE:
- * SELECT COUNT(1) FROM tx WHERE TRUE OR (tx.f1 = ty.f2)
- * Here we should get the product of the sizes of tx and ty. However,
- * the query optimizer can simplify the WHERE clause to "TRUE", so
- * ty will no longer be referred to explicitly; without a flag forcing
- * it to be included in the join, we will get the wrong answer. So,
- * a POSTQUEL implicit RTE must be marked inJoinSet but not inFromCl.
*--------------------
*/
typedef struct RangeTblEntry
{
NodeTag type;
char *relname; /* real name of the relation */
- Attr *ref; /* reference names (given in FROM clause) */
- Attr *eref; /* expanded reference names */
Oid relid; /* OID of the relation */
+ Attr *alias; /* user-written alias clause, if any */
+ Attr *eref; /* expanded reference names */
bool inh; /* inheritance requested? */
bool inFromCl; /* present in FROM clause */
- bool inJoinSet; /* planner must include this rel */
bool skipAcl; /* skip ACL check in executor */
} RangeTblEntry;
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: pg_list.h,v 1.18 2000/06/09 01:44:26 momjian Exp $
+ * $Id: pg_list.h,v 1.19 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern List *lcons(void *datum, List *list);
extern List *lconsi(int datum, List *list);
extern bool member(void *datum, List *list);
+extern bool ptrMember(void *datum, List *list);
extern bool intMember(int datum, List *list);
extern Value *makeInteger(long i);
extern Value *makeFloat(char *numericStr);
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: plannodes.h,v 1.41 2000/07/12 02:37:33 tgl Exp $
+ * $Id: plannodes.h,v 1.42 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* individual nodes point to one EState
* for the whole top-level plan */
List *targetlist;
- List *qual; /* Node* or List* ?? */
+ List *qual; /* implicitly-ANDed qual conditions */
struct Plan *lefttree;
struct Plan *righttree;
List *extParam; /* indices of _all_ _external_ PARAM_EXEC
/* ----------------
* Join node
+ *
+ * jointype: rule for joining tuples from left and right subtrees
+ * joinqual: qual conditions that came from JOIN/ON or JOIN/USING
+ * (plan.qual contains conditions that came from WHERE)
+ *
+ * When jointype is INNER, joinqual and plan.qual are semantically
+ * interchangeable. For OUTER jointypes, the two are *not* interchangeable;
+ * only joinqual is used to determine whether a match has been found for
+ * the purpose of deciding whether to generate null-extended tuples.
+ * (But plan.qual is still applied before actually returning a tuple.)
+ * For an outer join, only joinquals are allowed to be used as the merge
+ * or hash condition of a merge or hash join.
* ----------------
*/
-typedef Plan Join;
+typedef struct Join
+{
+ Plan plan;
+ JoinType jointype;
+ List *joinqual; /* JOIN quals (in addition to plan.qual) */
+} Join;
/* ----------------
* nest loop join node
List *hashclauses;
Oid hashjoinop;
HashJoinState *hashjoinstate;
- bool hashdone;
} HashJoin;
/* ---------------
/*-------------------------------------------------------------------------
*
* primnodes.h
- * Definitions for parse tree/query tree ("primitive") nodes.
+ * Definitions for "primitive" node types, those that are used in more
+ * than one of the parse/plan/execute stages of the query pipeline.
+ * Currently, these are mostly nodes for executable expressions
+ * and join trees.
*
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: primnodes.h,v 1.47 2000/08/24 03:29:13 tgl Exp $
+ * $Id: primnodes.h,v 1.48 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
BoolPtr fj_alwaysDone;
} Fjoin;
+
+/* ----------------------------------------------------------------
+ * node types for executable expressions
+ * ----------------------------------------------------------------
+ */
+
/* ----------------
* Expr
* typeOid - oid of the type of this expression
AttrNumber varattno;
Oid vartype;
int32 vartypmod;
- Index varlevelsup; /* erased by upper optimizer */
+ Index varlevelsup;
Index varnoold; /* mainly for debugging --- see above */
AttrNumber varoattno;
} Var;
int32 resulttypmod;
} RelabelType;
+
+/* ----------------------------------------------------------------
+ * node types for join trees
+ *
+ * The leaves of a join tree structure are RangeTblRef nodes. Above
+ * these, JoinExpr nodes can appear to denote a specific kind of join
+ * or qualified join. A join tree can also contain List nodes --- a list
+ * implies an unqualified cross-product join of its members. The planner
+ * is allowed to combine the elements of a list using whatever join order
+ * seems good to it. At present, JoinExpr nodes are always joined in
+ * exactly the order implied by the tree structure (except the planner
+ * may choose to swap inner and outer members of a join pair).
+ *
+ * NOTE: currently, the planner only supports a List at the top level of
+ * a join tree. Should generalize this to allow Lists at lower levels.
+ *
+ * NOTE: the qualification expressions present in JoinExpr nodes are
+ * *in addition to* the query's main WHERE clause. For outer joins there
+ * is a real semantic difference between a join qual and a WHERE clause,
+ * though if all joins are inner joins they are interchangeable.
+ *
+ * NOTE: in the raw output of gram.y, a join tree contains RangeVar and
+ * RangeSubselect nodes, which are both replaced by RangeTblRef nodes
+ * during the parse analysis phase.
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * RangeTblRef - reference to an entry in the query's rangetable
+ *
+ * We could use direct pointers to the RT entries and skip having these
+ * nodes, but multiple pointers to the same node in a querytree cause
+ * lots of headaches, so it seems better to store an index into the RT.
+ */
+typedef struct RangeTblRef
+{
+ NodeTag type;
+ int rtindex;
+} RangeTblRef;
+
+/*----------
+ * JoinExpr - for SQL JOIN expressions
+ *
+ * isNatural, using, and quals are interdependent. The user can write only
+ * one of NATURAL, USING(), or ON() (this is enforced by the grammar).
+ * If he writes NATURAL then parse analysis generates the equivalent USING()
+ * list, and from that fills in "quals" with the right equality comparisons.
+ * If he writes USING() then "quals" is filled with equality comparisons.
+ * If he writes ON() then only "quals" is set. Note that NATURAL/USING
+ * are not equivalent to ON() since they also affect the output column list.
+ *
+ * alias is an Attr node representing the AS alias-clause attached to the
+ * join expression, or NULL if no clause. During parse analysis, colnames
+ * is filled with a list of String nodes giving the column names (real or
+ * alias) of the output of the join, and colvars is filled with a list of
+ * expressions that can be copied to reference the output columns.
+ *----------
+ */
+typedef struct JoinExpr
+{
+ NodeTag type;
+ JoinType jointype; /* type of join */
+ bool isNatural; /* Natural join? Will need to shape table */
+ Node *larg; /* left subtree */
+ Node *rarg; /* right subtree */
+ List *using; /* USING clause, if any (list of String) */
+ Node *quals; /* qualifiers on join, if any */
+ struct Attr *alias; /* user-written alias clause, if any */
+ List *colnames; /* output column names (list of String) */
+ List *colvars; /* output column nodes (list of expressions) */
+} JoinExpr;
+
#endif /* PRIMNODES_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: relation.h,v 1.47 2000/04/12 17:16:40 momjian Exp $
+ * $Id: relation.h,v 1.48 2000/09/12 21:07:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* participates (only used for base rels)
* baserestrictcost - Estimated cost of evaluating the baserestrictinfo
* clauses at a single tuple (only used for base rels)
+ * outerjoinset - If the rel appears within the nullable side of an outer
+ * join, the list of all relids participating in the highest
+ * such outer join; else NIL (only used for base rels)
* joininfo - List of JoinInfo nodes, containing info about each join
* clause in which this relation participates
* innerjoin - List of Path nodes that represent indices that may be used
* We store baserestrictcost in the RelOptInfo (for base relations) because
* we know we will need it at least once (to price the sequential scan)
* and may need it multiple times to price index scans.
+ *
+ * outerjoinset is used to ensure correct placement of WHERE clauses that
+ * apply to outer-joined relations; we must not apply such WHERE clauses
+ * until after the outer join is performed.
*/
typedef struct RelOptInfo
List *baserestrictinfo; /* RestrictInfo structures (if
* base rel) */
Cost baserestrictcost; /* cost of evaluating the above */
+ Relids outerjoinset; /* integer list of base relids */
List *joininfo; /* JoinInfo structures */
List *innerjoin; /* potential indexscans for nestloop joins */
* that refer to values of other rels, so those other rels must be
* included in the outer joinrel in order to make a usable join.
*
+ * 'alljoinquals' is also used only for inner paths of nestloop joins.
+ * This flag is TRUE iff all the indexquals came from JOIN/ON conditions.
+ *
* 'rows' is the estimated result tuple count for the indexscan. This
* is the same as path.parent->rows for a simple indexscan, but it is
* different for a nestloop inner path, because the additional indexquals
List *indexqual;
ScanDirection indexscandir;
Relids joinrelids; /* other rels mentioned in indexqual */
+ bool alljoinquals; /* all indexquals derived from JOIN conds? */
double rows; /* estimated number of result tuples */
} IndexPath;
{
Path path;
+ JoinType jointype;
+
Path *outerjoinpath; /* path for the outer side of the join */
Path *innerjoinpath; /* path for the inner side of the join */
+
List *joinrestrictinfo; /* RestrictInfos to apply to join */
/*
* The clause cannot actually be applied until we have built a join rel
* containing all the base rels it references, however.
*
- * When we construct a join rel that describes exactly the set of base rels
- * referenced in a multi-relation restriction clause, we place that clause
- * into the joinrestrictinfo lists of paths for the join rel. It will be
- * applied at that join level, and will not propagate any further up the
- * join tree. (Note: the "predicate migration" code was once intended to
+ * When we construct a join rel that includes all the base rels referenced
+ * in a multi-relation restriction clause, we place that clause into the
+ * joinrestrictinfo lists of paths for the join rel, if neither left nor
+ * right sub-path includes all base rels referenced in the clause. The clause
+ * will be applied at that join level, and will not propagate any further up
+ * the join tree. (Note: the "predicate migration" code was once intended to
* push restriction clauses up and down the plan tree based on evaluation
* costs, but it's dead code and is unlikely to be resurrected in the
* foreseeable future.)
* or hashjoin clauses are fairly limited --- the code for each kind of
* path is responsible for identifying the restrict clauses it can use
* and ignoring the rest. Clauses not implemented by an indexscan,
- * mergejoin, or hashjoin will be placed in the qpqual field of the
- * final Plan node, where they will be enforced by general-purpose
+ * mergejoin, or hashjoin will be placed in the plan qual or joinqual field
+ * of the final Plan node, where they will be enforced by general-purpose
* qual-expression-evaluation code. (But we are still entitled to count
* their selectivity when estimating the result tuple count, if we
* can guess what it is...)
+ *
+ * When dealing with outer joins we must distinguish between qual clauses
+ * that came from WHERE and those that came from JOIN/ON or JOIN/USING.
+ * (For inner joins there's no semantic difference and we can treat the
+ * clauses interchangeably.) Both kinds of quals are stored as RestrictInfo
+ * nodes during planning, but there's a flag to indicate where they came from.
+ * Note also that when outer joins are present, a qual clause may be treated
+ * as referencing more rels than it really does. This trick ensures that the
+ * qual will be evaluated at the right level of the join tree --- we don't
+ * want quals from WHERE to be evaluated until after the outer join is done.
*/
typedef struct RestrictInfo
{
NodeTag type;
- Expr *clause; /* the represented clause of WHERE cond */
+ Expr *clause; /* the represented clause of WHERE or JOIN */
+
+ bool isjoinqual; /* TRUE if clause came from JOIN/ON */
/* only used if clause is an OR clause: */
List *subclauseindices; /* indexes matching subclauses */
typedef struct JoinInfo
{
NodeTag type;
- Relids unjoined_relids;/* some rels not yet part of my RelOptInfo */
+ Relids unjoined_relids; /* some rels not yet part of my RelOptInfo */
List *jinfo_restrictinfo; /* relevant RestrictInfos */
} JoinInfo;
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: clauses.h,v 1.38 2000/08/13 02:50:26 tgl Exp $
+ * $Id: clauses.h,v 1.39 2000/09/12 21:07:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern Node *eval_const_expressions(Node *node);
extern bool expression_tree_walker(Node *node, bool (*walker) (),
- void *context);
+ void *context);
extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
- void *context);
+ void *context);
+extern bool query_tree_walker(Query *query, bool (*walker) (),
+ void *context);
#define is_subplan(clause) ((clause) != NULL && \
IsA(clause, Expr) && \
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: pathnode.h,v 1.27 2000/04/12 17:16:42 momjian Exp $
+ * $Id: pathnode.h,v 1.28 2000/09/12 21:07:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern TidPath *create_tidscan_path(RelOptInfo *rel, List *tideval);
extern NestPath *create_nestloop_path(RelOptInfo *joinrel,
- Path *outer_path,
- Path *inner_path,
- List *restrict_clauses,
- List *pathkeys);
+ JoinType jointype,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys);
extern MergePath *create_mergejoin_path(RelOptInfo *joinrel,
- Path *outer_path,
- Path *inner_path,
- List *restrict_clauses,
- List *pathkeys,
- List *mergeclauses,
- List *outersortkeys,
- List *innersortkeys);
+ JoinType jointype,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys,
+ List *mergeclauses,
+ List *outersortkeys,
+ List *innersortkeys);
extern HashPath *create_hashjoin_path(RelOptInfo *joinrel,
- Path *outer_path,
- Path *inner_path,
- List *restrict_clauses,
- List *hashclauses,
- Selectivity innerdisbursion);
+ JoinType jointype,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *hashclauses,
+ Selectivity innerdisbursion);
/*
* prototypes for relnode.c
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: paths.h,v 1.46 2000/07/24 03:10:54 tgl Exp $
+ * $Id: paths.h,v 1.47 2000/09/12 21:07:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* routines to create join paths
*/
extern void add_paths_to_joinrel(Query *root, RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist);
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ JoinType jointype,
+ List *restrictlist);
/*
* joinrels.c
* routines to determine which relations to join
*/
-extern void make_rels_by_joins(Query *root, int level);
-extern RelOptInfo *make_rels_by_clause_joins(Query *root,
- RelOptInfo *old_rel,
- List *other_rels);
-extern RelOptInfo *make_rels_by_clauseless_joins(Query *root,
- RelOptInfo *old_rel,
- List *other_rels);
+extern List *make_rels_by_joins(Query *root, int level, List **joinrels);
+extern List *make_rels_by_clause_joins(Query *root,
+ RelOptInfo *old_rel,
+ List *other_rels);
+extern List *make_rels_by_clauseless_joins(Query *root,
+ RelOptInfo *old_rel,
+ List *other_rels);
+extern RelOptInfo *make_rel_from_jointree(Query *root, Node *jtnode);
/*
* pathkeys.c
extern List *find_mergeclauses_for_pathkeys(List *pathkeys,
List *restrictinfos);
extern List *make_pathkeys_for_mergeclauses(Query *root,
- List *mergeclauses,
- List *tlist);
+ List *mergeclauses,
+ RelOptInfo *rel);
#endif /* PATHS_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: planmain.h,v 1.43 2000/07/24 03:10:54 tgl Exp $
+ * $Id: planmain.h,v 1.44 2000/09/12 21:07:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* prototypes for plan/planmain.c
*/
-extern Plan *query_planner(Query *root, List *tlist, List *qual,
- double tuple_fraction);
+extern Plan *query_planner(Query *root, List *tlist, double tuple_fraction);
/*
* prototypes for plan/createplan.c
/*
* prototypes for plan/initsplan.c
*/
-extern void make_var_only_tlist(Query *root, List *tlist);
+extern void build_base_rel_tlists(Query *root, List *tlist);
+extern Relids add_join_quals_to_rels(Query *root, Node *jtnode);
extern void add_restrict_and_join_to_rels(Query *root, List *clauses);
-extern void add_missing_rels_to_query(Query *root);
+extern List *add_missing_rels_to_query(Query *root, Node *jtnode);
extern void process_implied_equality(Query *root, Node *item1, Node *item2,
Oid sortop1, Oid sortop2);
* prep/prepkeyset.c
*/
extern bool _use_keyset_query_optimizer;
+
extern void transformKeySetQuery(Query *origNode);
#endif /* PLANMAIN_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: restrictinfo.h,v 1.8 2000/01/26 05:58:21 momjian Exp $
+ * $Id: restrictinfo.h,v 1.9 2000/09/12 21:07:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
extern List *get_actual_clauses(List *restrictinfo_list);
+extern void get_actual_join_clauses(List *restrictinfo_list,
+ List **joinquals, List **otherquals);
#endif /* RESTRICTINFO_H */
/*-------------------------------------------------------------------------
*
* gramparse.h
- * scanner support routines. used by both the bootstrap lexer
- * as well as the normal lexer
+ * Declarations for routines exported from lexer and parser files.
+ *
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: gramparse.h,v 1.12 2000/04/12 17:16:44 momjian Exp $
+ * $Id: gramparse.h,v 1.13 2000/09/12 21:07:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef GRAMPARSE_H
-#define GRAMPARSE_H /* include once only */
+#define GRAMPARSE_H
-/* from scan.l */
-extern void init_io(void);
+/* from parser.c */
extern int yylex(void);
+
+/* from scan.l */
+extern void scanner_init(void);
+extern int base_yylex(void);
extern void yyerror(const char *message);
/* from gram.y */
-extern Oid param_type(int t);
extern void parser_init(Oid *typev, int nargs);
+extern Oid param_type(int t);
extern int yyparse(void);
#endif /* GRAMPARSE_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parse_clause.h,v 1.18 2000/06/09 01:44:29 momjian Exp $
+ * $Id: parse_clause.h,v 1.19 2000/09/12 21:07:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "parser/parse_node.h"
extern void makeRangeTable(ParseState *pstate, List *frmList);
-extern void setTargetTable(ParseState *pstate, char *relname, bool inh);
+extern void setTargetTable(ParseState *pstate, char *relname,
+ bool inh, bool inJoinSet);
extern Node *transformWhereClause(ParseState *pstate, Node *where);
extern List *transformGroupClause(ParseState *pstate, List *grouplist,
List *targetlist);
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parse_func.h,v 1.26 2000/08/20 00:44:17 tgl Exp $
+ * $Id: parse_func.h,v 1.27 2000/09/12 21:07:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
} *CandidateList;
extern Node *ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr,
- int *curr_resno, int precedence);
+ int precedence);
extern Node *ParseFuncOrColumn(ParseState *pstate,
- char *funcname, List *fargs,
- bool agg_star, bool agg_distinct,
- int *curr_resno, int precedence);
+ char *funcname, List *fargs,
+ bool agg_star, bool agg_distinct,
+ int precedence);
extern bool func_get_detail(char *funcname, int nargs, Oid *argtypes,
Oid *funcid, Oid *rettype,
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parse_node.h,v 1.20 2000/05/12 01:33:52 tgl Exp $
+ * $Id: parse_node.h,v 1.21 2000/09/12 21:07:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "nodes/parsenodes.h"
#include "utils/rel.h"
-/* State information used during parse analysis
- * p_join_quals is a list of untransformed qualification expressions
- * (implicitly ANDed together) found in the FROM clause.
- * Needs to be available later to merge with other qualifiers from the
- * WHERE clause.
+/*
+ * State information used during parse analysis
*/
typedef struct ParseState
{
- int p_last_resno;
- List *p_rtable;
- struct ParseState *parentParseState;
+ struct ParseState *parentParseState; /* stack link */
+ List *p_rtable; /* range table so far */
+ List *p_jointree; /* join tree so far */
+ int p_last_resno; /* last targetlist resno assigned */
bool p_hasAggs;
bool p_hasSubLinks;
bool p_is_insert;
bool p_is_update;
- bool p_is_rule;
- bool p_in_where_clause;
Relation p_target_relation;
RangeTblEntry *p_target_rangetblentry;
- List *p_shape;
- List *p_alias;
- List *p_join_quals;
} ParseState;
extern ParseState *make_parsestate(ParseState *parentParseState);
extern Expr *make_op(char *opname, Node *ltree, Node *rtree);
extern Node *make_operand(char *opname, Node *tree,
Oid orig_typeId, Oid target_typeId);
-extern Var *make_var(ParseState *pstate, Oid relid, char *refname,
- char *attrname);
+extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno);
extern ArrayRef *transformArraySubscripts(ParseState *pstate,
Node *arrayBase,
List *indirection,
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parse_relation.h,v 1.18 2000/06/08 22:37:53 momjian Exp $
+ * $Id: parse_relation.h,v 1.19 2000/09/12 21:07:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "parser/parse_node.h"
-extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate, char *refname);
+extern Node *refnameRangeOrJoinEntry(ParseState *pstate,
+ char *refname,
+ int *sublevels_up);
+extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate,
+ char *refname);
extern int refnameRangeTablePosn(ParseState *pstate,
- char *refname,
- int *sublevels_up);
-extern RangeTblEntry *colnameRangeTableEntry(ParseState *pstate, char *colname);
+ char *refname,
+ int *sublevels_up);
+extern int RTERangeTablePosn(ParseState *pstate,
+ RangeTblEntry *rte,
+ int *sublevels_up);
+extern JoinExpr *scanJoinTreeForRefname(Node *jtnode, char *refname);
+extern Node *colnameToVar(ParseState *pstate, char *colname);
+extern Node *qualifiedNameToVar(ParseState *pstate, char *refname,
+ char *colname, bool implicitRTEOK);
extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
- char *relname,
- Attr *ref,
- bool inh,
- bool inFromCl,
- bool inJoinSet);
-extern Attr *expandTable(ParseState *pstate, char *refname, bool getaliases);
-extern List *expandAll(ParseState *pstate, char *relname, Attr *ref,
- int *this_resno);
+ char *relname,
+ Attr *alias,
+ bool inh,
+ bool inFromCl);
+extern void addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte);
+extern RangeTblEntry *addImplicitRTE(ParseState *pstate, char *relname);
+extern void expandRTE(ParseState *pstate, RangeTblEntry *rte,
+ List **colnames, List **colvars);
+extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte);
+extern List *expandJoinAttrs(ParseState *pstate, JoinExpr *join,
+ int sublevels_up);
extern int attnameAttNum(Relation rd, char *a);
extern int specialAttNum(char *a);
extern Oid attnumTypeId(Relation rd, int attid);
-extern void warnAutoRange(ParseState *pstate, char *refname);
#endif /* PARSE_RELATION_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parsetree.h,v 1.10 2000/06/12 19:40:51 momjian Exp $
+ * $Id: parsetree.h,v 1.11 2000/09/12 21:07:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef PARSETREE_H
-#define PARSETREE_H /* include once only */
+#define PARSETREE_H
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"
/* ----------------
- * need pg_list.h for definitions of CAR(), etc. macros
+ * need pg_list.h for definitions of nth(), etc.
* ----------------
*/
/* ----------------
* range table macros
- *
- * parse tree:
- * (root targetlist qual)
- * ^^^^
- * parse root:
- * (numlevels cmdtype resrel rangetable priority ruleinfo nestdotinfo)
- * ^^^^^^^^^^
- * range table:
- * (rtentry ...)
- * rtentry:
* ----------------
*/
-#define rt_relname(rt_entry) \
- ((!strcmp(((rt_entry)->ref->relname),"*OLD*") ||\
- !strcmp(((rt_entry)->ref->relname),"*NEW*")) ? ((rt_entry)->ref->relname) : \
- ((char *)(rt_entry)->relname))
-
/*
* rt_fetch
* rt_store
*
*/
#define rt_fetch(rangetable_index, rangetable) \
- ((RangeTblEntry*)nth((rangetable_index)-1, rangetable))
+ ((RangeTblEntry*) nth((rangetable_index)-1, rangetable))
#define rt_store(rangetable_index, rangetable, rt) \
set_nth(rangetable, (rangetable_index)-1, rt)
/*
* getrelid
- * getrelname
*
* Given the range index of a relation, return the corresponding
- * relation id or relation name.
+ * relation OID.
*/
#define getrelid(rangeindex,rangetable) \
- ((RangeTblEntry*)nth((rangeindex)-1, rangetable))->relid
-
-#define getrelname(rangeindex, rangetable) \
- rt_relname((RangeTblEntry*)nth((rangeindex)-1, rangetable))
+ (rt_fetch(rangeindex, rangetable)->relid)
#endif /* PARSETREE_H */
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: rewriteHandler.h,v 1.12 2000/01/26 05:58:30 momjian Exp $
+ * $Id: rewriteHandler.h,v 1.13 2000/09/12 21:07:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "nodes/parsenodes.h"
-struct _rewrite_meta_knowledge
+typedef struct RewriteInfo
{
- List *rt;
int rt_index;
bool instead_flag;
int event;
Query *rule_action;
Node *rule_qual;
bool nothing;
-};
-
-typedef struct _rewrite_meta_knowledge RewriteInfo;
+} RewriteInfo;
extern List *QueryRewrite(Query *parsetree);
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: rewriteManip.h,v 1.21 2000/04/12 17:16:50 momjian Exp $
+ * $Id: rewriteManip.h,v 1.22 2000/09/12 21:07:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
int sublevels_up);
extern void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
int min_sublevels_up);
+
+extern bool rangeTableEntry_used(Node *node, int rt_index,
+ int sublevels_up);
+extern bool attribute_used(Node *node, int rt_index, int attno,
+ int sublevels_up);
+
extern void AddQual(Query *parsetree, Node *qual);
extern void AddHavingQual(Query *parsetree, Node *havingQual);
extern void AddNotQual(Query *parsetree, Node *qual);
case
-------
10.1
- 20.2
- -30.3
- 1
10.1
- 20.2
- -30.3
- 2
10.1
- 20.2
- -30.3
- 3
10.1
- 20.2
- -30.3
- 2
10.1
- 20.2
- -30.3
- 1
10.1
20.2
+ 20.2
+ 20.2
+ 20.2
+ 20.2
+ 20.2
+ -30.3
+ -30.3
+ -30.3
-30.3
+ -30.3
+ -30.3
+ 1
+ 2
+ 3
+ 2
+ 1
-6
(24 rows)
five | NULLIF(a.i,b.i) | NULLIF(b.i,4)
------+-----------------+---------------
| | 1
- | 2 | 1
- | 3 | 1
- | 4 | 1
| 1 | 2
- | | 2
- | 3 | 2
- | 4 | 2
| 1 | 3
- | 2 | 3
- | | 3
- | 4 | 3
| 1 | 2
- | | 2
- | 3 | 2
- | 4 | 2
| | 1
- | 2 | 1
- | 3 | 1
- | 4 | 1
| 1 |
+ | 2 | 1
+ | | 2
+ | 2 | 3
+ | | 2
+ | 2 | 1
| 2 |
+ | 3 | 1
+ | 3 | 2
+ | | 3
+ | 3 | 2
+ | 3 | 1
| 3 |
+ | 4 | 1
+ | 4 | 2
+ | 4 | 3
+ | 4 | 2
+ | 4 | 1
| 4 |
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (-0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (-0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,-0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (-0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (-0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,-0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (-0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (-0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,-0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (-0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (-0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,-0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (-0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (-0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,-0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
twentyfour | translation
------------+-------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (-8,2),(-10,0)
- | (-7,3),(-9,1)
- | (-7.5,3.5),(-7.5,2.5)
- | (-7,3),(-7,3)
| (-1,6),(-3,4)
- | (0,7),(-2,5)
- | (-0.5,7.5),(-0.5,6.5)
- | (0,7),(0,7)
| (7.1,36.5),(5.1,34.5)
- | (8.1,37.5),(6.1,35.5)
- | (7.6,38),(7.6,37)
- | (8.1,37.5),(8.1,37.5)
| (-3,-10),(-5,-12)
- | (-2,-9),(-4,-11)
- | (-2.5,-8.5),(-2.5,-9.5)
- | (-2,-9),(-2,-9)
| (12,12),(10,10)
+ | (3,3),(1,1)
+ | (-7,3),(-9,1)
+ | (0,7),(-2,5)
+ | (8.1,37.5),(6.1,35.5)
+ | (-2,-9),(-4,-11)
| (13,13),(11,11)
+ | (2.5,3.5),(2.5,2.5)
+ | (-7.5,3.5),(-7.5,2.5)
+ | (-0.5,7.5),(-0.5,6.5)
+ | (7.6,38),(7.6,37)
+ | (-2.5,-8.5),(-2.5,-9.5)
| (12.5,13.5),(12.5,12.5)
+ | (3,3),(3,3)
+ | (-7,3),(-7,3)
+ | (0,7),(0,7)
+ | (8.1,37.5),(8.1,37.5)
+ | (-2,-9),(-2,-9)
| (13,13),(13,13)
(24 rows)
twentyfour | translation
------------+---------------------------
| (2,2),(0,0)
- | (3,3),(1,1)
- | (2.5,3.5),(2.5,2.5)
- | (3,3),(3,3)
| (12,2),(10,0)
- | (13,3),(11,1)
- | (12.5,3.5),(12.5,2.5)
- | (13,3),(13,3)
| (5,-2),(3,-4)
- | (6,-1),(4,-3)
- | (5.5,-0.5),(5.5,-1.5)
- | (6,-1),(6,-1)
| (-3.1,-32.5),(-5.1,-34.5)
- | (-2.1,-31.5),(-4.1,-33.5)
- | (-2.6,-31),(-2.6,-32)
- | (-2.1,-31.5),(-2.1,-31.5)
| (7,14),(5,12)
- | (8,15),(6,13)
- | (7.5,15.5),(7.5,14.5)
- | (8,15),(8,15)
| (-8,-8),(-10,-10)
+ | (3,3),(1,1)
+ | (13,3),(11,1)
+ | (6,-1),(4,-3)
+ | (-2.1,-31.5),(-4.1,-33.5)
+ | (8,15),(6,13)
| (-7,-7),(-9,-9)
+ | (2.5,3.5),(2.5,2.5)
+ | (12.5,3.5),(12.5,2.5)
+ | (5.5,-0.5),(5.5,-1.5)
+ | (-2.6,-31),(-2.6,-32)
+ | (7.5,15.5),(7.5,14.5)
| (-7.5,-6.5),(-7.5,-7.5)
+ | (3,3),(3,3)
+ | (13,3),(13,3)
+ | (6,-1),(6,-1)
+ | (-2.1,-31.5),(-2.1,-31.5)
+ | (8,15),(8,15)
| (-7,-7),(-7,-7)
(24 rows)
FROM BOX_TBL b, POINT_TBL p;
twentyfour | rotation
------------+-----------------------------
- | (0,0),(0,0)
- | (0,0),(0,0)
- | (0,0),(0,0)
| (0,0),(0,0)
| (-0,0),(-20,-20)
- | (-10,-10),(-30,-30)
- | (-25,-25),(-25,-35)
- | (-30,-30),(-30,-30)
| (-0,2),(-14,0)
- | (-7,3),(-21,1)
- | (-17.5,2.5),(-21.5,-0.5)
- | (-21,3),(-21,3)
| (0,79.2),(-58.8,0)
- | (-29.4,118.8),(-88.2,39.6)
- | (-73.5,104.1),(-108,99)
- | (-88.2,118.8),(-88.2,118.8)
| (14,-0),(0,-34)
- | (21,-17),(7,-51)
- | (29.5,-42.5),(17.5,-47.5)
- | (21,-51),(21,-51)
| (0,40),(0,0)
+ | (0,0),(0,0)
+ | (-10,-10),(-30,-30)
+ | (-7,3),(-21,1)
+ | (-29.4,118.8),(-88.2,39.6)
+ | (21,-17),(7,-51)
| (0,60),(0,20)
+ | (0,0),(0,0)
+ | (-25,-25),(-25,-35)
+ | (-17.5,2.5),(-21.5,-0.5)
+ | (-73.5,104.1),(-108,99)
+ | (29.5,-42.5),(17.5,-47.5)
| (0,60),(-10,50)
+ | (0,0),(0,0)
+ | (-30,-30),(-30,-30)
+ | (-21,3),(-21,3)
+ | (-88.2,118.8),(-88.2,118.8)
+ | (21,-51),(21,-51)
| (0,60),(0,60)
(24 rows)
--
-- JOIN
--- Test join clauses
+-- Test JOIN clauses
--
CREATE TABLE J1_TBL (
i integer,
INSERT INTO J2_TBL VALUES (2, 2);
INSERT INTO J2_TBL VALUES (3, -3);
INSERT INTO J2_TBL VALUES (2, 4);
+INSERT INTO J2_TBL VALUES (5, -5);
--
-- CORRELATION NAMES
-- Make sure that table/column aliases are supported
xxx | a | b | c | d | e
-----+---+---+-------+---+----
| 1 | 3 | one | 1 | -1
- | 2 | 2 | two | 1 | -1
- | 3 | 1 | three | 1 | -1
- | 4 | 0 | four | 1 | -1
| 1 | 3 | one | 2 | 2
- | 2 | 2 | two | 2 | 2
- | 3 | 1 | three | 2 | 2
- | 4 | 0 | four | 2 | 2
| 1 | 3 | one | 3 | -3
- | 2 | 2 | two | 3 | -3
- | 3 | 1 | three | 3 | -3
- | 4 | 0 | four | 3 | -3
| 1 | 3 | one | 2 | 4
+ | 1 | 3 | one | 5 | -5
+ | 2 | 2 | two | 1 | -1
+ | 2 | 2 | two | 2 | 2
+ | 2 | 2 | two | 3 | -3
| 2 | 2 | two | 2 | 4
+ | 2 | 2 | two | 5 | -5
+ | 3 | 1 | three | 1 | -1
+ | 3 | 1 | three | 2 | 2
+ | 3 | 1 | three | 3 | -3
| 3 | 1 | three | 2 | 4
+ | 3 | 1 | three | 5 | -5
+ | 4 | 0 | four | 1 | -1
+ | 4 | 0 | four | 2 | 2
+ | 4 | 0 | four | 3 | -3
| 4 | 0 | four | 2 | 4
-(16 rows)
+ | 4 | 0 | four | 5 | -5
+(20 rows)
SELECT '' AS "xxx", t1.a, t2.e
FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e)
xxx | i | j | t | i | k
-----+---+---+-------+---+----
| 1 | 3 | one | 1 | -1
- | 2 | 2 | two | 1 | -1
- | 3 | 1 | three | 1 | -1
- | 4 | 0 | four | 1 | -1
| 1 | 3 | one | 2 | 2
- | 2 | 2 | two | 2 | 2
- | 3 | 1 | three | 2 | 2
- | 4 | 0 | four | 2 | 2
| 1 | 3 | one | 3 | -3
- | 2 | 2 | two | 3 | -3
- | 3 | 1 | three | 3 | -3
- | 4 | 0 | four | 3 | -3
| 1 | 3 | one | 2 | 4
+ | 1 | 3 | one | 5 | -5
+ | 2 | 2 | two | 1 | -1
+ | 2 | 2 | two | 2 | 2
+ | 2 | 2 | two | 3 | -3
| 2 | 2 | two | 2 | 4
+ | 2 | 2 | two | 5 | -5
+ | 3 | 1 | three | 1 | -1
+ | 3 | 1 | three | 2 | 2
+ | 3 | 1 | three | 3 | -3
| 3 | 1 | three | 2 | 4
+ | 3 | 1 | three | 5 | -5
+ | 4 | 0 | four | 1 | -1
+ | 4 | 0 | four | 2 | 2
+ | 4 | 0 | four | 3 | -3
| 4 | 0 | four | 2 | 4
-(16 rows)
+ | 4 | 0 | four | 5 | -5
+(20 rows)
-- ambiguous column
SELECT '' AS "xxx", i, k, t
FROM J1_TBL CROSS JOIN J2_TBL;
-ERROR: Column 'i' is ambiguous
+ERROR: Column reference "i" is ambiguous
-- resolve previous ambiguity by specifying the table name
SELECT '' AS "xxx", t1.i, k, t
FROM J1_TBL t1 CROSS JOIN J2_TBL t2;
xxx | i | k | t
-----+---+----+-------
| 1 | -1 | one
- | 2 | -1 | two
- | 3 | -1 | three
- | 4 | -1 | four
| 1 | 2 | one
- | 2 | 2 | two
- | 3 | 2 | three
- | 4 | 2 | four
| 1 | -3 | one
- | 2 | -3 | two
- | 3 | -3 | three
- | 4 | -3 | four
| 1 | 4 | one
+ | 1 | -5 | one
+ | 2 | -1 | two
+ | 2 | 2 | two
+ | 2 | -3 | two
| 2 | 4 | two
+ | 2 | -5 | two
+ | 3 | -1 | three
+ | 3 | 2 | three
+ | 3 | -3 | three
| 3 | 4 | three
+ | 3 | -5 | three
+ | 4 | -1 | four
+ | 4 | 2 | four
+ | 4 | -3 | four
| 4 | 4 | four
-(16 rows)
+ | 4 | -5 | four
+(20 rows)
SELECT '' AS "xxx", ii, tt, kk
FROM (J1_TBL CROSS JOIN J2_TBL)
AS tx (ii, jj, tt, ii2, kk);
-ERROR: JOIN table aliases are not supported
+ xxx | ii | tt | kk
+-----+----+-------+----
+ | 1 | one | -1
+ | 1 | one | 2
+ | 1 | one | -3
+ | 1 | one | 4
+ | 1 | one | -5
+ | 2 | two | -1
+ | 2 | two | 2
+ | 2 | two | -3
+ | 2 | two | 4
+ | 2 | two | -5
+ | 3 | three | -1
+ | 3 | three | 2
+ | 3 | three | -3
+ | 3 | three | 4
+ | 3 | three | -5
+ | 4 | four | -1
+ | 4 | four | 2
+ | 4 | four | -3
+ | 4 | four | 4
+ | 4 | four | -5
+(20 rows)
+
SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk
FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))
AS tx (ii, jj, tt, ii2, kk);
-ERROR: JOIN table aliases are not supported
+ xxx | ii | jj | kk
+-----+----+----+----
+ | 1 | 3 | -1
+ | 1 | 3 | 2
+ | 1 | 3 | -3
+ | 1 | 3 | 4
+ | 1 | 3 | -5
+ | 2 | 2 | -1
+ | 2 | 2 | 2
+ | 2 | 2 | -3
+ | 2 | 2 | 4
+ | 2 | 2 | -5
+ | 3 | 1 | -1
+ | 3 | 1 | 2
+ | 3 | 1 | -3
+ | 3 | 1 | 4
+ | 3 | 1 | -5
+ | 4 | 0 | -1
+ | 4 | 0 | 2
+ | 4 | 0 | -3
+ | 4 | 0 | 4
+ | 4 | 0 | -5
+(20 rows)
+
+SELECT '' AS "xxx", *
+ FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+ xxx | i | j | t | i | k | i | k
+-----+---+---+-------+---+----+---+----
+ | 1 | 3 | one | 1 | -1 | 1 | -1
+ | 1 | 3 | one | 1 | -1 | 2 | 2
+ | 1 | 3 | one | 1 | -1 | 3 | -3
+ | 1 | 3 | one | 1 | -1 | 2 | 4
+ | 1 | 3 | one | 1 | -1 | 5 | -5
+ | 1 | 3 | one | 2 | 2 | 1 | -1
+ | 1 | 3 | one | 2 | 2 | 2 | 2
+ | 1 | 3 | one | 2 | 2 | 3 | -3
+ | 1 | 3 | one | 2 | 2 | 2 | 4
+ | 1 | 3 | one | 2 | 2 | 5 | -5
+ | 1 | 3 | one | 3 | -3 | 1 | -1
+ | 1 | 3 | one | 3 | -3 | 2 | 2
+ | 1 | 3 | one | 3 | -3 | 3 | -3
+ | 1 | 3 | one | 3 | -3 | 2 | 4
+ | 1 | 3 | one | 3 | -3 | 5 | -5
+ | 1 | 3 | one | 2 | 4 | 1 | -1
+ | 1 | 3 | one | 2 | 4 | 2 | 2
+ | 1 | 3 | one | 2 | 4 | 3 | -3
+ | 1 | 3 | one | 2 | 4 | 2 | 4
+ | 1 | 3 | one | 2 | 4 | 5 | -5
+ | 1 | 3 | one | 5 | -5 | 1 | -1
+ | 1 | 3 | one | 5 | -5 | 2 | 2
+ | 1 | 3 | one | 5 | -5 | 3 | -3
+ | 1 | 3 | one | 5 | -5 | 2 | 4
+ | 1 | 3 | one | 5 | -5 | 5 | -5
+ | 2 | 2 | two | 1 | -1 | 1 | -1
+ | 2 | 2 | two | 1 | -1 | 2 | 2
+ | 2 | 2 | two | 1 | -1 | 3 | -3
+ | 2 | 2 | two | 1 | -1 | 2 | 4
+ | 2 | 2 | two | 1 | -1 | 5 | -5
+ | 2 | 2 | two | 2 | 2 | 1 | -1
+ | 2 | 2 | two | 2 | 2 | 2 | 2
+ | 2 | 2 | two | 2 | 2 | 3 | -3
+ | 2 | 2 | two | 2 | 2 | 2 | 4
+ | 2 | 2 | two | 2 | 2 | 5 | -5
+ | 2 | 2 | two | 3 | -3 | 1 | -1
+ | 2 | 2 | two | 3 | -3 | 2 | 2
+ | 2 | 2 | two | 3 | -3 | 3 | -3
+ | 2 | 2 | two | 3 | -3 | 2 | 4
+ | 2 | 2 | two | 3 | -3 | 5 | -5
+ | 2 | 2 | two | 2 | 4 | 1 | -1
+ | 2 | 2 | two | 2 | 4 | 2 | 2
+ | 2 | 2 | two | 2 | 4 | 3 | -3
+ | 2 | 2 | two | 2 | 4 | 2 | 4
+ | 2 | 2 | two | 2 | 4 | 5 | -5
+ | 2 | 2 | two | 5 | -5 | 1 | -1
+ | 2 | 2 | two | 5 | -5 | 2 | 2
+ | 2 | 2 | two | 5 | -5 | 3 | -3
+ | 2 | 2 | two | 5 | -5 | 2 | 4
+ | 2 | 2 | two | 5 | -5 | 5 | -5
+ | 3 | 1 | three | 1 | -1 | 1 | -1
+ | 3 | 1 | three | 1 | -1 | 2 | 2
+ | 3 | 1 | three | 1 | -1 | 3 | -3
+ | 3 | 1 | three | 1 | -1 | 2 | 4
+ | 3 | 1 | three | 1 | -1 | 5 | -5
+ | 3 | 1 | three | 2 | 2 | 1 | -1
+ | 3 | 1 | three | 2 | 2 | 2 | 2
+ | 3 | 1 | three | 2 | 2 | 3 | -3
+ | 3 | 1 | three | 2 | 2 | 2 | 4
+ | 3 | 1 | three | 2 | 2 | 5 | -5
+ | 3 | 1 | three | 3 | -3 | 1 | -1
+ | 3 | 1 | three | 3 | -3 | 2 | 2
+ | 3 | 1 | three | 3 | -3 | 3 | -3
+ | 3 | 1 | three | 3 | -3 | 2 | 4
+ | 3 | 1 | three | 3 | -3 | 5 | -5
+ | 3 | 1 | three | 2 | 4 | 1 | -1
+ | 3 | 1 | three | 2 | 4 | 2 | 2
+ | 3 | 1 | three | 2 | 4 | 3 | -3
+ | 3 | 1 | three | 2 | 4 | 2 | 4
+ | 3 | 1 | three | 2 | 4 | 5 | -5
+ | 3 | 1 | three | 5 | -5 | 1 | -1
+ | 3 | 1 | three | 5 | -5 | 2 | 2
+ | 3 | 1 | three | 5 | -5 | 3 | -3
+ | 3 | 1 | three | 5 | -5 | 2 | 4
+ | 3 | 1 | three | 5 | -5 | 5 | -5
+ | 4 | 0 | four | 1 | -1 | 1 | -1
+ | 4 | 0 | four | 1 | -1 | 2 | 2
+ | 4 | 0 | four | 1 | -1 | 3 | -3
+ | 4 | 0 | four | 1 | -1 | 2 | 4
+ | 4 | 0 | four | 1 | -1 | 5 | -5
+ | 4 | 0 | four | 2 | 2 | 1 | -1
+ | 4 | 0 | four | 2 | 2 | 2 | 2
+ | 4 | 0 | four | 2 | 2 | 3 | -3
+ | 4 | 0 | four | 2 | 2 | 2 | 4
+ | 4 | 0 | four | 2 | 2 | 5 | -5
+ | 4 | 0 | four | 3 | -3 | 1 | -1
+ | 4 | 0 | four | 3 | -3 | 2 | 2
+ | 4 | 0 | four | 3 | -3 | 3 | -3
+ | 4 | 0 | four | 3 | -3 | 2 | 4
+ | 4 | 0 | four | 3 | -3 | 5 | -5
+ | 4 | 0 | four | 2 | 4 | 1 | -1
+ | 4 | 0 | four | 2 | 4 | 2 | 2
+ | 4 | 0 | four | 2 | 4 | 3 | -3
+ | 4 | 0 | four | 2 | 4 | 2 | 4
+ | 4 | 0 | four | 2 | 4 | 5 | -5
+ | 4 | 0 | four | 5 | -5 | 1 | -1
+ | 4 | 0 | four | 5 | -5 | 2 | 2
+ | 4 | 0 | four | 5 | -5 | 3 | -3
+ | 4 | 0 | four | 5 | -5 | 2 | 4
+ | 4 | 0 | four | 5 | -5 | 5 | -5
+(100 rows)
+
--
--
-- Inner joins (equi-joins)
| 4 | 0 | four | 2
(2 rows)
-SELECT '' AS "xxx", *
- FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
- xxx | a | b | c | d
------+---+---+------+---
- | 2 | 2 | two | 2
- | 4 | 0 | four | 2
-(2 rows)
-
-- mismatch number of columns
-- currently, Postgres will fill in with underlying names
SELECT '' AS "xxx", *
| 4 | 0 | four | 2 | 4
(2 rows)
-SELECT '' AS "xxx", *
- FROM J1_TBL CROSS JOIN J2_TBL;
- xxx | i | j | t | i | k
------+---+---+-------+---+----
- | 1 | 3 | one | 1 | -1
- | 2 | 2 | two | 1 | -1
- | 3 | 1 | three | 1 | -1
- | 4 | 0 | four | 1 | -1
- | 1 | 3 | one | 2 | 2
- | 2 | 2 | two | 2 | 2
- | 3 | 1 | three | 2 | 2
- | 4 | 0 | four | 2 | 2
- | 1 | 3 | one | 3 | -3
- | 2 | 2 | two | 3 | -3
- | 3 | 1 | three | 3 | -3
- | 4 | 0 | four | 3 | -3
- | 1 | 3 | one | 2 | 4
- | 2 | 2 | two | 2 | 4
- | 3 | 1 | three | 2 | 4
- | 4 | 0 | four | 2 | 4
-(16 rows)
-
--
-- Non-equi-joins
--
xxx | i | j | t | i | k
-----+---+---+-------+---+---
| 1 | 3 | one | 2 | 2
- | 2 | 2 | two | 2 | 2
| 1 | 3 | one | 2 | 4
+ | 2 | 2 | two | 2 | 2
| 2 | 2 | two | 2 | 4
| 3 | 1 | three | 2 | 4
| 4 | 0 | four | 2 | 4
--
-- Outer joins
--
-SELECT '' AS "xxx", *
- FROM J1_TBL OUTER JOIN J2_TBL USING (i);
-ERROR: OUTER JOIN is not yet supported
SELECT '' AS "xxx", *
FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i);
-ERROR: OUTER JOIN is not yet supported
+ xxx | i | j | t | k
+-----+---+---+-------+----
+ | 1 | 3 | one | -1
+ | 2 | 2 | two | 2
+ | 2 | 2 | two | 4
+ | 3 | 1 | three | -3
+ | 4 | 0 | four |
+(5 rows)
+
SELECT '' AS "xxx", *
FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
-ERROR: OUTER JOIN is not yet supported
+ xxx | i | j | t | k
+-----+---+---+-------+----
+ | 1 | 3 | one | -1
+ | 2 | 2 | two | 2
+ | 2 | 2 | two | 4
+ | 3 | 1 | three | -3
+ | 5 | | | -5
+(5 rows)
+
+-- Note that OUTER is a noise word
SELECT '' AS "xxx", *
- FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i);
-ERROR: OUTER JOIN is not yet supported
+ FROM J1_TBL FULL JOIN J2_TBL USING (i);
+ xxx | i | j | t | k
+-----+---+---+-------+----
+ | 1 | 3 | one | -1
+ | 2 | 2 | two | 2
+ | 2 | 2 | two | 4
+ | 3 | 1 | three | -3
+ | 4 | 0 | four |
+ | 5 | | | -5
+(6 rows)
+
--
-- More complicated constructs
--
+-- UNION JOIN isn't implemented yet
+SELECT '' AS "xxx", *
+ FROM J1_TBL UNION JOIN J2_TBL;
+ERROR: UNION JOIN is not implemented yet
--
-- Clean up
--
WHERE (p1.f1 <-> p2.f1) > 3;
thirty | point1 | point2
--------+------------+------------
- | (-10,0) | (0,0)
- | (-3,4) | (0,0)
- | (5.1,34.5) | (0,0)
- | (-5,-12) | (0,0)
- | (10,10) | (0,0)
| (0,0) | (-10,0)
- | (-3,4) | (-10,0)
- | (5.1,34.5) | (-10,0)
- | (-5,-12) | (-10,0)
- | (10,10) | (-10,0)
| (0,0) | (-3,4)
- | (-10,0) | (-3,4)
- | (5.1,34.5) | (-3,4)
- | (-5,-12) | (-3,4)
- | (10,10) | (-3,4)
| (0,0) | (5.1,34.5)
- | (-10,0) | (5.1,34.5)
- | (-3,4) | (5.1,34.5)
- | (-5,-12) | (5.1,34.5)
- | (10,10) | (5.1,34.5)
| (0,0) | (-5,-12)
- | (-10,0) | (-5,-12)
- | (-3,4) | (-5,-12)
- | (5.1,34.5) | (-5,-12)
- | (10,10) | (-5,-12)
| (0,0) | (10,10)
+ | (-10,0) | (0,0)
+ | (-10,0) | (-3,4)
+ | (-10,0) | (5.1,34.5)
+ | (-10,0) | (-5,-12)
| (-10,0) | (10,10)
+ | (-3,4) | (0,0)
+ | (-3,4) | (-10,0)
+ | (-3,4) | (5.1,34.5)
+ | (-3,4) | (-5,-12)
| (-3,4) | (10,10)
+ | (5.1,34.5) | (0,0)
+ | (5.1,34.5) | (-10,0)
+ | (5.1,34.5) | (-3,4)
+ | (5.1,34.5) | (-5,-12)
| (5.1,34.5) | (10,10)
+ | (-5,-12) | (0,0)
+ | (-5,-12) | (-10,0)
+ | (-5,-12) | (-3,4)
+ | (-5,-12) | (5.1,34.5)
| (-5,-12) | (10,10)
+ | (10,10) | (0,0)
+ | (10,10) | (-10,0)
+ | (10,10) | (-3,4)
+ | (10,10) | (5.1,34.5)
+ | (10,10) | (-5,-12)
(30 rows)
-- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
4
(1 row)
+--
+-- Simple test of qualified ON INSERT ... this did not work in 7.0 ...
+--
+create table foo (f1 int);
+create table foo2 (f1 int);
+create rule foorule as on insert to foo where f1 < 100
+do instead nothing;
+insert into foo values(1);
+insert into foo values(1001);
+select * from foo;
+ f1
+------
+ 1001
+(1 row)
+
+drop rule foorule;
+-- this should fail because f1 is not exposed for unqualified reference:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (f1);
+ERROR: Attribute 'f1' not found
+-- this is the correct way:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (new.f1);
+insert into foo values(2);
+insert into foo values(100);
+select * from foo;
+ f1
+------
+ 1001
+ 100
+(2 rows)
+
+select * from foo2;
+ f1
+----
+ 2
+(1 row)
+
+drop rule foorule;
+drop table foo;
+drop table foo2;
--
-- Check that ruleutils are working
--
rtest_order1 | rtest_order_r1 | CREATE RULE rtest_order_r1 AS ON INSERT TO rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 1 - this should run 3rd or 4th'::text);
rtest_order1 | rtest_order_r2 | CREATE RULE rtest_order_r2 AS ON INSERT TO rtest_order1 DO INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 2 - this should run 1st'::text);
rtest_order1 | rtest_order_r3 | CREATE RULE rtest_order_r3 AS ON INSERT TO rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 3 - this should run 3rd or 4th'::text);
- rtest_order1 | rtest_order_r4 | CREATE RULE rtest_order_r4 AS ON INSERT TO rtest_order1 WHERE (rtest_order2.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 4 - this should run 2nd'::text);
+ rtest_order1 | rtest_order_r4 | CREATE RULE rtest_order_r4 AS ON INSERT TO rtest_order1 WHERE (new.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 4 - this should run 2nd'::text);
rtest_person | rtest_pers_del | CREATE RULE rtest_pers_del AS ON DELETE TO rtest_person DO DELETE FROM rtest_admin WHERE (rtest_admin.pname = old.pname);
rtest_person | rtest_pers_upd | CREATE RULE rtest_pers_upd AS ON UPDATE TO rtest_person DO UPDATE rtest_admin SET pname = new.pname WHERE (rtest_admin.pname = old.pname);
rtest_system | rtest_sys_del | CREATE RULE rtest_sys_del AS ON DELETE TO rtest_system DO (DELETE FROM rtest_interface WHERE (rtest_interface.sysname = old.sysname); DELETE FROM rtest_admin WHERE (rtest_admin.sysname = old.sysname); );
SELECT count(*) FROM test_missing_target x, test_missing_target y
WHERE x.a = y.a
GROUP BY b ORDER BY b;
-ERROR: Column 'b' is ambiguous
+ERROR: Column reference "b" is ambiguous
-- order w/ target under ambiguous condition
-- failure NOT expected
SELECT a, a FROM test_missing_target
SELECT count(x.a) FROM test_missing_target x, test_missing_target y
WHERE x.a = y.a
GROUP BY b/2 ORDER BY b/2;
-ERROR: Column 'b' is ambiguous
+ERROR: Column reference "b" is ambiguous
-- group w/ existing GROUP BY target under ambiguous condition
SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
WHERE x.a = y.a
SELECT count(b) FROM test_missing_target x, test_missing_target y
WHERE x.a = y.a
GROUP BY x.b/2;
-ERROR: Column 'b' is ambiguous
+ERROR: Column reference "b" is ambiguous
-- group w/o existing GROUP BY target under ambiguous condition
-- into a table
SELECT count(x.b) INTO TABLE test_missing_target3
--
-- JOIN
--- Test join clauses
+-- Test JOIN clauses
--
CREATE TABLE J1_TBL (
INSERT INTO J2_TBL VALUES (2, 2);
INSERT INTO J2_TBL VALUES (3, -3);
INSERT INTO J2_TBL VALUES (2, 4);
+INSERT INTO J2_TBL VALUES (5, -5);
--
-- CORRELATION NAMES
FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))
AS tx (ii, jj, tt, ii2, kk);
+SELECT '' AS "xxx", *
+ FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+
--
--
SELECT '' AS "xxx", *
FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
-SELECT '' AS "xxx", *
- FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
-
-- mismatch number of columns
-- currently, Postgres will fill in with underlying names
SELECT '' AS "xxx", *
SELECT '' AS "xxx", *
FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
-SELECT '' AS "xxx", *
- FROM J1_TBL CROSS JOIN J2_TBL;
-
--
-- Non-equi-joins
-- Outer joins
--
-SELECT '' AS "xxx", *
- FROM J1_TBL OUTER JOIN J2_TBL USING (i);
-
SELECT '' AS "xxx", *
FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i);
SELECT '' AS "xxx", *
FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
+-- Note that OUTER is a noise word
SELECT '' AS "xxx", *
- FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i);
+ FROM J1_TBL FULL JOIN J2_TBL USING (i);
--
-- More complicated constructs
--
+-- UNION JOIN isn't implemented yet
+SELECT '' AS "xxx", *
+ FROM J1_TBL UNION JOIN J2_TBL;
+
--
-- Clean up
--
SELECT count(*) FROM shoe;
+--
+-- Simple test of qualified ON INSERT ... this did not work in 7.0 ...
+--
+create table foo (f1 int);
+create table foo2 (f1 int);
+
+create rule foorule as on insert to foo where f1 < 100
+do instead nothing;
+
+insert into foo values(1);
+insert into foo values(1001);
+select * from foo;
+
+drop rule foorule;
+
+-- this should fail because f1 is not exposed for unqualified reference:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (f1);
+-- this is the correct way:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (new.f1);
+
+insert into foo values(2);
+insert into foo values(100);
+
+select * from foo;
+select * from foo2;
+
+drop rule foorule;
+drop table foo;
+drop table foo2;
+
+
--
-- Check that ruleutils are working
--