Skip to content

Commit df3b759

Browse files
committed
Release v0.6.1
1 parent e5e5e85 commit df3b759

6 files changed

Lines changed: 64 additions & 55 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.6.1] - 2026-02-23
11+
1012
### Fixed
1113
- Fixed all 45 performance issues identified in PERFORMANCE.md audit:
1214
- **Memory:** RowBuffer reference wrapper for QueryTab (MEM-1/2), index-based sort cache (MEM-3), streaming XLSX export with inline strings (MEM-4/15), driver-level row limits cap at 100K rows (MEM-5), removed redundant String deep copies (MEM-6), weak driver reference in SQLSchemaProvider (MEM-9), undo stack depth cap (MEM-10), dictionary-based tab pending changes (MEM-11), weak self in Task captures (MEM-12), clear cached data on disconnect (MEM-13), AI chat message cap (MEM-14)
@@ -330,7 +332,8 @@ TablePro is a native macOS database client built with SwiftUI and AppKit, design
330332
- Custom SQL query templates
331333
- Performance optimized for large datasets
332334

333-
[Unreleased]: https://github.com/datlechin/tablepro/compare/v0.6.0...HEAD
335+
[Unreleased]: https://github.com/datlechin/tablepro/compare/v0.6.1...HEAD
336+
[0.6.1]: https://github.com/datlechin/tablepro/compare/v0.6.0...v0.6.1
334337
[0.6.0]: https://github.com/datlechin/tablepro/compare/v0.5.0...v0.6.0
335338
[0.5.0]: https://github.com/datlechin/tablepro/compare/v0.4.0...v0.5.0
336339
[0.4.0]: https://github.com/datlechin/tablepro/compare/v0.3.2...v0.4.0

