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

- Execute All Statements shortcut (Cmd+Shift+Enter) to run all statements in the editor (#770)
- Drop database from the database switcher (context menu, toolbar button, Delete key)
- Structure tab: search, sort, count badges, PK column, Copy As (CSV/JSON/SQL), destructive change confirmation
- Structure tab: DDL view with tree-sitter highlighting, line numbers, and "Open in Editor"
- Structure tab: charset/collation (MySQL), index prefix length, partial indexes (PostgreSQL), cross-schema FK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable {
func fetchDependentTypes(table: String, schema: String?) async throws -> [(name: String, labels: [String])]
func fetchDependentSequences(table: String, schema: String?) async throws -> [(name: String, ddl: String)]
func createDatabase(name: String, charset: String, collation: String?) async throws
func dropDatabase(name: String) async throws
func executeParameterized(query: String, parameters: [String?]) async throws -> PluginQueryResult

// Query building (optional, for NoSQL plugins)
Expand Down Expand Up @@ -268,6 +269,11 @@ public extension PluginDatabaseDriver {
)
}

func dropDatabase(name: String) async throws {
throw NSError(domain: "PluginDatabaseDriver", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Drop database is not supported by this driver"])
}

func switchDatabase(to database: String) async throws {
throw NSError(
domain: "TableProPluginKit",
Expand Down
2 changes: 2 additions & 0 deletions Plugins/BigQueryDriverPlugin/BigQueryPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ final class BigQueryPlugin: NSObject, TableProPlugin, DriverPlugin {
]
}

static let supportsDropDatabase = true

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
BigQueryPluginDriver(config: config)
}
Expand Down
6 changes: 6 additions & 0 deletions Plugins/BigQueryDriverPlugin/BigQueryPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,12 @@ internal final class BigQueryPluginDriver: PluginDatabaseDriver, @unchecked Send
_ = try await conn.executeQuery("CREATE SCHEMA `\(escaped)`")
}

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

func generateAddColumnSQL(table: String, column: PluginColumnDefinition) -> String? {
guard let conn = connection else { return nil }
let dataset = lock.withLock { _currentDataset } ?? ""
Expand Down
7 changes: 7 additions & 0 deletions Plugins/CassandraDriverPlugin/CassandraPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ internal final class CassandraPlugin: NSObject, TableProPlugin, DriverPlugin {
)
}

static let supportsDropDatabase = true

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
CassandraPluginDriver(config: config)
}
Expand Down Expand Up @@ -1244,6 +1246,11 @@ internal final class CassandraPluginDriver: PluginDatabaseDriver, @unchecked Sen
_ = try await execute(query: query)
}

func dropDatabase(name: String) async throws {
let safeKs = escapeIdentifier(name)
_ = try await execute(query: "DROP KEYSPACE \"\(safeKs)\"")
}

func switchDatabase(to database: String) async throws {
try await connectionActor.switchKeyspace(database)
stateLock.lock()
Expand Down
6 changes: 6 additions & 0 deletions Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ final class ClickHousePlugin: NSObject, TableProPlugin, DriverPlugin {

static let structureColumnFields: [StructureColumnField] = [.name, .type, .nullable, .defaultValue, .comment]
static let supportsQueryProgress = true
static let supportsDropDatabase = true

static let sqlDialect: SQLDialectDescriptor? = SQLDialectDescriptor(
identifierQuote: "`",
Expand Down Expand Up @@ -583,6 +584,11 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
_ = try await execute(query: "CREATE DATABASE `\(escapedName)`")
}

func dropDatabase(name: String) async throws {
let escapedName = name.replacingOccurrences(of: "`", with: "``")
_ = try await execute(query: "DROP DATABASE `\(escapedName)`")
}

// MARK: - All Tables Metadata

func allTablesMetadataSQL(schema: String?) -> String? {
Expand Down
2 changes: 2 additions & 0 deletions Plugins/CloudflareD1DriverPlugin/CloudflareD1Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ final class CloudflareD1Plugin: NSObject, TableProPlugin, DriverPlugin {
)
]

static let supportsDropDatabase = true

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
CloudflareD1PluginDriver(config: config)
}
Expand Down
20 changes: 20 additions & 0 deletions Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,26 @@ final class CloudflareD1PluginDriver: PluginDatabaseDriver, @unchecked Sendable
lock.unlock()
}

