aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2012-08-07 19:02:54 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2012-08-07 19:02:54 -0400
commit5ebaaa49445eb1ba7b299bbea3a477d4e4c0430b (patch)
tree1a72c939a655e9acbba1c71a1831dd38ee41db95 /src/backend/parser
parent5078be480412790e4f1b2aeda04f8c65fc7a3b93 (diff)
downloadpostgresql-5ebaaa49445eb1ba7b299bbea3a477d4e4c0430b.tar.gz
postgresql-5ebaaa49445eb1ba7b299bbea3a477d4e4c0430b.zip
Implement SQL-standard LATERAL subqueries.
This patch implements the standard syntax of LATERAL attached to a sub-SELECT in FROM, and also allows LATERAL attached to a function in FROM, since set-returning function calls are expected to be one of the principal use-cases. The main change here is a rewrite of the mechanism for keeping track of which relations are visible for column references while the FROM clause is being scanned. The parser "namespace" lists are no longer lists of bare RTEs, but are lists of ParseNamespaceItem structs, which carry an RTE pointer as well as some visibility-controlling flags. Aside from supporting LATERAL correctly, this lets us get rid of the ancient hacks that required rechecking subqueries and JOIN/ON and function-in-FROM expressions for invalid references after they were initially parsed. Invalid column references are now always correctly detected on sight. In passing, remove assorted parser error checks that are now dead code by virtue of our having gotten rid of add_missing_from, as well as some comments that are obsolete for the same reason. (It was mainly add_missing_from that caused so much fudging here in the first place.) The planner support for this feature is very minimal, and will be improved in future patches. It works well enough for testing purposes, though. catversion bump forced due to new field in RangeTblEntry.
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c72
-rw-r--r--src/backend/parser/gram.y160
-rw-r--r--src/backend/parser/parse_agg.c10
-rw-r--r--src/backend/parser/parse_clause.c284
-rw-r--r--src/backend/parser/parse_expr.c14
-rw-r--r--src/backend/parser/parse_relation.c223
-rw-r--r--src/backend/parser/parse_target.c6
7 files changed, 438 insertions, 331 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 263edb5a7a6..1a112cd9a4e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -533,6 +533,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
rte = addRangeTableEntryForSubquery(pstate,
selectQuery,
makeAlias("*SELECT*", NIL),
+ false,
false);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
@@ -652,18 +653,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
collations = lappend_oid(collations, InvalidOid);
/*
- * There mustn't have been any table references in the expressions,
- * else strange things would happen, like Cartesian products of those
- * tables with the VALUES list ...
- */
- if (pstate->p_joinlist != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VALUES must not contain table references"),
- parser_errposition(pstate,
- locate_var_of_level((Node *) exprsLists, 0))));
-
- /*
* Another thing we can't currently support is NEW/OLD references in
* rules --- seems we'd need something like SQL99's LATERAL construct
* to ensure that the values would be available while evaluating the
@@ -1067,7 +1056,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
List **colexprs = NULL;
int sublist_length = -1;
RangeTblEntry *rte;
- RangeTblRef *rtr;
+ int rtindex;
ListCell *lc;
ListCell *lc2;
int i;
@@ -1215,19 +1204,17 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
NULL, true);
- rtr = makeNode(RangeTblRef);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
/* assume new rte is at end */
- rtr->rtindex = list_length(pstate->p_rtable);
- Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
- pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
- pstate->p_relnamespace = lappend(pstate->p_relnamespace, rte);
- pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
+ rtindex = list_length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
/*
* Generate a targetlist as though expanding "*"
*/
Assert(pstate->p_next_resno == 1);
- qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0, -1);
+ qry->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
/*
* The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
@@ -1250,19 +1237,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
/*
- * There mustn't have been any table references in the expressions, else
- * strange things would happen, like Cartesian products of those tables
- * with the VALUES list. We have to check this after parsing ORDER BY et
- * al since those could insert more junk.
- */
- if (list_length(pstate->p_joinlist) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VALUES must not contain table references"),
- parser_errposition(pstate,
- locate_var_of_level((Node *) exprsLists, 0))));
-
- /*
* Another thing we can't currently support is NEW/OLD references in rules
* --- seems we'd need something like SQL99's LATERAL construct to ensure
* that the values would be available while evaluating the VALUES RTE.
@@ -1477,10 +1451,12 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
false);
sv_relnamespace = pstate->p_relnamespace;
- pstate->p_relnamespace = NIL; /* no qualified names allowed */
-
sv_varnamespace = pstate->p_varnamespace;
- pstate->p_varnamespace = list_make1(jrte);
+ pstate->p_relnamespace = NIL;
+ pstate->p_varnamespace = NIL;
+
+ /* add jrte to varnamespace only */
+ addRTEtoQuery(pstate, jrte, false, false, true);
/*
* For now, we don't support resjunk sort clauses on the output of a
@@ -1577,7 +1553,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
/*
* If an internal node of a set-op tree has ORDER BY, LIMIT, FOR UPDATE,
* or WITH clauses attached, we need to treat it like a leaf node to
- * generate an independent sub-Query tree. Otherwise, it can be
+ * generate an independent sub-Query tree. Otherwise, it can be
* represented by a SetOperationStmt node underneath the parent Query.
*/
if (stmt->op == SETOP_NONE)
@@ -1652,6 +1628,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
rte = addRangeTableEntryForSubquery(pstate,
selectQuery,
makeAlias(selectName, NIL),
+ false,
false);
/*
@@ -2074,7 +2051,6 @@ transformReturningList(ParseState *pstate, List *returningList)
int save_next_resno;
bool save_hasAggs;
bool save_hasWindowFuncs;
- int length_rtable;
if (returningList == NIL)
return NIL; /* nothing to do */
@@ -2092,7 +2068,6 @@ transformReturningList(ParseState *pstate, List *returningList)
pstate->p_hasAggs = false;
save_hasWindowFuncs = pstate->p_hasWindowFuncs;
pstate->p_hasWindowFuncs = false;
- length_rtable = list_length(pstate->p_rtable);
/* transform RETURNING identically to a SELECT targetlist */
rlist = transformTargetList(pstate, returningList);
@@ -2113,25 +2088,6 @@ transformReturningList(ParseState *pstate, List *returningList)
parser_errposition(pstate,
locate_windowfunc((Node *) rlist))));
- /* no new relation references please */
- if (list_length(pstate->p_rtable) != length_rtable)
- {
- int vlocation = -1;
- int relid;
-
- /* try to locate such a reference to point to */
- for (relid = length_rtable + 1; relid <= list_length(pstate->p_rtable); relid++)
- {
- vlocation = locate_var_of_relation((Node *) rlist, relid, 0);
- if (vlocation >= 0)
- break;
- }
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("RETURNING cannot contain references to other relations"),
- parser_errposition(pstate, vlocation)));
- }
-
/* mark column origins */
markTargetListOrigins(pstate, rlist);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6b6901197db..90ea1f9f004 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -396,7 +396,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <node> ctext_expr
%type <value> NumericOnly
%type <list> NumericOnly_list
-%type <alias> alias_clause
+%type <alias> alias_clause opt_alias_clause
+%type <list> func_alias_clause
%type <sortby> sortby
%type <ielem> index_elem
%type <node> table_ref
@@ -532,9 +533,9 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
KEY
- LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING LEAKPROOF
- LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
- LOCATION LOCK_P
+ LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LC_COLLATE_P LC_CTYPE_P
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -9309,65 +9310,37 @@ from_list:
;
/*
- * 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 is where an alias clause can be attached.
*/
-table_ref: relation_expr
- {
- $$ = (Node *) $1;
- }
- | relation_expr alias_clause
+table_ref: relation_expr opt_alias_clause
{
$1->alias = $2;
$$ = (Node *) $1;
}
- | func_table
- {
- RangeFunction *n = makeNode(RangeFunction);
- n->funccallnode = $1;
- n->coldeflist = NIL;
- $$ = (Node *) n;
- }
- | func_table alias_clause
- {
- RangeFunction *n = makeNode(RangeFunction);
- n->funccallnode = $1;
- n->alias = $2;
- n->coldeflist = NIL;
- $$ = (Node *) n;
- }
- | func_table AS '(' TableFuncElementList ')'
- {
- RangeFunction *n = makeNode(RangeFunction);
- n->funccallnode = $1;
- n->coldeflist = $4;
- $$ = (Node *) n;
- }
- | func_table AS ColId '(' TableFuncElementList ')'
+ | func_table func_alias_clause
{
RangeFunction *n = makeNode(RangeFunction);
- Alias *a = makeNode(Alias);
+ n->lateral = false;
n->funccallnode = $1;
- a->aliasname = $3;
- n->alias = a;
- n->coldeflist = $5;
+ n->alias = linitial($2);
+ n->coldeflist = lsecond($2);
$$ = (Node *) n;
}
- | func_table ColId '(' TableFuncElementList ')'
+ | LATERAL_P func_table func_alias_clause
{
RangeFunction *n = makeNode(RangeFunction);
- Alias *a = makeNode(Alias);
- n->funccallnode = $1;
- a->aliasname = $2;
- n->alias = a;
- n->coldeflist = $4;
+ n->lateral = true;
+ n->funccallnode = $2;
+ n->alias = linitial($3);
+ n->coldeflist = lsecond($3);
$$ = (Node *) n;
}
- | select_with_parens
+ | select_with_parens opt_alias_clause
{
+ RangeSubselect *n = makeNode(RangeSubselect);
+ n->lateral = false;
+ n->subquery = $1;
+ n->alias = $2;
/*
* The SQL spec does not permit a subselect
* (<derived_table>) without an alias clause,
@@ -9379,26 +9352,47 @@ table_ref: relation_expr
* However, it does seem like a good idea to emit
* an error message that's better than "syntax error".
*/
- if (IsA($1, SelectStmt) &&
- ((SelectStmt *) $1)->valuesLists)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("VALUES in FROM must have an alias"),
- errhint("For example, FROM (VALUES ...) [AS] foo."),
- parser_errposition(@1)));
- else
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("subquery in FROM must have an alias"),
- errhint("For example, FROM (SELECT ...) [AS] foo."),
- parser_errposition(@1)));
- $$ = NULL;
+ if ($2 == NULL)
+ {
+ if (IsA($1, SelectStmt) &&
+ ((SelectStmt *) $1)->valuesLists)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("VALUES in FROM must have an alias"),
+ errhint("For example, FROM (VALUES ...) [AS] foo."),
+ parser_errposition(@1)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery in FROM must have an alias"),
+ errhint("For example, FROM (SELECT ...) [AS] foo."),
+ parser_errposition(@1)));
+ }
+ $$ = (Node *) n;
}
- | select_with_parens alias_clause
+ | LATERAL_P select_with_parens opt_alias_clause
{
RangeSubselect *n = makeNode(RangeSubselect);
- n->subquery = $1;
- n->alias = $2;
+ n->lateral = true;
+ n->subquery = $2;
+ n->alias = $3;
+ /* same coment as above */
+ if ($3 == NULL)
+ {
+ if (IsA($2, SelectStmt) &&
+ ((SelectStmt *) $2)->valuesLists)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("VALUES in FROM must have an alias"),
+ errhint("For example, FROM (VALUES ...) [AS] foo."),
+ parser_errposition(@2)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("subquery in FROM must have an alias"),
+ errhint("For example, FROM (SELECT ...) [AS] foo."),
+ parser_errposition(@2)));
+ }
$$ = (Node *) n;
}
| joined_table
@@ -9524,6 +9518,41 @@ alias_clause:
}
;
+opt_alias_clause: alias_clause { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+/*
+ * func_alias_clause can include both an Alias and a coldeflist, so we make it
+ * return a 2-element list that gets disassembled by calling production.
+ */
+func_alias_clause:
+ alias_clause
+ {
+ $$ = list_make2($1, NIL);
+ }
+ | AS '(' TableFuncElementList ')'
+ {
+ $$ = list_make2(NULL, $3);
+ }
+ | AS ColId '(' TableFuncElementList ')'
+ {
+ Alias *a = makeNode(Alias);
+ a->aliasname = $2;
+ $$ = list_make2(a, $4);
+ }
+ | ColId '(' TableFuncElementList ')'
+ {
+ Alias *a = makeNode(Alias);
+ a->aliasname = $1;
+ $$ = list_make2(a, $3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = list_make2(NULL, NIL);
+ }
+ ;
+
join_type: FULL join_outer { $$ = JOIN_FULL; }
| LEFT join_outer { $$ = JOIN_LEFT; }
| RIGHT join_outer { $$ = JOIN_RIGHT; }
@@ -12736,6 +12765,7 @@ reserved_keyword:
| INITIALLY
| INTERSECT
| INTO
+ | LATERAL_P
| LEADING
| LIMIT
| LOCALTIME
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 380d9d35605..5854f81005d 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -181,6 +181,16 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
while (min_varlevel-- > 0)
pstate = pstate->parentParseState;
pstate->p_hasAggs = true;
+
+ /*
+ * Complain if we are inside a LATERAL subquery of the aggregation query.
+ * We must be in its FROM clause, so the aggregate is misplaced.
+ */
+ if (pstate->p_lateral_active)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("aggregates not allowed in FROM clause"),
+ parser_errposition(pstate, agg->location)));
}
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 97ab9d5581a..f9faa11b2e9 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -58,8 +58,7 @@ static Node *transformJoinUsingClause(ParseState *pstate,
static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
RangeTblEntry *l_rte,
RangeTblEntry *r_rte,
- List *relnamespace,
- Relids containedRels);
+ List *relnamespace);
static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
static RangeTblEntry *transformCTEReference(ParseState *pstate, RangeVar *r,
CommonTableExpr *cte, Index levelsup);
@@ -69,10 +68,13 @@ static RangeTblEntry *transformRangeFunction(ParseState *pstate,
RangeFunction *r);
static Node *transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry **top_rte, int *top_rti,
- List **relnamespace,
- Relids *containedRels);
+ List **relnamespace);
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
Var *l_colvar, Var *r_colvar);
+static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
+ bool lateral_only, bool lateral_ok);
+static void setNamespaceLateralState(List *namespace,
+ bool lateral_only, bool lateral_ok);
static void checkExprIsVarFree(ParseState *pstate, Node *n,
const char *constructName);
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
@@ -101,11 +103,6 @@ static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
* p_varnamespace lists were initialized to NIL when the pstate was created.
* We will add onto any entries already present --- this is needed for rule
* processing, as well as for UPDATE and DELETE.
- *
- * The range table may grow still further when we transform the expressions
- * in the query's quals and target list. (This is possible because in
- * POSTQUEL, we allowed references to relations not specified in the
- * from-clause. PostgreSQL keeps this extension to standard SQL.)
*/
void
transformFromClause(ParseState *pstate, List *frmList)
@@ -117,6 +114,9 @@ transformFromClause(ParseState *pstate, List *frmList)
* RangeFunctions, and/or JoinExprs. Transform each one (possibly adding
* entries to the rtable), check for duplicate refnames, and then add it
* to the joinlist and namespaces.
+ *
+ * Note we must process the items left-to-right for proper handling of
+ * LATERAL references.
*/
foreach(fl, frmList)
{
@@ -124,20 +124,31 @@ transformFromClause(ParseState *pstate, List *frmList)
RangeTblEntry *rte;
int rtindex;
List *relnamespace;
- Relids containedRels;
n = transformFromClauseItem(pstate, n,
&rte,
&rtindex,
- &relnamespace,
- &containedRels);
+ &relnamespace);
+ /* Mark the new relnamespace items as visible to LATERAL */
+ setNamespaceLateralState(relnamespace, true, true);
+
checkNameSpaceConflicts(pstate, pstate->p_relnamespace, relnamespace);
+
pstate->p_joinlist = lappend(pstate->p_joinlist, n);
pstate->p_relnamespace = list_concat(pstate->p_relnamespace,
relnamespace);
- pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
- bms_free(containedRels);
+ pstate->p_varnamespace = lappend(pstate->p_varnamespace,
+ makeNamespaceItem(rte, true, true));
}
+
+ /*
+ * We're done parsing the FROM list, so make all namespace items
+ * unconditionally visible. Note that this will also reset lateral_only
+ * for any namespace items that were already present when we were called;
+ * but those should have been that way already.
+ */
+ setNamespaceLateralState(pstate->p_relnamespace, false, true);
+ setNamespaceLateralState(pstate->p_varnamespace, false, true);
}
/*
@@ -375,55 +386,34 @@ static Node *
transformJoinOnClause(ParseState *pstate, JoinExpr *j,
RangeTblEntry *l_rte,
RangeTblEntry *r_rte,
- List *relnamespace,
- Relids containedRels)
+ List *relnamespace)
{
Node *result;
List *save_relnamespace;
List *save_varnamespace;
- Relids clause_varnos;
- int varno;
/*
- * This is a tad tricky, for two reasons. First, the namespace that the
- * join expression should see is just the two subtrees of the JOIN plus
- * any outer references from upper pstate levels. So, temporarily set
- * this pstate's namespace accordingly. (We need not check for refname
- * conflicts, because transformFromClauseItem() already did.) NOTE: this
- * code is OK only because the ON clause can't legally alter the namespace
- * by causing implicit relation refs to be added.
+ * The namespace that the join expression should see is just the two
+ * subtrees of the JOIN plus any outer references from upper pstate
+ * levels. Temporarily set this pstate's namespace accordingly. (We need
+ * not check for refname conflicts, because transformFromClauseItem()
+ * already did.) All namespace items are marked visible regardless of
+ * LATERAL state.
*/
save_relnamespace = pstate->p_relnamespace;
save_varnamespace = pstate->p_varnamespace;
+ setNamespaceLateralState(relnamespace, false, true);
pstate->p_relnamespace = relnamespace;
- pstate->p_varnamespace = list_make2(l_rte, r_rte);
+
+ pstate->p_varnamespace = list_make2(makeNamespaceItem(l_rte, false, true),
+ makeNamespaceItem(r_rte, false, true));
result = transformWhereClause(pstate, j->quals, "JOIN/ON");
pstate->p_relnamespace = save_relnamespace;
pstate->p_varnamespace = save_varnamespace;
- /*
- * Second, we need to check that the ON condition doesn't refer to any
- * rels outside the input subtrees of the JOIN. It could do that despite
- * our hack on the namespace if it uses fully-qualified names. So, grovel
- * through the transformed clause and make sure there are no bogus
- * references. (Outer references are OK, and are ignored here.)
- */
- clause_varnos = pull_varnos(result);
- clause_varnos = bms_del_members(clause_varnos, containedRels);
- if ((varno = bms_first_member(clause_varnos)) >= 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("JOIN/ON clause refers to \"%s\", which is not part of JOIN",
- rt_fetch(varno, pstate->p_rtable)->eref->aliasname),
- parser_errposition(pstate,
- locate_var_of_relation(result, varno, 0))));
- }
- bms_free(clause_varnos);
-
return result;
}
@@ -435,13 +425,7 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
{
RangeTblEntry *rte;
- /*
- * 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.
- */
+ /* We need only build a range table entry */
rte = addRangeTableEntry(pstate, r, r->alias,
interpretInhOption(r->inhOpt), true);
@@ -476,17 +460,28 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
* We require user to supply an alias for a subselect, per SQL92. To relax
* this, we'd have to be prepared to gin up a unique alias for an
* unlabeled subselect. (This is just elog, not ereport, because the
- * grammar should have enforced it already.)
+ * grammar should have enforced it already. It'd probably be better to
+ * report the error here, but we don't have a good error location here.)
*/
if (r->alias == NULL)
elog(ERROR, "subquery in FROM must have an alias");
/*
+ * If the subselect is LATERAL, make lateral_only names of this level
+ * visible to it. (LATERAL can't nest within a single pstate level, so we
+ * don't need save/restore logic here.)
+ */
+ Assert(!pstate->p_lateral_active);
+ pstate->p_lateral_active = r->lateral;
+
+ /*
* Analyze and transform the subquery.
*/
query = parse_sub_analyze(r->subquery, pstate, NULL,
isLockedRefname(pstate, r->alias->aliasname));
+ pstate->p_lateral_active = false;
+
/*
* Check that we got something reasonable. Many of these conditions are
* impossible given restrictions of the grammar, but check 'em anyway.
@@ -497,32 +492,13 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
elog(ERROR, "unexpected non-SELECT command in subquery in FROM");
/*
- * The subquery cannot make use of any variables from FROM items created
- * earlier in the current query. Per SQL92, the scope of a FROM item does
- * not include other FROM items. Formerly we hacked the namespace so that
- * the other variables weren't even visible, but it seems more useful to
- * leave them visible and give a specific error message.
- *
- * XXX this will need further work to support SQL99's LATERAL() feature,
- * wherein such references would indeed be legal.
- *
- * We can skip groveling through the subquery if there's not anything
- * visible in the current query. Also note that outer references are OK.
- */
- if (pstate->p_relnamespace || pstate->p_varnamespace)
- {
- if (contain_vars_of_level((Node *) query, 1))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("subquery in FROM cannot refer to other relations of same query level"),
- parser_errposition(pstate,
- locate_var_of_level((Node *) query, 1))));
- }
-
- /*
* OK, build an RTE for the subquery.
*/
- rte = addRangeTableEntryForSubquery(pstate, query, r->alias, true);
+ rte = addRangeTableEntryForSubquery(pstate,
+ query,
+ r->alias,
+ r->lateral,
+ true);
return rte;
}
@@ -547,35 +523,26 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
funcname = FigureColname(r->funccallnode);
/*
+ * If the function is LATERAL, make lateral_only names of this level
+ * visible to it. (LATERAL can't nest within a single pstate level, so we
+ * don't need save/restore logic here.)
+ */
+ Assert(!pstate->p_lateral_active);
+ pstate->p_lateral_active = r->lateral;
+
+ /*
* Transform the raw expression.
*/
funcexpr = transformExpr(pstate, r->funccallnode);
+ pstate->p_lateral_active = false;
+
/*
* We must assign collations now so that we can fill funccolcollations.
*/
assign_expr_collations(pstate, funcexpr);
/*
- * The function parameters cannot make use of any variables from other
- * FROM items. (Compare to transformRangeSubselect(); the coding is
- * different though because we didn't parse as a sub-select with its own
- * level of namespace.)
- *
- * XXX this will need further work to support SQL99's LATERAL() feature,
- * wherein such references would indeed be legal.
- */
- if (pstate->p_relnamespace || pstate->p_varnamespace)
- {
- if (contain_vars_of_level(funcexpr, 0))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("function expression in FROM cannot refer to other relations of same query level"),
- parser_errposition(pstate,
- locate_var_of_level(funcexpr, 0))));
- }
-
- /*
* Disallow aggregate functions in the expression. (No reason to postpone
* this check until parseCheckAggregates.)
*/
@@ -598,7 +565,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
* OK, build an RTE for the function.
*/
rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
- r, true);
+ r, r->lateral, true);
/*
* If a coldeflist was supplied, ensure it defines a legal set of names
@@ -637,12 +604,9 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
*
* *top_rti: receives the rangetable index of top_rte. (Ditto.)
*
- * *relnamespace: receives a List of the RTEs exposed as relation names
- * by this item.
- *
- * *containedRels: receives a bitmap set of the rangetable indexes
- * of all the base and join relations represented in this jointree item.
- * This is needed for checking JOIN/ON conditions in higher levels.
+ * *relnamespace: receives a List of ParseNamespaceItems for the RTEs exposed
+ * as relation names by this item. (The lateral_only flags in these items
+ * are indeterminate and should be explicitly set by the caller before use.)
*
* We do not need to pass back an explicit varnamespace value, because
* in all cases the varnamespace contribution is exactly top_rte.
@@ -650,8 +614,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
static Node *
transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry **top_rte, int *top_rti,
- List **relnamespace,
- Relids *containedRels)
+ List **relnamespace)
{
if (IsA(n, RangeVar))
{
@@ -681,8 +644,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *relnamespace = list_make1(rte);
- *containedRels = bms_make_singleton(rtindex);
+ *relnamespace = list_make1(makeNamespaceItem(rte, false, true));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -700,8 +662,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *relnamespace = list_make1(rte);
- *containedRels = bms_make_singleton(rtindex);
+ *relnamespace = list_make1(makeNamespaceItem(rte, false, true));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -719,8 +680,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *relnamespace = list_make1(rte);
- *containedRels = bms_make_singleton(rtindex);
+ *relnamespace = list_make1(makeNamespaceItem(rte, false, true));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -733,9 +693,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry *r_rte;
int l_rtindex;
int r_rtindex;
- Relids l_containedRels,
- r_containedRels,
- my_containedRels;
List *l_relnamespace,
*r_relnamespace,
*my_relnamespace,
@@ -745,38 +702,66 @@ transformFromClauseItem(ParseState *pstate, Node *n,
*l_colvars,
*r_colvars,
*res_colvars;
+ bool lateral_ok;
+ int sv_relnamespace_length,
+ sv_varnamespace_length;
RangeTblEntry *rte;
int k;
/*
- * Recursively process the left and right subtrees
+ * Recursively process the left subtree, then the right. We must do
+ * it in this order for correct visibility of LATERAL references.
*/
j->larg = transformFromClauseItem(pstate, j->larg,
&l_rte,
&l_rtindex,
- &l_relnamespace,
- &l_containedRels);
+ &l_relnamespace);
+
+ /*
+ * Make the left-side RTEs available for LATERAL access within the
+ * right side, by temporarily adding them to the pstate's namespace
+ * lists. Per SQL:2008, if the join type is not INNER or LEFT then
+ * the left-side names must still be exposed, but it's an error to
+ * reference them. (Stupid design, but that's what it says.) Hence,
+ * we always push them into the namespaces, but mark them as not
+ * lateral_ok if the jointype is wrong.
+ *
+ * NB: this coding relies on the fact that list_concat is not
+ * destructive to its second argument.
+ */
+ lateral_ok = (j->jointype == JOIN_INNER || j->jointype == JOIN_LEFT);
+ setNamespaceLateralState(l_relnamespace, true, lateral_ok);
+ checkNameSpaceConflicts(pstate, pstate->p_relnamespace, l_relnamespace);
+ sv_relnamespace_length = list_length(pstate->p_relnamespace);
+ pstate->p_relnamespace = list_concat(pstate->p_relnamespace,
+ l_relnamespace);
+ sv_varnamespace_length = list_length(pstate->p_varnamespace);
+ pstate->p_varnamespace = lappend(pstate->p_varnamespace,
+ makeNamespaceItem(l_rte, true, lateral_ok));
+
+ /* And now we can process the RHS */
j->rarg = transformFromClauseItem(pstate, j->rarg,
&r_rte,
&r_rtindex,
- &r_relnamespace,
- &r_containedRels);
+ &r_relnamespace);
+
+ /* Remove the left-side RTEs from the namespace lists again */
+ pstate->p_relnamespace = list_truncate(pstate->p_relnamespace,
+ sv_relnamespace_length);
+ pstate->p_varnamespace = list_truncate(pstate->p_varnamespace,
+ sv_varnamespace_length);
/*
* Check for conflicting refnames in left and right subtrees. Must do
* this because higher levels will assume I hand back a self-
- * consistent namespace subtree.
+ * consistent namespace list.
*/
checkNameSpaceConflicts(pstate, l_relnamespace, r_relnamespace);
/*
- * Generate combined relation membership info for possible use by
- * transformJoinOnClause below.
+ * Generate combined relnamespace info for possible use below.
*/
my_relnamespace = list_concat(l_relnamespace, r_relnamespace);
- my_containedRels = bms_join(l_containedRels, r_containedRels);
-
- pfree(r_relnamespace); /* free unneeded list header */
/*
* Extract column name and var lists from both subtrees
@@ -941,8 +926,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
/* User-written ON-condition; transform it */
j->quals = transformJoinOnClause(pstate, j,
l_rte, r_rte,
- my_relnamespace,
- my_containedRels);
+ my_relnamespace);
}
else
{
@@ -1006,18 +990,10 @@ transformFromClauseItem(ParseState *pstate, Node *n,
* relnamespace.
*/
if (j->alias)
- {
- *relnamespace = list_make1(rte);
- list_free(my_relnamespace);
- }
+ *relnamespace = list_make1(makeNamespaceItem(rte, false, true));
else
*relnamespace = my_relnamespace;
- /*
- * Include join RTE in returned containedRels set
- */
- *containedRels = bms_add_member(my_containedRels, j->rtindex);
-
return (Node *) j;
}
else
@@ -1144,6 +1120,40 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype,
return res_node;
}
+/*
+ * makeNamespaceItem -
+ * Convenience subroutine to construct a ParseNamespaceItem.
+ */
+static ParseNamespaceItem *
+makeNamespaceItem(RangeTblEntry *rte, bool lateral_only, bool lateral_ok)
+{
+ ParseNamespaceItem *nsitem;
+
+ nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+ nsitem->p_rte = rte;
+ nsitem->p_lateral_only = lateral_only;
+ nsitem->p_lateral_ok = lateral_ok;
+ return nsitem;
+}
+
+/*
+ * setNamespaceLateralState -
+ * Convenience subroutine to update LATERAL flags in a namespace list.
+ */
+static void
+setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok)
+{
+ ListCell *lc;
+
+ foreach(lc, namespace)
+ {
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
+
+ nsitem->p_lateral_only = lateral_only;
+ nsitem->p_lateral_ok = lateral_ok;
+ }
+}
+
/*
* transformWhereClause -
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bb1ad9af96b..385f8e767e4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -751,19 +751,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
switch (crerr)
{
case CRERR_NO_COLUMN:
- if (relname)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %s.%s does not exist",
- relname, colname),
- parser_errposition(pstate, cref->location)));
-
- else
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" does not exist",
- colname),
- parser_errposition(pstate, cref->location)));
+ errorMissingColumn(pstate, relname, colname, cref->location);
break;
case CRERR_NO_RTE:
errorMissingRTE(pstate, makeRangeVar(nspname, relname,
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 30b307b191c..47686c8719c 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -137,7 +137,12 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
foreach(l, pstate->p_relnamespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
+
+ /* If not inside LATERAL, ignore lateral-only items */
+ if (nsitem->p_lateral_only && !pstate->p_lateral_active)
+ continue;
if (strcmp(rte->eref->aliasname, refname) == 0)
{
@@ -147,6 +152,14 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
errmsg("table reference \"%s\" is ambiguous",
refname),
parser_errposition(pstate, location)));
+ /* SQL:2008 demands this be an error, not an invisible item */
+ if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+ refname),
+ errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+ parser_errposition(pstate, location)));
result = rte;
}
}
@@ -170,7 +183,12 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location)
foreach(l, pstate->p_relnamespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
+
+ /* If not inside LATERAL, ignore lateral-only items */
+ if (nsitem->p_lateral_only && !pstate->p_lateral_active)
+ continue;
/* yes, the test for alias == NULL should be there... */
if (rte->rtekind == RTE_RELATION &&
@@ -183,6 +201,14 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location)
errmsg("table reference %u is ambiguous",
relid),
parser_errposition(pstate, location)));
+ /* SQL:2008 demands this be an error, not an invisible item */
+ if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+ rte->eref->aliasname),
+ errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+ parser_errposition(pstate, location)));
result = rte;
}
}
@@ -245,7 +271,7 @@ isFutureCTE(ParseState *pstate, const char *refname)
}
/*
- * searchRangeTable
+ * searchRangeTableForRel
* See if any RangeTblEntry could possibly match the RangeVar.
* If so, return a pointer to the RangeTblEntry; else return NULL.
*
@@ -260,7 +286,7 @@ isFutureCTE(ParseState *pstate, const char *refname)
* and matches on alias.
*/
static RangeTblEntry *
-searchRangeTable(ParseState *pstate, RangeVar *relation)
+searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
{
const char *refname = relation->relname;
Oid relId = InvalidOid;
@@ -322,6 +348,9 @@ searchRangeTable(ParseState *pstate, RangeVar *relation)
* Per SQL92, two alias-less plain relation RTEs do not conflict even if
* they have the same eref->aliasname (ie, same relation name), if they
* are for different relation OIDs (implying they are in different schemas).
+ *
+ * We ignore the lateral-only flags in the namespace items: the lists must
+ * not conflict, even when all items are considered visible.
*/
void
checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
@@ -331,13 +360,15 @@ checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
foreach(l1, namespace1)
{
- RangeTblEntry *rte1 = (RangeTblEntry *) lfirst(l1);
+ ParseNamespaceItem *nsitem1 = (ParseNamespaceItem *) lfirst(l1);
+ RangeTblEntry *rte1 = nsitem1->p_rte;
const char *aliasname1 = rte1->eref->aliasname;
ListCell *l2;
foreach(l2, namespace2)
{
- RangeTblEntry *rte2 = (RangeTblEntry *) lfirst(l2);
+ ParseNamespaceItem *nsitem2 = (ParseNamespaceItem *) lfirst(l2);
+ RangeTblEntry *rte2 = nsitem2->p_rte;
if (strcmp(rte2->eref->aliasname, aliasname1) != 0)
continue; /* definitely no conflict */
@@ -544,9 +575,14 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
foreach(l, pstate->p_varnamespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
Node *newresult;
+ /* If not inside LATERAL, ignore lateral-only items */
+ if (nsitem->p_lateral_only && !pstate->p_lateral_active)
+ continue;
+
/* use orig_pstate here to get the right sublevels_up */
newresult = scanRTEForColumn(orig_pstate, rte, colname, location);
@@ -558,6 +594,14 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
errmsg("column reference \"%s\" is ambiguous",
colname),
parser_errposition(orig_pstate, location)));
+ /* SQL:2008 demands this be an error, not an invisible item */
+ if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+ rte->eref->aliasname),
+ errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+ parser_errposition(orig_pstate, location)));
result = newresult;
}
}
@@ -572,6 +616,40 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
}
/*
+ * searchRangeTableForCol
+ * See if any RangeTblEntry could possibly provide the given column name.
+ * If so, return a pointer to the RangeTblEntry; else return NULL.
+ *
+ * This is different from colNameToVar in that it considers every entry in
+ * the ParseState's rangetable(s), not only those that are currently visible
+ * in the p_varnamespace lists. This behavior is invalid per the SQL spec,
+ * and it may give ambiguous results (there might be multiple equally valid
+ * matches, but only one will be returned). This must be used ONLY as a
+ * heuristic in giving suitable error messages. See errorMissingColumn.
+ */
+static RangeTblEntry *
+searchRangeTableForCol(ParseState *pstate, char *colname, int location)
+{
+ ParseState *orig_pstate = pstate;
+
+ while (pstate != NULL)
+ {
+ ListCell *l;
+
+ foreach(l, pstate->p_rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+
+ if (scanRTEForColumn(orig_pstate, rte, colname, location))
+ return rte;
+ }
+
+ pstate = pstate->parentParseState;
+ }
+ return NULL;
+}
+
+/*
* markRTEForSelectPriv
* Mark the specified column of an RTE as requiring SELECT privilege
*
@@ -917,16 +995,13 @@ addRangeTableEntry(ParseState *pstate,
*/
heap_close(rel, NoLock);
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
- *----------
*/
+ rte->lateral = false;
rte->inh = inh;
rte->inFromCl = inFromCl;
@@ -973,16 +1048,13 @@ addRangeTableEntryForRelation(ParseState *pstate,
rte->eref = makeAlias(refname, NIL);
buildRelationAliases(rel->rd_att, alias, rte->eref);
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
- *----------
*/
+ rte->lateral = false;
rte->inh = inh;
rte->inFromCl = inFromCl;
@@ -1011,6 +1083,7 @@ RangeTblEntry *
addRangeTableEntryForSubquery(ParseState *pstate,
Query *subquery,
Alias *alias,
+ bool lateral,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
@@ -1054,15 +1127,12 @@ addRangeTableEntryForSubquery(ParseState *pstate,
rte->eref = eref;
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
* Subqueries are never checked for access rights.
- *----------
*/
+ rte->lateral = lateral;
rte->inh = false; /* never true for subqueries */
rte->inFromCl = inFromCl;
@@ -1091,6 +1161,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
char *funcname,
Node *funcexpr,
RangeFunction *rangefunc,
+ bool lateral,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
@@ -1192,16 +1263,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
funcname, format_type_be(funcrettype)),
parser_errposition(pstate, exprLocation(funcexpr))));
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
- * Functions are never checked for access rights (at least, not by
- * the RTE permissions mechanism).
- *----------
+ * Functions are never checked for access rights (at least, not by the RTE
+ * permissions mechanism).
*/
+ rte->lateral = lateral;
rte->inh = false; /* never true for functions */
rte->inFromCl = inFromCl;
@@ -1267,15 +1335,12 @@ addRangeTableEntryForValues(ParseState *pstate,
rte->eref = eref;
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
* Subqueries are never checked for access rights.
- *----------
*/
+ rte->lateral = false;
rte->inh = false; /* never true for values RTEs */
rte->inFromCl = inFromCl;
@@ -1338,15 +1403,12 @@ addRangeTableEntryForJoin(ParseState *pstate,
rte->eref = eref;
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
* Joins are never checked for access rights.
- *----------
*/
+ rte->lateral = false;
rte->inh = false; /* never true for joins */
rte->inFromCl = inFromCl;
@@ -1441,15 +1503,12 @@ addRangeTableEntryForCTE(ParseState *pstate,
rte->eref = eref;
- /*----------
- * Flags:
- * - this RTE should be expanded to include descendant tables,
- * - this RTE is in the FROM clause,
- * - this RTE should be checked for appropriate access rights.
+ /*
+ * Set flags and access permissions.
*
* Subqueries are never checked for access rights.
- *----------
*/
+ rte->lateral = false;
rte->inh = false; /* never true for subqueries */
rte->inFromCl = inFromCl;
@@ -1519,7 +1578,8 @@ isLockedRefname(ParseState *pstate, const char *refname)
/*
* Add the given RTE as a top-level entry in the pstate's join list
* and/or name space lists. (We assume caller has checked for any
- * namespace conflicts.)
+ * namespace conflicts.) The RTE is always marked as unconditionally
+ * visible, that is, not LATERAL-only.
*/
void
addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
@@ -1534,10 +1594,19 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
rtr->rtindex = rtindex;
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
}
- if (addToRelNameSpace)
- pstate->p_relnamespace = lappend(pstate->p_relnamespace, rte);
- if (addToVarNameSpace)
- pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
+ if (addToRelNameSpace || addToVarNameSpace)
+ {
+ ParseNamespaceItem *nsitem;
+
+ nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+ nsitem->p_rte = rte;
+ nsitem->p_lateral_only = false;
+ nsitem->p_lateral_ok = true;
+ if (addToRelNameSpace)
+ pstate->p_relnamespace = lappend(pstate->p_relnamespace, nsitem);
+ if (addToVarNameSpace)
+ pstate->p_varnamespace = lappend(pstate->p_varnamespace, nsitem);
+ }
}
/*
@@ -2453,7 +2522,7 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
* rangetable. (Note: cases involving a bad schema name in the RangeVar
* will throw error immediately here. That seems OK.)
*/
- rte = searchRangeTable(pstate, relation);
+ rte = searchRangeTableForRel(pstate, relation);
/*
* If we found a match that has an alias and the alias is visible in the
@@ -2490,3 +2559,43 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
relation->relname),
parser_errposition(pstate, relation->location)));
}
+
+/*
+ * Generate a suitable error about a missing column.
+ *
+ * Since this is a very common type of error, we work rather hard to
+ * produce a helpful message.
+ */
+void
+errorMissingColumn(ParseState *pstate,
+ char *relname, char *colname, int location)
+{
+ RangeTblEntry *rte;
+
+ /*
+ * If relname was given, just play dumb and report it. (In practice, a
+ * bad qualification name should end up at errorMissingRTE, not here, so
+ * no need to work hard on this case.)
+ */
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %s.%s does not exist", relname, colname),
+ parser_errposition(pstate, location)));
+
+ /*
+ * Otherwise, search the entire rtable looking for possible matches. If
+ * we find one, emit a hint about it.
+ *
+ * TODO: improve this code (and also errorMissingRTE) to mention using
+ * LATERAL if appropriate.
+ */
+ rte = searchRangeTableForCol(pstate, colname, location);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist", colname),
+ rte ? errhint("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this part of the query.",
+ colname, rte->eref->aliasname) : 0,
+ parser_errposition(pstate, location)));
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3850a3bc646..4d9e6e61066 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1129,9 +1129,13 @@ ExpandAllTables(ParseState *pstate, int location)
foreach(l, pstate->p_varnamespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
int rtindex = RTERangeTablePosn(pstate, rte, NULL);
+ /* Should not have any lateral-only items when parsing targetlist */
+ Assert(!nsitem->p_lateral_only);
+
target = list_concat(target,
expandRelAttrs(pstate, rte, rtindex, 0,
location));