diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index fdcc3920cec..64a3272b9cf 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -1583,6 +1583,10 @@ select f2 from test_json_agg;
"d"=>NULL, "x"=>"xyzzy"
(3 rows)
+-- Test subscripting in plpgsql
+do $$ declare h hstore;
+begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$;
+NOTICE: h = "a"=>"b", h[a] = b
-- Check the hstore_hash() and hstore_hash_extended() function explicitly.
SELECT v as value, hstore_hash(v)::bit(32) as standard,
hstore_hash_extended(v, 0)::bit(32) as extended0,
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index 8d96e304030..a59db66b0ae 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -372,6 +372,10 @@ select f2['d':'e'] from test_json_agg; -- error
update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy';
select f2 from test_json_agg;
+-- Test subscripting in plpgsql
+do $$ declare h hstore;
+begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$;
+
-- Check the hstore_hash() and hstore_hash_extended() function explicitly.
SELECT v as value, hstore_hash(v)::bit(32) as standard,
hstore_hash_extended(v, 0)::bit(32) as extended0,
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 11246aa6534..45d3e43ed14 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -946,8 +946,8 @@ PREPARE statement_name(integer, integer) AS SELECT $1
database engine. The expression must yield a single value (possibly
a row value, if the variable is a row or record variable). The target
variable can be a simple variable (optionally qualified with a block
- name), a field of a row or record variable, or an element of an array
- that is a simple variable or field. Equal (=) can be
+ name), a field of a row or record target, or an element or slice of
+ an array target. Equal (=) can be
used instead of PL/SQL-compliant :=.
@@ -968,8 +968,25 @@ PREPARE statement_name(integer, integer) AS SELECT $1
tax := subtotal * 0.06;
my_record.user_id := 20;
+my_array[j] := 20;
+my_array[1:3] := array[1,2,3];
+complex_array[n].realpart = 12.3;
+
+
+ It's useful to know that what follows the assignment operator is
+ essentially treated as a SELECT command; as long
+ as it returns a single row and column, it will work. Thus for example
+ one can write something like
+
+total_sales := sum(quantity) from sales;
+
+ This provides an effect similar to the single-row SELECT
+ ... INTO syntax described in
+ . However, that syntax
+ is more portable.
+
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 55950e754d2..7a4e104623b 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1628,6 +1628,7 @@ CreateCast(CreateCastStmt *stmt)
case COERCION_ASSIGNMENT:
castcontext = COERCION_CODE_ASSIGNMENT;
break;
+ /* COERCION_PLPGSQL is intentionally not covered here */
case COERCION_EXPLICIT:
castcontext = COERCION_CODE_EXPLICIT;
break;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 6c0593686a9..e28d2429222 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -51,6 +51,12 @@ static _SPI_connection *_SPI_current = NULL;
static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1; /* current stack index */
+typedef struct SPICallbackArg
+{
+ const char *query;
+ RawParseMode mode;
+} SPICallbackArg;
+
static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
ParamListInfo paramLI, bool read_only);
@@ -1479,6 +1485,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
/*
@@ -1533,8 +1540,10 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
* Setup error traceback support for ereport(), in case GetCachedPlan
* throws an error.
*/
+ spicallbackarg.query = plansource->query_string;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, plansource->query_string);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -1952,6 +1961,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
{
CachedPlanSource *plansource;
CachedPlan *cplan;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
Assert(plan->magic == _SPI_PLAN_MAGIC);
@@ -1966,8 +1976,10 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Setup error traceback support for ereport() */
+ spicallbackarg.query = plansource->query_string;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, plansource->query_string);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2094,13 +2106,16 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
List *raw_parsetree_list;
List *plancache_list;
ListCell *list_item;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
/*
* Setup error traceback support for ereport()
*/
+ spicallbackarg.query = src;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, src);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2199,13 +2214,16 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
List *raw_parsetree_list;
List *plancache_list;
ListCell *list_item;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
/*
* Setup error traceback support for ereport()
*/
+ spicallbackarg.query = src;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, src);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2263,6 +2281,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
SPITupleTable *my_tuptable = NULL;
int res = 0;
bool pushed_active_snap = false;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
CachedPlan *cplan = NULL;
ListCell *lc1;
@@ -2270,8 +2289,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
/*
* Setup error traceback support for ereport()
*/
+ spicallbackarg.query = NULL; /* we'll fill this below */
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = NULL; /* we'll fill this below */
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2318,7 +2339,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
List *stmt_list;
ListCell *lc2;
- spierrcontext.arg = unconstify(char *, plansource->query_string);
+ spicallbackarg.query = plansource->query_string;
/*
* If this is a one-shot plan, we still need to do parse analysis.
@@ -2722,7 +2743,8 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
static void
_SPI_error_callback(void *arg)
{
- const char *query = (const char *) arg;
+ SPICallbackArg *carg = (SPICallbackArg *) arg;
+ const char *query = carg->query;
int syntaxerrposition;
if (query == NULL) /* in case arg wasn't set yet */
@@ -2740,7 +2762,23 @@ _SPI_error_callback(void *arg)
internalerrquery(query);
}
else
- errcontext("SQL statement \"%s\"", query);
+ {
+ /* Use the parse mode to decide how to describe the query */
+ switch (carg->mode)
+ {
+ case RAW_PARSE_PLPGSQL_EXPR:
+ errcontext("SQL expression \"%s\"", query);
+ break;
+ case RAW_PARSE_PLPGSQL_ASSIGN1:
+ case RAW_PARSE_PLPGSQL_ASSIGN2:
+ case RAW_PARSE_PLPGSQL_ASSIGN3:
+ errcontext("PL/pgSQL assignment \"%s\"", query);
+ break;
+ default:
+ errcontext("SQL statement \"%s\"", query);
+ break;
+ }
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67d45418662..ba3ccc712c8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3199,6 +3199,20 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static PLAssignStmt *
+_copyPLAssignStmt(const PLAssignStmt *from)
+{
+ PLAssignStmt *newnode = makeNode(PLAssignStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(indirection);
+ COPY_SCALAR_FIELD(nnames);
+ COPY_NODE_FIELD(val);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -5220,6 +5234,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_PLAssignStmt:
+ retval = _copyPLAssignStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4d4258b0cb2..a2ef853dc2a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1085,6 +1085,18 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(indirection);
+ COMPARE_SCALAR_FIELD(nnames);
+ COMPARE_NODE_FIELD(val);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -3275,6 +3287,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_PLAssignStmt:
+ retval = _equalPLAssignStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index a011bc7b98d..6be19916fce 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3669,6 +3669,16 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_PLAssignStmt:
+ {
+ PLAssignStmt *stmt = (PLAssignStmt *) node;
+
+ if (walker(stmt->indirection, context))
+ return true;
+ if (walker(stmt->val, context))
+ return true;
+ }
+ break;
case T_A_Expr:
{
A_Expr *expr = (A_Expr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6b8ec03fa77..8392be6d44a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2775,6 +2775,18 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
+{
+ WRITE_NODE_TYPE("PLASSIGN");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(indirection);
+ WRITE_INT_FIELD(nnames);
+ WRITE_NODE_FIELD(val);
+ WRITE_LOCATION_FIELD(location);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -4211,6 +4223,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_PLAssignStmt:
+ _outPLAssignStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 1066f9458f3..28e192f51c8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -42,8 +42,10 @@
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
+#include "parser/parse_type.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
#include "utils/rel.h"
@@ -70,6 +72,8 @@ static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
List *targetList);
+static Query *transformPLAssignStmt(ParseState *pstate,
+ PLAssignStmt *stmt);
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
@@ -304,6 +308,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_PLAssignStmt:
+ result = transformPLAssignStmt(pstate,
+ (PLAssignStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -367,6 +376,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_DeleteStmt:
case T_UpdateStmt:
case T_SelectStmt:
+ case T_PLAssignStmt:
result = true;
break;
@@ -2393,6 +2403,236 @@ transformReturningList(ParseState *pstate, List *returningList)
}
+/*
+ * transformPLAssignStmt -
+ * transform a PL/pgSQL assignment statement
+ *
+ * If there is no opt_indirection, the transformed statement looks like
+ * "SELECT a_expr ...", except the expression has been cast to the type of
+ * the target. With indirection, it's still a SELECT, but the expression will
+ * incorporate FieldStore and/or assignment SubscriptingRef nodes to compute a
+ * new value for a container-type variable represented by the target. The
+ * expression references the target as the container source.
+ */
+static Query *
+transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ ColumnRef *cref = makeNode(ColumnRef);
+ List *indirection = stmt->indirection;
+ int nnames = stmt->nnames;
+ SelectStmt *sstmt = stmt->val;
+ Node *target;
+ Oid targettype;
+ int32 targettypmod;
+ Oid targetcollation;
+ List *tlist;
+ TargetEntry *tle;
+ Oid type_id;
+ Node *qual;
+ ListCell *l;
+
+ /*
+ * First, construct a ColumnRef for the target variable. If the target
+ * has more than one dotted name, we have to pull the extra names out of
+ * the indirection list.
+ */
+ cref->fields = list_make1(makeString(stmt->name));
+ cref->location = stmt->location;
+ if (nnames > 1)
+ {
+ /* avoid munging the raw parsetree */
+ indirection = list_copy(indirection);
+ while (--nnames > 0 && indirection != NIL)
+ {
+ Node *ind = (Node *) linitial(indirection);
+
+ if (!IsA(ind, String))
+ elog(ERROR, "invalid name count in PLAssignStmt");
+ cref->fields = lappend(cref->fields, ind);
+ indirection = list_delete_first(indirection);
+ }
+ }
+
+ /*
+ * Transform the target reference. Typically we will get back a Param
+ * node, but there's no reason to be too picky about its type.
+ */
+ target = transformExpr(pstate, (Node *) cref,
+ EXPR_KIND_UPDATE_TARGET);
+ targettype = exprType(target);
+ targettypmod = exprTypmod(target);
+ targetcollation = exprCollation(target);
+
+ /*
+ * The rest mostly matches transformSelectStmt, except that we needn't
+ * consider WITH or DISTINCT, and we build a targetlist our own way.
+ */
+ qry->commandType = CMD_SELECT;
+ pstate->p_is_insert = false;
+
+ /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
+ pstate->p_locking_clause = sstmt->lockingClause;
+
+ /* make WINDOW info available for window functions, too */
+ pstate->p_windowdefs = sstmt->windowClause;
+
+ /* process the FROM clause */
+ transformFromClause(pstate, sstmt->fromClause);
+
+ /* initially transform the targetlist as if in SELECT */
+ tlist = transformTargetList(pstate, sstmt->targetList,
+ EXPR_KIND_SELECT_TARGET);
+
+ /* we should have exactly one targetlist item */
+ if (list_length(tlist) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("assignment source returned %d column",
+ "assignment source returned %d columns",
+ list_length(tlist),
+ list_length(tlist))));
+
+ tle = linitial_node(TargetEntry, tlist);
+
+ /*
+ * This next bit is similar to transformAssignedExpr; the key difference
+ * is we use COERCION_PLPGSQL not COERCION_ASSIGNMENT.
+ */
+ type_id = exprType((Node *) tle->expr);
+
+ pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
+
+ if (indirection)
+ {
+ tle->expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ target,
+ stmt->name,
+ false,
+ targettype,
+ targettypmod,
+ targetcollation,
+ indirection,
+ list_head(indirection),
+ (Node *) tle->expr,
+ COERCION_PLPGSQL,
+ exprLocation(target));
+ }
+ else if (targettype != type_id &&
+ (targettype == RECORDOID || ISCOMPLEX(targettype)) &&
+ (type_id == RECORDOID || ISCOMPLEX(type_id)))
+ {
+ /*
+ * Hack: do not let coerce_to_target_type() deal with inconsistent
+ * composite types. Just pass the expression result through as-is,
+ * and let the PL/pgSQL executor do the conversion its way. This is
+ * rather bogus, but it's needed for backwards compatibility.
+ */
+ }
+ else
+ {
+ /*
+ * For normal non-qualified target column, do type checking and
+ * coercion.
+ */
+ Node *orig_expr = (Node *) tle->expr;
+
+ tle->expr = (Expr *)
+ coerce_to_target_type(pstate,
+ orig_expr, type_id,
+ targettype, targettypmod,
+ COERCION_PLPGSQL,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ /* With COERCION_PLPGSQL, this error is probably unreachable */
+ if (tle->expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s\" is of type %s"
+ " but expression is of type %s",
+ stmt->name,
+ format_type_be(targettype),
+ format_type_be(type_id)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(pstate, exprLocation(orig_expr))));
+ }
+
+ pstate->p_expr_kind = EXPR_KIND_NONE;
+
+ qry->targetList = list_make1(tle);
+
+ /* transform WHERE */
+ qual = transformWhereClause(pstate, sstmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ /* initial processing of HAVING clause is much like WHERE clause */
+ qry->havingQual = transformWhereClause(pstate, sstmt->havingClause,
+ EXPR_KIND_HAVING, "HAVING");
+
+ /*
+ * Transform sorting/grouping stuff. Do ORDER BY first because both
+ * transformGroupClause and transformDistinctClause need the results. Note
+ * that these functions can also change the targetList, so it's passed to
+ * them by reference.
+ */
+ qry->sortClause = transformSortClause(pstate,
+ sstmt->sortClause,
+ &qry->targetList,
+ EXPR_KIND_ORDER_BY,
+ false /* allow SQL92 rules */ );
+
+ qry->groupClause = transformGroupClause(pstate,
+ sstmt->groupClause,
+ &qry->groupingSets,
+ &qry->targetList,
+ qry->sortClause,
+ EXPR_KIND_GROUP_BY,
+ false /* allow SQL92 rules */ );
+
+ /* No DISTINCT clause */
+ Assert(!sstmt->distinctClause);
+ qry->distinctClause = NIL;
+ qry->hasDistinctOn = false;
+
+ /* transform LIMIT */
+ qry->limitOffset = transformLimitClause(pstate, sstmt->limitOffset,
+ EXPR_KIND_OFFSET, "OFFSET",
+ sstmt->limitOption);
+ qry->limitCount = transformLimitClause(pstate, sstmt->limitCount,
+ EXPR_KIND_LIMIT, "LIMIT",
+ sstmt->limitOption);
+ qry->limitOption = sstmt->limitOption;
+
+ /* transform window clauses after we have seen all window functions */
+ qry->windowClause = transformWindowDefinitions(pstate,
+ pstate->p_windowdefs,
+ &qry->targetList);
+
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ foreach(l, sstmt->lockingClause)
+ {
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
+ }
+
+ assign_query_collations(pstate, qry);
+
+ /* this must be done after collations, for reliable comparison of exprs */
+ if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
+ parseCheckAggregates(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformDeclareCursorStmt -
* transform a DECLARE CURSOR Statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fb025f08a4e..31c95443a5b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -294,6 +294,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type select_no_parens select_with_parens select_clause
simple_select values_clause
+ PLpgSQL_Expr PLAssignStmt
%type alter_column_default opclass_item opclass_drop alter_using
%type add_drop opt_asc_desc opt_nulls_order
@@ -535,7 +536,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type ColId ColLabel BareColLabel
%type NonReservedWord NonReservedWord_or_Sconst
%type var_name type_function_name param_name
-%type createdb_opt_name
+%type createdb_opt_name plassign_target
%type var_value zone_value
%type auth_ident RoleSpec opt_granted_by
@@ -731,6 +732,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* something other than the usual list of SQL commands.
*/
%token MODE_TYPE_NAME
+%token MODE_PLPGSQL_EXPR
+%token MODE_PLPGSQL_ASSIGN1
+%token MODE_PLPGSQL_ASSIGN2
+%token MODE_PLPGSQL_ASSIGN3
/* Precedence: lowest to highest */
@@ -810,6 +815,32 @@ parse_toplevel:
{
pg_yyget_extra(yyscanner)->parsetree = list_make1($2);
}
+ | MODE_PLPGSQL_EXPR PLpgSQL_Expr
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_ASSIGN1 PLAssignStmt
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+ n->nnames = 1;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_ASSIGN2 PLAssignStmt
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+ n->nnames = 2;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_ASSIGN3 PLAssignStmt
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+ n->nnames = 3;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
;
/*
@@ -15024,6 +15055,72 @@ role_list: RoleSpec
{ $$ = lappend($1, $3); }
;
+
+/*****************************************************************************
+ *
+ * PL/pgSQL extensions
+ *
+ * You'd think a PL/pgSQL "expression" should be just an a_expr, but
+ * historically it can include just about anything that can follow SELECT.
+ * Therefore the returned struct is a SelectStmt.
+ *****************************************************************************/
+
+PLpgSQL_Expr: opt_target_list
+ from_clause where_clause
+ group_clause having_clause window_clause
+ opt_sort_clause opt_select_limit opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ n->fromClause = $2;
+ n->whereClause = $3;
+ n->groupClause = $4;
+ n->havingClause = $5;
+ n->windowClause = $6;
+ n->sortClause = $7;
+ if ($8)
+ {
+ n->limitOffset = $8->limitOffset;
+ n->limitCount = $8->limitCount;
+ if (!n->sortClause &&
+ $8->limitOption == LIMIT_OPTION_WITH_TIES)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("WITH TIES cannot be specified without ORDER BY clause")));
+ n->limitOption = $8->limitOption;
+ }
+ n->lockingClause = $9;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*
+ * PL/pgSQL Assignment statement: name opt_indirection := PLpgSQL_Expr
+ */
+
+PLAssignStmt: plassign_target opt_indirection plassign_equals PLpgSQL_Expr
+ {
+ PLAssignStmt *n = makeNode(PLAssignStmt);
+
+ n->name = $1;
+ n->indirection = check_indirection($2, yyscanner);
+ /* nnames will be filled by calling production */
+ n->val = (SelectStmt *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plassign_target: ColId { $$ = $1; }
+ | PARAM { $$ = psprintf("$%d", $1); }
+ ;
+
+plassign_equals: COLON_EQUALS
+ | '='
+ ;
+
+
/*
* Name classification hierarchy.
*
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 74eb39c0e4d..d5310f27db1 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -3098,6 +3098,14 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
}
}
+ /*
+ * When parsing PL/pgSQL assignments, allow an I/O cast to be used
+ * whenever no normal coercion is available.
+ */
+ if (result == COERCION_PATH_NONE &&
+ ccontext == COERCION_PLPGSQL)
+ result = COERCION_PATH_COERCEVIAIO;
+
return result;
}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index fdf5500bf20..7eaa076771a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -34,17 +34,6 @@
static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
Var *var, int levelsup);
-static Node *transformAssignmentIndirection(ParseState *pstate,
- Node *basenode,
- const char *targetName,
- bool targetIsSubscripting,
- Oid targetTypeId,
- int32 targetTypMod,
- Oid targetCollation,
- List *indirection,
- ListCell *indirection_cell,
- Node *rhs,
- int location);
static Node *transformAssignmentSubscripts(ParseState *pstate,
Node *basenode,
const char *targetName,
@@ -56,6 +45,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
List *indirection,
ListCell *next_indirection,
Node *rhs,
+ CoercionContext ccontext,
int location);
static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
bool make_target_entry);
@@ -561,6 +551,7 @@ transformAssignedExpr(ParseState *pstate,
indirection,
list_head(indirection),
(Node *) expr,
+ COERCION_ASSIGNMENT,
location);
}
else
@@ -642,15 +633,15 @@ updateTargetListEntry(ParseState *pstate,
/*
* Process indirection (field selection or subscripting) of the target
- * column in INSERT/UPDATE. This routine recurses for multiple levels
- * of indirection --- but note that several adjacent A_Indices nodes in
- * the indirection list are treated as a single multidimensional subscript
+ * column in INSERT/UPDATE/assignment. This routine recurses for multiple
+ * levels of indirection --- but note that several adjacent A_Indices nodes
+ * in the indirection list are treated as a single multidimensional subscript
* operation.
*
* In the initial call, basenode is a Var for the target column in UPDATE,
- * or a null Const of the target's type in INSERT. In recursive calls,
- * basenode is NULL, indicating that a substitute node should be consed up if
- * needed.
+ * or a null Const of the target's type in INSERT, or a Param for the target
+ * variable in PL/pgSQL assignment. In recursive calls, basenode is NULL,
+ * indicating that a substitute node should be consed up if needed.
*
* targetName is the name of the field or subfield we're assigning to, and
* targetIsSubscripting is true if we're subscripting it. These are just for
@@ -667,12 +658,16 @@ updateTargetListEntry(ParseState *pstate,
* rhs is the already-transformed value to be assigned; note it has not been
* coerced to any particular type.
*
+ * ccontext is the coercion level to use while coercing the rhs. For
+ * normal statements it'll be COERCION_ASSIGNMENT, but PL/pgSQL uses
+ * a special value.
+ *
* location is the cursor error position for any errors. (Note: this points
* to the head of the target clause, eg "foo" in "foo.bar[baz]". Later we
* might want to decorate indirection cells with their own location info,
* in which case the location argument could probably be dropped.)
*/
-static Node *
+Node *
transformAssignmentIndirection(ParseState *pstate,
Node *basenode,
const char *targetName,
@@ -683,6 +678,7 @@ transformAssignmentIndirection(ParseState *pstate,
List *indirection,
ListCell *indirection_cell,
Node *rhs,
+ CoercionContext ccontext,
int location)
{
Node *result;
@@ -757,6 +753,7 @@ transformAssignmentIndirection(ParseState *pstate,
indirection,
i,
rhs,
+ ccontext,
location);
}
@@ -807,6 +804,7 @@ transformAssignmentIndirection(ParseState *pstate,
indirection,
lnext(indirection, i),
rhs,
+ ccontext,
location);
/* and build a FieldStore node */
@@ -845,6 +843,7 @@ transformAssignmentIndirection(ParseState *pstate,
indirection,
NULL,
rhs,
+ ccontext,
location);
}
@@ -853,7 +852,7 @@ transformAssignmentIndirection(ParseState *pstate,
result = coerce_to_target_type(pstate,
rhs, exprType(rhs),
targetTypeId, targetTypMod,
- COERCION_ASSIGNMENT,
+ ccontext,
COERCE_IMPLICIT_CAST,
-1);
if (result == NULL)
@@ -898,6 +897,7 @@ transformAssignmentSubscripts(ParseState *pstate,
List *indirection,
ListCell *next_indirection,
Node *rhs,
+ CoercionContext ccontext,
int location)
{
Node *result;
@@ -949,6 +949,7 @@ transformAssignmentSubscripts(ParseState *pstate,
indirection,
next_indirection,
rhs,
+ ccontext,
location);
/*
@@ -969,7 +970,7 @@ transformAssignmentSubscripts(ParseState *pstate,
result = coerce_to_target_type(pstate,
result, resulttype,
targetTypeId, targetTypMod,
- COERCION_ASSIGNMENT,
+ ccontext,
COERCE_IMPLICIT_CAST,
-1);
/* can fail if we had int2vector/oidvector, but not for true domains */
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 8eb8feb372e..875de7ba28f 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -57,7 +57,11 @@ raw_parser(const char *str, RawParseMode mode)
/* this array is indexed by RawParseMode enum */
static const int mode_token[] = {
0, /* RAW_PARSE_DEFAULT */
- MODE_TYPE_NAME /* RAW_PARSE_TYPE_NAME */
+ MODE_TYPE_NAME, /* RAW_PARSE_TYPE_NAME */
+ MODE_PLPGSQL_EXPR, /* RAW_PARSE_PLPGSQL_EXPR */
+ MODE_PLPGSQL_ASSIGN1, /* RAW_PARSE_PLPGSQL_ASSIGN1 */
+ MODE_PLPGSQL_ASSIGN2, /* RAW_PARSE_PLPGSQL_ASSIGN2 */
+ MODE_PLPGSQL_ASSIGN3 /* RAW_PARSE_PLPGSQL_ASSIGN3 */
};
yyextra.have_lookahead = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5825d3d8142..53a511f1da8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2313,6 +2313,10 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_SELECT;
break;
+ case T_PLAssignStmt:
+ tag = CMDTAG_SELECT;
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -3181,6 +3185,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_ALL;
break;
+ case T_PLAssignStmt:
+ lev = LOGSTMT_ALL;
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
lev = LOGSTMT_ALL;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fa28b21821f..caed683ba92 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -314,6 +314,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_PLAssignStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a0f37e52687..dc2bb40926a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1675,6 +1675,25 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/* ----------------------
+ * PL/pgSQL Assignment Statement
+ *
+ * Like SelectStmt, this is transformed into a SELECT Query.
+ * However, the targetlist of the result looks more like an UPDATE.
+ * ----------------------
+ */
+typedef struct PLAssignStmt
+{
+ NodeTag type;
+
+ char *name; /* initial column name */
+ List *indirection; /* subscripts and field names, if any */
+ int nnames; /* number of names to use in ColumnRef */
+ SelectStmt *val; /* the PL/pgSQL expression to assign */
+ int location; /* name's token location, or -1 if unknown */
+} PLAssignStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index e51be4bce8f..d4ce0370884 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -457,6 +457,7 @@ typedef enum CoercionContext
{
COERCION_IMPLICIT, /* coercion in context of expression */
COERCION_ASSIGNMENT, /* coercion in context of assignment */
+ COERCION_PLPGSQL, /* if no assignment cast, use CoerceViaIO */
COERCION_EXPLICIT /* explicit cast operation */
} CoercionContext;
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index 9d3637747d4..1a7b1a92776 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -36,6 +36,18 @@ extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle,
char *colname, int attrno,
List *indirection,
int location);
+extern Node *transformAssignmentIndirection(ParseState *pstate,
+ Node *basenode,
+ const char *targetName,
+ bool targetIsSubscripting,
+ Oid targetTypeId,
+ int32 targetTypMod,
+ Oid targetCollation,
+ List *indirection,
+ ListCell *indirection_cell,
+ Node *rhs,
+ CoercionContext ccontext,
+ int location);
extern List *checkInsertTargets(ParseState *pstate, List *cols,
List **attrnos);
extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index 80d90027cc4..853b0f16061 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -27,12 +27,21 @@
* RAW_PARSE_TYPE_NAME: parse a type name, and return a one-element List
* containing a TypeName node.
*
- * ... more to come ...
+ * RAW_PARSE_PLPGSQL_EXPR: parse a PL/pgSQL expression, and return
+ * a one-element List containing a RawStmt node.
+ *
+ * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
+ * and return a one-element List containing a RawStmt node. "n"
+ * gives the number of dotted names comprising the target ColumnRef.
*/
typedef enum
{
RAW_PARSE_DEFAULT = 0,
- RAW_PARSE_TYPE_NAME
+ RAW_PARSE_TYPE_NAME,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_ASSIGN1,
+ RAW_PARSE_PLPGSQL_ASSIGN2,
+ RAW_PARSE_PLPGSQL_ASSIGN3
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 7f9be85eb66..a84243fc8f0 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -70,7 +70,11 @@ my %replace_types = (
'ColId' => 'ignore',
'type_function_name' => 'ignore',
'ColLabel' => 'ignore',
- 'Sconst' => 'ignore',);
+ 'Sconst' => 'ignore',
+ 'PLpgSQL_Expr' => 'ignore',
+ 'PLAssignStmt' => 'ignore',
+ 'plassign_target' => 'ignore',
+ 'plassign_equals' => 'ignore',);
# these replace_line commands excise certain keywords from the core keyword
# lists. Be sure to account for these in ColLabel and related productions.
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a0108..9946abbc1de 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -32,7 +32,7 @@ DATA = plpgsql.control plpgsql--1.0.sql
REGRESS_OPTS = --dbname=$(PL_TESTDB)
-REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
+REGRESS = plpgsql_array plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
plpgsql_trap plpgsql_trigger plpgsql_varprops
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
new file mode 100644
index 00000000000..5f28b4f685b
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -0,0 +1,94 @@
+--
+-- Tests for PL/pgSQL handling of array variables
+--
+-- We also check arrays of composites here, so this has some overlap
+-- with the plpgsql_record tests.
+--
+create type complex as (r float8, i float8);
+create type quadarray as (c1 complex[], c2 complex);
+do $$ declare a int[];
+begin a := array[1,2]; a[3] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2,4}
+do $$ declare a int[];
+begin a[3] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = [3:3]={4}
+do $$ declare a int[];
+begin a[1][4] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = [1:1][4:4]={{4}}
+do $$ declare a int[];
+begin a[1] := 23::text; raise notice 'a = %', a; end$$; -- lax typing
+NOTICE: a = {23}
+do $$ declare a int[];
+begin a := array[1,2]; a[2:3] := array[3,4]; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,3,4}
+do $$ declare a int[];
+begin a := array[1,2]; a[2] := a[2] + 1; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,3}
+do $$ declare a int[];
+begin a[1:2] := array[3,4]; raise notice 'a = %', a; end$$;
+NOTICE: a = {3,4}
+do $$ declare a int[];
+begin a[1:2] := 4; raise notice 'a = %', a; end$$; -- error
+ERROR: malformed array literal: "4"
+DETAIL: Array value must start with "{" or dimension information.
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a complex[];
+begin a[1] := (1,2); a[1].i := 11; raise notice 'a = %', a; end$$;
+NOTICE: a = {"(1,11)"}
+do $$ declare a complex[];
+begin a[1].i := 11; raise notice 'a = %, a[1].i = %', a, a[1].i; end$$;
+NOTICE: a = {"(,11)"}, a[1].i = 11
+-- perhaps this ought to work, but for now it doesn't:
+do $$ declare a complex[];
+begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
+ERROR: cannot assign to field "i" of column "a" because its type complex[] is not a composite type
+LINE 1: a[1:2].i := array[11,12]
+ ^
+QUERY: a[1:2].i := array[11,12]
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a quadarray;
+begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
+NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
+do $$ declare a int[];
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2,3}
+create temp table onecol as select array[1,2] as f1;
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+do $$ declare a int[];
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+-- error cases:
+do $$ declare a int[];
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a int[];
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
+insert into onecol values(array[11]);
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query "a := f1 from onecol" returned more than one row
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a int[];
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+do $$ declare a real;
+begin a[1] := 2; raise notice 'a = %', a; end$$;
+ERROR: cannot subscript type real because it does not support subscripting
+LINE 1: a[1] := 2
+ ^
+QUERY: a[1] := 2
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a complex;
+begin a.r[1] := 2; raise notice 'a = %', a; end$$;
+ERROR: cannot subscript type double precision because it does not support subscripting
+LINE 1: a.r[1] := 2
+ ^
+QUERY: a.r[1] := 2
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index cf6089cbb21..6e835c0751b 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -3,6 +3,7 @@
--
create type two_int4s as (f1 int4, f2 int4);
create type two_int8s as (q1 int8, q2 int8);
+create type nested_int8s as (c1 two_int8s, c2 two_int8s);
-- base-case return of a composite type
create function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1,1)::two_int8s; end $$;
@@ -82,6 +83,88 @@ begin
end$$;
NOTICE: c4 = (1,2)
NOTICE: c8 = (1,2)
+do $$ declare c two_int8s; d nested_int8s;
+begin
+ c := row(1,2);
+ d := row(c, row(c.q1, c.q2+1));
+ raise notice 'c = %, d = %', c, d;
+ c.q1 := 10;
+ d.c1 := row(11,12);
+ d.c2.q2 := 42;
+ raise notice 'c = %, d = %', c, d;
+ raise notice 'c.q1 = %, d.c2 = %', c.q1, d.c2;
+ raise notice '(d).c2.q2 = %', (d).c2.q2; -- doesn't work without parens
+ raise notice '(d.c2).q2 = %', (d.c2).q2; -- doesn't work without parens
+end$$;
+NOTICE: c = (1,2), d = ("(1,2)","(1,3)")
+NOTICE: c = (10,2), d = ("(11,12)","(1,42)")
+NOTICE: c.q1 = 10, d.c2 = (1,42)
+NOTICE: (d).c2.q2 = 42
+NOTICE: (d.c2).q2 = 42
+-- block-qualified naming
+do $$ <> declare c two_int8s; d nested_int8s;
+begin
+ b.c := row(1,2);
+ b.d := row(b.c, row(b.c.q1, b.c.q2+1));
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ b.c.q1 := 10;
+ b.d.c1 := row(11,12);
+ b.d.c2.q2 := 42;
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ raise notice 'b.c.q1 = %, b.d.c2 = %', b.c.q1, b.d.c2;
+ raise notice '(b.d).c2.q2 = %', (b.d).c2.q2; -- doesn't work without parens
+ raise notice '(b.d.c2).q2 = %', (b.d.c2).q2; -- doesn't work without parens
+end$$;
+NOTICE: b.c = (1,2), b.d = ("(1,2)","(1,3)")
+NOTICE: b.c = (10,2), b.d = ("(11,12)","(1,42)")
+NOTICE: b.c.q1 = 10, b.d.c2 = (1,42)
+NOTICE: (b.d).c2.q2 = 42
+NOTICE: (b.d.c2).q2 = 42
+-- error cases
+do $$ declare c two_int8s; begin c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x.q1 = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
+ERROR: cannot assign to field "x" of column "c" because there is no such column in data type two_int8s
+LINE 1: c.c2.x = 1
+ ^
+QUERY: c.c2.x = 1
+CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+ERROR: "d.c2.x" is not a known variable
+LINE 1: do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+ ^
+do $$ <> declare c two_int8s; begin b.c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <> declare c nested_int8s; begin b.c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x.q1 = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
+ERROR: cannot assign to field "x" of column "b" because there is no such column in data type two_int8s
+LINE 1: b.c.c2.x = 1
+ ^
+QUERY: b.c.c2.x = 1
+CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
+ERROR: "b.d.c2" is not a known variable
+LINE 1: do $$ <> declare c nested_int8s; begin b.d.c2.x = 1; end ...
+ ^
-- check passing composite result to another function
create function getq1(two_int8s) returns int8 language plpgsql as $$
declare r two_int8s; begin r := $1; return r.q1; end $$;
@@ -188,7 +271,7 @@ NOTICE: r1.q1 =
NOTICE: r1.q2 =
NOTICE: r1 =
ERROR: record "r1" has no field "nosuchfield"
-CONTEXT: SQL statement "SELECT r1.nosuchfield"
+CONTEXT: SQL expression "r1.nosuchfield"
PL/pgSQL function inline_code_block line 7 at RAISE
-- records, not so much
do $$
@@ -202,7 +285,7 @@ end$$;
NOTICE: r1 =
ERROR: record "r1" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
-CONTEXT: SQL statement "SELECT r1.f1"
+CONTEXT: SQL expression "r1.f1"
PL/pgSQL function inline_code_block line 5 at RAISE
-- but OK if you assign first
do $$
@@ -220,7 +303,7 @@ NOTICE: r1.f1 = 1
NOTICE: r1.f2 = 2
NOTICE: r1 = (1,2)
ERROR: record "r1" has no field "nosuchfield"
-CONTEXT: SQL statement "SELECT r1.nosuchfield"
+CONTEXT: SQL expression "r1.nosuchfield"
PL/pgSQL function inline_code_block line 9 at RAISE
-- check repeated assignments to composite fields
create table some_table (id int, data text);
@@ -431,7 +514,7 @@ create function getf3(x mutable) returns int language plpgsql as
$$ begin return x.f3; end $$;
select getf3(null::mutable); -- doesn't work yet
ERROR: record "x" has no field "f3"
-CONTEXT: SQL statement "SELECT x.f3"
+CONTEXT: SQL expression "x.f3"
PL/pgSQL function getf3(mutable) line 1 at RETURN
alter table mutable add column f3 int;
select getf3(null::mutable); -- now it works
diff --git a/src/pl/plpgsql/src/expected/plpgsql_varprops.out b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
index 18f03d75b42..3801dccc95a 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_varprops.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
@@ -76,7 +76,7 @@ begin
raise notice 'x = %', x;
end$$;
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 1/0"
+CONTEXT: SQL expression "1/0"
PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
do $$
declare x bigint[] := array[1,3,5];
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 81979c2961b..5336793a933 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1458,7 +1458,8 @@ plpgsql_parse_dblword(char *word1, char *word2,
/*
* We should do nothing in DECLARE sections. In SQL expressions, we
* really only need to make sure that RECFIELD datums are created when
- * needed.
+ * needed. In all the cases handled by this function, returning a T_DATUM
+ * with a two-word idents string is the right thing.
*/
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
@@ -1532,40 +1533,53 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
List *idents;
int nnames;
- idents = list_make3(makeString(word1),
- makeString(word2),
- makeString(word3));
-
/*
- * We should do nothing in DECLARE sections. In SQL expressions, we
- * really only need to make sure that RECFIELD datums are created when
- * needed.
+ * We should do nothing in DECLARE sections. In SQL expressions, we need
+ * to make sure that RECFIELD datums are created when needed, and we need
+ * to be careful about how many names are reported as belonging to the
+ * T_DATUM: the third word could be a sub-field reference, which we don't
+ * care about here.
*/
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
/*
- * Do a lookup in the current namespace stack. Must find a qualified
+ * Do a lookup in the current namespace stack. Must find a record
* reference, else ignore.
*/
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, word2, word3,
&nnames);
- if (ns != NULL && nnames == 2)
+ if (ns != NULL)
{
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_REC:
{
- /*
- * words 1/2 are a record name, so third word could be
- * a field in this record.
- */
PLpgSQL_rec *rec;
PLpgSQL_recfield *new;
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
- new = plpgsql_build_recfield(rec, word3);
-
+ if (nnames == 1)
+ {
+ /*
+ * First word is a record name, so second word
+ * could be a field in this record (and the third,
+ * a sub-field). We build a RECFIELD datum
+ * whether it is or not --- any error will be
+ * detected later.
+ */
+ new = plpgsql_build_recfield(rec, word2);
+ idents = list_make2(makeString(word1),
+ makeString(word2));
+ }
+ else
+ {
+ /* Block-qualified reference to record variable. */
+ new = plpgsql_build_recfield(rec, word3);
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
+ }
wdatum->datum = (PLpgSQL_datum *) new;
wdatum->ident = NULL;
wdatum->quoted = false; /* not used */
@@ -1580,6 +1594,9 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
}
/* Nothing found */
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
cword->idents = idents;
return false;
}
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4a51fb6d9f1..5e4dbd25a2a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4182,7 +4182,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
memset(&options, 0, sizeof(options));
options.parserSetup = (ParserSetupHook) plpgsql_parser_setup;
options.parserSetupArg = (void *) expr;
- options.parseMode = RAW_PARSE_DEFAULT;
+ options.parseMode = expr->parseMode;
options.cursorOptions = cursorOptions;
plan = SPI_prepare_extended(expr->query, &options);
if (plan == NULL)
@@ -8006,10 +8006,14 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
placeholder->collation = get_typcollation(srctype);
/*
- * Apply coercion. We use ASSIGNMENT coercion because that's the
- * closest match to plpgsql's historical behavior; in particular,
- * EXPLICIT coercion would allow silent truncation to a destination
- * varchar/bpchar's length, which we do not want.
+ * Apply coercion. We use the special coercion context
+ * COERCION_PLPGSQL to match plpgsql's historical behavior, namely
+ * that any cast not available at ASSIGNMENT level will be implemented
+ * as an I/O coercion. (It's somewhat dubious that we prefer I/O
+ * coercion over cast pathways that exist at EXPLICIT level. Changing
+ * that would cause assorted minor behavioral differences though, and
+ * a user who wants the explicit-cast behavior can always write an
+ * explicit cast.)
*
* If source type is UNKNOWN, coerce_to_target_type will fail (it only
* expects to see that for Const input nodes), so don't call it; we'll
@@ -8022,7 +8026,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
cast_expr = coerce_to_target_type(NULL,
(Node *) placeholder, srctype,
dsttype, dsttypmod,
- COERCION_ASSIGNMENT,
+ COERCION_PLPGSQL,
COERCE_IMPLICIT_CAST,
-1);
@@ -8030,7 +8034,8 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
* If there's no cast path according to the parser, fall back to using
* an I/O coercion; this is semantically dubious but matches plpgsql's
* historical behavior. We would need something of the sort for
- * UNKNOWN literals in any case.
+ * UNKNOWN literals in any case. (This is probably now only reachable
+ * in the case where srctype is UNKNOWN/RECORD.)
*/
if (cast_expr == NULL)
{
@@ -8339,7 +8344,8 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
return;
/*
- * Top level of expression must be a simple FuncExpr or OpExpr.
+ * Top level of expression must be a simple FuncExpr, OpExpr, or
+ * SubscriptingRef.
*/
if (IsA(expr->expr_simple_expr, FuncExpr))
{
@@ -8355,6 +8361,33 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
funcid = opexpr->opfuncid;
fargs = opexpr->args;
}
+ else if (IsA(expr->expr_simple_expr, SubscriptingRef))
+ {
+ SubscriptingRef *sbsref = (SubscriptingRef *) expr->expr_simple_expr;
+
+ /* We only trust standard varlena arrays to be safe */
+ if (get_typsubscript(sbsref->refcontainertype, NULL) !=
+ F_ARRAY_SUBSCRIPT_HANDLER)
+ return;
+
+ /* refexpr can be a simple Param, otherwise must not contain target */
+ if (!(sbsref->refexpr && IsA(sbsref->refexpr, Param)) &&
+ contains_target_param((Node *) sbsref->refexpr, &target_dno))
+ return;
+
+ /* the other subexpressions must not contain target */
+ if (contains_target_param((Node *) sbsref->refupperindexpr,
+ &target_dno) ||
+ contains_target_param((Node *) sbsref->reflowerindexpr,
+ &target_dno) ||
+ contains_target_param((Node *) sbsref->refassgnexpr,
+ &target_dno))
+ return;
+
+ /* OK, we can pass target as a read-write parameter */
+ expr->rwparam = target_dno;
+ return;
+ }
else
return;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index c09576efff5..ad248bc7648 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -51,7 +51,6 @@
typedef struct
{
int location;
- int leaderlen;
} sql_error_callback_arg;
#define parser_errposition(pos) plpgsql_scanner_errposition(pos)
@@ -67,7 +66,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
int until2,
int until3,
const char *expected,
- const char *sqlstart,
+ RawParseMode parsemode,
bool isexpression,
bool valid_sql,
bool trim,
@@ -78,7 +77,7 @@ static PLpgSQL_expr *read_sql_expression(int until,
static PLpgSQL_expr *read_sql_expression2(int until, int until2,
const char *expected,
int *endtoken);
-static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
+static PLpgSQL_expr *read_sql_stmt(void);
static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
static PLpgSQL_stmt_fetch *read_fetch_direction(void);
@@ -99,8 +98,8 @@ static PLpgSQL_row *read_into_scalar_list(char *initial_name,
static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location);
-static void check_sql_expr(const char *stmt, int location,
- int leaderlen);
+static void check_sql_expr(const char *stmt,
+ RawParseMode parseMode, int location);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location);
static void check_labels(const char *start_label,
@@ -540,7 +539,7 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
{
PLpgSQL_var *new;
PLpgSQL_expr *curname_def;
- char buf[1024];
+ char buf[NAMEDATALEN * 2 + 64];
char *cp1;
char *cp2;
@@ -557,9 +556,9 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
curname_def = palloc0(sizeof(PLpgSQL_expr));
- strcpy(buf, "SELECT ");
+ /* Note: refname has been truncated to NAMEDATALEN */
cp1 = new->refname;
- cp2 = buf + strlen(buf);
+ cp2 = buf;
/*
* Don't trust standard_conforming_strings here;
* it might change before we use the string.
@@ -575,6 +574,7 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
}
strcpy(cp2, "'::pg_catalog.refcursor");
curname_def->query = pstrdup(buf);
+ curname_def->parseMode = RAW_PARSE_PLPGSQL_EXPR;
new->default_val = curname_def;
new->cursor_explicit_expr = $7;
@@ -602,7 +602,7 @@ opt_scrollable :
decl_cursor_query :
{
- $$ = read_sql_stmt("");
+ $$ = read_sql_stmt();
}
;
@@ -904,15 +904,37 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
;
-stmt_perform : K_PERFORM expr_until_semi
+stmt_perform : K_PERFORM
{
PLpgSQL_stmt_perform *new;
+ int startloc;
new = palloc0(sizeof(PLpgSQL_stmt_perform));
new->cmd_type = PLPGSQL_STMT_PERFORM;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = $2;
+ plpgsql_push_back_token(K_PERFORM);
+
+ /*
+ * Since PERFORM isn't legal SQL, we have to cheat to
+ * the extent of substituting "SELECT" for "PERFORM"
+ * in the parsed text. It does not seem worth
+ * inventing a separate parse mode for this one case.
+ * We can't do syntax-checking until after we make the
+ * substitution.
+ */
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ RAW_PARSE_DEFAULT,
+ false, false, true,
+ &startloc, NULL);
+ /* overwrite "perform" ... */
+ memcpy(new->expr->query, " SELECT", 7);
+ /* left-justify to get rid of the leading space */
+ memmove(new->expr->query, new->expr->query + 1,
+ strlen(new->expr->query));
+ /* offset syntax error position to account for that */
+ check_sql_expr(new->expr->query, new->expr->parseMode,
+ startloc + 1);
$$ = (PLpgSQL_stmt *)new;
}
@@ -926,7 +948,8 @@ stmt_call : K_CALL
new->cmd_type = PLPGSQL_STMT_CALL;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = read_sql_stmt("CALL ");
+ plpgsql_push_back_token(K_CALL);
+ new->expr = read_sql_stmt();
new->is_call = true;
$$ = (PLpgSQL_stmt *)new;
@@ -941,7 +964,8 @@ stmt_call : K_CALL
new->cmd_type = PLPGSQL_STMT_CALL;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = read_sql_stmt("DO ");
+ plpgsql_push_back_token(K_DO);
+ new->expr = read_sql_stmt();
new->is_call = false;
$$ = (PLpgSQL_stmt *)new;
@@ -949,16 +973,40 @@ stmt_call : K_CALL
}
;
-stmt_assign : assign_var assign_operator expr_until_semi
+stmt_assign : T_DATUM
{
PLpgSQL_stmt_assign *new;
+ RawParseMode pmode;
+ /* see how many names identify the datum */
+ switch ($1.ident ? 1 : list_length($1.idents))
+ {
+ case 1:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ break;
+ case 2:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ break;
+ case 3:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ break;
+ default:
+ elog(ERROR, "unexpected number of names");
+ pmode = 0; /* keep compiler quiet */
+ }
+
+ check_assignable($1.datum, @1);
new = palloc0(sizeof(PLpgSQL_stmt_assign));
new->cmd_type = PLPGSQL_STMT_ASSIGN;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->varno = $1->dno;
- new->expr = $3;
+ new->varno = $1.datum->dno;
+ /* Push back the head name to include it in the stmt */
+ plpgsql_push_back_token(T_DATUM);
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ pmode,
+ false, true, true,
+ NULL, NULL);
$$ = (PLpgSQL_stmt *)new;
}
@@ -1452,16 +1500,16 @@ for_control : for_variable K_IN
/*
* Read tokens until we see either a ".."
- * or a LOOP. The text we read may not
- * necessarily be a well-formed SQL
- * statement, so we need to invoke
- * read_sql_construct directly.
+ * or a LOOP. The text we read may be either
+ * an expression or a whole SQL statement, so
+ * we need to invoke read_sql_construct directly,
+ * and tell it not to check syntax yet.
*/
expr1 = read_sql_construct(DOT_DOT,
K_LOOP,
0,
"LOOP",
- "SELECT ",
+ RAW_PARSE_DEFAULT,
true,
false,
true,
@@ -1476,8 +1524,13 @@ for_control : for_variable K_IN
PLpgSQL_var *fvar;
PLpgSQL_stmt_fori *new;
- /* Check first expression is well-formed */
- check_sql_expr(expr1->query, expr1loc, 7);
+ /*
+ * Relabel first expression as an expression;
+ * then we can check its syntax.
+ */
+ expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1522,12 +1575,8 @@ for_control : for_variable K_IN
else
{
/*
- * No "..", so it must be a query loop. We've
- * prefixed an extra SELECT to the query text,
- * so we need to remove that before performing
- * syntax checking.
+ * No "..", so it must be a query loop.
*/
- char *tmp_query;
PLpgSQL_stmt_fors *new;
if (reverse)
@@ -1536,12 +1585,9 @@ for_control : for_variable K_IN
errmsg("cannot specify REVERSE in query FOR loop"),
parser_errposition(tokloc)));
- Assert(strncmp(expr1->query, "SELECT ", 7) == 0);
- tmp_query = pstrdup(expr1->query + 7);
- pfree(expr1->query);
- expr1->query = tmp_query;
-
- check_sql_expr(expr1->query, expr1loc, 0);
+ /* Check syntax as a regular query */
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1870,7 +1916,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
- "SELECT ",
+ RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
@@ -1958,7 +2004,7 @@ loop_body : proc_sect K_END K_LOOP opt_label ';'
* variable. (The composite case is probably a syntax error, but we'll let
* the core parser decide that.) Normally, we should assume that such a
* word is a SQL statement keyword that isn't also a plpgsql keyword.
- * However, if the next token is assignment or '[', it can't be a valid
+ * However, if the next token is assignment or '[' or '.', it can't be a valid
* SQL statement, and what we're probably looking at is an intended variable
* assignment. Give an appropriate complaint for that, instead of letting
* the core parser throw an unhelpful "syntax error".
@@ -1977,7 +2023,8 @@ stmt_execsql : K_IMPORT
tok = yylex();
plpgsql_push_back_token(tok);
- if (tok == '=' || tok == COLON_EQUALS || tok == '[')
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
word_is_not_variable(&($1), @1);
$$ = make_execsql_stmt(T_WORD, @1);
}
@@ -1987,7 +2034,8 @@ stmt_execsql : K_IMPORT
tok = yylex();
plpgsql_push_back_token(tok);
- if (tok == '=' || tok == COLON_EQUALS || tok == '[')
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
cword_is_not_variable(&($1), @1);
$$ = make_execsql_stmt(T_CWORD, @1);
}
@@ -2001,7 +2049,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
- "SELECT ",
+ RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &endtoken);
@@ -2040,7 +2088,7 @@ stmt_dynexecute : K_EXECUTE
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
- "SELECT ",
+ RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &endtoken);
new->params = lappend(new->params, expr);
@@ -2122,7 +2170,7 @@ stmt_open : K_OPEN cursor_variable
else
{
plpgsql_push_back_token(tok);
- new->query = read_sql_stmt("");
+ new->query = read_sql_stmt();
}
}
else
@@ -2246,8 +2294,8 @@ stmt_set : K_SET
new->cmd_type = PLPGSQL_STMT_SET;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
-
- new->expr = read_sql_stmt("SET ");
+ plpgsql_push_back_token(K_SET);
+ new->expr = read_sql_stmt();
$$ = (PLpgSQL_stmt *)new;
}
@@ -2259,7 +2307,8 @@ stmt_set : K_SET
new->cmd_type = PLPGSQL_STMT_SET;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = read_sql_stmt("RESET ");
+ plpgsql_push_back_token(K_RESET);
+ new->expr = read_sql_stmt();
$$ = (PLpgSQL_stmt *)new;
}
@@ -2656,7 +2705,8 @@ static PLpgSQL_expr *
read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
- "SELECT ", true, true, true, NULL, NULL);
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, NULL);
}
/* Convenience routine to read an expression with two possible terminators */
@@ -2665,15 +2715,17 @@ read_sql_expression2(int until, int until2, const char *expected,
int *endtoken)
{
return read_sql_construct(until, until2, 0, expected,
- "SELECT ", true, true, true, NULL, endtoken);
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, endtoken);
}
/* Convenience routine to read a SQL statement that must end with ';' */
static PLpgSQL_expr *
-read_sql_stmt(const char *sqlstart)
+read_sql_stmt(void)
{
return read_sql_construct(';', 0, 0, ";",
- sqlstart, false, true, true, NULL, NULL);
+ RAW_PARSE_DEFAULT,
+ false, true, true, NULL, NULL);
}
/*
@@ -2683,9 +2735,9 @@ read_sql_stmt(const char *sqlstart)
* until2: token code for alternate terminator (pass 0 if none)
* until3: token code for another alternate terminator (pass 0 if none)
* expected: text to use in complaining that terminator was not found
- * sqlstart: text to prefix to the accumulated SQL text
+ * parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
- * valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
+ * valid_sql: whether to check the syntax of the expr
* trim: trim trailing whitespace
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2696,7 +2748,7 @@ read_sql_construct(int until,
int until2,
int until3,
const char *expected,
- const char *sqlstart,
+ RawParseMode parsemode,
bool isexpression,
bool valid_sql,
bool trim,
@@ -2711,7 +2763,6 @@ read_sql_construct(int until,
PLpgSQL_expr *expr;
initStringInfo(&ds);
- appendStringInfoString(&ds, sqlstart);
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = plpgsql_IdentifierLookup;
@@ -2787,6 +2838,7 @@ read_sql_construct(int until,
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
+ expr->parseMode = parsemode;
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
@@ -2794,7 +2846,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, startlocation, strlen(sqlstart));
+ check_sql_expr(expr->query, expr->parseMode, startlocation);
return expr;
}
@@ -3033,13 +3085,14 @@ make_execsql_stmt(int firsttoken, int location)
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_DEFAULT;
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
- check_sql_expr(expr->query, location, 0);
+ check_sql_expr(expr->query, expr->parseMode, location);
execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3382,7 +3435,7 @@ make_return_query_stmt(int location)
{
/* ordinary static query */
plpgsql_push_back_token(tok);
- new->query = read_sql_stmt("");
+ new->query = read_sql_stmt();
}
else
{
@@ -3637,13 +3690,12 @@ make_scalar_list1(char *initial_name,
* borders. So it is best to bail out as early as we can.
*
* It is assumed that "stmt" represents a copy of the function source text
- * beginning at offset "location", with leader text of length "leaderlen"
- * (typically "SELECT ") prefixed to the source text. We use this assumption
- * to transpose any error cursor position back to the function source text.
+ * beginning at offset "location". We use this assumption to transpose
+ * any error cursor position back to the function source text.
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, int location, int leaderlen)
+check_sql_expr(const char *stmt, RawParseMode parseMode, int location)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
@@ -3653,7 +3705,6 @@ check_sql_expr(const char *stmt, int location, int leaderlen)
return;
cbarg.location = location;
- cbarg.leaderlen = leaderlen;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
@@ -3661,7 +3712,7 @@ check_sql_expr(const char *stmt, int location, int leaderlen)
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, RAW_PARSE_DEFAULT);
+ (void) raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
@@ -3686,12 +3737,12 @@ plpgsql_sql_error_callback(void *arg)
* Note we are dealing with 1-based character numbers at this point.
*/
errpos = geterrposition();
- if (errpos > cbarg->leaderlen)
+ if (errpos > 0)
{
int myerrpos = getinternalerrposition();
if (myerrpos > 0) /* safety check */
- internalerrposition(myerrpos + errpos - cbarg->leaderlen - 1);
+ internalerrposition(myerrpos + errpos - 1);
}
/* In any case, flush errposition --- we want internalerrposition only */
@@ -3717,7 +3768,6 @@ parse_datatype(const char *string, int location)
ErrorContextCallback syntax_errcontext;
cbarg.location = location;
- cbarg.leaderlen = 0;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
@@ -3780,7 +3830,6 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
int argc;
char **argv;
StringInfoData ds;
- char *sqlstart = "SELECT ";
bool any_named = false;
tok = yylex();
@@ -3881,12 +3930,12 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
- sqlstart,
+ RAW_PARSE_PLPGSQL_EXPR,
true, true,
false, /* do not trim */
NULL, &endtoken);
- argv[argpos] = item->query + strlen(sqlstart);
+ argv[argpos] = item->query;
if (endtoken == ')' && !(argc == row->nfields - 1))
ereport(ERROR,
@@ -3905,7 +3954,6 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
/* Make positional argument list */
initStringInfo(&ds);
- appendStringInfoString(&ds, sqlstart);
for (argc = 0; argc < row->nfields; argc++)
{
Assert(argv[argc] != NULL);
@@ -3921,10 +3969,10 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
if (argc < row->nfields - 1)
appendStringInfoString(&ds, ", ");
}
- appendStringInfoChar(&ds, ';');
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
@@ -4097,14 +4145,14 @@ make_case(int location, PLpgSQL_expr *t_expr,
PLpgSQL_expr *expr = cwt->expr;
StringInfoData ds;
- /* copy expression query without SELECT keyword (expr->query + 7) */
- Assert(strncmp(expr->query, "SELECT ", 7) == 0);
+ /* We expect to have expressions not statements */
+ Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
- /* And do the string hacking */
+ /* Do the string hacking */
initStringInfo(&ds);
- appendStringInfo(&ds, "SELECT \"%s\" IN (%s)",
- varname, expr->query + 7);
+ appendStringInfo(&ds, "\"%s\" IN (%s)",
+ varname, expr->query);
pfree(expr->query);
expr->query = pstrdup(ds.data);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index dee175b7bf6..f80d023a5ad 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -218,8 +218,9 @@ typedef struct PLpgSQL_type
*/
typedef struct PLpgSQL_expr
{
- char *query;
- SPIPlanPtr plan;
+ char *query; /* query string, verbatim from function body */
+ RawParseMode parseMode; /* raw_parser() mode to use */
+ SPIPlanPtr plan; /* plan, or NULL if not made yet */
Bitmapset *paramnos; /* all dnos referenced by this query */
int rwparam; /* dno of read/write param, or -1 if none */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
new file mode 100644
index 00000000000..4c3f26be101
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -0,0 +1,79 @@
+--
+-- Tests for PL/pgSQL handling of array variables
+--
+-- We also check arrays of composites here, so this has some overlap
+-- with the plpgsql_record tests.
+--
+
+create type complex as (r float8, i float8);
+create type quadarray as (c1 complex[], c2 complex);
+
+do $$ declare a int[];
+begin a := array[1,2]; a[3] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[3] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1][4] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1] := 23::text; raise notice 'a = %', a; end$$; -- lax typing
+
+do $$ declare a int[];
+begin a := array[1,2]; a[2:3] := array[3,4]; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := array[1,2]; a[2] := a[2] + 1; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1:2] := array[3,4]; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1:2] := 4; raise notice 'a = %', a; end$$; -- error
+
+do $$ declare a complex[];
+begin a[1] := (1,2); a[1].i := 11; raise notice 'a = %', a; end$$;
+
+do $$ declare a complex[];
+begin a[1].i := 11; raise notice 'a = %, a[1].i = %', a, a[1].i; end$$;
+
+-- perhaps this ought to work, but for now it doesn't:
+do $$ declare a complex[];
+begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
+
+do $$ declare a quadarray;
+begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
+
+do $$ declare a int[];
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+
+create temp table onecol as select array[1,2] as f1;
+
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+
+-- error cases:
+
+do $$ declare a int[];
+begin a := from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+
+insert into onecol values(array[11]);
+
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+
+do $$ declare a real;
+begin a[1] := 2; raise notice 'a = %', a; end$$;
+
+do $$ declare a complex;
+begin a.r[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql
index 128846e6108..be10f00b1e6 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_record.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql
@@ -4,6 +4,7 @@
create type two_int4s as (f1 int4, f2 int4);
create type two_int8s as (q1 int8, q2 int8);
+create type nested_int8s as (c1 two_int8s, c2 two_int8s);
-- base-case return of a composite type
create function retc(int) returns two_int8s language plpgsql as
@@ -59,6 +60,47 @@ begin
raise notice 'c8 = %', c8;
end$$;
+do $$ declare c two_int8s; d nested_int8s;
+begin
+ c := row(1,2);
+ d := row(c, row(c.q1, c.q2+1));
+ raise notice 'c = %, d = %', c, d;
+ c.q1 := 10;
+ d.c1 := row(11,12);
+ d.c2.q2 := 42;
+ raise notice 'c = %, d = %', c, d;
+ raise notice 'c.q1 = %, d.c2 = %', c.q1, d.c2;
+ raise notice '(d).c2.q2 = %', (d).c2.q2; -- doesn't work without parens
+ raise notice '(d.c2).q2 = %', (d.c2).q2; -- doesn't work without parens
+end$$;
+
+-- block-qualified naming
+do $$ <> declare c two_int8s; d nested_int8s;
+begin
+ b.c := row(1,2);
+ b.d := row(b.c, row(b.c.q1, b.c.q2+1));
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ b.c.q1 := 10;
+ b.d.c1 := row(11,12);
+ b.d.c2.q2 := 42;
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ raise notice 'b.c.q1 = %, b.d.c2 = %', b.c.q1, b.d.c2;
+ raise notice '(b.d).c2.q2 = %', (b.d).c2.q2; -- doesn't work without parens
+ raise notice '(b.d.c2).q2 = %', (b.d.c2).q2; -- doesn't work without parens
+end$$;
+
+-- error cases
+do $$ declare c two_int8s; begin c.x = 1; end $$;
+do $$ declare c nested_int8s; begin c.x = 1; end $$;
+do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
+do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
+do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+do $$ <> declare c two_int8s; begin b.c.x = 1; end $$;
+do $$ <> declare c nested_int8s; begin b.c.x = 1; end $$;
+do $$ <> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
+do $$ <> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
+do $$ <> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
+
-- check passing composite result to another function
create function getq1(two_int8s) returns int8 language plpgsql as $$
declare r two_int8s; begin r := $1; return r.q1; end $$;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d55006d8c95..0c6c9ba1f59 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1761,10 +1761,10 @@ select f1(42) as int, f1(4.5) as num;
select f1(point(3,4)); -- fail for lack of + operator
ERROR: operator does not exist: point + integer
-LINE 1: SELECT x + 1
- ^
+LINE 1: x + 1
+ ^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
-QUERY: SELECT x + 1
+QUERY: x + 1
CONTEXT: PL/pgSQL function f1(anyelement) line 3 at RETURN
drop function f1(x anyelement);
create function f1(x anyelement) returns anyarray as $$
@@ -2361,7 +2361,7 @@ begin
end $$ language plpgsql;
select namedparmcursor_test7();
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 42/0 AS p1, 77 AS p2;"
+CONTEXT: SQL expression "42/0 AS p1, 77 AS p2"
PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
-- check that line comments work correctly within the argument list (there
-- is some special handling of this case in the code: the newline after the
@@ -2574,9 +2574,9 @@ end; $$ language plpgsql;
-- blocks
select excpt_test1();
ERROR: column "sqlstate" does not exist
-LINE 1: SELECT sqlstate
- ^
-QUERY: SELECT sqlstate
+LINE 1: sqlstate
+ ^
+QUERY: sqlstate
CONTEXT: PL/pgSQL function excpt_test1() line 3 at RAISE
create function excpt_test2() returns void as $$
begin
@@ -2589,9 +2589,9 @@ end; $$ language plpgsql;
-- should fail
select excpt_test2();
ERROR: column "sqlstate" does not exist
-LINE 1: SELECT sqlstate
- ^
-QUERY: SELECT sqlstate
+LINE 1: sqlstate
+ ^
+QUERY: sqlstate
CONTEXT: PL/pgSQL function excpt_test2() line 5 at RAISE
create function excpt_test3() returns void as $$
begin
@@ -4467,11 +4467,11 @@ end
$$;
select fail();
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 1/0"
+CONTEXT: SQL expression "1/0"
PL/pgSQL function fail() line 3 at RETURN
select fail();
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 1/0"
+CONTEXT: SQL expression "1/0"
PL/pgSQL function fail() line 3 at RETURN
drop function fail();
-- Test handling of string literals.
@@ -4497,10 +4497,10 @@ HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
select strtest();
NOTICE: foo\bar!baz
WARNING: nonstandard use of \\ in a string literal
-LINE 1: SELECT 'foo\\bar\041baz'
- ^
+LINE 1: 'foo\\bar\041baz'
+ ^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
-QUERY: SELECT 'foo\\bar\041baz'
+QUERY: 'foo\\bar\041baz'
strtest
-------------
foo\bar!baz
@@ -5621,9 +5621,9 @@ ALTER TABLE alter_table_under_transition_tables
UPDATE alter_table_under_transition_tables
SET id = id;
ERROR: column "name" does not exist
-LINE 1: SELECT (SELECT string_agg(id || '=' || name, ',') FROM d)
- ^
-QUERY: SELECT (SELECT string_agg(id || '=' || name, ',') FROM d)
+LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d)
+ ^
+QUERY: (SELECT string_agg(id || '=' || name, ',') FROM d)
CONTEXT: PL/pgSQL function alter_table_under_transition_tables_upd_func() line 3 at RAISE
--
-- Test multiple reference to a transition table