Skip to content

Fix ON CONFLICT placeholder numbering when inserting multiple rows - #6

Merged
jgaskins merged 1 commit into
jgaskins:masterfrom
concentric-health:fix-multi-row-upsert
Aug 17, 2026
Merged

jgaskins merged 1 commit into
jgaskins:masterfrom
concentric-health:fix-multi-row-upsert

Conversation

@mloughran

@mloughran mloughran commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

CreateManyOperation numbered the placeholders after ON CONFLICT incorrectly.

For example, the added spec generated:

INSERT INTO users ("email", "name") VALUES ($1, $2), ($3, $4) ON CONFLICT (email) DO UPDATE SET name = $3

which is incorrect and fails with "bind message supplies 5 parameters, but prepared statement requires 4".

Fixed version:

INSERT INTO users ("email", "name") VALUES ($1, $2), ($3, $4) ON CONFLICT (email) DO UPDATE SET name = $5

CreateManyOperation numbered the placeholders after ON CONFLICT incorrectly.

For example, the added spec generated:

    INSERT INTO users ("email", "name") VALUES ($1, $2), ($3, $4) ON CONFLICT (email) DO UPDATE SET name = $3

which is incorrect and fails with "bind message supplies 5 parameters, but prepared statement requires 4".

Fixed version:

    INSERT INTO users ("email", "name") VALUES ($1, $2), ($3, $4) ON CONFLICT (email) DO UPDATE SET name = $5

@jgaskins jgaskins left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thank you!

@jgaskins
jgaskins merged commit c3b5116 into jgaskins:master Aug 17, 2026
30 checks passed
@mloughran
mloughran deleted the fix-multi-row-upsert branch August 24, 2026 22:41
mloughran added a commit to concentric-health/interro that referenced this pull request Aug 26, 2026
A conflict handler was told where to start numbering: each INSERT counted the placeholders its VALUES lists had emitted, appended the handler's values to the args array itself, and passed the total as `start_at`. For a multi-row insert that count was rows × columns, which is what jgaskins#6 fixed.

The handler now appends its own values to the shared args array as it renders, so its placeholder numbers are simply the positions they land at, and nobody has to know how many came before. That deletes `start_at`, both create operations' computation of it, and the `Action#params` hook that only existed so they could collect those values ahead of rendering.

This was the last placeholder numbering done independently of the args array: every write path, like every read path before it, now builds SQL and args in one pass.
mloughran added a commit to concentric-health/interro that referenced this pull request Aug 26, 2026
A conflict handler was told where to start numbering: each INSERT counted the placeholders its VALUES lists had emitted, appended the handler's values to the args array itself, and passed the total as `start_at`. For a multi-row insert that count was rows × columns, which is what jgaskins#6 fixed.

The handler now appends its own values to the shared args array as it renders, so its placeholder numbers are simply the positions they land at, and nobody has to know how many came before. That deletes `start_at`, both create operations' computation of it, and the `Action#params` hook that only existed so they could collect those values ahead of rendering.

This was the last placeholder numbering done independently of the args array: every write path, like every read path before it, now builds SQL and args in one pass.
mloughran added a commit to concentric-health/interro that referenced this pull request Sep 21, 2026
A conflict handler was told where to start numbering: each INSERT counted the placeholders its VALUES lists had emitted, appended the handler's values to the args array itself, and passed the total as `start_at`. For a multi-row insert that count was rows × columns, which is what jgaskins#6 fixed.

The handler now appends its own values to the shared args array as it renders, so its placeholder numbers are simply the positions they land at, and nobody has to know how many came before. That deletes `start_at`, both create operations' computation of it, and the `Action#params` hook that only existed so they could collect those values ahead of rendering.

This was the last placeholder numbering done independently of the args array: every write path, like every read path before it, now builds SQL and args in one pass.
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.

2 participants