feat(sql): load remote SQLite and DuckDB databases - #272
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
d5e8cdb to
f7ff7e9
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #272 +/- ##
==========================================
+ Coverage 59.45% 60.15% +0.70%
==========================================
Files 234 236 +2
Lines 10977 11142 +165
==========================================
+ Hits 6526 6703 +177
+ Misses 4451 4439 -12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
amotl
left a comment
There was a problem hiding this comment.
Thank you for this improvement. I've added a comment with a question if the routing could be made differently, so that s3://analytics/snapshots/events.duckdb could be used as an input URL as-is.
| duckdb:///?location=<percent-encoded-s3-az-or-gs-uri>&<storage-options> | ||
| ``` | ||
|
|
||
| For example, this loads `main.events` from `s3://analytics/snapshots/events.duckdb`: |
There was a problem hiding this comment.
Is it possible to treat .duckdb and .sqlite resources using a reader, or otherwise dispatch processing differently, so that ingesting from s3://analytics/snapshots/events.duckdb actually becomes possible without encoding that request in a different way? The URL reads so well!
There was a problem hiding this comment.
Yes, and it reads better this way. Pushed: s3://analytics/snapshots/events.duckdb now works as written, and the location= parameter form is gone.
Routing. A filesystem-scheme source URI whose object carries a database extension (.db, .ddb, .duckdb, .sqlite, .sqlite3) goes to the SQL source instead of a format reader. --source-table main.events keeps selecting a table inside the database, and SQL reflection, chunking, and type mapping stay on the path. Credentials and storage options are the same query parameters the matching file source takes, so there is no second encoding layer:
omniload ingest \
--source-uri 's3://analytics/snapshots/events.duckdb?access_key_id=ACCESS&secret_access_key=SECRET' \
--source-table 'main.events' \
--dest-uri 'duckdb:///local.duckdb' \
--dest-table 'raw.events'I went with dispatch rather than a reader. A reader would have to reimplement reflection, chunking, type mapping and table selection that the SQL source already owns, one engine at a time, and dlt_filesystem would grow a SQLAlchemy dependency. The routing decision stays in omniload; dlt_filesystem only gained a generic "stage this object" primitive.
Engine detection is from the file header, SQLite's marker at offset 0 and DuckDB's DUCK behind its checksum, because .db names both engines and an object's name is not evidence of its contents. So a mislabeled object still loads, and a non-database reports that rather than failing somewhere inside the driver. Only an empty object has no header, and it falls back to an unambiguous extension so a freshly created database stays loadable.
Scope: s3://, r2://, gs://, az://, adls://, abfss://, the schemes whose credential parsing is already shared with the file sources. The connectors added in GH-254 build their fsspec client inside dlt_source(), so staging cannot reach them without lifting that construction into a shared "authorized filesystem for this URI" helper. Happy to do that as a follow-up if you want databases on the other transports; it is the same seam GH-256 is about.
Two things the change deliberately does not do. The split form (--source-uri s3:// plus the object path on --source-table) is rejected with a message naming the carrier to use, since the table has to name a table inside the database. And a dry run now validates against the SQL source the real run uses, instead of the storage source, without downloading the object.
Docs: the contract lives in one place on filesystem.md (Database files), with the DuckDB and SQLite pages carrying just the example and a link. ADR-001 records the grammar and the rejected alternatives.
Verified: full Docker-free suite (980 passed, 44 skipped), the S3, Azure and GCS emulator cases (10 passed in that file), ruff, ty, and a strict Sphinx build. Independent Codex and GLM-5.2 reviews both flagged the split form, which is now handled; GLM caught the dry-run inconsistency.
There was a problem hiding this comment.
That sounds excellent, thank you. Now that the other PR got merged first, this one only needs to resolve conflicts and will be ready to go.
- Load SQLite and DuckDB source files from S3, Azure Blob Storage, and Google Cloud Storage. - Stage each object for the complete ingestion lifetime with size checks, byte verification, cleanup, and no remote writeback. - Reuse filesystem credential parsing while keeping credentials out of logs, errors, and object representations. - Document the remote URI contract and cover parsing, lifecycle, compatibility, and emulator-backed ingestion.
Load a remote SQLite or DuckDB database from the URI its storage service already names it with, `s3://analytics/snapshots/events.duckdb`, rather than encoding that location into a `location` query parameter of a `duckdb:///` URI. A filesystem-scheme source URI whose object carries a database extension routes to the SQL source instead of a format reader, so `--source-table` keeps selecting a table inside the database and SQL reflection, chunking, and type mapping stay on the path. Resolve the engine from the staged file's header (SQLite's marker at offset 0, DuckDB's `DUCK` behind its checksum), because `.db` names both engines and an object's name is not evidence of its contents. An empty object, a database created but not yet written to, falls back to an unambiguous extension. Cover the schemes whose credential parsing is already shared: s3, r2, gs, az, adls, and abfss. Reject the split form (`--source-uri s3://` plus the object path on `--source-table`) by naming the carrier to use, because the table selects a table inside the database. Validate a dry run against the SQL source the real run uses, without downloading the object.
f7ff7e9 to
d944285
Compare
Summary
s3://analytics/snapshots/events.duckdb.--source-tablekeeps selecting a table inside the database..dbneeds no disambiguation and a mislabeled object still loads.Changes
dlt_filesystemand database routing to the SQL source, coverings3://,r2://,gs://,az://,adls://, andabfss://through the filesystem credential parsing those schemes already share.DUCKmagic, falling back to an unambiguous extension only for an empty object.#fragments, credentials in the authority, and the split form that puts the object path on--source-table.mq-bridge-py==0.3.9after its newly published macOS build corrupted values and failed the MQBridge suite; clean resolution selects 0.3.8.filesystem.md, keep the DuckDB and SQLite pages usage-first, and record the grammar plus rejected alternatives in ADR-001.Test plan
tests/main/filesystem/test_remote_integration.pyReferences