Skip to content

A DELETE whose qual deletes another row of the same columnar table fails: 'attempted to update invisible tuple' #1394

Closed

Description

@jdatcmd

What happens

A columnar DELETE fails when its qual calls a function that deletes another row of the same table. Heap deletes both.

CREATE FUNCTION g(i int, t text) RETURNS int LANGUAGE plpgsql AS $$
BEGIN EXECUTE format('DELETE FROM %s WHERE id = %s', t, i + 1); RETURN 1; END $$;
CREATE TABLE u (id int, v int) USING pgcolumnar;
INSERT INTO u SELECT g, 0 FROM generate_series(1, 20) g;
DELETE FROM u WHERE id % 2 = 0 AND g(id, 'u') IS NOT NULL;
-- columnar: ERROR:  attempted to update invisible tuple
-- heap:     DELETE 10, and 1 row is left

Measured on PG16, on #1364's tree (192d51b). No subtransaction is involved. The same error also appears when the inner DELETE runs in a plpgsql EXCEPTION block, which is how I found it (#1360).

Why (from reading the code; the fix will confirm it)

  1. The outer DELETE buffers a mark for row 2.
  2. For row 4, the function's DELETE starts a scan. Scan start flushes the buffered marks, so PgColumnarUpsertDeleteVector updates the group's delete_vector row in command k.
  3. When that inner statement ends, its own mark (row 5, same row group) is flushed in the same command k. The upsert finds the row version written in step 2 (SnapshotSelf sees it) and updates it.
  4. heap_update refuses a tuple whose cmin is not below the current command id (TM_Invisible), hence "attempted to update invisible tuple".

The comment on delete_vector_flush_buffer assumes each chunk group is upserted once per command. A scan-start flush followed by a statement-end flush in the same command breaks that.

Proposed fix

In the upsert, when the existing version was written by the current command, advance the command counter before updating it. Red first: the reproduction above as an arm comparing columnar with heap, in both twins.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions