Skip to content

x/sqlbuilder/upserter: add MultiExec - #47

Open
AlexisMontagne wants to merge 2 commits into
masterfrom
am-upserter-multi-exec
Open

AlexisMontagne wants to merge 2 commits into
masterfrom
am-upserter-multi-exec

Conversation

@AlexisMontagne

@AlexisMontagne AlexisMontagne commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Upserting many rows one by one costs a round trip per row, plus a serializable transaction of their own without the key constraint.

This adds MultiExecer to x/sqlbuilder/upserter, which executes a batch of rows at once, all or none of them:

  • a single INSERT ... ON CONFLICT for the key constrained Insert and Upsert modes, each row updating with its own values (via Excluded)
  • one transaction otherwise

What are the observable changes?

Feature: statements built by the upserter can be executed for many rows in one call. The capability is discovered with a type assertion:

if me, ok := execer.(upserter.MultiExecer); ok {
	res, err := me.MultiExec(ctx, rows)
}

The key constrained Update mode and the statements with Returning do not implement it.

Good PR checklist

  • Title makes sense
  • Is against the correct branch
  • Only addresses one issue
  • Properly assigned
  • Added/updated tests
  • Added/updated documentation
  • Properly labeled

Additional Notes

  • The single INSERT is bound by the database parameter limit, as the rows are not chunked.
  • Depends on InsertExecer.MultiExec and the Excluded marker, both already on master.

Upserting many rows one by one costs a round trip per row, and a serializable
transaction of their own without the key constraint. MultiExecer executes a
batch of rows at once, all or none of them: with a single INSERT ... ON
CONFLICT for the key constrained Insert and Upsert modes, each row updating
with its own values, and in one transaction otherwise. It is discovered with a
type assertion, as the key constrained Update mode, the key constrained Upsert
mode with a PredicateClause and the statements with Returning cannot implement
it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unconstrained batching through InTxUpserter can leave partial writes after an error, violating the all-or-none guarantee.

1 open issue
What changed in this PR

Adds optional batch execution to x/sqlbuilder/upserter, reducing transaction overhead while preserving existing single-row execution.

Changes:

  • Introduces MultiExecer with a single insert for constrained batches and a shared transaction for unconstrained batches.
  • Refactors row execution and hides batch support for returning statements and constrained updates.
  • Adds tests for batch results and capability detection.
File Description
x/​sqlbuilder/​upserter/​upserter_test.go Tests batch insert, update, upsert, and unsupported configurations.
x/​sqlbuilder/​upserter/​execer.go Implements batch execution and shared row-processing helpers.

Review effort: Balanced

Comment thread x/sqlbuilder/upserter/execer.go

@jlevesy jlevesy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👀

Sounds good

I'm not fully sure to wrap my head around everything. Would appreciate a walkthrough.

Comment thread x/sqlbuilder/upserter/execer.go Outdated
),
stmt.SetValues...,
),
Table: stmt.Table,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NIt: could be nice to split this function into newQueryConstrainedExecer and newInPlaceExecer or something like that.

Comment thread x/sqlbuilder/upserter/execer.go Outdated
Where: stmt.PredicateClause,
},
)
Returning: stmt.Returning,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚫 [golangci] reported by reviewdog 🐶
SA1019: (github.com/upfluence/sql/x/sqlbuilder.InsertStatement).Returning is deprecated: Please use Returnings (staticcheck)

},
Action: sqlbuilder.Nothing,
},
Returning: stmt.Returning,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚫 [golangci] reported by reviewdog 🐶
SA1019: (github.com/upfluence/sql/x/sqlbuilder.InsertStatement).Returning is deprecated: Please use Returnings (staticcheck)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants