Skip to content

Commit 207748d

Browse files
committed
fix: resolve SwiftLint violations for CI
- Extract navigation methods into MainContentCoordinator+Navigation extension - Fix force unwrapping in XLSXWriter with safe optional binding - Fix unused_enumerated, unused_optional_binding in XLSXWriter - Add number separators in DatabaseDriver and SQLiteDriver - Remove unnecessary parentheses in DatePickerCellEditor - Fix trailing newline in JSONEditorPopoverController
1 parent 99246eb commit 207748d

7 files changed

Lines changed: 212 additions & 199 deletions

File tree

TablePro/Core/Database/DatabaseDriver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ extension DatabaseDriver {
114114
/// Default timeout implementation using database-specific session variables
115115
func applyQueryTimeout(_ seconds: Int) async throws {
116116
guard seconds > 0 else { return }
117-
let ms = seconds * 1000
117+
let ms = seconds * 1_000
118118
switch connection.type {
119119
case .mysql, .mariadb:
120120
_ = try await execute(query: "SET SESSION max_execution_time = \(ms)")

TablePro/Core/Database/SQLiteDriver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ final class SQLiteDriver: DatabaseDriver {
6060

6161
func applyQueryTimeout(_ seconds: Int) async throws {
6262
guard seconds > 0, let db = db else { return }
63-
sqlite3_busy_timeout(db, Int32(seconds * 1000))
63+
sqlite3_busy_timeout(db, Int32(seconds * 1_000))
6464
}
6565

6666
func disconnect() {

TablePro/Core/Services/XLSXWriter.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ final class XLSXWriter {
5151
return .empty
5252
}
5353
// Try to detect numeric values
54-
if let _ = Double(val), !val.hasPrefix("0") || val == "0" || val.contains(".") {
54+
if Double(val) != nil, !val.hasPrefix("0") || val == "0" || val.contains(".") {
5555
return .number(val)
5656
}
5757
return .string(val)
@@ -134,7 +134,7 @@ final class XLSXWriter {
134134
if !sharedStrings.isEmpty {
135135
d.appendUTF8("<Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\"/>")
136136
}
137-
for (index, _) in sheets.enumerated() {
137+
for index in sheets.indices {
138138
d.appendUTF8("<Override PartName=\"/xl/worksheets/sheet\(index + 1).xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>")
139139
}
140140
d.appendUTF8("</Types>")
@@ -168,7 +168,7 @@ final class XLSXWriter {
168168
var d = Data()
169169
d.appendUTF8("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n")
170170
d.appendUTF8("<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">")
171-
for (index, _) in sheets.enumerated() {
171+
for index in sheets.indices {
172172
d.appendUTF8("<Relationship Id=\"rId\(index + 1)\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet\(index + 1).xml\"/>")
173173
}
174174
let nextId = sheets.count + 1
@@ -257,7 +257,9 @@ final class XLSXWriter {
257257
var result = ""
258258
var n = index
259259
repeat {
260-
result = String(UnicodeScalar(65 + (n % 26))!) + result
260+
if let scalar = UnicodeScalar(65 + (n % 26)) {
261+
result = String(scalar) + result
262+
}
261263
n = n / 26 - 1
262264
} while n >= 0
263265
return result
@@ -283,7 +285,9 @@ private extension Data {
283285
/// Append a UTF-8 string directly to Data (O(1) amortized, no intermediate String copies)
284286
mutating func appendUTF8(_ string: String) {
285287
string.utf8.withContiguousStorageIfAvailable { buffer in
286-
self.append(buffer.baseAddress!, count: buffer.count)
288+
if let baseAddress = buffer.baseAddress {
289+
self.append(baseAddress, count: buffer.count)
290+
}
287291
} ?? self.append(contentsOf: string.utf8)
288292
}
289293

@@ -368,7 +372,7 @@ private enum ZipBuilder {
368372
centralDirectory.appendUInt16(0)
369373
centralDirectory.appendUInt16(0)
370374
centralDirectory.appendUInt32(0)
371-
centralDirectory.appendUInt32(offsets.last!)
375+
centralDirectory.appendUInt32(offsets.last ?? 0)
372376
centralDirectory.append(pathData)
373377
}
374378

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//
2+
// MainContentCoordinator+Navigation.swift
3+
// TablePro
4+
//
5+
// Table tab opening and database switching operations for MainContentCoordinator
6+
//
7+
8+
import Foundation
9+
10+
extension MainContentCoordinator {
11+
// MARK: - Table Tab Opening
12+
13+
func openTableTab(_ tableName: String, showStructure: Bool = false, isView: Bool = false) {
14+
let needsQuery = tabManager.TableProTabSmart(
15+
tableName: tableName,
16+
hasUnsavedChanges: changeManager.hasChanges,
17+
databaseType: connection.type,
18+
isView: isView
19+
)
20+
21+
// Initialize pagination for new table tab
22+
if needsQuery, let tabIndex = tabManager.selectedTabIndex {
23+
tabManager.tabs[tabIndex].pagination.reset()
24+
}
25+
26+
// Update editable state for menu items (tab switch handler may not fire on reuse path)
27+
if let tabIndex = tabManager.selectedTabIndex {
28+
let tab = tabManager.tabs[tabIndex]
29+
AppState.shared.isCurrentTabEditable = tab.isEditable && !tab.isView && tab.tableName != nil
30+
}
31+
32+
// Toggle structure view if requested
33+
if showStructure, let tabIndex = tabManager.selectedTabIndex {
34+
tabManager.tabs[tabIndex].showStructure = true
35+
}
36+
37+
if needsQuery {
38+
Task { @MainActor in
39+
runQuery()
40+
}
41+
}
42+
}
43+
44+
func showAllTablesMetadata() {
45+
let sql: String
46+
switch connection.type {
47+
case .postgresql:
48+
sql = """
49+
SELECT
50+
schemaname as schema,
51+
relname as name,
52+
'TABLE' as kind,
53+
n_live_tup as estimated_rows,
54+
pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) as total_size,
55+
pg_size_pretty(pg_relation_size(schemaname||'.'||relname)) as data_size,
56+
pg_size_pretty(pg_indexes_size(schemaname||'.'||relname)) as index_size,
57+
obj_description((schemaname||'.'||relname)::regclass) as comment
58+
FROM pg_stat_user_tables
59+
WHERE schemaname = 'public'
60+
ORDER BY relname
61+
"""
62+
case .mysql, .mariadb:
63+
sql = """
64+
SELECT
65+
TABLE_SCHEMA as `schema`,
66+
TABLE_NAME as name,
67+
TABLE_TYPE as kind,
68+
IFNULL(CCSA.CHARACTER_SET_NAME, '') as charset,
69+
TABLE_COLLATION as collation,
70+
TABLE_ROWS as estimated_rows,
71+
CONCAT(ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2), ' MB') as total_size,
72+
CONCAT(ROUND(DATA_LENGTH / 1024 / 1024, 2), ' MB') as data_size,
73+
CONCAT(ROUND(INDEX_LENGTH / 1024 / 1024, 2), ' MB') as index_size,
74+
TABLE_COMMENT as comment
75+
FROM information_schema.TABLES
76+
LEFT JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY CCSA
77+
ON TABLE_COLLATION = CCSA.COLLATION_NAME
78+
WHERE TABLE_SCHEMA = DATABASE()
79+
ORDER BY TABLE_NAME
80+
"""
81+
case .sqlite:
82+
sql = """
83+
SELECT
84+
'' as schema,
85+
name,
86+
type as kind,
87+
'' as charset,
88+
'' as collation,
89+
'' as estimated_rows,
90+
'' as total_size,
91+
'' as data_size,
92+
'' as index_size,
93+
'' as comment
94+
FROM sqlite_master
95+
WHERE type IN ('table', 'view')
96+
AND name NOT LIKE 'sqlite_%'
97+
ORDER BY name
98+
"""
99+
}
100+
101+
if let existingTab = tabManager.tabs.first(where: { $0.title == "Tables" }) {
102+
if let index = tabManager.tabs.firstIndex(where: { $0.id == existingTab.id }) {
103+
tabManager.tabs[index].query = sql
104+
}
105+
tabManager.selectedTabId = existingTab.id
106+
runQuery()
107+
return
108+
}
109+
110+
let newTab = QueryTab(
111+
title: "Tables",
112+
query: sql,
113+
tabType: .table,
114+
tableName: nil
115+
)
116+
tabManager.tabs.append(newTab)
117+
tabManager.selectedTabId = newTab.id
118+
runQuery()
119+
}
120+
121+
// MARK: - Database Switching
122+
123+
/// Switch to a different database (called from database switcher)
124+
func switchDatabase(to database: String) async {
125+
guard let driver = DatabaseManager.shared.activeDriver else {
126+
return
127+
}
128+
129+
do {
130+
// For MySQL/MariaDB, use USE command
131+
if connection.type == .mysql || connection.type == .mariadb {
132+
_ = try await driver.execute(query: "USE `\(database)`")
133+
134+
// Update session with new database
135+
if let sessionId = DatabaseManager.shared.currentSessionId {
136+
DatabaseManager.shared.updateSession(sessionId) { session in
137+
var updatedConnection = session.connection
138+
updatedConnection.database = database
139+
session.connection = updatedConnection
140+
}
141+
}
142+
143+
// Update toolbar state
144+
toolbarState.databaseName = database
145+
146+
// Clear tab results but keep tabs open
147+
tabManager.tabs = tabManager.tabs.map { tab in
148+
var updatedTab = tab
149+
updatedTab.resultColumns = []
150+
updatedTab.resultRows = []
151+
updatedTab.resultVersion += 1
152+
updatedTab.errorMessage = nil
153+
updatedTab.executionTime = nil
154+
return updatedTab
155+
}
156+
157+
// Reload schema for autocomplete
158+
await loadSchema()
159+
160+
// Refresh tables list in sidebar
161+
NotificationCenter.default.post(name: .refreshAll, object: nil)
162+
163+
// Re-execute current tab's query if it's a table tab
164+
if let currentTab = tabManager.selectedTab, currentTab.tabType == .table {
165+
runQuery()
166+
}
167+
} else {
168+
// For PostgreSQL and SQLite, reconnect with new database
169+
// (SQLite doesn't apply, but keeping for completeness)
170+
}
171+
} catch {
172+
}
173+
}
174+
175+
/// Switch to a different database (legacy method - creates new connection)
176+
func switchToDatabase(_ database: String) {
177+
let newConnection = DatabaseConnection(
178+
id: UUID(),
179+
name: connection.name,
180+
host: connection.host,
181+
port: connection.port,
182+
database: database,
183+
username: connection.username,
184+
type: connection.type,
185+
sshConfig: connection.sshConfig,
186+
color: connection.color,
187+
tagId: connection.tagId
188+
)
189+
190+
Task {
191+
do {
192+
try await DatabaseManager.shared.connectToSession(newConnection)
193+
} catch {
194+
await MainActor.run {
195+
}
196+
}
197+
}
198+
}
199+
}

0 commit comments

Comments
 (0)