Skip to content

Commit fcc2d8a

Browse files
committed
fix: filter disabled rows, cast PostgreSQL quick search to text, add tests
1 parent 610ea51 commit fcc2d8a

4 files changed

Lines changed: 193 additions & 5 deletions

File tree

TablePro/Core/Database/FilterSQLGenerator.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@ struct FilterSQLGenerator {
5252
let pattern = "%\(escapedValue)%"
5353
let quotedPattern = escapeSQLQuote(pattern)
5454
let escape = likeEscapeClause
55+
// CAST to TEXT for databases like PostgreSQL where LIKE on non-text columns fails
56+
let needsCast = dialect.regexSyntax == .tilde
5557
let conditions = columns.map { column in
5658
let quoted = quoteIdentifierFn(column)
57-
return "\(quoted) LIKE '\(quotedPattern)'\(escape)"
59+
let target = needsCast ? "CAST(\(quoted) AS TEXT)" : quoted
60+
return "\(target) LIKE '\(quotedPattern)'\(escape)"
5861
}
5962
return conditions.joined(separator: " OR ")
6063
}

TablePro/Core/Services/Query/TableQueryBuilder.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ struct TableQueryBuilder {
103103
var query = "SELECT * FROM \(quotedTable)"
104104

105105
if let dialect {
106+
let activeFilters = filters.filter { $0.isEnabled }
106107
let filterGen = FilterSQLGenerator(dialect: dialect, quoteIdentifier: dialectQuote)
107-
let whereClause = filterGen.generateWhereClause(from: filters, logicMode: logicMode)
108+
let whereClause = filterGen.generateWhereClause(from: activeFilters, logicMode: logicMode)
108109
if !whereClause.isEmpty {
109110
query += " \(whereClause)"
110111
}
@@ -185,10 +186,11 @@ struct TableQueryBuilder {
185186
var query = "SELECT * FROM \(quotedTable)"
186187

187188
if let dialect {
189+
let activeFilters = filters.filter { $0.isEnabled }
188190
let filterGen = FilterSQLGenerator(dialect: dialect, quoteIdentifier: dialectQuote)
189191
var whereParts: [String] = []
190192

191-
let filterConditions = filterGen.generateConditions(from: filters, logicMode: logicMode)
193+
let filterConditions = filterGen.generateConditions(from: activeFilters, logicMode: logicMode)
192194
if !filterConditions.isEmpty {
193195
whereParts.append("(\(filterConditions))")
194196
}

TablePro/Views/Main/Extensions/MainContentCoordinator+Pagination.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ extension MainContentCoordinator {
102102

103103
private func reloadCurrentPage() {
104104
guard let tabIndex = tabManager.selectedTabIndex,
105-
tabIndex < tabManager.tabs.count,
106-
tabManager.tabs[tabIndex].tableName != nil else { return }
105+
tabIndex < tabManager.tabs.count else { return }
107106

108107
rebuildTableQuery(at: tabIndex)
109108
runQuery()
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//
2+
// TableQueryBuilderFilterTests.swift
3+
// TableProTests
4+
//
5+
// Tests for TableQueryBuilder WHERE clause generation in fallback paths.
6+
//
7+
8+
import Foundation
9+
@testable import TablePro
10+
import Testing
11+
12+
@Suite("Table Query Builder - Filtered Query Fallback")
13+
struct TableQueryBuilderFilteredQueryTests {
14+
private let builder = TableQueryBuilder(databaseType: .mysql)
15+
16+
@Test("buildFilteredQuery with enabled filter produces WHERE clause")
17+
func filteredQueryWithEnabledFilter() {
18+
var filter = TableFilter()
19+
filter.columnName = "name"
20+
filter.filterOperator = .equal
21+
filter.value = "Alice"
22+
filter.isEnabled = true
23+
24+
let query = builder.buildFilteredQuery(
25+
tableName: "users", filters: [filter]
26+
)
27+
#expect(query.contains("WHERE"))
28+
#expect(query.contains("name"))
29+
#expect(query.contains("Alice"))
30+
}
31+
32+
@Test("buildFilteredQuery excludes disabled filters")
33+
func filteredQueryExcludesDisabledFilter() {
34+
var enabledFilter = TableFilter()
35+
enabledFilter.columnName = "name"
36+
enabledFilter.filterOperator = .equal
37+
enabledFilter.value = "Alice"
38+
enabledFilter.isEnabled = true
39+
40+
var disabledFilter = TableFilter()
41+
disabledFilter.columnName = "age"
42+
disabledFilter.filterOperator = .equal
43+
disabledFilter.value = "30"
44+
disabledFilter.isEnabled = false
45+
46+
let query = builder.buildFilteredQuery(
47+
tableName: "users", filters: [enabledFilter, disabledFilter]
48+
)
49+
#expect(query.contains("name"))
50+
#expect(!query.contains("age"))
51+
}
52+
53+
@Test("buildFilteredQuery with no enabled filters produces no WHERE")
54+
func filteredQueryNoEnabledFilters() {
55+
var filter = TableFilter()
56+
filter.columnName = "name"
57+
filter.filterOperator = .equal
58+
filter.value = "Alice"
59+
filter.isEnabled = false
60+
61+
let query = builder.buildFilteredQuery(
62+
tableName: "users", filters: [filter]
63+
)
64+
#expect(!query.contains("WHERE"))
65+
}
66+
67+
@Test("buildFilteredQuery with empty filters produces no WHERE")
68+
func filteredQueryEmptyFilters() {
69+
let query = builder.buildFilteredQuery(
70+
tableName: "users", filters: []
71+
)
72+
#expect(!query.contains("WHERE"))
73+
#expect(query.contains("SELECT * FROM"))
74+
}
75+
}
76+
77+
@Suite("Table Query Builder - Quick Search Fallback")
78+
struct TableQueryBuilderQuickSearchTests {
79+
private let builder = TableQueryBuilder(databaseType: .mysql)
80+
81+
@Test("buildQuickSearchQuery produces OR-joined LIKE conditions")
82+
func quickSearchProducesLike() {
83+
let query = builder.buildQuickSearchQuery(
84+
tableName: "users", searchText: "alice",
85+
columns: ["name", "email"]
86+
)
87+
#expect(query.contains("WHERE"))
88+
#expect(query.contains("LIKE"))
89+
#expect(query.contains("alice"))
90+
}
91+
92+
@Test("buildQuickSearchQuery with empty search text produces no WHERE")
93+
func quickSearchEmptyText() {
94+
let query = builder.buildQuickSearchQuery(
95+
tableName: "users", searchText: "",
96+
columns: ["name", "email"]
97+
)
98+
#expect(!query.contains("WHERE"))
99+
}
100+
}
101+
102+
@Suite("Table Query Builder - Combined Query Fallback")
103+
struct TableQueryBuilderCombinedQueryTests {
104+
private let builder = TableQueryBuilder(databaseType: .mysql)
105+
106+
@Test("buildCombinedQuery with filter and search produces both in WHERE")
107+
func combinedQueryFilterAndSearch() {
108+
var filter = TableFilter()
109+
filter.columnName = "status"
110+
filter.filterOperator = .equal
111+
filter.value = "active"
112+
filter.isEnabled = true
113+
114+
let query = builder.buildCombinedQuery(
115+
tableName: "users", filters: [filter],
116+
searchText: "alice", searchColumns: ["name", "email"]
117+
)
118+
#expect(query.contains("WHERE"))
119+
#expect(query.contains("status"))
120+
#expect(query.contains("LIKE"))
121+
#expect(query.contains("AND"))
122+
}
123+
124+
@Test("buildCombinedQuery excludes disabled filters")
125+
func combinedQueryExcludesDisabledFilter() {
126+
var disabledFilter = TableFilter()
127+
disabledFilter.columnName = "age"
128+
disabledFilter.filterOperator = .equal
129+
disabledFilter.value = "30"
130+
disabledFilter.isEnabled = false
131+
132+
let query = builder.buildCombinedQuery(
133+
tableName: "users", filters: [disabledFilter],
134+
searchText: "alice", searchColumns: ["name"]
135+
)
136+
#expect(!query.contains("age"))
137+
#expect(query.contains("LIKE"))
138+
}
139+
}
140+
141+
@Suite("Table Query Builder - PostgreSQL Quick Search CAST")
142+
struct TableQueryBuilderPostgreSQLQuickSearchTests {
143+
private let builder = TableQueryBuilder(databaseType: .postgresql)
144+
145+
@Test("PostgreSQL quick search uses CAST for LIKE on non-text columns")
146+
func postgresQuickSearchCast() {
147+
let query = builder.buildQuickSearchQuery(
148+
tableName: "users", searchText: "test",
149+
columns: ["id", "name"]
150+
)
151+
#expect(query.contains("CAST("))
152+
#expect(query.contains("AS TEXT)"))
153+
#expect(query.contains("LIKE"))
154+
}
155+
}
156+
157+
@Suite("Table Query Builder - NoSQL Nil Dialect Fallback")
158+
struct TableQueryBuilderNoSQLTests {
159+
// MongoDB has no SQL dialect — should produce bare SELECT without WHERE
160+
private let builder = TableQueryBuilder(databaseType: .mongodb)
161+
162+
@Test("NoSQL type produces no WHERE for filtered query")
163+
func noSqlFilteredQueryNoWhere() {
164+
var filter = TableFilter()
165+
filter.columnName = "name"
166+
filter.filterOperator = .equal
167+
filter.value = "Alice"
168+
filter.isEnabled = true
169+
170+
let query = builder.buildFilteredQuery(
171+
tableName: "collection", filters: [filter]
172+
)
173+
#expect(!query.contains("WHERE"))
174+
}
175+
176+
@Test("NoSQL type produces no WHERE for quick search")
177+
func noSqlQuickSearchNoWhere() {
178+
let query = builder.buildQuickSearchQuery(
179+
tableName: "collection", searchText: "test",
180+
columns: ["field1"]
181+
)
182+
#expect(!query.contains("WHERE"))
183+
}
184+
}

0 commit comments

Comments
 (0)