Skip to content

Commit c9d5298

Browse files
committed
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 c7aba7c. 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/[email protected]
1 parent 844fe9f commit c9d5298

32 files changed

+1081
-159
lines changed

contrib/hstore/expected/hstore.out

+4
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,10 @@ select f2 from test_json_agg;
15831583
"d"=>NULL, "x"=>"xyzzy"
15841584
(3 rows)
15851585

1586+
-- Test subscripting in plpgsql
1587+
do $$ declare h hstore;
1588+
begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$;
1589+
NOTICE: h = "a"=>"b", h[a] = b
15861590
-- Check the hstore_hash() and hstore_hash_extended() function explicitly.
15871591
SELECT v as value, hstore_hash(v)::bit(32) as standard,
15881592
hstore_hash_extended(v, 0)::bit(32) as extended0,

contrib/hstore/sql/hstore.sql

+4
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ select f2['d':'e'] from test_json_agg; -- error
372372
update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy';
373373
select f2 from test_json_agg;
374374

375+
-- Test subscripting in plpgsql
376+
do $$ declare h hstore;
377+
begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$;
378+
375379
-- Check the hstore_hash() and hstore_hash_extended() function explicitly.
376380
SELECT v as value, hstore_hash(v)::bit(32) as standard,
377381
hstore_hash_extended(v, 0)::bit(32) as extended0,

doc/src/sgml/plpgsql.sgml

+19-2
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,8 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
946946
database engine. The expression must yield a single value (possibly
947947
a row value, if the variable is a row or record variable). The target
948948
variable can be a simple variable (optionally qualified with a block
949-
name), a field of a row or record variable, or an element of an array
950-
that is a simple variable or field. Equal (<literal>=</literal>) can be
949+
name), a field of a row or record target, or an element or slice of
950+
an array target. Equal (<literal>=</literal>) can be
951951
used instead of PL/SQL-compliant <literal>:=</literal>.
952952
</para>
953953

@@ -968,8 +968,25 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
968968
<programlisting>
969969
tax := subtotal * 0.06;
970970
my_record.user_id := 20;
971+
my_array[j] := 20;
972+
my_array[1:3] := array[1,2,3];
973+
complex_array[n].realpart = 12.3;
971974
</programlisting>
972975
</para>
976+
977+
<para>
978+
It's useful to know that what follows the assignment operator is
979+
essentially treated as a <literal>SELECT</literal> command; as long
980+
as it returns a single row and column, it will work. Thus for example
981+
one can write something like
982+
<programlisting>
983+
total_sales := sum(quantity) from sales;
984+
</programlisting>
985+
This provides an effect similar to the single-row <literal>SELECT
986+
... INTO</literal> syntax described in
987+
<xref linkend="plpgsql-statements-sql-onerow"/>. However, that syntax
988+
is more portable.
989+
</para>
973990
</sect2>
974991

975992
<sect2 id="plpgsql-statements-sql-noresult">

src/backend/commands/functioncmds.c

+1
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,7 @@ CreateCast(CreateCastStmt *stmt)
16281628
case COERCION_ASSIGNMENT:
16291629
castcontext = COERCION_CODE_ASSIGNMENT;
16301630
break;
1631+
/* COERCION_PLPGSQL is intentionally not covered here */
16311632
case COERCION_EXPLICIT:
16321633
castcontext = COERCION_CODE_EXPLICIT;
16331634
break;

src/backend/executor/spi.c

+46-8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ static _SPI_connection *_SPI_current = NULL;
5151
static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
5252
static int _SPI_connected = -1; /* current stack index */
5353

54+
typedef struct SPICallbackArg
55+
{
56+
const char *query;
57+
RawParseMode mode;
58+
} SPICallbackArg;
59+
5460
static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
5561
ParamListInfo paramLI, bool read_only);
5662

@@ -1479,6 +1485,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
14791485
Snapshot snapshot;
14801486
MemoryContext oldcontext;
14811487
Portal portal;
1488+
SPICallbackArg spicallbackarg;
14821489
ErrorContextCallback spierrcontext;
14831490

14841491
/*
@@ -1533,8 +1540,10 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
15331540
* Setup error traceback support for ereport(), in case GetCachedPlan
15341541
* throws an error.
15351542
*/
1543+
spicallbackarg.query = plansource->query_string;
1544+
spicallbackarg.mode = plan->parse_mode;
15361545
spierrcontext.callback = _SPI_error_callback;
1537-
spierrcontext.arg = unconstify(char *, plansource->query_string);
1546+
spierrcontext.arg = &spicallbackarg;
15381547
spierrcontext.previous = error_context_stack;
15391548
error_context_stack = &spierrcontext;
15401549

