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

## [Unreleased]

### Added

- BigQuery datasets can be switched from the toolbar, the Cmd+K switcher, and the File menu, including creating and dropping datasets. (#509)

### Changed

- Switcher, menus, and alerts now use each database's own container name: Dataset for BigQuery, Keyspace for Cassandra and ScyllaDB. (#509)

### Fixed

- iCloud Sync between the iPhone and Mac apps: the iOS app now uses the Production CloudKit environment, so a development build no longer syncs into a separate database the Mac never reads.
Expand Down
1 change: 1 addition & 0 deletions Plugins/BigQueryDriverPlugin/BigQueryPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final class BigQueryPlugin: NSObject, TableProPlugin, DriverPlugin {
static let supportsSSH = false
static let supportsSSL = false
static let tableEntityName = "Tables"
static let containerEntityName = "Dataset"
static let supportsForeignKeyDisable = false
static let supportsReadOnlyMode = true
static let databaseGroupingStrategy: GroupingStrategy = .hierarchicalSchema
Expand Down
4 changes: 2 additions & 2 deletions Plugins/CassandraDriverPlugin/CassandraPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal final class CassandraPlugin: NSObject, TableProPlugin, DriverPlugin {
static let databaseTypeId = "Cassandra"
static let databaseDisplayName = "Cassandra / ScyllaDB"
static let iconName = "cassandra-icon"
static let defaultPort = 9042
static let defaultPort = 9_042
static let additionalConnectionFields: [ConnectionField] = AWSAuthFields.standard()
static let additionalDatabaseTypeIds: [String] = ["ScyllaDB"]

Expand All @@ -36,6 +36,7 @@ internal final class CassandraPlugin: NSObject, TableProPlugin, DriverPlugin {
static let brandColorHex = "#26A0D8"
static let queryLanguageName = "CQL"
static let supportsDatabaseSwitching = true
static let containerEntityName = "Keyspace"
static let databaseGroupingStrategy: GroupingStrategy = .byDatabase
static let defaultGroupName = "default"
static let systemDatabaseNames: [String] = [
Expand Down Expand Up @@ -603,4 +604,3 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen
}
}
}

2 changes: 2 additions & 0 deletions Plugins/TableProPluginKit/DriverPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public protocol DriverPlugin: TableProPlugin {
static var sqlDialect: SQLDialectDescriptor? { get }
static var statementCompletions: [CompletionEntry] { get }
static var tableEntityName: String { get }
static var containerEntityName: String { get }
static var supportsCascadeDrop: Bool { get }
static var supportsForeignKeyDisable: Bool { get }
static var immutableColumns: [String] { get }
Expand Down Expand Up @@ -105,6 +106,7 @@ public extension DriverPlugin {
static var sqlDialect: SQLDialectDescriptor? { nil }
static var statementCompletions: [CompletionEntry] { [] }
static var tableEntityName: String { "Tables" }
static var containerEntityName: String { "Database" }
static var supportsCascadeDrop: Bool { false }
static var supportsForeignKeyDisable: Bool { true }
static var immutableColumns: [String] { [] }
Expand Down
9 changes: 9 additions & 0 deletions TablePro/Core/Plugins/ContainerSwitchTarget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// ContainerSwitchTarget.swift
// TablePro
//

enum ContainerSwitchTarget: Sendable {
case database
case schema
}
23 changes: 23 additions & 0 deletions TablePro/Core/Plugins/PluginManager+Registration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,20 @@ extension PluginManager {
.capabilities.supportsSchemaSwitching ?? false
}

func containerSwitchTarget(for databaseType: DatabaseType) -> ContainerSwitchTarget? {
if supportsDatabaseSwitching(for: databaseType) {
return .database
}
if supportsSchemaSwitching(for: databaseType) {
return .schema
}
return nil
}

func supportsContainerSwitching(for databaseType: DatabaseType) -> Bool {
containerSwitchTarget(for: databaseType) != nil
}

func supportsImport(for databaseType: DatabaseType) -> Bool {
PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)?
.capabilities.supportsImport ?? true
Expand Down Expand Up @@ -376,6 +390,15 @@ extension PluginManager {
.schema.tableEntityName ?? "Tables"
}

func containerEntityName(for databaseType: DatabaseType) -> String {
PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)?
.schema.containerEntityName ?? "Database"
}

func containerEntityNamePlural(for databaseType: DatabaseType) -> String {
containerEntityName(for: databaseType) + "s"
}

func supportsCascadeDrop(for databaseType: DatabaseType) -> Bool {
PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)?
.capabilities.supportsCascadeDrop ?? false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [],
Expand Down Expand Up @@ -182,6 +183,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "",
defaultGroupName: "default",
tableEntityName: "Tables",
containerEntityName: "Dataset",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [],
Expand Down Expand Up @@ -371,6 +373,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "PUBLIC",
defaultGroupName: "default",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["SNOWFLAKE", "SNOWFLAKE_SAMPLE_DATA"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Collections",
containerEntityName: "Database",
defaultPrimaryKeyColumn: "_id",
immutableColumns: ["_id"],
systemDatabaseNames: ["admin", "local", "config"],
Expand Down Expand Up @@ -617,6 +618,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "db0",
tableEntityName: "Keys",
containerEntityName: "Database",
defaultPrimaryKeyColumn: "Key",
immutableColumns: [],
systemDatabaseNames: [],
Expand Down Expand Up @@ -673,6 +675,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "dbo",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["master", "tempdb", "model", "msdb"],
Expand Down Expand Up @@ -726,6 +729,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [
Expand Down Expand Up @@ -787,6 +791,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["information_schema", "INFORMATION_SCHEMA", "system"],
Expand Down Expand Up @@ -837,6 +842,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["information_schema", "pg_catalog"],
Expand Down Expand Up @@ -889,6 +895,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "default",
tableEntityName: "Tables",
containerEntityName: "Keyspace",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [
Expand Down Expand Up @@ -952,6 +959,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "default",
tableEntityName: "Tables",
containerEntityName: "Keyspace",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [
Expand Down Expand Up @@ -1009,6 +1017,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Keys",
containerEntityName: "Database",
defaultPrimaryKeyColumn: "Key",
immutableColumns: ["Version", "ModRevision", "CreateRevision"],
systemDatabaseNames: [],
Expand Down Expand Up @@ -1094,6 +1103,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "main",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [],
Expand Down Expand Up @@ -1153,6 +1163,7 @@ extension PluginMetadataRegistry {
defaultSchemaName: "main",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [],
Expand Down
9 changes: 9 additions & 0 deletions TablePro/Core/Plugins/PluginMetadataRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ struct PluginMetadataSnapshot: Sendable {
let defaultSchemaName: String
let defaultGroupName: String
let tableEntityName: String
let containerEntityName: String
let defaultPrimaryKeyColumn: String?
let immutableColumns: [String]
let systemDatabaseNames: [String]
Expand All @@ -100,6 +101,7 @@ struct PluginMetadataSnapshot: Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [],
Expand Down Expand Up @@ -453,6 +455,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["information_schema", "mysql", "performance_schema", "sys"],
Expand Down Expand Up @@ -501,6 +504,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["information_schema", "mysql", "performance_schema", "sys"],
Expand Down Expand Up @@ -550,6 +554,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["postgres", "template0", "template1"],
Expand Down Expand Up @@ -598,6 +603,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["postgres", "template0", "template1"],
Expand Down Expand Up @@ -658,6 +664,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: ["postgres", "system", "defaultdb"],
Expand Down Expand Up @@ -708,6 +715,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: "public",
defaultGroupName: "main",
tableEntityName: "Tables",
containerEntityName: "Database",
defaultPrimaryKeyColumn: nil,
immutableColumns: [],
systemDatabaseNames: [],
Expand Down Expand Up @@ -895,6 +903,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
defaultSchemaName: driverType.defaultSchemaName,
defaultGroupName: driverType.defaultGroupName,
tableEntityName: driverType.tableEntityName,
containerEntityName: driverType.containerEntityName,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid reading new protocol witness from stale plugins

When loading any external/downloaded driver plugin that was compiled against the previous DriverPlugin protocol, this new requirement will not have a witness-table entry, and buildMetadataSnapshot will trap when it reads driverType.containerEntityName. The nearby comment for supportsColumnReorder documents this exact stale-plugin crash mode, so this should fall back to the existing snapshot/default unless the plugin is known to be rebuilt with the new PluginKit.

Useful? React with 👍 / 👎.

defaultPrimaryKeyColumn: driverType.defaultPrimaryKeyColumn,
immutableColumns: driverType.immutableColumns,
systemDatabaseNames: driverType.systemDatabaseNames,
Expand Down
1 change: 1 addition & 0 deletions TablePro/Core/Plugins/Registry/RegistryModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ struct RegistryPluginMetadata: Codable, Sendable {
let defaultSchemaName: String?
let defaultGroupName: String?
let tableEntityName: String?
let containerEntityName: String?
let defaultPrimaryKeyColumn: String?
let immutableColumns: [String]?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ struct DatabaseToolbarButton: View {

var body: some View {
let state = coordinator.toolbarState
let supportsSwitch = PluginManager.shared.supportsDatabaseSwitching(for: state.databaseType)
let supportsSwitch = PluginManager.shared.supportsContainerSwitching(for: state.databaseType)
let containerName = PluginManager.shared.containerEntityName(for: state.databaseType)
if supportsSwitch {
Button {
coordinator.commandActions?.openDatabaseSwitcher()
} label: {
Label("Database", systemImage: "cylinder")
Label(containerName, systemImage: "cylinder")
}
.help(String(localized: "Open Database (⌘K)"))
.help(String(format: String(localized: "Open %@ (⌘K)"), containerName))
.disabled(
state.connectionState != .connected
|| PluginManager.shared.connectionMode(for: state.databaseType) == .fileBased
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ extension MainWindowToolbar {
}

func subitemDatabase() -> NSToolbarItem {
menuOnlyItem(
let containerName = coordinator.map {
PluginManager.shared.containerEntityName(for: $0.toolbarState.databaseType)
} ?? String(localized: "Database")
return menuOnlyItem(
id: Self.database,
label: String(localized: "Database"),
label: containerName,
symbol: "cylinder",
action: #selector(performOpenDatabaseSwitcher(_:)),
keyEquivalent: "k",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension MainWindowToolbar: NSToolbarItemValidation {
let hasDataPendingChanges: Bool
let blocksAllWrites: Bool
let fileBased: Bool
let supportsDatabaseSwitching: Bool
let supportsContainerSwitching: Bool
let supportsImport: Bool
let supportsServerDashboard: Bool
}
Expand All @@ -24,7 +24,7 @@ extension MainWindowToolbar: NSToolbarItemValidation {
case Self.connection, Self.history:
return true
case Self.database:
return context.connected && !context.fileBased && context.supportsDatabaseSwitching
return context.connected && !context.fileBased && context.supportsContainerSwitching
case Self.refresh, Self.quickSwitcher, Self.newTab, Self.exportTables:
return context.connected
case Self.saveChanges:
Expand All @@ -51,7 +51,7 @@ extension MainWindowToolbar: NSToolbarItemValidation {
hasDataPendingChanges: state.hasDataPendingChanges,
blocksAllWrites: state.safeModeLevel.blocksAllWrites,
fileBased: PluginManager.shared.connectionMode(for: state.databaseType) == .fileBased,
supportsDatabaseSwitching: PluginManager.shared.supportsDatabaseSwitching(for: state.databaseType),
supportsContainerSwitching: PluginManager.shared.supportsContainerSwitching(for: state.databaseType),
supportsImport: PluginManager.shared.supportsImport(for: state.databaseType),
supportsServerDashboard: coordinator?.commandActions?.supportsServerDashboard ?? false
)
Expand Down
4 changes: 4 additions & 0 deletions TablePro/Models/Connection/ConnectionToolbarState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ final class ConnectionToolbarState {
}
return currentDatabase
case .byDatabase, .flat, .hierarchicalSchema:
if PluginManager.shared.containerSwitchTarget(for: databaseType) == .schema,
let schema = currentSchema, !schema.isEmpty {
return schema
}
return currentDatabase
}
}
Expand Down
Loading
Loading