0001-rewrite-plpgsql-assignment-1.patch
text/x-diff
Filename: 0001-rewrite-plpgsql-assignment-1.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
Series: patch 0001
| File | + | − |
|---|---|---|
| src/backend/commands/functioncmds.c | 1 | 0 |
| src/backend/nodes/copyfuncs.c | 26 | 0 |
| src/backend/nodes/equalfuncs.c | 24 | 0 |
| src/backend/nodes/nodeFuncs.c | 30 | 0 |
| src/backend/nodes/outfuncs.c | 24 | 0 |
| src/backend/parser/analyze.c | 213 | 0 |
| src/backend/parser/gram.y | 70 | 2 |
| src/backend/parser/parse_coerce.c | 8 | 0 |
| src/backend/parser/parse_target.c | 18 | 17 |
| src/backend/tcop/utility.c | 8 | 0 |
| src/include/nodes/nodes.h | 1 | 0 |
| src/include/nodes/parsenodes.h | 35 | 0 |
| src/include/nodes/primnodes.h | 1 | 0 |
| src/include/parser/parse_target.h | 12 | 0 |
| src/pl/plpgsql/src/pl_exec.c | 40 | 7 |
| src/pl/plpgsql/src/pl_gram.y | 8 | 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70f8b718e0..67dab37a54 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3199,6 +3199,29 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ExtensionSetStmt *
+_copyExtensionSetStmt(const ExtensionSetStmt *from)
+{
+ ExtensionSetStmt *newnode = makeNode(ExtensionSetStmt);
+
+ COPY_NODE_FIELD(target);
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(indirection);
+ COPY_NODE_FIELD(val);
+ COPY_NODE_FIELD(fromClause);
+ COPY_NODE_FIELD(whereClause);
+ COPY_NODE_FIELD(groupClause);
+ COPY_NODE_FIELD(havingClause);
+ COPY_NODE_FIELD(windowClause);
+ COPY_NODE_FIELD(sortClause);
+ COPY_NODE_FIELD(limitOffset);
+ COPY_NODE_FIELD(limitCount);
+ COPY_SCALAR_FIELD(limitOption);
+ COPY_NODE_FIELD(lockingClause);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -5220,6 +5243,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ExtensionSetStmt:
+ retval = _copyExtensionSetStmt(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..28884da7ec 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1085,6 +1085,27 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalExtensionSetStmt(const ExtensionSetStmt *a, const ExtensionSetStmt *b)
+{
+ COMPARE_NODE_FIELD(target);
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(indirection);
+ COMPARE_NODE_FIELD(val);
+ COMPARE_NODE_FIELD(fromClause);
+ COMPARE_NODE_FIELD(whereClause);
+ COMPARE_NODE_FIELD(groupClause);
+ COMPARE_NODE_FIELD(havingClause);
+ COMPARE_NODE_FIELD(windowClause);
+ COMPARE_NODE_FIELD(sortClause);
+ COMPARE_NODE_FIELD(limitOffset);
+ COMPARE_NODE_FIELD(limitCount);
+ COMPARE_SCALAR_FIELD(limitOption);
+ COMPARE_NODE_FIELD(lockingClause);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -3275,6 +3296,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ExtensionSetStmt:
+ retval = _equalExtensionSetStmt(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..166fbeddb2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3669,6 +3669,36 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_ExtensionSetStmt:
+ {
+ ExtensionSetStmt *stmt = (ExtensionSetStmt *) node;
+
+ if (walker(stmt->target, context))
+ return true;
+ if (walker(stmt->indirection, context))
+ return true;
+ if (walker(stmt->val, context))
+ return true;
+ if (walker(stmt->fromClause, context))
+ return true;
+ if (walker(stmt->whereClause, context))
+ return true;
+ if (walker(stmt->groupClause, context))
+ return true;
+ if (walker(stmt->havingClause, context))
+ return true;
+ if (walker(stmt->windowClause, context))
+ return true;
+ if (walker(stmt->sortClause, context))
+ return true;
+ if (walker(stmt->limitOffset, context))
+ return true;
+ if (walker(stmt->limitCount, context))
+ return true;
+ if (walker(stmt->lockingClause, 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..ef85556fc0 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2775,6 +2775,27 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outExtensionSetStmt(StringInfo str, const ExtensionSetStmt *node)
+{
+ WRITE_NODE_TYPE("EXTENSIONSET");
+
+ WRITE_NODE_FIELD(target);
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(indirection);
+ WRITE_NODE_FIELD(val);
+ WRITE_NODE_FIELD(fromClause);
+ WRITE_NODE_FIELD(whereClause);
+ WRITE_NODE_FIELD(groupClause);
+ WRITE_NODE_FIELD(havingClause);
+ WRITE_NODE_FIELD(windowClause);
+ WRITE_NODE_FIELD(sortClause);
+ WRITE_NODE_FIELD(limitOffset);
+ WRITE_NODE_FIELD(limitCount);
+ WRITE_ENUM_FIELD(limitOption, LimitOption);
+ WRITE_NODE_FIELD(lockingClause);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -4211,6 +4232,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ExtensionSetStmt:
+ _outExtensionSetStmt(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..9ef1ed4d4c 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 *transformExtensionSetStmt(ParseState *pstate,
+ ExtensionSetStmt *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_ExtensionSetStmt:
+ result = transformExtensionSetStmt(pstate,
+ (ExtensionSetStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -367,6 +376,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_DeleteStmt:
case T_UpdateStmt:
case T_SelectStmt:
+ case T_ExtensionSetStmt:
result = true;
break;
@@ -2393,6 +2403,209 @@ transformReturningList(ParseState *pstate, List *returningList)
}
+/*
+ * transformExtensionSetStmt -
+ * transform an Extension SET Statement,
+ * SET n: name opt_indirection := a_expr ...
+ *
+ * This undocumented syntax allows PL/pgSQL to make use of the regular parser
+ * for assignment statements. Everything after the colon is taken verbatim
+ * from the PL/pgSQL assignment statement, while the integer "n" says how many
+ * dotted names comprise the target ColumnRef. (That part is handled by
+ * gram.y, so we just see a ColumnRef and a suitably-truncated indirection
+ * list here.) 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 *
+transformExtensionSetStmt(ParseState *pstate,
+ ExtensionSetStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ Node *target;
+ Oid targettype;
+ int32 targettypmod;
+ Oid targetcollation;
+ TargetEntry *tle;
+ Oid type_id;
+ Node *qual;
+ ListCell *l;
+
+ /*
+ * 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 *) stmt->target,
+ 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 = stmt->lockingClause;
+
+ /* make WINDOW info available for window functions, too */
+ pstate->p_windowdefs = stmt->windowClause;
+
+ /* process the FROM clause */
+ transformFromClause(pstate, stmt->fromClause);
+
+ /* Transform the assignment source (cf. transformUpdateTargetList) */
+ tle = transformTargetEntry(pstate,
+ stmt->val,
+ NULL,
+ EXPR_KIND_SELECT_TARGET,
+ NULL,
+ false);
+
+ /*
+ * 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 (stmt->indirection)
+ {
+ tle->expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ target,
+ stmt->name,
+ false,
+ targettype,
+ targettypmod,
+ targetcollation,
+ COERCION_PLPGSQL,
+ stmt->indirection,
+ list_head(stmt->indirection),
+ (Node *) tle->expr,
+ 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, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ /* initial processing of HAVING clause is much like WHERE clause */
+ qry->havingQual = transformWhereClause(pstate, stmt->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,
+ stmt->sortClause,
+ &qry->targetList,
+ EXPR_KIND_ORDER_BY,
+ false /* allow SQL92 rules */ );
+
+ qry->groupClause = transformGroupClause(pstate,
+ stmt->groupClause,
+ &qry->groupingSets,
+ &qry->targetList,
+ qry->sortClause,
+ EXPR_KIND_GROUP_BY,
+ false /* allow SQL92 rules */ );
+
+ /* No DISTINCT clause */
+ qry->distinctClause = NIL;
+ qry->hasDistinctOn = false;
+
+ /* transform LIMIT */
+ qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
+ EXPR_KIND_OFFSET, "OFFSET",
+ stmt->limitOption);
+ qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
+ EXPR_KIND_LIMIT, "LIMIT",
+ stmt->limitOption);
+ qry->limitOption = stmt->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, stmt->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 8f341ac006..5279d23b7b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -275,7 +275,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DropCastStmt DropRoleStmt
DropdbStmt DropTableSpaceStmt
DropTransformStmt
- DropUserMappingStmt ExplainStmt FetchStmt
+ DropUserMappingStmt ExplainStmt ExtensionSetStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
@@ -535,7 +535,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 extension_set_target
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
@@ -916,6 +916,7 @@ stmt :
| DropdbStmt
| ExecuteStmt
| ExplainStmt
+ | ExtensionSetStmt
| FetchStmt
| GrantStmt
| GrantRoleStmt
@@ -11041,6 +11042,73 @@ set_target_list:
;
+/*****************************************************************************
+ *
+ * QUERY:
+ * SET n: name opt_indirection := a_expr ...
+ *
+ * This undocumented syntax allows PL/pgSQL to make use of the regular parser
+ * for its assignment statements. The stuff after the a_expr should match
+ * what can appear after the targetlist in SELECT.
+ *****************************************************************************/
+
+ExtensionSetStmt: SET Iconst ':' extension_set_target opt_indirection
+ extension_set_assignment a_expr
+ from_clause where_clause
+ group_clause having_clause window_clause
+ opt_sort_clause opt_select_limit opt_for_locking_clause
+ {
+ ExtensionSetStmt *n = makeNode(ExtensionSetStmt);
+ ColumnRef *cref = makeNode(ColumnRef);
+ int nnames = $2;
+ List *indirection = $5;
+
+ cref->fields = list_make1(makeString($4));
+ cref->location = @4;
+ while (--nnames > 0 && indirection != NIL)
+ {
+ Node *ind = (Node *) linitial(indirection);
+
+ if (!IsA(ind, String))
+ elog(ERROR, "invalid name count in extension SET");
+ cref->fields = lappend(cref->fields, ind);
+ indirection = list_delete_first(indirection);
+ }
+ n->target = cref;
+ n->name = $4;
+ n->indirection = check_indirection(indirection, yyscanner);
+ n->val = $7;
+ n->fromClause = $8;
+ n->whereClause = $9;
+ n->groupClause = $10;
+ n->havingClause = $11;
+ n->windowClause = $12;
+ n->sortClause = $13;
+ if ($14)
+ {
+ n->limitOffset = $14->limitOffset;
+ n->limitCount = $14->limitCount;
+ if (!n->sortClause &&
+ $14->limitOption == LIMIT_OPTION_WITH_TIES)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("WITH TIES cannot be specified without ORDER BY clause")));
+ n->limitOption = $14->limitOption;
+ }
+ n->lockingClause = $15;
+ $$ = (Node *) n;
+ }
+ ;
+
+extension_set_target: ColId { $$ = $1; }
+ | PARAM { $$ = psprintf("$%d", $1); }
+ ;
+
+extension_set_assignment: COLON_EQUALS
+ | '='
+ ;
+
+
/*****************************************************************************
*
* QUERY:
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..55a9a808da 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -34,23 +34,13 @@
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,
Oid targetTypeId,
int32 targetTypMod,
Oid targetCollation,
+ CoercionContext ccontext,
List *subscripts,
bool isSlice,
List *indirection,
@@ -558,6 +548,7 @@ transformAssignedExpr(ParseState *pstate,
attrtype,
attrtypmod,
attrcollation,
+ COERCION_ASSIGNMENT,
indirection,
list_head(indirection),
(Node *) expr,
@@ -648,9 +639,9 @@ updateTargetListEntry(ParseState *pstate,
* 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 extension SET operations. 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
@@ -660,6 +651,10 @@ updateTargetListEntry(ParseState *pstate,
* collation of the object to be assigned to (initially the target column,
* later some subobject).
*
+ * ccontext is the coercion level to use while coercing the "rhs". For
+ * normal statements it'll be COERCION_ASSIGNMENT, but extension SET uses
+ * a special value.
+ *
* indirection is the list of indirection nodes, and indirection_cell is the
* start of the sublist remaining to process. When it's NULL, we're done
* recursing and can just coerce and return the RHS.
@@ -672,7 +667,7 @@ updateTargetListEntry(ParseState *pstate,
* 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,
@@ -680,6 +675,7 @@ transformAssignmentIndirection(ParseState *pstate,
Oid targetTypeId,
int32 targetTypMod,
Oid targetCollation,
+ CoercionContext ccontext,
List *indirection,
ListCell *indirection_cell,
Node *rhs,
@@ -752,6 +748,7 @@ transformAssignmentIndirection(ParseState *pstate,
targetTypeId,
targetTypMod,
targetCollation,
+ ccontext,
subscripts,
isSlice,
indirection,
@@ -804,6 +801,7 @@ transformAssignmentIndirection(ParseState *pstate,
fieldTypeId,
fieldTypMod,
fieldCollation,
+ ccontext,
indirection,
lnext(indirection, i),
rhs,
@@ -840,6 +838,7 @@ transformAssignmentIndirection(ParseState *pstate,
targetTypeId,
targetTypMod,
targetCollation,
+ ccontext,
subscripts,
isSlice,
indirection,
@@ -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)
@@ -893,6 +892,7 @@ transformAssignmentSubscripts(ParseState *pstate,
Oid targetTypeId,
int32 targetTypMod,
Oid targetCollation,
+ CoercionContext ccontext,
List *subscripts,
bool isSlice,
List *indirection,
@@ -946,6 +946,7 @@ transformAssignmentSubscripts(ParseState *pstate,
typeNeeded,
typmodNeeded,
collationNeeded,
+ ccontext,
indirection,
next_indirection,
rhs,
@@ -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/tcop/utility.c b/src/backend/tcop/utility.c
index a42ead7d69..a2e3b332c7 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_ExtensionSetStmt:
+ 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_ExtensionSetStmt:
+ 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..273b8181e4 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_ExtensionSetStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48a79a7657..4d073f862d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1675,6 +1675,41 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/* ----------------------
+ * Extension SET Statement
+ *
+ * Like SelectStmt, this is transformed into a SELECT Query.
+ * However, the targetlist of the result looks more like an UPDATE.
+ * ----------------------
+ */
+typedef struct ExtensionSetStmt
+{
+ NodeTag type;
+
+ /* These fields represent "target opt_indirection := value" */
+ ColumnRef *target; /* ColumnRef for the target variable */
+ char *name; /* initial column name */
+ List *indirection; /* subscripts and field names, or NIL */
+ Node *val; /* the value expression to assign */
+
+ /*
+ * Historically, PL/pgSQL has allowed an assignment to have any decoration
+ * that could occur after the targetlist of SELECT, so we preserve that
+ * capability.
+ */
+ List *fromClause; /* the FROM clause */
+ Node *whereClause; /* WHERE qualification */
+ List *groupClause; /* GROUP BY clauses */
+ Node *havingClause; /* HAVING conditional-expression */
+ List *windowClause; /* WINDOW window_name AS (...), ... */
+ List *sortClause; /* sort clause (a list of SortBy's) */
+ Node *limitOffset; /* # of result tuples to skip */
+ Node *limitCount; /* # of result tuples to return */
+ LimitOption limitOption; /* limit type */
+ List *lockingClause; /* FOR UPDATE (list of LockingClause's) */
+} ExtensionSetStmt;
+
+
/*****************************************************************************
* 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..6f5741f824 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,
+ CoercionContext ccontext,
+ List *indirection,
+ ListCell *indirection_cell,
+ Node *rhs,
+ int location);
extern List *checkInsertTargets(ParseState *pstate, List *cols,
List **attrnos);
extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index ccbc50fc45..22fe037f90 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8005,10 +8005,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
@@ -8021,7 +8025,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);
@@ -8029,7 +8033,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)
{
@@ -8338,7 +8343,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))
{
@@ -8354,6 +8360,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 8227bf0449..561b0dec59 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -949,16 +949,21 @@ stmt_call : K_CALL
}
;
-stmt_assign : assign_var assign_operator expr_until_semi
+stmt_assign : T_DATUM
{
PLpgSQL_stmt_assign *new;
+ int nnames = $1.ident ? 1 : list_length($1.idents);
+ 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_stmt(psprintf("SET %d: ",
+ nnames));
$$ = (PLpgSQL_stmt *)new;
}