Skip to content

feat(analysis): accept ON CONFLICT upserts on inserts - #59

Merged
omer-cengel merged 2 commits into
masterfrom
feature/insert-on-conflict-upserts
Oct 3, 2026
Merged

omer-cengel merged 2 commits into
masterfrom
feature/insert-on-conflict-upserts

Conversation

@omer-cengel

Copy link
Copy Markdown
Member

Summary

Inserts can now be written as PostgreSQL upserts. INSERT ... ON CONFLICT (columns) DO NOTHING and ON CONFLICT (columns) DO UPDATE SET ... compile on :exec and returning inserts, with every placeholder typed and bound once per textual occurrence, so the common insert-or-update idiom no longer needs a workaround. Conflict forms the analyzer cannot describe are rejected on every insert instead of passing through unchecked on :exec.

Changes

  • Accept a conflict target that is a parenthesized list of plain column names of the inserted table, each resolved against the schema.
  • Accept DO NOTHING, which binds only the VALUES row.
  • Accept DO UPDATE SET assignments analyzed exactly like UPDATE assignments: one column at a time, where a value is a placeholder typed by its column, a cast placeholder typed by its cast, EXCLUDED.column (resolved against the inserted table), or any other expression passed through as written.
  • Bind DO UPDATE placeholders after the VALUES placeholders in textual order. A repeated named placeholder binds at each occurrence.
  • Allow the same upsert with RETURNING, including RETURNING * resolving the table's shared row record.
  • Reject, on :exec and returning inserts alike, a missing target, ON CONFLICT ON CONSTRAINT, an expression, collated, or operator-class target element, a target predicate, and DO UPDATE ... WHERE. Reject unknown target, assigned, or EXCLUDED columns.
  • Add analyzer tests for the accepted and rejected forms, and a PostgreSQL test that runs both actions, with and without RETURNING, over a new row and then a conflicting one.
  • Document the upsert shape, binding order, DO NOTHING ... RETURNING cardinality, and the rejected forms.

Scope and non-goals

  • sqlcj does not check that the conflict target matches a unique index; PostgreSQL does.
  • An EXCLUDED reference nested inside a larger expression is not resolved, the same as other computed write values.
  • MERGE, ON DUPLICATE KEY, multi-row and SELECT-sourced inserts are unchanged and unsupported.
  • SQL parameter compilation, code generation, and the runtime are unchanged.

Analyze INSERT ... ON CONFLICT (columns) DO NOTHING and DO UPDATE SET on
:exec and returning inserts. Target, assigned, and EXCLUDED columns resolve
against the inserted table, and DO UPDATE placeholders are typed like UPDATE
assignments and bound after the VALUES row in textual order. Constraint
targets, expression, collated, or operator-class target elements, target
predicates, and DO UPDATE ... WHERE are rejected on every insert.
@omer-cengel
omer-cengel merged commit ce5416f into master Oct 3, 2026
6 checks passed
@omer-cengel
omer-cengel deleted the feature/insert-on-conflict-upserts branch October 3, 2026 16:15
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.

1 participant