Reject primary-key conflicts on insert (BigQuery)#11
Conversation
A plain single-table SELECT with no ORDER BY lets BigQuery return rows in an arbitrary order, so LIMIT/OFFSET pages can overlap or skip rows. default_order_by() injects ORDER BY _id into such queries (ordering only, never the projection) and is a no-op for anything where _id is out of scope: existing ORDER BY, GROUP BY, DISTINCT, aggregates, set ops, CTEs, joins, subqueries, or a non-table FROM. Clauses are detected by child node type rather than sqlglot arg-key name, since those keys are renamed across releases (30.x: from -> from_, with -> with_) and a name-based lookup silently misfires on upgrade.
The unfiltered count path queried project.dataset.INFORMATION_SCHEMA. TABLE_STORAGE, but that view is region-scoped, not dataset-scoped, so the reference 404'd and total was silently dropped. Count the source table directly instead: an unfiltered COUNT(*) is a BigQuery metadata read (0 bytes scanned), so it's just as free, always fresh, and needs no region/location config. Filtered/aggregate queries are unchanged (COUNT(*) FROM (<inner>)). Also restores the default_order_by tests that were dropped earlier, so the committed ordering helper has coverage again.
BigQuery doesn't enforce primary keys, so a plain INSERT silently duplicates rows that collide with an existing row or with another row in the same batch. When a resource declares a primaryKey, wrap the INSERT in a single guarded BigQuery script (BEGIN … IF … RAISE … INSERT … END): a read-only conflict probe runs first and RAISEs before the INSERT if any incoming row would duplicate a key, so the whole batch is rejected with a 400 Validation Error and nothing is written. The probe and the INSERT share one job, so the @rows batch is serialised and uploaded only once. The single INSERT is the script's only write and is atomic — no partial-insert window. PK-less resources keep the plain INSERT (no check). Applies to datastore_upsert method=insert and datastore_create records on an existing table.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthrough
ChangesBigQuery Write Guard and Read Path
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
What
When a resource declares a
primaryKey,datastore_upsertwithmethod=insert(anddatastore_createwithrecordson an existing table) now rejects the batch with a400 Validation Errorif any incoming row would duplicate a key — either against an existing table row or against another row in the same batch. Nothing is written.PK-less resources are unchanged: a plain INSERT, no check.
Why
BigQuery does not enforce primary-key / unique constraints — they're informational only, and this codebase doesn't even emit them in DDL (the
primaryKeylives in the table's Frictionless metadata). So a plain INSERT silently created duplicate rows. This mirrors CKAN's Postgres datastore, wheremethod=inserton a duplicate key errors.How
insert_guarded_sqlwraps the conflict probe and the INSERT in a single BigQuery script:@rowsbatch is serialised + uploaded only once (vs. two jobs / two uploads).RAISEfires before the INSERT, and the INSERT is the script's only write and a single atomic DML statement — all rows land or none.RAISEemits a sentinel + count;_translate_bigquery_errorrebuilds a user-facingValidationErrorfrom BigQuery's wrapped error text.Tests
New unit tests cover the guarded-script shape, the single-write / guard-ordering invariant, the missing-PK guard, the reject path (writes nothing), the clean single-job path, and the no-PK skip. Full suite green.
Note for reviewers
origin/mainwas behind by 2 earlierdatastore_search_sqlcommits when this branch was cut, so this PR may list them untilmainis synced. Only the latest commit (Reject primary-key conflicts on insert) is the substance of this PR.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation
Performance