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 @@ -12,12 +12,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moved string literal escaping into plugin drivers via `escapeStringLiteral` on `PluginDatabaseDriver` and `DatabaseDriver` protocols; `SQLEscaping.escapeStringLiteral` now uses ANSI SQL escaping only (doubles single quotes, strips null bytes)
- SQL autocomplete data types and CREATE TABLE options now use plugin-provided dialect data instead of hardcoded per-database switches
- `FilterSQLGenerator` now uses `SQLDialectDescriptor` data (regex syntax, boolean literals, LIKE escape style, pagination style) instead of `DatabaseType` switch statements
- Moved identifier quoting, autocomplete statement completions, view templates, and FK disable/enable into plugin system
- Removed `DatabaseType` switches from `FilterSQLGenerator`, `SQLCompletionProvider`, `ImportDataSinkAdapter`, and `MainContentCoordinator+SidebarActions`

### Added

- `SQLDialectDescriptor` in TableProPluginKit: plugins can now self-describe their SQL dialect (keywords, functions, data types, identifier quoting), with `SQLDialectFactory` preferring plugin-provided dialect info over built-in structs
- DDL schema generation protocol in TableProPluginKit: plugins can now optionally provide database-specific ALTER TABLE syntax (ADD/MODIFY/DROP COLUMN, ADD/DROP INDEX, ADD/DROP FK, MODIFY PK) via `PluginDatabaseDriver`, with `SchemaStatementGenerator` trying plugin methods first before falling back to built-in logic
- Plugin-provided table operations: `truncateTableStatements`, `dropObjectStatement`, `foreignKeyDisableStatements`, `foreignKeyEnableStatements` in `PluginDatabaseDriver` protocol, allowing plugins to override TRUNCATE, DROP, and FK handling SQL
- `CompletionEntry` struct and `statementCompletions` on `DriverPlugin` for plugin-provided autocomplete entries (MongoDB MQL methods, Redis commands)
- `offsetFetchOrderBy` property on `SQLDialectDescriptor` for plugin-controlled ORDER BY in OFFSET/FETCH pagination
- `createViewTemplate()`, `editViewFallbackTemplate(viewName:)`, and `castColumnToText(_:)` on `PluginDatabaseDriver` for plugin-provided view DDL templates and column casting
- `buildExplainQuery` method in `PluginDatabaseDriver` protocol: plugins can now provide database-specific EXPLAIN syntax, with coordinator falling back to built-in logic when plugin returns nil
- `SettablePlugin` protocol in TableProPluginKit SDK: unified settings pattern for all plugins with automatic persistence via `loadSettings()`/`saveSettings()`, replacing duplicated boilerplate across export/import/driver plugins
- Plugin UI/capability metadata: each driver plugin now self-declares brand color, connection mode, supported features, column types, URL schemes, and grouping strategy via the `DriverPlugin` protocol
Expand Down
11 changes: 11 additions & 0 deletions Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,17 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
"EXPLAIN \(sql)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"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 REPLACE VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

// MARK: - Kill Query

