aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
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));