@@ -1952,6 +1961,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
19521961
{
19531962
CachedPlanSource *plansource;
19541963
CachedPlan *cplan;
1964+
SPICallbackArg spicallbackarg;
19551965
ErrorContextCallback spierrcontext;
19561966

19571967
Assert(plan->magic == _SPI_PLAN_MAGIC);
@@ -1966,8 +1976,10 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
19661976
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
19671977

19681978
/* Setup error traceback support for ereport() */
1979+
spicallbackarg.query = plansource->query_string;
1980+
spicallbackarg.mode = plan->parse_mode;
19691981
spierrcontext.callback = _SPI_error_callback;
1970-
spierrcontext.arg = unconstify(char *, plansource->query_string);
1982+
spierrcontext.arg = &spicallbackarg;
19711983
spierrcontext.previous = error_context_stack;
19721984
error_context_stack = &spierrcontext;
19731985

@@ -2094,13 +2106,16 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
20942106
List *raw_parsetree_list;
20952107
List *plancache_list;
20962108
ListCell *list_item;
2109+
SPICallbackArg spicallbackarg;
20972110
ErrorContextCallback spierrcontext;
20982111

20992112
/*
21002113
* Setup error traceback support for ereport()
21012114
*/
2115+
spicallbackarg.query = src;
2116+
spicallbackarg.mode = plan->parse_mode;
21022117
spierrcontext.callback = _SPI_error_callback;
2103-
spierrcontext.arg = unconstify(char *, src);
2118+
spierrcontext.arg = &spicallbackarg;
21042119
spierrcontext.previous = error_context_stack;
21052120
error_context_stack = &spierrcontext;
21062121

@@ -2199,13 +2214,16 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
21992214
List *raw_parsetree_list;
22002215
List *plancache_list;
22012216
ListCell *list_item;
2217+
SPICallbackArg spicallbackarg;
22022218
ErrorContextCallback spierrcontext;
22032219

22042220
/*
22052221
* Setup error traceback support for ereport()
22062222
*/
2223+
spicallbackarg.query = src;
2224+
spicallbackarg.mode = plan->parse_mode;
22072225
spierrcontext.callback = _SPI_error_callback;
2208-
spierrcontext.arg = unconstify(char *, src);
2226+
spierrcontext.arg = &spicallbackarg;
22092227
spierrcontext.previous = error_context_stack;
22102228
error_context_stack = &spierrcontext;
22112229

@@ -2263,15 +2281,18 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
22632281
SPITupleTable *my_tuptable = NULL;
22642282
int res = 0;
22652283
bool pushed_active_snap = false;
2284+
SPICallbackArg spicallbackarg;
22662285
ErrorContextCallback spierrcontext;
22672286
CachedPlan *cplan = NULL;
22682287
ListCell *lc1;
22692288

22702289
/*
22712290
* Setup error traceback support for ereport()
22722291
*/
2292+
spicallbackarg.query = NULL; /* we'll fill this below */
2293+
spicallbackarg.mode = plan->parse_mode;
22732294
spierrcontext.callback = _SPI_error_callback;
2274-
spierrcontext.arg = NULL; /* we'll fill this below */
2295+
spierrcontext.arg = &spicallbackarg;
22752296
spierrcontext.previous = error_context_stack;
22762297
error_context_stack = &spierrcontext;
22772298

@@ -2318,7 +2339,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
23182339
List *stmt_list;
23192340
ListCell *lc2;
23202341

2321-
spierrcontext.arg = unconstify(char *, plansource->query_string);
2342+
spicallbackarg.query = plansource->query_string;
23222343

23232344
/*
23242345
* 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)
27222743
static void
27232744
_SPI_error_callback(void *arg)
27242745
{
2725-
const char *query = (const char *) arg;
2746+
SPICallbackArg *carg = (SPICallbackArg *) arg;
2747+
const char *query = carg->query;
27262748
int syntaxerrposition;
27272749

27282750
if (query == NULL) /* in case arg wasn't set yet */
@@ -2740,7 +2762,23 @@ _SPI_error_callback(void *arg)
27402762
internalerrquery(query);
27412763
}
27422764
else
2743-
errcontext("SQL statement \"%s\"", query);
2765+
{
2766+
/* Use the parse mode to decide how to describe the query */
2767+
switch (carg->mode)
2768+
{
2769+
case RAW_PARSE_PLPGSQL_EXPR:
2770+
errcontext("SQL expression \"%s\"", query);
2771+
break;
2772+
case RAW_PARSE_PLPGSQL_ASSIGN1:
2773+
case RAW_PARSE_PLPGSQL_ASSIGN2:
2774+
case RAW_PARSE_PLPGSQL_ASSIGN3:
2775+
errcontext("PL/pgSQL assignment \"%s\"", query);
2776+
break;
2777+
default:
2778+
errcontext("SQL statement \"%s\"", query);
2779+
break;
2780+
}
2781+
}
27442782
}
27452783

