Fix some more cases of missed GENERATED-column updates.

If UPDATE is forced to retry after an EvalPlanQual check, it neglected
to repeat GENERATED-column computations, even though those might well
have changed since we're dealing with a different tuple than before.
Fixing this is mostly a matter of looping back a bit further when
we retry.  In v15 and HEAD that's most easily done by altering the API
of ExecUpdateAct so that it includes computing GENERATED expressions.

Also, if an UPDATE in a partitioned table turns into a cross-partition
INSERT operation, we failed to recompute GENERATED columns.  That's a
bug since 8bf6ec3ba allowed partitions to have different generation
expressions; although it seems to have no ill effects before that.
Fixing this is messier because we can now have situations where the same
query needs both the UPDATE-aligned set of GENERATED columns and the
INSERT-aligned set, and it's unclear which set will be generated first
(else we could hack things by forcing the INSERT-aligned set to be
generated, which is indeed how fe9e658f4 made it work for MERGE).
The best fix seems to be to build and store separate sets of expressions
for the INSERT and UPDATE cases.  That would create ABI issues in the
back branches, but so far it seems we can leave this alone in the back
branches.

Per bug #17823 from Hisahiro Kauchi.  The first part of this affects all
branches back to v12 where GENERATED columns were added.

Discussion: https://postgr.es/m/17823-b64909cf7d63de84@postgresql.org
This commit is contained in:
Tom Lane 2023-03-06 18:31:16 -05:00
parent d937904cce
commit 7fee7871b4
8 changed files with 279 additions and 206 deletions

View File

@ -1232,7 +1232,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_FdwState = NULL; resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_usesFdwDirectModify = false; resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_GeneratedExprs = NULL; resultRelInfo->ri_GeneratedExprsI = NULL;
resultRelInfo->ri_GeneratedExprsU = NULL;
resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_projectReturning = NULL;
resultRelInfo->ri_onConflictArbiterIndexes = NIL; resultRelInfo->ri_onConflictArbiterIndexes = NIL;
resultRelInfo->ri_onConflict = NULL; resultRelInfo->ri_onConflict = NULL;

View File

@ -1339,8 +1339,8 @@ ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
Bitmapset * Bitmapset *
ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate) ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate)
{ {
/* In some code paths we can reach here before initializing the info */ /* Compute the info if we didn't already */
if (relinfo->ri_GeneratedExprs == NULL) if (relinfo->ri_GeneratedExprsU == NULL)
ExecInitStoredGenerated(relinfo, estate, CMD_UPDATE); ExecInitStoredGenerated(relinfo, estate, CMD_UPDATE);
return relinfo->ri_extraUpdatedCols; return relinfo->ri_extraUpdatedCols;
} }

View File

@ -356,9 +356,14 @@ ExecCheckTIDVisible(EState *estate,
/* /*
* Initialize to compute stored generated columns for a tuple * Initialize to compute stored generated columns for a tuple
* *
* This fills the resultRelInfo's ri_GeneratedExprs and ri_extraUpdatedCols * This fills the resultRelInfo's ri_GeneratedExprsI/ri_NumGeneratedNeededI
* fields. (Currently, ri_extraUpdatedCols is consulted only in UPDATE, * or ri_GeneratedExprsU/ri_NumGeneratedNeededU fields, depending on cmdtype.
* but we might as well fill it for INSERT too.) * If cmdType == CMD_UPDATE, the ri_extraUpdatedCols field is filled too.
*
* Note: usually, a given query would need only one of ri_GeneratedExprsI and
* ri_GeneratedExprsU per result rel; but MERGE can need both, and so can
* cross-partition UPDATEs, since a partition might be the target of both
* UPDATE and INSERT actions.
*/ */
void void
ExecInitStoredGenerated(ResultRelInfo *resultRelInfo, ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
@ -368,12 +373,11 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
Relation rel = resultRelInfo->ri_RelationDesc; Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel); TupleDesc tupdesc = RelationGetDescr(rel);
int natts = tupdesc->natts; int natts = tupdesc->natts;
ExprState **ri_GeneratedExprs;
int ri_NumGeneratedNeeded;
Bitmapset *updatedCols; Bitmapset *updatedCols;
MemoryContext oldContext; MemoryContext oldContext;
/* Don't call twice */
Assert(resultRelInfo->ri_GeneratedExprs == NULL);
/* Nothing to do if no generated columns */ /* Nothing to do if no generated columns */
if (!(tupdesc->constr && tupdesc->constr->has_generated_stored)) if (!(tupdesc->constr && tupdesc->constr->has_generated_stored))
return; return;
@ -396,9 +400,8 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
*/ */
oldContext = MemoryContextSwitchTo(estate->es_query_cxt); oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
resultRelInfo->ri_GeneratedExprs = ri_GeneratedExprs = (ExprState **) palloc0(natts * sizeof(ExprState *));
(ExprState **) palloc0(natts * sizeof(ExprState *)); ri_NumGeneratedNeeded = 0;
resultRelInfo->ri_NumGeneratedNeeded = 0;
for (int i = 0; i < natts; i++) for (int i = 0; i < natts; i++)
{ {
@ -427,16 +430,35 @@ ExecInitStoredGenerated(ResultRelInfo *resultRelInfo,
} }
/* No luck, so prepare the expression for execution */ /* No luck, so prepare the expression for execution */
resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate); ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
resultRelInfo->ri_NumGeneratedNeeded++; ri_NumGeneratedNeeded++;
/* And mark this column in resultRelInfo->ri_extraUpdatedCols */ /* If UPDATE, mark column in resultRelInfo->ri_extraUpdatedCols */
resultRelInfo->ri_extraUpdatedCols = if (cmdtype == CMD_UPDATE)
bms_add_member(resultRelInfo->ri_extraUpdatedCols, resultRelInfo->ri_extraUpdatedCols =
i + 1 - FirstLowInvalidHeapAttributeNumber); bms_add_member(resultRelInfo->ri_extraUpdatedCols,
i + 1 - FirstLowInvalidHeapAttributeNumber);
} }
} }
/* Save in appropriate set of fields */
if (cmdtype == CMD_UPDATE)
{
/* Don't call twice */
Assert(resultRelInfo->ri_GeneratedExprsU == NULL);
resultRelInfo->ri_GeneratedExprsU = ri_GeneratedExprs;
resultRelInfo->ri_NumGeneratedNeededU = ri_NumGeneratedNeeded;
}
else
{
/* Don't call twice */
Assert(resultRelInfo->ri_GeneratedExprsI == NULL);
resultRelInfo->ri_GeneratedExprsI = ri_GeneratedExprs;
resultRelInfo->ri_NumGeneratedNeededI = ri_NumGeneratedNeeded;
}
MemoryContextSwitchTo(oldContext); MemoryContextSwitchTo(oldContext);
} }
@ -452,6 +474,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
TupleDesc tupdesc = RelationGetDescr(rel); TupleDesc tupdesc = RelationGetDescr(rel);
int natts = tupdesc->natts; int natts = tupdesc->natts;
ExprContext *econtext = GetPerTupleExprContext(estate); ExprContext *econtext = GetPerTupleExprContext(estate);
ExprState **ri_GeneratedExprs;
MemoryContext oldContext; MemoryContext oldContext;
Datum *values; Datum *values;
bool *nulls; bool *nulls;
@ -460,20 +483,25 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
Assert(tupdesc->constr && tupdesc->constr->has_generated_stored); Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
/* /*
* For relations named directly in the query, ExecInitStoredGenerated * Initialize the expressions if we didn't already, and check whether we
* should have been called already; but this might not have happened yet * can exit early because nothing needs to be computed.
* for a partition child rel. Also, it's convenient for outside callers
* to not have to call ExecInitStoredGenerated explicitly.
*/ */
if (resultRelInfo->ri_GeneratedExprs == NULL) if (cmdtype == CMD_UPDATE)
ExecInitStoredGenerated(resultRelInfo, estate, cmdtype); {
if (resultRelInfo->ri_GeneratedExprsU == NULL)
/* ExecInitStoredGenerated(resultRelInfo, estate, cmdtype);
* If no generated columns have been affected by this change, then skip if (resultRelInfo->ri_NumGeneratedNeededU == 0)
* the rest. return;
*/ ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsU;
if (resultRelInfo->ri_NumGeneratedNeeded == 0) }
return; else
{
if (resultRelInfo->ri_GeneratedExprsI == NULL)
ExecInitStoredGenerated(resultRelInfo, estate, cmdtype);
/* Early exit is impossible given the prior Assert */
Assert(resultRelInfo->ri_NumGeneratedNeededI > 0);
ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsI;
}
oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
@ -487,7 +515,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
{ {
Form_pg_attribute attr = TupleDescAttr(tupdesc, i); Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
if (resultRelInfo->ri_GeneratedExprs[i]) if (ri_GeneratedExprs[i])
{ {
Datum val; Datum val;
bool isnull; bool isnull;
@ -496,7 +524,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
econtext->ecxt_scantuple = slot; econtext->ecxt_scantuple = slot;
val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[i], econtext, &isnull); val = ExecEvalExpr(ri_GeneratedExprs[i], econtext, &isnull);
/* /*
* We must make a copy of val as we have no guarantees about where * We must make a copy of val as we have no guarantees about where
@ -1910,9 +1938,10 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
} }
/* /*
* ExecUpdatePrepareSlot -- subroutine for ExecUpdate * ExecUpdatePrepareSlot -- subroutine for ExecUpdateAct
* *
* Apply the final modifications to the tuple slot before the update. * Apply the final modifications to the tuple slot before the update.
* (This is split out because we also need it in the foreign-table code path.)
*/ */
static void static void
ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo, ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
@ -1962,13 +1991,14 @@ ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
updateCxt->crossPartUpdate = false; updateCxt->crossPartUpdate = false;
/* /*
* If we generate a new candidate tuple after EvalPlanQual testing, we * If we move the tuple to a new partition, we loop back here to recompute
* must loop back here and recheck any RLS policies and constraints. (We * GENERATED values (which are allowed to be different across partitions)
* don't need to redo triggers, however. If there are any BEFORE triggers * and recheck any RLS policies and constraints. We do not fire any
* then trigger.c will have done table_tuple_lock to lock the correct * BEFORE triggers of the new partition, however.
* tuple, so there's no need to do them again.)
*/ */
lreplace: lreplace:
/* Fill in GENERATEd columns */
ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
/* ensure slot is independent, consider e.g. EPQ */ /* ensure slot is independent, consider e.g. EPQ */
ExecMaterializeSlot(slot); ExecMaterializeSlot(slot);
@ -2268,6 +2298,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
} }
else if (resultRelInfo->ri_FdwRoutine) else if (resultRelInfo->ri_FdwRoutine)
{ {
/* Fill in GENERATEd columns */
ExecUpdatePrepareSlot(resultRelInfo, slot, estate); ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
/* /*
@ -2290,9 +2321,13 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
} }
else else
{ {
/* Fill in the slot appropriately */ /*
ExecUpdatePrepareSlot(resultRelInfo, slot, estate); * If we generate a new candidate tuple after EvalPlanQual testing, we
* must loop back here to try again. (We don't need to redo triggers,
* however. If there are any BEFORE triggers then trigger.c will have
* done table_tuple_lock to lock the correct tuple, so there's no need
* to do them again.)
*/
redo_act: redo_act:
result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot, result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
canSetTag, &updateCxt); canSetTag, &updateCxt);
@ -2876,7 +2911,6 @@ lmerge_matched:
result = TM_Ok; result = TM_Ok;
break; break;
} }
ExecUpdatePrepareSlot(resultRelInfo, newslot, context->estate);
result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL, result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
newslot, false, &updateCxt); newslot, false, &updateCxt);
if (result == TM_Ok && updateCxt.updated) if (result == TM_Ok && updateCxt.updated)
@ -4135,15 +4169,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
elog(ERROR, "could not find junk wholerow column"); elog(ERROR, "could not find junk wholerow column");
} }
} }
/*
* For INSERT/UPDATE/MERGE, prepare to evaluate any generated columns.
* We must do this now, even if we never insert or update any rows,
* because we have to fill resultRelInfo->ri_extraUpdatedCols for
* possible use by the trigger machinery.
*/
if (operation == CMD_INSERT || operation == CMD_UPDATE || operation == CMD_MERGE)
ExecInitStoredGenerated(resultRelInfo, estate, operation);
} }
/* /*

View File

@ -462,7 +462,7 @@ typedef struct ResultRelInfo
*/ */
AttrNumber ri_RowIdAttNo; AttrNumber ri_RowIdAttNo;
/* For INSERT/UPDATE, attnums of generated columns to be computed */ /* For UPDATE, attnums of generated columns to be computed */
Bitmapset *ri_extraUpdatedCols; Bitmapset *ri_extraUpdatedCols;
/* Projection to generate new tuple in an INSERT/UPDATE */ /* Projection to generate new tuple in an INSERT/UPDATE */
@ -516,11 +516,13 @@ typedef struct ResultRelInfo
/* array of constraint-checking expr states */ /* array of constraint-checking expr states */
ExprState **ri_ConstraintExprs; ExprState **ri_ConstraintExprs;
/* array of stored generated columns expr states */ /* arrays of stored generated columns expr states, for INSERT and UPDATE */
ExprState **ri_GeneratedExprs; ExprState **ri_GeneratedExprsI;
ExprState **ri_GeneratedExprsU;
/* number of stored generated columns we need to compute */ /* number of stored generated columns we need to compute */
int ri_NumGeneratedNeeded; int ri_NumGeneratedNeededI;
int ri_NumGeneratedNeededU;
/* list of RETURNING expressions */ /* list of RETURNING expressions */
List *ri_returningList; List *ri_returningList;

View File

@ -17,10 +17,10 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 850 checking | 850| 1700
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -40,10 +40,10 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1100 checking | 1100| 2200
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -64,10 +64,10 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1050 checking | 1050| 2100
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -88,10 +88,10 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1600 checking | 1600| 3200
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -117,9 +117,9 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -140,9 +140,9 @@ balance
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -168,10 +168,10 @@ balance
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1500 checking | 1500| 3000
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -192,9 +192,9 @@ balance
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -221,10 +221,10 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1050 checking | 1050| 2100
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -245,9 +245,9 @@ balance
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -274,9 +274,9 @@ balance
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -298,9 +298,9 @@ balance
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -320,9 +320,9 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
savings | 600 savings | 600| 1200
(1 row) (1 row)
@ -343,10 +343,10 @@ balance
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1050 checking | 1050| 2100
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -371,10 +371,10 @@ step wnested2:
step c1: COMMIT; step c1: COMMIT;
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | -600 checking | -600| -1200
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -420,10 +420,10 @@ s2: NOTICE: upid: text savings = text checking: f
step wnested2: <... completed> step wnested2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | -800 checking | -800| -1600
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -471,10 +471,10 @@ s2: NOTICE: upid: text savings = text checking: f
step wnested2: <... completed> step wnested2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 200 checking | 200| 400
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -527,10 +527,10 @@ s2: NOTICE: upid: text savings = text checking: f
step wnested2: <... completed> step wnested2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 200 checking | 200| 400
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -577,10 +577,10 @@ s2: NOTICE: upid: text savings = text checking: f
step wnested2: <... completed> step wnested2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 400 checking | 400| 800
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -614,10 +614,10 @@ s2: NOTICE: upid: text savings = text checking: f
step wnested2: <... completed> step wnested2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
cds | 400 cds | 400| 800
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -652,10 +652,10 @@ s2: NOTICE: upid: text savings = text checking: f
step wnested2: <... completed> step wnested2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 400 checking | 400| 800
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -669,17 +669,17 @@ balance
step updwcte: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) UPDATE accounts a SET balance = doup.balance + 100 FROM doup RETURNING *; <waiting ...> step updwcte: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) UPDATE accounts a SET balance = doup.balance + 100 FROM doup RETURNING *; <waiting ...>
step c1: COMMIT; step c1: COMMIT;
step updwcte: <... completed> step updwcte: <... completed>
accountid|balance|accountid|balance accountid|balance|balance2|accountid|balance|balance2
---------+-------+---------+------- ---------+-------+--------+---------+-------+--------
savings | 1600|checking | 1500 savings | 1600| 3200|checking | 1500| 3000
(1 row) (1 row)
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1500 checking | 1500| 3000
savings | 1600 savings | 1600| 3200
(2 rows) (2 rows)
@ -696,10 +696,10 @@ step updwctefail: <... completed>
ERROR: tuple to be updated was already modified by an operation triggered by the current command ERROR: tuple to be updated was already modified by an operation triggered by the current command
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 400 checking | 400| 800
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -713,16 +713,16 @@ balance
step delwcte: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) DELETE FROM accounts a USING doup RETURNING *; <waiting ...> step delwcte: WITH doup AS (UPDATE accounts SET balance = balance + 1100 WHERE accountid = 'checking' RETURNING *) DELETE FROM accounts a USING doup RETURNING *; <waiting ...>
step c1: COMMIT; step c1: COMMIT;
step delwcte: <... completed> step delwcte: <... completed>
accountid|balance|accountid|balance accountid|balance|balance2|accountid|balance|balance2
---------+-------+---------+------- ---------+-------+--------+---------+-------+--------
savings | 600|checking | 1500 savings | 600| 1200|checking | 1500| 3000
(1 row) (1 row)
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1500 checking | 1500| 3000
(1 row) (1 row)
@ -739,10 +739,10 @@ step delwctefail: <... completed>
ERROR: tuple to be deleted was already modified by an operation triggered by the current command ERROR: tuple to be deleted was already modified by an operation triggered by the current command
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 400 checking | 400| 800
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -767,10 +767,10 @@ step c1: COMMIT;
step upsert2: <... completed> step upsert2: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 600 checking | 600| 1200
savings | 2334 savings | 2334| 4668
(2 rows) (2 rows)
@ -856,18 +856,18 @@ step partiallock:
<waiting ...> <waiting ...>
step c2: COMMIT; step c2: COMMIT;
step partiallock: <... completed> step partiallock: <... completed>
accountid|balance|accountid|balance accountid|balance|balance2|accountid|balance|balance2
---------+-------+---------+------- ---------+-------+--------+---------+-------+--------
checking | 1050|checking | 600 checking | 1050| 2100|checking | 600| 1200
savings | 600|savings | 600 savings | 600| 1200|savings | 600| 1200
(2 rows) (2 rows)
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1050 checking | 1050| 2100
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -887,18 +887,18 @@ step lockwithvalues:
<waiting ...> <waiting ...>
step c2: COMMIT; step c2: COMMIT;
step lockwithvalues: <... completed> step lockwithvalues: <... completed>
accountid|balance|id accountid|balance|balance2|id
---------+-------+-------- ---------+-------+--------+--------
checking | 1050|checking checking | 1050| 2100|checking
savings | 600|savings savings | 600| 1200|savings
(2 rows) (2 rows)
step c1: COMMIT; step c1: COMMIT;
step read: SELECT * FROM accounts ORDER BY accountid; step read: SELECT * FROM accounts ORDER BY accountid;
accountid|balance accountid|balance|balance2
---------+------- ---------+-------+--------
checking | 1050 checking | 1050| 2100
savings | 600 savings | 600| 1200
(2 rows) (2 rows)
@ -1104,25 +1104,31 @@ subid|id
(1 row) (1 row)
starting permutation: simplepartupdate complexpartupdate c1 c2 starting permutation: simplepartupdate complexpartupdate c1 c2 read_part
step simplepartupdate: step simplepartupdate:
update parttbl set a = a; update parttbl set b = b + 10;
step complexpartupdate: step complexpartupdate:
with u as (update parttbl set a = a returning parttbl.*) with u as (update parttbl set b = b + 1 returning parttbl.*)
update parttbl set a = u.a from u; update parttbl set b = u.b + 100 from u;
<waiting ...> <waiting ...>
step c1: COMMIT; step c1: COMMIT;
step complexpartupdate: <... completed> step complexpartupdate: <... completed>
step c2: COMMIT; step c2: COMMIT;
step read_part: SELECT * FROM parttbl ORDER BY a;
a| b|c| d
-+--+-+--
1|12|1|13
(1 row)
starting permutation: simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2
starting permutation: simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2 read_part
step simplepartupdate_route1to2: step simplepartupdate_route1to2:
update parttbl set a = 2 where c = 1 returning *; update parttbl set a = 2 where c = 1 returning *;
a|b|c a|b|c| d
-+-+- -+-+-+----
2|1|1 2|1|1|1003
(1 row) (1 row)
step complexpartupdate_route_err1: step complexpartupdate_route_err1:
@ -1133,14 +1139,20 @@ step c1: COMMIT;
step complexpartupdate_route_err1: <... completed> step complexpartupdate_route_err1: <... completed>
ERROR: tuple to be locked was already moved to another partition due to concurrent update ERROR: tuple to be locked was already moved to another partition due to concurrent update
step c2: COMMIT; step c2: COMMIT;
step read_part: SELECT * FROM parttbl ORDER BY a;
a|b|c| d
-+-+-+----
2|1|1|1003
(1 row)
starting permutation: simplepartupdate_noroute complexpartupdate_route c1 c2
starting permutation: simplepartupdate_noroute complexpartupdate_route c1 c2 read_part
step simplepartupdate_noroute: step simplepartupdate_noroute:
update parttbl set b = 2 where c = 1 returning *; update parttbl set b = 2 where c = 1 returning *;
a|b|c a|b|c|d
-+-+- -+-+-+-
1|2|1 1|2|1|3
(1 row) (1 row)
step complexpartupdate_route: step complexpartupdate_route:
@ -1149,20 +1161,26 @@ step complexpartupdate_route:
<waiting ...> <waiting ...>
step c1: COMMIT; step c1: COMMIT;
step complexpartupdate_route: <... completed> step complexpartupdate_route: <... completed>
a|b|c a|b|c| d
-+-+- -+-+-+----
2|2|1 2|2|1|1004
(1 row) (1 row)
step c2: COMMIT; step c2: COMMIT;
step read_part: SELECT * FROM parttbl ORDER BY a;
a|b|c| d
-+-+-+----
2|2|1|1004
(1 row)
starting permutation: simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2
starting permutation: simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2 read_part
step simplepartupdate_noroute: step simplepartupdate_noroute:
update parttbl set b = 2 where c = 1 returning *; update parttbl set b = 2 where c = 1 returning *;
a|b|c a|b|c|d
-+-+- -+-+-+-
1|2|1 1|2|1|3
(1 row) (1 row)
step complexpartupdate_doesnt_route: step complexpartupdate_doesnt_route:
@ -1171,9 +1189,15 @@ step complexpartupdate_doesnt_route:
<waiting ...> <waiting ...>
step c1: COMMIT; step c1: COMMIT;
step complexpartupdate_doesnt_route: <... completed> step complexpartupdate_doesnt_route: <... completed>
a|b|c a|b|c|d
-+-+- -+-+-+-
1|2|1 1|2|1|3
(1 row) (1 row)
step c2: COMMIT; step c2: COMMIT;
step read_part: SELECT * FROM parttbl ORDER BY a;
a|b|c|d
-+-+-+-
1|2|1|3
(1 row)

View File

@ -6,7 +6,8 @@
setup setup
{ {
CREATE TABLE accounts (accountid text PRIMARY KEY, balance numeric not null); CREATE TABLE accounts (accountid text PRIMARY KEY, balance numeric not null,
balance2 numeric GENERATED ALWAYS AS (balance * 2) STORED);
INSERT INTO accounts VALUES ('checking', 600), ('savings', 600); INSERT INTO accounts VALUES ('checking', 600), ('savings', 600);
CREATE FUNCTION update_checking(int) RETURNS bool LANGUAGE sql AS $$ CREATE FUNCTION update_checking(int) RETURNS bool LANGUAGE sql AS $$
@ -33,9 +34,12 @@ setup
CREATE TABLE jointest AS SELECT generate_series(1,10) AS id, 0 AS data; CREATE TABLE jointest AS SELECT generate_series(1,10) AS id, 0 AS data;
CREATE INDEX ON jointest(id); CREATE INDEX ON jointest(id);
CREATE TABLE parttbl (a int, b int, c int) PARTITION BY LIST (a); CREATE TABLE parttbl (a int, b int, c int,
d int GENERATED ALWAYS AS (a + b) STORED) PARTITION BY LIST (a);
CREATE TABLE parttbl1 PARTITION OF parttbl FOR VALUES IN (1); CREATE TABLE parttbl1 PARTITION OF parttbl FOR VALUES IN (1);
CREATE TABLE parttbl2 PARTITION OF parttbl FOR VALUES IN (2); CREATE TABLE parttbl2 PARTITION OF parttbl
(d WITH OPTIONS GENERATED ALWAYS AS (a + b + 1000) STORED)
FOR VALUES IN (2);
INSERT INTO parttbl VALUES (1, 1, 1); INSERT INTO parttbl VALUES (1, 1, 1);
CREATE TABLE another_parttbl (a int, b int, c int) PARTITION BY LIST (a); CREATE TABLE another_parttbl (a int, b int, c int) PARTITION BY LIST (a);
@ -171,7 +175,7 @@ step selectresultforupdate {
# test for EPQ on a partitioned result table # test for EPQ on a partitioned result table
step simplepartupdate { step simplepartupdate {
update parttbl set a = a; update parttbl set b = b + 10;
} }
# test scenarios where update may cause row movement # test scenarios where update may cause row movement
@ -223,8 +227,8 @@ step updateforcip3 {
step wrtwcte { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; } step wrtwcte { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; }
step wrjt { UPDATE jointest SET data = 42 WHERE id = 7; } step wrjt { UPDATE jointest SET data = 42 WHERE id = 7; }
step complexpartupdate { step complexpartupdate {
with u as (update parttbl set a = a returning parttbl.*) with u as (update parttbl set b = b + 1 returning parttbl.*)
update parttbl set a = u.a from u; update parttbl set b = u.b + 100 from u;
} }
step complexpartupdate_route_err1 { step complexpartupdate_route_err1 {
@ -273,6 +277,7 @@ setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
step read { SELECT * FROM accounts ORDER BY accountid; } step read { SELECT * FROM accounts ORDER BY accountid; }
step read_ext { SELECT * FROM accounts_ext ORDER BY accountid; } step read_ext { SELECT * FROM accounts_ext ORDER BY accountid; }
step read_a { SELECT * FROM table_a ORDER BY id; } step read_a { SELECT * FROM table_a ORDER BY id; }
step read_part { SELECT * FROM parttbl ORDER BY a; }
# this test exercises EvalPlanQual with a CTE, cf bug #14328 # this test exercises EvalPlanQual with a CTE, cf bug #14328
step readwcte { step readwcte {
@ -353,7 +358,7 @@ permutation wrjt selectjoinforupdate c2 c1
permutation wrjt selectresultforupdate c2 c1 permutation wrjt selectresultforupdate c2 c1
permutation wrtwcte multireadwcte c1 c2 permutation wrtwcte multireadwcte c1 c2
permutation simplepartupdate complexpartupdate c1 c2 permutation simplepartupdate complexpartupdate c1 c2 read_part
permutation simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2 permutation simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2 read_part
permutation simplepartupdate_noroute complexpartupdate_route c1 c2 permutation simplepartupdate_noroute complexpartupdate_route c1 c2 read_part
permutation simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2 permutation simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2 read_part

View File

@ -792,6 +792,19 @@ SELECT * FROM gtest_child;
07-15-2016 | 1 | 2 07-15-2016 | 1 | 2
(1 row) (1 row)
UPDATE gtest_parent SET f1 = f1 + 60;
SELECT * FROM gtest_parent;
f1 | f2 | f3
------------+----+----
09-13-2016 | 1 | 33
(1 row)
SELECT * FROM gtest_child3;
f1 | f2 | f3
------------+----+----
09-13-2016 | 1 | 33
(1 row)
-- we leave these tables around for purposes of testing dump/reload/upgrade -- we leave these tables around for purposes of testing dump/reload/upgrade
-- generated columns in partition key (not allowed) -- generated columns in partition key (not allowed)
CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3);

View File

@ -413,6 +413,9 @@ ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
SELECT * FROM gtest_parent; SELECT * FROM gtest_parent;
SELECT * FROM gtest_child; SELECT * FROM gtest_child;
UPDATE gtest_parent SET f1 = f1 + 60;
SELECT * FROM gtest_parent;
SELECT * FROM gtest_child3;
-- we leave these tables around for purposes of testing dump/reload/upgrade -- we leave these tables around for purposes of testing dump/reload/upgrade
-- generated columns in partition key (not allowed) -- generated columns in partition key (not allowed)