66//
77
88import Foundation
9- import TableProPluginKit
109
1110extension MainContentCoordinator {
1211 // MARK: - Sidebar Save
@@ -17,129 +16,39 @@ extension MainContentCoordinator {
1716 ) async throws {
1817 guard let tab = tabManager. selectedTab,
1918 !selectedRowIndices. isEmpty,
20- let tableName = tab . tableName
19+ tab . tableName != nil
2120 else {
2221 return
2322 }
2423
2524 let editedFields = editState. getEditedFields ( )
2625 guard !editedFields. isEmpty else { return }
2726
28- if connection. type == . redis {
29- var redisStatements : [ ParameterizedStatement ] = [ ]
30- for rowIndex in selectedRowIndices. sorted ( ) {
31- guard rowIndex < tab. resultRows. count else { continue }
32- let row = tab. resultRows [ rowIndex]
33- let commands = generateSidebarRedisCommands (
34- originalRow: row. values,
35- editedFields: editedFields,
36- columns: tab. resultColumns
37- )
38- redisStatements += commands. map { ParameterizedStatement ( sql: $0, parameters: [ ] ) }
39- }
40- guard !redisStatements. isEmpty else { return }
41- try await executeSidebarChanges ( statements: redisStatements)
42- } else {
43- let generator = SQLStatementGenerator (
44- tableName: tableName,
45- columns: tab. resultColumns,
46- primaryKeyColumn: changeManager. primaryKeyColumn,
47- databaseType: connection. type,
48- quoteIdentifier: changeManager. pluginDriver? . quoteIdentifier
49- )
50-
51- var statements : [ ParameterizedStatement ] = [ ]
52- for rowIndex in selectedRowIndices. sorted ( ) {
53- guard rowIndex < tab. resultRows. count else { continue }
54- let originalRow = tab. resultRows [ rowIndex] . values
55-
56- let cellChanges = editedFields. map { field in
27+ // Build RowChange array from sidebar edits
28+ let changes : [ RowChange ] = selectedRowIndices. sorted ( ) . compactMap { rowIndex in
29+ guard rowIndex < tab. resultRows. count else { return nil }
30+ let originalRow = tab. resultRows [ rowIndex] . values
31+ return RowChange (
32+ rowIndex: rowIndex,
33+ type: . update,
34+ cellChanges: editedFields. map { field in
5735 CellChange (
5836 rowIndex: rowIndex,
5937 columnIndex: field. columnIndex,
6038 columnName: field. columnName,
6139 oldValue: originalRow [ field. columnIndex] ,
6240 newValue: field. newValue
6341 )
64- }
65- let change = RowChange (
66- rowIndex: rowIndex,
67- type: . update,
68- cellChanges: cellChanges,
69- originalRow: originalRow
70- )
71-
72- if let stmt = generator. generateUpdateSQL ( for: change) {
73- statements. append ( stmt)
74- }
75- }
76- guard !statements. isEmpty else { return }
77- try await executeSidebarChanges ( statements: statements)
78- }
79-
80- runQuery ( )
81- }
82-
83- private func generateSidebarRedisCommands(
84- originalRow: [ String ? ] ,
85- editedFields: [ ( columnIndex: Int , columnName: String , newValue: String ? ) ] ,
86- columns: [ String ]
87- ) -> [ String ] {
88- guard let keyIndex = columns. firstIndex ( of: " Key " ) ,
89- keyIndex < originalRow. count,
90- let originalKey = originalRow [ keyIndex]
91- else {
92- return [ ]
93- }
94-
95- var commands : [ String ] = [ ]
96- var effectiveKey = originalKey
97-
98- for field in editedFields {
99- switch field. columnName {
100- case " Key " :
101- if let newKey = field. newValue, newKey != originalKey {
102- commands. append ( " RENAME \( redisEscape ( originalKey) ) \( redisEscape ( newKey) ) " )
103- effectiveKey = newKey
104- }
105- case " Value " :
106- if let newValue = field. newValue {
107- // Only use SET for string-type keys — other types need specific commands
108- let typeIndex = columns. firstIndex ( of: " Type " )
109- let keyType = typeIndex. flatMap {
110- $0 < originalRow. count ? originalRow [ $0] ? . uppercased ( ) : nil
111- }
112- if keyType == nil || keyType == " STRING " || keyType == " NONE " {
113- commands. append ( " SET \( redisEscape ( effectiveKey) ) \( redisEscape ( newValue) ) " )
114- }
115- // Non-string types: skip (editing Value for complex types not supported via sidebar)
116- }
117- case " TTL " :
118- if let ttlStr = field. newValue, let ttl = Int ( ttlStr) , ttl >= 0 {
119- commands. append ( " EXPIRE \( redisEscape ( effectiveKey) ) \( ttl) " )
120- } else if field. newValue == nil || field. newValue == " -1 " {
121- commands. append ( " PERSIST \( redisEscape ( effectiveKey) ) " )
122- }
123- default :
124- break
125- }
42+ } ,
43+ originalRow: originalRow
44+ )
12645 }
12746
128- return commands
129- }
47+ // Route through the unified statement generation pipeline
48+ let statements = try changeManager. generateSQL ( for: changes)
49+ guard !statements. isEmpty else { return }
50+ try await executeSidebarChanges ( statements: statements)
13051
131- private func redisEscape( _ value: String ) -> String {
132- let needsQuoting =
133- value. isEmpty || value. contains ( where: { $0. isWhitespace || $0 == " \" " || $0 == " ' " } )
134- if needsQuoting {
135- let escaped =
136- value
137- . replacingOccurrences ( of: " \\ " , with: " \\ \\ " )
138- . replacingOccurrences ( of: " \" " , with: " \\ \" " )
139- . replacingOccurrences ( of: " \n " , with: " \\ n " )
140- . replacingOccurrences ( of: " \r " , with: " \\ r " )
141- return " \" \( escaped) \" "
142- }
143- return value
52+ runQuery ( )
14453 }
14554}
0 commit comments