Releases: datlechin/TablePro
Releases · datlechin/TablePro
v0.10.0
Added
- Support for multiple independent database connections in separate windows with per-window session isolation
- MongoDB database support
- Custom About window with version info and links (Website, GitHub, Documentation)
- Import database connections from URL/connection string (e.g.,
postgresql://user:pass@host:5432/db) - Release notes in Sparkle update window
Fixed
- New row (Cmd+I) and duplicated row not appearing in datagrid until manual refresh
- PostgreSQL SSH tunnel connections failing with "no encryption" due to SSL config not being preserved
- PostgreSQL SSL
sslrootcertpassed unconditionally to libpq, causing certificate verification failure even inRequiredmode
v0.9.3
Fixed
- PostgreSQL SSH tunnel connections failing with "no encryption" due to SSL config not being preserved
v0.9.2
Fixed
- Fix app bundle not ad-hoc signed — signing step was unreachable when no dylibs were bundled
v0.9.1
Fixed
- Fix Sparkle auto-update failing with "improperly signed" error — release ZIPs now preserve framework symlinks and include proper ad-hoc code signatures
v0.9.0
Added
- Vim keybindings for SQL editor (Normal/Insert/Visual modes, motions, operators, :w/:q commands) with toggle in Editor Settings
^and_motions (first non-blank character) in Vim normal, visual, and operator-pending modes:qcommand to close current tab in Vim command-line mode- PostgreSQL schema switching via ⌘K database switcher (browse and switch between schemas like
public,auth, custom schemas)
Changed
- Convert QueryHistoryStorage and QueryHistoryManager from callback-based async dispatch to native Swift async/await — eliminates double thread hops per history operation
- Consolidate ExportService @published properties into single state struct — reduces objectWillChange events from 7 per batch to 1
- Consolidate ImportService @published properties into single state struct — reduces objectWillChange events during SQL import
- Replace DispatchQueue.main.asyncAfter chains in AppDelegate startup with structured Task-based retry loops
- Merge 3 identical Combine notification subscriptions in SidebarViewModel into Publishers.Merge3
- Make AIChatStorage encoder/decoder static — shared across all instances instead of duplicated
Fixed
- Cell edit showing modified background but displaying original value until save (reloadData during active editing ignored by NSTableView, updateNSView blocked by editedRow guard)
- Undo on inserted row cell edit not syncing insertedRowData (stale values after undo)
- Vim Escape key not exiting Insert/Visual mode when autocomplete popup is visible (popup's event monitor consumed the key)
- Copy (Cmd+C) and Cut (Cmd+X) not working in SQL editor — clipboard retained old value due to CodeEditTextView's copy: silently failing
- Vim yank/delete operations not syncing to system clipboard (register only stored text internally)
- Vim word motions (
w,b,e) using two-class word boundary detection instead of correct three-class (word chars, punctuation, whitespace) - Vim visual mode selection now correctly includes cursor character (inclusive selection matching real Vim behavior)
- Arrow keys now work in Vim visual/normal mode (mapped to h/j/k/l instead of bypassing the Vim engine)
- Vim block cursor now follows the moving end of the selection in visual mode instead of staying at the anchor
- Vim visual mode selection highlight now renders visibly (trigger needsDisplay after programmatic selection)
- Fix event monitor leaks in SQL editor —
deinitnow cleans up NSEvent monitors, notification observers, and work items that leaked when CodeEditSourceEditor never calleddestroy() - Fix unbounded memory growth from NativeTabRegistry holding full QueryTab objects (including RowBuffer references) — registry now stores lightweight TabSnapshot structs
- Fix SortedRowsCache storing full row copies — now stores index permutations only, halving sorted-tab memory
- Fix schema provider memory leak — shared providers are now reference-counted with 5s grace period removal when all windows for a connection close
- Fix duplicate schema fetches in InlineSuggestionManager — now shares the coordinator's SQLSchemaProvider instead of maintaining a separate cache
- Fix background tabs retaining full result data indefinitely — RowBuffer eviction frees memory for inactive tabs (re-fetched on switch back)
- Fix InMemoryRowProvider bulk cache eviction — now uses proximity-based eviction keeping entries near current scroll position
- Fix stale tabRowProviders entries when tab IDs change without count changing
- Fix crash on macOS 14.x caused by
_strchrnulsymbol not found in libpq.5.dylib — switch libpq and OpenSSL from dynamic Homebrew linking to vendored static libraries built with MACOSX_DEPLOYMENT_TARGET=14.0 - Fix duplicate tabs and lag when inserting SQL from AI Chat or History panel with multiple window-tabs open — notification handlers now only fire in the key window
- Fix "Run in New Tab" race condition in History panel — replaced fragile two-notification + 100ms delay pattern with a single atomic notification
- Fix MainContentCoordinator deinit Task that may never execute — added explicit teardown() method with didTeardown guard and orphaned schema provider purge
- Fix SQLEditorCoordinator deinit deferring InlineSuggestionManager cleanup to Task — added explicit destroy() lifecycle and didDestroy guard with warning log
- Fix ExportService while-true batch loops not checking Task.isCancelled — cancelled exports now stop promptly instead of running all remaining batches
- Fix DataGridView full column reconfiguration on every resultVersion bump — narrowed rebuild condition to only trigger when transitioning from empty state
- Fix ConnectionHealthMonitor fixed 30s interval that delays failure detection — added checkNow() with wakeUpContinuation for immediate health checks and exponential backoff
- Fix HistoryPanelView and TableStructureView asyncAfter copy-reset timers not cancellable — replaced with cancellable Task pattern
- Fix MainContentView redundant onChange handler causing cascading re-renders on tab/table changes
- Fix DatabaseManager notification observer creating unnecessary Tasks when self is already deallocated — added guard let self before Task creation
v0.8.0
New Features
- Native macOS Window Tabs: The tab bar is now rendered by macOS itself — identical to Finder, Safari, and Xcode tabs with automatic dark/light mode support, drag-to-reorder, and "Merge All Windows" for free
- Independent Tab Windows: Each tab is a full independent window with its own sidebar, editor, and state — no more shared state between tabs
Improvements
- Tab Switching Performance: Schema is now cached per connection so new native tabs reuse the already-loaded schema instead of re-fetching from the database (saves 500ms–2s per tab)
- Background Schema Loading: Schema loads concurrently — table data appears immediately while autocomplete schema loads in the background
- Sidebar MVVM Refactor: Sidebar table list migrated to a testable
SidebarViewModelarchitecture with extractedTableRowViewand context menu components - Window title updates dynamically after in-place navigation (sidebar click, FK navigation)
- 10+ SwiftUI rendering optimizations to prevent O(N) view cascades across windows
Bug Fixes
- Fixed sidebar losing keyboard focus (arrow key navigation) after opening a second table tab
- Fixed sidebar active state flash when clicking a table that opens in a new native window tab
- Fixed sidebar losing active state when opening a second table in a new native window tab
- Fixed sidebar not refreshing after switching databases via Cmd+K
- Fixed Cmd+W in empty state doing nothing — now closes the connection window and disconnects
- Fixed Cmd+K database switch flooding all windows with error alerts
- Fixed clicking a table in the sidebar replacing the current tab instead of opening a new one
- Fixed Cmd+W on any tab disconnecting the entire session
- Fixed Cmd+T from empty state creating two native tabs instead of one
- Fixed native tab title showing "SQL Query" instead of the table name
- Fixed Cmd+W on the last tab disconnecting the session instead of returning to empty state
v0.7.0
Added
- Quick search and filter rows can now be combined — when both are active, their WHERE conditions are joined with AND
- Foreign key columns now show a navigation arrow icon in each cell — click to open the referenced table filtered by the FK value
Changed
- Metadata queries (columns, FKs, row count) now run on a dedicated parallel connection, eliminating 200-300ms delay for FK arrows and pagination count on initial table load
- Approximate row count from database metadata displays instantly with data; exact count refines silently in the background
- Show warning indicator on filter presets referencing columns not in current table
- Increase filter row height estimate for better accessibility support
- FK navigation now uses dedicated FilterStateManager.setFKFilter API instead of direct property manipulation
- Add syntax highlighting to Import SQL file preview
- XLSX export now enforces the Excel row limit (1,048,576) per sheet and uses autoreleasepool per row to reduce peak memory during large exports
- Multiline cell values now use a scrollable overlay editor instead of the constrained field editor, enabling proper vertical scrolling and line navigation during inline editing
- AnyChangeManager now uses a reference-type box for lazy initialization, avoiding Combine pipeline creation during SwiftUI body evaluation
- DataGridView identity check moved before AppSettingsManager read to skip settings access when nothing has changed
- DataGridView async column width write-back now uses an isWritingColumnLayout guard to prevent two-frame bounce
- Tab switch flushPendingSave debounced to skip redundant saves within 100ms of rapid tab switching
- SQL editor frame-change notification throttled to 50ms to avoid redundant syntax highlight viewport recalculation on every keystroke
- SQL editor text binding sync now uses O(1) NSString length pre-check before O(n) full string equality comparison
- Toolbar executing state now fires a single objectWillChange instead of double-publishing isExecuting and connectionState
- Row provider onChange handlers coalesced into a single trigger to avoid redundant InMemoryRowProvider rebuilds
- SQL import now uses file-size estimation instead of a separate counting pass, eliminating the double-parse overhead for large files
- History cleanup COUNT + DELETE now wrapped in a single transaction to reduce journal flushes
- SQLite
fetchTableMetadatanow caps row count scan at 100k rows to avoid full table scans on large tables - SQLite
fetchIndexesuses table-valued pragma functions in a single query instead of N+1 separate PRAGMA calls - MySQL empty-result DESCRIBE fallback now only triggers for SELECT queries, avoiding redundant round-trips for non-SELECT statements
- Remove redundant
String(query)copy in MariaDB query execution - MySQL result fetching now uses
mysql_use_result(streaming) instead ofmysql_store_result(full buffering), so only the capped row count is held in memory instead of the entire server result set - Instant pagination via approximate row count — MySQL/PostgreSQL tables now show "~N rows" immediately with data, then refine to exact count in background
- QueryTab uses value-based equality for SwiftUI diffing, eliminating unnecessary ForEach re-renders on tab array writes
- Cached static regex for
extractTableName,SQLiteDriver.stripLimitOffset, and SQL function expressions to avoid per-call compilation - Static NumberFormatter in status bar to avoid per-render locale resolution
- Batch
TableProTabSmartfield writes into single array store to avoid 14 CoW copies per query execution - Tab persistence writes moved off main thread via
Task.detached - Single history entry per SQL import instead of per-statement recording
- WAL mode enabled for query history SQLite database
- Merged
fetchDatabaseMetadatainto single query for MySQL and PostgreSQL - Health ping now uses dedicated metadata driver to avoid blocking user queries
- SSH tunnel setup extracted into shared helper to eliminate code duplication
- PostgreSQL DDL queries restructured with
async letfor cleaner dispatch (sequential on serial connection queue) - Cancel query connection now uses 5-second connect timeout
- PostgreSQL connection parameters properly escaped for special characters
- SQLite
fetchAllColumnsoverridden with singlesqlite_master+pragma_table_infoquery - Eliminated intermediate
[UInt8]buffer in MySQL and PostgreSQL field extraction - Column layout sync gated behind user-resize flag to skip O(n) loop on cursor moves
- Column width calculation uses monospace character arithmetic instead of per-row CoreText calls
- DataChangeManager maintains change index incrementally instead of full O(n) rebuild
- JSON export buffers writes per row instead of per field
SQLFormatterServiceuses NSMutableString for keyword uppercasing and integer counter for placeholders- SQLContextAnalyzer uses single alternation regex and single-pass state machine for string/comment detection
escapeJSONStringiterates UTF-8 bytes instead of grapheme clustersAppSettingsStoragecaches JSONDecoder/JSONEncoder as stored propertiesAppSettingsManagerstores validated settings in memory after didSetFilterSettingsStorageuses tracked key set instead of loading full plist- Keychain saves use
SecItemAdd+SecItemUpdateupsert pattern instead of delete + add - Autocomplete
detectFunctionContextuses index tracking instead of character-by-character string building
Fixed
- Fix AND/OR filter logic mode ignored in query execution — preview showed correct OR logic but actual query always used AND
- Fix filter panel state (filters, visibility, quick search, logic mode) not preserved when switching between tabs
- Fix foreign key navigation filter being wiped when switching to a new tab (tab switch restore overwrote FK filter state)
- Fix pagination count appearing 200-300ms after data loads — approximate row count from database metadata now displays instantly with data, exact count refines silently in the background
- Fix foreign key navigation arrows and pagination count appearing with visible delay on initial table load — metadata now fetches on a dedicated parallel connection concurrent with the main query
- Fix LibPQ parameterized query using Swift
deallocate()forstrdup-allocated memory instead offree() - FTS5 search input now sanitized to prevent parse errors from special characters like *, OR, AND
- Fix SQL export corrupting newline/tab/backslash characters for PostgreSQL and SQLite (MySQL-style backslash escaping was incorrectly applied to all database types)
- Fix PostgreSQL SQL export failing to import when types/sequences already exist (
DROP IF EXISTSnow always emitted for dependent types and sequences) - Fix PostgreSQL SQL export missing
CREATE TYPEdefinitions for enum columns, causing import errors - Fix PostgreSQL DDL tab not showing enum type definitions used by table columns
- Fix compilation error for PostgreSQL dependent sequences export (
fetchDependentSequencesmissing fromDatabaseDriverprotocol) - Fix PostgreSQL LIKE/NOT LIKE expressions missing
ESCAPE '\'clause, causing wildcard escaping (\%,\_) to be treated as literal characters - Fix SQLite regex filter silently degrading to LIKE substring match instead of being excluded from the WHERE clause
v0.6.4
Fixed
- Fix PostgreSQL SQL export failing to fetch DDL for tables (passed quoted identifier instead of raw table name to catalog queries)
v0.6.3
Changed
- Extract shared
performDirectTabSwitchintoMainContentCoordinatorto eliminate duplicate tab-switch logic - Welcome window now uses native macOS frosted glass translucency (NSVisualEffectView with behind-window blending)
Fixed
- Auto-detect MySQL vs MariaDB server type from version string to use correct timeout variable (
max_execution_timefor MySQL,max_statement_timefor MariaDB) - Improved tab switching performance by caching row providers and change managers across SwiftUI render cycles
- Eliminated selection sync feedback loop causing redundant DataGridView updates during tab switch
- Enabled NSTableView row view recycling to reduce heap allocations during scrolling
- Reduced SwiftUI re-render cascades by batching @published mutations during tab switch
- Improved DataGrid scrolling performance:
- Row views now recycled via NSTableView's reuse pool instead of allocating new objects per scroll
- Replaced O(n) String.count with O(1) NSString.length for large cell value truncation
- Replaced expensive NSFontDescriptor.symbolicTraits checks with O(1) pointer equality on cached fonts
- Added layerContentsRedrawPolicy and canDrawSubviewsIntoLayer to reduce compositing overhead
- Cached NULL display string locally instead of per-cell singleton access
- Cached AnyChangeManager to avoid per-render allocation with Combine subscriptions
- Deferred accessibility label generation to when VoiceOver is active
- Removed unnecessary async dispatch in focusedColumn, collapsed two reloadData calls into one
v0.6.2
- Bug fixes and improvements