fix(ios): six collateral defects found while fixing #2543 and #2544 - #2549
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
The six defects the investigation behind #2548 found and reported rather than fixed. One commit each.
Safe Mode let a write run when it followed a comment or a CTE
QueryEditorView.isWriteQueryclassified byhasPrefixover eight uppercased keywords, so recognition required the write verb to be the literal first characters. Anything else fell through thesafeModeLevel.writePermissiongate and ran unconfirmed. On a Read-Only connection:A write-keyword allowlist cannot be made safe, because whatever it has not been taught runs unguarded.
SQLWriteClassifierinTableProQueryinverts it: a statement is a read only when its leading keyword is one of a short closed set, and everything else writes, including a keyword it has never seen. It strips leading line and block comments, splits a batch on semicolons that are not inside a literal, an identifier quote or a comment, and treats any write in the batch as a write.Five holes were found by compiling and running an earlier draft, and each has a test:
EXPLAIN ANALYZE DELETE FROM usersPRAGMA journal_mode = WALSELECT * INTO backup FROM usersSELECT 'a\' ; DELETE FROM usersstandard_conforming_strings = on, so the backslash is not an escape and this is two statementsWITH x AS (…) SELECT * INTO backup FROM xRedis needed its own arm. It speaks commands rather than SQL, so the SQL path called every
GETa write, and Read-Only would have refused every read on a supported engine whose Query tab is always shown. The read-command set is the oneQueryClassifier.swift:436-451already curates for the Mac.The classifier lives in
TableProQuery, which only TableProMobile and TableProCore's own tests link, so this compiles nothing the macOS app builds. macOS keeps its ownQueryClassifier; sharing one would mean generalising ~1,300 lines over two differentDatabaseTypes and was out of scope here.Deep links could never work on iOS
TableProMobileApp.onOpenURLparsestablepro://connect/<uuid>, the Open Connection intent opens that URL, both widget views and the Live Activity build it, anddocs/ios/index.mdxsays deep links work as on the Mac.CFBundleURLTypeswas absent from the iOSInfo.plist, so the app claimed no scheme and the OS had nothing to route to. Measured:PlistBuddyreported the key does not exist on the built app, andsimctl openurlfailed withLSApplicationWorkspaceErrorDomain error 115until the plist was patched by hand.The entry mirrors the Mac's first one exactly. The Mac's second entry, the
postgresql/mysqlschemes, is deliberately not copied: nothing on iOS parses an external database URL, and an unused claim is how this drifted in the first place.PostgreSQL and Redshift read the wrong schema
fetchTables,fetchColumns,fetchIndexesandfetchForeignKeyseach resolved a nil schema to the literal"public"and never readcurrentSchema, whichswitchSchemakeeps in step withsearch_path. Measured against PostgreSQL 17: with anapp.ordersof three columns and apublic.ordersof two, the driver returned two columns, so the app showed and edited metadata belonging to a different table rather than simply finding nothing.effectiveSchemaalone would have been inert.connect()never probed, socurrentSchemastayed the seeded"public"and the fallback returned the literal it replaced on every fresh connection; the fix would only have bitten after the user switched schema by hand.adoptServerSchemarunsSELECT current_schema()at connect, the wayMSSQLDriverandOracleDriveralready do. Measured: returnsappfor a role carryingALTER ROLE … SET search_path TO app, andpublicotherwise.SQL Server read and wrote the login's default schema
T-SQL has no session-level schema to set, so
MSSQLDriver.switchSchemacorrectly only records the choice, and metadata already followed it. The data statements did not: everySELECT,COUNT,UPDATEandDELETEnamed a bare table, which resolves against the login's default schema rather than the one in the toolbar.Two paths were worse than the report described, and neither was in it:
TableListViewranTRUNCATE TABLEandDROP TABLEunqualified. With asales.ordersand adbo.orders, the destructive statement could hit the wrong one while the UI named the other.FKPreviewViewbuilt an unqualifiedSELECTreachable from the row context menu.schemais now a required parameter on all tenSQLBuilderdata builders, so no call site can silently stay unqualified, and it flows fromcoordinator.activeSchemathrough the view model. The foreign-key preview usesForeignKeyInfo.referencedSchemarather than the browsing schema, because a key may point into another schema; no iOS driver populates that field yet, so it stays unqualified exactly as before and becomes correct the moment one does.The browser keeps one schema snapshot. An earlier version recomputed it per child, so a schema switch with a table open left the view model on the old schema for the rows and deletes while the row editor and insert form used the new one, and an edit could reach a different table than the rows on screen. Every statement now reads
viewModel.schema, and a switch re-attaches and reloads.This reaches five engines, not one:
supportsSchemascovers PostgreSQL, Redshift, SQL Server, DuckDB and Oracle.Computed SQL Server columns were offered as editable
MSSQLColumnRowcarried no computed flag, soColumnInfo.isGeneratedstayed false and a computed column rendered as an ordinary field. Measured on SQL Server 2022:COLUMNPROPERTY(…, 'IsComputed')returns 1 for it, writing to it givesMsg 271 … cannot be modified because it is either a computed column or is the result of a UNION operator, and omitting it computes correctly.The macOS plugin had the same bug independently, and
PluginColumnInfo.isGeneratedpredates #2548 and was simply never populated. Because the plugin owns its own DML generation, populating the field was not enough on its own: computed columns now join the identity cache thatgenerateMssqlInsertalready filters, andfetchAllColumnspopulates both caches, having previously populated neither.Exports corrupted data on a round trip
Three separate defects in
ClipboardExporter:row[i] ?? "NULL"unquoted, so a real NULL and a cell holding the textNULLwere identical. NULL is now an unquoted empty field and the text is quoted.Int64("01234")succeeded and the export emitted01234unquoted, which no JSON parser accepts. It now tests the JSON number grammar over ASCII digits, which also keeps+5,0x10and١٢٣quoted. Gating on the column type was rejected:columnsis the result set, MySQL reportsNEWDECIMAL, and SQLite reports""for every expression, soSELECT COUNT(*)would have regressed to a quoted string.SQLBuilder.quoteIdentifierand literals through the driver, matching howSQLBuilderalready works. This also closes an unescaped embedded quote in an identifier.parseCSVis deliberately unchanged. Pairing CSVNULL → ""withparseCSV "" → .nullwould turn every empty string into NULL on re-import, which is the same indistinguishability being fixed.Verification
TableProQuery: 21 classifier tests passMSSQLDriverplugin target: builds clean, which PR CI never does because MSSQL is registry-onlyTableProMobile,Packages/TableProCore/SourcesandPlugins/MSSQLDriverPlugin; docs checks passBehaviour was measured against a real PostgreSQL 17, a real SQL Server 2022,
sqlite3and theduckdbCLI rather than read off documentation. Both patched MSSQL queries were run against the live server to confirmIS_COMPUTEDlands at the index the parser reads.Two reviewers read the diff. A
code-reviewpass produced six findings, five fixed here. A Codex pass produced five more, all addressed, including the schema-snapshot desync above and a CTE that calledreplace()classifying as a write, which would have made Read-Only block an ordinary read. A self-directed security pass then found one more, fixed here: narrowing the CTE scan to remove that false positive had reopenedSELECT INTOthrough the CTE path.Two things to know before this ships
MSSQL is registry-only. The plugin half of the computed-column fix reaches users through a
plugin-mssql-v*tag rather than the next app release. No plugin has been published.isGeneratednow also blocks grid edits on macOS for computed columns, viaDataGridView+Editing.swift. That is correct, since SQL Server refuses the write, but it is a visible change to existing behaviour.Not covered
TableProMobilehas no UI test target, so none of this could get XCUITest coverage. Oracle and DuckDB still report neither identity nor generated columns, and SQL Server reports no generated flag on iOS beyond the computed one added here; their metadata queries live in packages the macOS app also links. A column left on DEFAULT is omitted on every engine regardless, so the user-facing behaviour is correct everywhere; what those engines lose is the extra refusal when a value is typed in.