Commit c9d52984 authored by Tom Lane's avatar Tom Lane

Re-implement pl/pgsql's expression and assignment parsing.

Invent new RawParseModes that allow the core grammar to handle
pl/pgsql expressions and assignments directly, and thereby get rid
of a lot of hackery in pl/pgsql's parser.  This moves a good deal
of knowledge about pl/pgsql into the core code: notably, we have to
invent a CoercionContext that matches pl/pgsql's (rather dubious)
historical behavior for assignment coercions.  That's getting away
from the original idea of pl/pgsql as an arm's-length extension of
the core, but really we crossed that bridge a long time ago.

The main advantage of doing this is that we can now use the core
parser to generate FieldStore and/or SubscriptingRef nodes to handle
assignments to pl/pgsql variables that are records or arrays.  That
fixes a number of cases that had never been implemented in pl/pgsql
assignment, such as nested records and array slicing, and it allows
pl/pgsql assignment to support the datatype-specific subscripting
behaviors introduced in commit c7aba7c1.

There are cosmetic benefits too: when a syntax error occurs in a
pl/pgsql expression, the error report no longer includes the confusing
"SELECT" keyword that used to get prefixed to the expression text.
Also, there seem to be some small speed gains.

Discussion: https://postgr.es/m/4165684.1607707277@sss.pgh.pa.us
parent 844fe9f1
...@@ -1583,6 +1583,10 @@ select f2 from test_json_agg; ...@@ -1583,6 +1583,10 @@ select f2 from test_json_agg;
"d"=>NULL, "x"=>"xyzzy" "d"=>NULL, "x"=>"xyzzy"
(3 rows) (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. -- Check the hstore_hash() and hstore_hash_extended() function explicitly.
SELECT v as value, hstore_hash(v)::bit(32) as standard, SELECT v as value, hstore_hash(v)::bit(32) as standard,
hstore_hash_extended(v, 0)::bit(32) as extended0, hstore_hash_extended(v, 0)::bit(32) as extended0,
......
...@@ -372,6 +372,10 @@ select f2['d':'e'] from test_json_agg; -- error ...@@ -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'; update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy';
select f2 from test_json_agg; 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. -- Check the hstore_hash() and hstore_hash_extended() function explicitly.
SELECT v as value, hstore_hash(v)::bit(32) as standard, SELECT v as value, hstore_hash(v)::bit(32) as standard,
hstore_hash_extended(v, 0)::bit(32) as extended0, hstore_hash_extended(v, 0)::bit(32) as extended0,
......
...@@ -946,8 +946,8 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1 ...@@ -946,8 +946,8 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
database engine. The expression must yield a single value (possibly database engine. The expression must yield a single value (possibly
a row value, if the variable is a row or record variable). The target 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 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 name), a field of a row or record target, or an element or slice of
that is a simple variable or field. Equal (<literal>=</literal>) can be an array target. Equal (<literal>=</literal>) can be
used instead of PL/SQL-compliant <literal>:=</literal>. used instead of PL/SQL-compliant <literal>:=</literal>.
</para> </para>
...@@ -968,8 +968,25 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1 ...@@ -968,8 +968,25 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
<programlisting> <programlisting>
tax := subtotal * 0.06; tax := subtotal * 0.06;
my_record.user_id := 20; my_record.user_id := 20;
my_array[j] := 20;
my_array[1:3] := array[1,2,3];
complex_array[n].realpart = 12.3;
</programlisting> </programlisting>
</para> </para>
<para>
It's useful to know that what follows the assignment operator is
essentially treated as a <literal>SELECT</literal> command; as long
as it returns a single row and column, it will work. Thus for example
one can write something like
<programlisting>
total_sales := sum(quantity) from sales;
</programlisting>
This provides an effect similar to the single-row <literal>SELECT
... INTO</literal> syntax described in
<xref linkend="plpgsql-statements-sql-onerow"/>. However, that syntax
is more portable.
</para>
</sect2> </sect2>
<sect2 id="plpgsql-statements-sql-noresult"> <sect2 id="plpgsql-statements-sql-noresult">
......
...@@ -1628,6 +1628,7 @@ CreateCast(CreateCastStmt *stmt) ...@@ -1628,6 +1628,7 @@ CreateCast(CreateCastStmt *stmt)
case COERCION_ASSIGNMENT: case COERCION_ASSIGNMENT:
castcontext = COERCION_CODE_ASSIGNMENT; castcontext = COERCION_CODE_ASSIGNMENT;
break; break;
/* COERCION_PLPGSQL is intentionally not covered here */
case COERCION_EXPLICIT: case COERCION_EXPLICIT:
castcontext = COERCION_CODE_EXPLICIT; castcontext = COERCION_CODE_EXPLICIT;
break; break;
......
...@@ -51,6 +51,12 @@ static _SPI_connection *_SPI_current = NULL; ...@@ -51,6 +51,12 @@ static _SPI_connection *_SPI_current = NULL;
static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */ static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1; /* current stack index */ 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, static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
ParamListInfo paramLI, bool read_only); ParamListInfo paramLI, bool read_only);
...@@ -1479,6 +1485,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1479,6 +1485,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Snapshot snapshot; Snapshot snapshot;
MemoryContext oldcontext; MemoryContext oldcontext;
Portal portal; Portal portal;
SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext; ErrorContextCallback spierrcontext;
/* /*
...@@ -1533,8 +1540,10 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1533,8 +1540,10 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
* Setup error traceback support for ereport(), in case GetCachedPlan * Setup error traceback support for ereport(), in case GetCachedPlan
* throws an error. * throws an error.
*/ */
spicallbackarg.query = plansource->query_string;
spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback; spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = unconstify(char *, plansource->query_string); spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack; spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext; error_context_stack = &spierrcontext;
...@@ -1952,6 +1961,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan) ...@@ -1952,6 +1961,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
{ {
CachedPlanSource *plansource; CachedPlanSource *plansource;
CachedPlan *cplan; CachedPlan *cplan;
SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext; ErrorContextCallback spierrcontext;
Assert(plan->magic == _SPI_PLAN_MAGIC); Assert(plan->magic == _SPI_PLAN_MAGIC);
...@@ -1966,8 +1976,10 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan) ...@@ -1966,8 +1976,10 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
plansource = (CachedPlanSource *) linitial(plan->plancache_list); plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Setup error traceback support for ereport() */ /* Setup error traceback support for ereport() */
spicallbackarg.query = plansource->query_string;
spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback; spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = unconstify(char *, plansource->query_string); spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack; spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext; error_context_stack = &spierrcontext;
...@@ -2094,13 +2106,16 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan) ...@@ -2094,13 +2106,16 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
List *raw_parsetree_list; List *raw_parsetree_list;
List *plancache_list; List *plancache_list;
ListCell *list_item; ListCell *list_item;
SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext; ErrorContextCallback spierrcontext;
/* /*
* Setup error traceback support for ereport() * Setup error traceback support for ereport()
*/ */
spicallbackarg.query = src;
spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback; spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = unconstify(char *, src); spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack; spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext; error_context_stack = &spierrcontext;
...@@ -2199,13 +2214,16 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan) ...@@ -2199,13 +2214,16 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
List *raw_parsetree_list; List *raw_parsetree_list;
List *plancache_list; List *plancache_list;
ListCell *list_item; ListCell *list_item;
SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext; ErrorContextCallback spierrcontext;
/* /*
* Setup error traceback support for ereport() * Setup error traceback support for ereport()
*/ */
spicallbackarg.query = src;
spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback; spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = unconstify(char *, src); spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack; spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext; error_context_stack = &spierrcontext;
...@@ -2263,6 +2281,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -2263,6 +2281,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
SPITupleTable *my_tuptable = NULL; SPITupleTable *my_tuptable = NULL;
int res = 0; int res = 0;
bool pushed_active_snap = false; bool pushed_active_snap = false;
SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext; ErrorContextCallback spierrcontext;
CachedPlan *cplan = NULL; CachedPlan *cplan = NULL;
ListCell *lc1; ListCell *lc1;
...@@ -2270,8 +2289,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -2270,8 +2289,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
/* /*
* Setup error traceback support for ereport() * 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.callback = _SPI_error_callback;
spierrcontext.arg = NULL; /* we'll fill this below */ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack; spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext; error_context_stack = &spierrcontext;
...@@ -2318,7 +2339,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -2318,7 +2339,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
List *stmt_list; List *stmt_list;
ListCell *lc2; 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. * 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) ...@@ -2722,7 +2743,8 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
static void static void
_SPI_error_callback(void *arg) _SPI_error_callback(void *arg)
{ {
const char *query = (const char *) arg; SPICallbackArg *carg = (SPICallbackArg *) arg;
const char *query = carg->query;
int syntaxerrposition; int syntaxerrposition;
if (query == NULL) /* in case arg wasn't set yet */ if (query == NULL) /* in case arg wasn't set yet */
...@@ -2740,7 +2762,23 @@ _SPI_error_callback(void *arg) ...@@ -2740,7 +2762,23 @@ _SPI_error_callback(void *arg)
internalerrquery(query); internalerrquery(query);
} }
else 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;
}
}
} }
/* /*
......
...@@ -3199,6 +3199,20 @@ _copySetOperationStmt(const SetOperationStmt *from) ...@@ -3199,6 +3199,20 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode; 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 * static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from) _copyAlterTableStmt(const AlterTableStmt *from)
{ {
...@@ -5220,6 +5234,9 @@ copyObjectImpl(const void *from) ...@@ -5220,6 +5234,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt: case T_SetOperationStmt:
retval = _copySetOperationStmt(from); retval = _copySetOperationStmt(from);
break; break;
case T_PLAssignStmt:
retval = _copyPLAssignStmt(from);
break;
case T_AlterTableStmt: case T_AlterTableStmt:
retval = _copyAlterTableStmt(from); retval = _copyAlterTableStmt(from);
break; break;
......
...@@ -1085,6 +1085,18 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b) ...@@ -1085,6 +1085,18 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true; 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 static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b) _equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{ {
...@@ -3275,6 +3287,9 @@ equal(const void *a, const void *b) ...@@ -3275,6 +3287,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt: case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b); retval = _equalSetOperationStmt(a, b);
break; break;
case T_PLAssignStmt:
retval = _equalPLAssignStmt(a, b);
break;
case T_AlterTableStmt: case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b); retval = _equalAlterTableStmt(a, b);
break; break;
......
...@@ -3669,6 +3669,16 @@ raw_expression_tree_walker(Node *node, ...@@ -3669,6 +3669,16 @@ raw_expression_tree_walker(Node *node,
return true; return true;
} }
break; 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: case T_A_Expr:
{ {
A_Expr *expr = (A_Expr *) node; A_Expr *expr = (A_Expr *) node;
......
...@@ -2775,6 +2775,18 @@ _outSelectStmt(StringInfo str, const SelectStmt *node) ...@@ -2775,6 +2775,18 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg); 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 static void
_outFuncCall(StringInfo str, const FuncCall *node) _outFuncCall(StringInfo str, const FuncCall *node)
{ {
...@@ -4211,6 +4223,9 @@ outNode(StringInfo str, const void *obj) ...@@ -4211,6 +4223,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt: case T_SelectStmt:
_outSelectStmt(str, obj); _outSelectStmt(str, obj);
break; break;
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
case T_ColumnDef: case T_ColumnDef:
_outColumnDef(str, obj); _outColumnDef(str, obj);
break; break;
......
...@@ -42,8 +42,10 @@ ...@@ -42,8 +42,10 @@
#include "parser/parse_param.h" #include "parser/parse_param.h"
#include "parser/parse_relation.h" #include "parser/parse_relation.h"
#include "parser/parse_target.h" #include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parsetree.h" #include "parser/parsetree.h"
#include "rewrite/rewriteManip.h" #include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/rel.h" #include "utils/rel.h"
...@@ -70,6 +72,8 @@ static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); ...@@ -70,6 +72,8 @@ static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList); static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate, static List *transformUpdateTargetList(ParseState *pstate,
List *targetList); List *targetList);
static Query *transformPLAssignStmt(ParseState *pstate,
PLAssignStmt *stmt);
static Query *transformDeclareCursorStmt(ParseState *pstate, static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt); DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate, static Query *transformExplainStmt(ParseState *pstate,
...@@ -304,6 +308,11 @@ transformStmt(ParseState *pstate, Node *parseTree) ...@@ -304,6 +308,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
} }
break; break;
case T_PLAssignStmt:
result = transformPLAssignStmt(pstate,
(PLAssignStmt *) parseTree);
break;
/* /*
* Special cases * Special cases
*/ */
...@@ -367,6 +376,7 @@ analyze_requires_snapshot(RawStmt *parseTree) ...@@ -367,6 +376,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_DeleteStmt: case T_DeleteStmt:
case T_UpdateStmt: case T_UpdateStmt:
case T_SelectStmt: case T_SelectStmt:
case T_PLAssignStmt:
result = true; result = true;
break; break;
...@@ -2393,6 +2403,236 @@ transformReturningList(ParseState *pstate, List *returningList) ...@@ -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 - * transformDeclareCursorStmt -
* transform a DECLARE CURSOR Statement * transform a DECLARE CURSOR Statement
......
...@@ -294,6 +294,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -294,6 +294,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause %type <node> select_no_parens select_with_parens select_clause
simple_select values_clause simple_select values_clause
PLpgSQL_Expr PLAssignStmt
%type <node> alter_column_default opclass_item opclass_drop alter_using %type <node> alter_column_default opclass_item opclass_drop alter_using
%type <ival> add_drop opt_asc_desc opt_nulls_order %type <ival> add_drop opt_asc_desc opt_nulls_order
...@@ -535,7 +536,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -535,7 +536,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> ColId ColLabel BareColLabel %type <str> ColId ColLabel BareColLabel
%type <str> NonReservedWord NonReservedWord_or_Sconst %type <str> NonReservedWord NonReservedWord_or_Sconst
%type <str> var_name type_function_name param_name %type <str> var_name type_function_name param_name
%type <str> createdb_opt_name %type <str> createdb_opt_name plassign_target
%type <node> var_value zone_value %type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by %type <rolespec> auth_ident RoleSpec opt_granted_by
...@@ -731,6 +732,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -731,6 +732,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* something other than the usual list of SQL commands. * something other than the usual list of SQL commands.
*/ */
%token MODE_TYPE_NAME %token MODE_TYPE_NAME
%token MODE_PLPGSQL_EXPR
%token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
/* Precedence: lowest to highest */ /* Precedence: lowest to highest */
...@@ -810,6 +815,32 @@ parse_toplevel: ...@@ -810,6 +815,32 @@ parse_toplevel:
{ {
pg_yyget_extra(yyscanner)->parsetree = list_make1($2); 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 ...@@ -15024,6 +15055,72 @@ role_list: RoleSpec
{ $$ = lappend($1, $3); } { $$ = 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. * Name classification hierarchy.
* *
......
...@@ -3098,6 +3098,14 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, ...@@ -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; return result;
} }
......
...@@ -34,17 +34,6 @@ ...@@ -34,17 +34,6 @@
static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle, static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
Var *var, int levelsup); 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, static Node *transformAssignmentSubscripts(ParseState *pstate,
Node *basenode, Node *basenode,
const char *targetName, const char *targetName,
...@@ -56,6 +45,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate, ...@@ -56,6 +45,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
List *indirection, List *indirection,
ListCell *next_indirection, ListCell *next_indirection,
Node *rhs, Node *rhs,
CoercionContext ccontext,
int location); int location);
static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
bool make_target_entry); bool make_target_entry);
...@@ -561,6 +551,7 @@ transformAssignedExpr(ParseState *pstate, ...@@ -561,6 +551,7 @@ transformAssignedExpr(ParseState *pstate,
indirection, indirection,
list_head(indirection), list_head(indirection),
(Node *) expr, (Node *) expr,
COERCION_ASSIGNMENT,
location); location);
} }
else else
...@@ -642,15 +633,15 @@ updateTargetListEntry(ParseState *pstate, ...@@ -642,15 +633,15 @@ updateTargetListEntry(ParseState *pstate,
/* /*
* Process indirection (field selection or subscripting) of the target * Process indirection (field selection or subscripting) of the target
* column in INSERT/UPDATE. This routine recurses for multiple levels * column in INSERT/UPDATE/assignment. This routine recurses for multiple
* of indirection --- but note that several adjacent A_Indices nodes in * levels of indirection --- but note that several adjacent A_Indices nodes
* the indirection list are treated as a single multidimensional subscript * in the indirection list are treated as a single multidimensional subscript
* operation. * operation.
* *
* In the initial call, basenode is a Var for the target column in UPDATE, * 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, * or a null Const of the target's type in INSERT, or a Param for the target
* basenode is NULL, indicating that a substitute node should be consed up if * variable in PL/pgSQL assignment. In recursive calls, basenode is NULL,
* needed. * 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 * 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 * targetIsSubscripting is true if we're subscripting it. These are just for
...@@ -667,12 +658,16 @@ updateTargetListEntry(ParseState *pstate, ...@@ -667,12 +658,16 @@ updateTargetListEntry(ParseState *pstate,
* rhs is the already-transformed value to be assigned; note it has not been * rhs is the already-transformed value to be assigned; note it has not been
* coerced to any particular type. * 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 * 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 * 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, * might want to decorate indirection cells with their own location info,
* in which case the location argument could probably be dropped.) * in which case the location argument could probably be dropped.)
*/ */
static Node * Node *
transformAssignmentIndirection(ParseState *pstate, transformAssignmentIndirection(ParseState *pstate,
Node *basenode, Node *basenode,
const char *targetName, const char *targetName,
...@@ -683,6 +678,7 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -683,6 +678,7 @@ transformAssignmentIndirection(ParseState *pstate,
List *indirection, List *indirection,
ListCell *indirection_cell, ListCell *indirection_cell,
Node *rhs, Node *rhs,
CoercionContext ccontext,
int location) int location)
{ {
Node *result; Node *result;
...@@ -757,6 +753,7 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -757,6 +753,7 @@ transformAssignmentIndirection(ParseState *pstate,
indirection, indirection,
i, i,
rhs, rhs,
ccontext,
location); location);
} }
...@@ -807,6 +804,7 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -807,6 +804,7 @@ transformAssignmentIndirection(ParseState *pstate,
indirection, indirection,
lnext(indirection, i), lnext(indirection, i),
rhs, rhs,
ccontext,
location); location);
/* and build a FieldStore node */ /* and build a FieldStore node */
...@@ -845,6 +843,7 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -845,6 +843,7 @@ transformAssignmentIndirection(ParseState *pstate,
indirection, indirection,
NULL, NULL,
rhs, rhs,
ccontext,
location); location);
} }
...@@ -853,7 +852,7 @@ transformAssignmentIndirection(ParseState *pstate, ...@@ -853,7 +852,7 @@ transformAssignmentIndirection(ParseState *pstate,
result = coerce_to_target_type(pstate, result = coerce_to_target_type(pstate,
rhs, exprType(rhs), rhs, exprType(rhs),
targetTypeId, targetTypMod, targetTypeId, targetTypMod,
COERCION_ASSIGNMENT, ccontext,
COERCE_IMPLICIT_CAST, COERCE_IMPLICIT_CAST,
-1); -1);
if (result == NULL) if (result == NULL)
...@@ -898,6 +897,7 @@ transformAssignmentSubscripts(ParseState *pstate, ...@@ -898,6 +897,7 @@ transformAssignmentSubscripts(ParseState *pstate,
List *indirection, List *indirection,
ListCell *next_indirection, ListCell *next_indirection,
Node *rhs, Node *rhs,
CoercionContext ccontext,
int location) int location)
{ {
Node *result; Node *result;
...@@ -949,6 +949,7 @@ transformAssignmentSubscripts(ParseState *pstate, ...@@ -949,6 +949,7 @@ transformAssignmentSubscripts(ParseState *pstate,
indirection, indirection,
next_indirection, next_indirection,
rhs, rhs,
ccontext,
location); location);
/* /*
...@@ -969,7 +970,7 @@ transformAssignmentSubscripts(ParseState *pstate, ...@@ -969,7 +970,7 @@ transformAssignmentSubscripts(ParseState *pstate,
result = coerce_to_target_type(pstate, result = coerce_to_target_type(pstate,
result, resulttype, result, resulttype,
targetTypeId, targetTypMod, targetTypeId, targetTypMod,
COERCION_ASSIGNMENT, ccontext,
COERCE_IMPLICIT_CAST, COERCE_IMPLICIT_CAST,
-1); -1);
/* can fail if we had int2vector/oidvector, but not for true domains */ /* can fail if we had int2vector/oidvector, but not for true domains */
......
...@@ -57,7 +57,11 @@ raw_parser(const char *str, RawParseMode mode) ...@@ -57,7 +57,11 @@ raw_parser(const char *str, RawParseMode mode)
/* this array is indexed by RawParseMode enum */ /* this array is indexed by RawParseMode enum */
static const int mode_token[] = { static const int mode_token[] = {
0, /* RAW_PARSE_DEFAULT */ 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; yyextra.have_lookahead = true;
......
...@@ -2313,6 +2313,10 @@ CreateCommandTag(Node *parsetree) ...@@ -2313,6 +2313,10 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_SELECT; tag = CMDTAG_SELECT;
break; break;
case T_PLAssignStmt:
tag = CMDTAG_SELECT;
break;
/* utility statements --- same whether raw or cooked */ /* utility statements --- same whether raw or cooked */
case T_TransactionStmt: case T_TransactionStmt:
{ {
...@@ -3181,6 +3185,10 @@ GetCommandLogLevel(Node *parsetree) ...@@ -3181,6 +3185,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_ALL; lev = LOGSTMT_ALL;
break; break;
case T_PLAssignStmt:
lev = LOGSTMT_ALL;
break;
/* utility statements --- same whether raw or cooked */ /* utility statements --- same whether raw or cooked */
case T_TransactionStmt: case T_TransactionStmt:
lev = LOGSTMT_ALL; lev = LOGSTMT_ALL;
......
...@@ -314,6 +314,7 @@ typedef enum NodeTag ...@@ -314,6 +314,7 @@ typedef enum NodeTag
T_DeleteStmt, T_DeleteStmt,
T_UpdateStmt, T_UpdateStmt,
T_SelectStmt, T_SelectStmt,
T_PLAssignStmt,
T_AlterTableStmt, T_AlterTableStmt,
T_AlterTableCmd, T_AlterTableCmd,
T_AlterDomainStmt, T_AlterDomainStmt,
......
...@@ -1675,6 +1675,25 @@ typedef struct SetOperationStmt ...@@ -1675,6 +1675,25 @@ typedef struct SetOperationStmt
} 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) * Other Statements (no optimizations required)
* *
......
...@@ -457,6 +457,7 @@ typedef enum CoercionContext ...@@ -457,6 +457,7 @@ typedef enum CoercionContext
{ {
COERCION_IMPLICIT, /* coercion in context of expression */ COERCION_IMPLICIT, /* coercion in context of expression */
COERCION_ASSIGNMENT, /* coercion in context of assignment */ COERCION_ASSIGNMENT, /* coercion in context of assignment */
COERCION_PLPGSQL, /* if no assignment cast, use CoerceViaIO */
COERCION_EXPLICIT /* explicit cast operation */ COERCION_EXPLICIT /* explicit cast operation */
} CoercionContext; } CoercionContext;
......
...@@ -36,6 +36,18 @@ extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle, ...@@ -36,6 +36,18 @@ extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle,
char *colname, int attrno, char *colname, int attrno,
List *indirection, List *indirection,
int location); 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, extern List *checkInsertTargets(ParseState *pstate, List *cols,
List **attrnos); List **attrnos);
extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var, extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
......
...@@ -27,12 +27,21 @@ ...@@ -27,12 +27,21 @@
* RAW_PARSE_TYPE_NAME: parse a type name, and return a one-element List * RAW_PARSE_TYPE_NAME: parse a type name, and return a one-element List
* containing a TypeName node. * 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 typedef enum
{ {
RAW_PARSE_DEFAULT = 0, 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; } RawParseMode;
/* Values for the backslash_quote GUC */ /* Values for the backslash_quote GUC */
......
...@@ -70,7 +70,11 @@ my %replace_types = ( ...@@ -70,7 +70,11 @@ my %replace_types = (
'ColId' => 'ignore', 'ColId' => 'ignore',
'type_function_name' => 'ignore', 'type_function_name' => 'ignore',
'ColLabel' => '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 # these replace_line commands excise certain keywords from the core keyword
# lists. Be sure to account for these in ColLabel and related productions. # lists. Be sure to account for these in ColLabel and related productions.
......
...@@ -32,7 +32,7 @@ DATA = plpgsql.control plpgsql--1.0.sql ...@@ -32,7 +32,7 @@ DATA = plpgsql.control plpgsql--1.0.sql
REGRESS_OPTS = --dbname=$(PL_TESTDB) 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_record plpgsql_cache plpgsql_simple plpgsql_transaction \
plpgsql_trap plpgsql_trigger plpgsql_varprops plpgsql_trap plpgsql_trigger plpgsql_varprops
......
--
-- 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
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
-- --
create type two_int4s as (f1 int4, f2 int4); create type two_int4s as (f1 int4, f2 int4);
create type two_int8s as (q1 int8, q2 int8); 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 -- base-case return of a composite type
create function retc(int) returns two_int8s language plpgsql as create function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1,1)::two_int8s; end $$; $$ begin return row($1,1)::two_int8s; end $$;
...@@ -82,6 +83,88 @@ begin ...@@ -82,6 +83,88 @@ begin
end$$; end$$;
NOTICE: c4 = (1,2) NOTICE: c4 = (1,2)
NOTICE: c8 = (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 $$ <<b>> 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 $$ <<b>> 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 $$ <<b>> 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 $$ <<b>> 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 $$ <<b>> 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 $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
ERROR: "b.d.c2" is not a known variable
LINE 1: do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end ...
^
-- check passing composite result to another function -- check passing composite result to another function
create function getq1(two_int8s) returns int8 language plpgsql as $$ create function getq1(two_int8s) returns int8 language plpgsql as $$
declare r two_int8s; begin r := $1; return r.q1; end $$; declare r two_int8s; begin r := $1; return r.q1; end $$;
...@@ -188,7 +271,7 @@ NOTICE: r1.q1 = <NULL> ...@@ -188,7 +271,7 @@ NOTICE: r1.q1 = <NULL>
NOTICE: r1.q2 = <NULL> NOTICE: r1.q2 = <NULL>
NOTICE: r1 = <NULL> NOTICE: r1 = <NULL>
ERROR: record "r1" has no field "nosuchfield" 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 PL/pgSQL function inline_code_block line 7 at RAISE
-- records, not so much -- records, not so much
do $$ do $$
...@@ -202,7 +285,7 @@ end$$; ...@@ -202,7 +285,7 @@ end$$;
NOTICE: r1 = <NULL> NOTICE: r1 = <NULL>
ERROR: record "r1" is not assigned yet ERROR: record "r1" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate. 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 PL/pgSQL function inline_code_block line 5 at RAISE
-- but OK if you assign first -- but OK if you assign first
do $$ do $$
...@@ -220,7 +303,7 @@ NOTICE: r1.f1 = 1 ...@@ -220,7 +303,7 @@ NOTICE: r1.f1 = 1
NOTICE: r1.f2 = 2 NOTICE: r1.f2 = 2
NOTICE: r1 = (1,2) NOTICE: r1 = (1,2)
ERROR: record "r1" has no field "nosuchfield" 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 PL/pgSQL function inline_code_block line 9 at RAISE
-- check repeated assignments to composite fields -- check repeated assignments to composite fields
create table some_table (id int, data text); create table some_table (id int, data text);
...@@ -431,7 +514,7 @@ create function getf3(x mutable) returns int language plpgsql as ...@@ -431,7 +514,7 @@ create function getf3(x mutable) returns int language plpgsql as
$$ begin return x.f3; end $$; $$ begin return x.f3; end $$;
select getf3(null::mutable); -- doesn't work yet select getf3(null::mutable); -- doesn't work yet
ERROR: record "x" has no field "f3" 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 PL/pgSQL function getf3(mutable) line 1 at RETURN
alter table mutable add column f3 int; alter table mutable add column f3 int;
select getf3(null::mutable); -- now it works select getf3(null::mutable); -- now it works
......
...@@ -76,7 +76,7 @@ begin ...@@ -76,7 +76,7 @@ begin
raise notice 'x = %', x; raise notice 'x = %', x;
end$$; end$$;
ERROR: division by zero 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 PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
do $$ do $$
declare x bigint[] := array[1,3,5]; declare x bigint[] := array[1,3,5];
......
...@@ -1458,7 +1458,8 @@ plpgsql_parse_dblword(char *word1, char *word2, ...@@ -1458,7 +1458,8 @@ plpgsql_parse_dblword(char *word1, char *word2,
/* /*
* We should do nothing in DECLARE sections. In SQL expressions, we * We should do nothing in DECLARE sections. In SQL expressions, we
* really only need to make sure that RECFIELD datums are created when * 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) if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{ {
...@@ -1532,40 +1533,53 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3, ...@@ -1532,40 +1533,53 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
List *idents; List *idents;
int nnames; int nnames;
idents = list_make3(makeString(word1),
makeString(word2),
makeString(word3));
/* /*
* We should do nothing in DECLARE sections. In SQL expressions, we * We should do nothing in DECLARE sections. In SQL expressions, we need
* really only need to make sure that RECFIELD datums are created when * to make sure that RECFIELD datums are created when needed, and we need
* needed. * 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) 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. * reference, else ignore.
*/ */
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, word2, word3, word1, word2, word3,
&nnames); &nnames);
if (ns != NULL && nnames == 2) if (ns != NULL)
{ {
switch (ns->itemtype) switch (ns->itemtype)
{ {
case PLPGSQL_NSTYPE_REC: 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_rec *rec;
PLpgSQL_recfield *new; PLpgSQL_recfield *new;
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]); 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->datum = (PLpgSQL_datum *) new;
wdatum->ident = NULL; wdatum->ident = NULL;
wdatum->quoted = false; /* not used */ wdatum->quoted = false; /* not used */
...@@ -1580,6 +1594,9 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3, ...@@ -1580,6 +1594,9 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
} }
/* Nothing found */ /* Nothing found */
idents = list_make3(makeString(word1),
makeString(word2),
makeString(word3));
cword->idents = idents; cword->idents = idents;
return false; return false;
} }
......
...@@ -4182,7 +4182,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate, ...@@ -4182,7 +4182,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
memset(&options, 0, sizeof(options)); memset(&options, 0, sizeof(options));
options.parserSetup = (ParserSetupHook) plpgsql_parser_setup; options.parserSetup = (ParserSetupHook) plpgsql_parser_setup;
options.parserSetupArg = (void *) expr; options.parserSetupArg = (void *) expr;
options.parseMode = RAW_PARSE_DEFAULT; options.parseMode = expr->parseMode;
options.cursorOptions = cursorOptions; options.cursorOptions = cursorOptions;
plan = SPI_prepare_extended(expr->query, &options); plan = SPI_prepare_extended(expr->query, &options);
if (plan == NULL) if (plan == NULL)
...@@ -8006,10 +8006,14 @@ get_cast_hashentry(PLpgSQL_execstate *estate, ...@@ -8006,10 +8006,14 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
placeholder->collation = get_typcollation(srctype); placeholder->collation = get_typcollation(srctype);
/* /*
* Apply coercion. We use ASSIGNMENT coercion because that's the * Apply coercion. We use the special coercion context
* closest match to plpgsql's historical behavior; in particular, * COERCION_PLPGSQL to match plpgsql's historical behavior, namely
* EXPLICIT coercion would allow silent truncation to a destination * that any cast not available at ASSIGNMENT level will be implemented
* varchar/bpchar's length, which we do not want. * 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 * 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 * 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, ...@@ -8022,7 +8026,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
cast_expr = coerce_to_target_type(NULL, cast_expr = coerce_to_target_type(NULL,
(Node *) placeholder, srctype, (Node *) placeholder, srctype,
dsttype, dsttypmod, dsttype, dsttypmod,
COERCION_ASSIGNMENT, COERCION_PLPGSQL,
COERCE_IMPLICIT_CAST, COERCE_IMPLICIT_CAST,
-1); -1);
...@@ -8030,7 +8034,8 @@ get_cast_hashentry(PLpgSQL_execstate *estate, ...@@ -8030,7 +8034,8 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
* If there's no cast path according to the parser, fall back to using * 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 * an I/O coercion; this is semantically dubious but matches plpgsql's
* historical behavior. We would need something of the sort for * 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) if (cast_expr == NULL)
{ {
...@@ -8339,7 +8344,8 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno) ...@@ -8339,7 +8344,8 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
return; 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)) if (IsA(expr->expr_simple_expr, FuncExpr))
{ {
...@@ -8355,6 +8361,33 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno) ...@@ -8355,6 +8361,33 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
funcid = opexpr->opfuncid; funcid = opexpr->opfuncid;
fargs = opexpr->args; 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 else
return; return;
......
This diff is collapsed.
...@@ -218,8 +218,9 @@ typedef struct PLpgSQL_type ...@@ -218,8 +218,9 @@ typedef struct PLpgSQL_type
*/ */
typedef struct PLpgSQL_expr typedef struct PLpgSQL_expr
{ {
char *query; char *query; /* query string, verbatim from function body */
SPIPlanPtr plan; RawParseMode parseMode; /* raw_parser() mode to use */
SPIPlanPtr plan; /* plan, or NULL if not made yet */
Bitmapset *paramnos; /* all dnos referenced by this query */ Bitmapset *paramnos; /* all dnos referenced by this query */
int rwparam; /* dno of read/write param, or -1 if none */ int rwparam; /* dno of read/write param, or -1 if none */
......
--
-- 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$$;
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
create type two_int4s as (f1 int4, f2 int4); create type two_int4s as (f1 int4, f2 int4);
create type two_int8s as (q1 int8, q2 int8); 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 -- base-case return of a composite type
create function retc(int) returns two_int8s language plpgsql as create function retc(int) returns two_int8s language plpgsql as
...@@ -59,6 +60,47 @@ begin ...@@ -59,6 +60,47 @@ begin
raise notice 'c8 = %', c8; raise notice 'c8 = %', c8;
end$$; 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 $$ <<b>> 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 $$ <<b>> declare c two_int8s; begin b.c.x = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.c.x = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
-- check passing composite result to another function -- check passing composite result to another function
create function getq1(two_int8s) returns int8 language plpgsql as $$ create function getq1(two_int8s) returns int8 language plpgsql as $$
declare r two_int8s; begin r := $1; return r.q1; end $$; declare r two_int8s; begin r := $1; return r.q1; end $$;
......
...@@ -1761,10 +1761,10 @@ select f1(42) as int, f1(4.5) as num; ...@@ -1761,10 +1761,10 @@ select f1(42) as int, f1(4.5) as num;
select f1(point(3,4)); -- fail for lack of + operator select f1(point(3,4)); -- fail for lack of + operator
ERROR: operator does not exist: point + integer 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. 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 CONTEXT: PL/pgSQL function f1(anyelement) line 3 at RETURN
drop function f1(x anyelement); drop function f1(x anyelement);
create function f1(x anyelement) returns anyarray as $$ create function f1(x anyelement) returns anyarray as $$
...@@ -2361,7 +2361,7 @@ begin ...@@ -2361,7 +2361,7 @@ begin
end $$ language plpgsql; end $$ language plpgsql;
select namedparmcursor_test7(); select namedparmcursor_test7();
ERROR: division by zero 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 PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
-- check that line comments work correctly within the argument list (there -- 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 -- is some special handling of this case in the code: the newline after the
...@@ -2574,9 +2574,9 @@ end; $$ language plpgsql; ...@@ -2574,9 +2574,9 @@ end; $$ language plpgsql;
-- blocks -- blocks
select excpt_test1(); select excpt_test1();
ERROR: column "sqlstate" does not exist ERROR: column "sqlstate" does not exist
LINE 1: SELECT sqlstate LINE 1: sqlstate
^ ^
QUERY: SELECT sqlstate QUERY: sqlstate
CONTEXT: PL/pgSQL function excpt_test1() line 3 at RAISE CONTEXT: PL/pgSQL function excpt_test1() line 3 at RAISE
create function excpt_test2() returns void as $$ create function excpt_test2() returns void as $$
begin begin
...@@ -2589,9 +2589,9 @@ end; $$ language plpgsql; ...@@ -2589,9 +2589,9 @@ end; $$ language plpgsql;
-- should fail -- should fail
select excpt_test2(); select excpt_test2();
ERROR: column "sqlstate" does not exist ERROR: column "sqlstate" does not exist
LINE 1: SELECT sqlstate LINE 1: sqlstate
^ ^
QUERY: SELECT sqlstate QUERY: sqlstate
CONTEXT: PL/pgSQL function excpt_test2() line 5 at RAISE CONTEXT: PL/pgSQL function excpt_test2() line 5 at RAISE
create function excpt_test3() returns void as $$ create function excpt_test3() returns void as $$
begin begin
...@@ -4467,11 +4467,11 @@ end ...@@ -4467,11 +4467,11 @@ end
$$; $$;
select fail(); select fail();
ERROR: division by zero ERROR: division by zero
CONTEXT: SQL statement "SELECT 1/0" CONTEXT: SQL expression "1/0"
PL/pgSQL function fail() line 3 at RETURN PL/pgSQL function fail() line 3 at RETURN
select fail(); select fail();
ERROR: division by zero ERROR: division by zero
CONTEXT: SQL statement "SELECT 1/0" CONTEXT: SQL expression "1/0"
PL/pgSQL function fail() line 3 at RETURN PL/pgSQL function fail() line 3 at RETURN
drop function fail(); drop function fail();
-- Test handling of string literals. -- Test handling of string literals.
...@@ -4497,10 +4497,10 @@ HINT: Use the escape string syntax for backslashes, e.g., E'\\'. ...@@ -4497,10 +4497,10 @@ HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
select strtest(); select strtest();
NOTICE: foo\bar!baz NOTICE: foo\bar!baz
WARNING: nonstandard use of \\ in a string literal 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'\\'. HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
QUERY: SELECT 'foo\\bar\041baz' QUERY: 'foo\\bar\041baz'
strtest strtest
------------- -------------
foo\bar!baz foo\bar!baz
...@@ -5621,9 +5621,9 @@ ALTER TABLE alter_table_under_transition_tables ...@@ -5621,9 +5621,9 @@ ALTER TABLE alter_table_under_transition_tables
UPDATE alter_table_under_transition_tables UPDATE alter_table_under_transition_tables
SET id = id; SET id = id;
ERROR: column "name" does not exist ERROR: column "name" does not exist
LINE 1: SELECT (SELECT string_agg(id || '=' || name, ',') FROM d) LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d)
^ ^
QUERY: SELECT (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 CONTEXT: PL/pgSQL function alter_table_under_transition_tables_upd_func() line 3 at RAISE
-- --
-- Test multiple reference to a transition table -- Test multiple reference to a transition table
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment