Skip to content

Add DuckDB backend - #42

Merged
nicoloesch merged 17 commits into
mainfrom
4-duckdb-support
Oct 28, 2026
Merged

nicoloesch merged 17 commits into
mainfrom
4-duckdb-support

Conversation

@nicoloesch

@nicoloesch nicoloesch commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

  • Adds a DuckDB backend through a new duckdb extra, with staging tables and batched replace, upsert and insert merges.
  • Backends declare foreign keys they can't enforce through deferred_fk_constraints().
  • load_csv(validate_deferred_fks=True) checks those keys with an anti-join after each merge.
  • The new check does nothing on PostgreSQL and SQLite, which have no deferred keys.
  • On DuckDB, replace updates rows in place, because DuckDB rejects deleting a referenced row.
  • check_replace_feasible() lets callers reject an impossible replace before loading anything.
  • replace with index_strategy="keep" is rejected up front on backends that can't support it.
  • Pagination lives in one shared _paginate_batches helper instead of a copy per backend.
  • Each backend now declares whether it commits between batches.
  • load_csv always drops its staging table, including when the merge fails.
  • Depends on the DuckDB dialect in , so CI fails until that is released.

Checklist

  • Applied exactly one label (breaking, feature, fix, dependencies, or chore)
  • Tests pass locally (uv run pytest -q)
  • Lint passes (uv run ruff check .)

create_tables() only consulted resolved's static schema-translate map
when deciding if a foreign key can span two tags, so a tag claimed only
via create_engine(schema_claims=[...]) raised UnregisteredSchemaTagError
even though the connection itself already resolves it.
@gkennos
gkennos self-requested a review October 27, 2026 00:07
Comment thread src/orm_loader/backends/postgres.py Resolved
Comment thread src/orm_loader/tables/loadable_table.py Resolved
Comment thread src/orm_loader/backends/duckdb.py Resolved
Comment thread src/orm_loader/backends/duckdb.py Resolved
Comment thread src/orm_loader/backends/duckdb.py Resolved
…fore each batch commit

- deferred_fk_constraints/check_replace_feasible now require resolved and delegate to unenforced_foreign_keys() instead of a local cyclic-only approximation, closing the cross-schema blind spot
- validate deferred FKs before every intermediate batch commit, not just once after the whole merge, so an invalid batch is never committed in the first place
@nicoloesch
nicoloesch requested a review from gkennos October 27, 2026 04:27
Comment thread src/orm_loader/backends/base.py Resolved
Comment thread src/orm_loader/backends/postgres.py Resolved
nicoloesch and others added 4 commits October 27, 2026 21:56
…e FKs for real

- merge_insert/merge_upsert load a self-referential deferred FK wave by wave instead of by insertion order, so a child batched before its own parent no longer false-positives, while a genuine orphan is still caught before commit
- DuckDBBackend.validate_deferred_fks checks a cross-database FK by querying each connection separately and comparing in Python, instead of skipping it
…n dependency order

Replace and insert ran as two sealed phases, so an existing row's self-referential FK could be re-pointed at a row only the later insert phase adds, validating it before that row exists. merge_replace now updates and inserts together through the same dependency-ordered wave loop, draining staging itself so the merge_insert() call that follows has nothing left to do.
…alidation

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gkennos
gkennos self-requested a review October 27, 2026 23:25
nicoloesch and others added 3 commits October 28, 2026 02:57
DuckDBBackend now overrides _paginate_batches to yield self-referential
batches in dependency order, falling back to the base class's ordinary
pagination otherwise. This replaces _merge_in_dependency_waves, fixing
unbatched loads committing per wave, valid self-links/cycles being
rejected as orphans, validate_deferred_fks=False being ignored by an
unconditional final check, and upsert's conflict handling being
shadowed by premature readiness checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion

_validate_cross_database_fk rebuilt the remote role's engine from
config, which silently gets an empty, disconnected database for a
DuckDB ':memory:' connection instead of reconnecting to real data.
Raise clearly instead, matching bootstrap.py's existing is_ephemeral_url
guard for the same failure mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_pagination_column/_ensure_pagination_index were byte-for-byte
identical in postgres.py and duckdb.py. Make them the base class's
concrete default (ordering by staging's _rownum, indexed on demand),
with SQLite's rowid-based override now explicit about why it skips
indexing instead of relying on the old no-op default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@gkennos gkennos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the earlier merge and unbatched-commit issues are fixed. The real-vocabulary test loaded 40,466,062 rows (stubbed temporary null handling).

Queries, CRUD, FK validation and maintenance checks passed. No further changes requested for those resolved loader issues.

One thing to note is that restore pathway does commit all tables in one go which means it maxes RAM sooner than you'd otherwise expect but also it fails sensibly so just for noting...

Comment thread src/orm_loader/backends/base.py Resolved
@gkennos
gkennos self-requested a review October 28, 2026 11:45

@gkennos gkennos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sorry meant to approve but accidentally only commented :)

@nicoloesch
nicoloesch merged commit 3998ff9 into main Oct 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New backwards-compatible functionality. MINOR: x.y+1.z

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feasibility review for addition of duckdb backend

2 participants