Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Cassandra: connection now fails fast with a clear "Cassandra 2.x is not supported" message instead of cryptic "table not found" errors during sidebar load.
- MongoDB: dropped the `nameOnly: true` flag on `listDatabases` for servers older than 3.4, which previously rejected the flag.
- ClickHouse: index sidebar no longer fails on ClickHouse older than 19.17 by skipping the `system.data_skipping_indices` lookup when the table doesn't exist.
- MSSQL: view templates fall back to `IF EXISTS DROP / CREATE VIEW` on SQL Server 2014 and earlier, which lack `CREATE OR ALTER VIEW`.
- MySQL: added a plain `EXPLAIN` variant alongside `EXPLAIN FORMAT=JSON` so MySQL 5.5 users can run query plans.
- PostgreSQL: connecting to servers older than 9.3 no longer fails with "relation pg_matviews does not exist"; the driver feature-gates `pg_matviews`, `pg_foreign_table`, `pg_sequences`, `array_position`, `attidentity`, `attgenerated`, and ICU locale columns behind the detected server version (#1240).

## [0.40.2] - 2026-05-12
Expand Down
22 changes: 22 additions & 0 deletions Plugins/CassandraDriverPlugin/CassandraCapabilities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// CassandraCapabilities.swift
// CassandraDriverPlugin
//

import Foundation

struct CassandraCapabilities: Sendable, Equatable {
let releaseVersionMajor: Int

static let unknown = CassandraCapabilities(releaseVersionMajor: 0)

var hasSystemSchemaKeyspace: Bool { releaseVersionMajor >= 3 }

static func parseMajorVersion(_ version: String?) -> Int {
guard let version, let majorString = version.split(separator: ".").first,
let major = Int(majorString) else {
return 0
}
return major
}
}
11 changes: 10 additions & 1 deletion Plugins/CassandraDriverPlugin/CassandraPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,21 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen
stateLock.unlock()
}

// Cache server version
if let version = try? await connectionActor.serverVersion() {
stateLock.lock()
_cachedVersion = version
stateLock.unlock()
}

let caps = CassandraCapabilities(
releaseVersionMajor: CassandraCapabilities.parseMajorVersion(serverVersion)
)
guard caps.hasSystemSchemaKeyspace else {
throw CassandraPluginError.connectionFailed(String(
format: String(localized: "Cassandra %@ is not supported. TablePro requires Cassandra 3.0 or later (the system_schema keyspace was introduced in 3.0)."),
serverVersion ?? "<unknown>"
))
}
}

func disconnect() {
Expand Down
25 changes: 25 additions & 0 deletions Plugins/ClickHouseDriverPlugin/ClickHouseCapabilities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// ClickHouseCapabilities.swift
// ClickHouseDriverPlugin
//

import Foundation

struct ClickHouseCapabilities: Sendable, Equatable {
let major: Int
let minor: Int

static let unknown = ClickHouseCapabilities(major: 0, minor: 0)

var hasDataSkippingIndicesTable: Bool {
major > 19 || (major == 19 && minor >= 17)
}

static func parse(_ version: String?) -> ClickHouseCapabilities {
guard let version else { return .unknown }
let parts = version.split(separator: ".")
guard let major = parts.first.flatMap({ Int($0) }) else { return .unknown }
let minor = parts.count > 1 ? (Int(parts[1]) ?? 0) : 0
return ClickHouseCapabilities(major: major, minor: minor)
}
}
2 changes: 2 additions & 0 deletions Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
))
}

