Skip to content

INSERT RETURNING correlated subquery resolves columns to NULL #5489

@LeMikaelF

Description

@LeMikaelF

Description

INSERT and UPSERT RETURNING with correlated subqueries referencing the target table's columns see wrong values. Single-row INSERT sees NULL; multi-row INSERT sees the previous row's value (off-by-one). Affects all correlated subquery forms: scalar subqueries, EXISTS, etc. DELETE RETURNING works correctly.

Reproducer

CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
CREATE TABLE lookup(k INTEGER PRIMARY KEY, v TEXT);
INSERT INTO lookup VALUES(1, 'one'), (2, 'two'), (3, 'three');

-- Single INSERT: subquery sees NULL instead of inserted 'a' value
INSERT INTO t1 VALUES(1, 'x') RETURNING a, (SELECT v FROM lookup WHERE k = a) as val;
-- Turso: 1|NULL
-- SQLite: 1|one

-- Multi-row INSERT: off-by-one, each row sees PREVIOUS row's 'a'
INSERT INTO t1 VALUES(2, 'y'), (3, 'z')
RETURNING a, (SELECT v FROM lookup WHERE k = a) as val;
-- Turso: 2|one, 3|two
-- SQLite: 2|two, 3|three

-- UPSERT also affected:
CREATE TABLE t2(a INTEGER PRIMARY KEY, b TEXT);
INSERT INTO t2 VALUES(1, 'orig');
INSERT INTO t2 VALUES(1, 'new') ON CONFLICT(a) DO UPDATE SET b = 'upserted'
RETURNING a, (SELECT v FROM lookup WHERE k = a) as val;
-- Turso: 1|NULL
-- SQLite: 1|one

-- EXISTS also affected:
CREATE TABLE t3(a INTEGER PRIMARY KEY, b TEXT);
CREATE TABLE t4(x INTEGER PRIMARY KEY);
INSERT INTO t4 VALUES(1), (3);
INSERT INTO t3 VALUES(1, 'a'), (2, 'b'), (3, 'c')
RETURNING a, b, EXISTS(SELECT 1 FROM t4 WHERE x = a) as has_match;
-- Turso: 1|a|0, 2|b|1, 3|c|0 (shifted by one row)
-- SQLite: 1|a|1, 2|b|0, 3|c|1

Per SQLite documentation: "For INSERT and UPDATE statements, references to columns in the table being modified refer to the value of that column after the change has been applied."

The RETURNING subquery is evaluated before the Insert instruction in the bytecode, so cursor-based column reads (RowId/Column on the write cursor) haven't been positioned to the new row yet. Direct column references work because they use register values.


This issue brought to you by Mikaël and Claude Code.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions