Skip to content

Commit 63f471a

Browse files
[#18822] YSQL: Framework to skip redundant sec index updates and fkey checks when relevant columns not modified
Summary: **Background** Prior to this revision, an UPDATE statement specifying a list of target columns X in its SET clause, **always** performed the necessary work to update each of the target columns in the storage layer, irrespective of whether the values of the columns actually changed. The necessary work could include requiring locks, updating indexes, checking of constraints, firing of triggers etc. **The Optimization** This revision introduces an optimization that validates that the values of a column are indeed being modified, before sending (flushing) the updated value of the column to the storage layer. In particular, the set of columns whose values that are compared are those that can cause extra round trips to the storage layer in the form of: - Primary Key Updates - Secondary Index Updates - Foreign Key Constraints - Uniqueness Constraints The matrix of columns that are marked for update and the objects (indexes, constraints) they impact are computed at planning time. This is particularly useful when used in conjunction with prepared statements and ORMs, which tend to specify all columns (both modified and non-modified) as part of the target list. The decision of whether a column is indeed modified is done on a per-tuple basis at execution time. **Example** As a concrete example, consider a table with the following schema and data: ``` yugabyte=# CREATE TABLE foo (h INT PRIMARY KEY, v1 INT, v2 INT, v3 INT); yugabyte=# CREATE INDEX foo_v1_idx ON foo (v1); yugabyte=# CREATE INDEX foo_v2_idx ON foo (v2); yugabyte=# INSERT INTO foo (SELECT i, i, i % 10, i % 100 FROM generate_series(1, 10000) AS i); ``` Performing an UPDATE on the first 1000 rows (without the optimization) yields: ``` yugabyte=# SET yb_explain_hide_non_deterministic_fields TO true; yugabyte=# EXPLAIN (ANALYZE, DIST) UPDATE foo SET h = v1, v1 = v1, v3 = v3 + 1 WHERE v1 <= 1000; QUERY PLAN ------------------------------------------------------------------------------------------ Update on foo (cost=0.00..105.00 rows=1000 width=88) (actual rows=0 loops=1) -> Seq Scan on foo (cost=0.00..105.00 rows=1000 width=88) (actual rows=1000 loops=1) Remote Filter: (v1 <= 1000) Storage Table Read Requests: 1 Storage Table Rows Scanned: 10000 Storage Table Write Requests: 2000 Storage Index Write Requests: 4000 Storage Flush Requests: 2000 Storage Read Requests: 1 Storage Rows Scanned: 10000 Storage Write Requests: 6000 Storage Flush Requests: 2001 (12 rows) ``` The values of `h` and `v1` are not modified by the query, yet result in multiple write requests to both the main table as well as the secondary indices. Since updates to key columns (of a table or an index) is executed as a sequence of a DELETE followed by an INSERT, this query requires a large amount of flushes. This makes the query very expensive in terms of the amount of work to be done. With the proposed optimization the query is executed as follows: ``` yugabyte=# EXPLAIN (ANALYZE, DIST) UPDATE foo SET h = v1, v1 = v1, v3 = v3 + 1 WHERE v1 <= 1000; QUERY PLAN ------------------------------------------------------------------------------------------ Update on foo (cost=0.00..105.00 rows=1000 width=88) (actual rows=0 loops=1) -> Seq Scan on foo (cost=0.00..105.00 rows=1000 width=88) (actual rows=1000 loops=1) Remote Filter: (v1 <= 1000) Storage Table Read Requests: 1 Storage Rows Scanned: 10000 Storage Table Write Requests: 1000 Storage Read Requests: 1 Storage Rows Scanned: 10000 Storage Write Requests: 1000 Storage Flush Requests: 1 (10 rows) ``` **Flags and Feature Status** This revision introduces the following GUCs to control the behavior of this optimization: `yb_update_num_cols_to_compare` - The maximum number of columns to be compared. (default: 0) `yb_update_max_cols_size_to_compare` - The maximum size of an individual column that can be compared. (default: 10240) This feature is currently turned off as a result of setting `yb_update_num_cols_to_compare` to 0. **Debuggability** Turn on postgres debug2 logs via the following command: ``` ./bin/yb-ctl restart --ysql_pg_conf_csv='log_min_messages=debug2' ``` This produces the following debug information: ``` -- At planning time 2024-07-31 10:59:07.124 PDT [76120] DEBUG: Update matrix: rows represent OID of entities, columns represent attnum of cols 2024-07-31 10:59:07.124 PDT [76120] DEBUG: - 10 2024-07-31 10:59:07.124 PDT [76120] DEBUG: 17415 Y -- At execution time, on a per-tuple basis 2024-07-31 10:59:07.143 PDT [76120] DEBUG: Index/constraint with oid 17415 requires an update 2024-07-31 10:59:07.143 PDT [76120] DEBUG: Relation: 17412 Columns that are inspected and modified: 1 (10) 2024-07-31 10:59:07.143 PDT [76120] DEBUG: No cols in category: Columns that are inspected and unmodified 2024-07-31 10:59:07.143 PDT [76120] DEBUG: Relation: 17412 Columns that are marked for update: 1 (10) 2 (11) ``` **Future Work** 1. Introduce auto-flag infrastructure to safely use row-locking. This is in the context of upgrade safety while the cluster is being upgraded. 2. As a part of the flag infrastructure, ensure that flags/GUC values are immutable during the lifetime of a query. 3. #22994: PGSQL_UPDATEs with no column references should acquire row locks. 4. #23348: Add support for partitioned tables with out of order columns. 5. Support for serializing optimization metadata in plans. 6. Enhance randgen grammar to support ModifyTable (INSERT/UPDATE/DELETE ) queries 7. #23350: PG 15 support. jenkins: urgent Jira: DB-7701 Test Plan: Run the associated pg_regress test as follows: ``` # New tests ./yb_build.sh --java-test 'org.yb.pgsql.TestPgRegressUpdateOptimized#schedule' # Existing tests ./yb_build.sh --java-test 'org.yb.pgsql.TestPgUpdatePrimaryKey' ./yb_build.sh --java-test 'org.yb.pgsql.TestPgUniqueConstraint' ./yb_build.sh --java-test 'org.yb.pgsql.TestPgRegressTrigger#testPgRegressTrigger' ./yb_build.sh --java-test 'org.yb.pgsql.TestPgRegressDml#testPgRegressDml' ./yb_build.sh --java-test 'org.yb.pgsql.TestPgRegressPushdown#testPgRegressPushdown' ``` Tested scenarios include (but not limited to): 1. Single row and distributed transactions with and without the feature flag turned on. 2. Relations with a primary key and no secondary indexes or triggers (UPDATEs can take the single row path) 3. Relations with combinations of primary key and secondary indexes. 4. Relations with unconditional before-row triggers. 5. UPDATEs in Colocated databases. 6. UPDATEs covering multiple tuples. 7. Hierarchy of relations with foreign keys 8. Relations with self referential foreign keys 9. Relations with overlapping indexes. 10. Relations having columns with uniqueness constraints. 11. Relations having covering indexes. 12. Relations having partial indexes. 13. Relations having index expressions / predicates. 14. Relations with conditional column triggers. 15. Relations having indexes/constraints out of order (ie. order of columns in relation is different from that of entity) 16. Relations having combination of hash and range indexes. 17. UPDATEs with correlated subqueries. 18. INSERT ON CONFLICT DO UPDATE. 19. UPDATE RETURNING. 20. UPDATEs on temp tables. Reviewers: mihnea, jason, amartsinchyk Reviewed By: amartsinchyk Subscribers: pjain, jason, smishra, yql Tags: #jenkins-ready Differential Revision: https://phorge.dev.yugabyte.com/D34040
1 parent 8a0b95c commit 63f471a

36 files changed

+4712
-169
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) YugabyteDB, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4+
// in compliance with the License. You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software distributed under the License
9+
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10+
// or implied. See the License for the specific language governing permissions and limitations
11+
// under the License.
12+
//
13+
package org.yb.pgsql;
14+
15+
import java.util.Map;
16+
17+
import org.junit.Test;
18+
import org.junit.runner.RunWith;
19+
import org.yb.YBTestRunner;
20+
21+
@RunWith(value=YBTestRunner.class)
22+
public class TestPgRegressUpdateOptimized extends BasePgRegressTest {
23+
@Override
24+
public int getTestMethodTimeoutSec() {
25+
return 1800;
26+
}
27+
28+
@Override
29+
protected Map<String, String> getTServerFlags() {
30+
Map<String, String> flagMap = super.getTServerFlags();
31+
flagMap.put("ysql_skip_row_lock_for_update", "false");
32+
return flagMap;
33+
}
34+
35+
@Test
36+
public void schedule() throws Exception {
37+
runPgRegressTest("yb_update_optimized_schedule");
38+
}
39+
}

