Skip to content

Commit 0085ec7

Browse files
authored
Merge pull request #258 from datlechin/feat/plugin-standardization-phase1
feat: standardize plugin system patterns
2 parents 37b5cd8 + 6c43c3e commit 0085ec7

File tree

20 files changed

+511
-673
lines changed

20 files changed

+511
-673
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
- Result truncation at 100K rows now reported to UI via `PluginQueryResult.isTruncated` instead of being silently discarded
1213
- DELETE and UPDATE queries using all columns in WHERE clause instead of just the primary key for PostgreSQL, Redshift, MSSQL, and ClickHouse
1314
- SSL/TLS always being enabled for MongoDB, Redis, and ClickHouse connections due to case mismatch in SSL mode string comparison (#249)
1415
- Redis sidebar click showing data briefly then going empty due to double-navigation race condition (#251)
1516
- MongoDB showing "Invalid database name: ''" when connecting without a database name
1617

1718
### Changed
1819

20+
- Unified error formatting across all database drivers via default `PluginDriverError.errorDescription`, removing 10 per-driver implementations
21+
- Standardized async bridging: 5 queue-based drivers (MySQL, PostgreSQL, MongoDB, Redis, MSSQL) now use shared `pluginDispatchAsync` helper
22+
- Added localization to remaining driver error messages (MySQL, PostgreSQL, ClickHouse, Oracle, Redis, MongoDB)
1923
- NoSQL query building moved from Core to MongoDB/Redis plugins via optional `PluginDatabaseDriver` protocol methods
2024
- Standardized parameter binding across all database drivers with improved default escaping (type-aware numeric handling, NUL byte stripping, NULL literal support)
2125

Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@ final class ClickHousePlugin: NSObject, TableProPlugin, DriverPlugin {
2828
private struct ClickHouseError: Error, PluginDriverError {
2929
let message: String
3030

31-
var errorDescription: String? { "ClickHouse Error: \(message)" }
3231
var pluginErrorMessage: String { message }
3332

34-
static let notConnected = ClickHouseError(message: "Not connected to database")
35-
static let connectionFailed = ClickHouseError(message: "Failed to establish connection")
33+
static let notConnected = ClickHouseError(message: String(localized: "Not connected to database"))
34+
static let connectionFailed = ClickHouseError(message: String(localized: "Failed to establish connection"))
3635
}
3736

3837
// MARK: - Internal Query Result
@@ -42,6 +41,7 @@ private struct CHQueryResult {
4241
let columnTypeNames: [String]
4342
let rows: [[String?]]
4443
let affectedRows: Int
44+
let isTruncated: Bool
4545
}
4646

4747
// MARK: - Plugin Driver
@@ -139,7 +139,8 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
139139
columnTypeNames: result.columnTypeNames,
140140
rows: result.rows,
141141
rowsAffected: result.affectedRows,
142-
executionTime: executionTime
142+
executionTime: executionTime,
143+
isTruncated: result.isTruncated
143144
)
144145
}
145146

@@ -159,7 +160,8 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
159160
columnTypeNames: result.columnTypeNames,
160161
rows: result.rows,
161162
rowsAffected: result.affectedRows,
162-
executionTime: executionTime
163+
executionTime: executionTime,
164+
isTruncated: result.isTruncated
163165
)
164166
}
165167

@@ -584,7 +586,7 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
584586
return parseTabSeparatedResponse(data)
585587
}
586588

587-
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0)
589+
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0, isTruncated: false)
588590
}
589591

590592
private func executeRawWithParams(_ query: String, params: [String: String?], queryId: String? = nil) async throws -> CHQueryResult {
@@ -641,7 +643,7 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
641643
return parseTabSeparatedResponse(data)
642644
}
643645

644-
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0)
646+
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0, isTruncated: false)
645647
}
646648

647649
private func buildRequest(query: String, database: String, queryId: String? = nil, params: [String: String?]? = nil) throws -> URLRequest {
@@ -705,19 +707,20 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
705707

706708
private func parseTabSeparatedResponse(_ data: Data) -> CHQueryResult {
707709
guard let text = String(data: data, encoding: .utf8), !text.isEmpty else {
708-
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0)
710+
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0, isTruncated: false)
709711
}
710712

711713
let lines = text.components(separatedBy: "\n")
712714

713715
guard lines.count >= 2 else {
714-
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0)
716+
return CHQueryResult(columns: [], columnTypeNames: [], rows: [], affectedRows: 0, isTruncated: false)
715717
}
716718

717719
let columns = lines[0].components(separatedBy: "\t")
718720
let columnTypes = lines[1].components(separatedBy: "\t")
719721