func dropDatabase(name: String) async throws {
guard let client = getClient() else {
throw CloudflareD1Error.notConnected
}

lock.lock()
let uuid = databaseNameToUuid[name]
lock.unlock()

guard let databaseId = uuid ?? (isUuid(name) ? name : nil) else {
throw CloudflareD1Error(message: String(format: String(localized: "Database '%@' not found"), name))
}

try await client.deleteDatabase(databaseId: databaseId)

lock.lock()
databaseNameToUuid.removeValue(forKey: name)
lock.unlock()
}

func switchDatabase(to database: String) async throws {
lock.lock()
var uuid = databaseNameToUuid[database]
Expand Down
8 changes: 8 additions & 0 deletions Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ final class D1HttpClient: @unchecked Sendable {
return result
}

func deleteDatabase(databaseId: String) async throws {
let url = try baseURL(databaseId: databaseId)
let data = try await performRequest(url: url, method: "DELETE", body: nil)

let envelope = try JSONDecoder().decode(D1ApiResponse<D1DatabaseInfo>.self, from: data)
try checkApiSuccess(envelope)
}

// MARK: - Private Helpers

private func baseURL(databaseId: String?) throws -> URL {
Expand Down
7 changes: 7 additions & 0 deletions Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ final class MSSQLPlugin: NSObject, TableProPlugin, DriverPlugin {
autoLimitStyle: .top
)

static let supportsDropDatabase = true

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
MSSQLPluginDriver(config: config)
}
Expand Down Expand Up @@ -1362,6 +1364,11 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
_ = try await execute(query: "CREATE DATABASE \(quotedName)")
}

func dropDatabase(name: String) async throws {
let quotedName = "[\(name.replacingOccurrences(of: "]", with: "]]"))]"
_ = try await execute(query: "DROP DATABASE \(quotedName)")
}

// MARK: - All Tables Metadata

func allTablesMetadataSQL(schema: String?) -> String? {
Expand Down
2 changes: 2 additions & 0 deletions Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ final class MongoDBPlugin: NSObject, TableProPlugin, DriverPlugin {
]
}

static let supportsDropDatabase = true

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
MongoDBPluginDriver(config: config)
}
Expand Down
8 changes: 8 additions & 0 deletions Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,14 @@ final class MongoDBPluginDriver: PluginDatabaseDriver {
_ = try await conn.runCommand("{\"drop\": \"__tablepro_init\"}", database: name)
}

func dropDatabase(name: String) async throws {
guard let conn = mongoConnection else {
throw MongoDBPluginError.notConnected
}

_ = try await conn.runCommand("{\"dropDatabase\": 1}", database: name)
}

// MARK: - Database Switching

func switchDatabase(to database: String) async throws {
Expand Down
2 changes: 2 additions & 0 deletions Plugins/MySQLDriverPlugin/MySQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ final class MySQLPlugin: NSObject, TableProPlugin, DriverPlugin {
requiresBackslashEscaping: true
)

static let supportsDropDatabase = true

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
MySQLPluginDriver(config: config)
}
Expand Down
5 changes: 5 additions & 0 deletions Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,11 @@ final class MySQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
_ = try await execute(query: query)
}

func dropDatabase(name: String) async throws {
let escapedName = name.replacingOccurrences(of: "`", with: "``")
_ = try await execute(query: "DROP DATABASE `\(escapedName)`")
}

// MARK: - Database Switching

