Skip to content

feat(analysis): accept targetless, named, and predicated ON CONFLICT - #73

Merged
omer-cengel merged 1 commit into
masterfrom
feature/targetless-named-and-predicated-conflict-clauses
Oct 11, 2026
Merged

omer-cengel merged 1 commit into
masterfrom
feature/targetless-named-and-predicated-conflict-clauses

Conversation

@omer-cengel

Copy link
Copy Markdown
Member

Summary

Upserts can now use the conflict-clause forms that need no new type inference: ON CONFLICT DO NOTHING without a target, ON CONFLICT ON CONSTRAINT name, a predicate on a column-name conflict target, and DO UPDATE ... WHERE. These were previously rejected even though everything they bind can be typed by the existing WHERE rules.

Changes

  • Accept ON CONFLICT DO NOTHING without a conflict target on :exec and returning inserts.
  • Accept ON CONFLICT ON CONSTRAINT name with DO NOTHING or DO UPDATE; PostgreSQL resolves the constraint name.
  • Accept a predicate on a column-name conflict target, typing its placeholders by the WHERE rules against the inserted table under its exposed name (its alias when present).
  • Accept DO UPDATE ... WHERE, typing its placeholders against both the inserted table and EXCLUDED; an unqualified column present in both is reported as ambiguous, as PostgreSQL does.
  • Bind every conflict-clause placeholder in the order the query writes it.
  • Reject DO UPDATE without a conflict target with a message naming ON CONFLICT (id) and ON CONFLICT ON CONSTRAINT name as the fix.
  • Add analyzer tests for each form, scope, binding order, and rejection, and a PostgreSQL integration test that runs every form through generated code, including a partial unique index target and a returning DO UPDATE ... WHERE that updates once and then skips.
  • Document the accepted targets and actions, the predicate scopes, binding order, returning behavior, and why a conflict-target predicate should be written with literals: PostgreSQL chooses the arbiter index at plan time, so a parameterized predicate stops matching under a generic plan.

Scope and non-goals

  • Constraint names are not checked against the schema.
  • Conflict-target elements that are expressions or carry a collation or operator class remain rejected with the existing message.
  • Predicate columns not compared with a placeholder are left to PostgreSQL, as in WHERE.
  • Placeholders the WHERE rules cannot type keep the existing rejection; no new diagnostic is added.
  • No SQL parser changes.

An INSERT may now use ON CONFLICT DO NOTHING without a target,
ON CONFLICT ON CONSTRAINT name with either action, a predicate on a
column-name conflict target, and DO UPDATE ... WHERE, on :exec and
returning inserts alike. The constraint name is resolved by PostgreSQL.

Placeholders in a target predicate are typed by the WHERE rules over the
inserted table under its exposed name; those in DO UPDATE ... WHERE over
that table and EXCLUDED, so an unqualified column of both is ambiguous.
All conflict placeholders bind in textual order. DO UPDATE without a
target is rejected naming ON CONFLICT (id) and ON CONSTRAINT as the fix.
@omer-cengel
omer-cengel merged commit 1b5e9d7 into master Oct 11, 2026
7 checks passed
@omer-cengel
omer-cengel deleted the feature/targetless-named-and-predicated-conflict-clauses branch October 11, 2026 13:31
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