fix: Postgres SQL export/import round-trip (#1114)#1126
Conversation
There was a problem hiding this comment.
💡 Codex Review
When importing PostgreSQL SQL files, this path ignores the dialect stored on SqlFileImportSource and calls parseFile with its default .generic dialect. The dialog's statement count now uses the PostgreSQL dialect, but the actual import still treats $$...$$ function bodies as generic SQL and will split on semicolons inside them, so a dump containing dollar-quoted PL/pgSQL can be counted correctly and then fail during import. Use parser.parseFile(url: fileURL, encoding: encoding, dialect: dialect) here.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… to writeFinalizationPhase (#1114)
Summary
Closes #1114. Postgres dumps produced by TablePro now round-trip cleanly through TablePro itself.
Three classes of bug were in the same flow:
Parser:
SQLFileParsertreated\'as a MySQL-style escape inside single-quoted strings, but Postgres dumps are written understandard_conforming_strings=onsemantics where backslash is literal. A value ending in\followed by another value containing;desynced the parser and yielded a truncatedINSERTthat PG rejected withunterminated quoted string. The parser is now dialect-aware (postgres / mysql / sqlite / generic) with E-string opt-in (E'…'), tagged dollar-quoted strings ($tag$ … $tag$with$1$correctly rejected as a parameter), and dialect-gated#comments andDELIMITERhandling.Exporter: identity columns (
GENERATED ALWAYS AS IDENTITY) and computed columns (GENERATED ALWAYS AS (…) STORED) were emitted into INSERT lists; FK constraints sat inline inCREATE TABLEso child tables could fail when sorted before parents; sequences exported asCREATE SEQUENCE START WITH Xnever resynced. Export now runs in four phases — DROP (reverse FK-dep + CASCADE), CREATE (forward FK-dep, no FK clauses, withGENERATED … AS IDENTITYpreserved viapg_attribute.attidentity), data INSERT (withOVERRIDING SYSTEM VALUEonly when at least one identity-always column exists, generated columns filtered out), and ALTER ADD CONSTRAINT FK + per-identitypg_catalog.setval(pg_get_serial_sequence(...), MAX(col))to advance implicit sequences past the imported max id.Performance and UX: parser was per-
unicharCFStringAppendCharacterswith an unboundedAsyncThrowingStream(~900 MB / 100 % CPU on a 17 MB file). Now uses run-length substring appends andbufferingPolicy: .bufferingNewest(8), plus a UTF-8 tail buffer for chunks straddling the 64 KB read boundary. PG plugin'sforeignKeyDisableStatementsnow returnsSET session_replication_role = replicainstead ofnil. Tables sidebar refreshes after import — the.refreshDatanotification is now posted from the success sheet'sonDismissso the main window is key whenobserveKeyWindowOnlychecks.Plugin ABI bump
PluginColumnInfogainedisIdentity,identityKind,isGenerated(all defaulted, source-compatible).currentPluginKitVersionbumped 9 → 10 across all 21 plugin Info.plists. Built-in plugins ship with the app and rebuild together. User-installable plugins (MongoDB, Oracle, DuckDB, MSSQL, Cassandra, Etcd, CloudflareD1, DynamoDB, BigQuery, LibSQL) need separateplugin-*-v*releases tagged from main after this merges; until they ship, those plugins gracefully reject load with the existing "ABI mismatch" path atPluginManager.swift:442rather than crashing.Test plan
SQLFileParserTests— 16 cases covering each dialect, dollar quotes, E-strings,$1$rejection, doubled-quote escape, multi-byte UTF-8 at chunk boundary, large multi-row INSERT, and the verbatim Postgresql export then import fails #1114 repro fixture.sql.gz, drop tables, re-import, all 1002 rows present including the poison rows with backslashesGENERATED ALWAYS AS IDENTITYclauses preserved, 2pg_catalog.setvalcalls in phase DINSERT … RETURNING idreturnsMAX(id) + 1(no PK collision)