Skip to content

Commit 39a2cc8

Browse files
gregfeliceclaude
andcommitted
Fix TTS_EMPTY assertion when re-using scan slot after process_path
The four ExecStoreVirtualTuple calls in exec_cypher_merge were triggering an Assert failure under --enable-cassert: TRAP: failed Assert("TTS_EMPTY(slot)"), File: execTuples.c, Line: 1748 ExecStoreVirtualTuple (execTuples.c:1748) asserts that its target slot is in the TTS_EMPTY state. In our MERGE executor, process_path writes directly into the subquery's scan tuple slot -- which already holds the subquery's output tuple and therefore is NOT empty. On a release build the assertion compiles out and ExecStoreVirtualTuple just clears the flag and sets tts_nvalid; on an --enable-cassert build the backend aborts and takes down the regression run. We only need the bookkeeping half of ExecStoreVirtualTuple (clear TTS_FLAG_EMPTY and set tts_nvalid = natts) -- not the "store semantics" that motivate the assertion. Add a small static helper mark_scan_slot_valid() that does exactly the bookkeeping, and replace the four call sites. Release-build behavior is byte-identical since Assert() compiles to nothing; cassert-build behavior now matches release. Caught by the cassert-enabled regression suite we reinstated after #2380. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5e968fb commit 39a2cc8

1 file changed

Lines changed: 22 additions & 4 deletions

File tree

src/backend/executor/cypher_merge.c

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ static bool check_path(cypher_merge_custom_scan_state *css,
8080
static void process_path(cypher_merge_custom_scan_state *css,
8181
path_entry **path_array, bool should_insert);
8282
static void mark_tts_isnull(TupleTableSlot *slot);
83+
static void mark_scan_slot_valid(TupleTableSlot *slot);
8384

8485
const CustomExecMethods cypher_merge_exec_methods = {MERGE_SCAN_STATE_NAME,
8586
begin_cypher_merge,
@@ -357,7 +358,7 @@ static void process_simple_merge(CustomScanState *node)
357358
/* ON CREATE SET: path was just created */
358359
if (css->on_create_set_info)
359360
{
360-
ExecStoreVirtualTuple(econtext->ecxt_scantuple);
361+
mark_scan_slot_valid(econtext->ecxt_scantuple);
361362
apply_update_list(&css->css, css->on_create_set_info);
362363
}
363364
}
@@ -376,6 +377,23 @@ static void process_simple_merge(CustomScanState *node)
376377
}
377378
}
378379

380+
/*
381+
* mark_scan_slot_valid - mark a scan slot as populated after direct writes
382+
* to tts_values[] by process_path.
383+
*
384+
* This does the same bookkeeping as ExecStoreVirtualTuple (clear TTS_EMPTY,
385+
* set tts_nvalid = natts) but without the TTS_EMPTY precondition assertion.
386+
* We cannot use ExecStoreVirtualTuple here because process_path writes into
387+
* a scan slot that already holds the subquery's output tuple -- the slot is
388+
* NOT empty, and asserting it is would fire under --enable-cassert while
389+
* silently clearing the flag on release builds.
390+
*/
391+
static void mark_scan_slot_valid(TupleTableSlot *slot)
392+
{
393+
slot->tts_flags &= ~TTS_FLAG_EMPTY;
394+
slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
395+
}
396+
379397
/*
380398
* Iterate through the TupleTableSlot's tts_values and marks the isnull field
381399
* with true.
@@ -723,7 +741,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node)
723741
css->created_paths_list = new_path;
724742

725743
process_path(css, prebuilt_path_array, true);
726-
ExecStoreVirtualTuple(econtext->ecxt_scantuple);
744+
mark_scan_slot_valid(econtext->ecxt_scantuple);
727745

728746
/* ON CREATE SET: path was just created */
729747
if (css->on_create_set_info)
@@ -823,7 +841,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node)
823841
css->created_paths_list = new_path;
824842

825843
process_path(css, prebuilt_path_array, true);
826-
ExecStoreVirtualTuple(econtext->ecxt_scantuple);
844+
mark_scan_slot_valid(econtext->ecxt_scantuple);
827845

828846
/* ON CREATE SET: path was just created */
829847
if (css->on_create_set_info)
@@ -987,7 +1005,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node)
9871005
process_path(css, NULL, true);
9881006

9891007
/* mark the slot as valid so tts_nvalid reflects natts */
990-
ExecStoreVirtualTuple(econtext->ecxt_scantuple);
1008+
mark_scan_slot_valid(econtext->ecxt_scantuple);
9911009

9921010
/* ON CREATE SET: path was just created */
9931011
if (css->on_create_set_info)

0 commit comments

Comments
 (0)