TablePro.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@
381381
CODE_SIGN_IDENTITY = "Apple Development";
382382
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
383383
CODE_SIGN_STYLE = Automatic;
384-
CURRENT_PROJECT_VERSION = 10;
384+
CURRENT_PROJECT_VERSION = 11;
385385
DEAD_CODE_STRIPPING = YES;
386386
DEVELOPMENT_TEAM = D7HJ5TFYCU;
387387
ENABLE_APP_SANDBOX = NO;
@@ -412,7 +412,7 @@
412412
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
413413
LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/Libs";
414414
MACOSX_DEPLOYMENT_TARGET = 14.0;
415-
MARKETING_VERSION = 0.6.0;
415+
MARKETING_VERSION = 0.6.1;
416416
"OTHER_LDFLAGS[arch=arm64]" = (
417417
"-force_load",
418418
"$(PROJECT_DIR)/Libs/libmariadb.a",
@@ -471,7 +471,7 @@
471471
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
472472
CODE_SIGN_STYLE = Automatic;
473473
COPY_PHASE_STRIP = YES;
474-
CURRENT_PROJECT_VERSION = 10;
474+
CURRENT_PROJECT_VERSION = 11;
475475
DEAD_CODE_STRIPPING = YES;
476476
DEPLOYMENT_POSTPROCESSING = YES;
477477
DEVELOPMENT_TEAM = D7HJ5TFYCU;
@@ -503,7 +503,7 @@
503503
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
504504
LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/Libs";
505505
MACOSX_DEPLOYMENT_TARGET = 14.0;
506-
MARKETING_VERSION = 0.6.0;
506+
MARKETING_VERSION = 0.6.1;
507507
"OTHER_LDFLAGS[arch=arm64]" = (
508508
"-force_load",
509509
"$(PROJECT_DIR)/Libs/libmariadb.a",

TablePro/Core/Database/SQLiteDriver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ final class SQLiteDriver: DatabaseDriver {
265265
/// Raw db handle kept outside the actor for thread-safe sqlite3_interrupt() calls.
266266
/// Protected by `interruptLock` for concurrent access between disconnect() and cancelQuery().
267267
/// sqlite3_interrupt() is documented as safe to call from any thread.
268-
private nonisolated(unsafe) var _dbHandleForInterrupt: OpaquePointer?
268+
nonisolated(unsafe) private var _dbHandleForInterrupt: OpaquePointer?
269269

270270
/// Server version string (SQLite library version, e.g., "3.43.2")
271271
var serverVersion: String? {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// MainContentCoordinator+QueryAnalysis.swift
3+
// TablePro
4+
//
5+
// Write-query and dangerous-query detection for MainContentCoordinator.
6+
//
7+
8+
import Foundation
9+
10+
extension MainContentCoordinator {
11+
// MARK: - Write Query Detection
12+
13+
/// Write-operation SQL prefixes blocked in read-only mode
14+
private static let writeQueryPrefixes: [String] = [
15+
"INSERT ", "UPDATE ", "DELETE ", "REPLACE ",
16+
"DROP ", "TRUNCATE ", "ALTER ", "CREATE ",
17+
"RENAME ", "GRANT ", "REVOKE ",
18+
]
19+
20+
/// Check if a SQL statement is a write operation (modifies data or schema)
21+
func isWriteQuery(_ sql: String) -> Bool {
22+
let uppercased = sql.uppercased().trimmingCharacters(in: .whitespacesAndNewlines)
23+
return Self.writeQueryPrefixes.contains { uppercased.hasPrefix($0) }
24+
}
25+
26+
// MARK: - Dangerous Query Detection
27+
28+
/// Pre-compiled regex for detecting WHERE clause in DELETE queries (avoids per-call compilation)
29+
private static let whereClauseRegex = try? NSRegularExpression(pattern: "\\sWHERE\\s", options: [])
30+
31+
/// Check if a query is potentially dangerous (DROP, TRUNCATE, DELETE without WHERE)
32+
func isDangerousQuery(_ sql: String) -> Bool {
33+
let uppercased = sql.uppercased().trimmingCharacters(in: .whitespacesAndNewlines)
34+
35+
// Check for DROP
36+
if uppercased.hasPrefix("DROP ") {
37+
return true
38+
}
39+
40+
// Check for TRUNCATE
41+
if uppercased.hasPrefix("TRUNCATE ") {
42+
return true
43+
}
44+
45+
// Check for DELETE without WHERE clause
46+
if uppercased.hasPrefix("DELETE ") {
47+
// Check if there's a WHERE clause (handle any whitespace: space, tab, newline)
48+
let range = NSRange(uppercased.startIndex..., in: uppercased)
49+
let hasWhere = Self.whereClauseRegex?.firstMatch(in: uppercased, options: [], range: range) != nil
50+
return !hasWhere
51+
}
52+
53+
return false
54+
}
55+
}

TablePro/Views/Main/Extensions/MainContentView+Bindings.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ extension MainContentView {
101101
metadataTableName: coordinator.tableMetadata?.tableName
102102
)
103103
}
104-
105-
106104
}
107105

108106
// MARK: - Equatable Trigger Types
@@ -122,5 +120,3 @@ struct PendingChangeTrigger: Equatable {
122120
let pendingDeletes: Set<String>
123121
let hasStructureChanges: Bool
124122
}
125-
126-

TablePro/Views/Main/MainContentCoordinator.swift

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -176,51 +176,6 @@ final class MainContentCoordinator: ObservableObject {
176176
options: .caseInsensitive
177177
)
178178

179-
// MARK: - Write Query Detection
180-
181-
/// Write-operation SQL prefixes blocked in read-only mode
182-
private static let writeQueryPrefixes: [String] = [
183-
"INSERT ", "UPDATE ", "DELETE ", "REPLACE ",
184-
"DROP ", "TRUNCATE ", "ALTER ", "CREATE ",
185-
"RENAME ", "GRANT ", "REVOKE ",
186-
]
187-
188-
/// Check if a SQL statement is a write operation (modifies data or schema)
189-
func isWriteQuery(_ sql: String) -> Bool {
190-
let uppercased = sql.uppercased().trimmingCharacters(in: .whitespacesAndNewlines)
191-
return Self.writeQueryPrefixes.contains { uppercased.hasPrefix($0) }
192-
}
193-
194-
// MARK: - Dangerous Query Detection
195-
196-
/// Pre-compiled regex for detecting WHERE clause in DELETE queries (avoids per-call compilation)
197-
private static let whereClauseRegex = try? NSRegularExpression(pattern: "\\sWHERE\\s", options: [])
198-
199-
/// Check if a query is potentially dangerous (DROP, TRUNCATE, DELETE without WHERE)
200-
func isDangerousQuery(_ sql: String) -> Bool {
201-
let uppercased = sql.uppercased().trimmingCharacters(in: .whitespacesAndNewlines)
202-
203-
// Check for DROP
204-
if uppercased.hasPrefix("DROP ") {
205-
return true
206-
}
207-
208-
// Check for TRUNCATE
209-
if uppercased.hasPrefix("TRUNCATE ") {
210-
return true
211-
}
212-
213-
// Check for DELETE without WHERE clause
214-
if uppercased.hasPrefix("DELETE ") {
215-
// Check if there's a WHERE clause (handle any whitespace: space, tab, newline)
216-
let range = NSRange(uppercased.startIndex..., in: uppercased)
217-
let hasWhere = Self.whereClauseRegex?.firstMatch(in: uppercased, options: [], range: range) != nil
218-
return !hasWhere
219-
}
220-
221-
return false
222-
}
223-
224179
// MARK: - Query Execution
225180

226181
func runQuery() {

0 commit comments

Comments
 (0)