Skip to content

Commit 1ab7f96

Browse files
authored
Merge branch 'main' into feat/plugin-redis-sentinel-1021
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
2 parents 08b678b + 6b64534 commit 1ab7f96

143 files changed

Lines changed: 5046 additions & 1637 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Redis: Sentinel connection mode. Pick "Sentinel" in the connection form, list the Sentinel nodes, and set the master name; TablePro resolves the current master through the Sentinel quorum and re-resolves automatically on failover (#1021)
13+
- File > Backup Dump… and Restore Dump… for PostgreSQL and Redshift connections, running `pg_dump -Fc` and `pg_restore --no-owner --no-acl` with live progress, cancel, SSH tunnel reuse, and custom binary paths under Settings > Terminal > CLI Paths (#1211).
1314

1415
### Changed
1516

16-
- Quick Switcher rewritten as a native SwiftUI sheet matching the Database Switcher style. Adds a Recent section per connection.
17-
- Double-click handling in sidebar, database switcher, and connection type chooser now uses `contextMenu(forSelectionType:primaryAction:)` instead of a custom NSEvent monitor.
18-
- Welcome screen drops the "Import Connections..." button; both import flows remain in the `+` menu.
17+
- PluginKit ABI bumped to v12: `DriverConnectionConfig` now carries a typed `SSLConfiguration` instead of stringified `sslMode` / `sslCaCertPath` keys in `additionalFields`. Every bundled plugin migrated; registry-only plugins (MongoDB, Oracle, DuckDB, MSSQL, Cassandra, Etcd, CloudflareD1, DynamoDB, BigQuery, LibSQL) must be re-tagged and republished before the next app release.
1918

2019
### Fixed
2120

22-
- Quick Switcher crash on macOS 26 caused by an NSPanel + NSHostingController constraint loop.
23-
- Registry updates for built-in drivers (ClickHouse, Redis) now stick after restart. Plugin builds previously kept the default `1.0` version, tying with the bundled copy so the user-installed update was pruned on load.
21+
- Redis: "Required (skip verify)" SSL mode now actually skips certificate verification, matching `redis-cli --tls --insecure`. Previously every mode performed verification because of a phantom dictionary key the app never wrote. Connections to Upstash Redis and similar endpoints with untrusted CAs work (#1247).
22+
- MSSQL: SSL mode finally affects the connection. `Disabled` / `Preferred` / `Required` / `Verify CA` / `Verify Identity` map to FreeTDS `off` / `request` / `require` / `require` / `require` via `DBSETENCRYPT`. Previously the setting was read and silently ignored.
23+
- MongoDB: "Required" and "Verify CA" pass the right libmongoc flags (`tlsAllowInvalidCertificates`, `tlsAllowInvalidHostnames`) so connections to self-signed or untrusted-CA servers stop failing on those paths.
24+
- MySQL: CA certificate is no longer loaded when the user picked a mode that skips verification, matching PostgreSQL.
25+
26+
## [0.40.3] - 2026-05-13
27+
28+
### Fixed
29+
30+
- AI Chat: scrolling stays smooth on long conversations and stream completion no longer briefly hides the chat. (#1239)
31+
- AI Chat: starting a new conversation no longer carries context from the previous one.
32+
- PostgreSQL: connecting to servers older than 9.3 no longer fails on schema load. (#1240)
33+
- MySQL: EXPLAIN now offers a plain variant that works on every version.
34+
- MSSQL: editing a view on SQL Server 2014 or earlier no longer fails with a syntax error.
35+
- Cassandra: connecting to a 2.x server now shows a clear unsupported-version message instead of failing on sidebar load.
36+
- MongoDB: connecting to servers older than 3.4 no longer fails on the database listing.
37+
- ClickHouse: the index sidebar no longer fails on versions older than 19.17.
38+
39+
## [0.40.2] - 2026-05-12
40+
41+
### Added
42+
43+
- Right-click Set Value on date, datetime, and timestamp cells now offers `CURRENT_DATE`, `CURRENT_TIME`, `NOW()`, and `CURRENT_TIMESTAMP`, filtered by column type.
44+
- Welcome screen left pane gains an Import from Other App button.
45+
46+
### Changed
47+
48+
- Row numbers in the data grid continue across pages and the `#` column auto-sizes to fit the widest visible number.
49+
- Date, datetime, and timestamp cells use the standard inline text editor; the popover date picker is removed.
50+
- Foreign key preview popover follows the selected row as you arrow up or down.
51+
- The connection window shows the connecting state inline with a Cancel button.
52+
53+
### Fixed
54+
55+
- Closing the connection window during a slow connect no longer leaves a stuck "Connecting…" window or a stray failure alert (#1185).
56+
- Cmd+Z while editing a cell now undoes typing in the editor; pressing it after dismissing the editor no longer crashes.
57+
- Cmd+Z right after Add Row no longer leaves a stranded editor floating over the removed row.
58+
- Editing a NULL cell and dismissing without typing no longer flips the value to an empty string.
59+
- Double-clicking another cell while editing no longer delays the new editor or drops pending changes on the previous one.
60+
- Double-clicking an enum, set, or boolean cell now opens the inline text editor; the chevron still opens the picker popover.
61+
- Chevron-accessory cells (enum, boolean, JSON, blob) no longer truncate short values that fit the full cell width.
62+
- DATE columns no longer render a phantom `00:00:00` time suffix.
63+
- Adding a new row no longer renders the row on top of the auto-opened cell editor mid-animation.
64+
65+
## [0.40.1] - 2026-05-12
66+
67+
### Changed
68+
69+
- Quick Switcher matches the Open Database dialog and shows a Recent section per connection.
70+
- Connection Switcher and SQL Preview open as sheets so they work from the toolbar, overflow menu, and keyboard shortcuts.
71+
- Filters button moved out of the toolbar; the bottom-bar Filters control remains.
72+
- Welcome screen drops the Import Connections button; both import flows remain in the + menu.
73+
74+
### Fixed
75+
76+
- Toolbar overflow menu entries now fire their action when the window is narrowed.
77+
- SQL Preview no longer freezes when previewing a very large batch.
78+
- Quick Switcher crash on macOS 26.
79+
- Registry updates for bundled drivers (ClickHouse, Redis) now persist after restart.
2480

2581
## [0.40.0] - 2026-05-12
2682

@@ -1755,7 +1811,10 @@ TablePro is a native macOS database client built with SwiftUI and AppKit, design
17551811
- Custom SQL query templates
17561812
- Performance optimized for large datasets
17571813

1758-
[Unreleased]: https://github.com/TableProApp/TablePro/compare/v0.40.0...HEAD
1814+
[Unreleased]: https://github.com/TableProApp/TablePro/compare/v0.40.3...HEAD
1815+
[0.40.3]: https://github.com/TableProApp/TablePro/compare/v0.40.2...v0.40.3
1816+
[0.40.2]: https://github.com/TableProApp/TablePro/compare/v0.40.1...v0.40.2
1817+
[0.40.1]: https://github.com/TableProApp/TablePro/compare/v0.40.0...v0.40.1
17591818
[0.40.0]: https://github.com/TableProApp/TablePro/compare/v0.39.1...v0.40.0
17601819
[0.39.1]: https://github.com/TableProApp/TablePro/compare/v0.39.0...v0.39.1
17611820
[0.39.0]: https://github.com/TableProApp/TablePro/compare/v0.38.0...v0.39.0

Plugins/BigQueryDriverPlugin/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>11</integer>
6+
<integer>12</integer>
77
</dict>
88
</plist>

Plugins/CSVExportPlugin/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>11</integer>
6+
<integer>12</integer>
77
<key>TableProProvidesExportFormatIds</key>
88
<array>
99
<string>csv</string>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// CassandraCapabilities.swift
3+
// CassandraDriverPlugin
4+
//
5+
6+
import Foundation
7+
8+
struct CassandraCapabilities: Sendable, Equatable {
9+
let releaseVersionMajor: Int
10+
11+
static let unknown = CassandraCapabilities(releaseVersionMajor: 0)
12+
13+
var hasSystemSchemaKeyspace: Bool { releaseVersionMajor >= 3 }
14+
15+
static func parseMajorVersion(_ version: String?) -> Int {
16+
guard let version, let majorString = version.split(separator: ".").first,
17+
let major = Int(majorString) else {
18+
return 0
19+
}
20+
return major
21+
}
22+
}

Plugins/CassandraDriverPlugin/CassandraPlugin.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,12 +921,21 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen
921921
stateLock.unlock()
922922
}
923923

924-
// Cache server version
925924
if let version = try? await connectionActor.serverVersion() {
926925
stateLock.lock()
927926
_cachedVersion = version
928927
stateLock.unlock()
929928
}
929+
930+
let caps = CassandraCapabilities(
931+
releaseVersionMajor: CassandraCapabilities.parseMajorVersion(serverVersion)
932+
)
933+
guard caps.hasSystemSchemaKeyspace else {
934+
throw CassandraPluginError.connectionFailed(String(
935+
format: String(localized: "Cassandra %@ is not supported. TablePro requires Cassandra 3.0 or later (the system_schema keyspace was introduced in 3.0)."),
936+
serverVersion ?? "<unknown>"
937+
))
938+
}
930939
}
931940

932941
func disconnect() {

Plugins/CassandraDriverPlugin/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
<key>NSPrincipalClass</key>
2222
<string>$(PRODUCT_MODULE_NAME).CassandraPlugin</string>
2323
<key>TableProPluginKitVersion</key>
24-
<integer>11</integer>
24+
<integer>12</integer>
2525
</dict>
2626
</plist>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// ClickHouseCapabilities.swift
3+
// ClickHouseDriverPlugin
4+
//
5+
6+
import Foundation
7+
8+
struct ClickHouseCapabilities: Sendable, Equatable {
9+
let major: Int
10+
let minor: Int
11+
12+
static let unknown = ClickHouseCapabilities(major: 0, minor: 0)
13+
14+
var hasDataSkippingIndicesTable: Bool {
15+
major > 19 || (major == 19 && minor >= 17)
16+
}
17+
18+
static func parse(_ version: String?) -> ClickHouseCapabilities {
19+
guard let version else { return .unknown }
20+
let parts = version.split(separator: ".")
21+
guard let major = parts.first.flatMap({ Int($0) }) else { return .unknown }
22+
let minor = parts.count > 1 ? (Int(parts[1]) ?? 0) : 0
23+
return ClickHouseCapabilities(major: major, minor: minor)
24+
}
25+
}

Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,13 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
195195
// MARK: - Connection
196196

197197
func connect() async throws {
198-
let useTLS = config.additionalFields["sslMode"] != nil
199-
&& config.additionalFields["sslMode"] != "Disabled"
200-
let skipVerification = config.additionalFields["sslMode"] == "Required"
201-
202198
let urlConfig = URLSessionConfiguration.default
203199
urlConfig.timeoutIntervalForRequest = 30
204200
urlConfig.timeoutIntervalForResource = 300
205201

206202
lock.lock()
207-
if skipVerification {
208-
session = URLSession(configuration: urlConfig, delegate: InsecureTLSDelegate(), delegateQueue: nil)
203+
if let delegate = ClickHouseTLSDelegate.make(for: config.ssl) {
204+
session = URLSession(configuration: urlConfig, delegate: delegate, delegateQueue: nil)
209205
} else {
210206
session = URLSession(configuration: urlConfig)
211207
}
@@ -435,6 +431,8 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
435431
))
436432
}
437433

434+
let caps = ClickHouseCapabilities.parse(serverVersion)
435+
guard caps.hasDataSkippingIndicesTable else { return indexes }
438436
let skippingSql = """
439437
SELECT name, expr FROM system.data_skipping_indices
440438
WHERE database = currentDatabase() AND table = '\(escapedTable)'
@@ -909,8 +907,7 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
909907
}
910908

911909
private func buildRequest(query: String, database: String, queryId: String? = nil, params: [String: String?]? = nil) throws -> URLRequest {
912-
let useTLS = config.additionalFields["sslMode"] != nil
913-
&& config.additionalFields["sslMode"] != "Disabled"
910+
let useTLS = config.ssl.isEnabled
914911

915912
var components = URLComponents()
916913
components.scheme = useTLS ? "https" : "http"
@@ -1201,8 +1198,7 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
12011198
}
12021199

12031200
private func buildStreamRequest(query: String, database: String) throws -> URLRequest {
1204-
let useTLS = config.additionalFields["sslMode"] != nil
1205-
&& config.additionalFields["sslMode"] != "Disabled"
1201+
let useTLS = config.ssl.isEnabled
12061202

12071203
var components = URLComponents()
12081204
components.scheme = useTLS ? "https" : "http"
@@ -1321,19 +1317,66 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
13211317
"ALTER TABLE \(quoteIdentifier(table)) DROP INDEX \(quoteIdentifier(indexName))"
13221318
}
13231319

1324-
// MARK: - TLS Delegate
1320+
}
1321+
1322+
// MARK: - TLS Delegate
1323+
1324+
private final class ClickHouseTLSDelegate: NSObject, URLSessionDelegate, @unchecked Sendable {
1325+
private enum Strategy {
1326+
case skipVerify
1327+
case verifyChain(anchor: SecCertificate?)
1328+
}
1329+
1330+
private let strategy: Strategy
1331+
1332+
private init(strategy: Strategy) {
1333+
self.strategy = strategy
1334+
}
1335+
1336+
/// Returns nil when the default URLSession trust evaluation is correct
1337+
/// (`.disabled` and `.verifyIdentity`).
1338+
static func make(for ssl: SSLConfiguration) -> ClickHouseTLSDelegate? {
1339+
switch ssl.mode {
1340+
case .disabled, .verifyIdentity:
1341+
return nil
1342+
case .preferred, .required:
1343+
return ClickHouseTLSDelegate(strategy: .skipVerify)
1344+
case .verifyCa:
1345+
return ClickHouseTLSDelegate(strategy: .verifyChain(anchor: loadAnchor(at: ssl.caCertificatePath)))
1346+
}
1347+
}
1348+
1349+
private static func loadAnchor(at path: String) -> SecCertificate? {
1350+
guard !path.isEmpty, let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
1351+
return nil
1352+
}
1353+
return SecCertificateCreateWithData(nil, data as CFData)
1354+
}
13251355

1326-
private class InsecureTLSDelegate: NSObject, URLSessionDelegate {
1327-
func urlSession(
1328-
_ session: URLSession,
1329-
didReceive challenge: URLAuthenticationChallenge,
1330-
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
1331-
) {
1332-
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
1333-
let serverTrust = challenge.protectionSpace.serverTrust {
1356+
func urlSession(
1357+
_ session: URLSession,
1358+
didReceive challenge: URLAuthenticationChallenge,
1359+
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
1360+
) {
1361+
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
1362+
let serverTrust = challenge.protectionSpace.serverTrust else {
1363+
completionHandler(.performDefaultHandling, nil)
1364+
return
1365+
}
1366+
1367+
switch strategy {
1368+
case .skipVerify:
1369+
completionHandler(.useCredential, URLCredential(trust: serverTrust))
1370+
case .verifyChain(let anchor):
1371+
if let anchor {
1372+
SecTrustSetAnchorCertificates(serverTrust, [anchor] as CFArray)
1373+
}
1374+
let hostnameAgnostic = SecPolicyCreateSSL(true, nil)
1375+
SecTrustSetPolicies(serverTrust, [hostnameAgnostic] as CFArray)
1376+
if SecTrustEvaluateWithError(serverTrust, nil) {
13341377
completionHandler(.useCredential, URLCredential(trust: serverTrust))
13351378
} else {
1336-
completionHandler(.performDefaultHandling, nil)
1379+
completionHandler(.cancelAuthenticationChallenge, nil)
13371380
}
13381381
}
13391382
}

Plugins/ClickHouseDriverPlugin/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>11</integer>
6+
<integer>12</integer>
77
<key>TableProProvidesDatabaseTypeIds</key>
88
<array>
99
<string>ClickHouse</string>

Plugins/CloudflareD1DriverPlugin/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>11</integer>
6+
<integer>12</integer>
77
</dict>
88
</plist>

0 commit comments

Comments
 (0)