v1-0004-rewrite-plpgsql-assignment.patch
text/x-diff
Filename: v1-0004-rewrite-plpgsql-assignment.patch
Type: text/x-diff
Part: 3
Patch
Format: unified
Series: patch v1-0004
| File | + | − |
|---|---|---|
| src/backend/commands/functioncmds.c | 1 | 0 |
| src/backend/executor/spi.c | 5 | 0 |
| src/backend/nodes/copyfuncs.c | 17 | 0 |
| src/backend/nodes/equalfuncs.c | 15 | 0 |
| src/backend/nodes/nodeFuncs.c | 10 | 0 |
| src/backend/nodes/outfuncs.c | 15 | 0 |
| src/backend/parser/analyze.c | 240 | 0 |
| src/backend/parser/gram.y | 51 | 2 |
| src/backend/parser/parse_coerce.c | 8 | 0 |
| src/backend/parser/parser.c | 4 | 1 |
| src/backend/parser/parse_target.c | 21 | 20 |
| src/backend/tcop/utility.c | 8 | 0 |
| src/include/nodes/nodes.h | 1 | 0 |
| src/include/nodes/parsenodes.h | 19 | 0 |
| src/include/nodes/primnodes.h | 1 | 0 |
| src/include/parser/parser.h | 8 | 1 |
| src/include/parser/parse_target.h | 12 | 0 |
| src/interfaces/ecpg/preproc/parse.pl | 4 | 1 |
| src/pl/plpgsql/src/pl_exec.c | 40 | 7 |
| src/pl/plpgsql/src/pl_gram.y | 27 | 3 |
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c3ce480c8f..edea27697e 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 f529707458..eb0d9f51fb 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2769,6 +2769,11 @@ _SPI_error_callback(void *arg)
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 70f8b718e0..990daf1c98 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 541e0e6b48..7cbd31273d 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 963f71e99d..f66d0e5fd5 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 d78b16ed1d..4871702f02 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 084e00f73d..bf2824c00f 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 4c58b46651..9101435f7b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -294,7 +294,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
- PLpgSQL_Expr
+ PLpgSQL_Expr PLAssignStmt
%type <node> alter_column_default opclass_item opclass_drop alter_using
%type <ival> add_drop opt_asc_desc opt_nulls_order
@@ -536,7 +536,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> ColId ColLabel BareColLabel
%type <str> NonReservedWord NonReservedWord_or_Sconst
%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 <rolespec> auth_ident RoleSpec opt_granted_by
@@ -733,6 +733,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%token MODE_TYPE_NAME
%token MODE_PLPGSQL_EXPR
+%token MODE_PLPGSQL_ASSIGN1
+%token MODE_PLPGSQL_ASSIGN2
+%token MODE_PLPGSQL_ASSIGN3
/* Precedence: lowest to highest */
@@ -815,6 +818,27 @@ stmtblock: stmtmulti
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));
+ }
;
/*
@@ -15069,6 +15093,31 @@ PLpgSQL_Expr: opt_target_list
}
;
+/*
+ * 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 da6c3ae4b5..462e2af74e 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -2815,6 +2815,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 3dda8e2847..8f2b0f18e0 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 71df8ef022..291706c593 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -58,7 +58,10 @@ raw_parser(const char *str, RawParseMode mode)
static const int mode_token[] = {
0, /* RAW_PARSE_DEFAULT */
MODE_TYPE_NAME, /* RAW_PARSE_TYPE_NAME */
- MODE_PLPGSQL_EXPR /* RAW_PARSE_PLPGSQL_EXPR */
+ 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 a42ead7d69..fdcabe6a48 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 3684f87a88..2d445d03db 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 48a79a7657..8e09a457ab 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 dd85908fe2..71a58804dc 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 7039df29cb..5ce8c3666e 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 3b7dab17ef..ac89f2b93f 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -29,12 +29,19 @@
*
* 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_PLPGSQL_EXPR
+ 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 2b254c2b77..cea6dd6517 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -71,7 +71,10 @@ my %replace_types = (
'type_function_name' => 'ignore',
'ColLabel' => 'ignore',
'Sconst' => 'ignore',
- 'PLpgSQL_Expr' => '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/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index f56dcd0e79..cb5c7f9fea 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8008,10 +8008,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
@@ -8024,7 +8028,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);
@@ -8032,7 +8036,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)
{
@@ -8341,7 +8346,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))
{
@@ -8357,6 +8363,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 3b36220d73..051544a3b4 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -973,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;
}