feat(export): full import and export, object scope through native dump tools - #2621
Merged
Conversation
…mp with each engine's own tools Claude-Session: https://claude.ai/code/session_011EqgjCjCAU6tiiVmnMpF86
… transfer tables between connections Claude-Session: https://claude.ai/code/session_011EqgjCjCAU6tiiVmnMpF86
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
…sequences, and share the row writers Claude-Session: https://claude.ai/code/session_011EqgjCjCAU6tiiVmnMpF86
…nd format tests Claude-Session: https://claude.ai/code/session_011EqgjCjCAU6tiiVmnMpF86
…at/full-import-export # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2618.
What blocked the issue
Not five independent features. One model:
PluginExportTablecould only describe a table. There was no kind.ExportFormatPlugin.perTableOptionColumnswas static, one option set for the whole format, andoptionValueswas a positional[Bool]. A routine has no "Data" column, so the shape could not carry object kinds.ExportPreselectiononly spoke tables and containers.Every later phase was blocked on that, so this is a refactor rather than five additions.
Phase 1: object scope
PluginExportObjectKindis now carried onPluginExportTablethrough a new initializer overload; both published initializers keep their exact signatures and are marked@_disfavoredOverload, because adding a parameter to one replaces its mangled symbol and breaks every shipped plugin (0.49.0 shipped that withcolumnMeta:).A format declares
supportedObjectKinds; one that declares none receives tables and views, which is what every format written before this expects.supportsOption(columnId:for:)blanks a column a kind does not support in place, sooptionValuesstays positionally aligned for every kind rather than shifting.Two additions to
PluginExportDataSource, both defaulted:fetchObjectDDL(_:)andfetchGrantStatements(principal:host:). Most of the driver work already existed (fetchRoutines,fetchAllTriggers,fetchUserDefinedTypes,fetchViewDefinition,PluginPrincipalManagement.fetchGrants), so the adapter mostly plumbs.Two things worth calling out:
fetchViewDefinition, notfetchTableDDL. Verified both return a full runnableCREATE VIEWon MySQL (SHOW CREATE VIEW) and PostgreSQL (pg_views). PostgreSQL's excludes materialized views, so.materializedViewfalls back tofetchTableDDL.NSOutlineView. Three levels driven by programmaticisExpandedDisclosureGroups is the exact pattern that crashed the connection sidebar (Apple bug, Developer Forums 681275). The one-level tree got away with it; a three-level one would not. It also makes a database with thousands of objects lazy.DROPfor a trigger or routine is asked of the driver (generateDropTriggerSQL,generateDropRoutineSQL), because MySQL has noDROP ROUTINEand itsDROP TRIGGERtakes noONclause.Phase 2: data scope and SQL dialect
PluginExportRowScopecarries aWHERE, a row limit and a column subset per object, edited from a popover on the row. The filter is the user's own SQL against their own connection, so it is not sanitized in the injection sense; what it must not do is smuggle a second statement into theSELECTthe export builds, so a trailing;is dropped and a;anywhere else refuses the filter and the summary says the table went out whole.SQLExportOptionsgains:SQLExportInsertRenderer, which is pure and fully covered: MySQL puts it in the verb, SQLite in a resolution clause, PostgreSQL in a trailingON CONFLICTthat has to name a target. An engine with no spelling writes plain inserts and warns rather than shipping a dump that fails on restore.SQLExportFileWriterrotates between writes, never inside one, so a part always ends on a complete statement.Phase 3: native dump tools per engine
PostgresDumpServiceis nowNativeDumpService, driven by aNativeDumpDescriptorper engine. PostgreSQL and Redshift keep exactly what they had; MySQL/MariaDB, MongoDB, SQLite and libSQL are new.Generalizing was not parameterizing a binary name:
pg_dump -Fcis told a path,mysqldumpandsqlite3 .dumpwrite to standard output, andmongodumptakes neither host flags nor a-d. So a descriptor supplies its own arguments and says how its output is delivered, and the runner redirects stdout or stdin accordingly.Passwords never reach
argv, which every process on the machine can read throughps. PostgreSQL getsPGPASSWORDand MySQLMYSQL_PWD; MongoDB's tools read neither, so a0600config file is written and removed when the process exits, however it exits. A test asserts no engine leaks the password into the argument list in either direction.Phase 4: formats and transfer
Four of the "missing formats" in the issue already shipped or were one option away, which is worth recording rather than building redundant plugins for:
JSONImportParsing.isLineDelimitedalready detects it, and.jsonl/.ndjsonwere already accepted extensions.Transfer To… is the real addition:
TableTransferServicejoins an export data source to another connection's import sink, so rows move with no file in between. Rows only, because inventing DDL that crosses engines is a different problem and getting it half right creates tables whose types quietly disagree with the data.Phase 5: profiles, reports, gating
ExportProfileStoragesaves a selection under a name and reapplies it, including each object's options and row scope. Applying is idempotent, and a profile naming objects the database no longer holds says how many it still matches.Second batch: formats, dumps and the defects found on the way
Two defects in the first batch, both mine.
TableTransferSheetnever populatedcolumnMapping, so the sink skipped every field and each transfer failed on its first batch with "No values in this row matched the column mapping"; my tests covered the pure helper and the request struct but never the seam. It now matches columns by name with an editable per-table mapping, reports unmatched columns before starting, and refuses a table matching nothing by name. Separately,ImportErrorReportwrote server-controlled error text into a CSV with no formula sanitization, which is CSV injection into a file the user opens in a spreadsheet; the CSV export plugin has guarded that for a long time and the report now does too.Parquet needed no new library, and finding that out found a shipped defect. The published
libduckdb.alinked none of the extensionsscripts/duckdb-macos-extensions.cmakedeclares:sum,round,jsonandparquetall failed against it, which is the offline failure that cmake file exists to prevent. The library is rebuilt and republished,scripts/check-duckdb-extensions.shnow probes the shipped binary against its own config so the two cannot drift again, and #2626 records the defect on its own.Parquet export stages rows in an in-memory DuckDB and lets it encode, because Parquet is Thrift-encoded metadata over dictionary and RLE column chunks and a hand-written encoder has nothing local to check it against. Verified end to end against the real artifact: a written file read back as
id BIGINT · name VARCHAR · paid BOOLEAN · due DATE · amt DOUBLE, with an unparseable date becoming null rather than failing the export. DuckDB spills to a temp directory, so a table larger than memory takes longer instead of failing. The plugin is registry-only: it carries its own 149MB universal DuckDB and that is not worth adding to every app download for one format.Also in this batch: Markdown, HTML and XML export; XLSX import with its own ZIP reader and sheet parser; Server-Side Export for Oracle, Snowflake and BigQuery; SQL Server backup and restore through SqlPackage; MySQL events and PostgreSQL standalone sequences as object kinds; the shared row writers moved into PluginKit so MCP and the export plugins escape values identically.
sqlpackageis the one tool with no password channel butargv, wherepscan read it. The descriptor carriesexposesPasswordInArgumentsand the backup flow asks before running one, rather than leaving the user to find out.Deliberately not done
COPYexport. Not reached. Lower value than what shipped, and the SQL export already produces a restorable file for PostgreSQL.Two things to weigh rather than merge blind:
sqlpackageand Oracle Data Pump are written from documentation, not from a run. I had no SQL Server with SqlPackage and no Oracle with aDIRECTORYobject to test against, so an argument could be wrong in a way only a user hits. Everything else here was exercised.Verification
build: PASStestover 26 suites (220 cases): PASSlintoverTablePro, every touched plugin,TableProPluginKitandTableProTests: 0 violationsscripts/ci/check-plugin-manifest.py: 29 plugins agree with the registryscripts/check-duckdb-extensions.sh: every declared extension linkeddocs: PASS, house style and source claims both agreeabiagainst the merge base: additive. The one line the diff shows as removed is the publishedinit(...schema:), re-added byte-identically with@_disfavoredOverload, which is a type-checker hint and not part of the mangled symbol. No version bump, no plugin re-release.plugins(AllPlugins) fails locally on the vendored oracle-nio fork's@TaskLocalmacro (unknown attribute 'usableFromInlinenonisolated'), which is a known toolchain incompatibility unrelated to this diff. The two plugin targets this touches,SQLExportandJSONExport, both build; CI runs the aggregate on its own toolchain.Codex reviewed nothing. It ran for 1h23m and then failed with "Reviewer failed to output a response" (no credit error in its log). Its reasoning trace did name two concerns, which I chased myself: export memory, which is why Parquet now spills to disk, and plugin symbol duplication, which is the DuckDB caveat above. No second model read this diff.
No UI automation. The export tree, the row-scope popover and the transfer sheet all need a live connection with routines, triggers and a second open session to exercise, which
TableProUITestscannot set up deterministically.https://claude.ai/code/session_011EqgjCjCAU6tiiVmnMpF86