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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- PostgreSQL ICU collation provider in Create Database (PG 15+). Provider picker is added when the server reports PG 15 or newer. ICU locale list comes from `pg_collation`. SQL emission is version-aware: PG 16+ uses unified `LOCALE`, PG 15 uses `ICU_LOCALE` with `LC_COLLATE 'C' LC_CTYPE 'C'`.
- Connection URL parsing: SSH `user:password@host` split, `safeModeLevel` from TablePlus URLs, case-insensitive query params
- Connection URL export: SSH password, Redis database index, MongoDB auth params (`authSource`, `authMechanism`, `replicaSet`), and multi-host
- SSH Private Key auth resolves keys from `~/.ssh/config` and default locations (`id_ed25519`, `id_rsa`, `id_ecdsa`) when no explicit key path is set
Expand All @@ -24,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Create Database dialog is now driver-driven. Each driver discovers its own valid options (PostgreSQL queries `pg_collation` and `pg_database`, MySQL/MariaDB query `information_schema.character_sets`/`collations`). The hardcoded macOS-flavored locale list is gone. Engines that don't support creation hide the Create button instead of failing on click.
- Introduced TableRows, Row, and Delta value types in TablePro/Models/Query/ as the foundation for the data grid row model rewrite. No callers migrated yet (Phase C.1 of the DataGrid refactor).
- DataGrid columns and cells refactored to use a persistent column pool and typed cell view hierarchy. CPU usage on table switch reduced significantly through proper NSTableView reuse pool retention.
- Data grid column identifiers are now the column name (with positional fallback for duplicate names), so saved widths follow the column across schema changes that shift its position. Identifier resolution moved from static `DataGridView` helpers to a `ColumnIdentitySchema` value type owned by the coordinator.
- `ColumnLayoutStorage` singleton replaced by a `ColumnLayoutPersisting` protocol with an injectable `FileColumnLayoutPersister` default. The coordinator depends on the protocol, not the concrete class, so tests can substitute a fake.
Expand Down Expand Up @@ -67,7 +70,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Connection form: `usePrivateKey=true` from URL no longer disables Test/Create buttons
- Transient connections from URL clean up keychain entries on connection failure
- Native Search Field focus regression when clearing text
- Using the template0 database to resolve database creation failures in PostgreSQL
- PostgreSQL Create Database failed with `new collation incompatible with template database` on glibc-initialized servers (#927). Encodings, collations, and the `template1` defaults are now read from the server. `LC_CTYPE` mirrors `LC_COLLATE`, and `TEMPLATE template0` is added automatically when the chosen collation differs from `template1.datcollate`.
- Redshift Create Database emitted PostgreSQL `LC_COLLATE` syntax which is invalid Redshift grammar. Now emits `COLLATE { CASE_SENSITIVE | CASE_INSENSITIVE }`.

## [0.36.0] - 2026-04-27

Expand Down
8 changes: 6 additions & 2 deletions Plugins/BigQueryDriverPlugin/BigQueryPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -805,9 +805,13 @@ internal final class BigQueryPluginDriver: PluginDatabaseDriver, @unchecked Send
"CREATE OR REPLACE VIEW \(quoteIdentifier(viewName)) AS\nSELECT * FROM table_name;"
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
func createDatabaseFormSpec() async throws -> PluginCreateDatabaseFormSpec? {
PluginCreateDatabaseFormSpec(fields: [], footnote: nil)
}

func createDatabase(_ request: PluginCreateDatabaseRequest) async throws {
guard let conn = connection else { throw BigQueryError.notConnected }
let escaped = name.replacingOccurrences(of: "`", with: "\\`")
let escaped = request.name.replacingOccurrences(of: "`", with: "\\`")
_ = try await conn.executeQuery("CREATE SCHEMA `\(escaped)`")
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/BigQueryDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/CSVExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
8 changes: 6 additions & 2 deletions Plugins/CassandraDriverPlugin/CassandraPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1237,8 +1237,12 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen
return databases.map { PluginDatabaseMetadata(name: $0) }
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
let safeKs = escapeIdentifier(name)
func createDatabaseFormSpec() async throws -> PluginCreateDatabaseFormSpec? {
PluginCreateDatabaseFormSpec(fields: [], footnote: nil)
}

func createDatabase(_ request: PluginCreateDatabaseRequest) async throws {
let safeKs = escapeIdentifier(request.name)
let query = """
CREATE KEYSPACE "\(safeKs)"
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}
Expand Down
2 changes: 1 addition & 1 deletion Plugins/CassandraDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
<key>NSPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).CassandraPlugin</string>
</dict>
Expand Down
8 changes: 6 additions & 2 deletions Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -579,8 +579,12 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
}
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
let escapedName = name.replacingOccurrences(of: "`", with: "``")
func createDatabaseFormSpec() async throws -> PluginCreateDatabaseFormSpec? {
PluginCreateDatabaseFormSpec(fields: [], footnote: nil)
}

func createDatabase(_ request: PluginCreateDatabaseRequest) async throws {
let escapedName = request.name.replacingOccurrences(of: "`", with: "``")
_ = try await execute(query: "CREATE DATABASE `\(escapedName)`")
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/ClickHouseDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -500,12 +500,16 @@ final class CloudflareD1PluginDriver: PluginDatabaseDriver, @unchecked Sendable
PluginDatabaseMetadata(name: database)
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
func createDatabaseFormSpec() async throws -> PluginCreateDatabaseFormSpec? {
PluginCreateDatabaseFormSpec(fields: [], footnote: nil)
}

func createDatabase(_ request: PluginCreateDatabaseRequest) async throws {
guard let client = getClient() else {
throw CloudflareD1Error.notConnected
}

let newDb = try await client.createDatabase(name: name)
let newDb = try await client.createDatabase(name: request.name)

lock.lock()
databaseNameToUuid[newDb.name] = newDb.uuid
Expand Down
2 changes: 1 addition & 1 deletion Plugins/CloudflareD1DriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
4 changes: 0 additions & 4 deletions Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1061,10 +1061,6 @@ final class DuckDBPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
PluginDatabaseMetadata(name: database)
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
throw DuckDBPluginError.unsupportedOperation
}

// MARK: - EXPLAIN

func buildExplainQuery(_ sql: String) -> String? {
Expand Down
2 changes: 1 addition & 1 deletion Plugins/DuckDBDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/DynamoDBDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/EtcdDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/JSONExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/LibSQLDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
4 changes: 0 additions & 4 deletions Plugins/LibSQLDriverPlugin/LibSQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,6 @@ final class LibSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
PluginDatabaseMetadata(name: database)
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
throw LibSQLError(message: String(localized: "Creating databases is not supported"))
}

func dropDatabase(name: String) async throws {
throw LibSQLError(message: String(localized: "Dropping databases is not supported"))
}
Expand Down
2 changes: 1 addition & 1 deletion Plugins/MQLExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/MSSQLDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
8 changes: 6 additions & 2 deletions Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1359,8 +1359,12 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
return PluginDatabaseMetadata(name: database)
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
let quotedName = "[\(name.replacingOccurrences(of: "]", with: "]]"))]"
func createDatabaseFormSpec() async throws -> PluginCreateDatabaseFormSpec? {
PluginCreateDatabaseFormSpec(fields: [], footnote: nil)
}

func createDatabase(_ request: PluginCreateDatabaseRequest) async throws {
let quotedName = "[\(request.name.replacingOccurrences(of: "]", with: "]]"))]"
_ = try await execute(query: "CREATE DATABASE \(quotedName)")
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/MongoDBDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
10 changes: 7 additions & 3 deletions Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,17 @@ final class MongoDBPluginDriver: PluginDatabaseDriver {
)
}

func createDatabase(name: String, charset: String, collation: String?) async throws {
func createDatabaseFormSpec() async throws -> PluginCreateDatabaseFormSpec? {
PluginCreateDatabaseFormSpec(fields: [], footnote: nil)
}

func createDatabase(_ request: PluginCreateDatabaseRequest) async throws {
guard let conn = mongoConnection else {
throw MongoDBPluginError.notConnected
}

_ = try await conn.insertOne(database: name, collection: "__tablepro_init", document: "{\"_init\": true}")
_ = try await conn.runCommand("{\"drop\": \"__tablepro_init\"}", database: name)
_ = try await conn.insertOne(database: request.name, collection: "__tablepro_init", document: "{\"_init\": true}")
_ = try await conn.runCommand("{\"drop\": \"__tablepro_init\"}", database: request.name)
}

func dropDatabase(name: String) async throws {
Expand Down
2 changes: 1 addition & 1 deletion Plugins/MySQLDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>7</integer>
<integer>8</integer>
</dict>
</plist>
Loading
Loading