src/postgres/src/backend/commands/trigger.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -6054,7 +6054,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
60546054
case RI_TRIGGER_PK:
60556055
/* Update on trigger's PK table */
60566056
if (!RI_FKey_pk_upd_check_required(trigger, rel,
6057-
oldtup, newtup))
6057+
oldtup, newtup,
6058+
&estate->yb_skip_entities))
60586059
{
60596060
/* skip queuing this event */
60606061
continue;
@@ -6064,7 +6065,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
60646065
case RI_TRIGGER_FK:
60656066
/* Update on trigger's FK table */
60666067
if (!RI_FKey_fk_upd_check_required(trigger, rel,
6067-
oldtup, newtup))
6068+
oldtup, newtup,
6069+
&estate->yb_skip_entities))
60686070
{
60696071
/* skip queuing this event */
60706072
continue;

src/postgres/src/backend/executor/Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
3131
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
3232
nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
3333
nodeTableFuncscan.o ybcExpr.o ybcFunction.o ybc_fdw.o ybcModifyTable.o \
34-
nodeYbBitmapIndexscan.o nodeYbSeqscan.o nodeYbBitmapTablescan.o
34+
nodeYbBitmapIndexscan.o nodeYbSeqscan.o nodeYbBitmapTablescan.o \
35+
ybOptimizeModifyTable.o
3536

3637
include $(top_srcdir)/src/backend/common.mk

src/postgres/src/backend/executor/execIndexing.c

+12-12
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
279279
{
280280
return ExecInsertIndexTuplesOptimized(
281281
slot, tuple, estate, noDupErr, specConflict,
282-
arbiterIndexes, NIL /* no_update_index_list */);
282+
arbiterIndexes);
283283
}
284284

285285
List *
@@ -288,8 +288,7 @@ ExecInsertIndexTuplesOptimized(TupleTableSlot *slot,
288288
EState *estate,
289289
bool noDupErr,
290290
bool *specConflict,
291-
List *arbiterIndexes,
292-
List *no_update_index_list)
291+
List *arbiterIndexes)
293292
{
294293
List *result = NIL;
295294
ResultRelInfo *resultRelInfo;
@@ -337,9 +336,10 @@ ExecInsertIndexTuplesOptimized(TupleTableSlot *slot,
337336
* For an update command check if we need to skip index. For that purpose,
338337
* we check if the relid of the index is part of the skip list.
339338
*/
340-
if (indexRelation == NULL || (no_update_index_list &&
341-
list_member_oid(no_update_index_list, RelationGetRelid(indexRelation))))
342-
continue;
339+
if (indexRelation == NULL ||
340+
list_member_oid(estate->yb_skip_entities.index_list,
341+
RelationGetRelid(indexRelation)))
342+
continue;
343343

344344
indexInfo = indexInfoArray[i];
345345
Assert(indexInfo->ii_ReadyForInserts ==
@@ -503,14 +503,13 @@ ExecInsertIndexTuplesOptimized(TupleTableSlot *slot,
503503
void
504504
ExecDeleteIndexTuples(Datum ybctid, HeapTuple tuple, EState *estate)
505505
{
506-
ExecDeleteIndexTuplesOptimized(ybctid, tuple, estate, NIL /* no_update_index_list */);
506+
ExecDeleteIndexTuplesOptimized(ybctid, tuple, estate);
507507
}
508508

509509
void
510510
ExecDeleteIndexTuplesOptimized(Datum ybctid,
511511
HeapTuple tuple,
512-
EState *estate,
513-
List *no_update_index_list)
512+
EState *estate)
514513
{
515514
ResultRelInfo *resultRelInfo;
516515
int i;
@@ -561,9 +560,10 @@ ExecDeleteIndexTuplesOptimized(Datum ybctid,
561560
* For an update command check if we need to skip index.
562561
* For that purpose, we check if the relid of the index is part of the skip list.
563562
*/
564-
if (indexRelation == NULL || (no_update_index_list &&
565-
list_member_oid(no_update_index_list, RelationGetRelid(indexRelation))))
566-
continue;
563+
if (indexRelation == NULL ||
564+
list_member_oid(estate->yb_skip_entities.index_list,
565+
RelationGetRelid(indexRelation)))
566+
continue;
567567

568568
/*
569569
* No need to update YugaByte primary key which is intrinic part of

src/postgres/src/backend/executor/execUtils.c

+2
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ CreateExecutorState(void)
163163
estate->es_jit_flags = 0;
164164
estate->es_jit = NULL;
165165

166+
NodeSetTag(&estate->yb_skip_entities, T_YbSkippableEntities);
167+
166168
/*
167169
* Return the executor state structure
168170
*/

src/postgres/src/backend/executor/nodeModifyTable.c

+72-100
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,14 @@
7272

7373
/* YB includes. */
7474
#include "access/sysattr.h"
75+
#include "catalog/catalog.h"
7576
#include "catalog/dependency.h"
7677
#include "catalog/pg_database.h"
7778
#include "catalog/pg_depend.h"
7879
#include "catalog/pg_shdepend.h"
7980
#include "catalog/yb_catalog_version.h"
8081
#include "executor/ybcModifyTable.h"
82+
#include "executor/ybOptimizeModifyTable.h"
8183
#include "parser/parsetree.h"
8284
#include "pg_yb_utils.h"
8385
#include "optimizer/ybcplan.h"
@@ -1140,84 +1142,6 @@ ldelete:;
11401142
return NULL;
11411143
}
11421144

1143-
/* ----------------------------------------------------------------
1144-
* YBEqualDatums
1145-
*
1146-
* Function compares values of lhs and rhs datums with respect to value type and collation.
1147-
*
1148-
* Returns true in case value of lhs and rhs datums match.
1149-
* ----------------------------------------------------------------
1150-
*/
1151-
static bool
1152-
YBEqualDatums(Datum lhs, Datum rhs, Oid atttypid, Oid collation)
1153-
{
1154-
TypeCacheEntry *typentry = lookup_type_cache(atttypid, TYPECACHE_CMP_PROC_FINFO);
1155-
if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
1156-
ereport(ERROR,
1157-
(errcode(ERRCODE_UNDEFINED_FUNCTION),
1158-
errmsg("could not identify a comparison function for type %s",
1159-
format_type_be(typentry->type_id))));
1160-
1161-
FunctionCallInfoData locfcinfo;
1162-
InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2, collation, NULL, NULL);
1163-
locfcinfo.arg[0] = lhs;
1164-
locfcinfo.arg[1] = rhs;
1165-
locfcinfo.argnull[0] = false;
1166-
locfcinfo.argnull[1] = false;
1167-
return DatumGetInt32(FunctionCallInvoke(&locfcinfo)) == 0;
1168-
}
1169-
1170-
/* ----------------------------------------------------------------
1171-
* YBBuildExtraUpdatedCols
1172-
*
1173-
* Function compares attribute value in oldtuple and newtuple for attributes which are not in the
1174-
* updatedCols set. Returns set of changed attributes or NULL.
1175-
* ----------------------------------------------------------------
1176-
*/
1177-
static Bitmapset*
1178-
YBBuildExtraUpdatedCols(Relation rel,
1179-
HeapTuple oldtuple,
1180-
HeapTuple newtuple,
1181-
Bitmapset *updatedCols)
1182-
{
1183-
if (bms_is_member(InvalidAttrNumber, updatedCols))
1184-
/* No extra work required in case the whore row is changed */
1185-
return NULL;
1186-
1187-
Bitmapset *result = NULL;
1188-
AttrNumber firstLowInvalidAttributeNumber = YBGetFirstLowInvalidAttributeNumber(rel);
1189-
TupleDesc tupleDesc = RelationGetDescr(rel);
1190-
for (int idx = 0; idx < tupleDesc->natts; ++idx)
1191-
{
1192-
FormData_pg_attribute *att_desc = TupleDescAttr(tupleDesc, idx);
1193-
1194-
AttrNumber attnum = att_desc->attnum;
1195-
1196-
/* Skip virtual (system) and dropped columns */
1197-
if (!IsRealYBColumn(rel, attnum))
1198-
continue;
1199-
1200-
int bms_idx = attnum - firstLowInvalidAttributeNumber;
1201-
if (bms_is_member(bms_idx, updatedCols))
1202-
continue;
1203-
1204-
bool old_is_null = false;
1205-
bool new_is_null = false;
1206-
Datum old_value = heap_getattr(oldtuple, attnum, tupleDesc, &old_is_null);
1207-
Datum new_value = heap_getattr(newtuple, attnum, tupleDesc, &new_is_null);
1208-
if (old_is_null != new_is_null ||
1209-
(!new_is_null && !YBEqualDatums(old_value,
1210-
new_value,
1211-
att_desc->atttypid,
1212-
att_desc->attcollation)))
1213-
{
1214-
result = bms_add_member(result, bms_idx);
1215-
}
1216-
}
1217-
return result;
1218-
}
1219-
1220-
12211145
/*
12221146
* ExecCrossPartitionUpdate --- Move an updated tuple to another partition.
12231147
*
@@ -1547,30 +1471,53 @@ ExecUpdate(ModifyTableState *mtstate,
15471471

15481472
bool row_found = false;
15491473

1550-
Bitmapset *actualUpdatedCols = rte->updatedCols;
1551-
Bitmapset *extraUpdatedCols = NULL;
1552-
if (beforeRowUpdateTriggerFired)
1474+
/*
1475+
* A bitmapset of columns that have been marked as being updated at
1476+
* planning time. This set needs to be updated below if either:
1477+
* - The update optimization comparing new and old values to identify
1478+
* actually modified columns is enabled.
1479+
* - Before row triggers were fired.
1480+
*/
1481+
Bitmapset *cols_marked_for_update = bms_copy(rte->updatedCols);
1482+
1483+
ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
1484+
YbCopySkippableEntities(&estate->yb_skip_entities,
1485+
plan->yb_skip_entities);
1486+
1487+
/*
1488+
* If an update is a "single row transaction", then we have already
1489+
* confirmed at planning time that it has no secondary indexes or
1490+
* triggers or foreign key constraints. Such an update does not
1491+
* benefit from optimizations that skip constraint checking or index
1492+
* updates.
1493+
* While it may seem that a single row, distributed transaction can be
1494+
* transformed into a single row, non-distributed transaction, this is
1495+
* not the case. It is likely that the row to be updated has been read
1496+
* from the storage layer already, thus violating the non-distributed
1497+
* transaction semantics.
1498+
*/
1499+
if (!estate->yb_es_is_single_row_modify_txn)
15531500
{
1554-
/* trigger might have changed tuple */
1555-
extraUpdatedCols = YBBuildExtraUpdatedCols(
1556-
resultRelationDesc, oldtuple, tuple, rte->updatedCols);
1557-
if (extraUpdatedCols)
1558-
{
1559-
extraUpdatedCols = bms_add_members(extraUpdatedCols, rte->updatedCols);
1560-
actualUpdatedCols = extraUpdatedCols;
1561-
}
1501+
YbComputeModifiedColumnsAndSkippableEntities(
1502+
plan, estate, oldtuple, tuple, &cols_marked_for_update,
1503+
beforeRowUpdateTriggerFired);
15621504
}
15631505

1564-
Bitmapset *primary_key_bms = YBGetTablePrimaryKeyBms(resultRelationDesc);
1565-
bool is_pk_updated = bms_overlap(primary_key_bms, actualUpdatedCols);
1506+
/*
1507+
* Irrespective of whether the optimization is enabled or not, we have
1508+
* to check if the primary key is updated. It could be that the columns
1509+
* making up the primary key are not a part of the target list but are
1510+
* updated by a before row trigger.
1511+
*/
1512+
bool is_pk_updated = YbIsPrimaryKeyUpdated(resultRelationDesc,
1513+
cols_marked_for_update);
15661514

15671515
/*
15681516
* TODO(alex): It probably makes more sense to pass a
15691517
* transformed slot instead of a plan slot? Note though
15701518
* that it can have tuple materialized already.
15711519
*/
15721520

1573-
ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
15741521
if (is_pk_updated)
15751522
{
15761523
slot->tts_tuple->t_ybctid = YBCGetYBTupleIdFromSlot(planSlot);
@@ -1588,11 +1535,10 @@ ExecUpdate(ModifyTableState *mtstate,
15881535
mtstate->yb_fetch_target_tuple,
15891536
estate->yb_es_is_single_row_modify_txn
15901537
? YB_SINGLE_SHARD_TRANSACTION : YB_TRANSACTIONAL,
1591-
actualUpdatedCols,
1538+
cols_marked_for_update,
15921539
canSetTag);
15931540
}
15941541

1595-
bms_free(extraUpdatedCols);
15961542
if (!row_found)
15971543
{
15981544
/*
@@ -1610,14 +1556,13 @@ ExecUpdate(ModifyTableState *mtstate,
16101556
mtstate->yb_fetch_target_tuple)
16111557
{
16121558
Datum ybctid = YBCGetYBTupleIdFromSlot(planSlot);
1613-
List *no_update_index_list = ((ModifyTable *)mtstate->ps.plan)->no_update_index_list;
16141559

16151560
/* Delete index entries of the old tuple */
1616-
ExecDeleteIndexTuplesOptimized(ybctid, oldtuple, estate, no_update_index_list);
1561+
ExecDeleteIndexTuplesOptimized(ybctid, oldtuple, estate);
16171562

16181563
/* Insert new index entries for tuple */
16191564
recheckIndexes = ExecInsertIndexTuplesOptimized(
1620-
slot, tuple, estate, false, NULL, NIL, no_update_index_list);
1565+
slot, tuple, estate, false, NULL, NIL);
16211566
}
16221567
}
16231568
else
@@ -1833,6 +1778,9 @@ lreplace:;
18331778
mtstate->mt_oc_transition_capture :
18341779
mtstate->mt_transition_capture);
18351780
}
1781+
1782+
YbClearSkippableEntities(&estate->yb_skip_entities);
1783+
18361784
list_free(recheckIndexes);
18371785