27462784
/*

src/backend/nodes/copyfuncs.c

+17
Original file line numberDiff line numberDiff line change
@@ -3199,6 +3199,20 @@ _copySetOperationStmt(const SetOperationStmt *from)
31993199
return newnode;
32003200
}
32013201

3202+
static PLAssignStmt *
3203+
_copyPLAssignStmt(const PLAssignStmt *from)
3204+
{
3205+
PLAssignStmt *newnode = makeNode(PLAssignStmt);
3206+
3207+
COPY_STRING_FIELD(name);
3208+
COPY_NODE_FIELD(indirection);
3209+
COPY_SCALAR_FIELD(nnames);
3210+
COPY_NODE_FIELD(val);
3211+
COPY_LOCATION_FIELD(location);
3212+
3213+
return newnode;
3214+
}
3215+
32023216
static AlterTableStmt *
32033217
_copyAlterTableStmt(const AlterTableStmt *from)
32043218
{
@@ -5220,6 +5234,9 @@ copyObjectImpl(const void *from)
52205234
case T_SetOperationStmt:
52215235
retval = _copySetOperationStmt(from);
52225236
break;
5237+
case T_PLAssignStmt:
5238+
retval = _copyPLAssignStmt(from);
5239+
break;
52235240
case T_AlterTableStmt:
52245241
retval = _copyAlterTableStmt(from);
52255242
break;

src/backend/nodes/equalfuncs.c

+15
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,18 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
10851085
return true;
10861086
}
10871087

1088+
static bool
1089+
_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
1090+
{
1091+
COMPARE_STRING_FIELD(name);
1092+
COMPARE_NODE_FIELD(indirection);
1093+
COMPARE_SCALAR_FIELD(nnames);
1094+
COMPARE_NODE_FIELD(val);
1095+
COMPARE_LOCATION_FIELD(location);
1096+
1097+
return true;
1098+
}
1099+
10881100
static bool
10891101
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
10901102
{
@@ -3275,6 +3287,9 @@ equal(const void *a, const void *b)
32753287
case T_SetOperationStmt:
32763288
retval = _equalSetOperationStmt(a, b);
32773289
break;
3290+
case T_PLAssignStmt:
3291+
retval = _equalPLAssignStmt(a, b);
3292+
break;
32783293
case T_AlterTableStmt:
32793294
retval = _equalAlterTableStmt(a, b);
32803295
break;

src/backend/nodes/nodeFuncs.c

+10
Original file line numberDiff line numberDiff line change
@@ -3669,6 +3669,16 @@ raw_expression_tree_walker(Node *node,
36693669
return true;
36703670
}
36713671
break;
3672+
case T_PLAssignStmt:
3673+
{
3674+
PLAssignStmt *stmt = (PLAssignStmt *) node;
3675+
3676+
if (walker(stmt->indirection, context))
3677+
return true;
3678+
if (walker(stmt->val, context))
3679+
return true;
3680+
}
3681+
break;
36723682
case T_A_Expr:
36733683
{
36743684
A_Expr *expr = (A_Expr *) node;

src/backend/nodes/outfuncs.c

+15
Original file line numberDiff line numberDiff line change
@@ -2775,6 +2775,18 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
27752775
WRITE_NODE_FIELD(rarg);
27762776
}
27772777

2778+
static void
2779+
_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
2780+
{
2781+
WRITE_NODE_TYPE("PLASSIGN");
2782+
2783+
WRITE_STRING_FIELD(name);
2784+
WRITE_NODE_FIELD(indirection);
2785+
WRITE_INT_FIELD(nnames);
2786+
WRITE_NODE_FIELD(val);
2787+
WRITE_LOCATION_FIELD(location);
2788+
}
2789+
27782790
static void
27792791
_outFuncCall(StringInfo str, const FuncCall *node)
27802792
{
@@ -4211,6 +4223,9 @@ outNode(StringInfo str, const void *obj)
42114223
case T_SelectStmt:
42124224
_outSelectStmt(str, obj);
42134225
break;
4226+
case T_PLAssignStmt:
4227+
_outPLAssignStmt(str, obj);
4228+
break;
42144229
case T_ColumnDef:
42154230
_outColumnDef(str, obj);
42164231
break;

0 commit comments

Comments
 (0)