Skip to content

Commit 099eab0

Browse files
committed
Fix issue XXX related to Indirection in UPDATE SET()
ruleutils in Citus is based on PostgreSQL source code, but in PostgreSQL ruleutils is not used at the planner stage. For instance, it is assumed after parser that targetList are ordered as they were read, but it's not true after rewriter, the resulting rewrite tree is then provided to planner (and citus), but the ordering of the list is not granted anymore. It's similar to others previous issues reported and still open, as well as to other bugfixes/improvment over time, the most noticable being the ProcessIndirection() which is for domain and similar. However, the implications of this bug are huge for users of `UPDATE SET (...)`: 1. if you used to order by columns order, you're maybe safe: `SET (col1, col2, col3, ...)` 2. if you used not to order by column order: `SET (col2, col1, col3, ...)` then you probably found a problem, or you have one. Note about 1. that despite appearance and your QA, you are at risk: if physical columns ordering is changed (for example after DROPping/ADDing some), the same query which use to apparently works well will silently update other columns... As it is this code is not optimized for performance, not sure it'll be needed.
1 parent 755d626 commit 099eab0

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

src/backend/distributed/deparser/ruleutils_16.c

+153
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ static void get_tablesample_def(TableSampleClause *tablesample,
469469
deparse_context *context);
470470
static void get_opclass_name(Oid opclass, Oid actual_datatype,
471471
StringInfo buf);
472+
static bool is_update_set_with_multiple_columns(List *targetList);
473+
static List *processTargetsIndirection(List *targetList);
474+
static AttrNumber extract_paramid_from_funcexpr(FuncExpr *func);
472475
static Node *processIndirection(Node *node, deparse_context *context);
473476
static void printSubscripts(SubscriptingRef *aref, deparse_context *context);
474477
static char *get_relation_name(Oid relid);
@@ -3545,6 +3548,9 @@ get_update_query_targetlist_def(Query *query, List *targetList,
35453548
}
35463549
}
35473550
}
3551+
if (is_update_set_with_multiple_columns(targetList))
3552+
targetList = processTargetsIndirection(targetList);
3553+
35483554
next_ma_cell = list_head(ma_sublinks);
35493555
cur_ma_sublink = NULL;
35503556
remaining_ma_columns = 0;
@@ -8607,6 +8613,153 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
86078613
ReleaseSysCache(ht_opc);
86088614
}
86098615

8616+
/*
8617+
* helper function to evaluate if we are in an SET (...)
8618+
* Caller is responsible to check the command type (UPDATE)
8619+
*/
8620+
static bool is_update_set_with_multiple_columns(List *targetList)
8621+
{
8622+
ListCell *lc;
8623+
foreach(lc, targetList) {
8624+
TargetEntry *tle = (TargetEntry *) lfirst(lc);
8625+
Node *expr;
8626+
8627+
if (tle->resjunk)
8628+
continue;
8629+
8630+
expr = strip_implicit_coercions((Node *) tle->expr);
8631+
8632+
if (expr && IsA(expr, Param) &&
8633+
((Param *) expr)->paramkind == PARAM_MULTIEXPR)
8634+
{
8635+
return true;
8636+
}
8637+
}
8638+
8639+
// No multi-column set expression found
8640+
return false;
8641+
}
8642+
8643+
/*
8644+
* processTargetsIndirection - reorder targets list (from indirection)
8645+
*
8646+
* We don't change anything but the order the target list.
8647+
* The purpose here is to be able to deparse a query tree as if it was
8648+
* provided by the PostgreSQL parser, not the rewriter (which is the one
8649+
* received by the planner hook).
8650+
*
8651+
* It's required only for UPDATE SET (MULTIEXPR) queries, other candidates
8652+
* are not supported by Citus.
8653+
*
8654+
* Returns the new target list, reordered.
8655+
*/
8656+
static List *processTargetsIndirection(List *targetList)
8657+
{
8658+
int nAssignableCols;
8659+
int targetListPosition;
8660+
bool sawJunk = false;
8661+
List *newTargetList = NIL;
8662+
ListCell *lc;
8663+
8664+
/* Count non-junk columns and ensure they precede junk columns */
8665+
nAssignableCols = 0;
8666+
foreach(lc, targetList)
8667+
{
8668+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
8669+
8670+
if (tle->resjunk)
8671+
{
8672+
sawJunk = true;
8673+
}
8674+
else
8675+
{
8676+
if (sawJunk)
8677+
elog(ERROR, "Subplan target list is out of order");
8678+
8679+
nAssignableCols++;
8680+
}
8681+
}
8682+
8683+
/* If no assignable columns, return the original target list */
8684+
if (nAssignableCols == 0)
8685+
return targetList;
8686+
8687+
/* Reorder the target list */
8688+
/* we start from 1 */
8689+
targetListPosition = 1;
8690+
while (nAssignableCols > 0)
8691+
{
8692+
nAssignableCols--;
8693+
8694+
foreach(lc, targetList)
8695+
{
8696+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
8697+
8698+
if (IsA(tle->expr, FuncExpr))
8699+
{
8700+
FuncExpr *funcexpr = (FuncExpr *) tle->expr;
8701+
AttrNumber attnum = extract_paramid_from_funcexpr(funcexpr);
8702+
8703+
if (attnum == targetListPosition)
8704+
{
8705+
ereport(DEBUG1, (errmsg("Adding FuncExpr resno: %d", tle->resno)));
8706+
newTargetList = lappend(newTargetList, tle);
8707+
targetListPosition++;
8708+
break;
8709+
}
8710+
}
8711+
else if (IsA(tle->expr, Param))
8712+
{
8713+
Param *param = (Param *) tle->expr;
8714+
AttrNumber attnum = param->paramid;
8715+
8716+
if (attnum == targetListPosition)
8717+
{
8718+
newTargetList = lappend(newTargetList, tle);
8719+
targetListPosition++;
8720+
break;
8721+
}
8722+
}
8723+
}
8724+
}
8725+
8726+
// TODO add check about what we did here ?
8727+
8728+
/* Append any remaining junk columns */
8729+
foreach(lc, targetList)
8730+
{
8731+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
8732+
if (tle->resjunk)
8733+
newTargetList = lappend(newTargetList, tle);
8734+
}
8735+
8736+
return newTargetList;
8737+
}
8738+
8739+
/* Function to extract paramid from a FuncExpr node */
8740+
static AttrNumber extract_paramid_from_funcexpr(FuncExpr *func)
8741+
{
8742+
AttrNumber targetAttnum = InvalidAttrNumber;
8743+
ListCell *lc;
8744+
8745+
/* Iterate through the arguments of the FuncExpr */
8746+
foreach(lc, func->args)
8747+
{
8748+
Node *arg = (Node *) lfirst(lc);
8749+
8750+
/* Check if the argument is a PARAM node */
8751+
if (IsA(arg, Param))
8752+
{
8753+
Param *param = (Param *) arg;
8754+
targetAttnum = param->paramid;
8755+
8756+
break; // Exit loop once we find the PARAM node
8757+
}
8758+
}
8759+
8760+
return targetAttnum;
8761+
}
8762+
86108763
/*
86118764
* processIndirection - take care of array and subfield assignment
86128765
*

0 commit comments

Comments
 (0)