private func killQuery(queryId: String) {
Expand Down
11 changes: 11 additions & 0 deletions Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,17 @@ final class DuckDBPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
"EXPLAIN \(sql)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let quoted = quoteIdentifier(viewName)
return "CREATE OR REPLACE VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

// MARK: - Private Helpers

nonisolated private func setInterruptHandle(_ handle: duckdb_connection?) {
Expand Down
15 changes: 15 additions & 0 deletions Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,21 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
return "[\(escaped)]"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE OR ALTER 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;"
}

func castColumnToText(_ column: String) -> String {
"CAST(\(column) AS NVARCHAR(MAX))"
}

init(config: DriverConnectionConfig) {
self.config = config
self._currentSchema = config.additionalFields["mssqlSchema"]?.isEmpty == false
Expand Down
27 changes: 27 additions & 0 deletions Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,33 @@ final class MongoDBPlugin: NSObject, TableProPlugin, DriverPlugin {

static let sqlDialect: SQLDialectDescriptor? = nil

static var statementCompletions: [CompletionEntry] {
[
CompletionEntry(label: "db.", insertText: "db."),
CompletionEntry(label: "db.runCommand", insertText: "db.runCommand"),
CompletionEntry(label: "db.adminCommand", insertText: "db.adminCommand"),
CompletionEntry(label: "db.createView", insertText: "db.createView"),
CompletionEntry(label: "db.createCollection", insertText: "db.createCollection"),
CompletionEntry(label: "show dbs", insertText: "show dbs"),
CompletionEntry(label: "show collections", insertText: "show collections"),
CompletionEntry(label: ".find", insertText: ".find"),
CompletionEntry(label: ".findOne", insertText: ".findOne"),
CompletionEntry(label: ".aggregate", insertText: ".aggregate"),
CompletionEntry(label: ".insertOne", insertText: ".insertOne"),
CompletionEntry(label: ".insertMany", insertText: ".insertMany"),
CompletionEntry(label: ".updateOne", insertText: ".updateOne"),
CompletionEntry(label: ".updateMany", insertText: ".updateMany"),
CompletionEntry(label: ".deleteOne", insertText: ".deleteOne"),
CompletionEntry(label: ".deleteMany", insertText: ".deleteMany"),
CompletionEntry(label: ".replaceOne", insertText: ".replaceOne"),
CompletionEntry(label: ".findOneAndUpdate", insertText: ".findOneAndUpdate"),
CompletionEntry(label: ".findOneAndReplace", insertText: ".findOneAndReplace"),
CompletionEntry(label: ".findOneAndDelete", insertText: ".findOneAndDelete"),
CompletionEntry(label: ".countDocuments", insertText: ".countDocuments"),
CompletionEntry(label: ".createIndex", insertText: ".createIndex")
]
}

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
MongoDBPluginDriver(config: config)
}
Expand Down
11 changes: 11 additions & 0 deletions Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,17 @@ final class MongoDBPluginDriver: PluginDatabaseDriver {
}
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"db.createView(\"view_name\", \"source_collection\", [\n {\"$match\": {}},\n {\"$project\": {\"_id\": 1}}\n])"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let escaped = viewName.replacingOccurrences(of: "\"", with: "\\\"")
return "db.runCommand({\"collMod\": \"\(escaped)\", \"viewOn\": \"source_collection\", \"pipeline\": [{\"$match\": {}}]})"
}

// MARK: - Query Building

func buildBrowseQuery(
Expand Down
25 changes: 25 additions & 0 deletions Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,31 @@ final class MySQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
"EXPLAIN \(sql)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let quoted = quoteIdentifier(viewName)
return "ALTER VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

func castColumnToText(_ column: String) -> String {
"CAST(\(column) AS CHAR)"
}

// MARK: - Foreign Key Checks

func foreignKeyDisableStatements() -> [String]? {
["SET FOREIGN_KEY_CHECKS=0"]
}

func foreignKeyEnableStatements() -> [String]? {
["SET FOREIGN_KEY_CHECKS=1"]
}

// MARK: - Private Helpers

private func extractTableName(from query: String) -> String? {
Expand Down
14 changes: 13 additions & 1 deletion Plugins/OracleDriverPlugin/OraclePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin {
regexSyntax: .regexpLike,
booleanLiteralStyle: .numeric,
likeEscapeStyle: .explicit,
paginationStyle: .offsetFetch
paginationStyle: .offsetFetch,
offsetFetchOrderBy: "ORDER BY 1"
)

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
Expand All @@ -111,6 +112,17 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
self.config = config
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let quoted = quoteIdentifier(viewName)
return "CREATE OR REPLACE VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

// MARK: - Connection

func connect() async throws {
Expand Down
15 changes: 15 additions & 0 deletions Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
"EXPLAIN \(sql)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let quoted = quoteIdentifier(viewName)
return "CREATE OR REPLACE VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

func castColumnToText(_ column: String) -> String {
"CAST(\(column) AS TEXT)"
}

// MARK: - Schema

func fetchTables(schema: String?) async throws -> [PluginTableInfo] {
Expand Down
43 changes: 43 additions & 0 deletions Plugins/RedisDriverPlugin/RedisPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,49 @@ final class RedisPlugin: NSObject, TableProPlugin, DriverPlugin {

static let sqlDialect: SQLDialectDescriptor? = nil

static var statementCompletions: [CompletionEntry] {
[
CompletionEntry(label: "GET", insertText: "GET"),
CompletionEntry(label: "SET", insertText: "SET"),
CompletionEntry(label: "DEL", insertText: "DEL"),
CompletionEntry(label: "EXISTS", insertText: "EXISTS"),
CompletionEntry(label: "KEYS", insertText: "KEYS"),
CompletionEntry(label: "HGET", insertText: "HGET"),
CompletionEntry(label: "HSET", insertText: "HSET"),
CompletionEntry(label: "HGETALL", insertText: "HGETALL"),
CompletionEntry(label: "HDEL", insertText: "HDEL"),
CompletionEntry(label: "LPUSH", insertText: "LPUSH"),
CompletionEntry(label: "RPUSH", insertText: "RPUSH"),
CompletionEntry(label: "LRANGE", insertText: "LRANGE"),
CompletionEntry(label: "LLEN", insertText: "LLEN"),
CompletionEntry(label: "SADD", insertText: "SADD"),
CompletionEntry(label: "SMEMBERS", insertText: "SMEMBERS"),
CompletionEntry(label: "SREM", insertText: "SREM"),
CompletionEntry(label: "SCARD", insertText: "SCARD"),
CompletionEntry(label: "ZADD", insertText: "ZADD"),
CompletionEntry(label: "ZRANGE", insertText: "ZRANGE"),
CompletionEntry(label: "ZREM", insertText: "ZREM"),
CompletionEntry(label: "ZSCORE", insertText: "ZSCORE"),
CompletionEntry(label: "EXPIRE", insertText: "EXPIRE"),
CompletionEntry(label: "TTL", insertText: "TTL"),
CompletionEntry(label: "PERSIST", insertText: "PERSIST"),
CompletionEntry(label: "TYPE", insertText: "TYPE"),
CompletionEntry(label: "SCAN", insertText: "SCAN"),
CompletionEntry(label: "HSCAN", insertText: "HSCAN"),
CompletionEntry(label: "SSCAN", insertText: "SSCAN"),
CompletionEntry(label: "ZSCAN", insertText: "ZSCAN"),
CompletionEntry(label: "INFO", insertText: "INFO"),
CompletionEntry(label: "DBSIZE", insertText: "DBSIZE"),
CompletionEntry(label: "FLUSHDB", insertText: "FLUSHDB"),
CompletionEntry(label: "SELECT", insertText: "SELECT"),
CompletionEntry(label: "INCR", insertText: "INCR"),
CompletionEntry(label: "DECR", insertText: "DECR"),
CompletionEntry(label: "APPEND", insertText: "APPEND"),
CompletionEntry(label: "MGET", insertText: "MGET"),
CompletionEntry(label: "MSET", insertText: "MSET")
]
}

func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
RedisPluginDriver(config: config)
}
Expand Down
10 changes: 10 additions & 0 deletions Plugins/RedisDriverPlugin/RedisPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,16 @@ final class RedisPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
return "DEBUG OBJECT \(key)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"-- Redis does not support views"
}

func editViewFallbackTemplate(viewName: String) -> String? {
"-- Redis does not support views"
}

// MARK: - Query Building

func buildBrowseQuery(
Expand Down
21 changes: 21 additions & 0 deletions Plugins/SQLiteDriverPlugin/SQLitePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,27 @@ final class SQLitePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
"EXPLAIN QUERY PLAN \(sql)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
"CREATE VIEW IF NOT EXISTS view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
}

func editViewFallbackTemplate(viewName: String) -> String? {
let quoted = quoteIdentifier(viewName)
return "DROP VIEW IF EXISTS \(quoted);\nCREATE VIEW \(quoted) AS\nSELECT * FROM table_name;"
}

// MARK: - Foreign Key Checks

func foreignKeyDisableStatements() -> [String]? {
["PRAGMA foreign_keys = OFF"]
}

func foreignKeyEnableStatements() -> [String]? {
["PRAGMA foreign_keys = ON"]
}

// MARK: - Pagination

func fetchRowCount(query: String) async throws -> Int {
Expand Down
2 changes: 2 additions & 0 deletions Plugins/TableProPluginKit/DriverPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public protocol DriverPlugin: TableProPlugin {
static var defaultGroupName: String { get }
static var columnTypesByCategory: [String: [String]] { get }
static var sqlDialect: SQLDialectDescriptor? { get }
static var statementCompletions: [CompletionEntry] { get }
}

public extension DriverPlugin {
Expand Down Expand Up @@ -74,4 +75,5 @@ public extension DriverPlugin {
]
}
static var sqlDialect: SQLDialectDescriptor? { nil }
static var statementCompletions: [CompletionEntry] { [] }
}
8 changes: 8 additions & 0 deletions Plugins/TableProPluginKit/PluginDatabaseDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable {

// String escaping
func escapeStringLiteral(_ value: String) -> String

func createViewTemplate() -> String?
func editViewFallbackTemplate(viewName: String) -> String?
func castColumnToText(_ column: String) -> String
}

public extension PluginDatabaseDriver {
Expand Down Expand Up @@ -224,6 +228,10 @@ public extension PluginDatabaseDriver {

func buildExplainQuery(_ sql: String) -> String? { nil }

func createViewTemplate() -> String? { nil }
func editViewFallbackTemplate(viewName: String) -> String? { nil }
func castColumnToText(_ column: String) -> String { column }

func quoteIdentifier(_ name: String) -> String {
let escaped = name.replacingOccurrences(of: "\"", with: "\"\"")
return "\"\(escaped)\""
Expand Down
14 changes: 13 additions & 1 deletion Plugins/TableProPluginKit/SQLDialectDescriptor.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import Foundation

public struct CompletionEntry: Sendable {
public let label: String
public let insertText: String
public init(label: String, insertText: String) {
self.label = label
self.insertText = insertText
}
}

public struct SQLDialectDescriptor: Sendable {
public let identifierQuote: String
public let keywords: Set<String>
Expand All @@ -12,6 +21,7 @@ public struct SQLDialectDescriptor: Sendable {
public let booleanLiteralStyle: BooleanLiteralStyle
public let likeEscapeStyle: LikeEscapeStyle
public let paginationStyle: PaginationStyle
public let offsetFetchOrderBy: String

public enum RegexSyntax: String, Sendable {
case regexp // MySQL: column REGEXP 'pattern'
Expand Down Expand Up @@ -46,7 +56,8 @@ public struct SQLDialectDescriptor: Sendable {
regexSyntax: RegexSyntax = .unsupported,
booleanLiteralStyle: BooleanLiteralStyle = .numeric,
likeEscapeStyle: LikeEscapeStyle = .explicit,
paginationStyle: PaginationStyle = .limit
paginationStyle: PaginationStyle = .limit,
offsetFetchOrderBy: String = "ORDER BY (SELECT NULL)"
) {
self.identifierQuote = identifierQuote
self.keywords = keywords
Expand All @@ -57,5 +68,6 @@ public struct SQLDialectDescriptor: Sendable {
self.booleanLiteralStyle = booleanLiteralStyle
self.likeEscapeStyle = likeEscapeStyle
self.paginationStyle = paginationStyle
self.offsetFetchOrderBy = offsetFetchOrderBy
}
}
Loading
Loading