summaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/Makefile23
-rw-r--r--src/backend/parser/analyze.c674
-rw-r--r--src/backend/parser/check_keywords.pl235
-rw-r--r--src/backend/parser/gram.y2251
-rw-r--r--src/backend/parser/keywords.c2
-rw-r--r--src/backend/parser/kwlookup.c4
-rw-r--r--src/backend/parser/parse_agg.c1006
-rw-r--r--src/backend/parser/parse_clause.c840
-rw-r--r--src/backend/parser/parse_coerce.c57
-rw-r--r--src/backend/parser/parse_collate.c462
-rw-r--r--src/backend/parser/parse_cte.c25
-rw-r--r--src/backend/parser/parse_expr.c422
-rw-r--r--src/backend/parser/parse_func.c500
-rw-r--r--src/backend/parser/parse_node.c40
-rw-r--r--src/backend/parser/parse_oper.c19
-rw-r--r--src/backend/parser/parse_param.c40
-rw-r--r--src/backend/parser/parse_relation.c1120
-rw-r--r--src/backend/parser/parse_target.c158
-rw-r--r--src/backend/parser/parse_type.c139
-rw-r--r--src/backend/parser/parse_utilcmd.c364
-rw-r--r--src/backend/parser/parser.c9
-rw-r--r--src/backend/parser/scan.l112
-rw-r--r--src/backend/parser/scansup.c12
23 files changed, 5881 insertions, 2633 deletions
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 00e8e88e9c..0395bd5934 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -17,8 +17,6 @@ OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_expr.o parse_func.o parse_node.o parse_oper.o parse_param.o \
parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o
-FLEXFLAGS = -CF -b -p -p
-
include $(top_srcdir)/src/backend/common.mk
@@ -40,21 +38,12 @@ endif
gram.h: gram.c ;
-gram.c: gram.y
-ifdef BISON
- $(BISON) -d $(BISONFLAGS) -o $@ $<
-else
- @$(missing) bison $< $@
-endif
+gram.c: BISONFLAGS += -d
+gram.c: BISON_CHECK_CMD = $(PERL) $(srcdir)/check_keywords.pl $< $(top_srcdir)/src/include/parser/kwlist.h
-scan.c: scan.l
-ifdef FLEX
- $(FLEX) $(FLEXFLAGS) -o'$@' $<
- @if [ `wc -l <lex.backup` -eq 1 ]; then rm lex.backup; else echo "Scanner requires backup, see lex.backup."; exit 1; fi
-else
- @$(missing) flex $< $@
-endif
+scan.c: FLEXFLAGS = -CF -p -p
+scan.c: FLEX_NO_BACKUP=yes
# Force these dependencies to be known even without dependency info built:
@@ -65,7 +54,3 @@ gram.o keywords.o parser.o: gram.h
# are not cleaned here.
clean distclean maintainer-clean:
rm -f lex.backup
-
-
-maintainer-check:
- $(PERL) $(top_srcdir)/src/tools/check_keywords.pl $(top_srcdir)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c7befc1ba4..2dbf7f6619 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -19,7 +19,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Portions Copyright (c) 2012-2014, TransLattice, Inc.
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/backend/parser/analyze.c
@@ -43,6 +43,7 @@
#include "utils/tqual.h"
#endif
#include "catalog/pg_type.h"
+#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
@@ -112,6 +113,7 @@ static void transformLockingClause(ParseState *pstate, Query *qry,
#ifdef XCP
static void ParseAnalyze_rtable_walk(List *rtable);
+static void ParseAnalyze_substitute_func(FuncExpr *funcexpr);
#endif
/*
@@ -121,7 +123,7 @@ static void ParseAnalyze_rtable_walk(List *rtable);
* Optionally, information about $n parameter types can be supplied.
* References to $n indexes not defined by paramTypes[] are disallowed.
*
- * The result is a Query node. Optimizable statements require considerable
+ * The result is a Query node. Optimizable statements require considerable
* transformation, while utility-type statements are simply hung off
* a dummy CMD_UTILITY Query node.
*/
@@ -232,6 +234,7 @@ transformTopLevelStmt(ParseState *pstate, Node *parseTree)
ctas->query = parseTree;
ctas->into = stmt->intoClause;
+ ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = true;
/*
@@ -401,6 +404,7 @@ static Query *
transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
{
Query *qry = makeNode(Query);
+ ParseNamespaceItem *nsitem;
Node *qual;
qry->commandType = CMD_DELETE;
@@ -419,8 +423,16 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
true,
ACL_DELETE);
+ /* grab the namespace item made by setTargetTable */
+ nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
+
+ /* there's no DISTINCT in DELETE */
qry->distinctClause = NIL;
+ /* subqueries in USING cannot access the result relation */
+ nsitem->p_lateral_only = true;
+ nsitem->p_lateral_ok = false;
+
/*
* The USING clause is non-standard SQL syntax, and is equivalent in
* functionality to the FROM list that can be specified for UPDATE. The
@@ -429,7 +441,12 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
*/
transformFromClause(pstate, stmt->usingClause);
- qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
+ /* remaining clauses can reference the result relation normally */
+ nsitem->p_lateral_only = false;
+ nsitem->p_lateral_ok = true;
+
+ qual = transformWhereClause(pstate, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -439,8 +456,6 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
- if (pstate->p_hasWindowFuncs)
- parseCheckWindowFuncs(pstate, qry);
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs)
parseCheckAggregates(pstate, qry);
@@ -462,8 +477,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List *exprList = NIL;
bool isGeneralSelect;
List *sub_rtable;
- List *sub_relnamespace;
- List *sub_varnamespace;
+ List *sub_namespace;
List *icolumns;
List *attrnos;
RangeTblEntry *rte;
@@ -505,7 +519,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/*
* If a non-nil rangetable/namespace was passed in, and we are doing
* INSERT/SELECT, arrange to pass the rangetable/namespace down to the
- * SELECT. This can only happen if we are inside a CREATE RULE, and in
+ * SELECT. This can only happen if we are inside a CREATE RULE, and in
* that case we want the rule's OLD and NEW rtable entries to appear as
* part of the SELECT's rtable, not as outer references for it. (Kluge!)
* The SELECT's joinlist is not affected however. We must do this before
@@ -515,16 +529,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
{
sub_rtable = pstate->p_rtable;
pstate->p_rtable = NIL;
- sub_relnamespace = pstate->p_relnamespace;
- pstate->p_relnamespace = NIL;
- sub_varnamespace = pstate->p_varnamespace;
- pstate->p_varnamespace = NIL;
+ sub_namespace = pstate->p_namespace;
+ pstate->p_namespace = NIL;
}
else
{
sub_rtable = NIL; /* not used, but keep compiler quiet */
- sub_relnamespace = NIL;
- sub_varnamespace = NIL;
+ sub_namespace = NIL;
}
/*
@@ -579,8 +590,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
*/
sub_pstate->p_rtable = sub_rtable;
sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
- sub_pstate->p_relnamespace = sub_relnamespace;
- sub_pstate->p_varnamespace = sub_varnamespace;
+ sub_pstate->p_namespace = sub_namespace;
selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
@@ -599,6 +609,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
rte = addRangeTableEntryForSubquery(pstate,
selectQuery,
makeAlias("*SELECT*", NIL),
+ false,
false);
#ifdef PGXC
#ifndef XCP
@@ -676,6 +687,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List *exprsLists = NIL;
List *collations = NIL;
int sublist_length = -1;
+ bool lateral = false;
int i;
Assert(selectStmt->intoClause == NULL);
@@ -685,7 +697,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List *sublist = (List *) lfirst(lc);
/* Do basic expression transformation (same as a ROW() expr) */
- sublist = transformExpressionList(pstate, sublist);
+ sublist = transformExpressionList(pstate, sublist, EXPR_KIND_VALUES);
/*
* All the sublists must be the same length, *after*
@@ -715,7 +727,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* We must assign collations now because assign_query_collations
* doesn't process rangetable entries. We just assign all the
* collations independently in each row, and don't worry about
- * whether they are consistent vertically. The outer INSERT query
+ * whether they are consistent vertically. The outer INSERT query
* isn't going to care about the collations of the VALUES columns,
* so it's not worth the effort to identify a common collation for
* each one here. (But note this does have one user-visible
@@ -736,37 +748,20 @@ 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
- * VALUES RTE. This is a shame. FIXME
+ * Ordinarily there can't be any current-level Vars in the expression
+ * lists, because the namespace was empty ... but if we're inside
+ * CREATE RULE, then NEW/OLD references might appear. In that case we
+ * have to mark the VALUES RTE as LATERAL.
*/
if (list_length(pstate->p_rtable) != 1 &&
contain_vars_of_level((Node *) exprsLists, 0))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VALUES must not contain OLD or NEW references"),
- errhint("Use SELECT ... UNION ALL ... instead."),
- parser_errposition(pstate,
- locate_var_of_level((Node *) exprsLists, 0))));
+ lateral = true;
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
- NULL, true);
+ NULL, lateral, true);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
@@ -780,16 +775,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
}
else
{
- /*----------
- * Process INSERT ... VALUES with a single VALUES sublist.
- * We treat this separately for efficiency and for historical
- * compatibility --- specifically, allowing table references,
- * such as
- * INSERT INTO foo VALUES(bar.*)
- *
- * The sublist is just computed directly as the Query's targetlist,
- * with no VALUES RTE. So it works just like SELECT without FROM.
- *----------
+ /*
+ * Process INSERT ... VALUES with a single VALUES sublist. We treat
+ * this case separately for efficiency. The sublist is just computed
+ * directly as the Query's targetlist, with no VALUES RTE. So it
+ * works just like a SELECT without any FROM.
*/
List *valuesLists = selectStmt->valuesLists;
@@ -798,7 +788,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate,
- (List *) linitial(valuesLists));
+ (List *) linitial(valuesLists),
+ EXPR_KIND_VALUES);
/* Prepare row for assignment to target table */
exprList = transformInsertRow(pstate, exprList,
@@ -846,8 +837,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
*/
if (stmt->returningList)
{
- pstate->p_relnamespace = NIL;
- pstate->p_varnamespace = NIL;
+ pstate->p_namespace = NIL;
addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
false, true, true);
qry->returningList = transformReturningList(pstate,
@@ -859,19 +849,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
- /* aggregates not allowed (but subselects are okay) */
- if (pstate->p_hasAggs)
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in VALUES"),
- parser_errposition(pstate,
- locate_agg_of_level((Node *) qry, 0))));
- if (pstate->p_hasWindowFuncs)
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot use window function in VALUES"),
- parser_errposition(pstate,
- locate_windowfunc((Node *) qry))));
assign_query_collations(pstate, qry);
@@ -897,7 +874,7 @@ transformInsertRow(ParseState *pstate, List *exprlist,
* Check length of expr list. It must not have more expressions than
* there are target columns. We allow fewer, but only if no explicit
* columns list was given (the remaining columns are implicitly
- * defaulted). Note we must check this *after* transformation because
+ * defaulted). Note we must check this *after* transformation because
* that could expand '*' into multiple items.
*/
if (list_length(exprlist) > list_length(icolumns))
@@ -946,6 +923,7 @@ transformInsertRow(ParseState *pstate, List *exprlist,
Assert(IsA(col, ResTarget));
expr = transformAssignedExpr(pstate, expr,
+ EXPR_KIND_INSERT_TARGET,
col->name,
lfirst_int(attnos),
col->indirection,
@@ -966,7 +944,7 @@ transformInsertRow(ParseState *pstate, List *exprlist,
* return -1 if expression isn't a RowExpr or a Var referencing one.
*
* This is currently used only for hint purposes, so we aren't terribly
- * tense about recognizing all possible cases. The Var case is interesting
+ * tense about recognizing all possible cases. The Var case is interesting
* because that's what we'll get in the INSERT ... SELECT (...) case.
*/
static int
@@ -1046,19 +1024,19 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
transformFromClause(pstate, stmt->fromClause);
/* transform targetlist */
- qry->targetList = transformTargetList(pstate, stmt->targetList);
+ qry->targetList = transformTargetList(pstate, stmt->targetList,
+ EXPR_KIND_SELECT_TARGET);
/* mark column origins */
markTargetListOrigins(pstate, qry->targetList);
/* transform WHERE */
- qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
+ qual = transformWhereClause(pstate, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
- /*
- * Initial processing of HAVING clause is just like WHERE clause.
- */
+ /* initial processing of HAVING clause is much like WHERE clause */
qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
- "HAVING");
+ EXPR_KIND_HAVING, "HAVING");
/*
* Transform sorting/grouping stuff. Do ORDER BY first because both
@@ -1069,6 +1047,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->sortClause = transformSortClause(pstate,
stmt->sortClause,
&qry->targetList,
+ EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
false /* allow SQL92 rules */ );
@@ -1076,6 +1055,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
stmt->groupClause,
&qry->targetList,
qry->sortClause,
+ EXPR_KIND_GROUP_BY,
false /* allow SQL92 rules */ );
if (stmt->distinctClause == NIL)
@@ -1104,9 +1084,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
/* transform LIMIT */
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
- "OFFSET");
+ EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
- "LIMIT");
+ EXPR_KIND_LIMIT, "LIMIT");
/* transform window clauses after we have seen all window functions */
qry->windowClause = transformWindowDefinitions(pstate,
@@ -1118,8 +1098,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
- if (pstate->p_hasWindowFuncs)
- parseCheckWindowFuncs(pstate, qry);
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
@@ -1150,8 +1128,9 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
List *collations;
List **colexprs = NULL;
int sublist_length = -1;
+ bool lateral = false;
RangeTblEntry *rte;
- RangeTblRef *rtr;
+ int rtindex;
ListCell *lc;
ListCell *lc2;
int i;
@@ -1191,7 +1170,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
List *sublist = (List *) lfirst(lc);
/* Do basic expression transformation (same as a ROW() expr) */
- sublist = transformExpressionList(pstate, sublist);
+ sublist = transformExpressionList(pstate, sublist, EXPR_KIND_VALUES);
/*
* All the sublists must be the same length, *after* transformation
@@ -1295,23 +1274,31 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
}
/*
+ * Ordinarily there can't be any current-level Vars in the expression
+ * lists, because the namespace was empty ... but if we're inside CREATE
+ * RULE, then NEW/OLD references might appear. In that case we have to
+ * mark the VALUES RTE as LATERAL.
+ */
+ if (pstate->p_rtable != NIL &&
+ contain_vars_of_level((Node *) exprsLists, 0))
+ lateral = true;
+
+ /*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
- NULL, true);
- rtr = makeNode(RangeTblRef);
+ NULL, lateral, true);
+ 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
@@ -1320,64 +1307,28 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
qry->sortClause = transformSortClause(pstate,
stmt->sortClause,
&qry->targetList,
+ EXPR_KIND_ORDER_BY,
true /* fix unknowns */ ,
false /* allow SQL92 rules */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
- "OFFSET");
+ EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
- "LIMIT");
+ EXPR_KIND_LIMIT, "LIMIT");
if (stmt->lockingClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- 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.
- * This is a shame. FIXME
- */
- if (list_length(pstate->p_rtable) != 1 &&
- contain_vars_of_level((Node *) exprsLists, 0))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VALUES must not contain OLD or NEW references"),
- errhint("Use SELECT ... UNION ALL ... instead."),
- parser_errposition(pstate,
- locate_var_of_level((Node *) exprsLists, 0))));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to VALUES",
+ LCS_asString(((LockingClause *)
+ linitial(stmt->lockingClause))->strength))));
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
- /* aggregates not allowed (but subselects are okay) */
- if (pstate->p_hasAggs)
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in VALUES"),
- parser_errposition(pstate,
- locate_agg_of_level((Node *) exprsLists, 0))));
- if (pstate->p_hasWindowFuncs)
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot use window function in VALUES"),
- parser_errposition(pstate,
- locate_windowfunc((Node *) exprsLists))));
assign_query_collations(pstate, qry);
@@ -1406,6 +1357,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
Node *limitOffset;
Node *limitCount;
List *lockingClause;
+ WithClause *withClause;
Node *node;
ListCell *left_tlist,
*lct,
@@ -1414,22 +1366,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
*l;
List *targetvars,
*targetnames,
- *sv_relnamespace,
- *sv_varnamespace;
+ *sv_namespace;
int sv_rtable_length;
RangeTblEntry *jrte;
int tllen;
qry->commandType = CMD_SELECT;
- /* process the WITH clause independently of all else */
- if (stmt->withClause)
- {
- qry->hasRecursive = stmt->withClause->recursive;
- qry->cteList = transformWithClause(pstate, stmt->withClause);
- qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
- }
-
/*
* Find leftmost leaf SelectStmt. We currently only need to do this in
* order to deliver a suitable error message if there's an INTO clause
@@ -1459,17 +1402,31 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
limitOffset = stmt->limitOffset;
limitCount = stmt->limitCount;
lockingClause = stmt->lockingClause;
+ withClause = stmt->withClause;
stmt->sortClause = NIL;
stmt->limitOffset = NULL;
stmt->limitCount = NULL;
stmt->lockingClause = NIL;
+ stmt->withClause = NULL;
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (lockingClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
+ LCS_asString(((LockingClause *)
+ linitial(lockingClause))->strength))));
+
+ /* Process the WITH clause independently of all else */
+ if (withClause)
+ {
+ qry->hasRecursive = withClause->recursive;
+ qry->cteList = transformWithClause(pstate, withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
/*
* Recursively transform the components of the tree.
@@ -1540,8 +1497,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
/*
* As a first step towards supporting sort clauses that are expressions
- * using the output columns, generate a varnamespace entry that makes the
- * output columns visible. A Join RTE node is handy for this, since we
+ * using the output columns, generate a namespace entry that makes the
+ * output columns visible. A Join RTE node is handy for this, since we
* can easily control the Vars generated upon matches.
*
* Note: we don't yet do anything useful with such cases, but at least
@@ -1557,11 +1514,11 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
NULL,
false);
- sv_relnamespace = pstate->p_relnamespace;
- pstate->p_relnamespace = NIL; /* no qualified names allowed */
+ sv_namespace = pstate->p_namespace;
+ pstate->p_namespace = NIL;
- sv_varnamespace = pstate->p_varnamespace;
- pstate->p_varnamespace = list_make1(jrte);
+ /* add jrte to column namespace only */
+ addRTEtoQuery(pstate, jrte, false, false, true);
/*
* For now, we don't support resjunk sort clauses on the output of a
@@ -1574,12 +1531,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->sortClause = transformSortClause(pstate,
sortClause,
&qry->targetList,
+ EXPR_KIND_ORDER_BY,
false /* no unknowns expected */ ,
false /* allow SQL92 rules */ );
+ /* restore namespace, remove jrte from rtable */
+ pstate->p_namespace = sv_namespace;
pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
- pstate->p_relnamespace = sv_relnamespace;
- pstate->p_varnamespace = sv_varnamespace;
if (tllen != list_length(qry->targetList))
ereport(ERROR,
@@ -1591,17 +1549,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
exprLocation(list_nth(qry->targetList, tllen)))));
qry->limitOffset = transformLimitClause(pstate, limitOffset,
- "OFFSET");
+ EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, limitCount,
- "LIMIT");
+ EXPR_KIND_LIMIT, "LIMIT");
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
- if (pstate->p_hasWindowFuncs)
- parseCheckWindowFuncs(pstate, qry);
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
@@ -1622,7 +1578,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
* Recursively transform leaves and internal nodes of a set-op tree
*
* In addition to returning the transformed node, if targetlist isn't NULL
- * then we return a list of its non-resjunk TargetEntry nodes. For a leaf
+ * then we return a list of its non-resjunk TargetEntry nodes. For a leaf
* set-op node these are the actual targetlist entries; otherwise they are
* dummy entries created to carry the type, typmod, collation, and location
* (for error messages) of each output column of the set-op node. This info
@@ -1639,6 +1595,9 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
Assert(stmt && IsA(stmt, SelectStmt));
+ /* Guard against stack overflow due to overly complex set-expressions */
+ check_stack_depth();
+
/*
* Validity-check both leaf and internal SELECTs for disallowed ops.
*/
@@ -1653,13 +1612,17 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
if (stmt->lockingClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
+ LCS_asString(((LockingClause *)
+ linitial(stmt->lockingClause))->strength))));
/*
- * If an internal node of a set-op tree has ORDER BY, LIMIT, or FOR UPDATE
- * clauses attached, we need to treat it like a leaf node to generate an
- * independent sub-Query tree. Otherwise, it can be represented by a
- * SetOperationStmt node underneath the parent Query.
+ * 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
+ * represented by a SetOperationStmt node underneath the parent Query.
*/
if (stmt->op == SETOP_NONE)
{
@@ -1670,7 +1633,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
{
Assert(stmt->larg != NULL && stmt->rarg != NULL);
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
- stmt->lockingClause)
+ stmt->lockingClause || stmt->withClause)
isLeaf = true;
else
isLeaf = false;
@@ -1700,7 +1663,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
* because the namespace will be empty, but it could happen if we are
* inside a rule.
*/
- if (pstate->p_relnamespace || pstate->p_varnamespace)
+ if (pstate->p_namespace)
{
if (contain_vars_of_level((Node *) selectQuery, 1))
ereport(ERROR,
@@ -1733,6 +1696,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
rte = addRangeTableEntryForSubquery(pstate,
selectQuery,
makeAlias(selectName, NIL),
+ false,
false);
/*
@@ -1833,7 +1797,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
rescoltypmod = -1;
/*
- * Verify the coercions are actually possible. If not, we'd fail
+ * Verify the coercions are actually possible. If not, we'd fail
* later anyway, but we want to fail now while we have sufficient
* context to produce an error cursor position.
*
@@ -1842,7 +1806,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
* child query's semantics.
*
* If a child expression is an UNKNOWN-type Const or Param, we
- * want to replace it with the coerced expression. This can only
+ * want to replace it with the coerced expression. This can only
* happen when the child is a leaf set-op node. It's safe to
* replace the expression because if the child query's semantics
* depended on the type of this output column, it'd have already
@@ -2023,6 +1987,7 @@ static Query *
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
{
Query *qry = makeNode(Query);
+ ParseNamespaceItem *nsitem;
RangeTblEntry *target_rte;
Node *qual;
ListCell *origTargetList;
@@ -2044,15 +2009,28 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
true,
ACL_UPDATE);
+ /* grab the namespace item made by setTargetTable */
+ nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
+
+ /* subqueries in FROM cannot access the result relation */
+ nsitem->p_lateral_only = true;
+ nsitem->p_lateral_ok = false;
+
/*
* the FROM clause is non-standard SQL syntax. We used to be able to do
* this with REPLACE in POSTQUEL so we keep the feature.
*/
transformFromClause(pstate, stmt->fromClause);
- qry->targetList = transformTargetList(pstate, stmt->targetList);
+ /* remaining clauses can reference the result relation normally */
+ nsitem->p_lateral_only = false;
+ nsitem->p_lateral_ok = true;
+
+ qry->targetList = transformTargetList(pstate, stmt->targetList,
+ EXPR_KIND_UPDATE_SOURCE);
- qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
+ qual = transformWhereClause(pstate, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -2062,24 +2040,6 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks;
/*
- * Top-level aggregates are simply disallowed in UPDATE, per spec. (From
- * an implementation point of view, this is forced because the implicit
- * ctid reference would otherwise be an ungrouped variable.)
- */
- if (pstate->p_hasAggs)
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in UPDATE"),
- parser_errposition(pstate,
- locate_agg_of_level((Node *) qry, 0))));
- if (pstate->p_hasWindowFuncs)
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot use window function in UPDATE"),
- parser_errposition(pstate,
- locate_windowfunc((Node *) qry))));
-
- /*
* Now we are done with SELECT-like processing, and can get on with
* transforming the target list to match the UPDATE target columns.
*/
@@ -2153,9 +2113,6 @@ transformReturningList(ParseState *pstate, List *returningList)
{
List *rlist;
int save_next_resno;
- bool save_hasAggs;
- bool save_hasWindowFuncs;
- int length_rtable;
if (returningList == NIL)
return NIL; /* nothing to do */
@@ -2168,58 +2125,27 @@ transformReturningList(ParseState *pstate, List *returningList)
save_next_resno = pstate->p_next_resno;
pstate->p_next_resno = 1;
- /* save other state so that we can detect disallowed stuff */
- save_hasAggs = pstate->p_hasAggs;
- 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);
+ rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING);
- /* check for disallowed stuff */
-
- /* aggregates not allowed (but subselects are okay) */
- if (pstate->p_hasAggs)
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in RETURNING"),
- parser_errposition(pstate,
- locate_agg_of_level((Node *) rlist, 0))));
- if (pstate->p_hasWindowFuncs)
+ /*
+ * Complain if the nonempty tlist expanded to nothing (which is possible
+ * if it contains only a star-expansion of a zero-column table). If we
+ * allow this, the parsed Query will look like it didn't have RETURNING,
+ * with results that would probably surprise the user.
+ */
+ if (rlist == NIL)
ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot use window function in RETURNING"),
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RETURNING must have at least one column"),
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)));
- }
+ exprLocation(linitial(returningList)))));
/* mark column origins */
markTargetListOrigins(pstate, rlist);
/* restore state */
pstate->p_next_resno = save_next_resno;
- pstate->p_hasAggs = save_hasAggs;
- pstate->p_hasWindowFuncs = save_hasWindowFuncs;
return rlist;
}
@@ -2272,21 +2198,33 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("DECLARE CURSOR WITH HOLD ... %s is not supported",
+ LCS_asString(((RowMarkClause *)
+ linitial(result->rowMarks))->strength)),
errdetail("Holdable cursors must be READ ONLY.")));
/* FOR UPDATE and SCROLL are not compatible */
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("DECLARE SCROLL CURSOR ... %s is not supported",
+ LCS_asString(((RowMarkClause *)
+ linitial(result->rowMarks))->strength)),
errdetail("Scrollable cursors must be READ ONLY.")));
/* FOR UPDATE and INSENSITIVE are not compatible */
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DECLARE INSENSITIVE CURSOR ... FOR UPDATE/SHARE is not supported"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("DECLARE INSENSITIVE CURSOR ... %s is not supported",
+ LCS_asString(((RowMarkClause *)
+ linitial(result->rowMarks))->strength)),
errdetail("Insensitive cursors must be READ ONLY.")));
/* We won't need the raw querytree any more */
@@ -2327,7 +2265,8 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
/*
* transformCreateTableAsStmt -
- * transform a CREATE TABLE AS (or SELECT ... INTO) Statement
+ * transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW
+ * Statement
*
* As with EXPLAIN, transform the contained statement now.
*/
@@ -2335,9 +2274,65 @@ static Query *
transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
{
Query *result;
+ Query *query;
/* transform contained query */
- stmt->query = (Node *) transformStmt(pstate, stmt->query);
+ query = transformStmt(pstate, stmt->query);
+ stmt->query = (Node *) query;
+
+ /* additional work needed for CREATE MATERIALIZED VIEW */
+ if (stmt->relkind == OBJECT_MATVIEW)
+ {
+ /*
+ * Prohibit a data-modifying CTE in the query used to create a
+ * materialized view. It's not sufficiently clear what the user would
+ * want to happen if the MV is refreshed or incrementally maintained.
+ */
+ if (query->hasModifyingCTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use data-modifying statements in WITH")));
+
+ /*
+ * Check whether any temporary database objects are used in the
+ * creation query. It would be hard to refresh data or incrementally
+ * maintain it if a source disappeared.
+ */
+ if (isQueryUsingTempRelation(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use temporary tables or views")));
+
+ /*
+ * A materialized view would either need to save parameters for use in
+ * maintaining/loading the data or prohibit them entirely. The latter
+ * seems safer and more sane.
+ */
+ if (query_contains_extern_params(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views may not be defined using bound parameters")));
+
+ /*
+ * For now, we disallow unlogged materialized views, because it seems
+ * like a bad idea for them to just go to empty after a crash. (If we
+ * could mark them as unpopulated, that would be better, but that
+ * requires catalog changes which crash recovery can't presently
+ * handle.)
+ */
+ if (stmt->into->rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views cannot be UNLOGGED")));
+
+ /*
+ * At runtime, we'll need a copy of the parsed-but-not-rewritten Query
+ * for purposes of creating the view's ON SELECT rule. We stash that
+ * in the IntoClause because that's where intorel_startup() can
+ * conveniently get it from.
+ */
+ stmt->into->viewQuery = copyObject(query);
+ }
/* represent the command as a utility Query */
result = makeNode(Query);
@@ -2671,46 +2666,84 @@ is_rel_child_of_rel(RangeTblEntry *child_rte, RangeTblEntry *parent_rte)
#endif
#endif
+char *
+LCS_asString(LockClauseStrength strength)
+{
+ switch (strength)
+ {
+ case LCS_FORKEYSHARE:
+ return "FOR KEY SHARE";
+ case LCS_FORSHARE:
+ return "FOR SHARE";
+ case LCS_FORNOKEYUPDATE:
+ return "FOR NO KEY UPDATE";
+ case LCS_FORUPDATE:
+ return "FOR UPDATE";
+ }
+ return "FOR some"; /* shouldn't happen */
+}
+
/*
- * Check for features that are not supported together with FOR UPDATE/SHARE.
+ * Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
*
* exported so planner can check again after rewriting, query pullup, etc
*/
void
-CheckSelectLocking(Query *qry)
+CheckSelectLocking(Query *qry, LockClauseStrength strength)
{
if (qry->setOperations)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
+ LCS_asString(strength))));
if (qry->distinctClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with DISTINCT clause",
+ LCS_asString(strength))));
if (qry->groupClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with GROUP BY clause",
+ LCS_asString(strength))));
if (qry->havingQual != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with HAVING clause",
+ LCS_asString(strength))));
if (qry->hasAggs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with aggregate functions",
+ LCS_asString(strength))));
if (qry->hasWindowFuncs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with window functions")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with window functions",
+ LCS_asString(strength))));
if (expression_returns_set((Node *) qry->targetList))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not allowed with set-returning functions in the target list")));
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with set-returning functions in the target list",
+ LCS_asString(strength))));
}
/*
- * Transform a FOR UPDATE/SHARE clause
+ * Transform a FOR [KEY] UPDATE/SHARE clause
*
* This basically involves replacing names by integer relids.
*
@@ -2727,12 +2760,12 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
Index i;
LockingClause *allrels;
- CheckSelectLocking(qry);
+ CheckSelectLocking(qry, lc->strength);
/* make a clause we can pass down to subqueries to select all rels */
allrels = makeNode(LockingClause);
allrels->lockedRels = NIL; /* indicates all rels */
- allrels->forUpdate = lc->forUpdate;
+ allrels->strength = lc->strength;
allrels->noWait = lc->noWait;
if (lockedRels == NIL)
@@ -2747,16 +2780,13 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
- /* ignore foreign tables */
- if (rte->relkind == RELKIND_FOREIGN_TABLE)
- break;
applyLockingClause(qry, i,
- lc->forUpdate, lc->noWait, pushedDown);
+ lc->strength, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
- lc->forUpdate, lc->noWait, pushedDown);
+ lc->strength, lc->noWait, pushedDown);
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
@@ -2785,7 +2815,10 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
if (thisrel->catalogname || thisrel->schemaname)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("SELECT FOR UPDATE/SHARE must specify unqualified relation names"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s must specify unqualified relation names",
+ LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
i = 0;
@@ -2799,20 +2832,14 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
- if (rte->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be used with foreign table \"%s\"",
- rte->eref->aliasname),
- parser_errposition(pstate, thisrel->location)));
applyLockingClause(qry, i,
- lc->forUpdate, lc->noWait,
+ lc->strength, lc->noWait,
pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
- lc->forUpdate, lc->noWait,
+ lc->strength, lc->noWait,
pushedDown);
/* see comment above */
transformLockingClause(pstate, rte->subquery,
@@ -2821,25 +2848,37 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
case RTE_JOIN:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a join",
+ LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_FUNCTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a function",
+ LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_VALUES:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to VALUES",
+ LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_CTE:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a WITH query"),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a WITH query",
+ LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
break;
default:
@@ -2853,8 +2892,11 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
if (rt == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
- thisrel->relname),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("relation \"%s\" in %s clause not found in FROM clause",
+ thisrel->relname,
+ LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
}
}
@@ -2865,7 +2907,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
*/
void
applyLockingClause(Query *qry, Index rtindex,
- bool forUpdate, bool noWait, bool pushedDown)
+ LockClauseStrength strength, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
@@ -2877,10 +2919,10 @@ applyLockingClause(Query *qry, Index rtindex,
if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
{
/*
- * If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
- * it as FOR UPDATE. (Reasonable, since you can't take both a shared
- * and exclusive lock at the same time; it'll end up being exclusive
- * anyway.)
+ * If the same RTE is specified for more than one locking strength,
+ * treat is as the strongest. (Reasonable, since you can't take both
+ * a shared and exclusive lock at the same time; it'll end up being
+ * exclusive anyway.)
*
* We also consider that NOWAIT wins if it's specified both ways. This
* is a bit more debatable but raising an error doesn't seem helpful.
@@ -2889,7 +2931,7 @@ applyLockingClause(Query *qry, Index rtindex,
*
* And of course pushedDown becomes false if any clause is explicit.
*/
- rc->forUpdate |= forUpdate;
+ rc->strength = Max(rc->strength, strength);
rc->noWait |= noWait;
rc->pushedDown &= pushedDown;
return;
@@ -2898,7 +2940,7 @@ applyLockingClause(Query *qry, Index rtindex,
/* Make a new RowMarkClause */
rc = makeNode(RowMarkClause);
rc->rti = rtindex;
- rc->forUpdate = forUpdate;
+ rc->strength = strength;
rc->noWait = noWait;
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);
@@ -2924,59 +2966,69 @@ static void
ParseAnalyze_rtable_walk(List *rtable)
{
ListCell *item;
- StringInfoData buf;
if (!IsUnderPostmaster || superuser())
return;
- initStringInfo(&buf);
foreach(item, rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(item);
- resetStringInfo(&buf);
- if (rte->rtekind == RTE_FUNCTION &&
- get_func_namespace(((FuncExpr *) rte->funcexpr)->funcid) ==
- PG_CATALOG_NAMESPACE)
+ if (rte->rtekind == RTE_FUNCTION)
{
- Oid funcid = InvalidOid;
+ ListCell *lc;
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ ParseAnalyze_substitute_func((FuncExpr *) rtfunc->funcexpr);
+ }
+ }
+ else if (rte->rtekind == RTE_SUBQUERY) /* recurse for subqueries */
+ ParseAnalyze_rtable_walk(rte->subquery->rtable);
+ }
+}
- FuncExpr *funcexpr = (FuncExpr *) rte->funcexpr;
- const char *funcname = get_func_name(funcexpr->funcid);
+static void
+ParseAnalyze_substitute_func(FuncExpr *funcexpr)
+{
+ StringInfoData buf;
+ initStringInfo(&buf);
- /* Check if the funcname is in storm_catalog_remap_string */
- appendStringInfoString(&buf, funcname);
- appendStringInfoChar(&buf, ',');
+ if (get_func_namespace(funcexpr->funcid) == PG_CATALOG_NAMESPACE)
+ {
+ Oid funcid = InvalidOid;
+ const char *funcname = get_func_name(funcexpr->funcid);
- elog(DEBUG2, "the constructed name is %s", buf.data);
+ /* Check if the funcname is in storm_catalog_remap_string */
+ appendStringInfoString(&buf, funcname);
+ appendStringInfoChar(&buf, ',');
- /*
- * The unqualified function name should be satisfied from the
- * storm_catalog appropriately. Just provide a warning for now if
- * it is not..
- */
- if (strstr(storm_catalog_remap_string, buf.data))
- {
- Oid *argtypes = NULL;
- int nargs;
+ elog(DEBUG2, "the constructed name is %s", buf.data);
- get_func_signature(funcexpr->funcid, &argtypes, &nargs);
- funcid = get_funcid(funcname, buildoidvector(argtypes, nargs),
- STORM_CATALOG_NAMESPACE);
- }
- else
- continue;
+ /*
+ * The unqualified function name should be satisfied from the
+ * storm_catalog appropriately. Just provide a warning for now if
+ * it is not..
+ */
+ if (strstr(storm_catalog_remap_string, buf.data))
+ {
+ Oid *argtypes = NULL;
+ int nargs;
- if (get_func_namespace(funcid) != STORM_CATALOG_NAMESPACE)
- ereport(WARNING,
+ get_func_signature(funcexpr->funcid, &argtypes, &nargs);
+ funcid = get_funcid(funcname, buildoidvector(argtypes, nargs),
+ STORM_CATALOG_NAMESPACE);
+ }
+ else
+ return;
+
+ if (get_func_namespace(funcid) != STORM_CATALOG_NAMESPACE)
+ ereport(WARNING,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Entry (%s) present in storm_catalog_remap_string "
- "but object not picked from STORM_CATALOG", funcname)));
- else /* change the funcid to the storm_catalog one */
- funcexpr->funcid = funcid;
- }
- else if (rte->rtekind == RTE_SUBQUERY) /* recurse for subqueries */
- ParseAnalyze_rtable_walk(rte->subquery->rtable);
+ "but object not picked from STORM_CATALOG", funcname)));
+ else /* change the funcid to the storm_catalog one */
+ funcexpr->funcid = funcid;
}
}
#endif
diff --git a/src/backend/parser/check_keywords.pl b/src/backend/parser/check_keywords.pl
new file mode 100644
index 0000000000..aa9e0d001d
--- /dev/null
+++ b/src/backend/parser/check_keywords.pl
@@ -0,0 +1,235 @@
+#!/usr/bin/perl
+
+# Check that the keyword lists in gram.y and kwlist.h are sane.
+# Usage: check_keywords.pl gram.y kwlist.h
+
+# src/backend/parser/check_keywords.pl
+# Copyright (c) 2009-2014, PostgreSQL Global Development Group
+
+use warnings;
+use strict;
+
+my $gram_filename = $ARGV[0];
+my $kwlist_filename = $ARGV[1];
+
+my $errors = 0;
+
+sub error(@)
+{
+ print STDERR @_;
+ $errors = 1;
+}
+
+$, = ' '; # set output field separator
+$\ = "\n"; # set output record separator
+
+my %keyword_categories;
+$keyword_categories{'unreserved_keyword'} = 'UNRESERVED_KEYWORD';
+$keyword_categories{'col_name_keyword'} = 'COL_NAME_KEYWORD';
+$keyword_categories{'type_func_name_keyword'} = 'TYPE_FUNC_NAME_KEYWORD';
+$keyword_categories{'reserved_keyword'} = 'RESERVED_KEYWORD';
+
+open(GRAM, $gram_filename) || die("Could not open : $gram_filename");
+
+my ($S, $s, $k, $n, $kcat);
+my $comment;
+my @arr;
+my %keywords;
+
+line: while (<GRAM>)
+{
+ chomp; # strip record separator
+
+ $S = $_;
+
+ # Make sure any braces are split
+ $s = '{', $S =~ s/$s/ { /g;
+ $s = '}', $S =~ s/$s/ } /g;
+
+ # Any comments are split
+ $s = '[/][*]', $S =~ s#$s# /* #g;
+ $s = '[*][/]', $S =~ s#$s# */ #g;
+
+ if (!($kcat))
+ {
+
+ # Is this the beginning of a keyword list?
+ foreach $k (keys %keyword_categories)
+ {
+ if ($S =~ m/^($k):/)
+ {
+ $kcat = $k;
+ next line;
+ }
+ }
+ next line;
+ }
+
+ # Now split the line into individual fields
+ $n = (@arr = split(' ', $S));
+
+ # Ok, we're in a keyword list. Go through each field in turn
+ for (my $fieldIndexer = 0; $fieldIndexer < $n; $fieldIndexer++)
+ {
+ if ($arr[$fieldIndexer] eq '*/' && $comment)
+ {
+ $comment = 0;
+ next;
+ }
+ elsif ($comment)
+ {
+ next;
+ }
+ elsif ($arr[$fieldIndexer] eq '/*')
+ {
+
+ # start of a multiline comment
+ $comment = 1;
+ next;
+ }
+ elsif ($arr[$fieldIndexer] eq '//')
+ {
+ next line;
+ }
+
+ if ($arr[$fieldIndexer] eq ';')
+ {
+
+ # end of keyword list
+ $kcat = '';
+ next;
+ }
+
+ if ($arr[$fieldIndexer] eq '|')
+ {
+ next;
+ }
+
+ # Put this keyword into the right list
+ push @{ $keywords{$kcat} }, $arr[$fieldIndexer];
+ }
+}
+close GRAM;
+
+# Check that each keyword list is in alphabetical order (just for neatnik-ism)
+my ($prevkword, $kword, $bare_kword);
+foreach $kcat (keys %keyword_categories)
+{
+ $prevkword = '';
+
+ foreach $kword (@{ $keywords{$kcat} })
+ {
+
+ # Some keyword have a _P suffix. Remove it for the comparison.
+ $bare_kword = $kword;
+ $bare_kword =~ s/_P$//;
+ if ($bare_kword le $prevkword)
+ {
+ error
+ "'$bare_kword' after '$prevkword' in $kcat list is misplaced";
+ }
+ $prevkword = $bare_kword;
+ }
+}
+
+# Transform the keyword lists into hashes.
+# kwhashes is a hash of hashes, keyed by keyword category id,
+# e.g. UNRESERVED_KEYWORD.
+# Each inner hash is keyed by keyword id, e.g. ABORT_P, with a dummy value.
+my %kwhashes;
+while (my ($kcat, $kcat_id) = each(%keyword_categories))
+{
+ @arr = @{ $keywords{$kcat} };
+
+ my $hash;
+ foreach my $item (@arr) { $hash->{$item} = 1; }
+
+ $kwhashes{$kcat_id} = $hash;
+}
+
+# Now read in kwlist.h
+
+open(KWLIST, $kwlist_filename) || die("Could not open : $kwlist_filename");
+
+my $prevkwstring = '';
+my $bare_kwname;
+my %kwhash;
+kwlist_line: while (<KWLIST>)
+{
+ my ($line) = $_;
+
+ if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*)\)/)
+ {
+ my ($kwstring) = $1;
+ my ($kwname) = $2;
+ my ($kwcat_id) = $3;
+
+ # Check that the list is in alphabetical order (critical!)
+ if ($kwstring le $prevkwstring)
+ {
+ error
+ "'$kwstring' after '$prevkwstring' in kwlist.h is misplaced";
+ }
+ $prevkwstring = $kwstring;
+
+ # Check that the keyword string is valid: all lower-case ASCII chars
+ if ($kwstring !~ /^[a-z_]+$/)
+ {
+ error
+"'$kwstring' is not a valid keyword string, must be all lower-case ASCII chars";
+ }
+
+ # Check that the keyword name is valid: all upper-case ASCII chars
+ if ($kwname !~ /^[A-Z_]+$/)
+ {
+ error
+"'$kwname' is not a valid keyword name, must be all upper-case ASCII chars";
+ }
+
+ # Check that the keyword string matches keyword name
+ $bare_kwname = $kwname;
+ $bare_kwname =~ s/_P$//;
+ if ($bare_kwname ne uc($kwstring))
+ {
+ error
+"keyword name '$kwname' doesn't match keyword string '$kwstring'";
+ }
+
+ # Check that the keyword is present in the grammar
+ %kwhash = %{ $kwhashes{$kwcat_id} };
+
+ if (!(%kwhash))
+ {
+ error "Unknown keyword category: $kwcat_id";
+ }
+ else
+ {
+ if (!($kwhash{$kwname}))
+ {
+ error "'$kwname' not present in $kwcat_id section of gram.y";
+ }
+ else
+ {
+
+ # Remove it from the hash, so that we can
+ # complain at the end if there's keywords left
+ # that were not found in kwlist.h
+ delete $kwhashes{$kwcat_id}->{$kwname};
+ }
+ }
+ }
+}
+close KWLIST;
+
+# Check that we've paired up all keywords from gram.y with lines in kwlist.h
+while (my ($kwcat, $kwcat_id) = each(%keyword_categories))
+{
+ %kwhash = %{ $kwhashes{$kwcat_id} };
+
+ for my $kw (keys %kwhash)
+ {
+ error "'$kw' found in gram.y $kwcat category, but not in kwlist.h";
+ }
+}
+
+exit $errors;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d8d64c4d21..5931e2847e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6,7 +6,7 @@
* gram.y
* POSTGRESQL BISON rules/actions
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
*
@@ -22,8 +22,6 @@
* NOTES
* CAPITALS are used to represent terminal symbols.
* non-capitals are used to represent non-terminals.
- * SQL92-specific syntax is separated from plain SQL/Postgres syntax
- * to help isolate the non-extensible portions of the parser.
*
* In general, nothing in this file should initiate database accesses
* nor depend on changeable state (such as SET variables). If you do
@@ -57,6 +55,7 @@
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "miscadmin.h"
+#include "commands/trigger.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
@@ -70,16 +69,34 @@
#include "utils/xml.h"
-/* Location tracking support --- simpler than bison's default */
+/*
+ * Location tracking support --- simpler than bison's default, since we only
+ * want to track the start position not the end position of each nonterminal.
+ */
#define YYLLOC_DEFAULT(Current, Rhs, N) \
do { \
- if (N) \
+ if ((N) > 0) \
(Current) = (Rhs)[1]; \
else \
- (Current) = (Rhs)[0]; \
+ (Current) = (-1); \
} while (0)
/*
+ * The above macro assigns -1 (unknown) as the parse location of any
+ * nonterminal that was reduced from an empty rule. This is problematic
+ * for nonterminals defined like
+ * OptFooList: / * EMPTY * / { ... } | OptFooList Foo { ... } ;
+ * because we'll set -1 as the location during the first reduction and then
+ * copy it during each subsequent reduction, leaving us with -1 for the
+ * location even when the list is not empty. To fix that, do this in the
+ * action for the nonempty rule(s):
+ * if (@$ < 0) @$ = @2;
+ * (Although we have many nonterminals that follow this pattern, we only
+ * bother with fixing @$ like this when the nonterminal's parse location
+ * is actually referenced in some rule.)
+ */
+
+/*
* Bison doesn't allocate anything that needs to live across parser calls,
* so we can easily have it use palloc instead of malloc. This prevents
* memory leaks if we error out during parsing. Note this only works with
@@ -104,6 +121,7 @@ typedef struct PrivTarget
#define CAS_INITIALLY_IMMEDIATE 0x04
#define CAS_INITIALLY_DEFERRED 0x08
#define CAS_NOT_VALID 0x10
+#define CAS_NO_INHERIT 0x20
#define parser_yyerror(msg) scanner_yyerror(msg, yyscanner)
@@ -122,12 +140,13 @@ static Node *makeBitStringConst(char *str, int location);
static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
-static FuncCall *makeOverlaps(List *largs, List *rargs,
- int location, core_yyscan_t yyscanner);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
static List *extractArgTypes(List *parameters);
+static List *extractAggrArgTypes(List *aggrargs);
+static List *makeOrderedSetArgs(List *directargs, List *orderedargs,
+ core_yyscan_t yyscanner);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount,
@@ -147,7 +166,8 @@ static void SplitColQualList(List *qualList,
core_yyscan_t yyscanner);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
- core_yyscan_t yyscanner);
+ bool *no_inherit, core_yyscan_t yyscanner);
+static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -202,10 +222,11 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
}
%type <node> stmt schema_stmt
+ AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
- AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
- AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
+ AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
+ AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
@@ -215,7 +236,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
- CreateAssertStmt CreateTrigStmt
+ CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
@@ -236,6 +257,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
AlterTSConfigurationStmt AlterTSDictionaryStmt
BarrierStmt PauseStmt AlterNodeStmt CreateNodeStmt DropNodeStmt
CreateNodeGroupStmt DropNodeGroupStmt
+ CreateMatViewStmt RefreshMatViewStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -244,6 +266,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <ival> add_drop opt_asc_desc opt_nulls_order
%type <node> alter_table_cmd alter_type_cmd opt_collate_clause
+ replica_identity
%type <list> alter_table_cmds alter_type_cmds
%type <dbehavior> opt_drop_behavior
@@ -279,6 +302,10 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <value> TriggerFuncArg
%type <node> TriggerWhen
+%type <list> event_trigger_when_list event_trigger_value_list
+%type <defelt> event_trigger_when_item
+%type <chr> enable_trigger
+
%type <str> copy_file_name
database_name access_method_clause access_method attr_name
name cursor_name file_name
@@ -308,19 +335,19 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
- OptForeignTableElementList ForeignTableElementList
reloptions opt_reloptions
OptWith opt_distinct opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
+ aggr_args aggr_args_list
func_as createfunc_opt_list alterfunc_opt_list
- aggr_args old_aggr_definition old_aggr_list
+ old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
sort_clause opt_sort_clause sortby_list index_params
- name_list from_clause from_list opt_array_bounds
+ name_list role_list from_clause from_list opt_array_bounds
qualified_name_list any_name any_name_list
any_operator expr_list attrs
- target_list insert_column_list set_target_list
+ target_list opt_target_list insert_column_list set_target_list
set_clause_list set_clause multiple_set_clause
ctext_expr_list ctext_row def_list indirection opt_indirection
reloption_list group_clause TriggerFuncArgs select_limit
@@ -337,17 +364,19 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <defelt> fdw_option
%type <range> OptTempTableName
-%type <into> into_clause create_as_target
+%type <into> into_clause create_as_target create_mv_target
%type <defelt> createfunc_opt_item common_func_opt_item dostmt_opt_item
-%type <fun_param> func_arg func_arg_with_default table_func_column
+%type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
%type <fun_param_mode> arg_class
%type <typnam> func_return func_type
%type <boolean> opt_trusted opt_restart_seqs
%type <ival> OptTemp
+%type <ival> OptNoLog
%type <oncommit> OnCommitOption
+%type <ival> for_locking_strength
%type <node> for_locking_item
%type <list> for_locking_clause opt_for_locking_clause for_locking_items
%type <list> locked_rels_list
@@ -369,7 +398,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <str> DirectStmt CleanConnDbName CleanConnUserName
%type <boolean> OptCluster
/* PGXC_END */
-%type <boolean> copy_from
+%type <boolean> copy_from opt_program
%type <ival> opt_column event cursor_options opt_hold opt_set_data
%type <objtype> reindex_type drop_type comment_type security_label_type
@@ -384,16 +413,17 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <istmt> insert_rest
-%type <vsetstmt> set_rest set_rest_more SetResetClause FunctionSetResetClause
+%type <vsetstmt> generic_set set_rest set_rest_more SetResetClause FunctionSetResetClause
%type <node> TableElement TypedTableElement ConstraintElem TableFuncElement
- ForeignTableElement
%type <node> columnDef columnOptions
%type <defelt> def_elem reloption_elem old_aggr_elem
%type <node> def_arg columnElem where_clause where_or_current_clause
- a_expr b_expr c_expr func_expr AexprConst indirection_el
+ a_expr b_expr c_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
ExclusionWhereClause
+%type <list> rowsfrom_item rowsfrom_list opt_col_def_list
+%type <boolean> opt_ordinality
%type <list> ExclusionConstraintList ExclusionConstraintElem
%type <list> func_arg_list
%type <node> func_arg_expr
@@ -404,7 +434,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
@@ -439,9 +470,10 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <ival> Iconst SignedIconst
%type <str> Sconst comment_text notify_payload
-%type <str> RoleId opt_granted_by opt_boolean_or_string ColId_or_Sconst
+%type <str> RoleId opt_granted_by opt_boolean_or_string
%type <list> var_list
%type <str> ColId ColLabel var_name type_function_name param_name
+%type <str> NonReservedWord NonReservedWord_or_Sconst
%type <node> var_value zone_value
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -458,7 +490,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <list> constraints_set_list
%type <boolean> constraints_set_mode
%type <str> OptTableSpace OptConsTableSpace OptTableSpaceOwner
-%type <list> opt_check_option
+%type <ival> opt_check_option
%type <str> opt_provider security_label
@@ -469,10 +501,14 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <ival> document_or_content
%type <boolean> xml_whitespace_option
+%type <node> func_application func_expr_common_subexpr
+%type <node> func_expr func_expr_windowless
%type <node> common_table_expr
%type <with> with_clause opt_with_clause
%type <list> cte_list
+%type <list> within_group_clause
+%type <node> filter_clause
%type <list> window_clause window_definition_list opt_partition_clause
%type <windef> window_definition over_clause window_specification
opt_frame_clause frame_extent frame_bound
@@ -482,7 +518,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <distby> OptDistributeBy OptDistributeByInternal
%type <subclus> OptSubCluster OptSubClusterInternal
/* PGXC_END */
-
+%type <boolean> opt_if_not_exists
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -529,12 +565,12 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
/* PGXC_END */
DROP
- EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
+ EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
- FREEZE FROM FULL FUNCTION FUNCTIONS
+ FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
+ FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
@@ -549,25 +585,28 @@ 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
- MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+ 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 MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NODE NONE
- NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
+ NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
+ NULLS_P NUMERIC
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
- ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
+ ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
PARSER PARTIAL PARTITION PASSING PASSWORD PAUSE PLACING PLANS POSITION
/* PGXC_BEGIN */
PRECEDING PRECISION PREFERRED PRESERVE PREPARE PREPARED PRIMARY
/* PGXC_END */
- PRIOR PRIVILEGES PROCEDURAL PROCEDURE
+ PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
QUOTE
- RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
+ RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
ROW ROWS RULE
@@ -586,9 +625,9 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
UNPAUSE UNTIL UPDATE USER USING
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
- VERBOSE VERSION_P VIEW VOLATILE
+ VERBOSE VERSION_P VIEW VIEWS VOLATILE
- WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
+ WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
XMLPI XMLROOT XMLSERIALIZE
@@ -602,7 +641,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
* list and so can never be entered directly. The filter in parser.c
* creates these tokens when required.
*/
-%token NULLS_FIRST NULLS_LAST WITH_TIME
+%token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
/* Precedence: lowest to highest */
@@ -697,7 +736,8 @@ stmtmulti: stmtmulti ';' stmt
;
stmt :
- AlterDatabaseStmt
+ AlterEventTrigStmt
+ | AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
| AlterDomainStmt
@@ -713,7 +753,9 @@ stmt :
| AlterObjectSchemaStmt
| AlterOwnerStmt
| AlterSeqStmt
+ | AlterSystemStmt
| AlterTableStmt
+ | AlterTblSpcStmt
| AlterCompositeTypeStmt
| AlterRoleSetStmt
| AlterRoleStmt
@@ -742,6 +784,7 @@ stmt :
| CreateForeignTableStmt
| CreateFunctionStmt
| CreateGroupStmt
+ | CreateMatViewStmt
| CreateNodeGroupStmt
| CreateNodeStmt
| CreateOpClassStmt
@@ -753,6 +796,7 @@ stmt :
| CreateStmt
| CreateTableSpaceStmt
| CreateTrigStmt
+ | CreateEventTrigStmt
| CreateRoleStmt
| CreateUserStmt
| CreateUserMappingStmt
@@ -791,6 +835,7 @@ stmt :
| IndexStmt
| InsertStmt
| ListenStmt
+ | RefreshMatViewStmt
| LoadStmt
| LockStmt
| NotifyStmt
@@ -890,7 +935,7 @@ AlterOptRoleElem:
$$ = makeDefElem("validUntil", (Node *)makeString($3));
}
/* Supported but not documented for roles, for use by ALTER GROUP. */
- | USER name_list
+ | USER role_list
{
$$ = makeDefElem("rolemembers", (Node *)$2);
}
@@ -954,19 +999,19 @@ CreateOptRoleElem:
{
$$ = makeDefElem("sysid", (Node *)makeInteger($2));
}
- | ADMIN name_list
+ | ADMIN role_list
{
$$ = makeDefElem("adminmembers", (Node *)$2);
}
- | ROLE name_list
+ | ROLE role_list
{
$$ = makeDefElem("rolemembers", (Node *)$2);
}
- | IN_P ROLE name_list
+ | IN_P ROLE role_list
{
$$ = makeDefElem("addroleto", (Node *)$3);
}
- | IN_P GROUP_P name_list
+ | IN_P GROUP_P role_list
{
$$ = makeDefElem("addroleto", (Node *)$3);
}
@@ -1022,6 +1067,14 @@ AlterRoleSetStmt:
n->setstmt = $5;
$$ = (Node *)n;
}
+ | ALTER ROLE ALL opt_in_database SetResetClause
+ {
+ AlterRoleSetStmt *n = makeNode(AlterRoleSetStmt);
+ n->role = NULL;
+ n->database = $4;
+ n->setstmt = $5;
+ $$ = (Node *)n;
+ }
;
@@ -1065,14 +1118,14 @@ AlterUserSetStmt:
*****************************************************************************/
DropRoleStmt:
- DROP ROLE name_list
+ DROP ROLE role_list
{
DropRoleStmt *n = makeNode(DropRoleStmt);
n->missing_ok = FALSE;
n->roles = $3;
$$ = (Node *)n;
}
- | DROP ROLE IF_P EXISTS name_list
+ | DROP ROLE IF_P EXISTS role_list
{
DropRoleStmt *n = makeNode(DropRoleStmt);
n->missing_ok = TRUE;
@@ -1091,14 +1144,14 @@ DropRoleStmt:
*****************************************************************************/
DropUserStmt:
- DROP USER name_list
+ DROP USER role_list
{
DropRoleStmt *n = makeNode(DropRoleStmt);
n->missing_ok = FALSE;
n->roles = $3;
$$ = (Node *)n;
}
- | DROP USER IF_P EXISTS name_list
+ | DROP USER IF_P EXISTS role_list
{
DropRoleStmt *n = makeNode(DropRoleStmt);
n->roles = $5;
@@ -1133,7 +1186,7 @@ CreateGroupStmt:
*****************************************************************************/
AlterGroupStmt:
- ALTER GROUP_P RoleId add_drop USER name_list
+ ALTER GROUP_P RoleId add_drop USER role_list
{
AlterRoleStmt *n = makeNode(AlterRoleStmt);
n->role = $3;
@@ -1157,14 +1210,14 @@ add_drop: ADD_P { $$ = +1; }
*****************************************************************************/
DropGroupStmt:
- DROP GROUP_P name_list
+ DROP GROUP_P role_list
{
DropRoleStmt *n = makeNode(DropRoleStmt);
n->missing_ok = FALSE;
n->roles = $3;
$$ = (Node *)n;
}
- | DROP GROUP_P IF_P EXISTS name_list
+ | DROP GROUP_P IF_P EXISTS role_list
{
DropRoleStmt *n = makeNode(DropRoleStmt);
n->missing_ok = TRUE;
@@ -1191,6 +1244,7 @@ CreateSchemaStmt:
n->schemaname = $5;
n->authid = $5;
n->schemaElts = $6;
+ n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE SCHEMA ColId OptSchemaEltList
@@ -1200,6 +1254,40 @@ CreateSchemaStmt:
n->schemaname = $3;
n->authid = NULL;
n->schemaElts = $4;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE SCHEMA IF_P NOT EXISTS OptSchemaName AUTHORIZATION RoleId OptSchemaEltList
+ {
+ CreateSchemaStmt *n = makeNode(CreateSchemaStmt);
+ /* One can omit the schema name or the authorization id. */
+ if ($6 != NULL)
+ n->schemaname = $6;
+ else
+ n->schemaname = $8;
+ n->authid = $8;
+ if ($9 != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CREATE SCHEMA IF NOT EXISTS cannot include schema elements"),
+ parser_errposition(@9)));
+ n->schemaElts = $9;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
+ | CREATE SCHEMA IF_P NOT EXISTS ColId OptSchemaEltList
+ {
+ CreateSchemaStmt *n = makeNode(CreateSchemaStmt);
+ /* ...but not both */
+ n->schemaname = $6;
+ n->authid = NULL;
+ if ($7 != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CREATE SCHEMA IF NOT EXISTS cannot include schema elements"),
+ parser_errposition(@7)));
+ n->schemaElts = $7;
+ n->if_not_exists = true;
$$ = (Node *)n;
}
;
@@ -1210,8 +1298,14 @@ OptSchemaName:
;
OptSchemaEltList:
- OptSchemaEltList schema_stmt { $$ = lappend($1, $2); }
- | /* EMPTY */ { $$ = NIL; }
+ OptSchemaEltList schema_stmt
+ {
+ if (@$ < 0) /* see comments for YYLLOC_DEFAULT */
+ @$ = @2;
+ $$ = lappend($1, $2);
+ }
+ | /* EMPTY */
+ { $$ = NIL; }
;
/*
@@ -1232,7 +1326,7 @@ schema_stmt:
*
* Set PG internal variable
* SET name TO 'var_value'
- * Include SQL92 syntax (thomas 1997-10-22):
+ * Include SQL syntax (thomas 1997-10-22):
* SET TIME ZONE 'var_value'
*
*****************************************************************************/
@@ -1278,7 +1372,7 @@ set_rest:
| set_rest_more
;
-set_rest_more: /* Generic SET syntaxes: */
+generic_set:
var_name TO var_list
{
VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1309,6 +1403,9 @@ set_rest_more: /* Generic SET syntaxes: */
n->name = $1;
$$ = n;
}
+
+set_rest_more: /* Generic SET syntaxes: */
+ generic_set {$$ = $1;}
| var_name FROM CURRENT_P
{
VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1355,7 +1452,7 @@ set_rest_more: /* Generic SET syntaxes: */
n->kind = VAR_SET_DEFAULT;
$$ = n;
}
- | ROLE ColId_or_Sconst
+ | ROLE NonReservedWord_or_Sconst
{
VariableSetStmt *n = makeNode(VariableSetStmt);
n->kind = VAR_SET_VALUE;
@@ -1363,7 +1460,7 @@ set_rest_more: /* Generic SET syntaxes: */
n->args = list_make1(makeStringConst($2, @2));
$$ = n;
}
- | SESSION AUTHORIZATION ColId_or_Sconst
+ | SESSION AUTHORIZATION NonReservedWord_or_Sconst
{
VariableSetStmt *n = makeNode(VariableSetStmt);
n->kind = VAR_SET_VALUE;
@@ -1399,10 +1496,7 @@ set_rest_more: /* Generic SET syntaxes: */
var_name: ColId { $$ = $1; }
| var_name '.' ColId
- {
- $$ = palloc(strlen($1) + strlen($3) + 2);
- sprintf($$, "%s.%s", $1, $3);
- }
+ { $$ = psprintf("%s.%s", $1, $3); }
;
var_list: var_value { $$ = list_make1($1); }
@@ -1426,11 +1520,11 @@ opt_boolean_or_string:
| FALSE_P { $$ = "false"; }
| ON { $$ = "on"; }
/*
- * OFF is also accepted as a boolean value, but is handled
- * by the ColId rule below. The action for booleans and strings
+ * OFF is also accepted as a boolean value, but is handled by
+ * the NonReservedWord rule. The action for booleans and strings
* is the same, so we don't need to distinguish them here.
*/
- | ColId_or_Sconst { $$ = $1; }
+ | NonReservedWord_or_Sconst { $$ = $1; }
;
/* Timezone values can be:
@@ -1499,8 +1593,8 @@ opt_encoding:
| /*EMPTY*/ { $$ = NULL; }
;
-ColId_or_Sconst:
- ColId { $$ = $1; }
+NonReservedWord_or_Sconst:
+ NonReservedWord { $$ = $1; }
| Sconst { $$ = $1; }
;
@@ -1623,7 +1717,7 @@ CheckPointStmt:
/*****************************************************************************
*
- * DISCARD { ALL | TEMP | PLANS }
+ * DISCARD { ALL | TEMP | PLANS | SEQUENCES }
*
*****************************************************************************/
@@ -1652,14 +1746,21 @@ DiscardStmt:
n->target = DISCARD_PLANS;
$$ = (Node *) n;
}
+ | DISCARD SEQUENCES
+ {
+ DiscardStmt *n = makeNode(DiscardStmt);
+ n->target = DISCARD_SEQUENCES;
+ $$ = (Node *) n;
+ }
+
;
/*****************************************************************************
*
- * ALTER [ TABLE | INDEX | SEQUENCE | VIEW ] variations
+ * ALTER [ TABLE | INDEX | SEQUENCE | VIEW | MATERIALIZED VIEW ] variations
*
- * Note: we accept all subcommands for each of the four variants, and sort
+ * Note: we accept all subcommands for each of the five variants, and sort
* out what's really legal at execution time.
*****************************************************************************/
@@ -1736,6 +1837,24 @@ AlterTableStmt:
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $4;
+ n->cmds = $5;
+ n->relkind = OBJECT_MATVIEW;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $6;
+ n->cmds = $7;
+ n->relkind = OBJECT_MATVIEW;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
;
alter_table_cmds:
@@ -1852,10 +1971,11 @@ alter_table_cmd:
n->subtype = AT_AlterColumnType;
n->name = $3;
n->def = (Node *) def;
- /* We only use these three fields of the ColumnDef node */
+ /* We only use these fields of the ColumnDef node */
def->typeName = $6;
def->collClause = (CollateClause *) $7;
def->raw_default = $8;
+ def->location = @3;
$$ = (Node *)n;
}
/* ALTER FOREIGN TABLE <name> ALTER [COLUMN] <colname> OPTIONS */
@@ -1875,6 +1995,21 @@ alter_table_cmd:
n->def = $2;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> ALTER CONSTRAINT ... */
+ | ALTER CONSTRAINT name ConstraintAttributeSpec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ Constraint *c = makeNode(Constraint);
+ n->subtype = AT_AlterConstraint;
+ n->def = (Node *) c;
+ c->contype = CONSTR_FOREIGN; /* others not supported, yet */
+ c->conname = $3;
+ processCASbits($4, @4, "ALTER CONSTRAINT statement",
+ &c->deferrable,
+ &c->initdeferred,
+ NULL, NULL, yyscanner);
+ $$ = (Node *)n;
+ }
/* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
| VALIDATE CONSTRAINT name
{
@@ -2090,6 +2225,14 @@ alter_table_cmd:
n->def = (Node *)$2;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> REPLICA IDENTITY */
+ | REPLICA IDENTITY_P replica_identity
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ReplicaIdentity;
+ n->def = $3;
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2161,6 +2304,37 @@ alter_using:
| /* EMPTY */ { $$ = NULL; }
;
+replica_identity:
+ NOTHING
+ {
+ ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+ n->identity_type = REPLICA_IDENTITY_NOTHING;
+ n->name = NULL;
+ $$ = (Node *) n;
+ }
+ | FULL
+ {
+ ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+ n->identity_type = REPLICA_IDENTITY_FULL;
+ n->name = NULL;
+ $$ = (Node *) n;
+ }
+ | DEFAULT
+ {
+ ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+ n->identity_type = REPLICA_IDENTITY_DEFAULT;
+ n->name = NULL;
+ $$ = (Node *) n;
+ }
+ | USING INDEX name
+ {
+ ReplicaIdentityStmt *n = makeNode(ReplicaIdentityStmt);
+ n->identity_type = REPLICA_IDENTITY_INDEX;
+ n->name = $3;
+ $$ = (Node *) n;
+ }
+;
+
reloptions:
'(' reloption_list ')' { $$ = $2; }
;
@@ -2260,10 +2434,11 @@ alter_type_cmd:
n->name = $3;
n->def = (Node *) def;
n->behavior = $8;
- /* We only use these three fields of the ColumnDef node */
+ /* We only use these fields of the ColumnDef node */
def->typeName = $6;
def->collClause = (CollateClause *) $7;
def->raw_default = NULL;
+ def->location = @3;
$$ = (Node *)n;
}
;
@@ -2296,7 +2471,10 @@ ClosePortalStmt:
*
* QUERY :
* COPY relname [(columnList)] FROM/TO file [WITH] [(options)]
- * COPY ( SELECT ... ) TO file [WITH] [(options)]
+ * COPY ( SELECT ... ) TO file [WITH] [(options)]
+ *
+ * where 'file' can be one of:
+ * { PROGRAM 'command' | STDIN | STDOUT | 'filename' }
*
* In the preferred syntax the options are comma-separated
* and use generic identifiers instead of keywords. The pre-9.0
@@ -2311,14 +2489,21 @@ ClosePortalStmt:
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
- copy_from copy_file_name copy_delimiter opt_with copy_options
+ copy_from opt_program copy_file_name copy_delimiter opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = $3;
n->query = NULL;
n->attlist = $4;
n->is_from = $6;
- n->filename = $7;
+ n->is_program = $7;
+ n->filename = $8;
+
+ if (n->is_program && n->filename == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("STDIN/STDOUT not allowed with PROGRAM"),
+ parser_errposition(@8)));
n->options = NIL;
/* Concatenate user-supplied flags */
@@ -2326,21 +2511,29 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
n->options = lappend(n->options, $2);
if ($5)
n->options = lappend(n->options, $5);
- if ($8)
- n->options = lappend(n->options, $8);
- if ($10)
- n->options = list_concat(n->options, $10);
+ if ($9)
+ n->options = lappend(n->options, $9);
+ if ($11)
+ n->options = list_concat(n->options, $11);
$$ = (Node *)n;
}
- | COPY select_with_parens TO copy_file_name opt_with copy_options
+ | COPY select_with_parens TO opt_program copy_file_name opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
n->query = $2;
n->attlist = NIL;
n->is_from = false;
- n->filename = $4;
- n->options = $6;
+ n->is_program = $4;
+ n->filename = $5;
+ n->options = $7;
+
+ if (n->is_program && n->filename == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("STDIN/STDOUT not allowed with PROGRAM"),
+ parser_errposition(@5)));
+
$$ = (Node *)n;
}
;
@@ -2350,6 +2543,11 @@ copy_from:
| TO { $$ = FALSE; }
;
+opt_program:
+ PROGRAM { $$ = TRUE; }
+ | /* EMPTY */ { $$ = FALSE; }
+ ;
+
/*
* copy_file_name NULL indicates stdio is used. Whether stdin or stdout is
* used depends on the direction. (It really doesn't make sense to copy from
@@ -2380,6 +2578,10 @@ copy_opt_item:
{
$$ = makeDefElem("oids", (Node *)makeInteger(TRUE));
}
+ | FREEZE
+ {
+ $$ = makeDefElem("freeze", (Node *)makeInteger(TRUE));
+ }
| DELIMITER opt_as Sconst
{
$$ = makeDefElem("delimiter", (Node *)makeString($3));
@@ -2416,6 +2618,10 @@ copy_opt_item:
{
$$ = makeDefElem("force_not_null", (Node *)$4);
}
+ | FORCE NULL_P columnList
+ {
+ $$ = makeDefElem("force_null", (Node *)$3);
+ }
| ENCODING Sconst
{
$$ = makeDefElem("encoding", (Node *)makeString($2));
@@ -2711,6 +2917,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->fdwoptions = $3;
SplitColQualList($4, &n->constraints, &n->collClause,
yyscanner);
+ n->location = @1;
$$ = (Node *)n;
}
;
@@ -2730,6 +2937,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->collOid = InvalidOid;
SplitColQualList($4, &n->constraints, &n->collClause,
yyscanner);
+ n->location = @1;
$$ = (Node *)n;
}
;
@@ -2770,7 +2978,7 @@ ColConstraint:
* to make it explicit.
* - thomas 1998-09-13
*
- * WITH NULL and NULL are not SQL92-standard syntax elements,
+ * WITH NULL and NULL are not SQL-standard syntax elements,
* so leave them out. Use DEFAULT NULL to explicitly indicate
* that a column may have that value. WITH NULL leads to
* shift/reduce conflicts with WITH TIME ZONE anyway.
@@ -2817,13 +3025,13 @@ ColConstraintElem:
n->indexspace = $4;
$$ = (Node *)n;
}
- | CHECK opt_no_inherit '(' a_expr ')'
+ | CHECK '(' a_expr ')' opt_no_inherit
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_CHECK;
n->location = @1;
- n->is_no_inherit = $2;
- n->raw_expr = $4;
+ n->is_no_inherit = $5;
+ n->raw_expr = $3;
n->cooked_expr = NULL;
$$ = (Node *)n;
}
@@ -2863,10 +3071,10 @@ ColConstraintElem:
* combinations.
*
* See also ConstraintAttributeSpec, which can be used in places where
- * there is no parsing conflict. (Note: currently, NOT VALID is an allowed
- * clause in ConstraintAttributeSpec, but not here. Someday we might need
- * to allow it here too, but for the moment it doesn't seem useful in the
- * statements that use ConstraintAttr.)
+ * there is no parsing conflict. (Note: currently, NOT VALID and NO INHERIT
+ * are allowed clauses in ConstraintAttributeSpec, but not here. Someday we
+ * might need to allow them here too, but for the moment it doesn't seem
+ * useful in the statements that use ConstraintAttr.)
*/
ConstraintAttr:
DEFERRABLE
@@ -2943,17 +3151,16 @@ TableConstraint:
;
ConstraintElem:
- CHECK opt_no_inherit '(' a_expr ')' ConstraintAttributeSpec
+ CHECK '(' a_expr ')' ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_CHECK;
n->location = @1;
- n->is_no_inherit = $2;
- n->raw_expr = $4;
+ n->raw_expr = $3;
n->cooked_expr = NULL;
- processCASbits($6, @6, "CHECK",
+ processCASbits($5, @5, "CHECK",
NULL, NULL, &n->skip_validation,
- yyscanner);
+ &n->is_no_inherit, yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
}
@@ -2969,7 +3176,7 @@ ConstraintElem:
n->indexspace = $6;
processCASbits($7, @7, "UNIQUE",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
$$ = (Node *)n;
}
| UNIQUE ExistingIndex ConstraintAttributeSpec
@@ -2983,7 +3190,7 @@ ConstraintElem:
n->indexspace = NULL;
processCASbits($3, @3, "UNIQUE",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
@@ -2998,7 +3205,7 @@ ConstraintElem:
n->indexspace = $7;
processCASbits($8, @8, "PRIMARY KEY",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY ExistingIndex ConstraintAttributeSpec
@@ -3012,7 +3219,7 @@ ConstraintElem:
n->indexspace = NULL;
processCASbits($4, @4, "PRIMARY KEY",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
@@ -3030,7 +3237,7 @@ ConstraintElem:
n->where_clause = $8;
processCASbits($9, @9, "EXCLUDE",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
@@ -3047,7 +3254,7 @@ ConstraintElem:
n->fk_del_action = (char) ($10 & 0xFF);
processCASbits($11, @11, "FOREIGN KEY",
&n->deferrable, &n->initdeferred,
- &n->skip_validation,
+ &n->skip_validation, NULL,
yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
@@ -3088,11 +3295,11 @@ key_match: MATCH FULL
}
| MATCH SIMPLE
{
- $$ = FKCONSTR_MATCH_UNSPECIFIED;
+ $$ = FKCONSTR_MATCH_SIMPLE;
}
| /*EMPTY*/
{
- $$ = FKCONSTR_MATCH_UNSPECIFIED;
+ $$ = FKCONSTR_MATCH_SIMPLE;
}
;
@@ -3262,6 +3469,7 @@ CreateAsStmt:
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ctas->query = $6;
ctas->into = $4;
+ ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
@@ -3282,6 +3490,7 @@ create_as_target:
$$->options = $3;
$$->onCommit = $4;
$$->tableSpaceName = $5;
+ $$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
/* PGXC_BEGIN */
$$->distributeby = $6;
@@ -3300,6 +3509,66 @@ opt_with_data:
/*****************************************************************************
*
* QUERY :
+ * CREATE MATERIALIZED VIEW relname AS SelectStmt
+ *
+ *****************************************************************************/
+
+CreateMatViewStmt:
+ CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+ {
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+ ctas->query = $7;
+ ctas->into = $5;
+ ctas->relkind = OBJECT_MATVIEW;
+ ctas->is_select_into = false;
+ /* cram additional flags into the IntoClause */
+ $5->rel->relpersistence = $2;
+ $5->skipData = !($8);
+ $$ = (Node *) ctas;
+ }
+ ;
+
+create_mv_target:
+ qualified_name opt_column_list opt_reloptions OptTableSpace
+ {
+ $$ = makeNode(IntoClause);
+ $$->rel = $1;
+ $$->colNames = $2;
+ $$->options = $3;
+ $$->onCommit = ONCOMMIT_NOOP;
+ $$->tableSpaceName = $4;
+ $$->viewQuery = NULL; /* filled at analysis time */
+ $$->skipData = false; /* might get changed later */
+ }
+ ;
+
+OptNoLog: UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; }
+ | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; }
+ ;
+
+
+/*****************************************************************************
+ *
+ * QUERY :
+ * REFRESH MATERIALIZED VIEW qualified_name
+ *
+ *****************************************************************************/
+
+RefreshMatViewStmt:
+ REFRESH MATERIALIZED VIEW opt_concurrently qualified_name opt_with_data
+ {
+ RefreshMatViewStmt *n = makeNode(RefreshMatViewStmt);
+ n->concurrent = $4;
+ n->relation = $5;
+ n->skipData = !($6);
+ $$ = (Node *) n;
+ }
+ ;
+
+
+/*****************************************************************************
+ *
+ * QUERY :
* CREATE SEQUENCE seqname
* ALTER SEQUENCE seqname
*
@@ -3428,7 +3697,7 @@ NumericOnly_list: NumericOnly { $$ = list_make1($1); }
*****************************************************************************/
CreatePLangStmt:
- CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
+ CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
{
CreatePLangStmt *n = makeNode(CreatePLangStmt);
n->replace = $2;
@@ -3440,7 +3709,7 @@ CreatePLangStmt:
n->pltrusted = false;
$$ = (Node *)n;
}
- | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst
+ | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
HANDLER handler_name opt_inline_handler opt_validator
{
CreatePLangStmt *n = makeNode(CreatePLangStmt);
@@ -3484,7 +3753,7 @@ opt_validator:
;
DropPLangStmt:
- DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior
+ DROP opt_procedural LANGUAGE NonReservedWord_or_Sconst opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_LANGUAGE;
@@ -3495,7 +3764,7 @@ DropPLangStmt:
n->concurrent = false;
$$ = (Node *)n;
}
- | DROP opt_procedural LANGUAGE IF_P EXISTS ColId_or_Sconst opt_drop_behavior
+ | DROP opt_procedural LANGUAGE IF_P EXISTS NonReservedWord_or_Sconst opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_LANGUAGE;
@@ -3519,12 +3788,13 @@ opt_procedural:
*
*****************************************************************************/
-CreateTableSpaceStmt: CREATE TABLESPACE name OptTableSpaceOwner LOCATION Sconst
+CreateTableSpaceStmt: CREATE TABLESPACE name OptTableSpaceOwner LOCATION Sconst opt_reloptions
{
CreateTableSpaceStmt *n = makeNode(CreateTableSpaceStmt);
n->tablespacename = $3;
n->owner = $4;
n->location = $6;
+ n->options = $7;
$$ = (Node *) n;
}
;
@@ -3597,11 +3867,11 @@ create_extension_opt_item:
{
$$ = makeDefElem("schema", (Node *)makeString($2));
}
- | VERSION_P ColId_or_Sconst
+ | VERSION_P NonReservedWord_or_Sconst
{
$$ = makeDefElem("new_version", (Node *)makeString($2));
}
- | FROM ColId_or_Sconst
+ | FROM NonReservedWord_or_Sconst
{
$$ = makeDefElem("old_version", (Node *)makeString($2));
}
@@ -3630,7 +3900,7 @@ alter_extension_opt_list:
;
alter_extension_opt_item:
- TO ColId_or_Sconst
+ TO NonReservedWord_or_Sconst
{
$$ = makeDefElem("new_version", (Node *)makeString($2));
}
@@ -3650,7 +3920,7 @@ AlterExtensionContentsStmt:
n->action = $4;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
- n->objargs = $7;
+ n->objargs = extractAggrArgTypes($7);
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')'
@@ -3748,6 +4018,15 @@ AlterExtensionContentsStmt:
n->objname = list_make1(makeString($6));
$$ = (Node *)n;
}
+ | ALTER EXTENSION name add_drop EVENT TRIGGER name
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_EVENT_TRIGGER;
+ n->objname = list_make1(makeString($7));
+ $$ = (Node *)n;
+ }
| ALTER EXTENSION name add_drop TABLE any_name
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@@ -3811,6 +4090,15 @@ AlterExtensionContentsStmt:
n->objname = $6;
$$ = (Node *)n;
}
+ | ALTER EXTENSION name add_drop MATERIALIZED VIEW any_name
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_MATVIEW;
+ n->objname = $7;
+ $$ = (Node *)n;
+ }
| ALTER EXTENSION name add_drop FOREIGN TABLE any_name
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@@ -3902,7 +4190,7 @@ DropFdwStmt: DROP FOREIGN DATA_P WRAPPER name opt_drop_behavior
$$ = (Node *) n;
}
| DROP FOREIGN DATA_P WRAPPER IF_P EXISTS name opt_drop_behavior
- {
+ {
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_FDW;
n->objects = list_make1(list_make1(makeString($7)));
@@ -4064,7 +4352,7 @@ DropForeignServerStmt: DROP SERVER name opt_drop_behavior
$$ = (Node *) n;
}
| DROP SERVER IF_P EXISTS name opt_drop_behavior
- {
+ {
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_FOREIGN_SERVER;
n->objects = list_make1(list_make1(makeString($5)));
@@ -4118,57 +4406,37 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
- OptForeignTableElementList
+ '(' OptTableElementList ')'
SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
n->base.relation = $4;
- n->base.tableElts = $5;
+ n->base.tableElts = $6;
n->base.inhRelations = NIL;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $7;
- n->options = $8;
+ n->servername = $9;
+ n->options = $10;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
- OptForeignTableElementList
+ '(' OptTableElementList ')'
SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
n->base.relation = $7;
- n->base.tableElts = $8;
+ n->base.tableElts = $9;
n->base.inhRelations = NIL;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
;
-OptForeignTableElementList:
- '(' ForeignTableElementList ')' { $$ = $2; }
- | '(' ')' { $$ = NIL; }
- ;
-
-ForeignTableElementList:
- ForeignTableElement
- {
- $$ = list_make1($1);
- }
- | ForeignTableElementList ',' ForeignTableElement
- {
- $$ = lappend($1, $3);
- }
- ;
-
-ForeignTableElement:
- columnDef { $$ = $1; }
- ;
-
/*****************************************************************************
*
* QUERY:
@@ -4310,7 +4578,7 @@ CreateTrigStmt:
n->isconstraint = TRUE;
processCASbits($10, @10, "TRIGGER",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
n->constrrel = $9;
$$ = (Node *)n;
}
@@ -4447,6 +4715,7 @@ ConstraintAttributeElem:
| INITIALLY IMMEDIATE { $$ = CAS_INITIALLY_IMMEDIATE; }
| INITIALLY DEFERRED { $$ = CAS_INITIALLY_DEFERRED; }
| NOT VALID { $$ = CAS_NOT_VALID; }
+ | NO INHERIT { $$ = CAS_NO_INHERIT; }
;
@@ -4479,6 +4748,74 @@ DropTrigStmt:
/*****************************************************************************
*
* QUERIES :
+ * CREATE EVENT TRIGGER ...
+ * ALTER EVENT TRIGGER ...
+ *
+ *****************************************************************************/
+
+CreateEventTrigStmt:
+ CREATE EVENT TRIGGER name ON ColLabel
+ EXECUTE PROCEDURE func_name '(' ')'
+ {
+ CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt);
+ n->trigname = $4;
+ n->eventname = $6;
+ n->whenclause = NULL;
+ n->funcname = $9;
+ $$ = (Node *)n;
+ }
+ | CREATE EVENT TRIGGER name ON ColLabel
+ WHEN event_trigger_when_list
+ EXECUTE PROCEDURE func_name '(' ')'
+ {
+ CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt);
+ n->trigname = $4;
+ n->eventname = $6;
+ n->whenclause = $8;
+ n->funcname = $11;
+ $$ = (Node *)n;
+ }
+ ;
+
+event_trigger_when_list:
+ event_trigger_when_item
+ { $$ = list_make1($1); }
+ | event_trigger_when_list AND event_trigger_when_item
+ { $$ = lappend($1, $3); }
+ ;
+
+event_trigger_when_item:
+ ColId IN_P '(' event_trigger_value_list ')'
+ { $$ = makeDefElem($1, (Node *) $4); }
+ ;
+
+event_trigger_value_list:
+ SCONST
+ { $$ = list_make1(makeString($1)); }
+ | event_trigger_value_list ',' SCONST
+ { $$ = lappend($1, makeString($3)); }
+ ;
+
+AlterEventTrigStmt:
+ ALTER EVENT TRIGGER name enable_trigger
+ {
+ AlterEventTrigStmt *n = makeNode(AlterEventTrigStmt);
+ n->trigname = $4;
+ n->tgenabled = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+enable_trigger:
+ ENABLE_P { $$ = TRIGGER_FIRES_ON_ORIGIN; }
+ | ENABLE_P REPLICA { $$ = TRIGGER_FIRES_ON_REPLICA; }
+ | ENABLE_P ALWAYS { $$ = TRIGGER_FIRES_ALWAYS; }
+ | DISABLE_P { $$ = TRIGGER_DISABLED; }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERIES :
* CREATE ASSERTION ...
* DROP ASSERTION ...
*
@@ -4494,7 +4831,7 @@ CreateAssertStmt:
n->isconstraint = TRUE;
processCASbits($8, @8, "ASSERTION",
&n->deferrable, &n->initdeferred, NULL,
- yyscanner);
+ NULL, yyscanner);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4684,10 +5021,6 @@ def_arg: func_type { $$ = (Node *)$1; }
| Sconst { $$ = (Node *)makeString($1); }
;
-aggr_args: '(' type_list ')' { $$ = $2; }
- | '(' '*' ')' { $$ = NIL; }
- ;
-
old_aggr_definition: '(' old_aggr_list ')' { $$ = $2; }
;
@@ -4724,35 +5057,42 @@ enum_val_list: Sconst
*****************************************************************************/
AlterEnumStmt:
- ALTER TYPE_P any_name ADD_P VALUE_P Sconst
+ ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst
{
AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3;
- n->newVal = $6;
+ n->newVal = $7;
n->newValNeighbor = NULL;
n->newValIsAfter = true;
+ n->skipIfExists = $6;
$$ = (Node *) n;
}
- | ALTER TYPE_P any_name ADD_P VALUE_P Sconst BEFORE Sconst
+ | ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst BEFORE Sconst
{
AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3;
- n->newVal = $6;
- n->newValNeighbor = $8;
+ n->newVal = $7;
+ n->newValNeighbor = $9;
n->newValIsAfter = false;
+ n->skipIfExists = $6;
$$ = (Node *) n;
}
- | ALTER TYPE_P any_name ADD_P VALUE_P Sconst AFTER Sconst
+ | ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst AFTER Sconst
{
AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3;
- n->newVal = $6;
- n->newValNeighbor = $8;
+ n->newVal = $7;
+ n->newValNeighbor = $9;
n->newValIsAfter = true;
+ n->skipIfExists = $6;
$$ = (Node *) n;
}
;
+opt_if_not_exists: IF_P NOT EXISTS { $$ = true; }
+ | /* empty */ { $$ = false; }
+ ;
+
/*****************************************************************************
*
@@ -4982,7 +5322,7 @@ DropOpFamilyStmt:
*
*****************************************************************************/
DropOwnedStmt:
- DROP OWNED BY name_list opt_drop_behavior
+ DROP OWNED BY role_list opt_drop_behavior
{
DropOwnedStmt *n = makeNode(DropOwnedStmt);
n->roles = $4;
@@ -4992,7 +5332,7 @@ DropOwnedStmt:
;
ReassignOwnedStmt:
- REASSIGN OWNED BY name_list TO name
+ REASSIGN OWNED BY role_list TO name
{
ReassignOwnedStmt *n = makeNode(ReassignOwnedStmt);
n->roles = $4;
@@ -5047,7 +5387,7 @@ DropStmt: DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_INDEX;
- n->missing_ok = FALSE;
+ n->missing_ok = TRUE;
n->objects = $6;
n->arguments = NIL;
n->behavior = $7;
@@ -5060,8 +5400,10 @@ DropStmt: DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior
drop_type: TABLE { $$ = OBJECT_TABLE; }
| SEQUENCE { $$ = OBJECT_SEQUENCE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| INDEX { $$ = OBJECT_INDEX; }
| FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
+ | EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; }
| TYPE_P { $$ = OBJECT_TYPE; }
| DOMAIN_P { $$ = OBJECT_DOMAIN; }
| COLLATION { $$ = OBJECT_COLLATION; }
@@ -5119,19 +5461,25 @@ opt_restart_seqs:
* The COMMENT ON statement can take different forms based upon the type of
* the object associated with the comment. The form of the statement is:
*
- * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW |
- * COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS |
- * LARGE OBJECT | CAST | COLUMN | SCHEMA | TABLESPACE |
- * EXTENSION | ROLE | TEXT SEARCH PARSER |
- * TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
- * TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
- * FOREIGN DATA WRAPPER | SERVER ] <objname> |
+ * COMMENT ON [ [ CONVERSION | COLLATION | DATABASE | DOMAIN |
+ * EXTENSION | EVENT TRIGGER | FOREIGN DATA WRAPPER |
+ * FOREIGN TABLE | INDEX | [PROCEDURAL] LANGUAGE |
+ * MATERIALIZED VIEW | ROLE | SCHEMA | SEQUENCE |
+ * SERVER | TABLE | TABLESPACE |
+ * TEXT SEARCH CONFIGURATION | TEXT SEARCH DICTIONARY |
+ * TEXT SEARCH PARSER | TEXT SEARCH TEMPLATE | TYPE |
+ * VIEW] <objname> |
* AGGREGATE <aggname> (arg1, ...) |
+ * CAST (<src type> AS <dst type>) |
+ * COLUMN <relname>.<colname> |
+ * CONSTRAINT <constraintname> ON <relname> |
* FUNCTION <funcname> (arg1, arg2, ...) |
+ * LARGE OBJECT <oid> |
* OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
- * TRIGGER <triggername> ON <relname> |
- * CONSTRAINT <constraintname> ON <relname> |
- * RULE <rulename> ON <relname> ]
+ * OPERATOR CLASS <name> USING <access-method> |
+ * OPERATOR FAMILY <name> USING <access-method> |
+ * RULE <rulename> ON <relname> |
+ * TRIGGER <triggername> ON <relname> ]
* IS 'text'
*
*****************************************************************************/
@@ -5151,7 +5499,7 @@ CommentStmt:
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_AGGREGATE;
n->objname = $4;
- n->objargs = $5;
+ n->objargs = extractAggrArgTypes($5);
n->comment = $7;
$$ = (Node *) n;
}
@@ -5255,38 +5603,6 @@ CommentStmt:
n->comment = $7;
$$ = (Node *) n;
}
- | COMMENT ON TEXT_P SEARCH PARSER any_name IS comment_text
- {
- CommentStmt *n = makeNode(CommentStmt);
- n->objtype = OBJECT_TSPARSER;
- n->objname = $6;
- n->comment = $8;
- $$ = (Node *) n;
- }
- | COMMENT ON TEXT_P SEARCH DICTIONARY any_name IS comment_text
- {
- CommentStmt *n = makeNode(CommentStmt);
- n->objtype = OBJECT_TSDICTIONARY;
- n->objname = $6;
- n->comment = $8;
- $$ = (Node *) n;
- }
- | COMMENT ON TEXT_P SEARCH TEMPLATE any_name IS comment_text
- {
- CommentStmt *n = makeNode(CommentStmt);
- n->objtype = OBJECT_TSTEMPLATE;
- n->objname = $6;
- n->comment = $8;
- $$ = (Node *) n;
- }
- | COMMENT ON TEXT_P SEARCH CONFIGURATION any_name IS comment_text
- {
- CommentStmt *n = makeNode(CommentStmt);
- n->objtype = OBJECT_TSCONFIGURATION;
- n->objname = $6;
- n->comment = $8;
- $$ = (Node *) n;
- }
;
comment_type:
@@ -5299,6 +5615,7 @@ comment_type:
| DOMAIN_P { $$ = OBJECT_DOMAIN; }
| TYPE_P { $$ = OBJECT_TYPE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| COLLATION { $$ = OBJECT_COLLATION; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
@@ -5307,6 +5624,11 @@ comment_type:
| FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| SERVER { $$ = OBJECT_FOREIGN_SERVER; }
| FOREIGN DATA_P WRAPPER { $$ = OBJECT_FDW; }
+ | EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; }
+ | TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; }
+ | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
+ | TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
+ | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
;
comment_text:
@@ -5343,7 +5665,7 @@ SecLabelStmt:
n->provider = $3;
n->objtype = OBJECT_AGGREGATE;
n->objname = $6;
- n->objargs = $7;
+ n->objargs = extractAggrArgTypes($7);
n->label = $9;
$$ = (Node *) n;
}
@@ -5382,13 +5704,14 @@ SecLabelStmt:
}
;
-opt_provider: FOR ColId_or_Sconst { $$ = $2; }
- | /* empty */ { $$ = NULL; }
+opt_provider: FOR NonReservedWord_or_Sconst { $$ = $2; }
+ | /* empty */ { $$ = NULL; }
;
security_label_type:
COLUMN { $$ = OBJECT_COLUMN; }
| DATABASE { $$ = OBJECT_DATABASE; }
+ | EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; }
| FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| SCHEMA { $$ = OBJECT_SCHEMA; }
| SEQUENCE { $$ = OBJECT_SEQUENCE; }
@@ -5398,6 +5721,7 @@ security_label_type:
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
| TYPE_P { $$ = OBJECT_TYPE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
;
security_label: Sconst { $$ = $1; }
@@ -5874,7 +6198,7 @@ function_with_argtypes:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO name_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
@@ -5887,7 +6211,7 @@ GrantRoleStmt:
;
RevokeRoleStmt:
- REVOKE privilege_list FROM name_list opt_granted_by opt_drop_behavior
+ REVOKE privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
@@ -5897,7 +6221,7 @@ RevokeRoleStmt:
n->behavior = $6;
$$ = (Node*)n;
}
- | REVOKE ADMIN OPTION FOR privilege_list FROM name_list opt_granted_by opt_drop_behavior
+ | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
@@ -5943,11 +6267,11 @@ DefACLOption:
{
$$ = makeDefElem("schemas", (Node *)$3);
}
- | FOR ROLE name_list
+ | FOR ROLE role_list
{
$$ = makeDefElem("roles", (Node *)$3);
}
- | FOR USER name_list
+ | FOR USER role_list
{
$$ = makeDefElem("roles", (Node *)$3);
}
@@ -6031,7 +6355,14 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->options = $12;
n->tableSpace = $13;
n->whereClause = $14;
+ n->excludeOpNames = NIL;
+ n->idxcomment = NULL;
n->indexOid = InvalidOid;
+ n->oldNode = InvalidOid;
+ n->primary = false;
+ n->isconstraint = false;
+ n->deferrable = false;
+ n->initdeferred = false;
$$ = (Node *)n;
}
;
@@ -6076,7 +6407,7 @@ index_elem: ColId opt_collate opt_class opt_asc_desc opt_nulls_order
$$->ordering = $4;
$$->nulls_ordering = $5;
}
- | func_expr opt_collate opt_class opt_asc_desc opt_nulls_order
+ | func_expr_windowless opt_collate opt_class opt_asc_desc opt_nulls_order
{
$$ = makeNode(IndexElem);
$$->name = NULL;
@@ -6321,6 +6652,71 @@ func_arg_with_default:
}
;
+/* Aggregate args can be most things that function args can be */
+aggr_arg: func_arg
+ {
+ if (!($1->mode == FUNC_PARAM_IN ||
+ $1->mode == FUNC_PARAM_VARIADIC))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aggregates cannot have output arguments"),
+ parser_errposition(@1)));
+ $$ = $1;
+ }
+ ;
+
+/*
+ * The SQL standard offers no guidance on how to declare aggregate argument
+ * lists, since it doesn't have CREATE AGGREGATE etc. We accept these cases:
+ *
+ * (*) - normal agg with no args
+ * (aggr_arg,...) - normal agg with args
+ * (ORDER BY aggr_arg,...) - ordered-set agg with no direct args
+ * (aggr_arg,... ORDER BY aggr_arg,...) - ordered-set agg with direct args
+ *
+ * The zero-argument case is spelled with '*' for consistency with COUNT(*).
+ *
+ * An additional restriction is that if the direct-args list ends in a
+ * VARIADIC item, the ordered-args list must contain exactly one item that
+ * is also VARIADIC with the same type. This allows us to collapse the two
+ * VARIADIC items into one, which is necessary to represent the aggregate in
+ * pg_proc. We check this at the grammar stage so that we can return a list
+ * in which the second VARIADIC item is already discarded, avoiding extra work
+ * in cases such as DROP AGGREGATE.
+ *
+ * The return value of this production is a two-element list, in which the
+ * first item is a sublist of FunctionParameter nodes (with any duplicate
+ * VARIADIC item already dropped, as per above) and the second is an integer
+ * Value node, containing -1 if there was no ORDER BY and otherwise the number
+ * of argument declarations before the ORDER BY. (If this number is equal
+ * to the first sublist's length, then we dropped a duplicate VARIADIC item.)
+ * This representation is passed as-is to CREATE AGGREGATE; for operations
+ * on existing aggregates, we can just apply extractArgTypes to the first
+ * sublist.
+ */
+aggr_args: '(' '*' ')'
+ {
+ $$ = list_make2(NIL, makeInteger(-1));
+ }
+ | '(' aggr_args_list ')'
+ {
+ $$ = list_make2($2, makeInteger(-1));
+ }
+ | '(' ORDER BY aggr_args_list ')'
+ {
+ $$ = list_make2($4, makeInteger(0));
+ }
+ | '(' aggr_args_list ORDER BY aggr_args_list ')'
+ {
+ /* this is the only case requiring consistency checking */
+ $$ = makeOrderedSetArgs($2, $5, yyscanner);
+ }
+ ;
+
+aggr_args_list:
+ aggr_arg { $$ = list_make1($1); }
+ | aggr_args_list ',' aggr_arg { $$ = lappend($1, $3); }
+ ;
createfunc_opt_list:
/* Must be at least one to prevent conflict */
@@ -6400,7 +6796,7 @@ createfunc_opt_item:
{
$$ = makeDefElem("as", (Node *)$2);
}
- | LANGUAGE ColId_or_Sconst
+ | LANGUAGE NonReservedWord_or_Sconst
{
$$ = makeDefElem("language", (Node *)makeString($2));
}
@@ -6520,7 +6916,7 @@ RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($3);
- n->arguments = list_make1($4);
+ n->arguments = list_make1(extractAggrArgTypes($4));
n->behavior = $5;
n->missing_ok = false;
n->concurrent = false;
@@ -6531,7 +6927,7 @@ RemoveAggrStmt:
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_AGGREGATE;
n->objects = list_make1($5);
- n->arguments = list_make1($6);
+ n->arguments = list_make1(extractAggrArgTypes($6));
n->behavior = $7;
n->missing_ok = true;
n->concurrent = false;
@@ -6615,7 +7011,7 @@ dostmt_opt_item:
{
$$ = makeDefElem("as", (Node *)makeString($1));
}
- | LANGUAGE ColId_or_Sconst
+ | LANGUAGE NonReservedWord_or_Sconst
{
$$ = makeDefElem("language", (Node *)makeString($2));
}
@@ -6738,6 +7134,128 @@ opt_force: FORCE { $$ = TRUE; }
/*****************************************************************************
*
+ * ALTER TABLESPACE
+ *
+ *****************************************************************************/
+
+AlterTblSpcStmt: ALTER TABLESPACE name MOVE ALL TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = -1;
+ n->move_all = true;
+ n->roles = NIL;
+ n->new_tablespacename = $7;
+ n->nowait = $8;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE TABLES TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = OBJECT_TABLE;
+ n->move_all = false;
+ n->roles = NIL;
+ n->new_tablespacename = $7;
+ n->nowait = $8;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE INDEXES TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = OBJECT_INDEX;
+ n->move_all = false;
+ n->roles = NIL;
+ n->new_tablespacename = $7;
+ n->nowait = $8;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE MATERIALIZED VIEWS TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = OBJECT_MATVIEW;
+ n->move_all = false;
+ n->roles = NIL;
+ n->new_tablespacename = $8;
+ n->nowait = $9;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE ALL OWNED BY role_list TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = -1;
+ n->move_all = true;
+ n->roles = $8;
+ n->new_tablespacename = $10;
+ n->nowait = $11;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE TABLES OWNED BY role_list TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = OBJECT_TABLE;
+ n->move_all = false;
+ n->roles = $8;
+ n->new_tablespacename = $10;
+ n->nowait = $11;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE INDEXES OWNED BY role_list TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = OBJECT_INDEX;
+ n->move_all = false;
+ n->roles = $8;
+ n->new_tablespacename = $10;
+ n->nowait = $11;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name MOVE MATERIALIZED VIEWS OWNED BY role_list TO name opt_nowait
+ {
+ AlterTableSpaceMoveStmt *n =
+ makeNode(AlterTableSpaceMoveStmt);
+ n->orig_tablespacename = $3;
+ n->objtype = OBJECT_MATVIEW;
+ n->move_all = false;
+ n->roles = $9;
+ n->new_tablespacename = $11;
+ n->nowait = $12;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name SET reloptions
+ {
+ AlterTableSpaceOptionsStmt *n =
+ makeNode(AlterTableSpaceOptionsStmt);
+ n->tablespacename = $3;
+ n->options = $5;
+ n->isReset = FALSE;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLESPACE name RESET reloptions
+ {
+ AlterTableSpaceOptionsStmt *n =
+ makeNode(AlterTableSpaceOptionsStmt);
+ n->tablespacename = $3;
+ n->options = $5;
+ n->isReset = TRUE;
+ $$ = (Node *)n;
+ }
+ ;
+
+/*****************************************************************************
+ *
* ALTER THING name RENAME TO newname
*
*****************************************************************************/
@@ -6747,7 +7265,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = $4;
+ n->objarg = extractAggrArgTypes($4);
n->newname = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -6802,7 +7320,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_FDW;
- n->subname = $5;
+ n->object = list_make1(makeString($5));
n->newname = $8;
n->missing_ok = false;
$$ = (Node *)n;
@@ -6830,7 +7348,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_LANGUAGE;
- n->subname = $4;
+ n->object = list_make1(makeString($4));
n->newname = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -6840,7 +7358,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_OPCLASS;
n->object = $4;
- n->subname = $6;
+ n->objarg = list_make1(makeString($6));
n->newname = $9;
n->missing_ok = false;
$$ = (Node *)n;
@@ -6850,7 +7368,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_OPFAMILY;
n->object = $4;
- n->subname = $6;
+ n->objarg = list_make1(makeString($6));
n->newname = $9;
n->missing_ok = false;
$$ = (Node *)n;
@@ -6868,7 +7386,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_FOREIGN_SERVER;
- n->subname = $3;
+ n->object = list_make1(makeString($3));
n->newname = $6;
n->missing_ok = false;
$$ = (Node *)n;
@@ -6933,6 +7451,26 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_MATVIEW;
+ n->relation = $4;
+ n->subname = NULL;
+ n->newname = $7;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_MATVIEW;
+ n->relation = $6;
+ n->subname = NULL;
+ n->newname = $9;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
| ALTER INDEX qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -6995,6 +7533,28 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name RENAME opt_column name TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_COLUMN;
+ n->relationType = OBJECT_MATVIEW;
+ n->relation = $4;
+ n->subname = $7;
+ n->newname = $9;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME opt_column name TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_COLUMN;
+ n->relationType = OBJECT_MATVIEW;
+ n->relation = $6;
+ n->subname = $9;
+ n->newname = $11;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
| ALTER TABLE relation_expr RENAME CONSTRAINT name TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -7027,6 +7587,16 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER RULE name ON qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_RULE;
+ n->relation = $5;
+ n->subname = $3;
+ n->newname = $8;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER TRIGGER name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -7037,6 +7607,14 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER EVENT TRIGGER name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_EVENT_TRIGGER;
+ n->object = list_make1(makeString($4));
+ n->newname = $7;
+ $$ = (Node *)n;
+ }
| ALTER ROLE RoleId RENAME TO RoleId
{
RenameStmt *n = makeNode(RenameStmt);
@@ -7064,24 +7642,6 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
- | ALTER TABLESPACE name SET reloptions
- {
- AlterTableSpaceOptionsStmt *n =
- makeNode(AlterTableSpaceOptionsStmt);
- n->tablespacename = $3;
- n->options = $5;
- n->isReset = FALSE;
- $$ = (Node *)n;
- }
- | ALTER TABLESPACE name RESET reloptions
- {
- AlterTableSpaceOptionsStmt *n =
- makeNode(AlterTableSpaceOptionsStmt);
- n->tablespacename = $3;
- n->options = $5;
- n->isReset = TRUE;
- $$ = (Node *)n;
- }
| ALTER TEXT_P SEARCH PARSER any_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -7161,7 +7721,7 @@ AlterObjectSchemaStmt:
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = $4;
+ n->objarg = extractAggrArgTypes($4);
n->newschema = $7;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7227,7 +7787,7 @@ AlterObjectSchemaStmt:
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_OPCLASS;
n->object = $4;
- n->addname = $6;
+ n->objarg = list_make1(makeString($6));
n->newschema = $9;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7237,7 +7797,7 @@ AlterObjectSchemaStmt:
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_OPFAMILY;
n->object = $4;
- n->addname = $6;
+ n->objarg = list_make1(makeString($6));
n->newschema = $9;
n->missing_ok = false;
$$ = (Node *)n;
@@ -7332,6 +7892,24 @@ AlterObjectSchemaStmt:
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_MATVIEW;
+ n->relation = $4;
+ n->newschema = $7;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_MATVIEW;
+ n->relation = $6;
+ n->newschema = $9;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
| ALTER FOREIGN TABLE relation_expr SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -7372,7 +7950,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_AGGREGATE;
n->object = $3;
- n->objarg = $4;
+ n->objarg = extractAggrArgTypes($4);
n->newowner = $7;
$$ = (Node *)n;
}
@@ -7447,7 +8025,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_OPCLASS;
n->object = $4;
- n->addname = $6;
+ n->objarg = list_make1(makeString($6));
n->newowner = $9;
$$ = (Node *)n;
}
@@ -7456,7 +8034,7 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_OPFAMILY;
n->object = $4;
- n->addname = $6;
+ n->objarg = list_make1(makeString($6));
n->newowner = $9;
$$ = (Node *)n;
}
@@ -7516,6 +8094,14 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
n->newowner = $6;
$$ = (Node *)n;
}
+ | ALTER EVENT TRIGGER name OWNER TO RoleId
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_EVENT_TRIGGER;
+ n->object = list_make1(makeString($4));
+ n->newowner = $7;
+ $$ = (Node *)n;
+ }
;
@@ -7834,6 +8420,7 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
n->query = $8;
n->replace = false;
n->options = $6;
+ n->withCheckOption = $9;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list opt_reloptions
@@ -7846,30 +8433,52 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
n->query = $10;
n->replace = true;
n->options = $8;
+ n->withCheckOption = $11;
$$ = (Node *) n;
}
- ;
-
-opt_check_option:
- WITH CHECK OPTION
- {
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("WITH CHECK OPTION is not implemented")));
- }
- | WITH CASCADED CHECK OPTION
+ | CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
+ AS SelectStmt opt_check_option
{
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("WITH CHECK OPTION is not implemented")));
+ ViewStmt *n = makeNode(ViewStmt);
+ n->view = $5;
+ n->view->relpersistence = $2;
+ n->aliases = $7;
+ n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $11);
+ n->replace = false;
+ n->options = $9;
+ n->withCheckOption = $12;
+ if (n->withCheckOption != NO_CHECK_OPTION)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("WITH CHECK OPTION not supported on recursive views"),
+ parser_errposition(@12)));
+ $$ = (Node *) n;
}
- | WITH LOCAL CHECK OPTION
+ | CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
+ AS SelectStmt opt_check_option
{
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("WITH CHECK OPTION is not implemented")));
+ ViewStmt *n = makeNode(ViewStmt);
+ n->view = $7;
+ n->view->relpersistence = $4;
+ n->aliases = $9;
+ n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
+ n->replace = true;
+ n->options = $11;
+ n->withCheckOption = $14;
+ if (n->withCheckOption != NO_CHECK_OPTION)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("WITH CHECK OPTION not supported on recursive views"),
+ parser_errposition(@14)));
+ $$ = (Node *) n;
}
- | /* EMPTY */ { $$ = NIL; }
+ ;
+
+opt_check_option:
+ WITH CHECK OPTION { $$ = CASCADED_CHECK_OPTION; }
+ | WITH CASCADED CHECK OPTION { $$ = CASCADED_CHECK_OPTION; }
+ | WITH LOCAL CHECK OPTION { $$ = LOCAL_CHECK_OPTION; }
+ | /* EMPTY */ { $$ = NO_CHECK_OPTION; }
;
/*****************************************************************************
@@ -8059,6 +8668,23 @@ DropdbStmt: DROP DATABASE database_name
/*****************************************************************************
*
+ * ALTER SYSTEM SET
+ *
+ * This is used to change configuration parameters persistently.
+ *****************************************************************************/
+
+AlterSystemStmt:
+ ALTER SYSTEM_P SET generic_set
+ {
+ AlterSystemStmt *n = makeNode(AlterSystemStmt);
+ n->setstmt = $4;
+ $$ = (Node *)n;
+ }
+ ;
+
+
+/*****************************************************************************
+ *
* Manipulate a domain
*
*****************************************************************************/
@@ -8308,6 +8934,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_VERBOSE;
n->freeze_min_age = $3 ? 0 : -1;
n->freeze_table_age = $3 ? 0 : -1;
+ n->multixact_freeze_min_age = $3 ? 0 : -1;
+ n->multixact_freeze_table_age = $3 ? 0 : -1;
n->relation = NULL;
n->va_cols = NIL;
$$ = (Node *)n;
@@ -8322,6 +8950,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_VERBOSE;
n->freeze_min_age = $3 ? 0 : -1;
n->freeze_table_age = $3 ? 0 : -1;
+ n->multixact_freeze_min_age = $3 ? 0 : -1;
+ n->multixact_freeze_table_age = $3 ? 0 : -1;
n->relation = $5;
n->va_cols = NIL;
$$ = (Node *)n;
@@ -8336,6 +8966,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_VERBOSE;
n->freeze_min_age = $3 ? 0 : -1;
n->freeze_table_age = $3 ? 0 : -1;
+ n->multixact_freeze_min_age = $3 ? 0 : -1;
+ n->multixact_freeze_table_age = $3 ? 0 : -1;
$$ = (Node *)n;
}
| VACUUM '(' vacuum_option_list ')'
@@ -8343,9 +8975,17 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
if (n->options & VACOPT_FREEZE)
+ {
n->freeze_min_age = n->freeze_table_age = 0;
+ n->multixact_freeze_min_age = 0;
+ n->multixact_freeze_table_age = 0;
+ }
else
+ {
n->freeze_min_age = n->freeze_table_age = -1;
+ n->multixact_freeze_min_age = -1;
+ n->multixact_freeze_table_age = -1;
+ }
n->relation = NULL;
n->va_cols = NIL;
$$ = (Node *) n;
@@ -8355,9 +8995,17 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
if (n->options & VACOPT_FREEZE)
+ {
n->freeze_min_age = n->freeze_table_age = 0;
+ n->multixact_freeze_min_age = 0;
+ n->multixact_freeze_table_age = 0;
+ }
else
+ {
n->freeze_min_age = n->freeze_table_age = -1;
+ n->multixact_freeze_min_age = -1;
+ n->multixact_freeze_table_age = -1;
+ }
n->relation = $5;
n->va_cols = $6;
if (n->va_cols != NIL) /* implies analyze */
@@ -8387,6 +9035,8 @@ AnalyzeStmt:
n->options |= VACOPT_VERBOSE;
n->freeze_min_age = -1;
n->freeze_table_age = -1;
+ n->multixact_freeze_min_age = -1;
+ n->multixact_freeze_table_age = -1;
n->relation = NULL;
n->va_cols = NIL;
$$ = (Node *)n;
@@ -8399,6 +9049,8 @@ AnalyzeStmt:
n->options |= VACOPT_VERBOSE;
n->freeze_min_age = -1;
n->freeze_table_age = -1;
+ n->multixact_freeze_min_age = -1;
+ n->multixact_freeze_table_age = -1;
n->relation = $3;
n->va_cols = $4;
$$ = (Node *)n;
@@ -8630,6 +9282,8 @@ ExplainableStmt:
| DeleteStmt
| DeclareCursorStmt
| CreateAsStmt
+ | CreateMatViewStmt
+ | RefreshMatViewStmt
| ExecuteStmt /* by default all are $$=$1 */
;
@@ -8652,9 +9306,8 @@ explain_option_elem:
;
explain_option_name:
- ColId { $$ = $1; }
+ NonReservedWord { $$ = $1; }
| analyze_keyword { $$ = "analyze"; }
- | VERBOSE { $$ = "verbose"; }
;
explain_option_arg:
@@ -8792,6 +9445,7 @@ ExecuteStmt: EXECUTE name execute_param_clause
n->params = $8;
ctas->query = (Node *) n;
ctas->into = $4;
+ ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
@@ -9145,9 +9799,10 @@ select_with_parens:
* The duplicative productions are annoying, but hard to get rid of without
* creating shift/reduce conflicts.
*
- * FOR UPDATE/SHARE may be before or after LIMIT/OFFSET.
+ * The locking clause (FOR UPDATE etc) may be before or after LIMIT/OFFSET.
* In <=7.2.X, LIMIT/OFFSET had to be after FOR UPDATE
- * We now support both orderings, but prefer LIMIT/OFFSET before FOR UPDATE/SHARE
+ * We now support both orderings, but prefer LIMIT/OFFSET before the locking
+ * clause.
* 2002-08-28 bjm
*/
select_no_parens:
@@ -9223,7 +9878,7 @@ select_clause:
* As with select_no_parens, simple_select cannot have outer parentheses,
* but can have parenthesized subclauses.
*
- * Note that sort clauses cannot be included at this level --- SQL92 requires
+ * Note that sort clauses cannot be included at this level --- SQL requires
* SELECT foo UNION SELECT bar ORDER BY baz
* to be parsed as
* (SELECT foo UNION SELECT bar) ORDER BY baz
@@ -9238,7 +9893,7 @@ select_clause:
* However, this is not checked by the grammar; parse analysis must check it.
*/
simple_select:
- SELECT opt_distinct target_list
+ SELECT opt_distinct opt_target_list
into_clause from_clause where_clause
group_clause having_clause window_clause
{
@@ -9342,6 +9997,7 @@ into_clause:
$$->options = NIL;
$$->onCommit = ONCOMMIT_NOOP;
$$->tableSpaceName = NULL;
+ $$->viewQuery = NULL;
$$->skipData = false;
}
| /*EMPTY*/
@@ -9566,24 +10222,23 @@ for_locking_items:
;
for_locking_item:
- FOR UPDATE locked_rels_list opt_nowait
+ for_locking_strength locked_rels_list opt_nowait
{
LockingClause *n = makeNode(LockingClause);
- n->lockedRels = $3;
- n->forUpdate = TRUE;
- n->noWait = $4;
- $$ = (Node *) n;
- }
- | FOR SHARE locked_rels_list opt_nowait
- {
- LockingClause *n = makeNode(LockingClause);
- n->lockedRels = $3;
- n->forUpdate = FALSE;
- n->noWait = $4;
+ n->lockedRels = $2;
+ n->strength = $1;
+ n->noWait = $3;
$$ = (Node *) n;
}
;
+for_locking_strength:
+ FOR UPDATE { $$ = LCS_FORUPDATE; }
+ | FOR NO KEY UPDATE { $$ = LCS_FORNOKEYUPDATE; }
+ | FOR SHARE { $$ = LCS_FORSHARE; }
+ | FOR KEY SHARE { $$ = LCS_FORKEYSHARE; }
+ ;
+
locked_rels_list:
OF qualified_name_list { $$ = $2; }
| /* EMPTY */ { $$ = NIL; }
@@ -9625,65 +10280,34 @@ 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
+ | func_table func_alias_clause
{
- RangeFunction *n = makeNode(RangeFunction);
- n->funccallnode = $1;
- n->coldeflist = NIL;
+ RangeFunction *n = (RangeFunction *) $1;
+ n->alias = linitial($2);
+ n->coldeflist = lsecond($2);
$$ = (Node *) n;
}
- | func_table alias_clause
+ | LATERAL_P func_table func_alias_clause
{
- RangeFunction *n = makeNode(RangeFunction);
- n->funccallnode = $1;
- n->alias = $2;
- n->coldeflist = NIL;
+ RangeFunction *n = (RangeFunction *) $2;
+ n->lateral = true;
+ n->alias = linitial($3);
+ n->coldeflist = lsecond($3);
$$ = (Node *) n;
}
- | func_table AS '(' TableFuncElementList ')'
- {
- RangeFunction *n = makeNode(RangeFunction);
- n->funccallnode = $1;
- n->coldeflist = $4;
- $$ = (Node *) n;
- }
- | func_table AS ColId '(' TableFuncElementList ')'
- {
- RangeFunction *n = makeNode(RangeFunction);
- Alias *a = makeNode(Alias);
- n->funccallnode = $1;
- a->aliasname = $3;
- n->alias = a;
- n->coldeflist = $5;
- $$ = (Node *) n;
- }
- | func_table ColId '(' TableFuncElementList ')'
- {
- RangeFunction *n = makeNode(RangeFunction);
- Alias *a = makeNode(Alias);
- n->funccallnode = $1;
- a->aliasname = $2;
- n->alias = a;
- n->coldeflist = $4;
- $$ = (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,
@@ -9695,26 +10319,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
@@ -9731,7 +10376,7 @@ table_ref: relation_expr
/*
* It may seem silly to separate joined_table from table_ref, but there is
- * method in SQL92's madness: if you don't do it this way you get reduce-
+ * method in SQL's madness: if you don't do it this way you get reduce-
* reduce conflicts, because it's not clear to the parser generator whether
* to expect alias_clause after ')' or not. For the same reason we must
* treat 'JOIN' and 'join_type JOIN' separately, rather than allowing
@@ -9840,6 +10485,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; }
@@ -9932,8 +10612,55 @@ relation_expr_opt_alias: relation_expr %prec UMINUS
}
;
+/*
+ * func_table represents a function invocation in a FROM list. It can be
+ * a plain function call, like "foo(...)", or a ROWS FROM expression with
+ * one or more function calls, "ROWS FROM (foo(...), bar(...))",
+ * optionally with WITH ORDINALITY attached.
+ * In the ROWS FROM syntax, a column definition list can be given for each
+ * function, for example:
+ * ROWS FROM (foo() AS (foo_res_a text, foo_res_b text),
+ * bar() AS (bar_res_a text, bar_res_b text))
+ * It's also possible to attach a column definition list to the RangeFunction
+ * as a whole, but that's handled by the table_ref production.
+ */
+func_table: func_expr_windowless opt_ordinality
+ {
+ RangeFunction *n = makeNode(RangeFunction);
+ n->lateral = false;
+ n->ordinality = $2;
+ n->is_rowsfrom = false;
+ n->functions = list_make1(list_make2($1, NIL));
+ /* alias and coldeflist are set by table_ref production */
+ $$ = (Node *) n;
+ }
+ | ROWS FROM '(' rowsfrom_list ')' opt_ordinality
+ {
+ RangeFunction *n = makeNode(RangeFunction);
+ n->lateral = false;
+ n->ordinality = $6;
+ n->is_rowsfrom = true;
+ n->functions = $4;
+ /* alias and coldeflist are set by table_ref production */
+ $$ = (Node *) n;
+ }
+ ;
+
+rowsfrom_item: func_expr_windowless opt_col_def_list
+ { $$ = list_make2($1, $2); }
+ ;
-func_table: func_expr { $$ = $1; }
+rowsfrom_list:
+ rowsfrom_item { $$ = list_make1($1); }
+ | rowsfrom_list ',' rowsfrom_item { $$ = lappend($1, $3); }
+ ;
+
+opt_col_def_list: AS '(' TableFuncElementList ')' { $$ = $3; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_ordinality: WITH_ORDINALITY { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
;
@@ -9988,6 +10715,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->collClause = (CollateClause *) $3;
n->collOid = InvalidOid;
n->constraints = NIL;
+ n->location = @1;
$$ = (Node *)n;
}
;
@@ -9995,7 +10723,7 @@ TableFuncElement: ColId Typename opt_collate_clause
/*****************************************************************************
*
* Type syntax
- * SQL92 introduces a large amount of type-specific syntax.
+ * SQL introduces a large amount of type-specific syntax.
* Define individual clauses to handle these cases, and use
* the generic case to handle regular type-extensible Postgres syntax.
* - thomas 1997-10-10
@@ -10121,7 +10849,7 @@ opt_type_modifiers: '(' expr_list ')' { $$ = $2; }
;
/*
- * SQL92 numeric data types
+ * SQL numeric data types
*/
Numeric: INT_P
{
@@ -10211,7 +10939,7 @@ opt_float: '(' Iconst ')'
;
/*
- * SQL92 bit-field data types
+ * SQL bit-field data types
* The following implements BIT() and BIT VARYING().
*/
Bit: BitWithLength
@@ -10268,7 +10996,7 @@ BitWithoutLength:
/*
- * SQL92 character data types
+ * SQL character data types
* The following implements CHAR() and VARCHAR().
*/
Character: CharacterWithLength
@@ -10301,15 +11029,7 @@ ConstCharacter: CharacterWithLength
CharacterWithLength: character '(' Iconst ')' opt_charset
{
if (($5 != NULL) && (strcmp($5, "sql_text") != 0))
- {
- char *type;
-
- type = palloc(strlen($1) + 1 + strlen($5) + 1);
- strcpy(type, $1);
- strcat(type, "_");
- strcat(type, $5);
- $1 = type;
- }
+ $1 = psprintf("%s_%s", $1, $5);
$$ = SystemTypeName($1);
$$->typmods = list_make1(makeIntConst($3, @3));
@@ -10320,15 +11040,7 @@ CharacterWithLength: character '(' Iconst ')' opt_charset
CharacterWithoutLength: character opt_charset
{
if (($2 != NULL) && (strcmp($2, "sql_text") != 0))
- {
- char *type;
-
- type = palloc(strlen($1) + 1 + strlen($2) + 1);
- strcpy(type, $1);
- strcat(type, "_");
- strcat(type, $2);
- $1 = type;
- }
+ $1 = psprintf("%s_%s", $1, $2);
$$ = SystemTypeName($1);
@@ -10365,7 +11077,7 @@ opt_charset:
;
/*
- * SQL92 date/time types
+ * SQL date/time types
*/
ConstDatetime:
TIMESTAMP '(' Iconst ')' opt_timezone
@@ -10525,16 +11237,9 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr AT TIME ZONE a_expr %prec AT
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("timezone");
- n->args = list_make2($5, $1);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
- $$ = (Node *) n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("timezone"),
+ list_make2($5, $1),
+ @2);
}
/*
* These operators must be called out explicitly in order to make use
@@ -10586,118 +11291,70 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($3, $5);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($3, $5),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($3, $5);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($3, $5),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($4, makeNullAConst(-1));
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($4, makeNullAConst(-1)),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($5, makeNullAConst(-1));
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($5, makeNullAConst(-1)),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($5, $7);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($5, $7),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
/* NullTest clause
- * Define SQL92-style Null test clause.
+ * Define SQL-style Null test clause.
* Allow two forms described in the standard:
* a IS NULL
* a IS NOT NULL
@@ -10735,7 +11392,19 @@ a_expr: c_expr { $$ = $1; }
}
| row OVERLAPS row
{
- $$ = (Node *)makeOverlaps($1, $3, @2, yyscanner);
+ if (list_length($1) != 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong number of parameters on left side of OVERLAPS expression"),
+ parser_errposition(@1)));
+ if (list_length($3) != 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong number of parameters on right side of OVERLAPS expression"),
+ parser_errposition(@3)));
+ $$ = (Node *) makeFuncCall(SystemFuncName("overlaps"),
+ list_concat($1, $3),
+ @2);
}
| a_expr IS TRUE_P %prec IS
{
@@ -11055,6 +11724,29 @@ c_expr: columnref { $$ = $1; }
n->location = @1;
$$ = (Node *)n;
}
+ | select_with_parens indirection
+ {
+ /*
+ * Because the select_with_parens nonterminal is designed
+ * to "eat" as many levels of parens as possible, the
+ * '(' a_expr ')' opt_indirection production above will
+ * fail to match a sub-SELECT with indirection decoration;
+ * the sub-SELECT won't be regarded as an a_expr as long
+ * as there are parens around it. To support applying
+ * subscripting or field selection to a sub-SELECT result,
+ * we need this redundant-looking production.
+ */
+ SubLink *n = makeNode(SubLink);
+ A_Indirection *a = makeNode(A_Indirection);
+ n->subLinkType = EXPR_SUBLINK;
+ n->testexpr = NULL;
+ n->operName = NIL;
+ n->subselect = $1;
+ n->location = @1;
+ a->arg = (Node *)n;
+ a->indirection = check_indirection($2, yyscanner);
+ $$ = (Node *)a;
+ }
| EXISTS select_with_parens
{
SubLink *n = makeNode(SubLink);
@@ -11094,144 +11786,134 @@ c_expr: columnref { $$ = $1; }
}
;
-/*
- * func_expr is split out from c_expr just so that we have a classification
- * for "everything that is a function call or looks like one". This isn't
- * very important, but it saves us having to document which variants are
- * legal in the backwards-compatible functional-index syntax for CREATE INDEX.
- * (Note that many of the special SQL functions wouldn't actually make any
- * sense as functional index entries, but we ignore that consideration here.)
- */
-func_expr: func_name '(' ')' over_clause
+func_application: func_name '(' ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = $4;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall($1, NIL, @1);
}
- | func_name '(' func_arg_list ')' over_clause
+ | func_name '(' func_arg_list opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = $5;
- n->location = @1;
+ FuncCall *n = makeFuncCall($1, $3, @1);
+ n->agg_order = $4;
$$ = (Node *)n;
}
- | func_name '(' VARIADIC func_arg_expr ')' over_clause
+ | func_name '(' VARIADIC func_arg_expr opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = list_make1($4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
+ FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
- n->over = $6;
- n->location = @1;
+ n->agg_order = $5;
$$ = (Node *)n;
}
- | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
+ | func_name '(' func_arg_list ',' VARIADIC func_arg_expr opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = lappend($3, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
+ FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
- n->over = $8;
- n->location = @1;
+ n->agg_order = $7;
$$ = (Node *)n;
}
- | func_name '(' func_arg_list sort_clause ')' over_clause
+ | func_name '(' ALL func_arg_list opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
- n->agg_order = $4;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = $6;
- n->location = @1;
- $$ = (Node *)n;
- }
- | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
- {
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $4;
+ FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
- n->func_variadic = FALSE;
- n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
+ | func_name '(' DISTINCT func_arg_list opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $4;
+ FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
- n->agg_star = FALSE;
n->agg_distinct = TRUE;
- n->func_variadic = FALSE;
- n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' '*' ')' over_clause
+ | func_name '(' '*' ')'
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
* aggregate. This does the right thing for COUNT(*),
- * and there are no other aggregates in SQL92 that accept
+ * and there are no other aggregates in SQL that accept
* '*' as parameter.
*
* The FuncCall node is also marked agg_star = true,
* so that later processing can detect what the argument
* really was.
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = NIL;
- n->agg_order = NIL;
+ FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
- | COLLATION FOR '(' a_expr ')'
+ ;
+
+
+/*
+ * func_expr and its cousin func_expr_windowless are split out from c_expr just
+ * so that we have classifications for "everything that is a function call or
+ * looks like one". This isn't very important, but it saves us having to
+ * document which variants are legal in places like "FROM function()" or the
+ * backwards-compatible functional-index syntax for CREATE INDEX.
+ * (Note that many of the special SQL functions wouldn't actually make any
+ * sense as functional index entries, but we ignore that consideration here.)
+ */
+func_expr: func_application within_group_clause filter_clause over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("pg_collation_for");
- n->args = list_make1($4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ FuncCall *n = (FuncCall *) $1;
+ /*
+ * The order clause for WITHIN GROUP and the one for
+ * plain-aggregate ORDER BY share a field, so we have to
+ * check here that at most one is present. We also check
+ * for DISTINCT and VARIADIC here to give a better error
+ * location. Other consistency checks are deferred to
+ * parse analysis.
+ */
+ if ($2 != NIL)
+ {
+ if (n->agg_order != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use multiple ORDER BY clauses with WITHIN GROUP"),
+ parser_errposition(@2)));
+ if (n->agg_distinct)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use DISTINCT with WITHIN GROUP"),
+ parser_errposition(@2)));
+ if (n->func_variadic)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use VARIADIC with WITHIN GROUP"),
+ parser_errposition(@2)));
+ n->agg_order = $2;
+ n->agg_within_group = TRUE;
+ }
+ n->agg_filter = $3;
+ n->over = $4;
+ $$ = (Node *) n;
+ }
+ | func_expr_common_subexpr
+ { $$ = $1; }
+ ;
+
+/*
+ * As func_expr but does not accept WINDOW functions directly
+ * (but they can still be contained in arguments for functions etc).
+ * Use this when window expressions are not allowed, where needed to
+ * disambiguate the grammar (e.g. in CREATE INDEX).
+ */
+func_expr_windowless:
+ func_application { $$ = $1; }
+ | func_expr_common_subexpr { $$ = $1; }
+ ;
+
+/*
+ * Special expressions that are considered to be functions.
+ */
+func_expr_common_subexpr:
+ COLLATION FOR '(' a_expr ')'
+ {
+ $$ = (Node *) makeFuncCall(SystemFuncName("pg_collation_for"),
+ list_make1($4),
+ @1);
}
| CURRENT_DATE
{
@@ -11249,10 +11931,15 @@ func_expr: func_name '(' ')' over_clause
* of type-input conversion functions. (As of PG 7.3
* that is actually possible, but not clear that we want
* to rely on it.)
+ *
+ * The token location is attached to the run-time
+ * typecast, not to the Const, for the convenience of
+ * pg_stat_statements (which doesn't want these constructs
+ * to appear to be replaceable constants).
*/
Node *n;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
- $$ = makeTypeCast(n, SystemTypeName("date"), -1);
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
+ $$ = makeTypeCast(n, SystemTypeName("date"), @1);
}
| CURRENT_TIME
{
@@ -11261,8 +11948,8 @@ func_expr: func_name '(' ')' over_clause
* See comments for CURRENT_DATE.
*/
Node *n;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
- $$ = makeTypeCast(n, SystemTypeName("timetz"), -1);
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
+ $$ = makeTypeCast(n, SystemTypeName("timetz"), @1);
}
| CURRENT_TIME '(' Iconst ')'
{
@@ -11272,10 +11959,10 @@ func_expr: func_name '(' ')' over_clause
*/
Node *n;
TypeName *d;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
d = SystemTypeName("timetz");
d->typmods = list_make1(makeIntConst($3, @3));
- $$ = makeTypeCast(n, d, -1);
+ $$ = makeTypeCast(n, d, @1);
}
| CURRENT_TIMESTAMP
{
@@ -11283,16 +11970,7 @@ func_expr: func_name '(' ')' over_clause
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("now");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("now"), NIL, @1);
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
@@ -11302,10 +11980,10 @@ func_expr: func_name '(' ')' over_clause
*/
Node *n;
TypeName *d;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
d = SystemTypeName("timestamptz");
d->typmods = list_make1(makeIntConst($3, @3));
- $$ = makeTypeCast(n, d, -1);
+ $$ = makeTypeCast(n, d, @1);
}
| LOCALTIME
{
@@ -11314,8 +11992,8 @@ func_expr: func_name '(' ')' over_clause
* See comments for CURRENT_DATE.
*/
Node *n;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
- $$ = makeTypeCast((Node *)n, SystemTypeName("time"), -1);
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
+ $$ = makeTypeCast((Node *)n, SystemTypeName("time"), @1);
}
| LOCALTIME '(' Iconst ')'
{
@@ -11325,10 +12003,10 @@ func_expr: func_name '(' ')' over_clause
*/
Node *n;
TypeName *d;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
d = SystemTypeName("time");
d->typmods = list_make1(makeIntConst($3, @3));
- $$ = makeTypeCast((Node *)n, d, -1);
+ $$ = makeTypeCast((Node *)n, d, @1);
}
| LOCALTIMESTAMP
{
@@ -11337,8 +12015,8 @@ func_expr: func_name '(' ')' over_clause
* See comments for CURRENT_DATE.
*/
Node *n;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
- $$ = makeTypeCast(n, SystemTypeName("timestamp"), -1);
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
+ $$ = makeTypeCast(n, SystemTypeName("timestamp"), @1);
}
| LOCALTIMESTAMP '(' Iconst ')'
{
@@ -11348,103 +12026,40 @@ func_expr: func_name '(' ')' over_clause
*/
Node *n;
TypeName *d;
- n = makeStringConstCast("now", @1, SystemTypeName("text"));
+ n = makeStringConstCast("now", -1, SystemTypeName("text"));
d = SystemTypeName("timestamp");
d->typmods = list_make1(makeIntConst($3, @3));
- $$ = makeTypeCast(n, d, -1);
+ $$ = makeTypeCast(n, d, @1);
}
| CURRENT_ROLE
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| SESSION_USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("session_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("session_user"), NIL, @1);
}
| USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_CATALOG
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_database");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_database"), NIL, @1);
}
| CURRENT_SCHEMA
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_schema");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_schema"), NIL, @1);
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("date_part");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
}
| OVERLAY '(' overlay_list ')'
{
@@ -11453,46 +12068,19 @@ func_expr: func_name '(' ')' over_clause
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("overlay");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("overlay"), $3, @1);
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("position");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1);
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("substring");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("substring"), $3, @1);
}
| TREAT '(' a_expr AS Typename ')'
{
@@ -11501,75 +12089,32 @@ func_expr: func_name '(' ')' over_clause
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
- */
- FuncCall *n = makeNode(FuncCall);
- /* Convert SystemTypeName() to SystemFuncName() even though
+ *
+ * Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
- n->funcname = SystemFuncName(((Value *)llast($5->names))->val.str);
- n->args = list_make1($3);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName(((Value *)llast($5->names))->val.str),
+ list_make1($3),
+ @1);
}
| TRIM '(' BOTH trim_list ')'
{
- /* various trim expressions are defined in SQL92
+ /* various trim expressions are defined in SQL
* - thomas 1997-07-19
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("btrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $4, @1);
}
| TRIM '(' LEADING trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("ltrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"), $4, @1);
}
| TRIM '(' TRAILING trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("rtrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"), $4, @1);
}
| TRIM '(' trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("btrim");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $3, @1);
}
| NULLIF '(' a_expr ',' a_expr ')'
{
@@ -11622,16 +12167,7 @@ func_expr: func_name '(' ')' over_clause
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("xmlexists");
- n->args = list_make2($3, $4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"), list_make2($3, $4), @1);
}
| XMLFOREST '(' xml_attribute_list ')'
{
@@ -11745,6 +12281,20 @@ xmlexists_argument:
/*
+ * Aggregate decoration clauses
+ */
+within_group_clause:
+ WITHIN GROUP_P '(' sort_clause ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+
+/*
* Window Definitions
*/
window_clause:
@@ -12221,7 +12771,7 @@ in_expr: select_with_parens
;
/*
- * Define SQL92-style case clause.
+ * Define SQL-style CASE clause.
* - Full specification
* CASE WHEN a = b THEN c ... ELSE d END
* - Implicit argument
@@ -12351,6 +12901,10 @@ ctext_row: '(' ctext_expr_list ')' { $$ = $2; }
*
*****************************************************************************/
+opt_target_list: target_list { $$ = $1; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
target_list:
target_el { $$ = list_make1($1); }
| target_list ',' target_el { $$ = lappend($1, $3); }
@@ -12527,16 +13081,17 @@ AexprConst: Iconst
t->location = @1;
$$ = makeStringConstCast($2, @2, t);
}
- | func_name '(' func_arg_list ')' Sconst
+ | func_name '(' func_arg_list opt_sort_clause ')' Sconst
{
/* generic syntax with a type modifier */
TypeName *t = makeTypeNameFromNameList($1);
ListCell *lc;
/*
- * We must use func_arg_list in the production to avoid
- * reduce/reduce conflicts, but we don't actually wish
- * to allow NamedArgExpr in this context.
+ * We must use func_arg_list and opt_sort_clause in the
+ * production to avoid reduce/reduce conflicts, but we
+ * don't actually wish to allow NamedArgExpr in this
+ * context, nor ORDER BY.
*/
foreach(lc, $3)
{
@@ -12548,9 +13103,15 @@ AexprConst: Iconst
errmsg("type modifier cannot have parameter name"),
parser_errposition(arg->location)));
}
+ if ($4 != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("type modifier cannot have ORDER BY"),
+ parser_errposition(@4)));
+
t->typmods = $3;
t->location = @1;
- $$ = makeStringConstCast($5, @5, t);
+ $$ = makeStringConstCast($6, @6, t);
}
| ConstTypename Sconst
{
@@ -12595,7 +13156,13 @@ AexprConst: Iconst
Iconst: ICONST { $$ = $1; };
Sconst: SCONST { $$ = $1; };
-RoleId: ColId { $$ = $1; };
+RoleId: NonReservedWord { $$ = $1; };
+
+role_list: RoleId
+ { $$ = list_make1(makeString($1)); }
+ | role_list ',' RoleId
+ { $$ = lappend($1, makeString($3)); }
+ ;
SignedIconst: Iconst { $$ = $1; }
| '+' Iconst { $$ = + $2; }
@@ -12627,6 +13194,14 @@ type_function_name: IDENT { $$ = $1; }
| type_func_name_keyword { $$ = pstrdup($1); }
;
+/* Any not-fully-reserved word --- these names can be, eg, role names.
+ */
+NonReservedWord: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ | col_name_keyword { $$ = pstrdup($1); }
+ | type_func_name_keyword { $$ = pstrdup($1); }
+ ;
+
/* Column label --- allowed labels in "AS" clauses.
* This presently includes *all* Postgres keywords.
*/
@@ -12734,6 +13309,7 @@ unreserved_keyword:
| ENCRYPTED
| ENUM_P
| ESCAPE
+ | EVENT
| EXCLUDE
| EXCLUDING
| EXCLUSIVE
@@ -12742,6 +13318,7 @@ unreserved_keyword:
| EXTENSION
| EXTERNAL
| FAMILY
+ | FILTER
| FIRST_P
| FOLLOWING
| FORCE
@@ -12788,6 +13365,7 @@ unreserved_keyword:
| LOCK_P
| MAPPING
| MATCH
+ | MATERIALIZED
| MAXVALUE
| MINUTE_P
| MINVALUE
@@ -12810,6 +13388,8 @@ unreserved_keyword:
| OPERATOR
| OPTION
| OPTIONS
+ | ORDINALITY
+ | OVER
| OWNED
| OWNER
| PARSER
@@ -12832,6 +13412,7 @@ unreserved_keyword:
| PRIVILEGES
| PROCEDURAL
| PROCEDURE
+ | PROGRAM
| QUOTE
| RANGE
| READ
@@ -12839,6 +13420,7 @@ unreserved_keyword:
| RECHECK
| RECURSIVE
| REF
+ | REFRESH
| REINDEX
| RELATIVE_P
| RELEASE
@@ -12914,8 +13496,10 @@ unreserved_keyword:
| VARYING
| VERSION_P
| VIEW
+ | VIEWS
| VOLATILE
| WHITESPACE_P
+ | WITHIN
| WITHOUT
| WORK
| WRAPPER
@@ -13015,7 +13599,6 @@ type_func_name_keyword:
| NATURAL
| NOTNULL
| OUTER_P
- | OVER
| OVERLAPS
| RIGHT
| SIMILAR
@@ -13072,6 +13655,7 @@ reserved_keyword:
| INITIALLY
| INTERSECT
| INTO
+ | LATERAL_P
| LEADING
| LIMIT
| LOCALTIME
@@ -13288,39 +13872,6 @@ makeBoolAConst(bool state, int location)
return makeTypeCast((Node *)n, SystemTypeName("bool"), -1);
}
-/* makeOverlaps()
- * Create and populate a FuncCall node to support the OVERLAPS operator.
- */
-static FuncCall *
-makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
-{
- FuncCall *n = makeNode(FuncCall);
-
- n->funcname = SystemFuncName("overlaps");
- if (list_length(largs) == 1)
- largs = lappend(largs, largs);
- else if (list_length(largs) != 2)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("wrong number of parameters on left side of OVERLAPS expression"),
- parser_errposition(location)));
- if (list_length(rargs) == 1)
- rargs = lappend(rargs, rargs);
- else if (list_length(rargs) != 2)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("wrong number of parameters on right side of OVERLAPS expression"),
- parser_errposition(location)));
- n->args = list_concat(largs, rargs);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = location;
- return n;
-}
-
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
@@ -13399,6 +13950,56 @@ extractArgTypes(List *parameters)
return result;
}
+/* extractAggrArgTypes()
+ * As above, but work from the output of the aggr_args production.
+ */
+static List *
+extractAggrArgTypes(List *aggrargs)
+{
+ Assert(list_length(aggrargs) == 2);
+ return extractArgTypes((List *) linitial(aggrargs));
+}
+
+/* makeOrderedSetArgs()
+ * Build the result of the aggr_args production (which see the comments for).
+ * This handles only the case where both given lists are nonempty, so that
+ * we have to deal with multiple VARIADIC arguments.
+ */
+static List *
+makeOrderedSetArgs(List *directargs, List *orderedargs,
+ core_yyscan_t yyscanner)
+{
+ FunctionParameter *lastd = (FunctionParameter *) llast(directargs);
+ int ndirectargs;
+
+ /* No restriction unless last direct arg is VARIADIC */
+ if (lastd->mode == FUNC_PARAM_VARIADIC)
+ {
+ FunctionParameter *firsto = (FunctionParameter *) linitial(orderedargs);
+
+ /*
+ * We ignore the names, though the aggr_arg production allows them;
+ * it doesn't allow default values, so those need not be checked.
+ */
+ if (list_length(orderedargs) != 1 ||
+ firsto->mode != FUNC_PARAM_VARIADIC ||
+ !equal(lastd->argType, firsto->argType))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("an ordered-set aggregate with a VARIADIC direct argument must have one VARIADIC aggregated argument of the same data type"),
+ parser_errposition(exprLocation((Node *) firsto))));
+
+ /* OK, drop the duplicate VARIADIC argument from the internal form */
+ orderedargs = NIL;
+ }
+
+ /* don't merge into the next line, as list_concat changes directargs */
+ ndirectargs = list_length(directargs);
+
+ return list_make2(list_concat(directargs, orderedargs),
+ makeInteger(ndirectargs));
+}
+
/* insertSelectOptions()
* Insert ORDER BY, etc into an already-constructed SelectStmt.
*
@@ -13540,13 +14141,7 @@ doNegateFloat(Value *v)
if (*oldval == '-')
v->val.str = oldval+1; /* just strip the '-' */
else
- {
- char *newval = (char *) palloc(strlen(oldval) + 2);
-
- *newval = '-';
- strcpy(newval+1, oldval);
- v->val.str = newval;
- }
+ v->val.str = psprintf("-%s", oldval);
}
static Node *
@@ -13576,6 +14171,7 @@ makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
x->args = args;
/* xmloption, if relevant, must be filled in by caller */
/* type and typmod will be filled in during parse analysis */
+ x->type = InvalidOid; /* marks the node as not analyzed */
x->location = location;
return (Node *) x;
}
@@ -13717,7 +14313,7 @@ SplitColQualList(List *qualList,
static void
processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
- core_yyscan_t yyscanner)
+ bool *no_inherit, core_yyscan_t yyscanner)
{
/* defaults */
if (deferrable)
@@ -13765,6 +14361,79 @@ processCASbits(int cas_bits, int location, const char *constrType,
constrType),
parser_errposition(location)));
}
+
+ if (cas_bits & CAS_NO_INHERIT)
+ {
+ if (no_inherit)
+ *no_inherit = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is CHECK, UNIQUE, or similar */
+ errmsg("%s constraints cannot be marked NO INHERIT",
+ constrType),
+ parser_errposition(location)));
+ }
+}
+
+/*----------
+ * Recursive view transformation
+ *
+ * Convert
+ *
+ * CREATE RECURSIVE VIEW relname (aliases) AS query
+ *
+ * to
+ *
+ * CREATE VIEW relname (aliases) AS
+ * WITH RECURSIVE relname (aliases) AS (query)
+ * SELECT aliases FROM relname
+ *
+ * Actually, just the WITH ... part, which is then inserted into the original
+ * view definition as the query.
+ * ----------
+ */
+static Node *
+makeRecursiveViewSelect(char *relname, List *aliases, Node *query)
+{
+ SelectStmt *s = makeNode(SelectStmt);
+ WithClause *w = makeNode(WithClause);
+ CommonTableExpr *cte = makeNode(CommonTableExpr);
+ List *tl = NIL;
+ ListCell *lc;
+
+ /* create common table expression */
+ cte->ctename = relname;
+ cte->aliascolnames = aliases;
+ cte->ctequery = query;
+ cte->location = -1;
+
+ /* create WITH clause and attach CTE */
+ w->recursive = true;
+ w->ctes = list_make1(cte);
+ w->location = -1;
+
+ /* create target list for the new SELECT from the alias list of the
+ * recursive view specification */
+ foreach (lc, aliases)
+ {
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = makeColumnRef(strVal(lfirst(lc)), NIL, -1, 0);
+ rt->location = -1;
+
+ tl = lappend(tl, rt);
+ }
+
+ /* create new SELECT combining WITH clause, target list, and fake FROM
+ * clause */
+ s->withClause = w;
+ s->targetList = tl;
+ s->fromClause = list_make1(makeRangeVar(NULL, relname, -1));
+
+ return (Node *) s;
}
/* parser_init()
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 5ff6d44924..df9eaab189 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -4,7 +4,7 @@
* lexical token lookup for key words in PostgreSQL
*
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
diff --git a/src/backend/parser/kwlookup.c b/src/backend/parser/kwlookup.c
index 028ca6af41..af05aa70ea 100644
--- a/src/backend/parser/kwlookup.c
+++ b/src/backend/parser/kwlookup.c
@@ -6,7 +6,7 @@
* NB - this file is also used by ECPG and several frontend programs in
* src/bin/ including pg_dump and psql
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -52,7 +52,7 @@ ScanKeywordLookup(const char *text,
return NULL;
/*
- * Apply an ASCII-only downcasing. We must not use tolower() since it may
+ * Apply an ASCII-only downcasing. We must not use tolower() since it may
* produce the wrong translation in some locales (eg, Turkish).
*/
for (i = 0; i < len; i++)
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 24ce14d56b..9e81051893 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -8,7 +8,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Portions Copyright (c) 2012-2014, TransLattice, Inc.
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -19,12 +19,16 @@
*/
#include "postgres.h"
+#include "catalog/pg_aggregate.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/tlist.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
@@ -34,18 +38,34 @@
#include "catalog/pg_aggregate.h"
#include "utils/syscache.h"
#endif
+#include "utils/lsyscache.h"
typedef struct
{
ParseState *pstate;
+ int min_varlevel;
+ int min_agglevel;
+ int sublevels_up;
+} check_agg_arguments_context;
+
+typedef struct
+{
+ ParseState *pstate;
Query *qry;
List *groupClauses;
bool have_non_var_grouping;
List **func_grouped_rels;
int sublevels_up;
+ bool in_agg_direct_args;
} check_ungrouped_columns_context;
+static int check_agg_arguments(ParseState *pstate,
+ List *directargs,
+ List *args,
+ Expr *filter);
+static bool check_agg_arguments_walker(Node *node,
+ check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, bool have_non_var_grouping,
List **func_grouped_rels);
@@ -58,15 +78,21 @@ static bool check_ungrouped_columns_walker(Node *node,
* Finish initial transformation of an aggregate call
*
* parse_func.c has recognized the function as an aggregate, and has set up
- * all the fields of the Aggref except args, aggorder, aggdistinct and
- * agglevelsup. The passed-in args list has been through standard expression
- * transformation, while the passed-in aggorder list hasn't been transformed
- * at all.
+ * all the fields of the Aggref except aggdirectargs, args, aggorder,
+ * aggdistinct and agglevelsup. The passed-in args list has been through
+ * standard expression transformation and type coercion to match the agg's
+ * declared arg types, while the passed-in aggorder list hasn't been
+ * transformed at all.
*
- * Here we convert the args list into a targetlist by inserting TargetEntry
- * nodes, and then transform the aggorder and agg_distinct specifications to
- * produce lists of SortGroupClause nodes. (That might also result in adding
- * resjunk expressions to the targetlist.)
+ * Here we separate the args list into direct and aggregated args, storing the
+ * former in agg->aggdirectargs and the latter in agg->args. The regular
+ * args, but not the direct args, are converted into a targetlist by inserting
+ * TargetEntry nodes. We then transform the aggorder and agg_distinct
+ * specifications to produce lists of SortGroupClause nodes for agg->aggorder
+ * and agg->aggdistinct. (For a regular aggregate, this might result in
+ * adding resjunk expressions to the targetlist; but for ordered-set
+ * aggregates the aggorder list will always be one-to-one with the aggregated
+ * args.)
*
* We must also determine which query level the aggregate actually belongs to,
* set agglevelsup accordingly, and mark p_hasAggs true in the corresponding
@@ -76,10 +102,10 @@ void
transformAggregateCall(ParseState *pstate, Aggref *agg,
List *args, List *aggorder, bool agg_distinct)
{
- List *tlist;
- List *torder;
+ List *tlist = NIL;
+ List *torder = NIL;
List *tdistinct = NIL;
- AttrNumber attno;
+ AttrNumber attno = 1;
int save_next_resno;
int min_varlevel;
ListCell *lc;
@@ -89,66 +115,115 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
Form_pg_aggregate aggform;
#endif /* XCP */
#endif /* PGXC */
+ const char *err;
+ bool errkind;
- /*
- * Transform the plain list of Exprs into a targetlist. We don't bother
- * to assign column names to the entries.
- */
- tlist = NIL;
- attno = 1;
- foreach(lc, args)
+ if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
{
- Expr *arg = (Expr *) lfirst(lc);
- TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false);
+ /*
+ * For an ordered-set agg, the args list includes direct args and
+ * aggregated args; we must split them apart.
+ */
+ int numDirectArgs = list_length(args) - list_length(aggorder);
+ List *aargs;
+ ListCell *lc2;
- tlist = lappend(tlist, tle);
- }
+ Assert(numDirectArgs >= 0);
- /*
- * If we have an ORDER BY, transform it. This will add columns to the
- * tlist if they appear in ORDER BY but weren't already in the arg list.
- * They will be marked resjunk = true so we can tell them apart from
- * regular aggregate arguments later.
- *
- * We need to mess with p_next_resno since it will be used to number any
- * new targetlist entries.
- */
- save_next_resno = pstate->p_next_resno;
- pstate->p_next_resno = attno;
+ aargs = list_copy_tail(args, numDirectArgs);
+ agg->aggdirectargs = list_truncate(args, numDirectArgs);
+
+ /*
+ * Build a tlist from the aggregated args, and make a sortlist entry
+ * for each one. Note that the expressions in the SortBy nodes are
+ * ignored (they are the raw versions of the transformed args); we are
+ * just looking at the sort information in the SortBy nodes.
+ */
+ forboth(lc, aargs, lc2, aggorder)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+ SortBy *sortby = (SortBy *) lfirst(lc2);
+ TargetEntry *tle;
- torder = transformSortClause(pstate,
- aggorder,
- &tlist,
- true /* fix unknowns */ ,
- true /* force SQL99 rules */ );
+ /* We don't bother to assign column names to the entries */
+ tle = makeTargetEntry(arg, attno++, NULL, false);
+ tlist = lappend(tlist, tle);
- /*
- * If we have DISTINCT, transform that to produce a distinctList.
- */
- if (agg_distinct)
+ torder = addTargetToSortList(pstate, tle,
+ torder, tlist, sortby,
+ true /* fix unknowns */ );
+ }
+
+ /* Never any DISTINCT in an ordered-set agg */
+ Assert(!agg_distinct);
+ }
+ else
{
- tdistinct = transformDistinctClause(pstate, &tlist, torder, true);
+ /* Regular aggregate, so it has no direct args */
+ agg->aggdirectargs = NIL;
+
+ /*
+ * Transform the plain list of Exprs into a targetlist.
+ */
+ foreach(lc, args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+ TargetEntry *tle;
+
+ /* We don't bother to assign column names to the entries */
+ tle = makeTargetEntry(arg, attno++, NULL, false);
+ tlist = lappend(tlist, tle);
+ }
/*
- * Remove this check if executor support for hashed distinct for
- * aggregates is ever added.
+ * If we have an ORDER BY, transform it. This will add columns to the
+ * tlist if they appear in ORDER BY but weren't already in the arg
+ * list. They will be marked resjunk = true so we can tell them apart
+ * from regular aggregate arguments later.
+ *
+ * We need to mess with p_next_resno since it will be used to number
+ * any new targetlist entries.
+ */
+ save_next_resno = pstate->p_next_resno;
+ pstate->p_next_resno = attno;
+
+ torder = transformSortClause(pstate,
+ aggorder,
+ &tlist,
+ EXPR_KIND_ORDER_BY,
+ true /* fix unknowns */ ,
+ true /* force SQL99 rules */ );
+
+ /*
+ * If we have DISTINCT, transform that to produce a distinctList.
*/
- foreach(lc, tdistinct)
+ if (agg_distinct)
{
- SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+ tdistinct = transformDistinctClause(pstate, &tlist, torder, true);
- if (!OidIsValid(sortcl->sortop))
+ /*
+ * Remove this check if executor support for hashed distinct for
+ * aggregates is ever added.
+ */
+ foreach(lc, tdistinct)
{
- Node *expr = get_sortgroupclause_expr(sortcl, tlist);
-
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_FUNCTION),
- errmsg("could not identify an ordering operator for type %s",
- format_type_be(exprType(expr))),
- errdetail("Aggregates with DISTINCT must be able to sort their inputs."),
- parser_errposition(pstate, exprLocation(expr))));
+ SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+
+ if (!OidIsValid(sortcl->sortop))
+ {
+ Node *expr = get_sortgroupclause_expr(sortcl, tlist);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an ordering operator for type %s",
+ format_type_be(exprType(expr))),
+ errdetail("Aggregates with DISTINCT must be able to sort their inputs."),
+ parser_errposition(pstate, exprLocation(expr))));
+ }
}
}
+
+ pstate->p_next_resno = save_next_resno;
}
/* Update the Aggref with the transformation results */
@@ -156,45 +231,17 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
agg->aggorder = torder;
agg->aggdistinct = tdistinct;
- pstate->p_next_resno = save_next_resno;
-
- /*
- * The aggregate's level is the same as the level of the lowest-level
- * variable or aggregate in its arguments; or if it contains no variables
- * at all, we presume it to be local.
- */
- min_varlevel = find_minimum_var_level((Node *) agg->args);
-
/*
- * An aggregate can't directly contain another aggregate call of the same
- * level (though outer aggs are okay). We can skip this check if we
- * didn't find any local vars or aggs.
+ * Check the arguments to compute the aggregate's level and detect
+ * improper nesting.
*/
- if (min_varlevel == 0)
- {
- if (pstate->p_hasAggs &&
- checkExprHasAggs((Node *) agg->args))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregate function calls cannot be nested"),
- parser_errposition(pstate,
- locate_agg_of_level((Node *) agg->args, 0))));
- }
-
- /* It can't contain window functions either */
- if (pstate->p_hasWindowFuncs &&
- checkExprHasWindowFuncs((Node *) agg->args))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregate function calls cannot contain window function calls"),
- parser_errposition(pstate,
- locate_windowfunc((Node *) agg->args))));
-
- if (min_varlevel < 0)
- min_varlevel = 0;
+ min_varlevel = check_agg_arguments(pstate,
+ agg->aggdirectargs,
+ agg->args,
+ agg->aggfilter);
agg->agglevelsup = min_varlevel;
- /* Mark the correct pstate as having aggregates */
+ /* Mark the correct pstate level as having aggregates */
while (min_varlevel-- > 0)
pstate = pstate->parentParseState;
pstate->p_hasAggs = true;
@@ -222,6 +269,308 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
ReleaseSysCache(aggTuple);
#endif
#endif
+
+ /*
+ * Check to see if the aggregate function is in an invalid place within
+ * its aggregation query.
+ *
+ * For brevity we support two schemes for reporting an error here: set
+ * "err" to a custom message, or set "errkind" true if the error context
+ * is sufficiently identified by what ParseExprKindName will return, *and*
+ * what it will return is just a SQL keyword. (Otherwise, use a custom
+ * message to avoid creating translation problems.)
+ */
+ err = NULL;
+ errkind = false;
+ switch (pstate->p_expr_kind)
+ {
+ case EXPR_KIND_NONE:
+ Assert(false); /* can't happen */
+ break;
+ case EXPR_KIND_OTHER:
+ /* Accept aggregate here; caller must throw error if wanted */
+ break;
+ case EXPR_KIND_JOIN_ON:
+ case EXPR_KIND_JOIN_USING:
+ err = _("aggregate functions are not allowed in JOIN conditions");
+ break;
+ case EXPR_KIND_FROM_SUBSELECT:
+ /* Should only be possible in a LATERAL subquery */
+ Assert(pstate->p_lateral_active);
+ /* Aggregate scope rules make it worth being explicit here */
+ err = _("aggregate functions are not allowed in FROM clause of their own query level");
+ break;
+ case EXPR_KIND_FROM_FUNCTION:
+ err = _("aggregate functions are not allowed in functions in FROM");
+ break;
+ case EXPR_KIND_WHERE:
+ errkind = true;
+ break;
+ case EXPR_KIND_HAVING:
+ /* okay */
+ break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
+ case EXPR_KIND_WINDOW_PARTITION:
+ /* okay */
+ break;
+ case EXPR_KIND_WINDOW_ORDER:
+ /* okay */
+ break;
+ case EXPR_KIND_WINDOW_FRAME_RANGE:
+ err = _("aggregate functions are not allowed in window RANGE");
+ break;
+ case EXPR_KIND_WINDOW_FRAME_ROWS:
+ err = _("aggregate functions are not allowed in window ROWS");
+ break;
+ case EXPR_KIND_SELECT_TARGET:
+ /* okay */
+ break;
+ case EXPR_KIND_INSERT_TARGET:
+ case EXPR_KIND_UPDATE_SOURCE:
+ case EXPR_KIND_UPDATE_TARGET:
+ errkind = true;
+ break;
+ case EXPR_KIND_GROUP_BY:
+ errkind = true;
+ break;
+ case EXPR_KIND_ORDER_BY:
+ /* okay */
+ break;
+ case EXPR_KIND_DISTINCT_ON:
+ /* okay */
+ break;
+ case EXPR_KIND_LIMIT:
+ case EXPR_KIND_OFFSET:
+ errkind = true;
+ break;
+ case EXPR_KIND_RETURNING:
+ errkind = true;
+ break;
+ case EXPR_KIND_VALUES:
+ errkind = true;
+ break;
+ case EXPR_KIND_CHECK_CONSTRAINT:
+ case EXPR_KIND_DOMAIN_CHECK:
+ err = _("aggregate functions are not allowed in check constraints");
+ break;
+ case EXPR_KIND_COLUMN_DEFAULT:
+ case EXPR_KIND_FUNCTION_DEFAULT:
+ err = _("aggregate functions are not allowed in DEFAULT expressions");
+ break;
+ case EXPR_KIND_INDEX_EXPRESSION:
+ err = _("aggregate functions are not allowed in index expressions");
+ break;
+ case EXPR_KIND_INDEX_PREDICATE:
+ err = _("aggregate functions are not allowed in index predicates");
+ break;
+ case EXPR_KIND_ALTER_COL_TRANSFORM:
+ err = _("aggregate functions are not allowed in transform expressions");
+ break;
+ case EXPR_KIND_EXECUTE_PARAMETER:
+ err = _("aggregate functions are not allowed in EXECUTE parameters");
+ break;
+ case EXPR_KIND_TRIGGER_WHEN:
+ err = _("aggregate functions are not allowed in trigger WHEN conditions");
+ break;
+
+ /*
+ * There is intentionally no default: case here, so that the
+ * compiler will warn if we add a new ParseExprKind without
+ * extending this switch. If we do see an unrecognized value at
+ * runtime, the behavior will be the same as for EXPR_KIND_OTHER,
+ * which is sane anyway.
+ */
+ }
+ if (err)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg_internal("%s", err),
+ parser_errposition(pstate, agg->location)));
+ if (errkind)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ /* translator: %s is name of a SQL construct, eg GROUP BY */
+ errmsg("aggregate functions are not allowed in %s",
+ ParseExprKindName(pstate->p_expr_kind)),
+ parser_errposition(pstate, agg->location)));
+}
+
+/*
+ * check_agg_arguments
+ * Scan the arguments of an aggregate function to determine the
+ * aggregate's semantic level (zero is the current select's level,
+ * one is its parent, etc).
+ *
+ * The aggregate's level is the same as the level of the lowest-level variable
+ * or aggregate in its aggregated arguments (including any ORDER BY columns)
+ * or filter expression; or if it contains no variables at all, we presume it
+ * to be local.
+ *
+ * Vars/Aggs in direct arguments are *not* counted towards determining the
+ * agg's level, as those arguments aren't evaluated per-row but only
+ * per-group, and so in some sense aren't really agg arguments. However,
+ * this can mean that we decide an agg is upper-level even when its direct
+ * args contain lower-level Vars/Aggs, and that case has to be disallowed.
+ * (This is a little strange, but the SQL standard seems pretty definite that
+ * direct args are not to be considered when setting the agg's level.)
+ *
+ * We also take this opportunity to detect any aggregates or window functions
+ * nested within the arguments. We can throw error immediately if we find
+ * a window function. Aggregates are a bit trickier because it's only an
+ * error if the inner aggregate is of the same semantic level as the outer,
+ * which we can't know until we finish scanning the arguments.
+ */
+static int
+check_agg_arguments(ParseState *pstate,
+ List *directargs,
+ List *args,
+ Expr *filter)
+{
+ int agglevel;
+ check_agg_arguments_context context;
+
+ context.pstate = pstate;
+ context.min_varlevel = -1; /* signifies nothing found yet */
+ context.min_agglevel = -1;
+ context.sublevels_up = 0;
+
+ (void) expression_tree_walker((Node *) args,
+ check_agg_arguments_walker,
+ (void *) &context);
+
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
+ /*
+ * If we found no vars nor aggs at all, it's a level-zero aggregate;
+ * otherwise, its level is the minimum of vars or aggs.
+ */
+ if (context.min_varlevel < 0)
+ {
+ if (context.min_agglevel < 0)
+ agglevel = 0;
+ else
+ agglevel = context.min_agglevel;
+ }
+ else if (context.min_agglevel < 0)
+ agglevel = context.min_varlevel;
+ else
+ agglevel = Min(context.min_varlevel, context.min_agglevel);
+
+ /*
+ * If there's a nested aggregate of the same semantic level, complain.
+ */
+ if (agglevel == context.min_agglevel)
+ {
+ int aggloc;
+
+ aggloc = locate_agg_of_level((Node *) args, agglevel);
+ if (aggloc < 0)
+ aggloc = locate_agg_of_level((Node *) filter, agglevel);
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("aggregate function calls cannot be nested"),
+ parser_errposition(pstate, aggloc)));
+ }
+
+ /*
+ * Now check for vars/aggs in the direct arguments, and throw error if
+ * needed. Note that we allow a Var of the agg's semantic level, but not
+ * an Agg of that level. In principle such Aggs could probably be
+ * supported, but it would create an ordering dependency among the
+ * aggregates at execution time. Since the case appears neither to be
+ * required by spec nor particularly useful, we just treat it as a
+ * nested-aggregate situation.
+ */
+ if (directargs)
+ {
+ context.min_varlevel = -1;
+ context.min_agglevel = -1;
+ (void) expression_tree_walker((Node *) directargs,
+ check_agg_arguments_walker,
+ (void *) &context);
+ if (context.min_varlevel >= 0 && context.min_varlevel < agglevel)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("outer-level aggregate cannot contain a lower-level variable in its direct arguments"),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) directargs,
+ context.min_varlevel))));
+ if (context.min_agglevel >= 0 && context.min_agglevel <= agglevel)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("aggregate function calls cannot be nested"),
+ parser_errposition(pstate,
+ locate_agg_of_level((Node *) directargs,
+ context.min_agglevel))));
+ }
+
+ return agglevel;
+}
+
+static bool
+check_agg_arguments_walker(Node *node,
+ check_agg_arguments_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ int varlevelsup = ((Var *) node)->varlevelsup;
+
+ /* convert levelsup to frame of reference of original query */
+ varlevelsup -= context->sublevels_up;
+ /* ignore local vars of subqueries */
+ if (varlevelsup >= 0)
+ {
+ if (context->min_varlevel < 0 ||
+ context->min_varlevel > varlevelsup)
+ context->min_varlevel = varlevelsup;
+ }
+ return false;
+ }
+ if (IsA(node, Aggref))
+ {
+ int agglevelsup = ((Aggref *) node)->agglevelsup;
+
+ /* convert levelsup to frame of reference of original query */
+ agglevelsup -= context->sublevels_up;
+ /* ignore local aggs of subqueries */
+ if (agglevelsup >= 0)
+ {
+ if (context->min_agglevel < 0 ||
+ context->min_agglevel > agglevelsup)
+ context->min_agglevel = agglevelsup;
+ }
+ /* no need to examine args of the inner aggregate */
+ return false;
+ }
+ /* We can throw error on sight for a window function */
+ if (IsA(node, WindowFunc))
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("aggregate function calls cannot contain window function calls"),
+ parser_errposition(context->pstate,
+ ((WindowFunc *) node)->location)));
+ if (IsA(node, Query))
+ {
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node,
+ check_agg_arguments_walker,
+ (void *) context,
+ 0);
+ context->sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node,
+ check_agg_arguments_walker,
+ (void *) context);
}
/*
@@ -239,12 +588,19 @@ void
transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
WindowDef *windef)
{
+ const char *err;
+ bool errkind;
+
/*
* A window function call can't contain another one (but aggs are OK). XXX
* is this required by spec, or just an unimplemented feature?
+ *
+ * Note: we don't need to check the filter expression here, because the
+ * context checks done below and in transformAggregateCall would have
+ * already rejected any window funcs or aggs within the filter.
*/
if (pstate->p_hasWindowFuncs &&
- checkExprHasWindowFuncs((Node *) wfunc->args))
+ contain_windowfuncs((Node *) wfunc->args))
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("window function calls cannot be nested"),
@@ -252,6 +608,124 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
locate_windowfunc((Node *) wfunc->args))));
/*
+ * Check to see if the window function is in an invalid place within the
+ * query.
+ *
+ * For brevity we support two schemes for reporting an error here: set
+ * "err" to a custom message, or set "errkind" true if the error context
+ * is sufficiently identified by what ParseExprKindName will return, *and*
+ * what it will return is just a SQL keyword. (Otherwise, use a custom
+ * message to avoid creating translation problems.)
+ */
+ err = NULL;
+ errkind = false;
+ switch (pstate->p_expr_kind)
+ {
+ case EXPR_KIND_NONE:
+ Assert(false); /* can't happen */
+ break;
+ case EXPR_KIND_OTHER:
+ /* Accept window func here; caller must throw error if wanted */
+ break;
+ case EXPR_KIND_JOIN_ON:
+ case EXPR_KIND_JOIN_USING:
+ err = _("window functions are not allowed in JOIN conditions");
+ break;
+ case EXPR_KIND_FROM_SUBSELECT:
+ /* can't get here, but just in case, throw an error */
+ errkind = true;
+ break;
+ case EXPR_KIND_FROM_FUNCTION:
+ err = _("window functions are not allowed in functions in FROM");
+ break;
+ case EXPR_KIND_WHERE:
+ errkind = true;
+ break;
+ case EXPR_KIND_HAVING:
+ errkind = true;
+ break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
+ case EXPR_KIND_WINDOW_PARTITION:
+ case EXPR_KIND_WINDOW_ORDER:
+ case EXPR_KIND_WINDOW_FRAME_RANGE:
+ case EXPR_KIND_WINDOW_FRAME_ROWS:
+ err = _("window functions are not allowed in window definitions");
+ break;
+ case EXPR_KIND_SELECT_TARGET:
+ /* okay */
+ break;
+ case EXPR_KIND_INSERT_TARGET:
+ case EXPR_KIND_UPDATE_SOURCE:
+ case EXPR_KIND_UPDATE_TARGET:
+ errkind = true;
+ break;
+ case EXPR_KIND_GROUP_BY:
+ errkind = true;
+ break;
+ case EXPR_KIND_ORDER_BY:
+ /* okay */
+ break;
+ case EXPR_KIND_DISTINCT_ON:
+ /* okay */
+ break;
+ case EXPR_KIND_LIMIT:
+ case EXPR_KIND_OFFSET:
+ errkind = true;
+ break;
+ case EXPR_KIND_RETURNING:
+ errkind = true;
+ break;
+ case EXPR_KIND_VALUES:
+ errkind = true;
+ break;
+ case EXPR_KIND_CHECK_CONSTRAINT:
+ case EXPR_KIND_DOMAIN_CHECK:
+ err = _("window functions are not allowed in check constraints");
+ break;
+ case EXPR_KIND_COLUMN_DEFAULT:
+ case EXPR_KIND_FUNCTION_DEFAULT:
+ err = _("window functions are not allowed in DEFAULT expressions");
+ break;
+ case EXPR_KIND_INDEX_EXPRESSION:
+ err = _("window functions are not allowed in index expressions");
+ break;
+ case EXPR_KIND_INDEX_PREDICATE:
+ err = _("window functions are not allowed in index predicates");
+ break;
+ case EXPR_KIND_ALTER_COL_TRANSFORM:
+ err = _("window functions are not allowed in transform expressions");
+ break;
+ case EXPR_KIND_EXECUTE_PARAMETER:
+ err = _("window functions are not allowed in EXECUTE parameters");
+ break;
+ case EXPR_KIND_TRIGGER_WHEN:
+ err = _("window functions are not allowed in trigger WHEN conditions");
+ break;
+
+ /*
+ * There is intentionally no default: case here, so that the
+ * compiler will warn if we add a new ParseExprKind without
+ * extending this switch. If we do see an unrecognized value at
+ * runtime, the behavior will be the same as for EXPR_KIND_OTHER,
+ * which is sane anyway.
+ */
+ }
+ if (err)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg_internal("%s", err),
+ parser_errposition(pstate, wfunc->location)));
+ if (errkind)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ /* translator: %s is name of a SQL construct, eg GROUP BY */
+ errmsg("window functions are not allowed in %s",
+ ParseExprKindName(pstate->p_expr_kind)),
+ parser_errposition(pstate, wfunc->location)));
+
+ /*
* If the OVER clause just specifies a window name, find that WINDOW
* clause (which had better be present). Otherwise, try to match all the
* properties of the OVER clause, and make a new entry in the p_windowdefs
@@ -325,11 +799,14 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
/*
* parseCheckAggregates
* Check for aggregates where they shouldn't be and improper grouping.
+ * This function should be called after the target list and qualifications
+ * are finalized.
*
- * Ideally this should be done earlier, but it's difficult to distinguish
- * aggregates from plain functions at the grammar level. So instead we
- * check here. This function should be called after the target list and
- * qualifications are finalized.
+ * Misplaced aggregates are now mostly detected in transformAggregateCall,
+ * but it seems more robust to check for aggregates in recursive queries
+ * only after everything is finalized. In any case it's hard to detect
+ * improper grouping on-the-fly, so we have to make another pass over the
+ * query for that.
*/
void
parseCheckAggregates(ParseState *pstate, Query *qry)
@@ -362,31 +839,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
}
/*
- * Aggregates must never appear in WHERE or JOIN/ON clauses.
- *
- * (Note this check should appear first to deliver an appropriate error
- * message; otherwise we are likely to complain about some innocent
- * variable in the target list, which is outright misleading if the
- * problem is in WHERE.)
- */
- if (checkExprHasAggs(qry->jointree->quals))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregates not allowed in WHERE clause"),
- parser_errposition(pstate,
- locate_agg_of_level(qry->jointree->quals, 0))));
- if (checkExprHasAggs((Node *) qry->jointree->fromlist))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregates not allowed in JOIN conditions"),
- parser_errposition(pstate,
- locate_agg_of_level((Node *) qry->jointree->fromlist, 0))));
-
- /*
- * No aggregates allowed in GROUP BY clauses, either.
- *
- * While we are at it, build a list of the acceptable GROUP BY expressions
- * for use by check_ungrouped_columns().
+ * Build a list of the acceptable GROUP BY expressions for use by
+ * check_ungrouped_columns().
*/
foreach(l, qry->groupClause)
{
@@ -396,19 +850,13 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
expr = get_sortgroupclause_expr(grpcl, qry->targetList);
if (expr == NULL)
continue; /* probably cannot happen */
- if (checkExprHasAggs(expr))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("aggregates not allowed in GROUP BY clause"),
- parser_errposition(pstate,
- locate_agg_of_level(expr, 0))));
groupClauses = lcons(expr, groupClauses);
}
/*
* If there are join alias vars involved, we have to flatten them to the
* underlying vars, so that aliased and unaliased vars will be correctly
- * taken as equal. We can skip the expense of doing this if no rangetable
+ * taken as equal. We can skip the expense of doing this if no rangetable
* entries are RTE_JOIN kind. We use the planner's flatten_join_alias_vars
* routine to do the flattening; it wants a PlannerInfo root node, which
* fortunately can be mostly dummy.
@@ -447,7 +895,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
*
* Note: because we check resjunk tlist elements as well as regular ones,
* this will also find ungrouped variables that came from ORDER BY and
- * WINDOW clauses. For that matter, it's also going to examine the
+ * WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*/
clause = (Node *) qry->targetList;
@@ -470,97 +918,12 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
if (pstate->p_hasAggs && hasSelfRefRTEs)
ereport(ERROR,
(errcode(ERRCODE_INVALID_RECURSION),
- errmsg("aggregate functions not allowed in a recursive query's recursive term"),
+ errmsg("aggregate functions are not allowed in a recursive query's recursive term"),
parser_errposition(pstate,
locate_agg_of_level((Node *) qry, 0))));
}
/*
- * parseCheckWindowFuncs
- * Check for window functions where they shouldn't be.
- *
- * We have to forbid window functions in WHERE, JOIN/ON, HAVING, GROUP BY,
- * and window specifications. (Other clauses, such as RETURNING and LIMIT,
- * have already been checked.) Transformation of all these clauses must
- * be completed already.
- */
-void
-parseCheckWindowFuncs(ParseState *pstate, Query *qry)
-{
- ListCell *l;
-
- /* This should only be called if we found window functions */
- Assert(pstate->p_hasWindowFuncs);
-
- if (checkExprHasWindowFuncs(qry->jointree->quals))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window functions not allowed in WHERE clause"),
- parser_errposition(pstate,
- locate_windowfunc(qry->jointree->quals))));
- if (checkExprHasWindowFuncs((Node *) qry->jointree->fromlist))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window functions not allowed in JOIN conditions"),
- parser_errposition(pstate,
- locate_windowfunc((Node *) qry->jointree->fromlist))));
- if (checkExprHasWindowFuncs(qry->havingQual))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window functions not allowed in HAVING clause"),
- parser_errposition(pstate,
- locate_windowfunc(qry->havingQual))));
-
- foreach(l, qry->groupClause)
- {
- SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
- Node *expr;
-
- expr = get_sortgroupclause_expr(grpcl, qry->targetList);
- if (checkExprHasWindowFuncs(expr))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window functions not allowed in GROUP BY clause"),
- parser_errposition(pstate,
- locate_windowfunc(expr))));
- }
-
- foreach(l, qry->windowClause)
- {
- WindowClause *wc = (WindowClause *) lfirst(l);
- ListCell *l2;
-
- foreach(l2, wc->partitionClause)
- {
- SortGroupClause *grpcl = (SortGroupClause *) lfirst(l2);
- Node *expr;
-
- expr = get_sortgroupclause_expr(grpcl, qry->targetList);
- if (checkExprHasWindowFuncs(expr))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window functions not allowed in window definition"),
- parser_errposition(pstate,
- locate_windowfunc(expr))));
- }
- foreach(l2, wc->orderClause)
- {
- SortGroupClause *grpcl = (SortGroupClause *) lfirst(l2);
- Node *expr;
-
- expr = get_sortgroupclause_expr(grpcl, qry->targetList);
- if (checkExprHasWindowFuncs(expr))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window functions not allowed in window definition"),
- parser_errposition(pstate,
- locate_windowfunc(expr))));
- }
- /* startOffset and limitOffset were checked in transformFrameOffset */
- }
-}
-
-/*
* check_ungrouped_columns -
* Scan the given expression tree for ungrouped variables (variables
* that are not listed in the groupClauses list and are not within
@@ -594,6 +957,7 @@ check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
context.have_non_var_grouping = have_non_var_grouping;
context.func_grouped_rels = func_grouped_rels;
context.sublevels_up = 0;
+ context.in_agg_direct_args = false;
check_ungrouped_columns_walker(node, &context);
}
@@ -609,17 +973,39 @@ check_ungrouped_columns_walker(Node *node,
IsA(node, Param))
return false; /* constants are always acceptable */
- /*
- * If we find an aggregate call of the original level, do not recurse into
- * its arguments; ungrouped vars in the arguments are not an error. We can
- * also skip looking at the arguments of aggregates of higher levels,
- * since they could not possibly contain Vars that are of concern to us
- * (see transformAggregateCall). We do need to look into the arguments of
- * aggregates of lower levels, however.
- */
- if (IsA(node, Aggref) &&
- (int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
- return false;
+ if (IsA(node, Aggref))
+ {
+ Aggref *agg = (Aggref *) node;
+
+ if ((int) agg->agglevelsup == context->sublevels_up)
+ {
+ /*
+ * If we find an aggregate call of the original level, do not
+ * recurse into its normal arguments, ORDER BY arguments, or
+ * filter; ungrouped vars there are not an error. But we should
+ * check direct arguments as though they weren't in an aggregate.
+ * We set a special flag in the context to help produce a useful
+ * error message for ungrouped vars in direct arguments.
+ */
+ bool result;
+
+ Assert(!context->in_agg_direct_args);
+ context->in_agg_direct_args = true;
+ result = check_ungrouped_columns_walker((Node *) agg->aggdirectargs,
+ context);
+ context->in_agg_direct_args = false;
+ return result;
+ }
+
+ /*
+ * We can skip recursing into aggregates of higher levels altogether,
+ * since they could not possibly contain Vars of concern to us (see
+ * transformAggregateCall). We do need to look at aggregates of lower
+ * levels, however.
+ */
+ if ((int) agg->agglevelsup > context->sublevels_up)
+ return false;
+ }
/*
* If we have any GROUP BY items that are not simple Vars, check to see if
@@ -640,7 +1026,7 @@ check_ungrouped_columns_walker(Node *node,
/*
* If we have an ungrouped Var of the original query level, we have a
* failure. Vars below the original query level are not a problem, and
- * neither are Vars from above it. (If such Vars are ungrouped as far as
+ * neither are Vars from above it. (If such Vars are ungrouped as far as
* their own query level is concerned, that's someone else's problem...)
*/
if (IsA(node, Var))
@@ -671,7 +1057,7 @@ check_ungrouped_columns_walker(Node *node,
/*
* Check whether the Var is known functionally dependent on the GROUP
- * BY columns. If so, we can allow the Var to be used, because the
+ * BY columns. If so, we can allow the Var to be used, because the
* grouping is really a no-op for this table. However, this deduction
* depends on one or more constraints of the table, so we have to add
* those constraints to the query's constraintDeps list, because it's
@@ -712,6 +1098,8 @@ check_ungrouped_columns_walker(Node *node,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("column \"%s.%s\" must appear in the GROUP BY clause or be used in an aggregate function",
rte->eref->aliasname, attname),
+ context->in_agg_direct_args ?
+ errdetail("Direct arguments of an ordered-set aggregate must use only grouped columns.") : 0,
parser_errposition(context->pstate, var->location)));
else
ereport(ERROR,
@@ -739,6 +1127,93 @@ check_ungrouped_columns_walker(Node *node,
}
/*
+ * get_aggregate_argtypes
+ * Identify the specific datatypes passed to an aggregate call.
+ *
+ * Given an Aggref, extract the actual datatypes of the input arguments.
+ * The input datatypes are reported in a way that matches up with the
+ * aggregate's declaration, ie, any ORDER BY columns attached to a plain
+ * aggregate are ignored, but we report both direct and aggregated args of
+ * an ordered-set aggregate.
+ *
+ * Datatypes are returned into inputTypes[], which must reference an array
+ * of length FUNC_MAX_ARGS.
+ *
+ * The function result is the number of actual arguments.
+ */
+int
+get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes)
+{
+ int numArguments = 0;
+ ListCell *lc;
+
+ /* Any direct arguments of an ordered-set aggregate come first */
+ foreach(lc, aggref->aggdirectargs)
+ {
+ Node *expr = (Node *) lfirst(lc);
+
+ inputTypes[numArguments] = exprType(expr);
+ numArguments++;
+ }
+
+ /* Now get the regular (aggregated) arguments */
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ /* Ignore ordering columns of a plain aggregate */
+ if (tle->resjunk)
+ continue;
+
+ inputTypes[numArguments] = exprType((Node *) tle->expr);
+ numArguments++;
+ }
+
+ return numArguments;
+}
+
+/*
+ * resolve_aggregate_transtype
+ * Identify the transition state value's datatype for an aggregate call.
+ *
+ * This function resolves a polymorphic aggregate's state datatype.
+ * It must be passed the aggtranstype from the aggregate's catalog entry,
+ * as well as the actual argument types extracted by get_aggregate_argtypes.
+ * (We could fetch these values internally, but for all existing callers that
+ * would just duplicate work the caller has to do too, so we pass them in.)
+ */
+Oid
+resolve_aggregate_transtype(Oid aggfuncid,
+ Oid aggtranstype,
+ Oid *inputTypes,
+ int numArguments)
+{
+ /* resolve actual type of transition state, if polymorphic */
+ if (IsPolymorphicType(aggtranstype))
+ {
+ /* have to fetch the agg's declared input types... */
+ Oid *declaredArgTypes;
+ int agg_nargs;
+
+ (void) get_func_signature(aggfuncid, &declaredArgTypes, &agg_nargs);
+
+ /*
+ * VARIADIC ANY aggs could have more actual than declared args, but
+ * such extra args can't affect polymorphic type resolution.
+ */
+ Assert(agg_nargs <= numArguments);
+
+ aggtranstype = enforce_generic_type_consistency(inputTypes,
+ declaredArgTypes,
+ agg_nargs,
+ aggtranstype,
+ false);
+ pfree(declaredArgTypes);
+ }
+ return aggtranstype;
+}
+
+/*
* Create expression trees for the transition and final functions
* of an aggregate. These are needed so that polymorphic functions
* can be used within an aggregate --- without the expression trees,
@@ -751,15 +1226,23 @@ check_ungrouped_columns_walker(Node *node,
* resolved to actual types (ie, none should ever be ANYELEMENT etc).
* agg_input_collation is the aggregate function's input collation.
*
- * transfn_oid and finalfn_oid identify the funcs to be called; the latter
- * may be InvalidOid.
+ * For an ordered-set aggregate, remember that agg_input_types describes
+ * the direct arguments followed by the aggregated arguments.
+ *
+ * transfn_oid, invtransfn_oid and finalfn_oid identify the funcs to be
+ * called; the latter two may be InvalidOid.
*
- * Pointers to the constructed trees are returned into *transfnexpr and
- * *finalfnexpr. The latter is set to NULL if there's no finalfn.
+ * Pointers to the constructed trees are returned into *transfnexpr,
+ * *invtransfnexpr and *finalfnexpr. If there is no invtransfn or finalfn,
+ * the respective pointers are set to NULL. Since use of the invtransfn is
+ * optional, NULL may be passed for invtransfnexpr.
*/
void
build_aggregate_fnexprs(Oid *agg_input_types,
int agg_num_inputs,
+ int agg_num_direct_inputs,
+ int num_finalfn_inputs,
+ bool agg_variadic,
Oid agg_state_type,
#ifdef XCP
Oid agg_collect_type,
@@ -770,15 +1253,18 @@ build_aggregate_fnexprs(Oid *agg_input_types,
#ifdef XCP
Oid collectfn_oid,
#endif
+ Oid invtransfn_oid,
Oid finalfn_oid,
Expr **transfnexpr,
#ifdef XCP
Expr **collectfnexpr,
#endif
+ Expr **invtransfnexpr,
Expr **finalfnexpr)
{
Param *argp;
List *args;
+ FuncExpr *fexpr;
int i;
/*
@@ -797,7 +1283,7 @@ build_aggregate_fnexprs(Oid *agg_input_types,
args = list_make1(argp);
- for (i = 0; i < agg_num_inputs; i++)
+ for (i = agg_num_direct_inputs; i < agg_num_inputs; i++)
{
argp = makeNode(Param);
argp->paramkind = PARAM_EXEC;
@@ -809,12 +1295,34 @@ build_aggregate_fnexprs(Oid *agg_input_types,
args = lappend(args, argp);
}
- *transfnexpr = (Expr *) makeFuncExpr(transfn_oid,
- agg_state_type,
- args,
- InvalidOid,
- agg_input_collation,
- COERCE_DONTCARE);
+ fexpr = makeFuncExpr(transfn_oid,
+ agg_state_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *transfnexpr = (Expr *) fexpr;
+
+ /*
+ * Build invtransfn expression if requested, with same args as transfn
+ */
+ if (invtransfnexpr != NULL)
+ {
+ if (OidIsValid(invtransfn_oid))
+ {
+ fexpr = makeFuncExpr(invtransfn_oid,
+ agg_state_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *invtransfnexpr = (Expr *) fexpr;
+ }
+ else
+ *invtransfnexpr = NULL;
+ }
#ifdef XCP
/* see if we have a collect function */
@@ -844,7 +1352,7 @@ build_aggregate_fnexprs(Oid *agg_input_types,
args,
InvalidOid,
agg_input_collation,
- COERCE_DONTCARE);
+ COERCE_EXPLICIT_CALL);
}
else
*collectfnexpr = NULL;
@@ -878,10 +1386,24 @@ build_aggregate_fnexprs(Oid *agg_input_types,
argp->location = -1;
args = list_make1(argp);
+ /* finalfn may take additional args, which match agg's input types */
+ for (i = 0; i < num_finalfn_inputs - 1; i++)
+ {
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_input_types[i];
+ argp->paramtypmod = -1;
+ argp->paramcollid = agg_input_collation;
+ argp->location = -1;
+ args = lappend(args, argp);
+ }
+
*finalfnexpr = (Expr *) makeFuncExpr(finalfn_oid,
agg_result_type,
args,
InvalidOid,
agg_input_collation,
- COERCE_DONTCARE);
+ COERCE_EXPLICIT_CALL);
+ /* finalfn is currently never treated as variadic */
}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 97ab9d5581..fcee1379c0 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -3,7 +3,7 @@
* parse_clause.c
* handle clauses in parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -21,10 +21,10 @@
#include "commands/defrem.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
+#include "parser/parser.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -38,16 +38,8 @@
#include "utils/rel.h"
-/* clause types for findTargetlistEntrySQL92 */
-#define ORDER_CLAUSE 0
-#define GROUP_CLAUSE 1
-#define DISTINCT_ON_CLAUSE 2
-
-static const char *const clauseText[] = {
- "ORDER BY",
- "GROUP BY",
- "DISTINCT ON"
-};
+/* Convenience macro for the most common makeNamespaceItem() case */
+#define makeDefaultNSItem(rte) makeNamespaceItem(rte, true, true, false, true)
static void extractRemainingColumns(List *common_colnames,
List *src_colnames, List *src_colvars,
@@ -56,10 +48,7 @@ static Node *transformJoinUsingClause(ParseState *pstate,
RangeTblEntry *leftRTE, RangeTblEntry *rightRTE,
List *leftVars, List *rightVars);
static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
- RangeTblEntry *l_rte,
- RangeTblEntry *r_rte,
- List *relnamespace,
- Relids containedRels);
+ List *namespace);
static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
static RangeTblEntry *transformCTEReference(ParseState *pstate, RangeVar *r,
CommonTableExpr *cte, Index levelsup);
@@ -69,21 +58,23 @@ 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 **namespace);
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
Var *l_colvar, Var *r_colvar);
+static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
+ bool rel_visible, bool cols_visible,
+ bool lateral_only, bool lateral_ok);
+static void setNamespaceColumnVisibility(List *namespace, bool cols_visible);
+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,
- List **tlist, int clause);
+ List **tlist, ParseExprKind exprKind);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
- List **tlist);
+ List **tlist, ParseExprKind exprKind);
static int get_matching_location(int sortgroupref,
List *sortgrouprefs, List *exprs);
-static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
- List *sortlist, List *targetlist, SortBy *sortby,
- bool resolveUnknown);
static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
List *grouplist, List *targetlist, int location,
bool resolveUnknown);
@@ -95,17 +86,12 @@ static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
/*
* transformFromClause -
* Process the FROM clause and add items to the query's range table,
- * joinlist, and namespaces.
+ * joinlist, and namespace.
*
- * Note: we assume that pstate's p_rtable, p_joinlist, p_relnamespace, and
- * p_varnamespace lists were initialized to NIL when the pstate was created.
+ * Note: we assume that the pstate's p_rtable, p_joinlist, and p_namespace
+ * 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)
@@ -116,28 +102,39 @@ transformFromClause(ParseState *pstate, List *frmList)
* The grammar will have produced a list of RangeVars, RangeSubselects,
* 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.
+ * to the joinlist and namespace.
+ *
+ * Note we must process the items left-to-right for proper handling of
+ * LATERAL references.
*/
foreach(fl, frmList)
{
Node *n = lfirst(fl);
RangeTblEntry *rte;
int rtindex;
- List *relnamespace;
- Relids containedRels;
+ List *namespace;
n = transformFromClauseItem(pstate, n,
&rte,
&rtindex,
- &relnamespace,
- &containedRels);
- checkNameSpaceConflicts(pstate, pstate->p_relnamespace, relnamespace);
+ &namespace);
+
+ checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
+
+ /* Mark the new namespace items as visible only to LATERAL */
+ setNamespaceLateralState(namespace, true, true);
+
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_namespace = list_concat(pstate->p_namespace, namespace);
}
+
+ /*
+ * 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_namespace, false, true);
}
/*
@@ -152,7 +149,7 @@ transformFromClause(ParseState *pstate, List *frmList)
*
* If alsoSource is true, add the target to the query's joinlist and
* namespace. For INSERT, we don't want the target to be joined to;
- * it's a destination of tuples, not a source. For UPDATE/DELETE,
+ * it's a destination of tuples, not a source. For UPDATE/DELETE,
* we do need to scan or join the target. (NOTE: we do not bother
* to check for namespace conflict; we assume that the namespace was
* initially empty in these cases.)
@@ -206,7 +203,11 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
rte->requiredPerms = requiredPerms;
/*
- * If UPDATE/DELETE, add table to joinlist and namespaces.
+ * If UPDATE/DELETE, add table to joinlist and namespace.
+ *
+ * Note: some callers know that they can find the new ParseNamespaceItem
+ * at the end of the pstate->p_namespace list. This is a bit ugly but not
+ * worth complicating this function's signature for.
*/
if (alsoSource)
addRTEtoQuery(pstate, rte, true, true, true);
@@ -218,7 +219,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
* Simplify InhOption (yes/no/default) into boolean yes/no.
*
* The reason we do things this way is that we don't want to examine the
- * SQL_inheritance option flag until parse_analyze() is run. Otherwise,
+ * SQL_inheritance option flag until parse_analyze() is run. Otherwise,
* we'd do the wrong thing with query strings that intermix SET commands
* with queries.
*/
@@ -243,9 +244,13 @@ interpretInhOption(InhOption inhOpt)
* table/result set should be created with OIDs. This needs to be done after
* parsing the query string because the return value can depend upon the
* default_with_oids GUC var.
+ *
+ * In some situations, we want to reject an OIDS option even if it's present.
+ * That's (rather messily) handled here rather than reloptions.c, because that
+ * code explicitly punts checking for oids to here.
*/
bool
-interpretOidsOption(List *defList)
+interpretOidsOption(List *defList, bool allowOids)
{
ListCell *cell;
@@ -256,9 +261,20 @@ interpretOidsOption(List *defList)
if (def->defnamespace == NULL &&
pg_strcasecmp(def->defname, "oids") == 0)
+ {
+ if (!allowOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized parameter \"%s\"",
+ def->defname)));
return defGetBoolean(def);
+ }
}
+ /* Force no-OIDS result if caller disallows OIDS. */
+ if (!allowOids)
+ return false;
+
/* OIDS option was not specified, so use default. */
return default_with_oids;
}
@@ -360,7 +376,7 @@ transformJoinUsingClause(ParseState *pstate,
* transformJoinOnClause() does. Just invoke transformExpr() to fix up
* the operators, and we're done.
*/
- result = transformExpr(pstate, result);
+ result = transformExpr(pstate, result, EXPR_KIND_JOIN_USING);
result = coerce_to_boolean(pstate, result, "JOIN/USING");
@@ -372,57 +388,28 @@ transformJoinUsingClause(ParseState *pstate,
* Result is a transformed qualification expression.
*/
static Node *
-transformJoinOnClause(ParseState *pstate, JoinExpr *j,
- RangeTblEntry *l_rte,
- RangeTblEntry *r_rte,
- List *relnamespace,
- Relids containedRels)
+transformJoinOnClause(ParseState *pstate, JoinExpr *j, List *namespace)
{
Node *result;
- List *save_relnamespace;
- List *save_varnamespace;
- Relids clause_varnos;
- int varno;
+ List *save_namespace;
/*
- * 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;
-
- pstate->p_relnamespace = relnamespace;
- pstate->p_varnamespace = list_make2(l_rte, r_rte);
+ setNamespaceLateralState(namespace, false, true);
- result = transformWhereClause(pstate, j->quals, "JOIN/ON");
+ save_namespace = pstate->p_namespace;
+ pstate->p_namespace = namespace;
- pstate->p_relnamespace = save_relnamespace;
- pstate->p_varnamespace = save_varnamespace;
+ result = transformWhereClause(pstate, j->quals,
+ EXPR_KIND_JOIN_ON, "JOIN/ON");
- /*
- * 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);
+ pstate->p_namespace = save_namespace;
return result;
}
@@ -435,13 +422,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,19 +457,40 @@ 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");
/*
+ * Set p_expr_kind to show this parse level is recursing to a subselect.
+ * We can't be nested within any expression, so don't need save-restore
+ * logic here.
+ */
+ Assert(pstate->p_expr_kind == EXPR_KIND_NONE);
+ pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT;
+
+ /*
+ * 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));
+ /* Restore state */
+ pstate->p_lateral_active = false;
+ pstate->p_expr_kind = EXPR_KIND_NONE;
+
/*
- * Check that we got something reasonable. Many of these conditions are
+ * Check that we got something reasonable. Many of these conditions are
* impossible given restrictions of the grammar, but check 'em anyway.
*/
if (!IsA(query, Query) ||
@@ -497,32 +499,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;
}
@@ -534,88 +517,190 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
static RangeTblEntry *
transformRangeFunction(ParseState *pstate, RangeFunction *r)
{
- Node *funcexpr;
- char *funcname;
+ List *funcexprs = NIL;
+ List *funcnames = NIL;
+ List *coldeflists = NIL;
+ bool is_lateral;
RangeTblEntry *rte;
+ ListCell *lc;
/*
- * Get function name for possible use as alias. We use the same
- * transformation rules as for a SELECT output expression. For a FuncCall
- * node, the result will be the function name, but it is possible for the
- * grammar to hand back other node types.
+ * We make lateral_only names of this level visible, whether or not the
+ * RangeFunction is explicitly marked LATERAL. This is needed for SQL
+ * spec compliance in the case of UNNEST(), and seems useful on
+ * convenience grounds for all functions in FROM.
+ *
+ * (LATERAL can't nest within a single pstate level, so we don't need
+ * save/restore logic here.)
*/
- funcname = FigureColname(r->funccallnode);
+ Assert(!pstate->p_lateral_active);
+ pstate->p_lateral_active = true;
/*
- * Transform the raw expression.
+ * Transform the raw expressions.
+ *
+ * While transforming, also save function names for possible use as alias
+ * and column names. We use the same transformation rules as for a SELECT
+ * output expression. For a FuncCall node, the result will be the
+ * function name, but it is possible for the grammar to hand back other
+ * node types.
+ *
+ * We have to get this info now, because FigureColname only works on raw
+ * parsetrees. Actually deciding what to do with the names is left up to
+ * addRangeTableEntryForFunction.
+ *
+ * Likewise, collect column definition lists if there were any. But
+ * complain if we find one here and the RangeFunction has one too.
*/
- funcexpr = transformExpr(pstate, r->funccallnode);
+ foreach(lc, r->functions)
+ {
+ List *pair = (List *) lfirst(lc);
+ Node *fexpr;
+ List *coldeflist;
+
+ /* Disassemble the function-call/column-def-list pairs */
+ Assert(list_length(pair) == 2);
+ fexpr = (Node *) linitial(pair);
+ coldeflist = (List *) lsecond(pair);
+
+ /*
+ * If we find a function call unnest() with more than one argument and
+ * no special decoration, transform it into separate unnest() calls on
+ * each argument. This is a kluge, for sure, but it's less nasty than
+ * other ways of implementing the SQL-standard UNNEST() syntax.
+ *
+ * If there is any decoration (including a coldeflist), we don't
+ * transform, which probably means a no-such-function error later. We
+ * could alternatively throw an error right now, but that doesn't seem
+ * tremendously helpful. If someone is using any such decoration,
+ * then they're not using the SQL-standard syntax, and they're more
+ * likely expecting an un-tweaked function call.
+ *
+ * Note: the transformation changes a non-schema-qualified unnest()
+ * function name into schema-qualified pg_catalog.unnest(). This
+ * choice is also a bit debatable, but it seems reasonable to force
+ * use of built-in unnest() when we make this transformation.
+ */
+ if (IsA(fexpr, FuncCall))
+ {
+ FuncCall *fc = (FuncCall *) fexpr;
+
+ if (list_length(fc->funcname) == 1 &&
+ strcmp(strVal(linitial(fc->funcname)), "unnest") == 0 &&
+ list_length(fc->args) > 1 &&
+ fc->agg_order == NIL &&
+ fc->agg_filter == NULL &&
+ !fc->agg_star &&
+ !fc->agg_distinct &&
+ !fc->func_variadic &&
+ fc->over == NULL &&
+ coldeflist == NIL)
+ {
+ ListCell *lc;
+
+ foreach(lc, fc->args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+ FuncCall *newfc;
+
+ newfc = makeFuncCall(SystemFuncName("unnest"),
+ list_make1(arg),
+ fc->location);
+
+ funcexprs = lappend(funcexprs,
+ transformExpr(pstate, (Node *) newfc,
+ EXPR_KIND_FROM_FUNCTION));
+
+ funcnames = lappend(funcnames,
+ FigureColname((Node *) newfc));
+
+ /* coldeflist is empty, so no error is possible */
+
+ coldeflists = lappend(coldeflists, coldeflist);
+ }
+ continue; /* done with this function item */
+ }
+ }
+
+ /* normal case ... */
+ funcexprs = lappend(funcexprs,
+ transformExpr(pstate, fexpr,
+ EXPR_KIND_FROM_FUNCTION));
+
+ funcnames = lappend(funcnames,
+ FigureColname(fexpr));
+
+ if (coldeflist && r->coldeflist)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple column definition lists are not allowed for the same function"),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+
+ coldeflists = lappend(coldeflists, coldeflist);
+ }
+
+ pstate->p_lateral_active = false;
/*
- * We must assign collations now so that we can fill funccolcollations.
+ * We must assign collations now so that the RTE exposes correct collation
+ * info for Vars created from it.
*/
- assign_expr_collations(pstate, funcexpr);
+ assign_list_collations(pstate, funcexprs);
/*
- * 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.)
+ * Install the top-level coldeflist if there was one (we already checked
+ * that there was no conflicting per-function coldeflist).
*
- * XXX this will need further work to support SQL99's LATERAL() feature,
- * wherein such references would indeed be legal.
+ * We only allow this when there's a single function (even after UNNEST
+ * expansion) and no WITH ORDINALITY. The reason for the latter
+ * restriction is that it's not real clear whether the ordinality column
+ * should be in the coldeflist, and users are too likely to make mistakes
+ * in one direction or the other. Putting the coldeflist inside ROWS
+ * FROM() is much clearer in this case.
*/
- if (pstate->p_relnamespace || pstate->p_varnamespace)
+ if (r->coldeflist)
{
- if (contain_vars_of_level(funcexpr, 0))
+ if (list_length(funcexprs) != 1)
+ {
+ if (r->is_rowsfrom)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("ROWS FROM() with multiple functions cannot have a column definition list"),
+ errhint("Put a separate column definition list for each function inside ROWS FROM()."),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("UNNEST() with multiple arguments cannot have a column definition list"),
+ errhint("Use separate UNNEST() calls inside ROWS FROM(), and attach a column definition list to each one."),
+ parser_errposition(pstate,
+ exprLocation((Node *) r->coldeflist))));
+ }
+ if (r->ordinality)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("function expression in FROM cannot refer to other relations of same query level"),
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("WITH ORDINALITY cannot be used with a column definition list"),
+ errhint("Put the column definition list inside ROWS FROM()."),
parser_errposition(pstate,
- locate_var_of_level(funcexpr, 0))));
+ exprLocation((Node *) r->coldeflist))));
+
+ coldeflists = list_make1(r->coldeflist);
}
/*
- * Disallow aggregate functions in the expression. (No reason to postpone
- * this check until parseCheckAggregates.)
+ * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
+ * there are any lateral cross-references in it.
*/
- if (pstate->p_hasAggs &&
- checkExprHasAggs(funcexpr))
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in function expression in FROM"),
- parser_errposition(pstate,
- locate_agg_of_level(funcexpr, 0))));
- if (pstate->p_hasWindowFuncs &&
- checkExprHasWindowFuncs(funcexpr))
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot use window function in function expression in FROM"),
- parser_errposition(pstate,
- locate_windowfunc(funcexpr))));
+ is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0);
/*
* OK, build an RTE for the function.
*/
- rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
- r, true);
-
- /*
- * If a coldeflist was supplied, ensure it defines a legal set of names
- * (no duplicates) and datatypes (no pseudo-types, for instance).
- * addRangeTableEntryForFunction looked up the type names but didn't check
- * them further than that.
- */
- if (r->coldeflist)
- {
- TupleDesc tupdesc;
-
- tupdesc = BuildDescFromLists(rte->eref->colnames,
- rte->funccoltypes,
- rte->funccoltypmods,
- rte->funccolcollations);
- CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false);
- }
+ rte = addRangeTableEntryForFunction(pstate,
+ funcnames, funcexprs, coldeflists,
+ r, is_lateral, true);
return rte;
}
@@ -625,7 +710,8 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
* transformFromClauseItem -
* Transform a FROM-clause item, adding any required entries to the
* range table list being built in the ParseState, and return the
- * transformed item ready to include in the joinlist and namespaces.
+ * transformed item ready to include in the joinlist. Also build a
+ * ParseNamespaceItem list describing the names exposed by this item.
* This routine can recurse to handle SQL92 JOIN expressions.
*
* The function return value is the node to add to the jointree (a
@@ -635,23 +721,16 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
* (We could extract this from the function return node, but it saves cycles
* to pass it back separately.)
*
- * *top_rti: receives the rangetable index of top_rte. (Ditto.)
- *
- * *relnamespace: receives a List of the RTEs exposed as relation names
- * by this item.
+ * *top_rti: receives the rangetable index of top_rte. (Ditto.)
*
- * *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.
- *
- * We do not need to pass back an explicit varnamespace value, because
- * in all cases the varnamespace contribution is exactly top_rte.
+ * *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
+ * as table/column names by this item. (The lateral_only flags in these items
+ * are indeterminate and should be explicitly set by the caller before use.)
*/
static Node *
transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry **top_rte, int *top_rti,
- List **relnamespace,
- Relids *containedRels)
+ List **namespace)
{
if (IsA(n, RangeVar))
{
@@ -681,8 +760,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);
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -700,8 +778,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);
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -719,8 +796,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);
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -733,50 +809,71 @@ 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,
+ List *l_namespace,
+ *r_namespace,
+ *my_namespace,
*l_colnames,
*r_colnames,
*res_colnames,
*l_colvars,
*r_colvars,
*res_colvars;
+ bool lateral_ok;
+ int sv_namespace_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_namespace);
+
+ /*
+ * Make the left-side RTEs available for LATERAL access within the
+ * right side, by temporarily adding them to the pstate's namespace
+ * list. 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 namespace, but mark them as not
+ * lateral_ok if the jointype is wrong.
+ *
+ * Notice that we don't require the merged namespace list to be
+ * conflict-free. See the comments for scanNameSpaceForRefname().
+ *
+ * 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_namespace, true, lateral_ok);
+
+ sv_namespace_length = list_length(pstate->p_namespace);
+ pstate->p_namespace = list_concat(pstate->p_namespace, l_namespace);
+
+ /* And now we can process the RHS */
j->rarg = transformFromClauseItem(pstate, j->rarg,
&r_rte,
&r_rtindex,
- &r_relnamespace,
- &r_containedRels);
+ &r_namespace);
+
+ /* Remove the left-side RTEs from the namespace list again */
+ pstate->p_namespace = list_truncate(pstate->p_namespace,
+ sv_namespace_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);
+ checkNameSpaceConflicts(pstate, l_namespace, r_namespace);
/*
- * Generate combined relation membership info for possible use by
- * transformJoinOnClause below.
+ * Generate combined namespace 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 */
+ my_namespace = list_concat(l_namespace, r_namespace);
/*
* Extract column name and var lists from both subtrees
@@ -939,10 +1036,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
else if (j->quals)
{
/* User-written ON-condition; transform it */
- j->quals = transformJoinOnClause(pstate, j,
- l_rte, r_rte,
- my_relnamespace,
- my_containedRels);
+ j->quals = transformJoinOnClause(pstate, j, my_namespace);
}
else
{
@@ -1001,22 +1095,30 @@ transformFromClauseItem(ParseState *pstate, Node *n,
/*
* Prepare returned namespace list. If the JOIN has an alias then it
- * hides the contained RTEs as far as the relnamespace goes;
- * otherwise, put the contained RTEs and *not* the JOIN into
- * relnamespace.
+ * hides the contained RTEs completely; otherwise, the contained RTEs
+ * are still visible as table names, but are not visible for
+ * unqualified column-name access.
+ *
+ * Note: if there are nested alias-less JOINs, the lower-level ones
+ * will remain in the list although they have neither p_rel_visible
+ * nor p_cols_visible set. We could delete such list items, but it's
+ * unclear that it's worth expending cycles to do so.
*/
- if (j->alias)
- {
- *relnamespace = list_make1(rte);
- list_free(my_relnamespace);
- }
+ if (j->alias != NULL)
+ my_namespace = NIL;
else
- *relnamespace = my_relnamespace;
+ setNamespaceColumnVisibility(my_namespace, false);
/*
- * Include join RTE in returned containedRels set
+ * The join RTE itself is always made visible for unqualified column
+ * names. It's visible as a relation name only if it has an alias.
*/
- *containedRels = bms_add_member(my_containedRels, j->rtindex);
+ *namespace = lappend(my_namespace,
+ makeNamespaceItem(rte,
+ (j->alias != NULL),
+ true,
+ false,
+ true));
return (Node *) j;
}
@@ -1144,6 +1246,60 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype,
return res_node;
}
+/*
+ * makeNamespaceItem -
+ * Convenience subroutine to construct a ParseNamespaceItem.
+ */
+static ParseNamespaceItem *
+makeNamespaceItem(RangeTblEntry *rte, bool rel_visible, bool cols_visible,
+ bool lateral_only, bool lateral_ok)
+{
+ ParseNamespaceItem *nsitem;
+
+ nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+ nsitem->p_rte = rte;
+ nsitem->p_rel_visible = rel_visible;
+ nsitem->p_cols_visible = cols_visible;
+ nsitem->p_lateral_only = lateral_only;
+ nsitem->p_lateral_ok = lateral_ok;
+ return nsitem;
+}
+
+/*
+ * setNamespaceColumnVisibility -
+ * Convenience subroutine to update cols_visible flags in a namespace list.
+ */
+static void
+setNamespaceColumnVisibility(List *namespace, bool cols_visible)
+{
+ ListCell *lc;
+
+ foreach(lc, namespace)
+ {
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
+
+ nsitem->p_cols_visible = cols_visible;
+ }
+}
+
+/*
+ * 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 -
@@ -1154,14 +1310,14 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype,
*/
Node *
transformWhereClause(ParseState *pstate, Node *clause,
- const char *constructName)
+ ParseExprKind exprKind, const char *constructName)
{
Node *qual;
if (clause == NULL)
return NULL;
- qual = transformExpr(pstate, clause);
+ qual = transformExpr(pstate, clause, exprKind);
qual = coerce_to_boolean(pstate, qual, constructName);
@@ -1181,18 +1337,18 @@ transformWhereClause(ParseState *pstate, Node *clause,
*/
Node *
transformLimitClause(ParseState *pstate, Node *clause,
- const char *constructName)
+ ParseExprKind exprKind, const char *constructName)
{
Node *qual;
if (clause == NULL)
return NULL;
- qual = transformExpr(pstate, clause);
+ qual = transformExpr(pstate, clause, exprKind);
qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName);
- /* LIMIT can't refer to any vars or aggregates of the current query */
+ /* LIMIT can't refer to any variables of the current query */
checkExprIsVarFree(pstate, qual, constructName);
return qual;
@@ -1201,7 +1357,7 @@ transformLimitClause(ParseState *pstate, Node *clause,
/*
* checkExprIsVarFree
* Check that given expr has no Vars of the current query level
- * (and no aggregates or window functions, either).
+ * (aggregates and window functions should have been rejected already).
*
* This is used to check expressions that have to have a consistent value
* across all rows of the query, such as a LIMIT. Arguably it should reject
@@ -1223,31 +1379,57 @@ checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName)
parser_errposition(pstate,
locate_var_of_level(n, 0))));
}
- if (pstate->p_hasAggs &&
- checkExprHasAggs(n))
- {
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- /* translator: %s is name of a SQL construct, eg LIMIT */
- errmsg("argument of %s must not contain aggregate functions",
- constructName),
- parser_errposition(pstate,
- locate_agg_of_level(n, 0))));
- }
- if (pstate->p_hasWindowFuncs &&
- checkExprHasWindowFuncs(n))
+}
+
+
+/*
+ * checkTargetlistEntrySQL92 -
+ * Validate a targetlist entry found by findTargetlistEntrySQL92
+ *
+ * When we select a pre-existing tlist entry as a result of syntax such
+ * as "GROUP BY 1", we have to make sure it is acceptable for use in the
+ * indicated clause type; transformExpr() will have treated it as a regular
+ * targetlist item.
+ */
+static void
+checkTargetlistEntrySQL92(ParseState *pstate, TargetEntry *tle,
+ ParseExprKind exprKind)
+{
+ switch (exprKind)
{
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- /* translator: %s is name of a SQL construct, eg LIMIT */
- errmsg("argument of %s must not contain window functions",
- constructName),
- parser_errposition(pstate,
- locate_windowfunc(n))));
+ case EXPR_KIND_GROUP_BY:
+ /* reject aggregates and window functions */
+ if (pstate->p_hasAggs &&
+ contain_aggs_of_level((Node *) tle->expr, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ /* translator: %s is name of a SQL construct, eg GROUP BY */
+ errmsg("aggregate functions are not allowed in %s",
+ ParseExprKindName(exprKind)),
+ parser_errposition(pstate,
+ locate_agg_of_level((Node *) tle->expr, 0))));
+ if (pstate->p_hasWindowFuncs &&
+ contain_windowfuncs((Node *) tle->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ /* translator: %s is name of a SQL construct, eg GROUP BY */
+ errmsg("window functions are not allowed in %s",
+ ParseExprKindName(exprKind)),
+ parser_errposition(pstate,
+ locate_windowfunc((Node *) tle->expr))));
+ break;
+ case EXPR_KIND_ORDER_BY:
+ /* no extra checks needed */
+ break;
+ case EXPR_KIND_DISTINCT_ON:
+ /* no extra checks needed */
+ break;
+ default:
+ elog(ERROR, "unexpected exprKind in checkTargetlistEntrySQL92");
+ break;
}
}
-
/*
* findTargetlistEntrySQL92 -
* Returns the targetlist entry matching the given (untransformed) node.
@@ -1256,18 +1438,18 @@ checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName)
*
* This function supports the old SQL92 ORDER BY interpretation, where the
* expression is an output column name or number. If we fail to find a
- * match of that sort, we fall through to the SQL99 rules. For historical
+ * match of that sort, we fall through to the SQL99 rules. For historical
* reasons, Postgres also allows this interpretation for GROUP BY, though
- * the standard never did. However, for GROUP BY we prefer a SQL99 match.
+ * the standard never did. However, for GROUP BY we prefer a SQL99 match.
* This function is *not* used for WINDOW definitions.
*
* node the ORDER BY, GROUP BY, or DISTINCT ON expression to be matched
* tlist the target list (passed by reference so we can append to it)
- * clause identifies clause type being processed
+ * exprKind identifies clause type being processed
*/
static TargetEntry *
findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
- int clause)
+ ParseExprKind exprKind)
{
ListCell *tl;
@@ -1276,7 +1458,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
*
* 1. Bare ColumnName (no qualifier or subscripts)
* For a bare identifier, we search for a matching column name
- * in the existing target list. Multiple matches are an error
+ * in the existing target list. Multiple matches are an error
* unless they refer to identical values; for example,
* we allow SELECT a, a FROM table ORDER BY a
* but not SELECT a AS b, b FROM table ORDER BY b
@@ -1285,7 +1467,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
* For GROUP BY, it is incorrect to match the grouping item against
* targetlist entries: according to SQL92, an identifier in GROUP BY
* is a reference to a column name exposed by FROM, not to a target
- * list column. However, many implementations (including pre-7.0
+ * list column. However, many implementations (including pre-7.0
* PostgreSQL) accept this anyway. So for GROUP BY, we look first
* to see if the identifier matches any FROM column name, and only
* try for a targetlist name if it doesn't. This ensures that we
@@ -1316,7 +1498,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
char *name = strVal(linitial(((ColumnRef *) node)->fields));
int location = ((ColumnRef *) node)->location;
- if (clause == GROUP_CLAUSE)
+ if (exprKind == EXPR_KIND_GROUP_BY)
{
/*
* In GROUP BY, we must prefer a match against a FROM-clause
@@ -1358,7 +1540,8 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
/*------
translator: first %s is name of a SQL construct, eg ORDER BY */
errmsg("%s \"%s\" is ambiguous",
- clauseText[clause], name),
+ ParseExprKindName(exprKind),
+ name),
parser_errposition(pstate, location)));
}
else
@@ -1367,7 +1550,11 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
}
}
if (target_result != NULL)
- return target_result; /* return the first match */
+ {
+ /* return the first match, after suitable validation */
+ checkTargetlistEntrySQL92(pstate, target_result, exprKind);
+ return target_result;
+ }
}
}
if (IsA(node, A_Const))
@@ -1382,7 +1569,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
(errcode(ERRCODE_SYNTAX_ERROR),
/* translator: %s is name of a SQL construct, eg ORDER BY */
errmsg("non-integer constant in %s",
- clauseText[clause]),
+ ParseExprKindName(exprKind)),
parser_errposition(pstate, location)));
target_pos = intVal(val);
@@ -1393,21 +1580,25 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
if (!tle->resjunk)
{
if (++targetlist_pos == target_pos)
- return tle; /* return the unique match */
+ {
+ /* return the unique match, after suitable validation */
+ checkTargetlistEntrySQL92(pstate, tle, exprKind);
+ return tle;
+ }
}
}
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
/* translator: %s is name of a SQL construct, eg ORDER BY */
errmsg("%s position %d is not in select list",
- clauseText[clause], target_pos),
+ ParseExprKindName(exprKind), target_pos),
parser_errposition(pstate, location)));
}
/*
* Otherwise, we have an expression, so process it per SQL99 rules.
*/
- return findTargetlistEntrySQL99(pstate, node, tlist);
+ return findTargetlistEntrySQL99(pstate, node, tlist, exprKind);
}
/*
@@ -1421,9 +1612,11 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
*
* node the ORDER BY, GROUP BY, etc expression to be matched
* tlist the target list (passed by reference so we can append to it)
+ * exprKind identifies clause type being processed
*/
static TargetEntry *
-findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist)
+findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
+ ParseExprKind exprKind)
{
TargetEntry *target_result;
ListCell *tl;
@@ -1432,11 +1625,11 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist)
/*
* Convert the untransformed node to a transformed expression, and search
* for a match in the tlist. NOTE: it doesn't really matter whether there
- * is more than one match. Also, we are willing to match an existing
+ * is more than one match. Also, we are willing to match an existing
* resjunk target here, though the SQL92 cases above must ignore resjunk
* targets.
*/
- expr = transformExpr(pstate, node);
+ expr = transformExpr(pstate, node, exprKind);
foreach(tl, *tlist)
{
@@ -1460,10 +1653,11 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist)
/*
* If no matches, construct a new target entry which is appended to the
- * end of the target list. This target is given resjunk = TRUE so that it
+ * end of the target list. This target is given resjunk = TRUE so that it
* will not be projected into the final tuple.
*/
- target_result = transformTargetEntry(pstate, node, expr, NULL, true);
+ target_result = transformTargetEntry(pstate, node, expr, exprKind,
+ NULL, true);
*tlist = lappend(*tlist, target_result);
@@ -1483,7 +1677,7 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist)
List *
transformGroupClause(ParseState *pstate, List *grouplist,
List **targetlist, List *sortClause,
- bool useSQL99)
+ ParseExprKind exprKind, bool useSQL99)
{
List *result = NIL;
ListCell *gl;
@@ -1495,10 +1689,11 @@ transformGroupClause(ParseState *pstate, List *grouplist,
bool found = false;
if (useSQL99)
- tle = findTargetlistEntrySQL99(pstate, gexpr, targetlist);
+ tle = findTargetlistEntrySQL99(pstate, gexpr,
+ targetlist, exprKind);
else
- tle = findTargetlistEntrySQL92(pstate, gexpr, targetlist,
- GROUP_CLAUSE);
+ tle = findTargetlistEntrySQL92(pstate, gexpr,
+ targetlist, exprKind);
/* Eliminate duplicates (GROUP BY x, x) */
if (targetIsInSortList(tle, InvalidOid, result))
@@ -1560,6 +1755,7 @@ List *
transformSortClause(ParseState *pstate,
List *orderlist,
List **targetlist,
+ ParseExprKind exprKind,
bool resolveUnknown,
bool useSQL99)
{
@@ -1572,10 +1768,11 @@ transformSortClause(ParseState *pstate,
TargetEntry *tle;
if (useSQL99)
- tle = findTargetlistEntrySQL99(pstate, sortby->node, targetlist);
+ tle = findTargetlistEntrySQL99(pstate, sortby->node,
+ targetlist, exprKind);
else
- tle = findTargetlistEntrySQL92(pstate, sortby->node, targetlist,
- ORDER_CLAUSE);
+ tle = findTargetlistEntrySQL92(pstate, sortby->node,
+ targetlist, exprKind);
sortlist = addTargetToSortList(pstate, tle,
sortlist, *targetlist, sortby,
@@ -1640,12 +1837,14 @@ transformWindowDefinitions(ParseState *pstate,
orderClause = transformSortClause(pstate,
windef->orderClause,
targetlist,
+ EXPR_KIND_WINDOW_ORDER,
true /* fix unknowns */ ,
true /* force SQL99 rules */ );
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
targetlist,
orderClause,
+ EXPR_KIND_WINDOW_PARTITION,
true /* force SQL99 rules */ );
/*
@@ -1658,11 +1857,16 @@ transformWindowDefinitions(ParseState *pstate,
/*
* Per spec, a windowdef that references a previous one copies the
* previous partition clause (and mustn't specify its own). It can
- * specify its own ordering clause. but only if the previous one had
+ * specify its own ordering clause, but only if the previous one had
* none. It always specifies its own frame clause, and the previous
- * one must not have a frame clause. (Yeah, it's bizarre that each of
+ * one must not have a frame clause. Yeah, it's bizarre that each of
* these cases works differently, but SQL:2008 says so; see 7.11
- * <window clause> syntax rule 10 and general rule 1.)
+ * <window clause> syntax rule 10 and general rule 1. The frame
+ * clause rule is especially bizarre because it makes "OVER foo"
+ * different from "OVER (foo)", and requires the latter to throw an
+ * error if foo has a nondefault frame clause. Well, ours not to
+ * reason why, but we do go out of our way to throw a useful error
+ * message for such cases.
*/
if (refwc)
{
@@ -1701,11 +1905,27 @@ transformWindowDefinitions(ParseState *pstate,
wc->copiedOrder = false;
}
if (refwc && refwc->frameOptions != FRAMEOPTION_DEFAULTS)
+ {
+ /*
+ * Use this message if this is a WINDOW clause, or if it's an OVER
+ * clause that includes ORDER BY or framing clauses. (We already
+ * rejected PARTITION BY above, so no need to check that.)
+ */
+ if (windef->name ||
+ orderClause || windef->frameOptions != FRAMEOPTION_DEFAULTS)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot copy window \"%s\" because it has a frame clause",
+ windef->refname),
+ parser_errposition(pstate, windef->location)));
+ /* Else this clause is just OVER (foo), so say this: */
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot override frame clause of window \"%s\"",
- windef->refname),
+ errmsg("cannot copy window \"%s\" because it has a frame clause",
+ windef->refname),
+ errhint("Omit the parentheses in this OVER clause."),
parser_errposition(pstate, windef->location)));
+ }
wc->frameOptions = windef->frameOptions;
/* Process frame offset expressions */
wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
@@ -1747,7 +1967,7 @@ transformDistinctClause(ParseState *pstate,
/*
* The distinctClause should consist of all ORDER BY items followed by all
- * other non-resjunk targetlist items. There must not be any resjunk
+ * other non-resjunk targetlist items. There must not be any resjunk
* ORDER BY items --- that would imply that we are sorting by a value that
* isn't necessarily unique within a DISTINCT group, so the results
* wouldn't be well-defined. This construction ensures we follow the rule
@@ -1792,6 +2012,20 @@ transformDistinctClause(ParseState *pstate,
true);
}
+ /*
+ * Complain if we found nothing to make DISTINCT. Returning an empty list
+ * would cause the parsed Query to look like it didn't have DISTINCT, with
+ * results that would probably surprise the user. Note: this case is
+ * presently impossible for aggregates because of grammar restrictions,
+ * but we check anyway.
+ */
+ if (result == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ is_agg ?
+ errmsg("an aggregate with DISTINCT must have at least one argument") :
+ errmsg("SELECT DISTINCT must have at least one column")));
+
return result;
}
@@ -1833,7 +2067,7 @@ transformDistinctOnClause(ParseState *pstate, List *distinctlist,
TargetEntry *tle;
tle = findTargetlistEntrySQL92(pstate, dexpr, targetlist,
- DISTINCT_ON_CLAUSE);
+ EXPR_KIND_DISTINCT_ON);
sortgroupref = assignSortGroupRef(tle, *targetlist);
sortgrouprefs = lappend_int(sortgrouprefs, sortgroupref);
}
@@ -1870,7 +2104,7 @@ transformDistinctOnClause(ParseState *pstate, List *distinctlist,
/*
* Now add any remaining DISTINCT ON items, using default sort/group
- * semantics for their data types. (Note: this is pretty questionable; if
+ * semantics for their data types. (Note: this is pretty questionable; if
* the ORDER BY list doesn't include all the DISTINCT ON items and more
* besides, you certainly aren't using DISTINCT ON in the intended way,
* and you probably aren't going to get consistent results. It might be
@@ -1896,6 +2130,12 @@ transformDistinctOnClause(ParseState *pstate, List *distinctlist,
true);
}
+ /*
+ * An empty result list is impossible here because of grammar
+ * restrictions.
+ */
+ Assert(result != NIL);
+
return result;
}
@@ -1939,7 +2179,7 @@ get_matching_location(int sortgroupref, List *sortgrouprefs, List *exprs)
*
* Returns the updated SortGroupClause list.
*/
-static List *
+List *
addTargetToSortList(ParseState *pstate, TargetEntry *tle,
List *sortlist, List *targetlist, SortBy *sortby,
bool resolveUnknown)
@@ -2242,11 +2482,11 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
if (clause == NULL)
return NULL;
- /* Transform the raw expression tree */
- node = transformExpr(pstate, clause);
-
if (frameOptions & FRAMEOPTION_ROWS)
{
+ /* Transform the raw expression tree */
+ node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_ROWS);
+
/*
* Like LIMIT clause, simply coerce to int8
*/
@@ -2255,6 +2495,9 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
+ /* Transform the raw expression tree */
+ node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE);
+
/*
* this needs a lot of thought to decide how to support in the context
* of Postgres' extensible datatype framework
@@ -2264,9 +2507,12 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
elog(ERROR, "window frame with value offset is not implemented");
}
else
+ {
Assert(false);
+ node = NULL;
+ }
- /* Disallow variables and aggregates in frame offsets */
+ /* Disallow variables in frame offsets */
checkExprIsVarFree(pstate, node, constructName);
return node;
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 3cfa5bce55..96c70065a1 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -3,7 +3,7 @@
* parse_coerce.c
* handle type coercions/conversions for parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "access/htup_details.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_inherits_fn.h"
@@ -58,12 +59,12 @@ static bool typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId);
* Convert an expression to a target type and typmod.
*
* This is the general-purpose entry point for arbitrary type coercion
- * operations. Direct use of the component operations can_coerce_type,
+ * operations. Direct use of the component operations can_coerce_type,
* coerce_type, and coerce_type_typmod should be restricted to special
* cases (eg, when the conversion is expected to succeed).
*
* Returns the possibly-transformed expression tree, or NULL if the type
- * conversion is not possible. (We do this, rather than ereport'ing directly,
+ * conversion is not possible. (We do this, rather than ereport'ing directly,
* so that callers can generate custom error messages indicating context.)
*
* pstate - parse state (can be NULL, see coerce_type)
@@ -151,7 +152,7 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
* already be properly coerced to the specified typmod.
*
* pstate is only used in the case that we are able to resolve the type of
- * a previously UNKNOWN Param. It is okay to pass pstate = NULL if the
+ * a previously UNKNOWN Param. It is okay to pass pstate = NULL if the
* caller does not want type information updated for Params.
*
* Note: this function must not modify the given expression tree, only add
@@ -181,7 +182,7 @@ coerce_type(ParseState *pstate, Node *node,
*
* Note: by returning the unmodified node here, we are saying that
* it's OK to treat an UNKNOWN constant as a valid input for a
- * function accepting ANY, ANYELEMENT, or ANYNONARRAY. This should be
+ * function accepting ANY, ANYELEMENT, or ANYNONARRAY. This should be
* all right, since an UNKNOWN value is still a perfectly valid Datum.
*
* NB: we do NOT want a RelabelType here: the exposed type of the
@@ -256,7 +257,7 @@ coerce_type(ParseState *pstate, Node *node,
/*
* If the target type is a domain, we want to call its base type's
- * input routine, not domain_in(). This is to avoid premature failure
+ * input routine, not domain_in(). This is to avoid premature failure
* when the domain applies a typmod: existing input routines follow
* implicit-coercion semantics for length checks, which is not always
* what we want here. The needed check will be applied properly
@@ -269,7 +270,7 @@ coerce_type(ParseState *pstate, Node *node,
* For most types we pass typmod -1 to the input routine, because
* existing input routines follow implicit-coercion semantics for
* length checks, which is not always what we want here. Any length
- * constraint will be applied later by our caller. An exception
+ * constraint will be applied later by our caller. An exception
* however is the INTERVAL type, for which we *must* pass the typmod
* or it won't be able to obey the bizarre SQL-spec input rules. (Ugly
* as sin, but so is this part of the spec...)
@@ -349,7 +350,7 @@ coerce_type(ParseState *pstate, Node *node,
{
/*
* If we have a COLLATE clause, we have to push the coercion
- * underneath the COLLATE. This is really ugly, but there is little
+ * underneath the COLLATE. This is really ugly, but there is little
* choice because the above hacks on Consts and Params wouldn't happen
* otherwise. This kluge has consequences in coerce_to_target_type.
*/
@@ -372,7 +373,7 @@ coerce_type(ParseState *pstate, Node *node,
{
/*
* Generate an expression tree representing run-time application
- * of the conversion function. If we are dealing with a domain
+ * of the conversion function. If we are dealing with a domain
* target type, the conversion function will yield the base type,
* and we need to extract the correct typmod to use from the
* domain's typtypmod.
@@ -408,7 +409,7 @@ coerce_type(ParseState *pstate, Node *node,
* to have the intended type when inspected by higher-level code.
*
* Also, domains may have value restrictions beyond the base type
- * that must be accounted for. If the destination is a domain
+ * that must be accounted for. If the destination is a domain
* then we won't need a RelabelType node.
*/
result = coerce_to_domain(node, InvalidOid, -1, targetTypeId,
@@ -655,7 +656,7 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
}
/*
- * Now build the domain coercion node. This represents run-time checking
+ * Now build the domain coercion node. This represents run-time checking
* of any constraints currently attached to the domain. This also ensures
* that the expression is properly labeled as to result type.
*/
@@ -728,7 +729,7 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
* Mark a coercion node as IMPLICIT so it will never be displayed by
* ruleutils.c. We use this when we generate a nest of coercion nodes
* to implement what is logically one conversion; the inner nodes are
- * forced to IMPLICIT_CAST format. This does not change their semantics,
+ * forced to IMPLICIT_CAST format. This does not change their semantics,
* only display behavior.
*
* It is caller error to call this on something that doesn't have a
@@ -1187,7 +1188,7 @@ select_common_type(ParseState *pstate, List *exprs, const char *context,
}
/*
- * Nope, so set up for the full algorithm. Note that at this point, lc
+ * Nope, so set up for the full algorithm. Note that at this point, lc
* points to the first list item with type different from pexpr's; we need
* not re-examine any items the previous loop advanced over.
*/
@@ -1482,7 +1483,7 @@ check_generic_type_consistency(Oid *actual_arg_types,
*
* If any polymorphic pseudotype is used in a function's arguments or
* return type, we make sure the actual data types are consistent with
- * each other. The argument consistency rules are shown above for
+ * each other. The argument consistency rules are shown above for
* check_generic_type_consistency().
*
* If we have UNKNOWN input (ie, an untyped literal) for any polymorphic
@@ -1504,7 +1505,7 @@ check_generic_type_consistency(Oid *actual_arg_types,
* impossible to determine the range type from the subtype alone.)
* 4) If return type is ANYARRAY, but no argument is ANYARRAY or ANYELEMENT,
* generate an error. Similarly, if return type is ANYRANGE, but no
- * argument is ANYRANGE, generate an error. (These conditions are
+ * argument is ANYRANGE, generate an error. (These conditions are
* prevented by CREATE FUNCTION and therefore are not expected here.)
* 5) If return type is ANYELEMENT, and any argument is ANYELEMENT, use the
* argument's actual type as the function's return type.
@@ -1514,7 +1515,7 @@ check_generic_type_consistency(Oid *actual_arg_types,
* type or the range type's corresponding subtype (or both, in which case
* they must match).
* 7) If return type is ANYELEMENT, no argument is ANYELEMENT, ANYARRAY, or
- * ANYRANGE, generate an error. (This condition is prevented by CREATE
+ * ANYRANGE, generate an error. (This condition is prevented by CREATE
* FUNCTION and therefore is not expected here.)
* 8) ANYENUM is treated the same as ANYELEMENT except that if it is used
* (alone or in combination with plain ANYELEMENT), we add the extra
@@ -1531,14 +1532,14 @@ check_generic_type_consistency(Oid *actual_arg_types,
*
* When allow_poly is false, we are not expecting any of the actual_arg_types
* to be polymorphic, and we should not return a polymorphic result type
- * either. When allow_poly is true, it is okay to have polymorphic "actual"
+ * either. When allow_poly is true, it is okay to have polymorphic "actual"
* arg types, and we can return ANYARRAY, ANYRANGE, or ANYELEMENT as the
- * result. (This case is currently used only to check compatibility of an
+ * result. (This case is currently used only to check compatibility of an
* aggregate's declaration with the underlying transfn.)
*
* A special case is that we could see ANYARRAY as an actual_arg_type even
* when allow_poly is false (this is possible only because pg_statistic has
- * columns shown as anyarray in the catalogs). We allow this to match a
+ * columns shown as anyarray in the catalogs). We allow this to match a
* declared ANYARRAY argument, but only if there is no ANYELEMENT argument
* or result (since we can't determine a specific element type to match to
* ANYELEMENT). Note this means that functions taking ANYARRAY had better
@@ -1644,7 +1645,7 @@ enforce_generic_type_consistency(Oid *actual_arg_types,
/*
* Fast Track: if none of the arguments are polymorphic, return the
- * unmodified rettype. We assume it can't be polymorphic either.
+ * unmodified rettype. We assume it can't be polymorphic either.
*/
if (!have_generics)
return rettype;
@@ -1700,7 +1701,7 @@ enforce_generic_type_consistency(Oid *actual_arg_types,
if (!OidIsValid(range_typelem))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("argument declared \"anyrange\" is not a range but type %s",
+ errmsg("argument declared \"anyrange\" is not a range type but type %s",
format_type_be(range_typeid))));
}
@@ -1921,7 +1922,7 @@ resolve_generic_type(Oid declared_type,
if (!OidIsValid(range_typelem))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("argument declared \"anyrange\" is not a range but type %s",
+ errmsg("argument declared \"anyrange\" is not a range type but type %s",
format_type_be(context_base_type))));
return range_typelem;
}
@@ -1987,8 +1988,8 @@ IsPreferredType(TYPCATEGORY category, Oid type)
* Check if srctype is binary-coercible to targettype.
*
* This notion allows us to cheat and directly exchange values without
- * going through the trouble of calling a conversion function. Note that
- * in general, this should only be an implementation shortcut. Before 7.4,
+ * going through the trouble of calling a conversion function. Note that
+ * in general, this should only be an implementation shortcut. Before 7.4,
* this was also used as a heuristic for resolving overloaded functions and
* operators, but that's basically a bad idea.
*
@@ -2001,7 +2002,7 @@ IsPreferredType(TYPCATEGORY category, Oid type)
* types.
*
* This function replaces IsBinaryCompatible(), which was an inherently
- * symmetric test. Since the pg_cast entries aren't necessarily symmetric,
+ * symmetric test. Since the pg_cast entries aren't necessarily symmetric,
* the order of the operands is now significant.
*/
bool
@@ -2015,6 +2016,10 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
if (srctype == targettype)
return true;
+ /* Anything is coercible to ANY or ANYELEMENT */
+ if (targettype == ANYOID || targettype == ANYELEMENTOID)
+ return true;
+
/* If srctype is a domain, reduce to its base type */
if (OidIsValid(srctype))
srctype = getBaseType(srctype);
@@ -2183,7 +2188,7 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
* Hack: disallow coercions to oidvector and int2vector, which
* otherwise tend to capture coercions that should go to "real" array
* types. We want those types to be considered "real" arrays for many
- * purposes, but not this one. (Also, ArrayCoerceExpr isn't
+ * purposes, but not this one. (Also, ArrayCoerceExpr isn't
* guaranteed to produce an output that meets the restrictions of
* these datatypes, such as being 1-dimensional.)
*/
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index ba49f9fd59..bbd10304cc 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -14,22 +14,22 @@
* 1. The output collation of each expression node, or InvalidOid if it
* returns a noncollatable data type. This can also be InvalidOid if the
* result type is collatable but the collation is indeterminate.
- * 2. The collation to be used in executing each function. InvalidOid means
+ * 2. The collation to be used in executing each function. InvalidOid means
* that there are no collatable inputs or their collation is indeterminate.
* This value is only stored in node types that might call collation-using
* functions.
*
* You might think we could get away with storing only one collation per
- * node, but the two concepts really need to be kept distinct. Otherwise
+ * node, but the two concepts really need to be kept distinct. Otherwise
* it's too confusing when a function produces a collatable output type but
* has no collatable inputs or produces noncollatable output from collatable
* inputs.
*
* Cases with indeterminate collation might result in an error being thrown
- * at runtime. If we knew exactly which functions require collation
+ * at runtime. If we knew exactly which functions require collation
* information, we could throw those errors at parse time instead.
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -40,7 +40,9 @@
*/
#include "postgres.h"
+#include "catalog/pg_aggregate.h"
#include "catalog/pg_collation.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_collate.h"
#include "utils/lsyscache.h"
@@ -73,6 +75,18 @@ typedef struct
static bool assign_query_collations_walker(Node *node, ParseState *pstate);
static bool assign_collations_walker(Node *node,
assign_collations_context *context);
+static void merge_collation_state(Oid collation,
+ CollateStrength strength,
+ int location,
+ Oid collation2,
+ int location2,
+ assign_collations_context *context);
+static void assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *loccontext);
+static void assign_ordered_set_collations(Aggref *aggref,
+ assign_collations_context *loccontext);
+static void assign_hypothetical_collations(Aggref *aggref,
+ assign_collations_context *loccontext);
/*
@@ -231,7 +245,7 @@ select_common_collation(ParseState *pstate, List *exprs, bool none_ok)
* Recursive guts of collation processing.
*
* Nodes with no children (eg, Vars, Consts, Params) must have been marked
- * when built. All upper-level nodes are marked here.
+ * when built. All upper-level nodes are marked here.
*
* Note: if this is invoked directly on a List, it will attempt to infer a
* common collation for all the list members. In particular, it will throw
@@ -258,6 +272,9 @@ assign_collations_walker(Node *node, assign_collations_context *context)
loccontext.collation = InvalidOid;
loccontext.strength = COLLATE_NONE;
loccontext.location = -1;
+ /* Set these fields just to suppress uninitialized-value warnings: */
+ loccontext.collation2 = InvalidOid;
+ loccontext.location2 = -1;
/*
* Recurse if appropriate, then determine the collation for this node.
@@ -319,86 +336,6 @@ assign_collations_walker(Node *node, assign_collations_context *context)
}
}
break;
- case T_CaseExpr:
- {
- /*
- * CaseExpr is a special case because we do not want to
- * recurse into the test expression (if any). It was already
- * marked with collations during transformCaseExpr, and
- * furthermore its collation is not relevant to the result of
- * the CASE --- only the output expressions are. So we can't
- * use expression_tree_walker here.
- */
- CaseExpr *expr = (CaseExpr *) node;
- Oid typcollation;
- ListCell *lc;
-
- foreach(lc, expr->args)
- {
- CaseWhen *when = (CaseWhen *) lfirst(lc);
-
- Assert(IsA(when, CaseWhen));
-
- /*
- * The condition expressions mustn't affect the CASE's
- * result collation either; but since they are known to
- * yield boolean, it's safe to recurse directly on them
- * --- they won't change loccontext.
- */
- (void) assign_collations_walker((Node *) when->expr,
- &loccontext);
- (void) assign_collations_walker((Node *) when->result,
- &loccontext);
- }
- (void) assign_collations_walker((Node *) expr->defresult,
- &loccontext);
-
- /*
- * Now determine the CASE's output collation. This is the
- * same as the general case below.
- */
- typcollation = get_typcollation(exprType(node));
- if (OidIsValid(typcollation))
- {
- /* Node's result is collatable; what about its input? */
- if (loccontext.strength > COLLATE_NONE)
- {
- /* Collation state bubbles up from children. */
- collation = loccontext.collation;
- strength = loccontext.strength;
- location = loccontext.location;
- }
- else
- {
- /*
- * Collatable output produced without any collatable
- * input. Use the type's collation (which is usually
- * DEFAULT_COLLATION_OID, but might be different for a
- * domain).
- */
- collation = typcollation;
- strength = COLLATE_IMPLICIT;
- location = exprLocation(node);
- }
- }
- else
- {
- /* Node's result type isn't collatable. */
- collation = InvalidOid;
- strength = COLLATE_NONE;
- location = -1; /* won't be used */
- }
-
- /*
- * Save the state into the expression node. We know it
- * doesn't care about input collation.
- */
- if (strength == COLLATE_CONFLICT)
- exprSetCollation(node, InvalidOid);
- else
- exprSetCollation(node, collation);
- }
- break;
case T_RowExpr:
{
/*
@@ -511,7 +448,7 @@ assign_collations_walker(Node *node, assign_collations_context *context)
/*
* TargetEntry can have only one child, and should bubble that
- * state up to its parent. We can't use the general-case code
+ * state up to its parent. We can't use the general-case code
* below because exprType and friends don't work on TargetEntry.
*/
collation = loccontext.collation;
@@ -526,7 +463,7 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* There are some cases where there might not be a failure, for
* example if the planner chooses to use hash aggregation instead
* of sorting for grouping; but it seems better to predictably
- * throw an error. (Compare transformSetOperationTree, which will
+ * throw an error. (Compare transformSetOperationTree, which will
* throw error for indeterminate collation of set-op columns, even
* though the planner might be able to implement the set-op
* without sorting.)
@@ -564,16 +501,22 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* SubLink. Act as though the Query returns its first output
* column, which indeed is what it does for EXPR_SUBLINK and
* ARRAY_SUBLINK cases. In the cases where the SubLink
- * returns boolean, this info will be ignored.
+ * returns boolean, this info will be ignored. Special case:
+ * in EXISTS, the Query might return no columns, in which case
+ * we need do nothing.
*
* We needn't recurse, since the Query is already processed.
*/
Query *qtree = (Query *) node;
TargetEntry *tent;
+ if (qtree->targetList == NIL)
+ return false;
tent = (TargetEntry *) linitial(qtree->targetList);
Assert(IsA(tent, TargetEntry));
- Assert(!tent->resjunk);
+ if (tent->resjunk)
+ return false;
+
collation = exprCollation((Node *) tent->expr);
/* collation doesn't change if it's converted to array */
strength = COLLATE_IMPLICIT;
@@ -630,14 +573,116 @@ assign_collations_walker(Node *node, assign_collations_context *context)
{
/*
* General case for most expression nodes with children. First
- * recurse, then figure out what to assign here.
+ * recurse, then figure out what to assign to this node.
*/
Oid typcollation;
- (void) expression_tree_walker(node,
- assign_collations_walker,
- (void *) &loccontext);
+ /*
+ * For most node types, we want to treat all the child
+ * expressions alike; but there are a few exceptions, hence
+ * this inner switch.
+ */
+ switch (nodeTag(node))
+ {
+ case T_Aggref:
+ {
+ /*
+ * Aggref is messy enough that we give it its own
+ * function, in fact three of them. The FILTER
+ * clause is independent of the rest of the
+ * aggregate, however, so it can be processed
+ * separately.
+ */
+ Aggref *aggref = (Aggref *) node;
+
+ switch (aggref->aggkind)
+ {
+ case AGGKIND_NORMAL:
+ assign_aggregate_collations(aggref,
+ &loccontext);
+ break;
+ case AGGKIND_ORDERED_SET:
+ assign_ordered_set_collations(aggref,
+ &loccontext);
+ break;
+ case AGGKIND_HYPOTHETICAL:
+ assign_hypothetical_collations(aggref,
+ &loccontext);
+ break;
+ default:
+ elog(ERROR, "unrecognized aggkind: %d",
+ (int) aggref->aggkind);
+ }
+
+ assign_expr_collations(context->pstate,
+ (Node *) aggref->aggfilter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its aggfilter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+ assign_expr_collations(context->pstate,
+ (Node *) wfunc->aggfilter);
+ }
+ break;
+ case T_CaseExpr:
+ {
+ /*
+ * CaseExpr is a special case because we do not
+ * want to recurse into the test expression (if
+ * any). It was already marked with collations
+ * during transformCaseExpr, and furthermore its
+ * collation is not relevant to the result of the
+ * CASE --- only the output expressions are.
+ */
+ CaseExpr *expr = (CaseExpr *) node;
+ ListCell *lc;
+
+ foreach(lc, expr->args)
+ {
+ CaseWhen *when = (CaseWhen *) lfirst(lc);
+
+ Assert(IsA(when, CaseWhen));
+
+ /*
+ * The condition expressions mustn't affect
+ * the CASE's result collation either; but
+ * since they are known to yield boolean, it's
+ * safe to recurse directly on them --- they
+ * won't change loccontext.
+ */
+ (void) assign_collations_walker((Node *) when->expr,
+ &loccontext);
+ (void) assign_collations_walker((Node *) when->result,
+ &loccontext);
+ }
+ (void) assign_collations_walker((Node *) expr->defresult,
+ &loccontext);
+ }
+ break;
+ default:
+
+ /*
+ * Normal case: all child expressions contribute
+ * equally to loccontext.
+ */
+ (void) expression_tree_walker(node,
+ assign_collations_walker,
+ (void *) &loccontext);
+ break;
+ }
+
+ /*
+ * Now figure out what collation to assign to this node.
+ */
typcollation = get_typcollation(exprType(node));
if (OidIsValid(typcollation))
{
@@ -693,9 +738,33 @@ assign_collations_walker(Node *node, assign_collations_context *context)
}
/*
- * Now, merge my information into my parent's state. If the collation
- * strength for this node is different from what's already in *context,
- * then this node either dominates or is dominated by earlier siblings.
+ * Now, merge my information into my parent's state.
+ */
+ merge_collation_state(collation,
+ strength,
+ location,
+ loccontext.collation2,
+ loccontext.location2,
+ context);
+
+ return false;
+}
+
+/*
+ * Merge collation state of a subexpression into the context for its parent.
+ */
+static void
+merge_collation_state(Oid collation,
+ CollateStrength strength,
+ int location,
+ Oid collation2,
+ int location2,
+ assign_collations_context *context)
+{
+ /*
+ * If the collation strength for this node is different from what's
+ * already in *context, then this node either dominates or is dominated by
+ * earlier siblings.
*/
if (strength > context->strength)
{
@@ -706,8 +775,8 @@ assign_collations_walker(Node *node, assign_collations_context *context)
/* Bubble up error info if applicable */
if (strength == COLLATE_CONFLICT)
{
- context->collation2 = loccontext.collation2;
- context->location2 = loccontext.location2;
+ context->collation2 = collation2;
+ context->location2 = location2;
}
}
else if (strength == context->strength)
@@ -768,6 +837,201 @@ assign_collations_walker(Node *node, assign_collations_context *context)
break;
}
}
+}
- return false;
+/*
+ * Aggref is a special case because expressions used only for ordering
+ * shouldn't be taken to conflict with each other or with regular args,
+ * indeed shouldn't affect the aggregate's result collation at all.
+ * We handle this by applying assign_expr_collations() to them rather than
+ * passing down our loccontext.
+ *
+ * Note that we recurse to each TargetEntry, not directly to its contained
+ * expression, so that the case above for T_TargetEntry will complain if we
+ * can't resolve a collation for an ORDER BY item (whether or not it is also
+ * a normal aggregate arg).
+ *
+ * We need not recurse into the aggorder or aggdistinct lists, because those
+ * contain only SortGroupClause nodes which we need not process.
+ */
+static void
+assign_aggregate_collations(Aggref *aggref,
+ assign_collations_context *loccontext)
+{
+ ListCell *lc;
+
+ /* Plain aggregates have no direct args */
+ Assert(aggref->aggdirectargs == NIL);
+
+ /* Process aggregated args, holding resjunk ones at arm's length */
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ Assert(IsA(tle, TargetEntry));
+ if (tle->resjunk)
+ assign_expr_collations(loccontext->pstate, (Node *) tle);
+ else
+ (void) assign_collations_walker((Node *) tle, loccontext);
+ }
+}
+
+/*
+ * For ordered-set aggregates, it's somewhat unclear how best to proceed.
+ * The spec-defined inverse distribution functions have only one sort column
+ * and don't return collatable types, but this is clearly too restrictive in
+ * the general case. Our solution is to consider that the aggregate's direct
+ * arguments contribute normally to determination of the aggregate's own
+ * collation, while aggregated arguments contribute only when the aggregate
+ * is designed to have exactly one aggregated argument (i.e., it has a single
+ * aggregated argument and is non-variadic). If it can have more than one
+ * aggregated argument, we process the aggregated arguments as independent
+ * sort columns. This avoids throwing error for something like
+ * agg(...) within group (order by x collate "foo", y collate "bar")
+ * while also guaranteeing that variadic aggregates don't change in behavior
+ * depending on how many sort columns a particular call happens to have.
+ *
+ * Otherwise this is much like the plain-aggregate case.
+ */
+static void
+assign_ordered_set_collations(Aggref *aggref,
+ assign_collations_context *loccontext)
+{
+ bool merge_sort_collations;
+ ListCell *lc;
+
+ /* Merge sort collations to parent only if there can be only one */
+ merge_sort_collations = (list_length(aggref->args) == 1 &&
+ get_func_variadictype(aggref->aggfnoid) == InvalidOid);
+
+ /* Direct args, if any, are normal children of the Aggref node */
+ (void) assign_collations_walker((Node *) aggref->aggdirectargs,
+ loccontext);
+
+ /* Process aggregated args appropriately */
+ foreach(lc, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ Assert(IsA(tle, TargetEntry));
+ if (merge_sort_collations)
+ (void) assign_collations_walker((Node *) tle, loccontext);
+ else
+ assign_expr_collations(loccontext->pstate, (Node *) tle);
+ }
+}
+
+/*
+ * Hypothetical-set aggregates are even more special: per spec, we need to
+ * unify the collations of each pair of hypothetical and aggregated args.
+ * And we need to force the choice of collation down into the sort column
+ * to ensure that the sort happens with the chosen collation. Other than
+ * that, the behavior is like regular ordered-set aggregates. Note that
+ * hypothetical direct arguments contribute to the aggregate collation
+ * only when their partner aggregated arguments do.
+ */
+static void
+assign_hypothetical_collations(Aggref *aggref,
+ assign_collations_context *loccontext)
+{
+ ListCell *h_cell = list_head(aggref->aggdirectargs);
+ ListCell *s_cell = list_head(aggref->args);
+ bool merge_sort_collations;
+ int extra_args;
+
+ /* Merge sort collations to parent only if there can be only one */
+ merge_sort_collations = (list_length(aggref->args) == 1 &&
+ get_func_variadictype(aggref->aggfnoid) == InvalidOid);
+
+ /* Process any non-hypothetical direct args */
+ extra_args = list_length(aggref->aggdirectargs) - list_length(aggref->args);
+ Assert(extra_args >= 0);
+ while (extra_args-- > 0)
+ {
+ (void) assign_collations_walker((Node *) lfirst(h_cell), loccontext);
+ h_cell = lnext(h_cell);
+ }
+
+ /* Scan hypothetical args and aggregated args in parallel */
+ while (h_cell && s_cell)
+ {
+ Node *h_arg = (Node *) lfirst(h_cell);
+ TargetEntry *s_tle = (TargetEntry *) lfirst(s_cell);
+ assign_collations_context paircontext;
+
+ /*
+ * Assign collations internally in this pair of expressions, then
+ * choose a common collation for them. This should match
+ * select_common_collation(), but we can't use that function as-is
+ * because we need access to the whole collation state so we can
+ * bubble it up to the aggregate function's level.
+ */
+ paircontext.pstate = loccontext->pstate;
+ paircontext.collation = InvalidOid;
+ paircontext.strength = COLLATE_NONE;
+ paircontext.location = -1;
+ /* Set these fields just to suppress uninitialized-value warnings: */
+ paircontext.collation2 = InvalidOid;
+ paircontext.location2 = -1;
+
+ (void) assign_collations_walker(h_arg, &paircontext);
+ (void) assign_collations_walker((Node *) s_tle->expr, &paircontext);
+
+ /* deal with collation conflict */
+ if (paircontext.strength == COLLATE_CONFLICT)
+ ereport(ERROR,
+ (errcode(ERRCODE_COLLATION_MISMATCH),
+ errmsg("collation mismatch between implicit collations \"%s\" and \"%s\"",
+ get_collation_name(paircontext.collation),
+ get_collation_name(paircontext.collation2)),
+ errhint("You can choose the collation by applying the COLLATE clause to one or both expressions."),
+ parser_errposition(paircontext.pstate,
+ paircontext.location2)));
+
+ /*
+ * At this point paircontext.collation can be InvalidOid only if the
+ * type is not collatable; no need to do anything in that case. If we
+ * do have to change the sort column's collation, do it by inserting a
+ * RelabelType node into the sort column TLE.
+ *
+ * XXX This is pretty grotty for a couple of reasons:
+ * assign_collations_walker isn't supposed to be changing the
+ * expression structure like this, and a parse-time change of
+ * collation ought to be signaled by a CollateExpr not a RelabelType
+ * (the use of RelabelType for collation marking is supposed to be a
+ * planner/executor thing only). But we have no better alternative.
+ * In particular, injecting a CollateExpr could result in the
+ * expression being interpreted differently after dump/reload, since
+ * we might be effectively promoting an implicit collation to
+ * explicit. This kluge is relying on ruleutils.c not printing a
+ * COLLATE clause for a RelabelType, and probably on some other
+ * fragile behaviors.
+ */
+ if (OidIsValid(paircontext.collation) &&
+ paircontext.collation != exprCollation((Node *) s_tle->expr))
+ {
+ s_tle->expr = (Expr *)
+ makeRelabelType(s_tle->expr,
+ exprType((Node *) s_tle->expr),
+ exprTypmod((Node *) s_tle->expr),
+ paircontext.collation,
+ COERCE_IMPLICIT_CAST);
+ }
+
+ /*
+ * If appropriate, merge this column's collation state up to the
+ * aggregate function.
+ */
+ if (merge_sort_collations)
+ merge_collation_state(paircontext.collation,
+ paircontext.strength,
+ paircontext.location,
+ paircontext.collation2,
+ paircontext.location2,
+ loccontext);
+
+ h_cell = lnext(h_cell);
+ s_cell = lnext(s_cell);
+ }
+ Assert(h_cell == NULL && s_cell == NULL);
}
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index 2a7d4cd072..04b585d1e2 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -3,7 +3,7 @@
* parse_cte.c
* handle CTEs (common table expressions) in parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -181,7 +181,7 @@ transformWithClause(ParseState *pstate, WithClause *withClause)
checkWellFormedRecursion(&cstate);
/*
- * Set up the ctenamespace for parse analysis. Per spec, all the WITH
+ * Set up the ctenamespace for parse analysis. Per spec, all the WITH
* items are visible to all others, so stuff them all in before parse
* analysis. We build the list in safe processing order so that the
* planner can process the queries in sequence.
@@ -207,7 +207,7 @@ transformWithClause(ParseState *pstate, WithClause *withClause)
{
/*
* For non-recursive WITH, just analyze each CTE in sequence and then
- * add it to the ctenamespace. This corresponds to the spec's
+ * add it to the ctenamespace. This corresponds to the spec's
* definition of the scope of each WITH name. However, to allow error
* reports to be aware of the possibility of an erroneous reference,
* we maintain a list in p_future_ctes of the not-yet-visible CTEs.
@@ -245,7 +245,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
cte->ctequery = (Node *) query;
/*
- * Check that we got something reasonable. These first two cases should
+ * Check that we got something reasonable. These first two cases should
* be prevented by the grammar.
*/
if (!IsA(query, Query))
@@ -393,7 +393,7 @@ analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist)
/*
* If the CTE is recursive, force the exposed column type of any
- * "unknown" column to "text". This corresponds to the fact that
+ * "unknown" column to "text". This corresponds to the fact that
* SELECT 'foo' UNION SELECT 'bar' will ultimately produce text. We
* might see "unknown" as a result of an untyped literal in the
* non-recursive term's select list, and if we don't convert to text
@@ -678,6 +678,18 @@ checkWellFormedRecursion(CteState *cstate)
if (cstate->selfrefcount != 1) /* shouldn't happen */
elog(ERROR, "missing recursive reference");
+ /* WITH mustn't contain self-reference, either */
+ if (stmt->withClause)
+ {
+ cstate->curitem = i;
+ cstate->innerwiths = NIL;
+ cstate->selfrefcount = 0;
+ cstate->context = RECURSION_SUBLINK;
+ checkWellFormedRecursionWalker((Node *) stmt->withClause->ctes,
+ cstate);
+ Assert(cstate->innerwiths == NIL);
+ }
+
/*
* Disallow ORDER BY and similar decoration atop the UNION. These
* don't make sense because it's impossible to figure out what they
@@ -933,7 +945,7 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
cstate);
checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
cstate);
- break;
+ /* stmt->withClause is intentionally ignored here */
break;
case SETOP_EXCEPT:
if (stmt->all)
@@ -952,6 +964,7 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
cstate);
checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
cstate);
+ /* stmt->withClause is intentionally ignored here */
break;
default:
elog(ERROR, "unrecognized set op: %d",
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 53de843cbe..f055042d5e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3,7 +3,7 @@
* parse_expr.c
* handle expressions in parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -22,6 +22,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -37,6 +38,7 @@
bool Transform_null_equals = false;
+static Node *transformExprRecurse(ParseState *pstate, Node *expr);
static Node *transformParamRef(ParseState *pstate, ParamRef *pref);
static Node *transformAExprOp(ParseState *pstate, A_Expr *a);
static Node *transformAExprAnd(ParseState *pstate, A_Expr *a);
@@ -91,6 +93,8 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
* function argument to the required type (via coerce_type())
* can apply transformExpr to an already-transformed subexpression.
* An example here is "SELECT count(*) + 1.0 FROM table".
+ * 3. CREATE TABLE t1 (LIKE t2 INCLUDING INDEXES) can pass in
+ * already-transformed index expressions.
* While it might be possible to eliminate these cases, the path of
* least resistance so far has been to ensure that transformExpr() does
* no damage if applied to an already-transformed tree. This is pretty
@@ -100,9 +104,27 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
* input and output of transformExpr; see SubLink for example.
*/
Node *
-transformExpr(ParseState *pstate, Node *expr)
+transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind)
{
- Node *result = NULL;
+ Node *result;
+ ParseExprKind sv_expr_kind;
+
+ /* Save and restore identity of expression type we're parsing */
+ Assert(exprKind != EXPR_KIND_NONE);
+ sv_expr_kind = pstate->p_expr_kind;
+ pstate->p_expr_kind = exprKind;
+
+ result = transformExprRecurse(pstate, expr);
+
+ pstate->p_expr_kind = sv_expr_kind;
+
+ return result;
+}
+
+static Node *
+transformExprRecurse(ParseState *pstate, Node *expr)
+{
+ Node *result;
if (expr == NULL)
return NULL;
@@ -133,7 +155,7 @@ transformExpr(ParseState *pstate, Node *expr)
{
A_Indirection *ind = (A_Indirection *) expr;
- result = transformExpr(pstate, ind->arg);
+ result = transformExprRecurse(pstate, ind->arg);
result = transformIndirection(pstate, result,
ind->indirection);
break;
@@ -230,6 +252,8 @@ transformExpr(ParseState *pstate, Node *expr)
break;
default:
elog(ERROR, "unrecognized A_Expr kind: %d", a->kind);
+ result = NULL; /* keep compiler quiet */
+ break;
}
break;
}
@@ -242,7 +266,7 @@ transformExpr(ParseState *pstate, Node *expr)
{
NamedArgExpr *na = (NamedArgExpr *) expr;
- na->arg = (Expr *) transformExpr(pstate, (Node *) na->arg);
+ na->arg = (Expr *) transformExprRecurse(pstate, (Node *) na->arg);
result = expr;
break;
}
@@ -279,7 +303,7 @@ transformExpr(ParseState *pstate, Node *expr)
{
NullTest *n = (NullTest *) expr;
- n->arg = (Expr *) transformExpr(pstate, (Node *) n->arg);
+ n->arg = (Expr *) transformExprRecurse(pstate, (Node *) n->arg);
/* the argument can be any type, so don't coerce it */
n->argisrow = type_is_rowtype(exprType((Node *) n->arg));
result = expr;
@@ -334,6 +358,7 @@ transformExpr(ParseState *pstate, Node *expr)
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
+ result = NULL; /* keep compiler quiet */
break;
}
@@ -438,8 +463,8 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
newresult = ParseFuncOrColumn(pstate,
list_make1(n),
list_make1(result),
- NIL, false, false, false,
- NULL, true, location);
+ NULL,
+ location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -481,7 +506,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
} crerr = CRERR_NO_COLUMN;
/*
- * Give the PreParseColumnRefHook, if any, first shot. If it returns
+ * Give the PreParseColumnRefHook, if any, first shot. If it returns
* non-null then that's all, folks.
*/
if (pstate->p_pre_columnref_hook != NULL)
@@ -552,7 +577,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
}
/*
- * Try to find the name as a relation. Note that only
+ * Try to find the name as a relation. Note that only
* relations already entered into the rangetable will be
* recognized.
*
@@ -606,8 +631,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, false, false, false,
- NULL, true, cref->location);
+ NULL,
+ cref->location);
}
break;
}
@@ -651,8 +676,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, false, false, false,
- NULL, true, cref->location);
+ NULL,
+ cref->location);
}
break;
}
@@ -709,8 +734,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, false, false, false,
- NULL, true, cref->location);
+ NULL,
+ cref->location);
}
break;
}
@@ -751,19 +776,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,
@@ -795,7 +808,7 @@ transformParamRef(ParseState *pstate, ParamRef *pref)
Node *result;
/*
- * The core parser knows nothing about Params. If a hook is supplied,
+ * The core parser knows nothing about Params. If a hook is supplied,
* call it. If not, or if the hook returns NULL, throw a generic error.
*/
if (pstate->p_paramref_hook != NULL)
@@ -855,7 +868,7 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
else
n->arg = (Expr *) lexpr;
- result = transformExpr(pstate, (Node *) n);
+ result = transformExprRecurse(pstate, (Node *) n);
}
else if (lexpr && IsA(lexpr, RowExpr) &&
rexpr && IsA(rexpr, SubLink) &&
@@ -872,14 +885,14 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
s->testexpr = lexpr;
s->operName = a->name;
s->location = a->location;
- result = transformExpr(pstate, (Node *) s);
+ result = transformExprRecurse(pstate, (Node *) s);
}
else if (lexpr && IsA(lexpr, RowExpr) &&
rexpr && IsA(rexpr, RowExpr))
{
- /* "row op row" */
- lexpr = transformExpr(pstate, lexpr);
- rexpr = transformExpr(pstate, rexpr);
+ /* ROW() op ROW() is handled specially */
+ lexpr = transformExprRecurse(pstate, lexpr);
+ rexpr = transformExprRecurse(pstate, rexpr);
Assert(IsA(lexpr, RowExpr));
Assert(IsA(rexpr, RowExpr));
@@ -892,8 +905,8 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
else
{
/* Ordinary scalar operator */
- lexpr = transformExpr(pstate, lexpr);
- rexpr = transformExpr(pstate, rexpr);
+ lexpr = transformExprRecurse(pstate, lexpr);
+ rexpr = transformExprRecurse(pstate, rexpr);
result = (Node *) make_op(pstate,
a->name,
@@ -908,8 +921,8 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
static Node *
transformAExprAnd(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExpr(pstate, a->lexpr);
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
lexpr = coerce_to_boolean(pstate, lexpr, "AND");
rexpr = coerce_to_boolean(pstate, rexpr, "AND");
@@ -922,8 +935,8 @@ transformAExprAnd(ParseState *pstate, A_Expr *a)
static Node *
transformAExprOr(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExpr(pstate, a->lexpr);
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
lexpr = coerce_to_boolean(pstate, lexpr, "OR");
rexpr = coerce_to_boolean(pstate, rexpr, "OR");
@@ -936,7 +949,7 @@ transformAExprOr(ParseState *pstate, A_Expr *a)
static Node *
transformAExprNot(ParseState *pstate, A_Expr *a)
{
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
rexpr = coerce_to_boolean(pstate, rexpr, "NOT");
@@ -948,8 +961,8 @@ transformAExprNot(ParseState *pstate, A_Expr *a)
static Node *
transformAExprOpAny(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExpr(pstate, a->lexpr);
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
return (Node *) make_scalar_array_op(pstate,
a->name,
@@ -962,8 +975,8 @@ transformAExprOpAny(ParseState *pstate, A_Expr *a)
static Node *
transformAExprOpAll(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExpr(pstate, a->lexpr);
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
return (Node *) make_scalar_array_op(pstate,
a->name,
@@ -976,13 +989,13 @@ transformAExprOpAll(ParseState *pstate, A_Expr *a)
static Node *
transformAExprDistinct(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExpr(pstate, a->lexpr);
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
if (lexpr && IsA(lexpr, RowExpr) &&
rexpr && IsA(rexpr, RowExpr))
{
- /* "row op row" */
+ /* ROW() op ROW() is handled specially */
return make_row_distinct_op(pstate, a->name,
(RowExpr *) lexpr,
(RowExpr *) rexpr,
@@ -1002,8 +1015,8 @@ transformAExprDistinct(ParseState *pstate, A_Expr *a)
static Node *
transformAExprNullIf(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExpr(pstate, a->lexpr);
- Node *rexpr = transformExpr(pstate, a->rexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *rexpr = transformExprRecurse(pstate, a->rexpr);
OpExpr *result;
result = (OpExpr *) make_op(pstate,
@@ -1041,7 +1054,7 @@ transformAExprOf(ParseState *pstate, A_Expr *a)
* Checking an expression for match to a list of type names. Will result
* in a boolean constant node.
*/
- Node *lexpr = transformExpr(pstate, a->lexpr);
+ Node *lexpr = transformExprRecurse(pstate, a->lexpr);
Const *result;
ListCell *telem;
Oid ltype,
@@ -1081,7 +1094,6 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
List *rvars;
List *rnonvars;
bool useOr;
- bool haveRowExpr;
ListCell *l;
/*
@@ -1094,24 +1106,21 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
/*
* We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only
- * possible if the inputs are all scalars (no RowExprs) and there is a
- * suitable array type available. If not, we fall back to a boolean
- * condition tree with multiple copies of the lefthand expression. Also,
- * any IN-list items that contain Vars are handled as separate boolean
- * conditions, because that gives the planner more scope for optimization
- * on such clauses.
+ * possible if there is a suitable array type available. If not, we fall
+ * back to a boolean condition tree with multiple copies of the lefthand
+ * expression. Also, any IN-list items that contain Vars are handled as
+ * separate boolean conditions, because that gives the planner more scope
+ * for optimization on such clauses.
*
- * First step: transform all the inputs, and detect whether any are
- * RowExprs or contain Vars.
+ * First step: transform all the inputs, and detect whether any contain
+ * Vars.
*/
- lexpr = transformExpr(pstate, a->lexpr);
- haveRowExpr = (lexpr && IsA(lexpr, RowExpr));
+ lexpr = transformExprRecurse(pstate, a->lexpr);
rexprs = rvars = rnonvars = NIL;
foreach(l, (List *) a->rexpr)
{
- Node *rexpr = transformExpr(pstate, lfirst(l));
+ Node *rexpr = transformExprRecurse(pstate, lfirst(l));
- haveRowExpr |= (rexpr && IsA(rexpr, RowExpr));
rexprs = lappend(rexprs, rexpr);
if (contain_vars_of_level(rexpr, 0))
rvars = lappend(rvars, rexpr);
@@ -1121,16 +1130,16 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
/*
* ScalarArrayOpExpr is only going to be useful if there's more than one
- * non-Var righthand item. Also, it won't work for RowExprs.
+ * non-Var righthand item.
*/
- if (!haveRowExpr && list_length(rnonvars) > 1)
+ if (list_length(rnonvars) > 1)
{
List *allexprs;
Oid scalar_type;
Oid array_type;
/*
- * Try to select a common type for the array elements. Note that
+ * Try to select a common type for the array elements. Note that
* since the LHS' type is first in the list, it will be preferred when
* there is doubt (eg, when all the RHS items are unknown literals).
*
@@ -1139,8 +1148,13 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
allexprs = list_concat(list_make1(lexpr), rnonvars);
scalar_type = select_common_type(pstate, allexprs, NULL, NULL);
- /* Do we have an array type to use? */
- if (OidIsValid(scalar_type))
+ /*
+ * Do we have an array type to use? Aside from the case where there
+ * isn't one, we don't risk using ScalarArrayOpExpr when the common
+ * type is RECORD, because the RowExpr comparison logic below can cope
+ * with some cases of non-identical row types.
+ */
+ if (OidIsValid(scalar_type) && scalar_type != RECORDOID)
array_type = get_array_type(scalar_type);
else
array_type = InvalidOid;
@@ -1191,14 +1205,10 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
Node *rexpr = (Node *) lfirst(l);
Node *cmp;
- if (haveRowExpr)
+ if (IsA(lexpr, RowExpr) &&
+ IsA(rexpr, RowExpr))
{
- if (!IsA(lexpr, RowExpr) ||
- !IsA(rexpr, RowExpr))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("arguments of row IN must all be row expressions"),
- parser_errposition(pstate, a->location)));
+ /* ROW() op ROW() is handled specially */
cmp = make_row_comparison_op(pstate,
a->name,
(List *) copyObject(((RowExpr *) lexpr)->args),
@@ -1206,11 +1216,14 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
a->location);
}
else
+ {
+ /* Ordinary scalar operator */
cmp = (Node *) make_op(pstate,
a->name,
copyObject(lexpr),
rexpr,
a->location);
+ }
cmp = coerce_to_boolean(pstate, cmp, "IN");
if (result == NULL)
@@ -1234,20 +1247,35 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
targs = NIL;
foreach(args, fn->args)
{
- targs = lappend(targs, transformExpr(pstate,
- (Node *) lfirst(args)));
+ targs = lappend(targs, transformExprRecurse(pstate,
+ (Node *) lfirst(args)));
+ }
+
+ /*
+ * When WITHIN GROUP is used, we treat its ORDER BY expressions as
+ * additional arguments to the function, for purposes of function lookup
+ * and argument type coercion. So, transform each such expression and add
+ * them to the targs list. We don't explicitly mark where each argument
+ * came from, but ParseFuncOrColumn can tell what's what by reference to
+ * list_length(fn->agg_order).
+ */
+ if (fn->agg_within_group)
+ {
+ Assert(fn->agg_order != NIL);
+ foreach(args, fn->agg_order)
+ {
+ SortBy *arg = (SortBy *) lfirst(args);
+
+ targs = lappend(targs, transformExpr(pstate, arg->node,
+ EXPR_KIND_ORDER_BY));
+ }
}
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
targs,
- fn->agg_order,
- fn->agg_star,
- fn->agg_distinct,
- fn->func_variadic,
- fn->over,
- false,
+ fn,
fn->location);
}
@@ -1271,7 +1299,7 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c)
newc = makeNode(CaseExpr);
/* transform the test expression, if any */
- arg = transformExpr(pstate, (Node *) c->arg);
+ arg = transformExprRecurse(pstate, (Node *) c->arg);
/* generate placeholder for test expression */
if (arg)
@@ -1324,14 +1352,14 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c)
warg,
w->location);
}
- neww->expr = (Expr *) transformExpr(pstate, warg);
+ neww->expr = (Expr *) transformExprRecurse(pstate, warg);
neww->expr = (Expr *) coerce_to_boolean(pstate,
(Node *) neww->expr,
"CASE/WHEN");
warg = (Node *) w->result;
- neww->result = (Expr *) transformExpr(pstate, warg);
+ neww->result = (Expr *) transformExprRecurse(pstate, warg);
neww->location = w->location;
newargs = lappend(newargs, neww);
@@ -1350,7 +1378,7 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c)
n->location = -1;
defresult = (Node *) n;
}
- newc->defresult = (Expr *) transformExpr(pstate, defresult);
+ newc->defresult = (Expr *) transformExprRecurse(pstate, defresult);
/*
* Note: default result is considered the most significant type in
@@ -1393,16 +1421,97 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
{
Node *result = (Node *) sublink;
Query *qtree;
+ const char *err;
/* If we already transformed this node, do nothing */
if (IsA(sublink->subselect, Query))
return result;
+ /*
+ * Check to see if the sublink is in an invalid place within the query. We
+ * allow sublinks everywhere in SELECT/INSERT/UPDATE/DELETE, but generally
+ * not in utility statements.
+ */
+ err = NULL;
+ switch (pstate->p_expr_kind)
+ {
+ case EXPR_KIND_NONE:
+ Assert(false); /* can't happen */
+ break;
+ case EXPR_KIND_OTHER:
+ /* Accept sublink here; caller must throw error if wanted */
+ break;
+ case EXPR_KIND_JOIN_ON:
+ case EXPR_KIND_JOIN_USING:
+ case EXPR_KIND_FROM_SUBSELECT:
+ case EXPR_KIND_FROM_FUNCTION:
+ case EXPR_KIND_WHERE:
+ case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
+ case EXPR_KIND_WINDOW_PARTITION:
+ case EXPR_KIND_WINDOW_ORDER:
+ case EXPR_KIND_WINDOW_FRAME_RANGE:
+ case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_SELECT_TARGET:
+ case EXPR_KIND_INSERT_TARGET:
+ case EXPR_KIND_UPDATE_SOURCE:
+ case EXPR_KIND_UPDATE_TARGET:
+ case EXPR_KIND_GROUP_BY:
+ case EXPR_KIND_ORDER_BY:
+ case EXPR_KIND_DISTINCT_ON:
+ case EXPR_KIND_LIMIT:
+ case EXPR_KIND_OFFSET:
+ case EXPR_KIND_RETURNING:
+ case EXPR_KIND_VALUES:
+ /* okay */
+ break;
+ case EXPR_KIND_CHECK_CONSTRAINT:
+ case EXPR_KIND_DOMAIN_CHECK:
+ err = _("cannot use subquery in check constraint");
+ break;
+ case EXPR_KIND_COLUMN_DEFAULT:
+ case EXPR_KIND_FUNCTION_DEFAULT:
+ err = _("cannot use subquery in DEFAULT expression");
+ break;
+ case EXPR_KIND_INDEX_EXPRESSION:
+ err = _("cannot use subquery in index expression");
+ break;
+ case EXPR_KIND_INDEX_PREDICATE:
+ err = _("cannot use subquery in index predicate");
+ break;
+ case EXPR_KIND_ALTER_COL_TRANSFORM:
+ err = _("cannot use subquery in transform expression");
+ break;
+ case EXPR_KIND_EXECUTE_PARAMETER:
+ err = _("cannot use subquery in EXECUTE parameter");
+ break;
+ case EXPR_KIND_TRIGGER_WHEN:
+ err = _("cannot use subquery in trigger WHEN condition");
+ break;
+
+ /*
+ * There is intentionally no default: case here, so that the
+ * compiler will warn if we add a new ParseExprKind without
+ * extending this switch. If we do see an unrecognized value at
+ * runtime, the behavior will be the same as for EXPR_KIND_OTHER,
+ * which is sane anyway.
+ */
+ }
+ if (err)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg_internal("%s", err),
+ parser_errposition(pstate, sublink->location)));
+
pstate->p_hasSubLinks = true;
+
+ /*
+ * OK, let's transform the sub-SELECT.
+ */
qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false);
/*
- * Check that we got something reasonable. Many of these conditions are
+ * Check that we got something reasonable. Many of these conditions are
* impossible given restrictions of the grammar, but check 'em anyway.
*/
if (!IsA(qtree, Query) ||
@@ -1463,7 +1572,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
/*
* Transform lefthand expression, and convert to a list
*/
- lefthand = transformExpr(pstate, sublink->testexpr);
+ lefthand = transformExprRecurse(pstate, sublink->testexpr);
if (lefthand && IsA(lefthand, RowExpr))
left_list = ((RowExpr *) lefthand)->args;
else
@@ -1570,7 +1679,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
- newe = transformExpr(pstate, e);
+ newe = transformExprRecurse(pstate, e);
/*
* Check for sub-array expressions, if we haven't already found
@@ -1686,13 +1795,19 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
static Node *
transformRowExpr(ParseState *pstate, RowExpr *r)
{
- RowExpr *newr = makeNode(RowExpr);
+ RowExpr *newr;
char fname[16];
int fnum;
ListCell *lc;
+ /* If we already transformed this node, do nothing */
+ if (OidIsValid(r->row_typeid))
+ return (Node *) r;
+
+ newr = makeNode(RowExpr);
+
/* Transform the field expressions */
- newr->args = transformExpressionList(pstate, r->args);
+ newr->args = transformExpressionList(pstate, r->args, pstate->p_expr_kind);
/* Barring later casting, we consider the type RECORD */
newr->row_typeid = RECORDOID;
@@ -1725,7 +1840,7 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c)
Node *e = (Node *) lfirst(args);
Node *newe;
- newe = transformExpr(pstate, e);
+ newe = transformExprRecurse(pstate, e);
newargs = lappend(newargs, newe);
}
@@ -1764,7 +1879,7 @@ transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m)
Node *e = (Node *) lfirst(args);
Node *newe;
- newe = transformExpr(pstate, e);
+ newe = transformExprRecurse(pstate, e);
newargs = lappend(newargs, newe);
}
@@ -1791,20 +1906,27 @@ transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m)
static Node *
transformXmlExpr(ParseState *pstate, XmlExpr *x)
{
- XmlExpr *newx = makeNode(XmlExpr);
+ XmlExpr *newx;
ListCell *lc;
int i;
+ /* If we already transformed this node, do nothing */
+ if (OidIsValid(x->type))
+ return (Node *) x;
+
+ newx = makeNode(XmlExpr);
newx->op = x->op;
if (x->name)
newx->name = map_sql_identifier_to_xml_name(x->name, false, false);
else
newx->name = NULL;
newx->xmloption = x->xmloption;
+ newx->type = XMLOID; /* this just marks the node as transformed */
+ newx->typmod = -1;
newx->location = x->location;
/*
- * gram.y built the named args as a list of ResTarget. Transform each,
+ * gram.y built the named args as a list of ResTarget. Transform each,
* and break the names out as a separate list.
*/
newx->named_args = NIL;
@@ -1818,7 +1940,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
Assert(IsA(r, ResTarget));
- expr = transformExpr(pstate, r->val);
+ expr = transformExprRecurse(pstate, r->val);
if (r->name)
argname = map_sql_identifier_to_xml_name(r->name, false, false);
@@ -1864,7 +1986,7 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
Node *e = (Node *) lfirst(lc);
Node *newe;
- newe = transformExpr(pstate, e);
+ newe = transformExprRecurse(pstate, e);
switch (x->op)
{
case IS_XMLCONCAT:
@@ -1927,7 +2049,7 @@ transformXmlSerialize(ParseState *pstate, XmlSerialize *xs)
xexpr = makeNode(XmlExpr);
xexpr->op = IS_XMLSERIALIZE;
xexpr->args = list_make1(coerce_to_specific_type(pstate,
- transformExpr(pstate, xs->expr),
+ transformExprRecurse(pstate, xs->expr),
XMLOID,
"XMLSERIALIZE"));
@@ -1990,7 +2112,7 @@ transformBooleanTest(ParseState *pstate, BooleanTest *b)
clausename = NULL; /* keep compiler quiet */
}
- b->arg = (Expr *) transformExpr(pstate, (Node *) b->arg);
+ b->arg = (Expr *) transformExprRecurse(pstate, (Node *) b->arg);
b->arg = (Expr *) coerce_to_boolean(pstate,
(Node *) b->arg,
@@ -2073,9 +2195,9 @@ transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location)
vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
/*
- * Build the appropriate referencing node. Note that if the RTE is a
+ * Build the appropriate referencing node. Note that if the RTE is a
* function returning scalar, we create just a plain reference to the
- * function value, not a composite containing a single column. This is
+ * function value, not a composite containing a single column. This is
* pretty inconsistent at first sight, but it's what we've done
* historically. One argument for it is that "rel" and "rel.*" mean the
* same thing for composite relations, so why not for scalar functions...
@@ -2101,7 +2223,7 @@ static Node *
transformTypeCast(ParseState *pstate, TypeCast *tc)
{
Node *result;
- Node *expr = transformExpr(pstate, tc->arg);
+ Node *expr = transformExprRecurse(pstate, tc->arg);
Oid inputType = exprType(expr);
Oid targetType;
int32 targetTypmod;
@@ -2149,7 +2271,7 @@ transformCollateClause(ParseState *pstate, CollateClause *c)
Oid argtype;
newc = makeNode(CollateExpr);
- newc->arg = (Expr *) transformExpr(pstate, c->arg);
+ newc->arg = (Expr *) transformExprRecurse(pstate, c->arg);
argtype = exprType((Node *) newc->arg);
@@ -2259,7 +2381,7 @@ make_row_comparison_op(ParseState *pstate, List *opname,
/*
* Now we must determine which row comparison semantics (= <> < <= > >=)
- * apply to this set of operators. We look for btree opfamilies
+ * apply to this set of operators. We look for btree opfamilies
* containing the operators, and see which interpretations (strategy
* numbers) exist for each operator.
*/
@@ -2452,3 +2574,89 @@ make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
return result;
}
+
+/*
+ * Produce a string identifying an expression by kind.
+ *
+ * Note: when practical, use a simple SQL keyword for the result. If that
+ * doesn't work well, check call sites to see whether custom error message
+ * strings are required.
+ */
+const char *
+ParseExprKindName(ParseExprKind exprKind)
+{
+ switch (exprKind)
+ {
+ case EXPR_KIND_NONE:
+ return "invalid expression context";
+ case EXPR_KIND_OTHER:
+ return "extension expression";
+ case EXPR_KIND_JOIN_ON:
+ return "JOIN/ON";
+ case EXPR_KIND_JOIN_USING:
+ return "JOIN/USING";
+ case EXPR_KIND_FROM_SUBSELECT:
+ return "sub-SELECT in FROM";
+ case EXPR_KIND_FROM_FUNCTION:
+ return "function in FROM";
+ case EXPR_KIND_WHERE:
+ return "WHERE";
+ case EXPR_KIND_HAVING:
+ return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
+ case EXPR_KIND_WINDOW_PARTITION:
+ return "window PARTITION BY";
+ case EXPR_KIND_WINDOW_ORDER:
+ return "window ORDER BY";
+ case EXPR_KIND_WINDOW_FRAME_RANGE:
+ return "window RANGE";
+ case EXPR_KIND_WINDOW_FRAME_ROWS:
+ return "window ROWS";
+ case EXPR_KIND_SELECT_TARGET:
+ return "SELECT";
+ case EXPR_KIND_INSERT_TARGET:
+ return "INSERT";
+ case EXPR_KIND_UPDATE_SOURCE:
+ case EXPR_KIND_UPDATE_TARGET:
+ return "UPDATE";
+ case EXPR_KIND_GROUP_BY:
+ return "GROUP BY";
+ case EXPR_KIND_ORDER_BY:
+ return "ORDER BY";
+ case EXPR_KIND_DISTINCT_ON:
+ return "DISTINCT ON";
+ case EXPR_KIND_LIMIT:
+ return "LIMIT";
+ case EXPR_KIND_OFFSET:
+ return "OFFSET";
+ case EXPR_KIND_RETURNING:
+ return "RETURNING";
+ case EXPR_KIND_VALUES:
+ return "VALUES";
+ case EXPR_KIND_CHECK_CONSTRAINT:
+ case EXPR_KIND_DOMAIN_CHECK:
+ return "CHECK";
+ case EXPR_KIND_COLUMN_DEFAULT:
+ case EXPR_KIND_FUNCTION_DEFAULT:
+ return "DEFAULT";
+ case EXPR_KIND_INDEX_EXPRESSION:
+ return "index expression";
+ case EXPR_KIND_INDEX_PREDICATE:
+ return "index predicate";
+ case EXPR_KIND_ALTER_COL_TRANSFORM:
+ return "USING";
+ case EXPR_KIND_EXECUTE_PARAMETER:
+ return "EXECUTE";
+ case EXPR_KIND_TRIGGER_WHEN:
+ return "WHEN";
+
+ /*
+ * There is intentionally no default: case here, so that the
+ * compiler will warn if we add a new ParseExprKind without
+ * extending this switch. If we do see an unrecognized value at
+ * runtime, we'll fall through to the "unrecognized" return.
+ */
+ }
+ return "unrecognized expression kind";
+}
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index b051707d7e..9ebd3fd43b 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -3,7 +3,7 @@
* parse_func.c
* handle function calls in parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -14,12 +14,16 @@
*/
#include "postgres.h"
+#include "access/htup_details.h"
+#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
+#include "lib/stringinfo.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_relation.h"
@@ -30,6 +34,9 @@
#include "utils/syscache.h"
+static void unify_hypothetical_args(ParseState *pstate,
+ List *fargs, int numAggregatedArgs,
+ Oid *actual_arg_types, Oid *declared_arg_types);
static Oid FuncNameAsType(List *funcname);
static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
Node *first_arg, int location);
@@ -45,24 +52,30 @@ static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
* a function of a single complex-type argument can be written like a
* column reference, allowing functions to act like computed columns.
*
- * Hence, both cases come through here. The is_column parameter tells us
- * which syntactic construct is actually being dealt with, but this is
- * intended to be used only to deliver an appropriate error message,
- * not to affect the semantics. When is_column is true, we should have
- * a single argument (the putative table), unqualified function name
- * equal to the column name, and no aggregate or variadic decoration.
- * Also, when is_column is true, we return NULL on failure rather than
+ * Hence, both cases come through here. If fn is null, we're dealing with
+ * column syntax not function syntax, but in principle that should not
+ * affect the lookup behavior, only which error messages we deliver.
+ * The FuncCall struct is needed however to carry various decoration that
+ * applies to aggregate and window functions.
+ *
+ * Also, when fn is null, we return NULL on failure rather than
* reporting a no-such-function error.
*
- * The argument expressions (in fargs) must have been transformed already.
- * But the agg_order expressions, if any, have not been.
+ * The argument expressions (in fargs) must have been transformed
+ * already. However, nothing in *fn has been transformed.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- List *agg_order, bool agg_star, bool agg_distinct,
- bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ FuncCall *fn, int location)
{
+ bool is_column = (fn == NULL);
+ List *agg_order = (fn ? fn->agg_order : NIL);
+ Expr *agg_filter = NULL;
+ bool agg_within_group = (fn ? fn->agg_within_group : false);
+ bool agg_star = (fn ? fn->agg_star : false);
+ bool agg_distinct = (fn ? fn->agg_distinct : false);
+ bool func_variadic = (fn ? fn->func_variadic : false);
+ WindowDef *over = (fn ? fn->over : NULL);
Oid rettype;
Oid funcid;
ListCell *l;
@@ -77,11 +90,21 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
Node *retval;
bool retset;
int nvargs;
+ Oid vatype;
FuncDetailCode fdresult;
+ char aggkind = 0;
+
+ /*
+ * If there's an aggregate filter, transform it using transformWhereClause
+ */
+ if (fn && fn->agg_filter != NULL)
+ agg_filter = (Expr *) transformWhereClause(pstate, fn->agg_filter,
+ EXPR_KIND_FILTER,
+ "FILTER");
/*
* Most of the rest of the parser just assumes that functions do not have
- * more than FUNC_MAX_ARGS parameters. We have to test here to protect
+ * more than FUNC_MAX_ARGS parameters. We have to test here to protect
* against array overruns, etc. Of course, this may not be a function,
* but the test doesn't hurt.
*/
@@ -98,10 +121,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* Extract arg type info in preparation for function lookup.
*
* If any arguments are Param markers of type VOID, we discard them from
- * the parameter list. This is a hack to allow the JDBC driver to not
- * have to distinguish "input" and "output" parameter symbols while
- * parsing function-call constructs. We can't use foreach() because we
- * may modify the list ...
+ * the parameter list. This is a hack to allow the JDBC driver to not have
+ * to distinguish "input" and "output" parameter symbols while parsing
+ * function-call constructs. Don't do this if dealing with column syntax,
+ * nor if we had WITHIN GROUP (because in that case it's critical to keep
+ * the argument count unchanged). We can't use foreach() because we may
+ * modify the list ...
*/
nargs = 0;
for (l = list_head(fargs); l != NULL; l = nextl)
@@ -111,7 +136,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
nextl = lnext(l);
- if (argtype == VOIDOID && IsA(arg, Param) &&!is_column)
+ if (argtype == VOIDOID && IsA(arg, Param) &&
+ !is_column && !agg_within_group)
{
fargs = list_delete_ptr(fargs, arg);
continue;
@@ -172,8 +198,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* the "function call" could be a projection. We also check that there
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
- if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
- over == NULL && !func_variadic && argnames == NIL &&
+ if (nargs == 1 && agg_order == NIL && agg_filter == NULL && !agg_star &&
+ !agg_distinct && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
@@ -212,7 +238,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true,
- &funcid, &rettype, &retset, &nvargs,
+ &funcid, &rettype, &retset,
+ &nvargs, &vatype,
&declared_arg_types, &argdefaults);
if (fdresult == FUNCDETAIL_COERCION)
{
@@ -243,12 +270,24 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("DISTINCT specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (agg_order != NIL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -256,8 +295,181 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
NameListToString(funcname)),
parser_errposition(pstate, location)));
}
- else if (!(fdresult == FUNCDETAIL_AGGREGATE ||
- fdresult == FUNCDETAIL_WINDOWFUNC))
+ else if (fdresult == FUNCDETAIL_AGGREGATE)
+ {
+ /*
+ * It's an aggregate; fetch needed info from the pg_aggregate entry.
+ */
+ HeapTuple tup;
+ Form_pg_aggregate classForm;
+ int catDirectArgs;
+
+ tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for aggregate %u", funcid);
+ classForm = (Form_pg_aggregate) GETSTRUCT(tup);
+ aggkind = classForm->aggkind;
+ catDirectArgs = classForm->aggnumdirectargs;
+ ReleaseSysCache(tup);
+
+ /* Now check various disallowed cases. */
+ if (AGGKIND_IS_ORDERED_SET(aggkind))
+ {
+ int numAggregatedArgs;
+ int numDirectArgs;
+
+ if (!agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("WITHIN GROUP is required for ordered-set aggregate %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ if (over)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("OVER is not supported for ordered-set aggregate %s",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ /* gram.y rejects DISTINCT + WITHIN GROUP */
+ Assert(!agg_distinct);
+ /* gram.y rejects VARIADIC + WITHIN GROUP */
+ Assert(!func_variadic);
+
+ /*
+ * Since func_get_detail was working with an undifferentiated list
+ * of arguments, it might have selected an aggregate that doesn't
+ * really match because it requires a different division of direct
+ * and aggregated arguments. Check that the number of direct
+ * arguments is actually OK; if not, throw an "undefined function"
+ * error, similarly to the case where a misplaced ORDER BY is used
+ * in a regular aggregate call.
+ */
+ numAggregatedArgs = list_length(agg_order);
+ numDirectArgs = nargs - numAggregatedArgs;
+ Assert(numDirectArgs >= 0);
+
+ if (!OidIsValid(vatype))
+ {
+ /* Test is simple if aggregate isn't variadic */
+ if (numDirectArgs != catDirectArgs)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("function %s does not exist",
+ func_signature_string(funcname, nargs,
+ argnames,
+ actual_arg_types)),
+ errhint("There is an ordered-set aggregate %s, but it requires %d direct arguments, not %d.",
+ NameListToString(funcname),
+ catDirectArgs, numDirectArgs),
+ parser_errposition(pstate, location)));
+ }
+ else
+ {
+ /*
+ * If it's variadic, we have two cases depending on whether
+ * the agg was "... ORDER BY VARIADIC" or "..., VARIADIC ORDER
+ * BY VARIADIC". It's the latter if catDirectArgs equals
+ * pronargs; to save a catalog lookup, we reverse-engineer
+ * pronargs from the info we got from func_get_detail.
+ */
+ int pronargs;
+
+ pronargs = nargs;
+ if (nvargs > 1)
+ pronargs -= nvargs - 1;
+ if (catDirectArgs < pronargs)
+ {
+ /* VARIADIC isn't part of direct args, so still easy */
+ if (numDirectArgs != catDirectArgs)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("function %s does not exist",
+ func_signature_string(funcname, nargs,
+ argnames,
+ actual_arg_types)),
+ errhint("There is an ordered-set aggregate %s, but it requires %d direct arguments, not %d.",
+ NameListToString(funcname),
+ catDirectArgs, numDirectArgs),
+ parser_errposition(pstate, location)));
+ }
+ else
+ {
+ /*
+ * Both direct and aggregated args were declared variadic.
+ * For a standard ordered-set aggregate, it's okay as long
+ * as there aren't too few direct args. For a
+ * hypothetical-set aggregate, we assume that the
+ * hypothetical arguments are those that matched the
+ * variadic parameter; there must be just as many of them
+ * as there are aggregated arguments.
+ */
+ if (aggkind == AGGKIND_HYPOTHETICAL)
+ {
+ if (nvargs != 2 * numAggregatedArgs)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("function %s does not exist",
+ func_signature_string(funcname, nargs,
+ argnames,
+ actual_arg_types)),
+ errhint("To use the hypothetical-set aggregate %s, the number of hypothetical direct arguments (here %d) must match the number of ordering columns (here %d).",
+ NameListToString(funcname),
+ nvargs - numAggregatedArgs, numAggregatedArgs),
+ parser_errposition(pstate, location)));
+ }
+ else
+ {
+ if (nvargs <= numAggregatedArgs)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("function %s does not exist",
+ func_signature_string(funcname, nargs,
+ argnames,
+ actual_arg_types)),
+ errhint("There is an ordered-set aggregate %s, but it requires at least %d direct arguments.",
+ NameListToString(funcname),
+ catDirectArgs),
+ parser_errposition(pstate, location)));
+ }
+ }
+ }
+
+ /* Check type matching of hypothetical arguments */
+ if (aggkind == AGGKIND_HYPOTHETICAL)
+ unify_hypothetical_args(pstate, fargs, numAggregatedArgs,
+ actual_arg_types, declared_arg_types);
+ }
+ else
+ {
+ /* Normal aggregate, so it can't have WITHIN GROUP */
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not an ordered-set aggregate, so it cannot have WITHIN GROUP",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ }
+ }
+ else if (fdresult == FUNCDETAIL_WINDOWFUNC)
+ {
+ /*
+ * True window functions must be called with a window definition.
+ */
+ if (!over)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("window function %s requires an OVER clause",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ /* And, per spec, WITHIN GROUP isn't allowed */
+ if (agg_within_group)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("window function %s cannot have WITHIN GROUP",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+ }
+ else
{
/*
* Oops. Time to die.
@@ -280,7 +492,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errhint("Could not choose a best candidate function. "
"You might need to add explicit type casts."),
parser_errposition(pstate, location)));
- else if (list_length(agg_order) > 1)
+ else if (list_length(agg_order) > 1 && !agg_within_group)
{
/* It's agg(x, ORDER BY y,z) ... perhaps misplaced ORDER BY */
ereport(ERROR,
@@ -308,7 +520,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* If there are default arguments, we have to include their types in
* actual_arg_types for the purpose of checking generic type consistency.
* However, we do NOT put them into the generated parse node, because
- * their actual values might change before the query gets run. The
+ * their actual values might change before the query gets run. The
* planner has to insert the up-to-date values at plan time.
*/
nargsplusdefs = nargs;
@@ -344,10 +556,21 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
/*
+ * If the function isn't actually variadic, forget any VARIADIC decoration
+ * on the call. (Perhaps we should throw an error instead, but
+ * historically we've allowed people to write that.)
+ */
+ if (!OidIsValid(vatype))
+ {
+ Assert(nvargs == 0);
+ func_variadic = false;
+ }
+
+ /*
* If it's a variadic function call, transform the last nvargs arguments
* into an array --- unless it's an "any" variadic.
*/
- if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID)
+ if (nvargs > 0 && vatype != ANYOID)
{
ArrayExpr *newa = makeNode(ArrayExpr);
int non_var_args = nargs - nvargs;
@@ -372,6 +595,27 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
newa->location = exprLocation((Node *) vargs);
fargs = lappend(fargs, newa);
+
+ /* We could not have had VARIADIC marking before ... */
+ Assert(!func_variadic);
+ /* ... but now, it's a VARIADIC call */
+ func_variadic = true;
+ }
+
+ /*
+ * If an "any" variadic is called with explicit VARIADIC marking, insist
+ * that the variadic parameter be of some array type.
+ */
+ if (nargs > 0 && vatype == ANYOID && func_variadic)
+ {
+ Oid va_arr_typid = actual_arg_types[nargs - 1];
+
+ if (!OidIsValid(get_base_element_type(va_arr_typid)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("VARIADIC argument must be an array"),
+ parser_errposition(pstate,
+ exprLocation((Node *) llast(fargs)))));
}
/* build the appropriate output structure */
@@ -382,6 +626,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
funcexpr->funcid = funcid;
funcexpr->funcresulttype = rettype;
funcexpr->funcretset = retset;
+ funcexpr->funcvariadic = func_variadic;
funcexpr->funcformat = COERCE_EXPLICIT_CALL;
/* funccollid and inputcollid will be set by parse_collate.c */
funcexpr->args = fargs;
@@ -397,16 +642,20 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
aggref->aggfnoid = funcid;
aggref->aggtype = rettype;
/* aggcollid and inputcollid will be set by parse_collate.c */
- /* args, aggorder, aggdistinct will be set by transformAggregateCall */
+ /* aggdirectargs and args will be set by transformAggregateCall */
+ /* aggorder and aggdistinct will be set by transformAggregateCall */
+ aggref->aggfilter = agg_filter;
aggref->aggstar = agg_star;
+ aggref->aggvariadic = func_variadic;
+ aggref->aggkind = aggkind;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
/*
* Reject attempt to call a parameterless aggregate without (*)
- * syntax. This is mere pedantry but some folks insisted ...
+ * syntax. This is mere pedantry but some folks insisted ...
*/
- if (fargs == NIL && !agg_star)
+ if (fargs == NIL && !agg_star && !agg_within_group)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("%s(*) must be used to call a parameterless aggregate function",
@@ -420,10 +669,13 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
- * Currently it's not possible to define an aggregate with named
- * arguments, so this case should be impossible. Check anyway because
- * the planner and executor wouldn't cope with NamedArgExprs in an
- * Aggref node.
+ * We might want to support named arguments later, but disallow it for
+ * now. We'd need to figure out the parsed representation (should the
+ * NamedArgExprs go above or below the TargetEntry nodes?) and then
+ * teach the planner to reorder the list properly. Or maybe we could
+ * make transformAggregateCall do that? However, if you'd also like
+ * to allow default arguments for aggregates, we'd need to do it in
+ * planning to avoid semantic problems.
*/
if (argnames != NIL)
ereport(ERROR,
@@ -441,14 +693,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* window function */
WindowFunc *wfunc = makeNode(WindowFunc);
- /*
- * True window functions must be called with a window definition.
- */
- if (!over)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("window function call requires an OVER clause"),
- parser_errposition(pstate, location)));
+ Assert(over); /* lack of this was checked above */
+ Assert(!agg_within_group); /* also checked above */
wfunc->winfnoid = funcid;
wfunc->wintype = rettype;
@@ -457,6 +703,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->aggfilter = agg_filter;
wfunc->location = location;
/*
@@ -470,7 +717,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/*
* Reject attempt to call a parameterless aggregate without (*)
- * syntax. This is mere pedantry but some folks insisted ...
+ * syntax. This is mere pedantry but some folks insisted ...
*/
if (wfunc->winagg && fargs == NIL && !agg_star)
ereport(ERROR,
@@ -488,21 +735,19 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("aggregate ORDER BY is not implemented for window functions"),
parser_errposition(pstate, location)));
- if (retset)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("window functions cannot return sets"),
- parser_errposition(pstate, location)));
-
/*
- * We might want to support this later, but for now reject it because
- * the planner and executor wouldn't cope with NamedArgExprs in a
- * WindowFunc node.
+ * FILTER is not yet supported with true window functions
*/
- if (argnames != NIL)
+ if (!wfunc->winagg && agg_filter)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("window functions cannot use named arguments"),
+ errmsg("FILTER is not implemented for non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ if (retset)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("window functions cannot return sets"),
parser_errposition(pstate, location)));
/* parse_agg.c does additional window-func-specific processing */
@@ -650,7 +895,7 @@ func_select_candidate(int nargs,
* matches" in the exact-match heuristic; it also makes it possible to do
* something useful with the type-category heuristics. Note that this
* makes it difficult, but not impossible, to use functions declared to
- * take a domain as an input datatype. Such a function will be selected
+ * take a domain as an input datatype. Such a function will be selected
* over the base-type function only if it is an exact match at all
* argument positions, and so was already chosen by our caller.
*
@@ -774,7 +1019,7 @@ func_select_candidate(int nargs,
/*
* The next step examines each unknown argument position to see if we can
- * determine a "type category" for it. If any candidate has an input
+ * determine a "type category" for it. If any candidate has an input
* datatype of STRING category, use STRING category (this bias towards
* STRING is appropriate since unknown-type literals look like strings).
* Otherwise, if all the candidates agree on the type category of this
@@ -785,7 +1030,7 @@ func_select_candidate(int nargs,
* the candidates takes a preferred datatype within the category.
*
* Having completed this examination, remove candidates that accept the
- * wrong category at any unknown position. Also, if at least one
+ * wrong category at any unknown position. Also, if at least one
* candidate accepted a preferred type at a position, remove candidates
* that accept non-preferred types. If just one candidate remains, return
* that one. However, if this rule turns out to reject all candidates,
@@ -914,7 +1159,7 @@ func_select_candidate(int nargs,
* type, and see if that gives us a unique match. If so, use that match.
*
* NOTE: for a binary operator with one unknown and one non-unknown input,
- * we already tried this heuristic in binary_oper_exact(). However, that
+ * we already tried this heuristic in binary_oper_exact(). However, that
* code only finds exact matches, whereas here we will handle matches that
* involve coercion, polymorphic type resolution, etc.
*/
@@ -1012,6 +1257,7 @@ func_get_detail(List *funcname,
Oid *rettype, /* return value */
bool *retset, /* return value */
int *nvargs, /* return value */
+ Oid *vatype, /* return value */
Oid **true_typeids, /* return value */
List **argdefaults) /* optional return value */
{
@@ -1023,13 +1269,15 @@ func_get_detail(List *funcname,
*rettype = InvalidOid;
*retset = false;
*nvargs = 0;
+ *vatype = InvalidOid;
*true_typeids = NULL;
if (argdefaults)
*argdefaults = NIL;
/* Get list of possible candidates from namespace search */
raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames,
- expand_variadic, expand_defaults);
+ expand_variadic, expand_defaults,
+ false);
/*
* Quickly check if there is an exact match to the input datatypes (there
@@ -1080,7 +1328,7 @@ func_get_detail(List *funcname,
*
* NB: it's important that this code does not exceed what coerce_type
* can do, because the caller will try to apply coerce_type if we
- * return FUNCDETAIL_COERCION. If we return that result for something
+ * return FUNCDETAIL_COERCION. If we return that result for something
* coerce_type can't handle, we'll cause infinite recursion between
* this module and coerce_type!
*/
@@ -1133,6 +1381,7 @@ func_get_detail(List *funcname,
*rettype = targetType;
*retset = false;
*nvargs = 0;
+ *vatype = InvalidOid;
*true_typeids = argtypes;
return FUNCDETAIL_COERCION;
}
@@ -1230,6 +1479,7 @@ func_get_detail(List *funcname,
pform = (Form_pg_proc) GETSTRUCT(ftup);
*rettype = pform->prorettype;
*retset = pform->proretset;
+ *vatype = pform->provariadic;
/* fetch default args if caller wants 'em */
if (argdefaults && best_candidate->ndargs > 0)
{
@@ -1256,7 +1506,7 @@ func_get_detail(List *funcname,
{
/*
* This is a bit tricky in named notation, since the supplied
- * arguments could replace any subset of the defaults. We
+ * arguments could replace any subset of the defaults. We
* work by making a bitmapset of the argnumbers of defaulted
* arguments, then scanning the defaults list and selecting
* the needed items. (This assumes that defaulted arguments
@@ -1314,6 +1564,101 @@ func_get_detail(List *funcname,
/*
+ * unify_hypothetical_args()
+ *
+ * Ensure that each hypothetical direct argument of a hypothetical-set
+ * aggregate has the same type as the corresponding aggregated argument.
+ * Modify the expressions in the fargs list, if necessary, and update
+ * actual_arg_types[].
+ *
+ * If the agg declared its args non-ANY (even ANYELEMENT), we need only a
+ * sanity check that the declared types match; make_fn_arguments will coerce
+ * the actual arguments to match the declared ones. But if the declaration
+ * is ANY, nothing will happen in make_fn_arguments, so we need to fix any
+ * mismatch here. We use the same type resolution logic as UNION etc.
+ */
+static void
+unify_hypothetical_args(ParseState *pstate,
+ List *fargs,
+ int numAggregatedArgs,
+ Oid *actual_arg_types,
+ Oid *declared_arg_types)
+{
+ Node *args[FUNC_MAX_ARGS];
+ int numDirectArgs,
+ numNonHypotheticalArgs;
+ int i;
+ ListCell *lc;
+
+ numDirectArgs = list_length(fargs) - numAggregatedArgs;
+ numNonHypotheticalArgs = numDirectArgs - numAggregatedArgs;
+ /* safety check (should only trigger with a misdeclared agg) */
+ if (numNonHypotheticalArgs < 0)
+ elog(ERROR, "incorrect number of arguments to hypothetical-set aggregate");
+
+ /* Deconstruct fargs into an array for ease of subscripting */
+ i = 0;
+ foreach(lc, fargs)
+ {
+ args[i++] = (Node *) lfirst(lc);
+ }
+
+ /* Check each hypothetical arg and corresponding aggregated arg */
+ for (i = numNonHypotheticalArgs; i < numDirectArgs; i++)
+ {
+ int aargpos = numDirectArgs + (i - numNonHypotheticalArgs);
+ Oid commontype;
+
+ /* A mismatch means AggregateCreate didn't check properly ... */
+ if (declared_arg_types[i] != declared_arg_types[aargpos])
+ elog(ERROR, "hypothetical-set aggregate has inconsistent declared argument types");
+
+ /* No need to unify if make_fn_arguments will coerce */
+ if (declared_arg_types[i] != ANYOID)
+ continue;
+
+ /*
+ * Select common type, giving preference to the aggregated argument's
+ * type (we'd rather coerce the direct argument once than coerce all
+ * the aggregated values).
+ */
+ commontype = select_common_type(pstate,
+ list_make2(args[aargpos], args[i]),
+ "WITHIN GROUP",
+ NULL);
+
+ /*
+ * Perform the coercions. We don't need to worry about NamedArgExprs
+ * here because they aren't supported with aggregates.
+ */
+ args[i] = coerce_type(pstate,
+ args[i],
+ actual_arg_types[i],
+ commontype, -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ actual_arg_types[i] = commontype;
+ args[aargpos] = coerce_type(pstate,
+ args[aargpos],
+ actual_arg_types[aargpos],
+ commontype, -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ actual_arg_types[aargpos] = commontype;
+ }
+
+ /* Reconstruct fargs from array */
+ i = 0;
+ foreach(lc, fargs)
+ {
+ lfirst(lc) = args[i++];
+ }
+}
+
+
+/*
* make_fn_arguments()
*
* Given the actual argument expressions for a function, and the desired
@@ -1388,7 +1733,7 @@ FuncNameAsType(List *funcname)
Oid result;
Type typtup;
- typtup = LookupTypeName(NULL, makeTypeNameFromNameList(funcname), NULL);
+ typtup = LookupTypeName(NULL, makeTypeNameFromNameList(funcname), NULL, false);
if (typtup == NULL)
return InvalidOid;
@@ -1406,7 +1751,7 @@ FuncNameAsType(List *funcname)
* ParseComplexProjection -
* handles function calls with a single argument that is of complex type.
* If the function call is actually a column projection, return a suitably
- * transformed expression tree. If not, return NULL.
+ * transformed expression tree. If not, return NULL.
*/
static Node *
ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg,
@@ -1480,7 +1825,7 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg,
* The result is something like "foo(integer)".
*
* If argnames isn't NIL, it is a list of C strings representing the actual
- * arg names for the last N arguments. This must be considered part of the
+ * arg names for the last N arguments. This must be considered part of the
* function signature too, when dealing with named-notation function calls.
*
* This is typically used in the construction of function-not-found error
@@ -1547,7 +1892,7 @@ LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool noError)
{
FuncCandidateList clist;
- clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false);
+ clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false, noError);
while (clist)
{
@@ -1567,27 +1912,6 @@ LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool noError)
}
/*
- * LookupTypeNameOid
- * Convenience routine to look up a type, silently accepting shell types
- */
-static Oid
-LookupTypeNameOid(const TypeName *typename)
-{
- Oid result;
- Type typtup;
-
- typtup = LookupTypeName(NULL, typename, NULL);
- if (typtup == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("type \"%s\" does not exist",
- TypeNameToString(typename))));
- result = typeTypeId(typtup);
- ReleaseSysCache(typtup);
- return result;
-}
-
-/*
* LookupFuncNameTypeNames
* Like LookupFuncName, but the argument types are specified by a
* list of TypeName nodes.
@@ -1614,7 +1938,7 @@ LookupFuncNameTypeNames(List *funcname, List *argtypes, bool noError)
{
TypeName *t = (TypeName *) lfirst(args_item);
- argoids[i] = LookupTypeNameOid(t);
+ argoids[i] = LookupTypeNameOid(NULL, t, noError);
args_item = lnext(args_item);
}
@@ -1654,7 +1978,7 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError)
{
TypeName *t = (TypeName *) lfirst(lc);
- argoids[i] = LookupTypeNameOid(t);
+ argoids[i] = LookupTypeNameOid(NULL, t, noError);
i++;
}
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 80dbdd19e4..1e3d1f68fa 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -3,7 +3,7 @@
* parse_node.c
* various routines that make nodes for querytrees
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "access/heapam.h"
+#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "nodes/makefuncs.h"
@@ -98,8 +99,8 @@ free_parsestate(ParseState *pstate)
* is a dummy (always 0, in fact).
*
* The locations stored in raw parsetrees are byte offsets into the source
- * string. We have to convert them to 1-based character indexes for reporting
- * to clients. (We do things this way to avoid unnecessary overhead in the
+ * string. We have to convert them to 1-based character indexes for reporting
+ * to clients. (We do things this way to avoid unnecessary overhead in the
* normal non-error case: computing character indexes would be much more
* expensive than storing token offsets.)
*/
@@ -128,7 +129,7 @@ parser_errposition(ParseState *pstate, int location)
* Sometimes the parser calls functions that aren't part of the parser
* subsystem and can't reasonably be passed a ParseState; yet we would
* like any errors thrown in those functions to be tagged with a parse
- * error location. Use this function to set up an error context stack
+ * error location. Use this function to set up an error context stack
* entry that will accomplish that. Usage pattern:
*
* declare a local variable "ParseCallbackState pcbstate"
@@ -144,10 +145,10 @@ setup_parser_errposition_callback(ParseCallbackState *pcbstate,
/* Setup error traceback support for ereport() */
pcbstate->pstate = pstate;
pcbstate->location = location;
- pcbstate->errcontext.callback = pcb_error_callback;
- pcbstate->errcontext.arg = (void *) pcbstate;
- pcbstate->errcontext.previous = error_context_stack;
- error_context_stack = &pcbstate->errcontext;
+ pcbstate->errcallback.callback = pcb_error_callback;
+ pcbstate->errcallback.arg = (void *) pcbstate;
+ pcbstate->errcallback.previous = error_context_stack;
+ error_context_stack = &pcbstate->errcallback;
}
/*
@@ -157,7 +158,7 @@ void
cancel_parser_errposition_callback(ParseCallbackState *pcbstate)
{
/* Pop the error context stack */
- error_context_stack = pcbstate->errcontext.previous;
+ error_context_stack = pcbstate->errcallback.previous;
}
/*
@@ -220,11 +221,23 @@ transformArrayType(Oid *arrayType, int32 *arrayTypmod)
* If the input is a domain, smash to base type, and extract the actual
* typmod to be applied to the base type. Subscripting a domain is an
* operation that necessarily works on the base array type, not the domain
- * itself. (Note that we provide no method whereby the creator of a
+ * itself. (Note that we provide no method whereby the creator of a
* domain over an array type could hide its ability to be subscripted.)
*/
*arrayType = getBaseTypeAndTypmod(*arrayType, arrayTypmod);
+ /*
+ * We treat int2vector and oidvector as though they were domains over
+ * int2[] and oid[]. This is needed because array slicing could create an
+ * array that doesn't satisfy the dimensionality constraints of the
+ * xxxvector type; so we want the result of a slice operation to be
+ * considered to be of the more general type.
+ */
+ if (*arrayType == INT2VECTOROID)
+ *arrayType = INT2ARRAYOID;
+ else if (*arrayType == OIDVECTOROID)
+ *arrayType = OIDARRAYOID;
+
/* Get the type tuple for the array */
type_tuple_array = SearchSysCache1(TYPEOID, ObjectIdGetDatum(*arrayType));
if (!HeapTupleIsValid(type_tuple_array))
@@ -256,12 +269,13 @@ transformArrayType(Oid *arrayType, int32 *arrayTypmod)
*
* In an array assignment, we are given a destination array value plus a
* source value that is to be assigned to a single element or a slice of
- * that array. We produce an expression that represents the new array value
+ * that array. We produce an expression that represents the new array value
* with the source data inserted into the right part of the array.
*
* For both cases, if the source array is of a domain-over-array type,
* the result is of the base array type or its element type; essentially,
* we must fold a domain to its base type before applying subscripting.
+ * (Note that int2vector and oidvector are treated as domains here.)
*
* pstate Parse state
* arrayBase Already-transformed expression for the array as a whole
@@ -328,7 +342,7 @@ transformArraySubscripts(ParseState *pstate,
{
if (ai->lidx)
{
- subexpr = transformExpr(pstate, ai->lidx);
+ subexpr = transformExpr(pstate, ai->lidx, pstate->p_expr_kind);
/* If it's not int4 already, try to coerce */
subexpr = coerce_to_target_type(pstate,
subexpr, exprType(subexpr),
@@ -355,7 +369,7 @@ transformArraySubscripts(ParseState *pstate,
}
lowerIndexpr = lappend(lowerIndexpr, subexpr);
}
- subexpr = transformExpr(pstate, ai->uidx);
+ subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
/* If it's not int4 already, try to coerce */
subexpr = coerce_to_target_type(pstate,
subexpr, exprType(subexpr),
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index bb5f04031d..b65b632f17 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -3,7 +3,7 @@
* parse_oper.c
* handle operator things for parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -15,6 +15,7 @@
#include "postgres.h"
+#include "access/htup_details.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
@@ -147,12 +148,12 @@ LookupOperNameTypeNames(ParseState *pstate, List *opername,
if (oprleft == NULL)
leftoid = InvalidOid;
else
- leftoid = typenameTypeId(pstate, oprleft);
+ leftoid = LookupTypeNameOid(pstate, oprleft, noError);
if (oprright == NULL)
rightoid = InvalidOid;
else
- rightoid = typenameTypeId(pstate, oprright);
+ rightoid = LookupTypeNameOid(pstate, oprright, noError);
return LookupOperName(pstate, opername, leftoid, rightoid,
noError, location);
@@ -406,7 +407,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
FuncCandidateList clist;
/* Get binary operators of given name */
- clist = OpernameGetCandidates(opname, 'b');
+ clist = OpernameGetCandidates(opname, 'b', false);
/* No operators found? Then fail... */
if (clist != NULL)
@@ -446,7 +447,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
*
* This is tighter than oper() because it will not return an operator that
* requires coercion of the input datatypes (but binary-compatible operators
- * are accepted). Otherwise, the semantics are the same.
+ * are accepted). Otherwise, the semantics are the same.
*/
Operator
compatible_oper(ParseState *pstate, List *op, Oid arg1, Oid arg2,
@@ -552,7 +553,7 @@ right_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
FuncCandidateList clist;
/* Get postfix operators of given name */
- clist = OpernameGetCandidates(op, 'r');
+ clist = OpernameGetCandidates(op, 'r', false);
/* No operators found? Then fail... */
if (clist != NULL)
@@ -630,7 +631,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
FuncCandidateList clist;
/* Get prefix operators of given name */
- clist = OpernameGetCandidates(op, 'l');
+ clist = OpernameGetCandidates(op, 'l', false);
/* No operators found? Then fail... */
if (clist != NULL)
@@ -979,7 +980,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
* mapping is pretty expensive to compute, especially for ambiguous operators;
* this is mainly because there are a *lot* of instances of popular operator
* names such as "=", and we have to check each one to see which is the
- * best match. So once we have identified the correct mapping, we save it
+ * best match. So once we have identified the correct mapping, we save it
* in a cache that need only be flushed on pg_operator or pg_cast change.
* (pg_cast must be considered because changes in the set of implicit casts
* affect the set of applicable operators for any given input datatype.)
@@ -1026,7 +1027,7 @@ make_oper_cache_key(OprCacheKey *key, List *opname, Oid ltypeId, Oid rtypeId)
if (schemaname)
{
/* search only in exact schema given */
- key->search_path[0] = LookupExplicitNamespace(schemaname);
+ key->search_path[0] = LookupExplicitNamespace(schemaname, false);
}
else
{
diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c
index cfe72629ea..41b755a1fa 100644
--- a/src/backend/parser/parse_param.c
+++ b/src/backend/parser/parse_param.c
@@ -12,7 +12,7 @@
* Note that other approaches to parameters are possible using the parser
* hooks defined in ParseState.
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -57,6 +57,7 @@ static Node *variable_coerce_param_hook(ParseState *pstate, Param *param,
Oid targetTypeId, int32 targetTypeMod,
int location);
static bool check_parameter_resolution_walker(Node *node, ParseState *pstate);
+static bool query_contains_extern_params_walker(Node *node, void *context);
/*
@@ -255,7 +256,7 @@ variable_coerce_param_hook(ParseState *pstate, Param *param,
* of parsing with parse_variable_parameters.
*
* Note: this code intentionally does not check that all parameter positions
- * were used, nor that all got non-UNKNOWN types assigned. Caller of parser
+ * were used, nor that all got non-UNKNOWN types assigned. Caller of parser
* should enforce that if it's important.
*/
void
@@ -316,3 +317,38 @@ check_parameter_resolution_walker(Node *node, ParseState *pstate)
return expression_tree_walker(node, check_parameter_resolution_walker,
(void *) pstate);
}
+
+/*
+ * Check to see if a fully-parsed query tree contains any PARAM_EXTERN Params.
+ */
+bool
+query_contains_extern_params(Query *query)
+{
+ return query_tree_walker(query,
+ query_contains_extern_params_walker,
+ NULL, 0);
+}
+
+static bool
+query_contains_extern_params_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Param))
+ {
+ Param *param = (Param *) node;
+
+ if (param->paramkind == PARAM_EXTERN)
+ return true;
+ return false;
+ }
+ if (IsA(node, Query))
+ {
+ /* Recurse into RTE subquery or not-yet-planned sublink subquery */
+ return query_tree_walker((Query *) node,
+ query_contains_extern_params_walker,
+ context, 0);
+ }
+ return expression_tree_walker(node, query_contains_extern_params_walker,
+ context);
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4b4cc2cae6..d8fbd640a2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Portions Copyright (c) 2012-2014, TransLattice, Inc.
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -21,6 +21,7 @@
#include <ctype.h>
+#include "access/htup_details.h"
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
@@ -48,6 +49,8 @@ static RangeTblEntry *scanNameSpaceForRefname(ParseState *pstate,
const char *refname, int location);
static RangeTblEntry *scanNameSpaceForRelid(ParseState *pstate, Oid relid,
int location);
+static void check_lateral_ref_ok(ParseState *pstate, ParseNamespaceItem *nsitem,
+ int location);
static void markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
int rtindex, AttrNumber col);
static void expandRelation(Oid relid, Alias *eref,
@@ -55,12 +58,15 @@ static void expandRelation(Oid relid, Alias *eref,
int location, bool include_dropped,
List **colnames, List **colvars);
static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
+ int count, int offset,
int rtindex, int sublevels_up,
int location, bool include_dropped,
List **colnames, List **colvars);
#ifndef PGXC
static int specialAttNum(const char *attname);
#endif
+static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+
/*
* refnameRangeTblEntry
@@ -79,9 +85,9 @@ static int specialAttNum(const char *attname);
*
* A qualified refname (schemaname != NULL) can only match a relation RTE
* that (a) has no alias and (b) is for the same relation identified by
- * schemaname.refname. In this case we convert schemaname.refname to a
+ * schemaname.refname. In this case we convert schemaname.refname to a
* relation OID and search by relid, rather than by alias name. This is
- * peculiar, but it's what SQL92 says to do.
+ * peculiar, but it's what SQL says to do.
*/
RangeTblEntry *
refnameRangeTblEntry(ParseState *pstate,
@@ -141,6 +147,18 @@ refnameRangeTblEntry(ParseState *pstate,
* Search the query's table namespace for an RTE matching the
* given unqualified refname. Return the RTE if a unique match, or NULL
* if no match. Raise error if multiple matches.
+ *
+ * Note: it might seem that we shouldn't have to worry about the possibility
+ * of multiple matches; after all, the SQL standard disallows duplicate table
+ * aliases within a given SELECT level. Historically, however, Postgres has
+ * been laxer than that. For example, we allow
+ * SELECT ... FROM tab1 x CROSS JOIN (tab2 x CROSS JOIN tab3 y) z
+ * on the grounds that the aliased join (z) hides the aliases within it,
+ * therefore there is no conflict between the two RTEs named "x". However,
+ * if tab3 is a LATERAL subquery, then from within the subquery both "x"es
+ * are visible. Rather than rejecting queries that used to work, we allow
+ * this situation, and complain only if there's actually an ambiguous
+ * reference to "x".
*/
static RangeTblEntry *
scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
@@ -148,9 +166,17 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
RangeTblEntry *result = NULL;
ListCell *l;
- foreach(l, pstate->p_relnamespace)
+ foreach(l, pstate->p_namespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
+
+ /* Ignore columns-only items */
+ if (!nsitem->p_rel_visible)
+ continue;
+ /* 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)
{
@@ -160,6 +186,7 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
errmsg("table reference \"%s\" is ambiguous",
refname),
parser_errposition(pstate, location)));
+ check_lateral_ref_ok(pstate, nsitem, location);
result = rte;
}
}
@@ -168,9 +195,8 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
/*
* Search the query's table namespace for a relation RTE matching the
- * given relation OID. Return the RTE if a unique match, or NULL
- * if no match. Raise error if multiple matches (which shouldn't
- * happen if the namespace was checked correctly when it was created).
+ * given relation OID. Return the RTE if a unique match, or NULL
+ * if no match. Raise error if multiple matches.
*
* See the comments for refnameRangeTblEntry to understand why this
* acts the way it does.
@@ -181,9 +207,17 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location)
RangeTblEntry *result = NULL;
ListCell *l;
- foreach(l, pstate->p_relnamespace)
+ foreach(l, pstate->p_namespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
+
+ /* Ignore columns-only items */
+ if (!nsitem->p_rel_visible)
+ continue;
+ /* 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 &&
@@ -196,6 +230,7 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location)
errmsg("table reference %u is ambiguous",
relid),
parser_errposition(pstate, location)));
+ check_lateral_ref_ok(pstate, nsitem, location);
result = rte;
}
}
@@ -258,13 +293,13 @@ 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.
*
* This is different from refnameRangeTblEntry in that it considers every
* entry in the ParseState's rangetable(s), not only those that are currently
- * visible in the p_relnamespace lists. This behavior is invalid per the SQL
+ * visible in the p_namespace list(s). 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 errorMissingRTE.
@@ -273,7 +308,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;
@@ -287,8 +322,8 @@ searchRangeTable(ParseState *pstate, RangeVar *relation)
* relation.
*
* NB: It's not critical that RangeVarGetRelid return the correct answer
- * here in the face of concurrent DDL. If it doesn't, the worst case
- * scenario is a less-clear error message. Also, the tables involved in
+ * here in the face of concurrent DDL. If it doesn't, the worst case
+ * scenario is a less-clear error message. Also, the tables involved in
* the query are already locked, which reduces the number of cases in
* which surprising behavior can occur. So we do the name lookup
* unlocked.
@@ -326,15 +361,19 @@ searchRangeTable(ParseState *pstate, RangeVar *relation)
}
/*
- * Check for relation-name conflicts between two relnamespace lists.
+ * Check for relation-name conflicts between two namespace lists.
* Raise an error if any is found.
*
* Note: we assume that each given argument does not contain conflicts
* itself; we just want to know if the two can be merged together.
*
- * Per SQL92, two alias-less plain relation RTEs do not conflict even if
+ * Per SQL, 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. However,
+ * columns-only items should be ignored.
*/
void
checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
@@ -344,20 +383,27 @@ 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;
+ if (!nsitem1->p_rel_visible)
+ continue;
+
foreach(l2, namespace2)
{
- RangeTblEntry *rte2 = (RangeTblEntry *) lfirst(l2);
+ ParseNamespaceItem *nsitem2 = (ParseNamespaceItem *) lfirst(l2);
+ RangeTblEntry *rte2 = nsitem2->p_rte;
+ if (!nsitem2->p_rel_visible)
+ continue;
if (strcmp(rte2->eref->aliasname, aliasname1) != 0)
continue; /* definitely no conflict */
if (rte1->rtekind == RTE_RELATION && rte1->alias == NULL &&
rte2->rtekind == RTE_RELATION && rte2->alias == NULL &&
rte1->relid != rte2->relid)
- continue; /* no conflict per SQL92 rule */
+ continue; /* no conflict per SQL rule */
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("table name \"%s\" specified more than once",
@@ -367,8 +413,39 @@ checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
}
/*
+ * Complain if a namespace item is currently disallowed as a LATERAL reference.
+ * This enforces both SQL:2008's rather odd idea of what to do with a LATERAL
+ * reference to the wrong side of an outer join, and our own prohibition on
+ * referencing the target table of an UPDATE or DELETE as a lateral reference
+ * in a FROM/USING clause.
+ *
+ * Convenience subroutine to avoid multiple copies of a rather ugly ereport.
+ */
+static void
+check_lateral_ref_ok(ParseState *pstate, ParseNamespaceItem *nsitem,
+ int location)
+{
+ if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+ {
+ /* SQL:2008 demands this be an error, not an invisible item */
+ RangeTblEntry *rte = nsitem->p_rte;
+ char *refname = rte->eref->aliasname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+ refname),
+ (rte == pstate->p_target_rangetblentry) ?
+ errhint("There is an entry for table \"%s\", but it cannot be referenced from this part of the query.",
+ refname) :
+ errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+ parser_errposition(pstate, location)));
+ }
+}
+
+/*
* given an RTE, return RT index (starting with 1) of the entry,
- * and optionally get its nesting depth (0 = current). If sublevels_up
+ * and optionally get its nesting depth (0 = current). If sublevels_up
* is NULL, only consider rels at the current nesting level.
* Raises error if RTE not found.
*/
@@ -519,6 +596,16 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
{
/* quick check to see if name could be a system column */
attnum = specialAttNum(colname);
+
+ /* In constraint check, no system column is allowed except tableOid */
+ if (pstate->p_expr_kind == EXPR_KIND_CHECK_CONSTRAINT &&
+ attnum < InvalidAttrNumber && attnum != TableOidAttributeNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("system column \"%s\" reference in check constraint is invalid",
+ colname),
+ parser_errposition(pstate, location)));
+
if (attnum != InvalidAttrNumber)
{
/* now check to see if column actually is defined */
@@ -555,11 +642,19 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
{
ListCell *l;
- foreach(l, pstate->p_varnamespace)
+ foreach(l, pstate->p_namespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
Node *newresult;
+ /* Ignore table-only items */
+ if (!nsitem->p_cols_visible)
+ continue;
+ /* 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);
@@ -570,7 +665,8 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
errmsg("column reference \"%s\" is ambiguous",
colname),
- parser_errposition(orig_pstate, location)));
+ parser_errposition(pstate, location)));
+ check_lateral_ref_ok(pstate, nsitem, location);
result = newresult;
}
}
@@ -585,6 +681,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_namespace list(s). 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
*
@@ -686,14 +816,15 @@ markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
* The aliasvar could be either a Var or a COALESCE expression,
* but in the latter case we should already have marked the two
* referent variables as being selected, due to their use in the
- * JOIN clause. So we need only be concerned with the simple Var
- * case.
+ * JOIN clause. So we need only be concerned with the Var case.
+ * But we do need to drill down through implicit coercions.
*/
Var *aliasvar;
Assert(col > 0 && col <= list_length(rte->joinaliasvars));
aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1);
- if (IsA(aliasvar, Var))
+ aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
+ if (aliasvar && IsA(aliasvar, Var))
markVarForSelectPriv(pstate, aliasvar, NULL);
}
}
@@ -722,8 +853,7 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
/*
* buildRelationAliases
* Construct the eref column name list for a relation RTE.
- * This code is also used for the case of a function RTE returning
- * a named composite type.
+ * This code is also used for function RTEs.
*
* tupdesc: the physical column information
* alias: the user-supplied alias, or NULL if none
@@ -732,6 +862,8 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
* eref->colnames is filled in. Also, alias->colnames is rebuilt to insert
* empty strings for any dropped columns, so that it will be one-to-one with
* physical column numbers.
+ *
+ * It is an error for there to be more aliases present than required.
*/
static void
buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
@@ -795,37 +927,24 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
}
/*
- * buildScalarFunctionAlias
- * Construct the eref column name list for a function RTE,
+ * chooseScalarFunctionAlias
+ * Select the column alias for a function in a function RTE,
* when the function returns a scalar type (not composite or RECORD).
*
* funcexpr: transformed expression tree for the function call
- * funcname: function name (used only for error message)
- * alias: the user-supplied alias, or NULL if none
- * eref: the eref Alias to store column names in
+ * funcname: function name (as determined by FigureColname)
+ * alias: the user-supplied alias for the RTE, or NULL if none
+ * nfuncs: the number of functions appearing in the function RTE
*
- * eref->colnames is filled in.
+ * Note that the name we choose might be overridden later, if the user-given
+ * alias includes column alias names. That's of no concern here.
*/
-static void
-buildScalarFunctionAlias(Node *funcexpr, char *funcname,
- Alias *alias, Alias *eref)
+static char *
+chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
+ Alias *alias, int nfuncs)
{
char *pname;
- Assert(eref->colnames == NIL);
-
- /* Use user-specified column alias if there is one. */
- if (alias && alias->colnames != NIL)
- {
- if (list_length(alias->colnames) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("too many column aliases specified for function %s",
- funcname)));
- eref->colnames = copyObject(alias->colnames);
- return;
- }
-
/*
* If the expression is a simple function call, and the function has a
* single OUT parameter that is named, use the parameter's name.
@@ -834,17 +953,21 @@ buildScalarFunctionAlias(Node *funcexpr, char *funcname,
{
pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
if (pname)
- {
- eref->colnames = list_make1(makeString(pname));
- return;
- }
+ return pname;
}
/*
- * Otherwise use the previously-determined alias (not necessarily the
- * function name!)
+ * If there's just one function in the RTE, and the user gave an RTE alias
+ * name, use that name. (This makes FROM func() AS foo use "foo" as the
+ * column name as well as the table alias.)
+ */
+ if (nfuncs == 1 && alias)
+ return alias->aliasname;
+
+ /*
+ * Otherwise use the function name.
*/
- eref->colnames = list_make1(makeString(eref->aliasname));
+ return funcname;
}
/*
@@ -926,7 +1049,7 @@ addRangeTableEntry(ParseState *pstate,
/*
* Get the rel's OID. This access also ensures that we have an up-to-date
- * relcache entry for the rel. Since this is typically the first access
+ * relcache entry for the rel. Since this is typically the first access
* to a rel in a statement, be careful to get the right access level
* depending on whether we're doing SELECT FOR UPDATE/SHARE.
*/
@@ -998,16 +1121,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;
@@ -1079,16 +1199,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;
@@ -1117,6 +1234,7 @@ RangeTblEntry *
addRangeTableEntryForSubquery(ParseState *pstate,
Query *subquery,
Alias *alias,
+ bool lateral,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
@@ -1160,15 +1278,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;
@@ -1188,126 +1303,241 @@ addRangeTableEntryForSubquery(ParseState *pstate,
}
/*
- * Add an entry for a function to the pstate's range table (p_rtable).
+ * Add an entry for a function (or functions) to the pstate's range table
+ * (p_rtable).
*
* This is just like addRangeTableEntry() except that it makes a function RTE.
*/
RangeTblEntry *
addRangeTableEntryForFunction(ParseState *pstate,
- char *funcname,
- Node *funcexpr,
+ List *funcnames,
+ List *funcexprs,
+ List *coldeflists,
RangeFunction *rangefunc,
+ bool lateral,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
Alias *alias = rangefunc->alias;
- List *coldeflist = rangefunc->coldeflist;
Alias *eref;
+ char *aliasname;
+ int nfuncs = list_length(funcexprs);
+ TupleDesc *functupdescs;
+ TupleDesc tupdesc;
+ ListCell *lc1,
+ *lc2,
+ *lc3;
+ int i;
+ int j;
+ int funcno;
+ int natts,
+ totalatts;
rte->rtekind = RTE_FUNCTION;
rte->relid = InvalidOid;
rte->subquery = NULL;
- rte->funcexpr = funcexpr;
- rte->funccoltypes = NIL;
- rte->funccoltypmods = NIL;
- rte->funccolcollations = NIL;
+ rte->functions = NIL; /* we'll fill this list below */
+ rte->funcordinality = rangefunc->ordinality;
rte->alias = alias;
- eref = makeAlias(alias ? alias->aliasname : funcname, NIL);
- rte->eref = eref;
-
- /*
- * Now determine if the function returns a simple or composite type.
- */
- functypclass = get_expr_result_type(funcexpr,
- &funcrettype,
- &tupdesc);
-
/*
- * A coldeflist is required if the function returns RECORD and hasn't got
- * a predetermined record type, and is prohibited otherwise.
+ * Choose the RTE alias name. We default to using the first function's
+ * name even when there's more than one; which is maybe arguable but beats
+ * using something constant like "table".
*/
- if (coldeflist != NIL)
- {
- if (functypclass != TYPEFUNC_RECORD)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("a column definition list is only allowed for functions returning \"record\""),
- parser_errposition(pstate, exprLocation(funcexpr))));
- }
+ if (alias)
+ aliasname = alias->aliasname;
else
+ aliasname = linitial(funcnames);
+
+ eref = makeAlias(aliasname, NIL);
+ rte->eref = eref;
+
+ /* Process each function ... */
+ functupdescs = (TupleDesc *) palloc(nfuncs * sizeof(TupleDesc));
+
+ totalatts = 0;
+ funcno = 0;
+ forthree(lc1, funcexprs, lc2, funcnames, lc3, coldeflists)
{
- if (functypclass == TYPEFUNC_RECORD)
+ Node *funcexpr = (Node *) lfirst(lc1);
+ char *funcname = (char *) lfirst(lc2);
+ List *coldeflist = (List *) lfirst(lc3);
+ RangeTblFunction *rtfunc = makeNode(RangeTblFunction);
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+
+ /* Initialize RangeTblFunction node */
+ rtfunc->funcexpr = funcexpr;
+ rtfunc->funccolnames = NIL;
+ rtfunc->funccoltypes = NIL;
+ rtfunc->funccoltypmods = NIL;
+ rtfunc->funccolcollations = NIL;
+ rtfunc->funcparams = NULL; /* not set until planning */
+
+ /*
+ * Now determine if the function returns a simple or composite type.
+ */
+ functypclass = get_expr_result_type(funcexpr,
+ &funcrettype,
+ &tupdesc);
+
+ /*
+ * A coldeflist is required if the function returns RECORD and hasn't
+ * got a predetermined record type, and is prohibited otherwise.
+ */
+ if (coldeflist != NIL)
+ {
+ if (functypclass != TYPEFUNC_RECORD)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("a column definition list is only allowed for functions returning \"record\""),
+ parser_errposition(pstate,
+ exprLocation((Node *) coldeflist))));
+ }
+ else
+ {
+ if (functypclass == TYPEFUNC_RECORD)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("a column definition list is required for functions returning \"record\""),
+ parser_errposition(pstate, exprLocation(funcexpr))));
+ }
+
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ chooseScalarFunctionAlias(funcexpr, funcname,
+ alias, nfuncs),
+ funcrettype,
+ -1,
+ 0);
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ ListCell *col;
+
+ /*
+ * Use the column definition list to construct a tupdesc and fill
+ * in the RangeTblFunction's lists.
+ */
+ tupdesc = CreateTemplateTupleDesc(list_length(coldeflist), false);
+ i = 1;
+ foreach(col, coldeflist)
+ {
+ ColumnDef *n = (ColumnDef *) lfirst(col);
+ char *attrname;
+ Oid attrtype;
+ int32 attrtypmod;
+ Oid attrcollation;
+
+ attrname = n->colname;
+ if (n->typeName->setof)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" cannot be declared SETOF",
+ attrname),
+ parser_errposition(pstate, n->location)));
+ typenameTypeIdAndMod(pstate, n->typeName,
+ &attrtype, &attrtypmod);
+ attrcollation = GetColumnDefCollation(pstate, n, attrtype);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) i,
+ attrname,
+ attrtype,
+ attrtypmod,
+ 0);
+ TupleDescInitEntryCollation(tupdesc,
+ (AttrNumber) i,
+ attrcollation);
+ rtfunc->funccolnames = lappend(rtfunc->funccolnames,
+ makeString(pstrdup(attrname)));
+ rtfunc->funccoltypes = lappend_oid(rtfunc->funccoltypes,
+ attrtype);
+ rtfunc->funccoltypmods = lappend_int(rtfunc->funccoltypmods,
+ attrtypmod);
+ rtfunc->funccolcollations = lappend_oid(rtfunc->funccolcollations,
+ attrcollation);
+
+ i++;
+ }
+
+ /*
+ * Ensure that the coldeflist defines a legal set of names (no
+ * duplicates) and datatypes (no pseudo-types, for instance).
+ */
+ CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false);
+ }
+ else
ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("a column definition list is required for functions returning \"record\""),
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function \"%s\" in FROM has unsupported return type %s",
+ funcname, format_type_be(funcrettype)),
parser_errposition(pstate, exprLocation(funcexpr))));
- }
- if (functypclass == TYPEFUNC_COMPOSITE)
- {
- /* Composite data type, e.g. a table's row type */
- Assert(tupdesc);
- /* Build the column alias list */
- buildRelationAliases(tupdesc, alias, eref);
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- /* Base data type, i.e. scalar */
- buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
+ /* Finish off the RangeTblFunction and add it to the RTE's list */
+ rtfunc->funccolcount = tupdesc->natts;
+ rte->functions = lappend(rte->functions, rtfunc);
+
+ /* Save the tupdesc for use below */
+ functupdescs[funcno] = tupdesc;
+ totalatts += tupdesc->natts;
+ funcno++;
}
- else if (functypclass == TYPEFUNC_RECORD)
+
+ /*
+ * If there's more than one function, or we want an ordinality column, we
+ * have to produce a merged tupdesc.
+ */
+ if (nfuncs > 1 || rangefunc->ordinality)
{
- ListCell *col;
+ if (rangefunc->ordinality)
+ totalatts++;
- /*
- * Use the column definition list to form the alias list and
- * funccoltypes/funccoltypmods/funccolcollations lists.
- */
- foreach(col, coldeflist)
+ /* Merge the tuple descs of each function into a composite one */
+ tupdesc = CreateTemplateTupleDesc(totalatts, false);
+ natts = 0;
+ for (i = 0; i < nfuncs; i++)
{
- ColumnDef *n = (ColumnDef *) lfirst(col);
- char *attrname;
- Oid attrtype;
- int32 attrtypmod;
- Oid attrcollation;
-
- attrname = pstrdup(n->colname);
- if (n->typeName->setof)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("column \"%s\" cannot be declared SETOF",
- attrname),
- parser_errposition(pstate, n->typeName->location)));
- typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod);
- attrcollation = GetColumnDefCollation(pstate, n, attrtype);
- eref->colnames = lappend(eref->colnames, makeString(attrname));
- rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype);
- rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod);
- rte->funccolcollations = lappend_oid(rte->funccolcollations,
- attrcollation);
+ for (j = 1; j <= functupdescs[i]->natts; j++)
+ TupleDescCopyEntry(tupdesc, ++natts, functupdescs[i], j);
}
+
+ /* Add the ordinality column if needed */
+ if (rangefunc->ordinality)
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) ++natts,
+ "ordinality",
+ INT8OID,
+ -1,
+ 0);
+
+ Assert(natts == totalatts);
}
else
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("function \"%s\" in FROM has unsupported return type %s",
- funcname, format_type_be(funcrettype)),
- parser_errposition(pstate, exprLocation(funcexpr))));
+ {
+ /* We can just use the single function's tupdesc as-is */
+ tupdesc = functupdescs[0];
+ }
- /*----------
- * 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.
+ /* Use the tupdesc while assigning column aliases for the RTE */
+ buildRelationAliases(tupdesc, alias, eref);
+
+ /*
+ * 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;
@@ -1336,6 +1566,7 @@ addRangeTableEntryForValues(ParseState *pstate,
List *exprs,
List *collations,
Alias *alias,
+ bool lateral,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
@@ -1373,15 +1604,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 = lateral;
rte->inh = false; /* never true for values RTEs */
rte->inFromCl = inFromCl;
@@ -1444,15 +1672,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;
@@ -1556,15 +1781,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;
@@ -1633,8 +1855,13 @@ 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.)
+ * and/or namespace list. (We assume caller has checked for any
+ * namespace conflicts.) The RTE is always marked as unconditionally
+ * visible, that is, not LATERAL-only.
+ *
+ * Note: some callers know that they can find the new ParseNamespaceItem
+ * at the end of the pstate->p_namespace list. This is a bit ugly but not
+ * worth complicating this function's signature for.
*/
void
addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
@@ -1649,10 +1876,18 @@ 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_rel_visible = addToRelNameSpace;
+ nsitem->p_cols_visible = addToVarNameSpace;
+ nsitem->p_lateral_only = false;
+ nsitem->p_lateral_ok = true;
+ pstate->p_namespace = lappend(pstate->p_namespace, nsitem);
+ }
}
/*
@@ -1736,77 +1971,117 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
case RTE_FUNCTION:
{
/* Function RTE */
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
-
- functypclass = get_expr_result_type(rte->funcexpr,
- &funcrettype,
- &tupdesc);
- if (functypclass == TYPEFUNC_COMPOSITE)
- {
- /* Composite data type, e.g. a table's row type */
- Assert(tupdesc);
- expandTupleDesc(tupdesc, rte->eref,
- rtindex, sublevels_up, location,
- include_dropped, colnames, colvars);
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- /* Base data type, i.e. scalar */
- if (colnames)
- *colnames = lappend(*colnames,
- linitial(rte->eref->colnames));
+ int atts_done = 0;
+ ListCell *lc;
- if (colvars)
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+
+ functypclass = get_expr_result_type(rtfunc->funcexpr,
+ &funcrettype,
+ &tupdesc);
+ if (functypclass == TYPEFUNC_COMPOSITE)
{
- Var *varnode;
-
- varnode = makeVar(rtindex, 1,
- funcrettype, -1,
- exprCollation(rte->funcexpr),
- sublevels_up);
- varnode->location = location;
-
- *colvars = lappend(*colvars, varnode);
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ expandTupleDesc(tupdesc, rte->eref,
+ rtfunc->funccolcount, atts_done,
+ rtindex, sublevels_up, location,
+ include_dropped, colnames, colvars);
}
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- if (colnames)
- *colnames = copyObject(rte->eref->colnames);
- if (colvars)
+ else if (functypclass == TYPEFUNC_SCALAR)
{
- ListCell *l1;
- ListCell *l2;
- ListCell *l3;
- int attnum = 0;
-
- forthree(l1, rte->funccoltypes,
- l2, rte->funccoltypmods,
- l3, rte->funccolcollations)
+ /* Base data type, i.e. scalar */
+ if (colnames)
+ *colnames = lappend(*colnames,
+ list_nth(rte->eref->colnames,
+ atts_done));
+
+ if (colvars)
{
- Oid attrtype = lfirst_oid(l1);
- int32 attrtypmod = lfirst_int(l2);
- Oid attrcollation = lfirst_oid(l3);
Var *varnode;
- attnum++;
- varnode = makeVar(rtindex,
- attnum,
- attrtype,
- attrtypmod,
- attrcollation,
+ varnode = makeVar(rtindex, atts_done + 1,
+ funcrettype, -1,
+ exprCollation(rtfunc->funcexpr),
sublevels_up);
varnode->location = location;
+
*colvars = lappend(*colvars, varnode);
}
}
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ if (colnames)
+ {
+ List *namelist;
+
+ /* extract appropriate subset of column list */
+ namelist = list_copy_tail(rte->eref->colnames,
+ atts_done);
+ namelist = list_truncate(namelist,
+ rtfunc->funccolcount);
+ *colnames = list_concat(*colnames, namelist);
+ }
+
+ if (colvars)
+ {
+ ListCell *l1;
+ ListCell *l2;
+ ListCell *l3;
+ int attnum = atts_done;
+
+ forthree(l1, rtfunc->funccoltypes,
+ l2, rtfunc->funccoltypmods,
+ l3, rtfunc->funccolcollations)
+ {
+ Oid attrtype = lfirst_oid(l1);
+ int32 attrtypmod = lfirst_int(l2);
+ Oid attrcollation = lfirst_oid(l3);
+ Var *varnode;
+
+ attnum++;
+ varnode = makeVar(rtindex,
+ attnum,
+ attrtype,
+ attrtypmod,
+ attrcollation,
+ sublevels_up);
+ varnode->location = location;
+ *colvars = lappend(*colvars, varnode);
+ }
+ }
+ }
+ else
+ {
+ /* addRangeTableEntryForFunction should've caught this */
+ elog(ERROR, "function in FROM has unsupported return type");
+ }
+ atts_done += rtfunc->funccolcount;
}
- else
+
+ /* Append the ordinality column if any */
+ if (rte->funcordinality)
{
- /* addRangeTableEntryForFunction should've caught this */
- elog(ERROR, "function in FROM has unsupported return type");
+ if (colnames)
+ *colnames = lappend(*colnames,
+ llast(rte->eref->colnames));
+
+ if (colvars)
+ {
+ Var *varnode = makeVar(rtindex,
+ atts_done + 1,
+ INT8OID,
+ -1,
+ InvalidOid,
+ sublevels_up);
+
+ *colvars = lappend(*colvars, varnode);
+ }
}
}
break;
@@ -1870,10 +2145,10 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
* deleted columns in the join; but we have to check since
* this routine is also used by the rewriter, and joins
* found in stored rules might have join columns for
- * since-deleted columns. This will be signaled by a NULL
- * Const in the alias-vars list.
+ * since-deleted columns. This will be signaled by a null
+ * pointer in the alias-vars list.
*/
- if (IsA(avar, Const))
+ if (avar == NULL)
{
if (include_dropped)
{
@@ -1881,8 +2156,16 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
*colnames = lappend(*colnames,
makeString(pstrdup("")));
if (colvars)
+ {
+ /*
+ * Can't use join's column type here (it might
+ * be dropped!); but it doesn't really matter
+ * what type the Const claims to be.
+ */
*colvars = lappend(*colvars,
- copyObject(avar));
+ makeNullConst(INT4OID, -1,
+ InvalidOid));
+ }
}
continue;
}
@@ -1967,7 +2250,8 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
/* Get the tupledesc and turn it over to expandTupleDesc */
rel = relation_open(relid, AccessShareLock);
- expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up,
+ expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
+ rtindex, sublevels_up,
location, include_dropped,
colnames, colvars);
relation_close(rel, AccessShareLock);
@@ -1975,18 +2259,35 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
/*
* expandTupleDesc -- expandRTE subroutine
+ *
+ * Generate names and/or Vars for the first "count" attributes of the tupdesc,
+ * and append them to colnames/colvars. "offset" is added to the varattno
+ * that each Var would otherwise have, and we also skip the first "offset"
+ * entries in eref->colnames. (These provisions allow use of this code for
+ * an individual composite-returning function in an RTE_FUNCTION RTE.)
*/
static void
-expandTupleDesc(TupleDesc tupdesc, Alias *eref,
+expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
int rtindex, int sublevels_up,
int location, bool include_dropped,
List **colnames, List **colvars)
{
- int maxattrs = tupdesc->natts;
- int numaliases = list_length(eref->colnames);
+ ListCell *aliascell = list_head(eref->colnames);
int varattno;
- for (varattno = 0; varattno < maxattrs; varattno++)
+ if (colnames)
+ {
+ int i;
+
+ for (i = 0; i < offset; i++)
+ {
+ if (aliascell)
+ aliascell = lnext(aliascell);
+ }
+ }
+
+ Assert(count <= tupdesc->natts);
+ for (varattno = 0; varattno < count; varattno++)
{
Form_pg_attribute attr = tupdesc->attrs[varattno];
@@ -2006,6 +2307,8 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
makeNullConst(INT4OID, -1, InvalidOid));
}
}
+ if (aliascell)
+ aliascell = lnext(aliascell);
continue;
}
@@ -2013,10 +2316,16 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
{
char *label;
- if (varattno < numaliases)
- label = strVal(list_nth(eref->colnames, varattno));
+ if (aliascell)
+ {
+ label = strVal(lfirst(aliascell));
+ aliascell = lnext(aliascell);
+ }
else
+ {
+ /* If we run out of aliases, use the underlying name */
label = NameStr(attr->attname);
+ }
*colnames = lappend(*colnames, makeString(pstrdup(label)));
}
@@ -2024,7 +2333,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
{
Var *varnode;
- varnode = makeVar(rtindex, attr->attnum,
+ varnode = makeVar(rtindex, varattno + offset + 1,
attr->atttypid, attr->atttypmod,
attr->attcollation,
sublevels_up);
@@ -2188,62 +2497,93 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
case RTE_FUNCTION:
{
/* Function RTE */
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
-
- functypclass = get_expr_result_type(rte->funcexpr,
- &funcrettype,
- &tupdesc);
+ ListCell *lc;
+ int atts_done = 0;
- if (functypclass == TYPEFUNC_COMPOSITE)
+ /* Identify which function covers the requested column */
+ foreach(lc, rte->functions)
{
- /* Composite data type, e.g. a table's row type */
- Form_pg_attribute att_tup;
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
- Assert(tupdesc);
- /* this is probably a can't-happen case */
- if (attnum < 1 || attnum > tupdesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum,
- rte->eref->aliasname)));
+ if (attnum > atts_done &&
+ attnum <= atts_done + rtfunc->funccolcount)
+ {
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
- att_tup = tupdesc->attrs[attnum - 1];
+ attnum -= atts_done; /* now relative to this func */
+ functypclass = get_expr_result_type(rtfunc->funcexpr,
+ &funcrettype,
+ &tupdesc);
- /*
- * If dropped column, pretend it ain't there. See notes
- * in scanRTEForColumn.
- */
- if (att_tup->attisdropped)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- NameStr(att_tup->attname),
- rte->eref->aliasname)));
- *vartype = att_tup->atttypid;
- *vartypmod = att_tup->atttypmod;
- *varcollid = att_tup->attcollation;
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Form_pg_attribute att_tup;
+
+ Assert(tupdesc);
+ Assert(attnum <= tupdesc->natts);
+ att_tup = tupdesc->attrs[attnum - 1];
+
+ /*
+ * If dropped column, pretend it ain't there. See
+ * notes in scanRTEForColumn.
+ */
+ if (att_tup->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ NameStr(att_tup->attname),
+ rte->eref->aliasname)));
+ *vartype = att_tup->atttypid;
+ *vartypmod = att_tup->atttypmod;
+ *varcollid = att_tup->attcollation;
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ *vartype = funcrettype;
+ *vartypmod = -1;
+ *varcollid = exprCollation(rtfunc->funcexpr);
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ *vartype = list_nth_oid(rtfunc->funccoltypes,
+ attnum - 1);
+ *vartypmod = list_nth_int(rtfunc->funccoltypmods,
+ attnum - 1);
+ *varcollid = list_nth_oid(rtfunc->funccolcollations,
+ attnum - 1);
+ }
+ else
+ {
+ /*
+ * addRangeTableEntryForFunction should've caught
+ * this
+ */
+ elog(ERROR, "function in FROM has unsupported return type");
+ }
+ return;
+ }
+ atts_done += rtfunc->funccolcount;
}
- else if (functypclass == TYPEFUNC_SCALAR)
+
+ /* If we get here, must be looking for the ordinality column */
+ if (rte->funcordinality && attnum == atts_done + 1)
{
- /* Base data type, i.e. scalar */
- *vartype = funcrettype;
+ *vartype = INT8OID;
*vartypmod = -1;
- *varcollid = exprCollation(rte->funcexpr);
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- *vartype = list_nth_oid(rte->funccoltypes, attnum - 1);
- *vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1);
- *varcollid = list_nth_oid(rte->funccolcollations, attnum - 1);
- }
- else
- {
- /* addRangeTableEntryForFunction should've caught this */
- elog(ERROR, "function in FROM has unsupported return type");
+ *varcollid = InvalidOid;
+ return;
}
+
+ /* this probably can't happen ... */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum,
+ rte->eref->aliasname)));
}
break;
case RTE_VALUES:
@@ -2271,6 +2611,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
aliasvar = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
+ Assert(aliasvar != NULL);
*vartype = exprType(aliasvar);
*vartypmod = exprTypmod(aliasvar);
*varcollid = exprCollation(aliasvar);
@@ -2333,7 +2674,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
* but one in a stored rule might contain columns that were
* dropped from the underlying tables, if said columns are
* nowhere explicitly referenced in the rule. This will be
- * signaled to us by a NULL Const in the joinaliasvars list.
+ * signaled to us by a null pointer in the joinaliasvars list.
*/
Var *aliasvar;
@@ -2342,42 +2683,63 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
elog(ERROR, "invalid varattno %d", attnum);
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
- result = IsA(aliasvar, Const);
+ result = (aliasvar == NULL);
}
break;
case RTE_FUNCTION:
{
/* Function RTE */
- Oid funcrettype = exprType(rte->funcexpr);
- Oid funcrelid = typeidTypeRelid(funcrettype);
+ ListCell *lc;
+ int atts_done = 0;
- if (OidIsValid(funcrelid))
- {
- /*
- * Composite data type, i.e. a table's row type
- *
- * Same as ordinary relation RTE
- */
- HeapTuple tp;
- Form_pg_attribute att_tup;
-
- tp = SearchSysCache2(ATTNUM,
- ObjectIdGetDatum(funcrelid),
- Int16GetDatum(attnum));
- if (!HeapTupleIsValid(tp)) /* shouldn't happen */
- elog(ERROR, "cache lookup failed for attribute %d of relation %u",
- attnum, funcrelid);
- att_tup = (Form_pg_attribute) GETSTRUCT(tp);
- result = att_tup->attisdropped;
- ReleaseSysCache(tp);
- }
- else
+ /*
+ * Dropped attributes are only possible with functions that
+ * return named composite types. In such a case we have to
+ * look up the result type to see if it currently has this
+ * column dropped. So first, loop over the funcs until we
+ * find the one that covers the requested column.
+ */
+ foreach(lc, rte->functions)
{
- /*
- * Must be a base data type, i.e. scalar
- */
- result = false;
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (attnum > atts_done &&
+ attnum <= atts_done + rtfunc->funccolcount)
+ {
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+
+ functypclass = get_expr_result_type(rtfunc->funcexpr,
+ &funcrettype,
+ &tupdesc);
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Form_pg_attribute att_tup;
+
+ Assert(tupdesc);
+ Assert(attnum - atts_done <= tupdesc->natts);
+ att_tup = tupdesc->attrs[attnum - atts_done - 1];
+ return att_tup->attisdropped;
+ }
+ /* Otherwise, it can't have any dropped columns */
+ return false;
+ }
+ atts_done += rtfunc->funccolcount;
}
+
+ /* If we get here, must be looking for the ordinality column */
+ if (rte->funcordinality && attnum == atts_done + 1)
+ return false;
+
+ /* this probably can't happen ... */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum,
+ rte->eref->aliasname)));
+ result = false; /* keep compiler quiet */
}
break;
default:
@@ -2570,10 +2932,10 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
/*
* Check to see if there are any potential matches in the query's
- * rangetable. (Note: cases involving a bad schema name in the RangeVar
+ * 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
@@ -2610,3 +2972,91 @@ 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)));
+}
+
+
+/*
+ * Examine a fully-parsed query, and return TRUE iff any relation underlying
+ * the query is a temporary relation (table, view, or materialized view).
+ */
+bool
+isQueryUsingTempRelation(Query *query)
+{
+ return isQueryUsingTempRelation_walker((Node *) query, NULL);
+}
+
+static bool
+isQueryUsingTempRelation_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *rtable;
+
+ foreach(rtable, query->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ Relation rel = heap_open(rte->relid, AccessShareLock);
+ char relpersistence = rel->rd_rel->relpersistence;
+
+ heap_close(rel, AccessShareLock);
+ if (relpersistence == RELPERSISTENCE_TEMP)
+ return true;
+ }
+ }
+
+ return query_tree_walker(query,
+ isQueryUsingTempRelation_walker,
+ context,
+ QTW_IGNORE_JOINALIASES);
+ }
+
+ return expression_tree_walker(node,
+ isQueryUsingTempRelation_walker,
+ context);
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index feec677050..3cd29df324 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -3,7 +3,7 @@
* parse_target.c
* handle target lists
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -57,14 +57,14 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
Node *rhs,
int location);
static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
- bool targetlist);
+ bool make_target_entry);
static List *ExpandAllTables(ParseState *pstate, int location);
static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
- bool targetlist);
+ bool make_target_entry, ParseExprKind exprKind);
static List *ExpandSingleTable(ParseState *pstate, RangeTblEntry *rte,
- int location, bool targetlist);
+ int location, bool make_target_entry);
static List *ExpandRowReference(ParseState *pstate, Node *expr,
- bool targetlist);
+ bool make_target_entry);
static int FigureColnameInternal(Node *node, char **name);
@@ -76,6 +76,7 @@ static int FigureColnameInternal(Node *node, char **name);
*
* node the (untransformed) parse tree for the value expression.
* expr the transformed expression, or NULL if caller didn't do it yet.
+ * exprKind expression kind (EXPR_KIND_SELECT_TARGET, etc)
* colname the column name to be assigned, or NULL if none yet set.
* resjunk true if the target should be marked resjunk, ie, it is not
* wanted in the final projected tuple.
@@ -84,12 +85,13 @@ TargetEntry *
transformTargetEntry(ParseState *pstate,
Node *node,
Node *expr,
+ ParseExprKind exprKind,
char *colname,
bool resjunk)
{
/* Transform the node if caller didn't do it already */
if (expr == NULL)
- expr = transformExpr(pstate, node);
+ expr = transformExpr(pstate, node, exprKind);
if (colname == NULL && !resjunk)
{
@@ -111,11 +113,13 @@ transformTargetEntry(ParseState *pstate,
* transformTargetList()
* Turns a list of ResTarget's into a list of TargetEntry's.
*
- * At this point, we don't care whether we are doing SELECT, INSERT,
- * or UPDATE; we just transform the given expressions (the "val" fields).
+ * At this point, we don't care whether we are doing SELECT, UPDATE,
+ * or RETURNING; we just transform the given expressions (the "val" fields).
+ * However, our subroutines care, so we need the exprKind parameter.
*/
List *
-transformTargetList(ParseState *pstate, List *targetlist)
+transformTargetList(ParseState *pstate, List *targetlist,
+ ParseExprKind exprKind)
{
List *p_target = NIL;
ListCell *o_target;
@@ -151,7 +155,7 @@ transformTargetList(ParseState *pstate, List *targetlist)
/* It is something.*, expand into multiple items */
p_target = list_concat(p_target,
ExpandIndirectionStar(pstate, ind,
- true));
+ true, exprKind));
continue;
}
}
@@ -163,6 +167,7 @@ transformTargetList(ParseState *pstate, List *targetlist)
transformTargetEntry(pstate,
res->val,
NULL,
+ exprKind,
res->name,
false));
}
@@ -177,10 +182,11 @@ transformTargetList(ParseState *pstate, List *targetlist)
* This is the identical transformation to transformTargetList, except that
* the input list elements are bare expressions without ResTarget decoration,
* and the output elements are likewise just expressions without TargetEntry
- * decoration. We use this for ROW() and VALUES() constructs.
+ * decoration. We use this for ROW() and VALUES() constructs.
*/
List *
-transformExpressionList(ParseState *pstate, List *exprlist)
+transformExpressionList(ParseState *pstate, List *exprlist,
+ ParseExprKind exprKind)
{
List *result = NIL;
ListCell *lc;
@@ -216,7 +222,7 @@ transformExpressionList(ParseState *pstate, List *exprlist)
/* It is something.*, expand into multiple items */
result = list_concat(result,
ExpandIndirectionStar(pstate, ind,
- false));
+ false, exprKind));
continue;
}
}
@@ -225,7 +231,7 @@ transformExpressionList(ParseState *pstate, List *exprlist)
* Not "something.*", so transform as a single expression
*/
result = lappend(result,
- transformExpr(pstate, e));
+ transformExpr(pstate, e, exprKind));
}
return result;
@@ -305,6 +311,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
+ /* We intentionally don't strip implicit coercions here */
markTargetListOrigin(pstate, tle, aliasvar, netlevelsup);
}
break;
@@ -346,7 +353,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
/*
* transformAssignedExpr()
- * This is used in INSERT and UPDATE statements only. It prepares an
+ * This is used in INSERT and UPDATE statements only. It prepares an
* expression for assignment to a column of the target table.
* This includes coercing the given value to the target column's type
* (if necessary), and dealing with any subfield names or subscripts
@@ -355,6 +362,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
*
* pstate parse state
* expr expression to be modified
+ * exprKind indicates which type of statement we're dealing with
* colname target column name (ie, name of attribute to be assigned to)
* attrno target attribute number
* indirection subscripts/field names for target column, if any
@@ -364,22 +372,33 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
*
* Note: location points at the target column name (SET target or INSERT
* column name list entry), and must therefore be -1 in an INSERT that
- * omits the column name list. So we should usually prefer to use
+ * omits the column name list. So we should usually prefer to use
* exprLocation(expr) for errors that can happen in a default INSERT.
*/
Expr *
transformAssignedExpr(ParseState *pstate,
Expr *expr,
+ ParseExprKind exprKind,
char *colname,
int attrno,
List *indirection,
int location)
{
+ Relation rd = pstate->p_target_relation;
Oid type_id; /* type of value provided */
Oid attrtype; /* type of target column */
int32 attrtypmod;
Oid attrcollation; /* collation of target column */
- Relation rd = pstate->p_target_relation;
+ ParseExprKind sv_expr_kind;
+
+ /*
+ * Save and restore identity of expression type we're parsing. We must
+ * set p_expr_kind here because we can parse subscripts without going
+ * through transformExpr().
+ */
+ Assert(exprKind != EXPR_KIND_NONE);
+ sv_expr_kind = pstate->p_expr_kind;
+ pstate->p_expr_kind = exprKind;
Assert(rd != NULL);
if (attrno <= 0)
@@ -428,7 +447,7 @@ transformAssignedExpr(ParseState *pstate,
/*
* If there is indirection on the target column, prepare an array or
- * subfield assignment expression. This will generate a new column value
+ * subfield assignment expression. This will generate a new column value
* that the source value has been inserted into, which can then be placed
* in the new tuple constructed by INSERT or UPDATE.
*/
@@ -496,6 +515,8 @@ transformAssignedExpr(ParseState *pstate,
parser_errposition(pstate, exprLocation(orig_expr))));
}
+ pstate->p_expr_kind = sv_expr_kind;
+
return expr;
}
@@ -526,6 +547,7 @@ updateTargetListEntry(ParseState *pstate,
/* Fix up expression as needed */
tle->expr = transformAssignedExpr(pstate,
tle->expr,
+ EXPR_KIND_UPDATE_TARGET,
colname,
attrno,
indirection,
@@ -533,7 +555,7 @@ updateTargetListEntry(ParseState *pstate,
/*
* Set the resno to identify the target column --- the rewriter and
- * planner depend on this. We also set the resname to identify the target
+ * planner depend on this. We also set the resname to identify the target
* column, but this is only for debugging purposes; it should not be
* relied on. (In particular, it might be out of date in a stored rule.)
*/
@@ -822,18 +844,20 @@ transformAssignmentSubscripts(ParseState *pstate,
/* If target was a domain over array, need to coerce up to the domain */
if (arrayType != targetTypeId)
{
+ Oid resulttype = exprType(result);
+
result = coerce_to_target_type(pstate,
- result, exprType(result),
+ result, resulttype,
targetTypeId, targetTypMod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
- /* probably shouldn't fail, but check */
+ /* can fail if we had int2vector/oidvector, but not for true domains */
if (result == NULL)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
- format_type_be(exprType(result)),
+ format_type_be(resulttype),
format_type_be(targetTypeId)),
parser_errposition(pstate, location)));
}
@@ -952,7 +976,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
*/
static List *
ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
- bool targetlist)
+ bool make_target_entry)
{
List *fields = cref->fields;
int numnames = list_length(fields);
@@ -965,9 +989,9 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
* (e.g., SELECT * FROM emp, dept)
*
* Since the grammar only accepts bare '*' at top level of SELECT, we
- * need not handle the targetlist==false case here.
+ * need not handle the make_target_entry==false case here.
*/
- Assert(targetlist);
+ Assert(make_target_entry);
return ExpandAllTables(pstate, cref->location);
}
else
@@ -979,7 +1003,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
*
* Note: this code is a lot like transformColumnRef; it's tempting to
* call that instead and then replace the resulting whole-row Var with
- * a list of Vars. However, that would leave us with the RTE's
+ * a list of Vars. However, that would leave us with the RTE's
* selectedCols bitmap showing the whole row as needing select
* permission, as well as the individual columns. That would be
* incorrect (since columns added later shouldn't need select
@@ -998,7 +1022,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
} crserr = CRSERR_NO_RTE;
/*
- * Give the PreParseColumnRefHook, if any, first shot. If it returns
+ * Give the PreParseColumnRefHook, if any, first shot. If it returns
* non-null then we should use that expression.
*/
if (pstate->p_pre_columnref_hook != NULL)
@@ -1007,7 +1031,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
node = (*pstate->p_pre_columnref_hook) (pstate, cref);
if (node != NULL)
- return ExpandRowReference(pstate, node, targetlist);
+ return ExpandRowReference(pstate, node, make_target_entry);
}
switch (numnames)
@@ -1070,7 +1094,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
errmsg("column reference \"%s\" is ambiguous",
NameListToString(cref->fields)),
parser_errposition(pstate, cref->location)));
- return ExpandRowReference(pstate, node, targetlist);
+ return ExpandRowReference(pstate, node, make_target_entry);
}
}
@@ -1105,7 +1129,7 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
/*
* OK, expand the RTE into fields.
*/
- return ExpandSingleTable(pstate, rte, cref->location, targetlist);
+ return ExpandSingleTable(pstate, rte, cref->location, make_target_entry);
}
}
@@ -1113,9 +1137,10 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
* ExpandAllTables()
* Transforms '*' (in the target list) into a list of targetlist entries.
*
- * tlist entries are generated for each relation appearing in the query's
- * varnamespace. We do not consider relnamespace because that would include
- * input tables of aliasless JOINs, NEW/OLD pseudo-entries, etc.
+ * tlist entries are generated for each relation visible for unqualified
+ * column name access. We do not consider qualified-name-only entries because
+ * that would include input tables of aliasless JOINs, NEW/OLD pseudo-entries,
+ * etc.
*
* The referenced relations/columns are marked as requiring SELECT access.
*/
@@ -1123,25 +1148,42 @@ static List *
ExpandAllTables(ParseState *pstate, int location)
{
List *target = NIL;
+ bool found_table = false;
ListCell *l;
- /* Check for SELECT *; */
- if (!pstate->p_varnamespace)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("SELECT * with no tables specified is not valid"),
- parser_errposition(pstate, location)));
-
- foreach(l, pstate->p_varnamespace)
+ foreach(l, pstate->p_namespace)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
- int rtindex = RTERangeTablePosn(pstate, rte, NULL);
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+ RangeTblEntry *rte = nsitem->p_rte;
+
+ /* Ignore table-only items */
+ if (!nsitem->p_cols_visible)
+ continue;
+ /* Should not have any lateral-only items when parsing targetlist */
+ Assert(!nsitem->p_lateral_only);
+ /* Remember we found a p_cols_visible item */
+ found_table = true;
target = list_concat(target,
- expandRelAttrs(pstate, rte, rtindex, 0,
+ expandRelAttrs(pstate,
+ rte,
+ RTERangeTablePosn(pstate, rte,
+ NULL),
+ 0,
location));
}
+ /*
+ * Check for "SELECT *;". We do it this way, rather than checking for
+ * target == NIL, because we want to allow SELECT * FROM a zero_column
+ * table.
+ */
+ if (!found_table)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT * with no tables specified is not valid"),
+ parser_errposition(pstate, location)));
+
return target;
}
@@ -1153,10 +1195,12 @@ ExpandAllTables(ParseState *pstate, int location)
* The code is shared between the case of foo.* at the top level in a SELECT
* target list (where we want TargetEntry nodes in the result) and foo.* in
* a ROW() or VALUES() construct (where we want just bare expressions).
+ * For robustness, we use a separate "make_target_entry" flag to control
+ * this rather than relying on exprKind.
*/
static List *
ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
- bool targetlist)
+ bool make_target_entry, ParseExprKind exprKind)
{
Node *expr;
@@ -1166,10 +1210,10 @@ ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
list_length(ind->indirection) - 1);
/* And transform that */
- expr = transformExpr(pstate, (Node *) ind);
+ expr = transformExpr(pstate, (Node *) ind, exprKind);
/* Expand the rowtype expression into individual fields */
- return ExpandRowReference(pstate, expr, targetlist);
+ return ExpandRowReference(pstate, expr, make_target_entry);
}
/*
@@ -1183,14 +1227,14 @@ ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
*/
static List *
ExpandSingleTable(ParseState *pstate, RangeTblEntry *rte,
- int location, bool targetlist)
+ int location, bool make_target_entry)
{
int sublevels_up;
int rtindex;
rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
- if (targetlist)
+ if (make_target_entry)
{
/* expandRelAttrs handles permissions marking */
return expandRelAttrs(pstate, rte, rtindex, sublevels_up,
@@ -1232,7 +1276,7 @@ ExpandSingleTable(ParseState *pstate, RangeTblEntry *rte,
*/
static List *
ExpandRowReference(ParseState *pstate, Node *expr,
- bool targetlist)
+ bool make_target_entry)
{
List *result = NIL;
TupleDesc tupleDesc;
@@ -1241,7 +1285,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
/*
* If the rowtype expression is a whole-row Var, we can expand the fields
- * as simple Vars. Note: if the RTE is a relation, this case leaves us
+ * as simple Vars. Note: if the RTE is a relation, this case leaves us
* with the RTE's selectedCols bitmap showing the whole row as needing
* select permission, as well as the individual columns. However, we can
* only get here for weird notations like (table.*).*, so it's not worth
@@ -1255,7 +1299,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
RangeTblEntry *rte;
rte = GetRTEByRangeTablePosn(pstate, var->varno, var->varlevelsup);
- return ExpandSingleTable(pstate, rte, var->location, targetlist);
+ return ExpandSingleTable(pstate, rte, var->location, make_target_entry);
}
/*
@@ -1300,7 +1344,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
/* save attribute's collation for parse_collate.c */
fselect->resultcollid = att->attcollation;
- if (targetlist)
+ if (make_target_entry)
{
/* add TargetEntry decoration */
TargetEntry *te;
@@ -1323,7 +1367,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
* Get the tuple descriptor for a Var of type RECORD, if possible.
*
* Since no actual table or view column is allowed to have type RECORD, such
- * a Var must refer to a JOIN or FUNCTION RTE or to a subquery output. We
+ * a Var must refer to a JOIN or FUNCTION RTE or to a subquery output. We
* drill down to find the ultimate defining expression and attempt to infer
* the tupdesc from it. We ereport if we can't determine the tupdesc.
*
@@ -1406,7 +1450,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
{
/*
* Recurse into the sub-select to see what its Var refers
- * to. We have to build an additional level of ParseState
+ * to. We have to build an additional level of ParseState
* to keep in step with varlevelsup in the subselect.
*/
ParseState mypstate;
@@ -1425,6 +1469,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
/* Join RTE --- recursively inspect the alias variable */
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
+ Assert(expr != NULL);
+ /* We intentionally don't strip implicit coercions here */
if (IsA(expr, Var))
return expandRecordVariable(pstate, (Var *) expr, netlevelsup);
/* else fall through to inspect the expression */
@@ -1483,7 +1529,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
/*
* We now have an expression we can't expand any more, so see if
- * get_expr_result_type() can do anything with it. If not, pass to
+ * get_expr_result_type() can do anything with it. If not, pass to
* lookup_rowtype_tupdesc() which will probably fail, but will give an
* appropriate error message while failing.
*/
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 871a7d1ce3..d0803dfafd 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -3,7 +3,7 @@
* parse_type.c
* handle type operations for parser
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
@@ -34,7 +35,7 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName,
/*
* LookupTypeName
* Given a TypeName object, lookup the pg_type syscache entry of the type.
- * Returns NULL if no such type can be found. If the type is found,
+ * Returns NULL if no such type can be found. If the type is found,
* the typmod value represented in the TypeName struct is computed and
* stored into *typmod_p.
*
@@ -47,7 +48,7 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName,
*
* typmod_p can be passed as NULL if the caller does not care to know the
* typmod value, but the typmod decoration (if any) will be validated anyway,
- * except in the case where the type is not found. Note that if the type is
+ * except in the case where the type is not found. Note that if the type is
* found but is a shell, and there is typmod decoration, an error will be
* thrown --- this is intentional.
*
@@ -55,7 +56,7 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName,
*/
Type
LookupTypeName(ParseState *pstate, const TypeName *typeName,
- int32 *typmod_p)
+ int32 *typmod_p, bool missing_ok)
{
Oid typoid;
HeapTuple tup;
@@ -112,27 +113,35 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName,
* Look up the field.
*
* XXX: As no lock is taken here, this might fail in the presence of
- * concurrent DDL. But taking a lock would carry a performance
+ * concurrent DDL. But taking a lock would carry a performance
* penalty and would also require a permissions check.
*/
- relid = RangeVarGetRelid(rel, NoLock, false);
+ relid = RangeVarGetRelid(rel, NoLock, missing_ok);
attnum = get_attnum(relid, field);
if (attnum == InvalidAttrNumber)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- field, rel->relname),
- parser_errposition(pstate, typeName->location)));
- typoid = get_atttype(relid, attnum);
+ {
+ if (missing_ok)
+ typoid = InvalidOid;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ field, rel->relname),
+ parser_errposition(pstate, typeName->location)));
+ }
+ else
+ {
+ typoid = get_atttype(relid, attnum);
- /* this construct should never have an array indicator */
- Assert(typeName->arrayBounds == NIL);
+ /* this construct should never have an array indicator */
+ Assert(typeName->arrayBounds == NIL);
- /* emit nuisance notice (intentionally not errposition'd) */
- ereport(NOTICE,
- (errmsg("type reference %s converted to %s",
- TypeNameToString(typeName),
- format_type_be(typoid))));
+ /* emit nuisance notice (intentionally not errposition'd) */
+ ereport(NOTICE,
+ (errmsg("type reference %s converted to %s",
+ TypeNameToString(typeName),
+ format_type_be(typoid))));
+ }
}
else
{
@@ -148,10 +157,13 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName,
/* Look in specific schema only */
Oid namespaceId;
- namespaceId = LookupExplicitNamespace(schemaname);
- typoid = GetSysCacheOid2(TYPENAMENSP,
- PointerGetDatum(typname),
- ObjectIdGetDatum(namespaceId));
+ namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
+ if (OidIsValid(namespaceId))
+ typoid = GetSysCacheOid2(TYPENAMENSP,
+ PointerGetDatum(typname),
+ ObjectIdGetDatum(namespaceId));
+ else
+ typoid = InvalidOid;
}
else
{
@@ -184,6 +196,43 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName,
}
/*
+ * LookupTypeNameOid
+ * Given a TypeName object, lookup the pg_type syscache entry of the type.
+ * Returns InvalidOid if no such type can be found. If the type is found,
+ * return its Oid.
+ *
+ * NB: direct callers of this function need to be aware that the type OID
+ * returned may correspond to a shell type. Most code should go through
+ * typenameTypeId instead.
+ *
+ * pstate is only used for error location info, and may be NULL.
+ */
+Oid
+LookupTypeNameOid(ParseState *pstate, const TypeName *typeName, bool missing_ok)
+{
+ Oid typoid;
+ Type tup;
+
+ tup = LookupTypeName(pstate, typeName, NULL, missing_ok);
+ if (tup == NULL)
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" does not exist",
+ TypeNameToString(typeName)),
+ parser_errposition(pstate, typeName->location)));
+
+ return InvalidOid;
+ }
+
+ typoid = HeapTupleGetOid(tup);
+ ReleaseSysCache(tup);
+
+ return typoid;
+}
+
+/*
* typenameType - given a TypeName, return a Type structure and typmod
*
* This is equivalent to LookupTypeName, except that this will report
@@ -195,7 +244,7 @@ typenameType(ParseState *pstate, const TypeName *typeName, int32 *typmod_p)
{
Type tup;
- tup = LookupTypeName(pstate, typeName, typmod_p);
+ tup = LookupTypeName(pstate, typeName, typmod_p, false);
if (tup == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -312,8 +361,7 @@ typenameTypeMod(ParseState *pstate, const TypeName *typeName, Type typ)
if (IsA(&ac->val, Integer))
{
- cstr = (char *) palloc(32);
- snprintf(cstr, 32, "%ld", (long) ac->val.val.ival);
+ cstr = psprintf("%ld", (long) ac->val.val.ival);
}
else if (IsA(&ac->val, Float) ||
IsA(&ac->val, String))
@@ -471,7 +519,7 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid)
{
Oid result;
Oid typcollation = get_typcollation(typeOid);
- int location = -1;
+ int location = coldef->location;
if (coldef->collClause)
{
@@ -577,7 +625,7 @@ typeTypeCollation(Type typ)
/*
* Given a type structure and a string, returns the internal representation
- * of that string. The "string" can be NULL to perform conversion of a NULL
+ * of that string. The "string" can be NULL to perform conversion of a NULL
* (which might result in failure, if the input function rejects NULLs).
*/
Datum
@@ -601,7 +649,7 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
* instability in the input function is that comparison of Const nodes
* relies on bytewise comparison of the datums, so if the input function
* leaves garbage then subexpressions that should be identical may not get
- * recognized as such. See pgsql-hackers discussion of 2008-04-04.
+ * recognized as such. See pgsql-hackers discussion of 2008-04-04.
*/
if (string && !typform->typbyval)
{
@@ -648,7 +696,7 @@ pts_error_callback(void *arg)
/*
* Currently we just suppress any syntax error position report, rather
- * than transforming to an "internal query" error. It's unlikely that a
+ * than transforming to an "internal query" error. It's unlikely that a
* type name is complex enough to need positioning.
*/
errposition(0);
@@ -658,9 +706,12 @@ pts_error_callback(void *arg)
* Given a string that is supposed to be a SQL-compatible type declaration,
* such as "int4" or "integer" or "character varying(32)", parse
* the string and convert it to a type OID and type modifier.
+ * If missing_ok is true, InvalidOid is returned rather than raising an error
+ * when the type name is not found.
*/
void
-parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p)
+parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
+ bool missing_ok)
{
StringInfoData buf;
List *raw_parsetree_list;
@@ -669,6 +720,7 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p)
TypeCast *typecast;
TypeName *typeName;
ErrorContextCallback ptserrcontext;
+ Type tup;
/* make sure we give useful error for empty input */
if (strspn(str, " \t\n\r\f") == strlen(str))
@@ -705,12 +757,12 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p)
stmt->groupClause != NIL ||
stmt->havingClause != NULL ||
stmt->windowClause != NIL ||
- stmt->withClause != NULL ||
stmt->valuesLists != NIL ||
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
stmt->limitCount != NULL ||
stmt->lockingClause != NIL ||
+ stmt->withClause != NULL ||
stmt->op != SETOP_NONE)
goto fail;
if (list_length(stmt->targetList) != 1)
@@ -734,7 +786,28 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p)
if (typeName->setof)
goto fail;
- typenameTypeIdAndMod(NULL, typeName, typeid_p, typmod_p);
+ tup = LookupTypeName(NULL, typeName, typmod_p, missing_ok);
+ if (tup == NULL)
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" does not exist",
+ TypeNameToString(typeName)),
+ parser_errposition(NULL, typeName->location)));
+ *typeid_p = InvalidOid;
+ }
+ else
+ {
+ if (!((Form_pg_type) GETSTRUCT(tup))->typisdefined)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" is only a shell",
+ TypeNameToString(typeName)),
+ parser_errposition(NULL, typeName->location)));
+ *typeid_p = HeapTupleGetOid(tup);
+ ReleaseSysCache(tup);
+ }
pfree(buf.data);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ec00730eec..0dca7b1214 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -21,7 +21,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Portions Copyright (c) 2012-2014, TransLattice, Inc.
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
*
@@ -32,6 +32,7 @@
#include "postgres.h"
+#include "access/htup_details.h"
#include "access/reloptions.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
@@ -97,6 +98,7 @@ typedef struct
RangeVar *relation; /* relation to create */
Relation rel; /* opened/locked rel, if ALTER */
List *inhRelations; /* relations to inherit from */
+ bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */
bool isalter; /* true if altering existing table */
bool hasoids; /* does relation have an OID column? */
List *columns; /* ColumnDef items */
@@ -147,9 +149,9 @@ static void transformTableLikeClause(CreateStmtContext *cxt,
TableLikeClause *table_like_clause);
static void transformOfType(CreateStmtContext *cxt,
TypeName *ofTypename);
-static char *chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt);
static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,
- Relation parent_index, AttrNumber *attmap);
+ Relation source_idx,
+ const AttrNumber *attmap, int attmap_length);
static List *get_collation(Oid collation, Oid actual_datatype);
static List *get_opclass(Oid opclass, Oid actual_datatype);
static void transformIndexConstraints(CreateStmtContext *cxt);
@@ -178,7 +180,7 @@ static PGXCSubCluster *makeSubCluster(List *nodelist);
* will be the transformed CreateStmt, but there may be additional actions
* to be done before and after the actual DefineRelation() call.
*
- * SQL92 allows constraints to be scattered all over, so thumb through
+ * SQL allows constraints to be scattered all over, so thumb through
* the columns and collect all constraints into one place.
* If there are any implied indices (e.g. UNIQUE or PRIMARY KEY)
* then expand those into multiple IndexStmt blocks.
@@ -208,7 +210,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
stmt = (CreateStmt *) copyObject(stmt);
/*
- * Look up the creation namespace. This also checks permissions on the
+ * Look up the creation namespace. This also checks permissions on the
* target namespace, locks it against concurrent drops, checks for a
* preexisting relation in that namespace with the same name, and updates
* stmt->relation->relpersistence if the select namespace is temporary.
@@ -234,7 +236,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
* If the target relation name isn't schema-qualified, make it so. This
* prevents some corner cases in which added-on rewritten commands might
* think they should apply to other relations that have the same name and
- * are earlier in the search path. But a local temp table is effectively
+ * are earlier in the search path. But a local temp table is effectively
* specified to be in pg_temp, so no need for anything extra in that case.
*/
if (stmt->relation->schemaname == NULL
@@ -247,9 +249,15 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.pstate = pstate;
if (IsA(stmt, CreateForeignTableStmt))
+ {
cxt.stmtType = "CREATE FOREIGN TABLE";
+ cxt.isforeign = true;
+ }
else
+ {
cxt.stmtType = "CREATE TABLE";
+ cxt.isforeign = false;
+ }
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -262,7 +270,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.hasoids = interpretOidsOption(stmt->options);
+ cxt.hasoids = interpretOidsOption(stmt->options, true);
#ifdef PGXC
#ifdef XCP
cxt.fallback_source = FBS_NONE;
@@ -539,7 +547,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
"seq",
snamespaceid);
- ereport(NOTICE,
+ ereport(DEBUG1,
(errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"",
cxt->stmtType, sname,
cxt->relation->relname, column->colname)));
@@ -607,16 +615,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
- funccallnode = makeNode(FuncCall);
- funccallnode->funcname = SystemFuncName("nextval");
- funccallnode->args = list_make1(castnode);
- funccallnode->agg_order = NIL;
- funccallnode->agg_star = false;
- funccallnode->agg_distinct = false;
- funccallnode->func_variadic = false;
- funccallnode->over = NULL;
- funccallnode->location = -1;
-
+ funccallnode = makeFuncCall(SystemFuncName("nextval"),
+ list_make1(castnode),
+ -1);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
@@ -681,11 +682,23 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_CHECK:
+ if (cxt->isforeign)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("constraints are not supported on foreign tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
+ if (cxt->isforeign)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("constraints are not supported on foreign tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -697,6 +710,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_FOREIGN:
+ if (cxt->isforeign)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("constraints are not supported on foreign tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -721,8 +740,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
}
/*
- * Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds
- * per-column foreign data wrapper options for this column.
+ * If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add
+ * per-column foreign data wrapper options to this column after creation.
*/
if (column->fdwoptions != NIL)
{
@@ -753,6 +772,13 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
+ if (cxt->isforeign)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("constraints are not supported on foreign tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
+
switch (constraint->contype)
{
case CONSTR_PRIMARY:
@@ -801,22 +827,31 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
Relation relation;
TupleDesc tupleDesc;
TupleConstr *constr;
+ AttrNumber *attmap;
AclResult aclresult;
char *comment;
ParseCallbackState pcbstate;
- setup_parser_errposition_callback(&pcbstate, cxt->pstate, table_like_clause->relation->location);
+ setup_parser_errposition_callback(&pcbstate, cxt->pstate,
+ table_like_clause->relation->location);
+
+ /* we could support LIKE in many cases, but worry about it another day */
+ if (cxt->isforeign)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("LIKE is not supported for creating foreign tables")));
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
- if (relation->rd_rel->relkind != RELKIND_RELATION
- && relation->rd_rel->relkind != RELKIND_VIEW
- && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE
- && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_VIEW &&
+ relation->rd_rel->relkind != RELKIND_MATVIEW &&
+ relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, composite type, or foreign table",
- table_like_clause->relation->relname)));
+ errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
+ RelationGetRelationName(relation))));
cancel_parser_errposition_callback(&pcbstate);
@@ -876,6 +911,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
constr = tupleDesc->constr;
/*
+ * Initialize column number map for map_variable_attnos(). We need this
+ * since dropped columns in the source table aren't copied, so the new
+ * table can have different column numbers.
+ */
+ attmap = (AttrNumber *) palloc0(sizeof(AttrNumber) * tupleDesc->natts);
+
+ /*
* Insert the copied attributes into the cxt for the new table definition.
*/
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
@@ -886,7 +928,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
ColumnDef *def;
/*
- * Ignore dropped columns in the parent.
+ * Ignore dropped columns in the parent. attmap entry is left zero.
*/
if (attribute->attisdropped)
continue;
@@ -911,6 +953,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->collClause = NULL;
def->collOid = attribute->attcollation;
def->constraints = NIL;
+ def->location = -1;
/*
* Add to column list
@@ -931,6 +974,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
cxt->fallback_source = FBS_COLDEF;
}
#endif
+ attmap[parent_attno - 1] = list_length(cxt->columns);
/*
* Copy default, if present and the default has been requested
@@ -990,22 +1034,39 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
/*
* Copy CHECK constraints if requested, being careful to adjust attribute
- * numbers
+ * numbers so they match the child.
*/
if ((table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) &&
tupleDesc->constr)
{
- AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
int ccnum;
for (ccnum = 0; ccnum < tupleDesc->constr->num_check; ccnum++)
{
char *ccname = tupleDesc->constr->check[ccnum].ccname;
char *ccbin = tupleDesc->constr->check[ccnum].ccbin;
- Node *ccbin_node = stringToNode(ccbin);
Constraint *n = makeNode(Constraint);
+ Node *ccbin_node;
+ bool found_whole_row;
- change_varattnos_of_a_node(ccbin_node, attmap);
+ ccbin_node = map_variable_attnos(stringToNode(ccbin),
+ 1, 0,
+ attmap, tupleDesc->natts,
+ &found_whole_row);
+
+ /*
+ * We reject whole-row variables because the whole point of LIKE
+ * is that the new table's rowtype might later diverge from the
+ * parent's. So, while translation might be possible right now,
+ * it wouldn't be possible to guarantee it would work in future.
+ */
+ if (found_whole_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert whole-row table reference"),
+ errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
+ ccname,
+ RelationGetRelationName(relation))));
n->contype = CONSTR_CHECK;
n->location = -1;
@@ -1041,7 +1102,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) &&
relation->rd_rel->relhasindex)
{
- AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
List *parent_indexes;
ListCell *l;
@@ -1056,35 +1116,19 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
parent_index = index_open(parent_index_oid, AccessShareLock);
/* Build CREATE INDEX statement to recreate the parent_index */
- index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
+ index_stmt = generateClonedIndexStmt(cxt, parent_index,
+ attmap, tupleDesc->natts);
- /* Copy comment on index */
+ /* Copy comment on index, if requested */
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
{
comment = GetComment(parent_index_oid, RelationRelationId, 0);
- if (comment != NULL)
- {
- CommentStmt *stmt;
-
- /*
- * We have to assign the index a name now, so that we can
- * reference it in CommentStmt.
- */
- if (index_stmt->idxname == NULL)
- index_stmt->idxname = chooseIndexName(cxt->relation,
- index_stmt);
-
- stmt = makeNode(CommentStmt);
- stmt->objtype = OBJECT_INDEX;
- stmt->objname =
- list_make2(makeString(cxt->relation->schemaname),
- makeString(index_stmt->idxname));
- stmt->objargs = NIL;
- stmt->comment = comment;
-
- cxt->alist = lappend(cxt->alist, stmt);
- }
+ /*
+ * We make use of IndexStmt's idxcomment option, so as not to
+ * need to know now what name the index will have.
+ */
+ index_stmt->idxcomment = comment;
}
/* Save it in the inh_indexes list for the time being */
@@ -1096,7 +1140,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
/*
* Close the parent rel, but keep our AccessShareLock on it until xact
- * commit. That will prevent someone else from deleting or ALTERing the
+ * commit. That will prevent someone else from deleting or ALTERing the
* parent before the child is committed.
*/
heap_close(relation, NoLock);
@@ -1139,6 +1183,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->collClause = NULL;
n->collOid = attr->attcollation;
n->constraints = NIL;
+ n->location = -1;
cxt->columns = lappend(cxt->columns, n);
}
DecrTupleDescRefCount(tupdesc);
@@ -1147,35 +1192,12 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
}
/*
- * chooseIndexName
- *
- * Compute name for an index. This must match code in indexcmds.c.
- *
- * XXX this is inherently broken because the indexes aren't created
- * immediately, so we fail to resolve conflicts when the same name is
- * derived for multiple indexes. However, that's a reasonably uncommon
- * situation, so we'll live with it for now.
- */
-static char *
-chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
-{
- Oid namespaceId;
- List *colnames;
-
- namespaceId = RangeVarGetCreationNamespace(relation);
- colnames = ChooseIndexColumnNames(index_stmt->indexParams);
- return ChooseIndexName(relation->relname, namespaceId,
- colnames, index_stmt->excludeOpNames,
- index_stmt->primary, index_stmt->isconstraint);
-}
-
-/*
* Generate an IndexStmt node using information from an already existing index
* "source_idx". Attribute numbers should be adjusted according to attmap.
*/
static IndexStmt *
generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
- AttrNumber *attmap)
+ const AttrNumber *attmap, int attmap_length)
{
Oid source_relid = RelationGetRelid(source_idx);
Form_pg_attribute *attrs = RelationGetDescr(source_idx)->attrs;
@@ -1232,7 +1254,10 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->tableSpace = get_tablespace_name(idxrelrec->reltablespace);
else
index->tableSpace = NULL;
+ index->excludeOpNames = NIL;
+ index->idxcomment = NULL;
index->indexOid = InvalidOid;
+ index->oldNode = InvalidOid;
index->unique = idxrec->indisunique;
index->primary = idxrec->indisprimary;
index->concurrent = false;
@@ -1363,14 +1388,26 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
{
/* Expressional index */
Node *indexkey;
+ bool found_whole_row;
if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item);
indexpr_item = lnext(indexpr_item);
- /* OK to modify indexkey since we are working on a private copy */
- change_varattnos_of_a_node(indexkey, attmap);
+ /* Adjust Vars to match new table's column numbering */
+ indexkey = map_variable_attnos(indexkey,
+ 1, 0,
+ attmap, attmap_length,
+ &found_whole_row);
+
+ /* As in transformTableLikeClause, reject whole-row variables */
+ if (found_whole_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert whole-row table reference"),
+ errdetail("Index \"%s\" contains a whole-row table reference.",
+ RelationGetRelationName(source_idx))));
iparam->name = NULL;
iparam->expr = indexkey;
@@ -1427,12 +1464,28 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
if (!isnull)
{
char *pred_str;
+ Node *pred_tree;
+ bool found_whole_row;
/* Convert text string to node tree */
pred_str = TextDatumGetCString(datum);
- index->whereClause = (Node *) stringToNode(pred_str);
- /* Adjust attribute numbers */
- change_varattnos_of_a_node(index->whereClause, attmap);
+ pred_tree = (Node *) stringToNode(pred_str);
+
+ /* Adjust Vars to match new table's column numbering */
+ pred_tree = map_variable_attnos(pred_tree,
+ 1, 0,
+ attmap, attmap_length,
+ &found_whole_row);
+
+ /* As in transformTableLikeClause, reject whole-row variables */
+ if (found_whole_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert whole-row table reference"),
+ errdetail("Index \"%s\" contains a whole-row table reference.",
+ RelationGetRelationName(source_idx))));
+
+ index->whereClause = pred_tree;
}
/* Clean up */
@@ -1561,8 +1614,8 @@ transformIndexConstraints(CreateStmtContext *cxt)
/*
* Scan the index list and remove any redundant index specifications. This
* can happen if, for instance, the user writes UNIQUE PRIMARY KEY. A
- * strict reading of SQL92 would suggest raising an error instead, but
- * that strikes me as too anal-retentive. - tgl 2001-02-14
+ * strict reading of SQL would suggest raising an error instead, but that
+ * strikes me as too anal-retentive. - tgl 2001-02-14
*
* XXX in ALTER TABLE case, it'd be nice to look for duplicate
* pre-existing indexes, too.
@@ -1669,7 +1722,9 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->whereClause = constraint->where_clause;
index->indexParams = NIL;
index->excludeOpNames = NIL;
+ index->idxcomment = NULL;
index->indexOid = InvalidOid;
+ index->oldNode = InvalidOid;
index->concurrent = false;
/*
@@ -1733,18 +1788,12 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index_name, RelationGetRelationName(heap_rel)),
parser_errposition(cxt->pstate, constraint->location)));
- if (!index_form->indisvalid)
+ if (!IndexIsValid(index_form))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("index \"%s\" is not valid", index_name),
parser_errposition(cxt->pstate, constraint->location)));
- if (!index_form->indisready)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("index \"%s\" is not ready", index_name),
- parser_errposition(cxt->pstate, constraint->location)));
-
if (!index_form->indisunique)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1779,7 +1828,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
parser_errposition(cxt->pstate, constraint->location)));
/*
- * Insist on it being a btree. That's the only kind that supports
+ * Insist on it being a btree. That's the only kind that supports
* uniqueness at the moment anyway; but we must have an index that
* exactly matches what you'd get from plain ADD CONSTRAINT syntax,
* else dump and reload will produce a different index (breaking
@@ -1799,14 +1848,14 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
for (i = 0; i < index_form->indnatts; i++)
{
- int2 attnum = index_form->indkey.values[i];
+ int16 attnum = index_form->indkey.values[i];
Form_pg_attribute attform;
char *attname;
Oid defopclass;
/*
* We shouldn't see attnum == 0 here, since we already rejected
- * expression indexes. If we do, SystemAttributeDefinition will
+ * expression indexes. If we do, SystemAttributeDefinition will
* throw an error.
*/
if (attnum > 0)
@@ -1820,7 +1869,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
attname = pstrdup(NameStr(attform->attname));
/*
- * Insist on default opclass and sort options. While the index
+ * Insist on default opclass and sort options. While the index
* would still work as a constraint with non-default settings, it
* might not provide exactly the same uniqueness semantics as
* you'd get from a normally-created constraint; and there's also
@@ -2274,17 +2323,21 @@ transformFKConstraints(CreateStmtContext *cxt,
* transformIndexStmt - parse analysis for CREATE INDEX and ALTER TABLE
*
* Note: this is a no-op for an index not using either index expressions or
- * a predicate expression. There are several code paths that create indexes
+ * a predicate expression. There are several code paths that create indexes
* without bothering to call this, because they know they don't have any
* such expressions to deal with.
+ *
+ * To avoid race conditions, it's important that this function rely only on
+ * the passed-in relid (and not on stmt->relation) to determine the target
+ * relation.
*/
IndexStmt *
-transformIndexStmt(IndexStmt *stmt, const char *queryString)
+transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
{
- Relation rel;
ParseState *pstate;
RangeTblEntry *rte;
ListCell *l;
+ Relation rel;
/*
* We must not scribble on the passed-in IndexStmt, so copy it. (This is
@@ -2292,26 +2345,17 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString)
*/
stmt = (IndexStmt *) copyObject(stmt);
- /*
- * Open the parent table with appropriate locking. We must do this
- * because addRangeTableEntry() would acquire only AccessShareLock,
- * leaving DefineIndex() needing to do a lock upgrade with consequent risk
- * of deadlock. Make sure this stays in sync with the type of lock
- * DefineIndex() wants. If we are being called by ALTER TABLE, we will
- * already hold a higher lock.
- */
- rel = heap_openrv(stmt->relation,
- (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));
-
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification.
+ * to its fields without qualification. Caller is responsible for locking
+ * relation, but we still need to open it.
*/
- rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
+ rel = relation_open(relid, NoLock);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
/* no to join list, yes to namespaces */
addRTEtoQuery(pstate, rte, false, true, true);
@@ -2321,6 +2365,7 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString)
{
stmt->whereClause = transformWhereClause(pstate,
stmt->whereClause,
+ EXPR_KIND_INDEX_PREDICATE,
"WHERE");
/* we have to fix its collations too */
assign_expr_collations(pstate, stmt->whereClause);
@@ -2338,15 +2383,20 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString)
ielem->indexcolname = FigureIndexColname(ielem->expr);
/* Now do parse transformation of the expression */
- ielem->expr = transformExpr(pstate, ielem->expr);
+ ielem->expr = transformExpr(pstate, ielem->expr,
+ EXPR_KIND_INDEX_EXPRESSION);
/* We have to fix its collations too */
assign_expr_collations(pstate, ielem->expr);
/*
- * We check only that the result type is legitimate; this is for
- * consistency with what transformWhereClause() checks for the
- * predicate. DefineIndex() will make more checks.
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * an index expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * with what transformWhereClause() checks for the predicate.
+ * DefineIndex() will make more checks.
*/
if (expression_returns_set(ielem->expr))
ereport(ERROR,
@@ -2356,7 +2406,8 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString)
}
/*
- * Check that only the base rel is mentioned.
+ * Check that only the base rel is mentioned. (This should be dead code
+ * now that add_missing_from is history.)
*/
if (list_length(pstate->p_rtable) != 1)
ereport(ERROR,
@@ -2365,7 +2416,7 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation, but keep the lock */
+ /* Close relation */
heap_close(rel, NoLock);
return stmt;
@@ -2395,12 +2446,17 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
/*
* To avoid deadlock, make sure the first thing we do is grab
- * AccessExclusiveLock on the target relation. This will be needed by
+ * AccessExclusiveLock on the target relation. This will be needed by
* DefineQueryRewrite(), and we don't want to grab a lesser lock
* beforehand.
*/
rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+ if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("rules on materialized views are not supported")));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -2451,25 +2507,17 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
/* take care of the where clause */
*whereClause = transformWhereClause(pstate,
(Node *) copyObject(stmt->whereClause),
+ EXPR_KIND_WHERE,
"WHERE");
/* we have to fix its collations too */
assign_expr_collations(pstate, *whereClause);
+ /* this is probably dead code without add_missing_from: */
if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("rule WHERE condition cannot contain references to other relations")));
- /* aggregates not allowed (but subselects are okay) */
- if (pstate->p_hasAggs)
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in rule WHERE condition")));
- if (pstate->p_hasWindowFuncs)
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("cannot use window function in rule WHERE condition")));
-
/*
* 'instead nothing' rules with a qualification need a query rangetable so
* the rewrite handler can add the negated rule qualification to the
@@ -2693,9 +2741,14 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
* Returns a List of utility commands to be done in sequence. One of these
* will be the transformed AlterTableStmt, but there may be additional actions
* to be done before and after the actual AlterTable() call.
+ *
+ * To avoid race conditions, it's important that this function rely only on
+ * the passed-in relid (and not on stmt->relation) to determine the target
+ * relation.
*/
List *
-transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
+transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
+ const char *queryString)
{
Relation rel;
ParseState *pstate;
@@ -2707,7 +2760,6 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
List *newcmds = NIL;
bool skipValidation = true;
AlterTableCmd *newcmd;
- LOCKMODE lockmode;
/*
* We must not scribble on the passed-in AlterTableStmt, so copy it. (This
@@ -2715,36 +2767,24 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
*/
stmt = (AlterTableStmt *) copyObject(stmt);
- /*
- * Determine the appropriate lock level for this list of subcommands.
- */
- lockmode = AlterTableGetLockLevel(stmt->cmds);
-
- /*
- * Acquire appropriate lock on the target relation, which will be held
- * until end of transaction. This ensures any decisions we make here
- * based on the state of the relation will still be good at execution. We
- * must get lock now because execution will later require it; taking a
- * lower grade lock now and trying to upgrade later risks deadlock. Any
- * new commands we add after this must not upgrade the lock level
- * requested here.
- */
- rel = relation_openrv_extended(stmt->relation, lockmode, stmt->missing_ok);
- if (rel == NULL)
- {
- /* this message is consistent with relation_openrv */
- ereport(NOTICE,
- (errmsg("relation \"%s\" does not exist, skipping",
- stmt->relation->relname)));
- return NIL;
- }
+ /* Caller is responsible for locking the relation */
+ rel = relation_open(relid, NoLock);
/* Set up pstate and CreateStmtContext */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
cxt.pstate = pstate;
- cxt.stmtType = "ALTER TABLE";
+ if (stmt->relkind == OBJECT_FOREIGN_TABLE)
+ {
+ cxt.stmtType = "ALTER FOREIGN TABLE";
+ cxt.isforeign = true;
+ }
+ else
+ {
+ cxt.stmtType = "ALTER TABLE";
+ cxt.isforeign = false;
+ }
cxt.relation = stmt->relation;
cxt.rel = rel;
cxt.inhRelations = NIL;
@@ -2862,7 +2902,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
IndexStmt *idxstmt = (IndexStmt *) lfirst(l);
Assert(IsA(idxstmt, IndexStmt));
- idxstmt = transformIndexStmt(idxstmt, queryString);
+ idxstmt = transformIndexStmt(relid, idxstmt, queryString);
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
newcmd->def = (Node *) idxstmt;
@@ -2886,7 +2926,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
newcmds = lappend(newcmds, newcmd);
}
- /* Close rel but keep lock */
+ /* Close rel */
relation_close(rel, NoLock);
/*
@@ -3080,7 +3120,7 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
* that the logic we use for determining forward references is
* presently quite incomplete.
*
- * SQL92 also allows constraints to make forward references, so thumb through
+ * SQL also allows constraints to make forward references, so thumb through
* the table columns and move forward references to a posterior alter-table
* command.
*
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index f9ec2b220a..663296683a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -10,7 +10,7 @@
* analyze.c and related files.
*
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
@@ -65,7 +65,7 @@ raw_parser(const char *str)
* Intermediate filter between parser and core lexer (core_yylex in scan.l).
*
* The filter is needed because in some cases the standard SQL grammar
- * requires more than one token lookahead. We reduce these cases to one-token
+ * requires more than one token lookahead. We reduce these cases to one-token
* lookahead by combining tokens here, in order to keep the grammar LALR(1).
*
* Using a filter is simpler than trying to recognize multiword tokens
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case WITH:
/*
- * WITH TIME must be reduced to one token
+ * WITH TIME and WITH ORDINALITY must each be reduced to one token
*/
cur_yylval = lvalp->core_yystype;
cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case TIME:
cur_token = WITH_TIME;
break;
+ case ORDINALITY:
+ cur_token = WITH_ORDINALITY;
+ break;
default:
/* save the lookahead token for next time */
yyextra->lookahead_token = next_token;
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index 6b216b5378..146ae3db2d 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -21,7 +21,7 @@
* Postgres 9.2, this check is made automatically by the Makefile.)
*
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
@@ -42,7 +42,13 @@
/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
#undef fprintf
-#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
+#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg)
+
+static void
+fprintf_to_ereport(const char *fmt, const char *msg)
+{
+ ereport(ERROR, (errmsg_internal("%s", msg)));
+}
/*
* GUC variables. This is a DIRECT violation of the warning given at the
@@ -91,6 +97,7 @@ static bool is_utf16_surrogate_first(pg_wchar c);
static bool is_utf16_surrogate_second(pg_wchar c);
static pg_wchar surrogate_pair_to_codepoint(pg_wchar first, pg_wchar second);
static void addunicode(pg_wchar c, yyscan_t yyscanner);
+static bool check_uescapechar(unsigned char escape);
#define yyerror(msg) scanner_yyerror(msg, yyscanner)
@@ -144,8 +151,13 @@ extern void core_yyset_column(int column_no, yyscan_t yyscanner);
* <xe> extended quoted strings (support backslash escape sequences)
* <xdolq> $foo$ quoted strings
* <xui> quoted identifier with Unicode escapes
+ * <xuiend> end of a quoted identifier with Unicode escapes, UESCAPE can follow
* <xus> quoted string with Unicode escapes
+ * <xusend> end of a quoted string with Unicode escapes, UESCAPE can follow
* <xeu> Unicode surrogate pair in extended quoted string
+ *
+ * Remember to add an <<EOF>> case whenever you add a new exclusive state!
+ * The default one is probably not the right thing.
*/
%x xb
@@ -156,7 +168,9 @@ extern void core_yyset_column(int column_no, yyscan_t yyscanner);
%x xq
%x xdolq
%x xui
+%x xuiend
%x xus
+%x xusend
%x xeu
/*
@@ -273,17 +287,17 @@ xdinside [^"]+
/* Unicode escapes */
uescape [uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}[^']{quote}
/* error rule to avoid backup */
-uescapefail ("-"|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*"-"|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}[^']|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*|[uU][eE][sS][cC][aA][pP]|[uU][eE][sS][cC][aA]|[uU][eE][sS][cC]|[uU][eE][sS]|[uU][eE]|[uU])
+uescapefail [uU][eE][sS][cC][aA][pP][eE]{whitespace}*"-"|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}[^']|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*{quote}|[uU][eE][sS][cC][aA][pP][eE]{whitespace}*|[uU][eE][sS][cC][aA][pP]|[uU][eE][sS][cC][aA]|[uU][eE][sS][cC]|[uU][eE][sS]|[uU][eE]|[uU]
/* Quoted identifier with Unicode escapes */
xuistart [uU]&{dquote}
-xuistop1 {dquote}{whitespace}*{uescapefail}?
-xuistop2 {dquote}{whitespace}*{uescape}
/* Quoted string with Unicode escapes */
xusstart [uU]&{quote}
-xusstop1 {quote}{whitespace}*{uescapefail}?
-xusstop2 {quote}{whitespace}*{uescape}
+
+/* Optional UESCAPE after a quoted string or identifier with Unicode escapes. */
+xustop1 {uescapefail}?
+xustop2 {uescape}
/* error rule to avoid backup */
xufailed [uU]&
@@ -530,15 +544,32 @@ other .
yylval->str = litbufdup(yyscanner);
return SCONST;
}
-<xus>{xusstop1} {
+<xus>{quotestop} |
+<xus>{quotefail} {
/* throw back all but the quote */
yyless(1);
+ /* xusend state looks for possible UESCAPE */
+ BEGIN(xusend);
+ }
+<xusend>{whitespace} { /* stay in xusend state over whitespace */ }
+<xusend>{other} |
+<xusend>{xustop1} |
+<xusend><<EOF>> {
+ /* no UESCAPE after the quote, throw back everything */
+ yyless(0);
BEGIN(INITIAL);
yylval->str = litbuf_udeescape('\\', yyscanner);
return SCONST;
}
-<xus>{xusstop2} {
+<xusend>{xustop2} {
+ /* found UESCAPE after the end quote */
BEGIN(INITIAL);
+ if (!check_uescapechar(yytext[yyleng-2]))
+ {
+ SET_YYLLOC();
+ ADVANCE_YYLLOC(yyleng-2);
+ yyerror("invalid Unicode escape character");
+ }
yylval->str = litbuf_udeescape(yytext[yyleng-2], yyscanner);
return SCONST;
}
@@ -696,29 +727,49 @@ other .
yylval->str = ident;
return IDENT;
}
-<xui>{xuistop1} {
- char *ident;
+<xui>{dquote} {
+ yyless(1);
+ /* xuiend state looks for possible UESCAPE */
+ BEGIN(xuiend);
+ }
+<xuiend>{whitespace} { /* stay in xuiend state over whitespace */ }
+<xuiend>{other} |
+<xuiend>{xustop1} |
+<xuiend><<EOF>> {
+ /* no UESCAPE after the quote, throw back everything */
+ char *ident;
+ int identlen;
+
+ yyless(0);
BEGIN(INITIAL);
if (yyextra->literallen == 0)
yyerror("zero-length delimited identifier");
ident = litbuf_udeescape('\\', yyscanner);
- if (yyextra->literallen >= NAMEDATALEN)
- truncate_identifier(ident, yyextra->literallen, true);
+ identlen = strlen(ident);
+ if (identlen >= NAMEDATALEN)
+ truncate_identifier(ident, identlen, true);
yylval->str = ident;
- /* throw back all but the quote */
- yyless(1);
return IDENT;
}
-<xui>{xuistop2} {
- char *ident;
+<xuiend>{xustop2} {
+ /* found UESCAPE after the end quote */
+ char *ident;
+ int identlen;
BEGIN(INITIAL);
if (yyextra->literallen == 0)
yyerror("zero-length delimited identifier");
+ if (!check_uescapechar(yytext[yyleng-2]))
+ {
+ SET_YYLLOC();
+ ADVANCE_YYLLOC(yyleng-2);
+ yyerror("invalid Unicode escape character");
+ }
ident = litbuf_udeescape(yytext[yyleng - 2], yyscanner);
- if (yyextra->literallen >= NAMEDATALEN)
- truncate_identifier(ident, yyextra->literallen, true);
+ identlen = strlen(ident);
+ if (identlen >= NAMEDATALEN)
+ truncate_identifier(ident, identlen, true);
yylval->str = ident;
return IDENT;
}
@@ -1197,22 +1248,29 @@ addunicode(pg_wchar c, core_yyscan_t yyscanner)
addlit(buf, pg_mblen(buf), yyscanner);
}
-static char *
-litbuf_udeescape(unsigned char escape, core_yyscan_t yyscanner)
+/* is 'escape' acceptable as Unicode escape character (UESCAPE syntax) ? */
+static bool
+check_uescapechar(unsigned char escape)
{
- char *new;
- char *litbuf, *in, *out;
- pg_wchar pair_first = 0;
-
if (isxdigit(escape)
|| escape == '+'
|| escape == '\''
|| escape == '"'
|| scanner_isspace(escape))
{
- ADVANCE_YYLLOC(yyextra->literallen + yyleng + 1);
- yyerror("invalid Unicode escape character");
+ return false;
}
+ else
+ return true;
+}
+
+/* like litbufdup, but handle unicode escapes */
+static char *
+litbuf_udeescape(unsigned char escape, core_yyscan_t yyscanner)
+{
+ char *new;
+ char *litbuf, *in, *out;
+ pg_wchar pair_first = 0;
/* Make literalbuf null-terminated to simplify the scanning loop */
litbuf = yyextra->literalbuf;
diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c
index 6101457a10..e9fa5dd0b0 100644
--- a/src/backend/parser/scansup.c
+++ b/src/backend/parser/scansup.c
@@ -4,7 +4,7 @@
* support routines for the lex/flex scanner, used by both the normal
* backend as well as the bootstrap backend
*
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
@@ -56,6 +56,8 @@ scanstr(const char *s)
* appear in pairs, so there should be another character.
*/
i++;
+ /* The bootstrap parser is not as smart, so check here. */
+ Assert(s[i] == '\'');
newStr[j] = s[i];
}
else if (s[i] == '\\')
@@ -130,8 +132,10 @@ downcase_truncate_identifier(const char *ident, int len, bool warn)
{
char *result;
int i;
+ bool enc_is_single_byte;
result = palloc(len + 1);
+ enc_is_single_byte = pg_database_encoding_max_length() == 1;
/*
* SQL99 specifies Unicode-aware case normalization, which we don't yet
@@ -139,8 +143,8 @@ downcase_truncate_identifier(const char *ident, int len, bool warn)
* locale-aware translation. However, there are some locales where this
* is not right either (eg, Turkish may do strange things with 'i' and
* 'I'). Our current compromise is to use tolower() for characters with
- * the high bit set, and use an ASCII-only downcasing for 7-bit
- * characters.
+ * the high bit set, as long as they aren't part of a multi-byte
+ * character, and use an ASCII-only downcasing for 7-bit characters.
*/
for (i = 0; i < len; i++)
{
@@ -148,7 +152,7 @@ downcase_truncate_identifier(const char *ident, int len, bool warn)
if (ch >= 'A' && ch <= 'Z')
ch += 'a' - 'A';
- else if (IS_HIGHBIT_SET(ch) && isupper(ch))
+ else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch))
ch = tolower(ch);
result[i] = (char) ch;
}