720722
var rows: [[String?]] = []
723+
var truncated = false
721724
for i in 2..<lines.count {
722725
let line = lines[i]
723726
if line.isEmpty { continue }
@@ -731,6 +734,7 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
731734
}
732735
rows.append(row)
733736
if rows.count >= PluginRowLimits.defaultMax {
737+
truncated = true
734738
break
735739
}
736740
}
@@ -739,7 +743,8 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
739743
columns: columns,
740744
columnTypeNames: columnTypes,
741745
rows: rows,
742-
affectedRows: rows.count
746+
affectedRows: rows.count,
747+
isTruncated: truncated
743748
)
744749
}
745750

Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ private struct FreeTDSQueryResult {
7979
let columnTypeNames: [String]
8080
let rows: [[String?]]
8181
let affectedRows: Int
82+
let isTruncated: Bool
8283
}
8384

8485
private final class FreeTDSConnection: @unchecked Sendable {
@@ -109,15 +110,8 @@ private final class FreeTDSConnection: @unchecked Sendable {
109110
}
110111

111112
func connect() async throws {
112-
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
113-
queue.async { [self] in
114-
do {
115-
try self.connectSync()
116-
continuation.resume()
117-
} catch {
118-
continuation.resume(throwing: error)
119-
}
120-
}
113+
try await pluginDispatchAsync(on: queue) { [self] in
114+
try self.connectSync()
121115
}
122116
}
123117

@@ -153,17 +147,12 @@ private final class FreeTDSConnection: @unchecked Sendable {
153147
}
154148

155149
func switchDatabase(_ database: String) async throws {
156-
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
157-
queue.async { [self] in
158-
guard let proc = self.dbproc else {
159-
continuation.resume(throwing: MSSQLPluginError.notConnected)
160-
return
161-
}
162-
if dbuse(proc, database) == FAIL {
163-
continuation.resume(throwing: MSSQLPluginError.queryFailed("Cannot switch to database '\(database)'"))
164-
} else {
165-
continuation.resume()
166-
}
150+
try await pluginDispatchAsync(on: queue) { [self] in
151+
guard let proc = self.dbproc else {
152+
throw MSSQLPluginError.notConnected
153+
}
154+
if dbuse(proc, database) == FAIL {
155+
throw MSSQLPluginError.queryFailed("Cannot switch to database '\(database)'")
167156
}
168157
}
169158
}
@@ -185,15 +174,8 @@ private final class FreeTDSConnection: @unchecked Sendable {
185174

186175
func executeQuery(_ query: String) async throws -> FreeTDSQueryResult {
187176
let queryToRun = String(query)
188-
return try await withCheckedThrowingContinuation { [self] (cont: CheckedContinuation<FreeTDSQueryResult, Error>) in
189-
queue.async { [self] in
190-
do {
191-
let result = try self.executeQuerySync(queryToRun)
192-
cont.resume(returning: result)
193-
} catch {
194-
cont.resume(throwing: error)
195-
}
196-
}
177+
return try await pluginDispatchAsync(on: queue) { [self] in
178+
try self.executeQuerySync(queryToRun)
197179
}
198180
}
199181

@@ -217,6 +199,7 @@ private final class FreeTDSConnection: @unchecked Sendable {
217199
var allTypeNames: [String] = []
218200
var allRows: [[String?]] = []
219201
var firstResultSet = true
202+
var truncated = false
220203

221204
while true {
222205
let resCode = dbresults(proc)
@@ -264,6 +247,7 @@ private final class FreeTDSConnection: @unchecked Sendable {
264247
}
265248
allRows.append(row)
266249
if allRows.count >= PluginRowLimits.defaultMax {
250+
truncated = true
267251
break
268252
}
269253
}
@@ -274,7 +258,8 @@ private final class FreeTDSConnection: @unchecked Sendable {
274258
columns: allColumns,
275259
columnTypeNames: allTypeNames,
276260
rows: allRows,
277-
affectedRows: affectedRows
261+
affectedRows: affectedRows,
262+
isTruncated: truncated
278263
)
279264
}
280265

@@ -396,7 +381,8 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
396381
columnTypeNames: result.columnTypeNames,
397382
rows: result.rows,
398383
rowsAffected: result.affectedRows,
399-
executionTime: Date().timeIntervalSince(startTime)
384+
executionTime: Date().timeIntervalSince(startTime),
385+
isTruncated: result.isTruncated
400386
)
401387
}
402388

@@ -1043,18 +1029,10 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
10431029

10441030
// MARK: - Errors
10451031

1046-
enum MSSQLPluginError: LocalizedError {
1032+
enum MSSQLPluginError: Error {
10471033
case connectionFailed(String)
10481034
case notConnected
10491035
case queryFailed(String)
1050-
1051-
var errorDescription: String? {
1052-
switch self {
1053-
case .connectionFailed(let message): return "SQL Server Error: \(message)"
1054-
case .notConnected: return "SQL Server Error: Not connected to database"
1055-
case .queryFailed(let message): return "SQL Server Error: \(message)"
1056-
}
1057-
}
10581036
}
10591037

10601038
extension MSSQLPluginError: PluginDriverError {

0 commit comments

Comments
 (0)