1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Support assignment to subfields of composite columns in UPDATE and INSERT.

As a side effect, cause subscripts in INSERT targetlists to do something
more or less sensible; previously we evaluated such subscripts and then
effectively ignored them.  Another side effect is that UPDATE-ing an
element or slice of an array value that is NULL now produces a non-null
result, namely an array containing just the assigned-to positions.
This commit is contained in:
Tom Lane
2004-06-09 19:08:20 +00:00
parent 3a0df651da
commit 7e64dbc6b5
27 changed files with 1468 additions and 574 deletions

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.138 2004/05/30 23:40:35 neilc Exp $
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.139 2004/06/09 19:08:17 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -50,6 +50,7 @@ static void rewriteTargetList(Query *parsetree, Relation target_relation);
static TargetEntry *process_matched_tle(TargetEntry *src_tle,
TargetEntry *prior_tle,
const char *attrName);
static Node *get_assignment_input(Node *node);
static void markQueryForUpdate(Query *qry, bool skipOldNew);
static List *matchLocks(CmdType event, RuleLock *rulelocks,
int varno, Query *parsetree);
@ -273,8 +274,9 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
* expressions.
*
* 2. Merge multiple entries for the same target attribute, or declare error
* if we can't. Presently, multiple entries are only allowed for UPDATE of
* an array field, for example "UPDATE table SET foo[2] = 42, foo[4] = 43".
* if we can't. Multiple entries are only allowed for INSERT/UPDATE of
* portions of an array or record field, for example
* UPDATE table SET foo[2] = 42, foo[4] = 43;
* We can merge such operations into a single assignment op. Essentially,
* the expression we want to produce in this case is like
* foo = array_set(array_set(foo, 2, 42), 4, 43)
@ -431,8 +433,12 @@ process_matched_tle(TargetEntry *src_tle,
const char *attrName)
{
Resdom *resdom = src_tle->resdom;
Node *src_expr;
Node *prior_expr;
Node *src_input;
Node *prior_input;
Node *priorbottom;
ArrayRef *newexpr;
Node *newexpr;
if (prior_tle == NULL)
{
@ -443,30 +449,55 @@ process_matched_tle(TargetEntry *src_tle,
return src_tle;
}
/*
/*----------
* Multiple assignments to same attribute. Allow only if all are
* array-assign operators with same bottom array object.
* FieldStore or ArrayRef assignment operations. This is a bit
* tricky because what we may actually be looking at is a nest of
* such nodes; consider
* UPDATE tab SET col.fld1.subfld1 = x, col.fld2.subfld2 = y
* The two expressions produced by the parser will look like
* FieldStore(col, fld1, FieldStore(placeholder, subfld1, x))
* FieldStore(col, fld2, FieldStore(placeholder, subfld2, x))
* However, we can ignore the substructure and just consider the top
* FieldStore or ArrayRef from each assignment, because it works to
* combine these as
* FieldStore(FieldStore(col, fld1,
* FieldStore(placeholder, subfld1, x)),
* fld2, FieldStore(placeholder, subfld2, x))
* Note the leftmost expression goes on the inside so that the
* assignments appear to occur left-to-right.
*
* For FieldStore, instead of nesting we can generate a single
* FieldStore with multiple target fields. We must nest when
* ArrayRefs are involved though.
*----------
*/
if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) ||
((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
((ArrayRef *) src_tle->expr)->refrestype !=
((ArrayRef *) prior_tle->expr)->refrestype)
src_expr = (Node *) src_tle->expr;
prior_expr = (Node *) prior_tle->expr;
src_input = get_assignment_input(src_expr);
prior_input = get_assignment_input(prior_expr);
if (src_input == NULL ||
prior_input == NULL ||
exprType(src_expr) != exprType(prior_expr))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple assignments to same column \"%s\"",
attrName)));
/*
* Prior TLE could be a nest of ArrayRefs if we do this more than
* Prior TLE could be a nest of assignments if we do this more than
* once.
*/
priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr;
while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
((ArrayRef *) priorbottom)->refassgnexpr != NULL)
priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr;
if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
priorbottom = prior_input;
for (;;)
{
Node *newbottom = get_assignment_input(priorbottom);
if (newbottom == NULL)
break; /* found the original Var reference */
priorbottom = newbottom;
}
if (!equal(priorbottom, src_input))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple assignments to same column \"%s\"",
@ -475,13 +506,70 @@ process_matched_tle(TargetEntry *src_tle,
/*
* Looks OK to nest 'em.
*/
newexpr = makeNode(ArrayRef);
memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
newexpr->refexpr = prior_tle->expr;
if (IsA(src_expr, FieldStore))
{
FieldStore *fstore = makeNode(FieldStore);
if (IsA(prior_expr, FieldStore))
{
/* combine the two */
memcpy(fstore, prior_expr, sizeof(FieldStore));
fstore->newvals =
list_concat(list_copy(((FieldStore *) prior_expr)->newvals),
list_copy(((FieldStore *) src_expr)->newvals));
fstore->fieldnums =
list_concat(list_copy(((FieldStore *) prior_expr)->fieldnums),
list_copy(((FieldStore *) src_expr)->fieldnums));
}
else
{
/* general case, just nest 'em */
memcpy(fstore, src_expr, sizeof(FieldStore));
fstore->arg = (Expr *) prior_expr;
}
newexpr = (Node *) fstore;
}
else if (IsA(src_expr, ArrayRef))
{
ArrayRef *aref = makeNode(ArrayRef);
memcpy(aref, src_expr, sizeof(ArrayRef));
aref->refexpr = (Expr *) prior_expr;
newexpr = (Node *) aref;
}
else
{
elog(ERROR, "can't happen");
newexpr = NULL;
}
return makeTargetEntry(resdom, (Expr *) newexpr);
}
/*
* If node is an assignment node, return its input; else return NULL
*/
static Node *
get_assignment_input(Node *node)
{
if (node == NULL)
return NULL;
if (IsA(node, FieldStore))
{
FieldStore *fstore = (FieldStore *) node;
return (Node *) fstore->arg;
}
else if (IsA(node, ArrayRef))
{
ArrayRef *aref = (ArrayRef *) node;
if (aref->refassgnexpr == NULL)
return NULL;
return (Node *) aref->refexpr;
}
return NULL;
}
/*
* Make an expression tree for the default value for a column.