Skip to content

Clamp bulk_update batches to the dialect bind-parameter ceiling#21

Merged
vsdudakov merged 2 commits into
mainfrom
fix-bulk-update-param-cap
Jul 16, 2026
Merged

Clamp bulk_update batches to the dialect bind-parameter ceiling#21
vsdudakov merged 2 commits into
mainfrom
fix-bulk-update-param-cap

Conversation

@vsdudakov

Copy link
Copy Markdown
Owner

Problem

bulk_update sized batches purely by batch_size, unlike bulk_create which already clamps by dialect.max_bind_params. Each row costs 2*F + 1 bind parameters (a pk/value pair per field in the CASE arms plus one pk slot in the WHERE ... IN list), so on SQL Server the default batch_size=500 with two or more fields exceeded the 2100-parameter ceiling and failed at runtime.

Changes

  • Model.bulk_update now clamps the effective batch size: min(batch_size, max(1, max_bind_params // (2*F + 1))) — same approach bulk_create uses.
  • SqliteDialect.max_bind_params lowered to 32766, SQLite's actual SQLITE_MAX_VARIABLE_NUMBER default (3.32+); the inherited 65535 allowed bulk statements past it.

Tests

  • SQL-shape guard: the generated UPDATE carries exactly one WHERE and one IN clause per statement and one CASE per written field (tripwire against the per-field WHERE duplication pattern that makes PostgreSQL reject the PK index and seq-scan).
  • Clamp test: with a lowered ceiling, 5 rows x 2 fields split into 3 statements, each under the cap, all rows updated.

Full sqlite suite (1223 tests) and make lint pass.

bulk_update sized batches purely by batch_size, unlike bulk_create which
already clamps by dialect.max_bind_params. Each row costs 2*F + 1 bind
parameters (a pk/value pair per field in the CASE arms plus one pk slot
in the WHERE IN list), so on SQL Server the default batch_size=500 with
two or more fields exceeded the 2100-parameter ceiling and failed at
runtime.

Also lower SqliteDialect.max_bind_params to 32766, SQLite's actual
SQLITE_MAX_VARIABLE_NUMBER default (3.32+); the inherited 65535 allowed
bulk statements past it.

Tests: a SQL-shape guard asserting one WHERE / one IN clause per
statement (one CASE per field), and a clamp test verifying batches split
under a lowered ceiling with all rows still updated.
Oracle treats '' as NULL, so the field's empty-string default violated
the NOT NULL constraint on insert (ORA-01400) and both new tests failed
on the oracle backend in CI.
@vsdudakov
vsdudakov merged commit 185d20f into main Jul 16, 2026
4 checks passed
@vsdudakov
vsdudakov deleted the fix-bulk-update-param-cap branch July 16, 2026 18:56
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