Skip to content

Commit 1df506e

Browse files
authored
Merge pull request #212 from datlechin/feat/startup-commands
feat: add startup commands for connections
2 parents fa6a4d0 + 253f51c commit 1df506e

File tree

12 files changed

+345
-33
lines changed

12 files changed

+345
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Startup commands — run custom SQL after connecting (e.g., SET time_zone) in Connection > Advanced tab
1213
- Plugin system architecture — all 8 database drivers (MySQL, PostgreSQL, SQLite, ClickHouse, MSSQL, MongoDB, Redis, Oracle) extracted into `.tableplugin` bundles loaded at runtime
1314
- Settings > Plugins tab for plugin management — list installed plugins, enable/disable, install from file, uninstall user plugins, view plugin details
1415
- Plugin marketplace — browse, search, and install plugins from the GitHub-hosted registry with SHA-256 checksum verification, ETag caching, and offline fallback

TablePro/Core/Database/DatabaseManager.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ final class DatabaseManager {
138138
try await driver.applyQueryTimeout(timeoutSeconds)
139139
}
140140

141+
// Run startup commands before schema init
142+
await executeStartupCommands(
143+
connection.startupCommands, on: driver, connectionName: connection.name
144+
)
145+
141146
// Initialize schema for drivers that support schema switching
142147
if let schemaDriver = driver as? SchemaSwitchable {
143148
activeSessions[connection.id]?.currentSchema = schemaDriver.currentSchema
@@ -190,6 +195,9 @@ final class DatabaseManager {
190195
if metaTimeout > 0 {
191196
try? await metaDriver.applyQueryTimeout(metaTimeout)
192197
}
198+
await self.executeStartupCommands(
199+
connection.startupCommands, on: metaDriver, connectionName: connection.name
200+
)
193201
if let savedSchema = self.activeSessions[metaConnectionId]?.currentSchema,
194202
let schemaMetaDriver = metaDriver as? SchemaSwitchable {
195203
try? await schemaMetaDriver.switchSchema(to: savedSchema)
@@ -547,6 +555,10 @@ final class DatabaseManager {
547555
try await driver.applyQueryTimeout(timeoutSeconds)
548556
}
549557

558+
await executeStartupCommands(
559+
session.connection.startupCommands, on: driver, connectionName: session.connection.name
560+
)
561+
550562
if let savedSchema = session.currentSchema,
551563
let schemaDriver = driver as? SchemaSwitchable {
552564
try? await schemaDriver.switchSchema(to: savedSchema)
@@ -617,6 +629,10 @@ final class DatabaseManager {
617629
try await driver.applyQueryTimeout(timeoutSeconds)
618630
}
619631

632+
await executeStartupCommands(
633+
session.connection.startupCommands, on: driver, connectionName: session.connection.name
634+
)
635+
620636
if let savedSchema = activeSessions[sessionId]?.currentSchema,
621637
let schemaDriver = driver as? SchemaSwitchable {
622638
try? await schemaDriver.switchSchema(to: savedSchema)
@@ -639,6 +655,8 @@ final class DatabaseManager {
639655
let metaConnection = effectiveConnection
640656
let metaConnectionId = sessionId
641657
let metaTimeout = AppSettingsManager.shared.general.queryTimeoutSeconds
658+
let startupCmds = session.connection.startupCommands
659+
let connName = session.connection.name
642660
Task { [weak self] in
643661
guard let self else { return }
644662
do {
@@ -647,6 +665,9 @@ final class DatabaseManager {
647665
if metaTimeout > 0 {
648666
try? await metaDriver.applyQueryTimeout(metaTimeout)
649667
}
668+
await self.executeStartupCommands(
669+
startupCmds, on: metaDriver, connectionName: connName
670+
)
650671
if let savedSchema = self.activeSessions[metaConnectionId]?.currentSchema,
651672
let schemaMetaDriver = metaDriver as? SchemaSwitchable {
652673
try? await schemaMetaDriver.switchSchema(to: savedSchema)
@@ -720,6 +741,34 @@ final class DatabaseManager {
720741
}
721742
}
722743

744+
// MARK: - Startup Commands
745+
746+
nonisolated private func executeStartupCommands(
747+
_ commands: String?, on driver: DatabaseDriver, connectionName: String
748+
) async {
749+
guard let commands, !commands.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
750+
return
751+
}
752+
753+
let statements = commands
754+
.components(separatedBy: CharacterSet(charactersIn: ";\n"))
755+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
756+
.filter { !$0.isEmpty }
757+
758+
for statement in statements {
759+
do {
760+
_ = try await driver.execute(query: statement)
761+
Self.logger.info(
762+
"Startup command succeeded for '\(connectionName)': \(statement)"
763+
)
764+
} catch {
765+
Self.logger.warning(
766+
"Startup command failed for '\(connectionName)': \(statement)\(error.localizedDescription)"
767+
)
768+
}
769+
}
770+
}
771+
723772
// MARK: - Schema Changes
724773

725774
/// Execute schema changes (ALTER TABLE, CREATE INDEX, etc.) in a transaction

TablePro/Core/Storage/ConnectionStorage.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,18 @@ final class ConnectionStorage {
115115
username: connection.username,
116116
type: connection.type,
117117
sshConfig: connection.sshConfig,
118+
sslConfig: connection.sslConfig,
118119
color: connection.color,
119120
tagId: connection.tagId,
120-
groupId: connection.groupId
121+
groupId: connection.groupId,
122+
isReadOnly: connection.isReadOnly,
123+
aiPolicy: connection.aiPolicy,
124+
mongoReadPreference: connection.mongoReadPreference,
125+
mongoWriteConcern: connection.mongoWriteConcern,
126+
redisDatabase: connection.redisDatabase,
127+
mssqlSchema: connection.mssqlSchema,
128+
oracleServiceName: connection.oracleServiceName,
129+
startupCommands: connection.startupCommands
121130
)
122131

123132
// Save the duplicate connection
@@ -365,6 +374,9 @@ private struct StoredConnection: Codable {
365374
// Oracle service name
366375
let oracleServiceName: String?
367376

377+
// Startup commands
378+
let startupCommands: String?
379+
368380
init(from connection: DatabaseConnection) {
369381
self.id = connection.id
370382
self.name = connection.name
@@ -406,6 +418,9 @@ private struct StoredConnection: Codable {
406418

407419
// Oracle service name
408420
self.oracleServiceName = connection.oracleServiceName
421+
422+
// Startup commands
423+
self.startupCommands = connection.startupCommands
409424
}
410425

411426
// Custom decoder to handle migration from old format
@@ -445,6 +460,7 @@ private struct StoredConnection: Codable {
445460
aiPolicy = try container.decodeIfPresent(String.self, forKey: .aiPolicy)
446461
mssqlSchema = try container.decodeIfPresent(String.self, forKey: .mssqlSchema)
447462
oracleServiceName = try container.decodeIfPresent(String.self, forKey: .oracleServiceName)
463+
startupCommands = try container.decodeIfPresent(String.self, forKey: .startupCommands)
448464
}
449465

450466
func toConnection() -> DatabaseConnection {
@@ -487,7 +503,8 @@ private struct StoredConnection: Codable {
487503
isReadOnly: isReadOnly,
488504
aiPolicy: parsedAIPolicy,
489505
mssqlSchema: mssqlSchema,
490-
oracleServiceName: oracleServiceName
506+
oracleServiceName: oracleServiceName,
507+
startupCommands: startupCommands
491508
)
492509
}
493510
}

TablePro/Models/Connection/DatabaseConnection.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ struct DatabaseConnection: Identifiable, Hashable {
408408
var redisDatabase: Int?
409409
var mssqlSchema: String?
410410
var oracleServiceName: String?
411+
var startupCommands: String?
411412

412413
init(
413414
id: UUID = UUID(),
@@ -428,7 +429,8 @@ struct DatabaseConnection: Identifiable, Hashable {
428429
mongoWriteConcern: String? = nil,
429430
redisDatabase: Int? = nil,
430431
mssqlSchema: String? = nil,
431-
oracleServiceName: String? = nil
432+
oracleServiceName: String? = nil,
433+
startupCommands: String? = nil
432434
) {
433435
self.id = id
434436
self.name = name
@@ -449,6 +451,7 @@ struct DatabaseConnection: Identifiable, Hashable {
449451
self.redisDatabase = redisDatabase
450452
self.mssqlSchema = mssqlSchema
451453
self.oracleServiceName = oracleServiceName
454+
self.startupCommands = startupCommands
452455
}
453456

454457
/// Returns the display color (custom color or database type color)

TablePro/Resources/Localizable.xcstrings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8586,6 +8586,9 @@
85868586
}
85878587
}
85888588
}
8589+
},
8590+
"SQL commands to run after connecting, e.g. SET time_zone = 'Asia/Ho_Chi_Minh'. One per line or separated by semicolons." : {
8591+
85898592
},
85908593
"SQL Dialect" : {
85918594

@@ -8781,6 +8784,9 @@
87818784
}
87828785
}
87838786
}
8787+
},
8788+
"Startup Commands" : {
8789+
87848790
},
87858791
"statement" : {
87868792
"localizations" : {

0 commit comments

Comments
 (0)