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 .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@
*.yaml text eol=lf
*.plist text eol=lf

# Vendored C headers (database driver bridges, tree-sitter grammars)
Plugins/*/C*/include/** linguist-vendored
TablePro/Core/SSH/CLibSSH2/** linguist-vendored
LocalPackages/CodeEditLanguages/Sources/TreeSitterGrammars/** linguist-vendored

.github/workflows/*.lock.yml linguist-generated=true merge=ours
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Drag to reorder columns in the Structure tab (MySQL/MariaDB)
- Nested hierarchical groups for connection list (up to 3 levels deep)
- Confirmation dialogs for deep link queries, connection imports, and pre-connect scripts
- JSON fields in Row Details sidebar now display in a scrollable monospaced text area
Expand Down
49 changes: 49 additions & 0 deletions Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,55 @@ final class MySQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
"EXPLAIN \(sql)"
}

// MARK: - Column Reorder DDL

func generateMoveColumnSQL(table: String, column: PluginColumnDefinition, afterColumn: String?) -> String? {
let tableName = quoteIdentifier(table)
let colName = quoteIdentifier(column.name)

var def = "\(column.dataType)"
if column.unsigned {
def += " UNSIGNED"
}
if column.isNullable {
def += " NULL"
} else {
def += " NOT NULL"
}
if let defaultValue = column.defaultValue {
let upper = defaultValue.uppercased()
if upper == "NULL" || upper == "CURRENT_TIMESTAMP" || upper == "CURRENT_TIMESTAMP()"
|| defaultValue.hasPrefix("'") {
def += " DEFAULT \(defaultValue)"
} else if Int64(defaultValue) != nil || Double(defaultValue) != nil {
def += " DEFAULT \(defaultValue)"
} else {
def += " DEFAULT '\(escapeStringLiteral(defaultValue))'"
}
}
if column.autoIncrement {
def += " AUTO_INCREMENT"
}
if let onUpdate = column.onUpdate, !onUpdate.isEmpty {
let upper = onUpdate.uppercased()
if upper == "CURRENT_TIMESTAMP" || upper == "CURRENT_TIMESTAMP()" || upper.hasPrefix("CURRENT_TIMESTAMP(") {
def += " ON UPDATE \(onUpdate)"
}
}
if let comment = column.comment, !comment.isEmpty {
def += " COMMENT '\(escapeStringLiteral(comment))'"
}

let position: String
if let afterCol = afterColumn {
position = "AFTER \(quoteIdentifier(afterCol))"
} else {
position = "FIRST"
}

return "ALTER TABLE \(tableName) MODIFY COLUMN \(colName) \(def) \(position)"
}

// MARK: - View Templates

func createViewTemplate() -> String? {
Expand Down
2 changes: 2 additions & 0 deletions Plugins/TableProPluginKit/PluginDatabaseDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable {
func generateAddForeignKeySQL(table: String, fk: PluginForeignKeyDefinition) -> String?
func generateDropForeignKeySQL(table: String, constraintName: String) -> String?
func generateModifyPrimaryKeySQL(table: String, oldColumns: [String], newColumns: [String], constraintName: String?) -> [String]?
func generateMoveColumnSQL(table: String, column: PluginColumnDefinition, afterColumn: String?) -> String?

// Table operations (optional — return nil to use app-level fallback)
func truncateTableStatements(table: String, schema: String?, cascade: Bool) -> [String]?
Expand Down Expand Up @@ -230,6 +231,7 @@ public extension PluginDatabaseDriver {
func generateAddForeignKeySQL(table: String, fk: PluginForeignKeyDefinition) -> String? { nil }
func generateDropForeignKeySQL(table: String, constraintName: String) -> String? { nil }
func generateModifyPrimaryKeySQL(table: String, oldColumns: [String], newColumns: [String], constraintName: String?) -> [String]? { nil }
func generateMoveColumnSQL(table: String, column: PluginColumnDefinition, afterColumn: String?) -> String? { nil }

func truncateTableStatements(table: String, schema: String?, cascade: Bool) -> [String]? { nil }
func dropObjectStatement(name: String, objectType: String, schema: String?, cascade: Bool) -> String? { nil }
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 @@ -334,6 +334,10 @@ final class PluginDriverAdapter: DatabaseDriver, SchemaSwitchable {
pluginDriver.generateModifyPrimaryKeySQL(table: table, oldColumns: oldColumns, newColumns: newColumns, constraintName: constraintName)
}

func generateMoveColumnSQL(table: String, column: PluginColumnDefinition, afterColumn: String?) -> String? {
pluginDriver.generateMoveColumnSQL(table: table, column: column, afterColumn: afterColumn)
}

// MARK: - Table Operations

func truncateTableStatements(table: String, schema: String?, cascade: Bool) -> [String] {
Expand Down
5 changes: 5 additions & 0 deletions TablePro/Core/Plugins/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,11 @@ final class PluginManager {
.capabilities.supportsSSL ?? true
}

func supportsColumnReorder(for databaseType: DatabaseType) -> Bool {
PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)?
.supportsColumnReorder ?? 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 @@ -516,6 +516,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#00ED63",
queryLanguageName: "MQL", editorLanguage: .javascript,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: true,
Expand Down Expand Up @@ -586,6 +587,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#DC382D",
queryLanguageName: "Redis CLI", editorLanguage: .bash,
connectionMode: .network, supportsDatabaseSwitching: false,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: false,
Expand Down Expand Up @@ -636,6 +638,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#E34517",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: .defaults,
schema: PluginMetadataSnapshot.SchemaInfo(
defaultSchemaName: "dbo",
Expand Down Expand Up @@ -671,6 +674,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#C3160B",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: true,
Expand Down Expand Up @@ -726,6 +730,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#FFD100",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: true,
Expand Down Expand Up @@ -766,6 +771,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#FFD900",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .fileBased, supportsDatabaseSwitching: false,
supportsColumnReorder: false,
capabilities: .defaults,
schema: PluginMetadataSnapshot.SchemaInfo(
defaultSchemaName: "public",
Expand Down Expand Up @@ -796,6 +802,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#26A0D8",
queryLanguageName: "CQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: false,
Expand Down Expand Up @@ -849,6 +856,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#6B2EE3",
queryLanguageName: "CQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: false,
Expand Down Expand Up @@ -901,6 +909,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#419EDA",
queryLanguageName: "etcdctl", editorLanguage: .bash,
connectionMode: .network, supportsDatabaseSwitching: false,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: false,
Expand Down Expand Up @@ -982,6 +991,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#F6821F",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .apiOnly, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: false,
Expand Down Expand Up @@ -1033,6 +1043,7 @@ extension PluginMetadataRegistry {
brandColorHex: "#4053D6",
queryLanguageName: "PartiQL", editorLanguage: .sql,
connectionMode: .apiOnly, supportsDatabaseSwitching: false,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: false,
Expand Down
13 changes: 13 additions & 0 deletions TablePro/Core/Plugins/PluginMetadataRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct PluginMetadataSnapshot: Sendable {
let editorLanguage: EditorLanguage
let connectionMode: ConnectionMode
let supportsDatabaseSwitching: Bool
let supportsColumnReorder: Bool

let capabilities: CapabilityFlags
let schema: SchemaInfo
Expand Down Expand Up @@ -130,6 +131,7 @@ struct PluginMetadataSnapshot: Sendable {
brandColorHex: brandColorHex, queryLanguageName: queryLanguageName,
editorLanguage: editorLanguage, connectionMode: connectionMode,
supportsDatabaseSwitching: supportsDatabaseSwitching,
supportsColumnReorder: supportsColumnReorder,
capabilities: capabilities, schema: schema, editor: editor, connection: connection
)
}
Expand Down Expand Up @@ -340,6 +342,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
brandColorHex: "#FF9500",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: true,
capabilities: .defaults,
schema: PluginMetadataSnapshot.SchemaInfo(
defaultSchemaName: "public",
Expand Down Expand Up @@ -369,6 +372,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
brandColorHex: "#00B4D8",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: true,
capabilities: .defaults,
schema: PluginMetadataSnapshot.SchemaInfo(
defaultSchemaName: "public",
Expand Down Expand Up @@ -398,6 +402,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
brandColorHex: "#336791",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: true,
supportsImport: true,
Expand Down Expand Up @@ -440,6 +445,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
brandColorHex: "#205B8E",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .network, supportsDatabaseSwitching: true,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: true,
supportsImport: true,
Expand Down Expand Up @@ -482,6 +488,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
brandColorHex: "#003B57",
queryLanguageName: "SQL", editorLanguage: .sql,
connectionMode: .fileBased, supportsDatabaseSwitching: false,
supportsColumnReorder: false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: false,
supportsImport: true,
Expand Down Expand Up @@ -614,6 +621,11 @@ final class PluginMetadataRegistry: @unchecked Sendable {
let schemes = driverType.urlSchemes
let primaryScheme = schemes.first ?? driverType.databaseTypeId.lowercased()

// Preserve supportsColumnReorder from existing built-in snapshot.
// Cannot read from driverType directly — stale plugins without the
// property crash with EXC_BAD_INSTRUCTION (missing witness table entry).
let existingSnapshot = snapshot(forTypeId: driverType.databaseTypeId)

return PluginMetadataSnapshot(
displayName: driverType.databaseDisplayName,
iconName: driverType.iconName,
Expand All @@ -635,6 +647,7 @@ final class PluginMetadataRegistry: @unchecked Sendable {
editorLanguage: driverType.editorLanguage,
connectionMode: driverType.connectionMode,
supportsDatabaseSwitching: driverType.supportsDatabaseSwitching,
supportsColumnReorder: existingSnapshot?.supportsColumnReorder ?? false,
capabilities: PluginMetadataSnapshot.CapabilityFlags(
supportsSchemaSwitching: driverType.supportsSchemaSwitching,
supportsImport: driverType.supportsImport,
Expand Down
15 changes: 15 additions & 0 deletions TablePro/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -18472,6 +18472,9 @@
}
}
}
},
"Move Group to..." : {

},
"Move to" : {
"localizations" : {
Expand Down Expand Up @@ -19190,6 +19193,9 @@
}
}
}
},
"New Subgroup" : {

},
"New Tab" : {
"localizations" : {
Expand Down Expand Up @@ -20612,6 +20618,9 @@
}
}
}
},
"None (Top Level)" : {

},
"Normal" : {
"localizations" : {
Expand Down Expand Up @@ -21844,6 +21853,9 @@
}
}
}
},
"Parent Group" : {

},
"Partition" : {
"localizations" : {
Expand Down Expand Up @@ -31756,6 +31768,9 @@
}
}
}
},
"Top Level" : {

},
"Total Size" : {
"localizations" : {
Expand Down
44 changes: 44 additions & 0 deletions TablePro/Views/Results/DataGridView+RowActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,48 @@ extension TableViewCoordinator {
guard let connectionId else { return nil }
return DatabaseManager.shared.driver(for: connectionId)
}

// MARK: - Row Drag and Drop

private static let rowDragType = NSPasteboard.PasteboardType("com.TablePro.rowDrag")

func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> (any NSPasteboardWriting)? {
guard onMoveRow != nil else { return nil }
let item = NSPasteboardItem()
item.setString(String(row), forType: Self.rowDragType)
return item
}

func tableView(
_ tableView: NSTableView,
validateDrop info: any NSDraggingInfo,
proposedRow row: Int,
proposedDropOperation dropOperation: NSTableView.DropOperation
) -> NSDragOperation {
guard onMoveRow != nil else { return [] }
guard info.draggingSource as? NSTableView === tableView else { return [] }
guard info.draggingPasteboard.availableType(from: [Self.rowDragType]) != nil else { return [] }
guard dropOperation == .above else {
tableView.setDropRow(row, dropOperation: .above)
return .move
}
return .move
}

func tableView(
_ tableView: NSTableView,
acceptDrop info: any NSDraggingInfo,
row: Int,
dropOperation: NSTableView.DropOperation
) -> Bool {
guard let onMoveRow else { return false }
guard let item = info.draggingPasteboard.pasteboardItems?.first,
let rowString = item.string(forType: Self.rowDragType),
let fromRow = Int(rowString) else {
return false
}
guard fromRow != row && fromRow != row - 1 else { return false }
onMoveRow(fromRow, row)
return true
}
}
Loading
Loading