Skip to content

fix(sqlite): single row RETURNING codegen#124

Open
okuuva wants to merge 5 commits intowsporto:mainfrom
okuuva:push-yluoxooyruop
Open

fix(sqlite): single row RETURNING codegen#124
okuuva wants to merge 5 commits intowsporto:mainfrom
okuuva:push-yluoxooyruop

Conversation

@okuuva
Copy link
Copy Markdown
Contributor

@okuuva okuuva commented Apr 29, 2026

Closes #122.

Summary

Fixes broken or missing RETURNING codegen for INSERT/UPDATE/DELETE statements across all 4 SQLite drivers (better-sqlite3, libsql, bun:sqlite, d1). Each driver had a distinct defect; pg and mysql2 codegen are unchanged.

What was broken

  • better-sqlite3: single-row U/D RETURNING emitted mapArrayTo<T>(res) with no null guard — .get() returns undefined on 0 rows, producing crashes or garbage.
  • libsql (libsql codegen for UPDATE/DELETE … RETURNING indexes ResultSet as array and omits 0-row handling #122): U/D/INSERT RETURNING indexed the libsql ResultSet as if it were an array, with no 0-row handling and no multi-row map.
  • bun:sqlite: had no RETURNING branch at all — UPDATE/DELETE … RETURNING fell into the .run() path and silently dropped output while the type signature lied.
  • d1: returning branch emitted rows.map(...)[0], returning undefined on 0 rows with no INSERT guard and no multi-row path.

What changed

Per-driver, RETURNING codegen now uses the correct accessor for that driver's row representation and respects result cardinality:

  • Single-row UPDATE/DELETE … RETURNING returns T | null with an explicit empty-result guard.
  • Multi-row UPDATE/DELETE … RETURNING returns T[] via rows.map(...).
  • INSERT … RETURNING (single-row): returns T, throws Error('INSERT ... RETURNING returned no rows') if the row vanishes (e.g. concurrent constraint violation handled outside the wrapper).
  • INSERT … RETURNING (multi-row): returns T[] via rows.map(...).

Also extracts a shared writeRowsResultMapping helper for the U/D RETURNING shape and a module-scope INSERT_RETURNING_NO_ROWS_ERROR constant to remove duplicated error literals across the 4 driver branches.

The orNull return-type rule is widened so single-row U/D RETURNING gets T | null regardless of driver.

Known limitation (followup)

The SQLite analyzer in src/sqlite-query-analyzer/parser.ts currently hardcodes multipleRowsResult: false for all DML schema results. As a consequence, the new multi-row RETURNING codegen branches are correct-by-construction but currently unreachable through the public generateTsCode API. They are kept in place — and tagged with TODO comments — so they will Just Work the moment the analyzer is fixed in a followup.

Until the followup lands, queries like INSERT … VALUES (?,?),(?,?) RETURNING * or UPDATE … RETURNING * (without LIMIT 1) emit single-row codegen, which type-lies for the multi-row case.

Test plan

  • npm test (sqlite codegen suite) — 102 passing
  • Single-row UPDATE/DELETE RETURNING fixtures added for libsql, bun:sqlite, d1; existing better-sqlite3 fixtures updated to T | null shape
  • INSERT … RETURNING throw-guard fixtures across all 4 drivers
  • pg + mysql2 fixtures byte-identical (regression gate)

🤖 Generated with Claude Code using Opus 4.7

okuuva added 5 commits April 29, 2026 17:25
INSERT…RETURNING: throw on 0 rows. UPDATE/DELETE…RETURNING:
return T|null when no row matches. Widen single-row U/D
RETURNING return type to T|null.
Fix INSERT…RETURNING to throw on 0 rows. Add UPDATE/DELETE…RETURNING
branch (was indexing ResultSet object as array). Single-row returns
T|null.
Add RETURNING branch (was missing entirely; fell through to .run()
path which dropped the row). INSERT throws on 0 rows; UPDATE/DELETE
single-row returns T|null.
Fix INSERT…RETURNING to throw on 0 rows (was silently dropping).
Add UPDATE/DELETE…RETURNING branch with single-row T|null.
@okuuva okuuva force-pushed the push-yluoxooyruop branch from c97c851 to 48e7121 Compare April 29, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libsql codegen for UPDATE/DELETE … RETURNING indexes ResultSet as array and omits 0-row handling

1 participant