18381786
/*
@@ -2519,6 +2467,32 @@ tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
25192467
}
25202468
}
25212469

2470+
/*
2471+
* A tuple of a result relation has the "wholerow" junk attribute set at
2472+
* planning time if attributes other than the tuple's YBCTID need to be read at
2473+
* the time of execution. Examples of this include UPDATEs and DELETEs on
2474+
* relations that have secondary indexes -- the columns referenced by the index
2475+
* need to be read in order to update or delete them.
2476+
* Another example is when "update optimizations" are enabled. The optimizations
2477+
* may read the values of multiple attributes to decide whether or not to update
2478+
* them.
2479+
* "wholerow" junk attribute is not applicable to INSERTs as all the information
2480+
* needed for inserting the tuple is supplied in the query.
2481+
*/
2482+
static bool
2483+
YBCHasWholeRowJunkAttr(ResultRelInfo *resultRelInfo, CmdType operation)
2484+
{
2485+
if (operation == CMD_UPDATE && YbIsUpdateOptimizationEnabled())
2486+
return true;
2487+
2488+
if (operation == CMD_UPDATE || operation == CMD_DELETE)
2489+
return YBCRelInfoHasSecondaryIndices(resultRelInfo) ||
2490+
YBRelHasOldRowTriggers(resultRelInfo->ri_RelationDesc,
2491+
operation);
2492+
2493+
return false;
2494+
}
2495+
25222496
/* ----------------------------------------------------------------
25232497
* ExecModifyTable
25242498
*
@@ -2697,9 +2671,7 @@ ExecModifyTable(PlanState *pstate)
26972671
* trigger execution.
26982672
*/
26992673
if (IsYBRelation(resultRelInfo->ri_RelationDesc) &&
2700-
(YBCRelInfoHasSecondaryIndices(resultRelInfo) ||
2701-
YBRelHasOldRowTriggers(resultRelInfo->ri_RelationDesc,
2702-
operation)))
2674+
YBCHasWholeRowJunkAttr(resultRelInfo, operation))
27032675
{
27042676
resno = ExecFindJunkAttribute(junkfilter, "wholerow");
27052677
datum = ExecGetJunkAttribute(slot, resno, &isNull);

0 commit comments

Comments
 (0)