let caps = ClickHouseCapabilities.parse(serverVersion)
guard caps.hasDataSkippingIndicesTable else { return indexes }
let skippingSql = """
SELECT name, expr FROM system.data_skipping_indices
WHERE database = currentDatabase() AND table = '\(escapedTable)'
Expand Down
29 changes: 29 additions & 0 deletions Plugins/MSSQLDriverPlugin/MSSQLCapabilities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// MSSQLCapabilities.swift
// MSSQLDriverPlugin
//

import Foundation

struct MSSQLCapabilities: Sendable, Equatable {
let major: Int

static let unknown = MSSQLCapabilities(major: 0)

var hasCreateOrAlterView: Bool { major >= 13 }

static func parse(_ versionString: String?) -> MSSQLCapabilities {
guard let versionString else { return .unknown }
let pattern = #"(\d+)\.\d+\.\d+"#
guard let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(
in: versionString,
range: NSRange(versionString.startIndex..., in: versionString)
),
let range = Range(match.range(at: 1), in: versionString),
let major = Int(versionString[range]) else {
return .unknown
}
return MSSQLCapabilities(major: major)
}
}
10 changes: 8 additions & 2 deletions Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -752,12 +752,18 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE OR ALTER VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
if MSSQLCapabilities.parse(serverVersion).hasCreateOrAlterView {
return "CREATE OR ALTER VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}
return "CREATE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let quoted = quoteIdentifier(viewName)
return "CREATE OR ALTER VIEW \(quoted) AS\nSELECT * FROM table_name;"
if MSSQLCapabilities.parse(serverVersion).hasCreateOrAlterView {
return "CREATE OR ALTER VIEW \(quoted) AS\nSELECT * FROM table_name;"
}
return "IF OBJECT_ID('\(viewName)', 'V') IS NOT NULL DROP VIEW \(quoted);\nCREATE VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

func castColumnToText(_ column: String) -> String {
Expand Down
25 changes: 25 additions & 0 deletions Plugins/MongoDBDriverPlugin/MongoDBCapabilities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// MongoDBCapabilities.swift
// MongoDBDriverPlugin
//

import Foundation

struct MongoDBCapabilities: Sendable, Equatable {
let major: Int
let minor: Int

static let unknown = MongoDBCapabilities(major: 0, minor: 0)

var supportsListDatabasesNameOnly: Bool {
major > 3 || (major == 3 && minor >= 4)
}

static func parse(_ version: String?) -> MongoDBCapabilities {
guard let version else { return .unknown }
let parts = version.split(separator: ".")
guard let major = parts.first.flatMap({ Int($0) }) else { return .unknown }
let minor = parts.count > 1 ? (Int(parts[1]) ?? 0) : 0
return MongoDBCapabilities(major: major, minor: minor)
}
}
6 changes: 5 additions & 1 deletion Plugins/MongoDBDriverPlugin/MongoDBConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,11 @@ private extension MongoDBConnection {
func listDatabasesSync(client: OpaquePointer) throws -> [String] {
try checkCancelled()

guard let command = jsonToBson("{\"listDatabases\": 1, \"nameOnly\": true}") else {
let caps = MongoDBCapabilities.parse(serverVersion())
let commandJSON = caps.supportsListDatabasesNameOnly
? "{\"listDatabases\": 1, \"nameOnly\": true}"
: "{\"listDatabases\": 1}"
guard let command = jsonToBson(commandJSON) else {
throw MongoDBError(code: 0, message: "Failed to create listDatabases command")
}
defer { bson_destroy(command) }
Expand Down
3 changes: 2 additions & 1 deletion Plugins/MySQLDriverPlugin/MySQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ final class MySQLPlugin: NSObject, TableProPlugin, DriverPlugin {

static let urlSchemes: [String] = ["mysql"]
static let explainVariants: [ExplainVariant] = [
ExplainVariant(id: "explain", label: "EXPLAIN", sqlPrefix: "EXPLAIN FORMAT=JSON"),
ExplainVariant(id: "explain", label: "EXPLAIN", sqlPrefix: "EXPLAIN"),
ExplainVariant(id: "explain-json", label: "EXPLAIN (JSON)", sqlPrefix: "EXPLAIN FORMAT=JSON"),
]
static let brandColorHex = "#FF9500"
static let postConnectActions: [PostConnectAction] = [.selectDatabaseFromLastSession]
Expand Down
Loading