Guided implementation for adding a new database engine to TablePro. Pre-loaded with all integration points, file locations, patterns, and the complete checklist derived from Redis implementation experience. Use when asked to add support for a new database type (e.g., Cassandra, DynamoDB, ClickHouse).
42:T3c5f,
Complete guide for adding a new database engine, based on the Redis implementation (35 files, 103+ integration points across 41 files).
| Layer | Files to Create | Files to Modify |
|---|---|---|
| C Bridge (if native lib) | CNewDB/ module | project.pbxproj, Libs/ |
| Connection | NewDBConnection.swift | — |
| Driver | NewDBDriver.swift, +ResultBuilding.swift | DatabaseDriver.swift |
| Core Utilities | NewDBCommandParser.swift, NewDBQueryBuilder.swift, NewDBStatementGenerator.swift | — |
| Models |
| — |
DatabaseConnection.swift, ExportModels.swift, QueryTab.swift |
| Services | — | ColumnType.swift, SQLDialectProvider.swift, TableQueryBuilder.swift, ExportService.swift, ImportService.swift, SQLEscaping.swift, FilterSQLGenerator.swift |
| Change Tracking | — | DataChangeManager.swift, SQLStatementGenerator.swift |
| Coordinator | MainContentCoordinator+NewDB.swift | MainContentCoordinator.swift, +Navigation.swift, +TableOperations.swift, +SidebarSave.swift |
| Views | — | ConnectionFormView.swift, TableProToolbarView.swift, SidebarView.swift, DataGridView.swift, ExportDialog.swift, FilterPanelView.swift, SQLEditorView.swift, HighlightedSQLTextView.swift, SQLReviewPopover.swift, TypePickerContentView.swift, StructureRowProvider.swift |
| AI | — | AISchemaContext.swift, AIPromptTemplates.swift, AIChatPanelView.swift |
| Other | — | ContentView.swift, MainContentView.swift, Theme.swift, ConnectionURLParser.swift, ConnectionURLFormatter.swift, SQLParameterInliner.swift, SchemaStatementGenerator.swift |
| Tests | NewDBTests/ directory | TestFixtures.swift, DatabaseTypeTests.swift |
| Docs | docs/databases/newdb.mdx, docs/vi/databases/newdb.mdx | docs/docs.json, docs/databases/overview.mdx, docs/vi/databases/overview.mdx |
| Build | scripts/build-newdb-lib.sh (if native) | scripts/ci/prepare-libs.sh, scripts/build-release.sh |
Create TablePro/Core/Database/CNewDB/:
CNewDB/
├── CNewDB.h # Umbrella header
├── module.modulemap # Swift module map
└── include/
└── newdb/ # C library headers
module.modulemap pattern:
module CNewDB {
umbrella header "CNewDB.h"
export *
link "newdb" // Links against libNewDB.a
}
Build static libs — create scripts/build-newdb-lib.sh:
lipo -createLibs/libnewdb_universal.aUpdate Xcode project — add to project.pbxproj:
Libs/libnewdb*.a to Link Binary With LibrariesCreate: TablePro/Core/Database/NewDBConnection.swift
Pattern from RedisConnection.swift:
import Foundation
import OSLog
import CNewDB // if C bridge
final class NewDBConnection: @unchecked Sendable {
private static let logger = Logger(subsystem: "com.TablePro", category: "NewDBConnection")
private let host: String
private let port: Int
// ... connection parameters
func connect() throws { ... }
func disconnect() { ... }
func execute(_ command: String) throws -> NewDBReply { ... }
}
Create: TablePro/Core/Database/NewDBDriver.swift
Must conform to DatabaseDriver protocol. Key methods:
final class NewDBDriver: DatabaseDriver {
let connection: DatabaseConnection
var status: ConnectionStatus = .disconnected
var serverVersion: String?
// Required protocol methods:
func connect() async throws
func disconnect()
func testConnection() async throws -> Bool
func applyQueryTimeout(_ seconds: Int) async throws
func execute(query: String) async throws -> QueryResult
func executeParameterized(query: String, parameters: [Any?]) async throws -> QueryResult
func fetchRowCount(query: String) async throws -> Int
func fetchRows(query: String, offset: Int, limit: Int) async throws -> QueryResult
func fetchTables() async throws -> [TableInfo]
func fetchColumns(table: String) async throws -> [ColumnInfo]
func fetchAllColumns() async throws -> [String: [ColumnInfo]]
func fetchIndexes(table: String) async throws -> [IndexInfo]
func fetchTableMetadata(table: String) async throws -> TableMetadata?
func fetchDatabases() async throws -> [String]
func switchDatabase(_ name: String) async throws
func fetchForeignKeys(table: String) async throws -> [ForeignKeyInfo]
func fetchTriggers(table: String) async throws -> [TriggerInfo]
}
Create: TablePro/Core/Database/NewDBDriver+ResultBuilding.swift
For non-SQL databases, build virtual table results:
extension NewDBDriver {
func buildBrowseResult(items: [...]) -> QueryResult {
// Map native data to columns/rows/columnTypes
QueryResult(
columns: ["col1", "col2", ...],
rows: mappedRows,
columnTypes: [.text(rawType: "String"), ...],
affectedRows: count,
metadata: nil
)
}
}
Column types for custom badges — use rawType to customize ColumnType.badgeLabel:
// In ColumnType.swift badgeLabel:
case .text(let rawType):
return rawType == "NewDBRaw" ? "custom-label" : "string"
File: TablePro/Models/DatabaseConnection.swift (~line 100)
Add case to DatabaseType:
case newdb = "NewDB"
Then update ALL switch statements on DatabaseType. Search with:
Grep pattern="switch.*self|case \\.mysql" path="TablePro/"
Properties to add in DatabaseType:
iconName → asset namedisplayName → localized display namedefaultPort → default connection portquoteIdentifier(_:) → identifier quoting styleconnectionURLScheme → URL scheme for connection stringsAdd any engine-specific connection properties (e.g., redisDatabase: Int for Redis).
File: TablePro/Models/ExportModels.swift
File: TablePro/Models/QueryTab.swift
columnEnumValues for Redis Type dropdown)File: TablePro/Core/Services/ColumnType.swift
badgeLabel computed propertyFile: TablePro/Core/Services/SQLDialectProvider.swift
File: TablePro/Core/Services/TableQueryBuilder.swift
File: TablePro/Core/Database/SQLEscaping.swift
File: TablePro/Core/Database/FilterSQLGenerator.swift
Files: ExportService.swift, ImportService.swift
For SQL databases, modify SQLStatementGenerator.swift.
For non-SQL databases, create a dedicated generator:
Create: TablePro/Core/NewDB/NewDBStatementGenerator.swift
Pattern from RedisStatementGenerator.swift:
struct NewDBStatementGenerator {
static func generateInsert(...) -> String { ... }
static func generateUpdate(...) -> String { ... }
static func generateDelete(...) -> String { ... }
}
File: TablePro/Core/ChangeTracking/DataChangeManager.swift
configureForTable if neededgenerateSQL() routes to the correct statement generatorFile: TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarSave.swift
CRITICAL: The right sidebar has .keyboardShortcut("s", modifiers: .command) which intercepts Cmd+S. The sidebar's saveSidebarEdits() must handle the new engine:
if connection.type == .newdb {
// Generate engine-specific commands
statements += generateSidebarNewDBCommands(...)
} else {
// Existing SQL path
}
File: TablePro/Views/Main/MainContentCoordinator.swift
Key integration points (search for case .redis to find all):
isEditable, tableName, columnEnumValuesFile: TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift
Create: TablePro/Views/Main/Extensions/MainContentCoordinator+NewDB.swift
File: TablePro/Views/Main/Extensions/MainContentCoordinator+TableOperations.swift
File: TablePro/Views/Connection/ConnectionFormView.swift
File: TablePro/Views/Toolbar/TableProToolbarView.swift
File: TablePro/TableProApp.swift
File: TablePro/Views/Results/DataGridView.swift
Files that commonly need case .newdb handling:
SidebarView.swift — sidebar display logicFilterPanelView.swift — filter UIExportDialog.swift — export optionsSQLEditorView.swift — editor configurationHighlightedSQLTextView.swift — syntax highlightingSQLReviewPopover.swift — SQL previewTypePickerContentView.swift — type pickerStructureRowProvider.swift — structure viewMainEditorContentView.swift — editor content areaContentView.swift — app layoutMainContentView.swift — main viewAISchemaContext.swift — schema context for AIAIPromptTemplates.swift — prompt templatesAIChatPanelView.swift — chat panelConnectionURLParser.swift — parse connection URLsConnectionURLFormatter.swift — format connection URLsSQLParameterInliner.swift — parameter inliningSchemaStatementGenerator.swift — schema DDL generationSQLCompletionProvider.swift — autocompleteTheme.swift — engine-specific themingCreate test directory: TableProTests/Core/NewDB/
Required test files (pattern from Redis):
NewDBCommandParserTests.swiftNewDBQueryBuilderTests.swiftNewDBStatementGeneratorTests.swiftColumnTypeNewDBTests.swiftExportModelsNewDBTests.swiftAlso update:
TableProTests/Models/DatabaseTypeTests.swiftTableProTests/Helpers/TestFixtures.swiftdocs/databases/newdb.mdx and docs/vi/databases/newdb.mdxdocs/docs.json — add page to navigationdocs/databases/overview.mdx and docs/vi/databases/overview.mdxdocs/features/import-export.mdx if applicablescripts/ci/prepare-libs.sh — download/build native libsscripts/build-release.sh — include new libs in releaseproject.pbxproj — add all new files to Xcode projectUse subagents with isolation: "worktree" for parallel work:
Wave 1 (Foundation): C Bridge + Connection + Driver (sequential, depends on each other) Wave 2 (Models — parallel):
DatabaseConnection.swift + DatabaseType enum updatesColumnType.swift + ExportModels.swiftWave 3 (Integration — parallel):
MainContentCoordinator.swift + extensionsDataChangeManager.swift + SQLStatementGenerator.swift + SidebarSave.swiftSQLDialectProvider, TableQueryBuilder, SQLEscaping, FilterSQLGenerator)Wave 4 (Views — parallel):
ConnectionFormView.swift + TableProToolbarView.swift + TableProApp.swiftDataGridView.swift + SidebarView.swift + FilterPanelView.swiftWave 5 (Tests + Docs — parallel):
Wave 6 (Build verification):
xcodebuild -project TablePro.xcodeproj -scheme TablePro -configuration Debug build -skipPackagePluginValidation
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation
swiftlint lint --strict
Sidebar Cmd+S intercepts menu bar Cmd+S — the right sidebar's .keyboardShortcut("s") takes priority. saveSidebarEdits() must handle the new engine, not just the main save path.
extractTableName(from:) returns nil for non-SQL — preserve tableName from the tab for non-SQL engines instead of parsing SQL.
configureForTable requires metadata — non-SQL engines won't have metadata?.primaryKeyColumn. Add a fallback to manually configure the changeManager with a known primary key.
Toolbar items with .opacity(0) still occupy space — use conditional if to completely remove toolbar items, not .opacity(0) or .hidden().
xcodebuild and Xcode IDE use different DerivedData — debug logging may not appear if building with one but running with the other.
Every switch on DatabaseType must be updated — there are 100+ switch sites. Use Grep pattern="case \\.mysql" path="TablePro/" to find them all.
Column type rawType drives badge labels — use custom rawType strings (e.g., "RedisRaw", "RedisInt") and override in ColumnType.badgeLabel rather than adding new enum cases.
.enumType column type triggers dropdown picker — set columnEnumValues[columnName] on the tab to populate the picker values.
| Category | New Files | Modified Files |
|---|---|---|
| Database Core | 3-5 | 2 |
| Models | 0 | 3-4 |
| Services | 0-1 | 6-8 |
| Change Tracking | 1 | 2-3 |
| Coordinator | 1 | 4-5 |
| Views | 0 | 12-15 |
| AI | 0 | 3 |
| Utilities | 0 | 4-6 |
| Tests | 5-8 | 2 |
| Docs | 2 | 4 |
| Build/CI | 1-2 | 2-3 |
| Total | ~15-20 | ~45-55 |