fix(export): give a dump the TLS options its own client tool takes - #3049
Merged
Merged
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
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.
What happened
File > Backup Dump on a MySQL or MariaDB connection failed with:
NativeDumpRegistry.mysqlConnectionFlagsbuilt the TLS part of everymysqldump/mariadb-dump/mysql/mariadbargument list fromsslConfig.modealone, in MySQL's spelling, for whicheverbinary
CLIExecutableFinderfound onPATH. Three facts make that wrong:--ssl-modeat all.mariadbformula installs MariaDB'sdump tool as
/opt/homebrew/bin/mysqldump, which is the first candidate TablePro tries.NativeDumpDescriptor.Requestcarried no identityof the resolved tool, and
resolveExecutable's(name, path)was handed tobuildCommandas asibling argument and dropped.
Measured
MariaDB 12.3.3 and MySQL 8.4.11 client tools, against a MariaDB server with TLS off, a MariaDB
server with a self-signed certificate, a MySQL server with its own, and PostgreSQL 17 with one.
Authenticating through
MYSQL_PWD, which is how TablePro passes a password.--ssl-mode=PREFERREDunknown variable 'ssl-mode=PREFERRED'--ssl/--skip-sslunknown option '--ssl'The surfaces are disjoint, so the flags follow the tool:
--ssl-mode=DISABLED--skip-ssl--ssl-mode=PREFERRED--ssl --skip-ssl-verify-server-cert--ssl-mode=REQUIRED--ssl --ssl-verify-server-cert--ssl-mode=VERIFY_CA--ssl --ssl-verify-server-cert--ssl-mode=VERIFY_IDENTITY--ssl --ssl-verify-server-certplus
--ssl-ca,--ssl-certand--ssl-keyon both, which nothing sent before.Three measurements decided that table:
--sslalone does not enforce TLS on MariaDB. With verification off it falls back toplaintext against a server without TLS and exits 0, which is the silent cleartext dump Required
exists to prevent. Only
--ssl-verify-server-certrefuses that server.--ssl-cagiven, MariaDB 12.3.3 accepted aserver whose certificate said
CN=totally.other.invalid. The flag requires TLS;--ssl-caiswhat adds the identity check.
--loose-ssl-mode=PREFERREDis accepted by MariaDB and silently ignored. It looks like aone-character fix and downgrades the dump to plaintext.
The fix
NativeDumpResolvedTool(name, path, flavor, version text) is what resolution now produces, andthe argument closures take it as a second parameter instead of guessing.
MySQLDumpToolIdentifierclassifies a binary from its
--version, which carries the token through a rename because it comesfrom the build rather than from
argv[0]. MariaDB's own client names still answer when the toolcannot be run at all, and a tool that stays unidentified is used for Disabled and Preferred and
refused for the three modes that promise encryption, rather than guessed at.
Four more defects in the same argument list, all found while tracing this one and all measured:
connection carries reached neither tool.
mysqldump --ssl-mode=VERIFY_CAwith no--ssl-cafails before connecting with
error: 2026: CA certificate is required, andpg_dumpwithPGSSLMODE=verify-cafails withroot certificate file "~/.postgresql/root.crt" does not exist.Both now get the whole configuration, gated the way the live drivers already gate it.
Unknown table 'column_statistics' in information_schema (1109). It now passes--skip-column-statisticsfor exactly those servers, on the backup direction only.--terminator sat after the database name, so
my_getoptstill parsed it: measured on bothfamilies, a narrowed backup of a database named
--no-datadumped databaset1's tablet2with no rows and exited 0, which the sheet reported as a successful backup. The terminator moved
in front of the database name, in both directions.
--versionprobe is shared.PostgreSQLDumpToolLocator's private spawn becameCLIToolVersionProbe, so one probe serves both locators.Reporting the failure
The same screenshot shows a second defect. The result sheet joined the destination folder and one
sentence per database into a single monospaced block, so
/Users/Nick/Music/New/above a databasecalled
Musicread as the one path/Users/Nick/Music/New/Music, which is how the reporter quotedit. It also kept only the last line of the tool's output: for
pg_dumpthat isIs the server running on that host and accepting TCP/IP connections?whileConnection refusedisthrown away, and for a role without SELECT it is
detail: Query was: LOCK TABLE …whilepermission denied for table …is thrown away.The batch outcome is now a labelled Destination row and one row per database, following
CompareApplySheetView.outcomeRow: a state symbol with its own accessibility label, the database,its size or
Failed, and the failed row's whole output inline and selectable.And
ProcessNativeDumpRunnerdrains its stderr pipe before reading the buffer. The readabilitysource and the process reaper run on independent queues, so a tool that rejects an argument and
exits at once can leave its message in the pipe. Measured with a harness mirroring that class
against a child that writes 66 bytes and exits immediately: between 1 and 6 of every 300 runs
captured nothing at all, the rate rising with load, and 0 of 1,200 with the fix. What the user saw
instead was
Music failed: Process exited with code 7.Two parts to it. A dedicated
stderrLockis held across the read as well as the append, in thereadability handler and in the drain both, so a chunk can never be out of the pipe and still
missing from the buffer when the termination handler snapshots it: nil'ing
readabilityHandlerdoes not join a callback already running. It is separate from
stateLock, whichcancel()takesand which must never wait on a pipe. And the drain is a non-blocking read rather than
readDataToEndOfFile: the child has exited, so everything it wrote is already in the pipe'sbuffer, while reading to EOF would take the same bytes and then wait for every writer to close,
which a grandchild that inherited this end would never do.
Three more, from the review of this branch
Codex's adversarial pass came back "do not ship" on the preflight this change introduces, and it
was right three times:
cancel()only acts once the state is.running, andstartnow spends two process spawns finding the binary and asking it what itis. A user who cancelled in that window saw the tool launch afterwards, which on a restore means
writing to the database they had just said to leave alone.
cancel()latches, andrunrefusesto start a runner once it is set.
readDataToEndOfFilewaits for everywriter to close, so a wrapper that prints its version, starts a helper inheriting standard output
and exits leaves that EOF to the helper and the dump never starts. It reads what the pipe already
holds instead, bounded.
stderrByteCaponly afterwards, so a surviving grandchild writing to inherited stderr could keepthe loop fed and hold up the result and the credentials file's removal. The cap now applies per
chunk and bounds the loop.
The last two are
PostgreSQLDumpToolLocator's own code and this file's own drain; what puts them inscope is that this change makes both run on every MySQL dump.
Before / After
Before is the reporter's own screenshot in #3046: two lines of one monospaced block, where
/Users/Nick/Music/New/sits aboveMusic failed: …and reads as a single path.After is a labelled Destination row, then one row per database: a state symbol, the database,
its size or
Failed, and a failed row's whole output inline underneath. I could not capture it froma running build. The sheet only exists at the end of a real dump, the destination is an open panel
whose fallback is the user's own Downloads folder, and a second TablePro process would disturb the
test runs another session is running on this machine.
#Preview("Backup Batch With A Failure")inBackupResultSheet.swiftrenders exactly this case, the reporter'sMusicrow included, so thelayout can be seen in Xcode without setting any of that up.
Verification
verify.sh build: PASSverify.sh testover the eleven suites that own the changed types: PASS, 123 casesverify.sh lint: PASSdocs/scripts/check-writing-style.shandcheck-docs-against-source.py: PASSscripts/check-mysql-dump-tool-flags.sh: new, and passing against both installed client families.It hands every flag in the table to a real tool of that flavor and fails on one the tool does not
know. No server needed: a rejected flag fails before the socket opens, so a closed port tells the
two apart.
No UI test. Producing this sheet deterministically needs a real dump to a chosen folder, and the
failed row needs a tool that fails on demand; the destination is an open panel with the user's own
Downloads as its fallback, which a UI test must not write into. The row model is a pure function
with unit coverage, including the reporter's exact case.
Fixes #3046