refactor(ios): streaming data architecture for large tables and queries#1045
Merged
refactor(ios): streaming data architecture for large tables and queries#1045
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…t remap whole window
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.
Summary
Rebuilds the iOS data layer around streaming so opening large tables and running ad-hoc queries no longer materialises the entire result set in memory. Eliminates several latent OOM crashes reported on TestFlight (the most recent being the 100k-row table bug fixed in #1041; same shape would still have killed the app on a single 700 MB BLOB column).
Approach
TableProModels(used by both iOS and macOS).Cellenum has four cases:.null,.text(String),.truncatedText(prefix:totalBytes:ref:)for text > 4 KB,.binary(byteCount:ref:)for any binary column. Wide cells carry only metadata plus aCellRef(table + PK values) for re-fetching the full value on demand.Rowwraps[Cell].StreamElementis the streaming event type.StreamOptionscarries truncation thresholds and lazy context.executeStreaming(query:options:)method onDatabaseDriverwith a backwards-compatible default implementation that bridges through legacyexecute(query:). macOS app and all 14 plugins keep working unchanged through the default; iOS drivers override with real C-level streaming.mysql_store_result(buffered, libmariadb mallocs the full result before Swift sees the first row) tomysql_use_result(server-side cursor, one row per fetch).PQexectoPQsendQuery+PQsetSingleRowMode+PQgetResultper-row.sqlite3_steprow loop. Stops eagerly base64-encoding BLOBs; emitsCell.binary(byteCount:ref:)so the bytes never enter Swift memory.AsyncThrowingStream.onTerminationpropagates to the C layer (PQcancel,sqlite3_interrupt).MemoryPressureMonitor(new actor): wrapsDispatchSourceMemoryPressure+os_proc_available_memory; started at app launch.RowWindow(new): bounded sliding buffer (default 200 rows) replacing the unbounded[[String?]]@Statearrays in views.DataBrowserViewModel/QueryEditorViewModel(new,@Observable): own theRowWindow, drive the streaming, listen to memory pressure, callRowWindow.shrink(to:)on warning (100 rows) or critical (50 rows + cancel).StreamingExporter(new): writes CSV / JSON / SQL to aFileHandlein O(1) memory regardless of result size.RowDetailView: TabView (which pre-rendered 2-3 full rows on iPad split view) replaced with singlerowContent+ horizontalDragGesturefor prev/next. Memory cost O(1) per row.Test plan
Run on a real iPhone (not Simulator) to exercise iOS memory enforcement.
[BLOB 200 MB], no decode at fetch time.SELECT * FROM big_table. Tap Stop. VerifyPQcancel/sqlite3_interrupt/mysql_use_resultcleanup actually fires.DataBrowserViewModel.windowshould shrink and post a toast.xcodebuild test -scheme TablePro -only-testing:TableProTests/MySQLCreateTableTestsstill passes (no behaviour change in macOS plugin path).swiftlint lint --strictclean on every new file.What's NOT in scope (deferred)
NavigationSplitViewregular-size-class adaptation inConnectionListView. The OOM root cause is fixed by streaming + windowing; the iPad split view is UX polish for a follow-up.executeStreaming. They keep using the legacyexecute(query:)path via the protocol default. A future PR can switch them piecemeal.Cell-aware UI (per-cell "Load full value" button inRowDetailView). The currentlegacyValuesbridge surfaces wide cells with placeholder strings; the lazy-load button can ship in a follow-up that doesn't change the streaming layer.Risk
mysql_use_resultsemantics: requires draining all rows before the next query. The actor'sendStreamdoes this. Cancellation drains too.PQsetSingleRowMode: must be called immediately afterPQsendQuery, before anyPQgetResult. Implementation matches that order.