func switchDatabase(to database: String) async throws {
Expand Down
1 change: 1 addition & 0 deletions Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ final class PostgreSQLPlugin: NSObject, TableProPlugin, DriverPlugin {
static let supportsForeignKeyDisable = false
static let requiresReconnectForDatabaseSwitch = true
static let parameterStyle: ParameterStyle = .dollar
static let supportsDropDatabase = true

static let sqlDialect: SQLDialectDescriptor? = SQLDialectDescriptor(
identifierQuote: "\"",
Expand Down
5 changes: 5 additions & 0 deletions Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,11 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
_ = try await execute(query: query)
}

func dropDatabase(name: String) async throws {
let escapedName = name.replacingOccurrences(of: "\"", with: "\"\"")
_ = try await execute(query: "DROP DATABASE \"\(escapedName)\"")
}

// MARK: - All Tables Metadata

func allTablesMetadataSQL(schema: String?) -> String? {
Expand Down
5 changes: 5 additions & 0 deletions Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,11 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
_ = try await execute(query: query)
}

func dropDatabase(name: String) async throws {
let escapedName = name.replacingOccurrences(of: "\"", with: "\"\"")
_ = try await execute(query: "DROP DATABASE \"\(escapedName)\"")
}

// MARK: - All Tables Metadata

func allTablesMetadataSQL(schema: String?) -> String? {
Expand Down
2 changes: 2 additions & 0 deletions Plugins/TableProPluginKit/DriverPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public protocol DriverPlugin: TableProPlugin {
static var isDownloadable: Bool { get }
static var postConnectActions: [PostConnectAction] { get }
static var parameterStyle: ParameterStyle { get }
static var supportsDropDatabase: Bool { get }
}

public extension DriverPlugin {
Expand Down Expand Up @@ -114,4 +115,5 @@ public extension DriverPlugin {
static var parameterStyle: ParameterStyle { .questionMark }
static var isDownloadable: Bool { false }
static var postConnectActions: [PostConnectAction] { [] }
static var supportsDropDatabase: Bool { false }
}
6 changes: 6 additions & 0 deletions Plugins/TableProPluginKit/PluginDatabaseDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable {
func fetchDependentTypes(table: String, schema: String?) async throws -> [(name: String, labels: [String])]
func fetchDependentSequences(table: String, schema: String?) async throws -> [(name: String, ddl: String)]
func createDatabase(name: String, charset: String, collation: String?) async throws
func dropDatabase(name: String) async throws
func executeParameterized(query: String, parameters: [String?]) async throws -> PluginQueryResult

// Query building (optional, for NoSQL plugins)
Expand Down Expand Up @@ -223,6 +224,11 @@ public extension PluginDatabaseDriver {
throw NSError(domain: "PluginDatabaseDriver", code: -1, userInfo: [NSLocalizedDescriptionKey: "createDatabase not supported"])
}

func dropDatabase(name: String) async throws {
throw NSError(domain: "PluginDatabaseDriver", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Drop database is not supported by this driver"])
}

func switchDatabase(to database: String) async throws {
throw NSError(
domain: "TableProPluginKit",
Expand Down
8 changes: 8 additions & 0 deletions TablePro/Core/Database/DatabaseDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ protocol DatabaseDriver: AnyObject {
/// Create a new database
func createDatabase(name: String, charset: String, collation: String?) async throws

/// Drop a database
func dropDatabase(name: String) async throws

// MARK: - Maintenance

/// Returns the list of supported maintenance operations (e.g. "VACUUM", "ANALYZE").
Expand Down Expand Up @@ -233,6 +236,11 @@ extension DatabaseDriver {
return true
}

func dropDatabase(name: String) async throws {
throw NSError(domain: "DatabaseDriver", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Drop database is not supported by this driver"])
}

/// Default fetchAllDatabaseMetadata: falls back to per-database calls (N+1).
/// Drivers should override with a single bulk query where possible.
func fetchAllDatabaseMetadata() async throws -> [DatabaseMetadata] {
Expand Down
4 changes: 4 additions & 0 deletions TablePro/Core/Plugins/PluginDriverAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ final class PluginDriverAdapter: DatabaseDriver, SchemaSwitchable {
try await pluginDriver.createDatabase(name: name, charset: charset, collation: collation)
}

func dropDatabase(name: String) async throws {
try await pluginDriver.dropDatabase(name: name)
}

// MARK: - Batch Operations

func fetchAllColumns() async throws -> [String: [ColumnInfo]] {
Expand Down
5 changes: 5 additions & 0 deletions TablePro/Core/Plugins/PluginManager+Registration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ extension PluginManager {
.supportsColumnReorder ?? false
}

func supportsDropDatabase(for databaseType: DatabaseType) -> Bool {
PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)?
.capabilities.supportsDropDatabase ?? false
}

func autoLimitStyle(for databaseType: DatabaseType) -> AutoLimitStyle {
guard let snapshot = PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId) else {
return .limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ extension PluginMetadataRegistry {
supportsForeignKeyDisable: false,
supportsReadOnlyMode: true,
supportsQueryProgress: false,
requiresReconnectForDatabaseSwitch: false
requiresReconnectForDatabaseSwitch: false,
supportsDropDatabase: false
),
schema: PluginMetadataSnapshot.SchemaInfo(
defaultSchemaName: "",
Expand Down Expand Up @@ -172,7 +173,8 @@ extension PluginMetadataRegistry {
supportsForeignKeyDisable: false,
supportsReadOnlyMode: true,
supportsQueryProgress: false,
requiresReconnectForDatabaseSwitch: false
requiresReconnectForDatabaseSwitch: false,
supportsDropDatabase: true
),
schema: PluginMetadataSnapshot.SchemaInfo(
defaultSchemaName: "",
Expand Down
Loading
Loading