Skip to content

fix: harden MySQL driver integer conversions to prevent crash on table open#1041

Merged
datlechin merged 1 commit intomainfrom
fix/mysql-int-overflow-on-table-open
May 6, 2026
Merged

fix: harden MySQL driver integer conversions to prevent crash on table open#1041
datlechin merged 1 commit intomainfrom
fix/mysql-int-overflow-on-table-open

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

iOS users crashed with EXC_BREAKPOINT "Not enough bits to represent the passed value" when opening some MySQL tables — TestFlight report on a 100k-record table. MySQLActor.execute did Int(mysql_affected_rows(mysql)), but libmariadb is documented to return ~(my_ulonglong)0 (= UInt64.max) as an error sentinel ("for a SELECT, mysql_affected_rows() was called prior to mysql_store_result()"). The unchecked Int conversion trapped on the sentinel.

A full-codebase audit confirmed the bug pattern is confined to MySQL — Postgres (PQntuples/PQnfields return int), SQLite (sqlite3_column_bytes/sqlite3_changes return int), and Redis (uses Int64 directly via redisReply.integer) are all clean by the type system. The macOS MySQL plugin (MariaDBPluginConnection.swift) had the same per-cell length conversion shape with identical exposure but no reported crash; hardened in the same PR.

Changes

  • MySQLDriver.swift:298, 341 (iOS, MySQLActor.execute): detect the documented ~0 sentinel from mysql_affected_rows and map to 0 rather than Int(...)-trapping. Clamping UInt64.max to Int.max would surface "9 quintillion rows affected" in the UI, so the explicit sentinel branch is preferred.
  • MySQLDriver.swift:330 (iOS, row-fetch loop): per-cell length now uses Int(clamping: lengths?[i] ?? 0). A pathological length truncates to Int.max and the downstream Data(bytes:count:) then either succeeds or fails the alloc with a recoverable error — both better than SIGTRAP.
  • MariaDBPluginConnection.swift:504, 918 (macOS plugin, default-fetch + streaming paths): same per-cell length clamping. Removed the intermediate lengthValue: UInt binding (no longer needed).

The macOS plugin's mysql_affected_rows call site (line 430) was already safe — stores the value as UInt64 directly without conversion. iOS was the regression.

What's NOT in scope

  • Postgres iOS driver has for r in 0..<Int32(maxRows) style. Real-world safe (PG caps columns at 1600, maxRows is min(_, 100_000)). Cosmetic refactor would dilute this PR's focus.
  • All other drivers verified safe by audit; no changes.
  • Tests: the trap path requires libmariadb to return sentinel values (or memory corruption), which is not reachable from a unit test without a live broken connection. Existing TableProTests/Plugins/MySQLCreateTableTests.swift covers the SQL-generation path which is unchanged.

Test plan

  • swiftlint lint --strict clean on both touched .swift files
  • Banned-words / em-dash sweep clean on the diff
  • xcodebuild Debug builds for iOS app and macOS app (the user runs these)
  • On TestFlight (post-merge): open a MySQL table with 100k+ rows on iPhone; verify it loads without crash
  • Run an UPDATE … LIMIT 0 (zero rows affected) to exercise the no-result-set affected-rows branch
  • Run SELECT over a TEXT column with multi-MB values to exercise the cell-length path
  • macOS plugin: existing MySQL test suite still passes (xcodebuild test -only-testing:TableProTests/MySQLCreateTableTests)

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit a560c9c into main May 6, 2026
2 checks passed
@datlechin datlechin deleted the fix/mysql-int-overflow-on-table-open branch May 6, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant