@@ -201,5 +201,73 @@ final class QueryViewModelTests: XCTestCase {
201201 vm.setDefaultQuery(stream: "stream\"name")
202202 XCTAssertTrue(vm.sqlQuery.contains("\"stream\"\"name\""))
203203 }
204+
205+ // MARK: - Column filter
206+
207+ func testAddColumnFilterIncludeStringValue() {
208+ let vm = QueryViewModel()
209+ vm.sqlQuery = "SELECT * FROM \"logs\" ORDER BY p_timestamp DESC LIMIT 1000"
210+ vm.addColumnFilter(column: "level", value: .string("error"), exclude: false)
211+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"level\" = 'error'"))
212+ XCTAssertTrue(vm.sqlQuery.contains("ORDER BY"))
213+ }
214+
215+ func testAddColumnFilterExcludeStringValue() {
216+ let vm = QueryViewModel()
217+ vm.sqlQuery = "SELECT * FROM \"logs\" ORDER BY p_timestamp DESC LIMIT 1000"
218+ vm.addColumnFilter(column: "level", value: .string("debug"), exclude: true)
219+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"level\" <> 'debug'"))
220+ XCTAssertTrue(vm.sqlQuery.contains("ORDER BY"))
221+ }
222+
223+ func testAddColumnFilterNullValue() {
224+ let vm = QueryViewModel()
225+ vm.sqlQuery = "SELECT * FROM \"logs\" ORDER BY p_timestamp DESC"
226+ vm.addColumnFilter(column: "tag", value: nil, exclude: false)
227+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"tag\" IS NULL"))
228+ }
229+
230+ func testAddColumnFilterExcludeNullValue() {
231+ let vm = QueryViewModel()
232+ vm.sqlQuery = "SELECT * FROM \"logs\" ORDER BY p_timestamp DESC"
233+ vm.addColumnFilter(column: "tag", value: .null, exclude: true)
234+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"tag\" IS NOT NULL"))
235+ }
236+
237+ func testAddColumnFilterAppendsToExistingWhere() {
238+ let vm = QueryViewModel()
239+ vm.sqlQuery = "SELECT * FROM \"logs\" WHERE \"level\" = 'error' ORDER BY p_timestamp DESC"
240+ vm.addColumnFilter(column: "host", value: .string("web-1"), exclude: false)
241+ XCTAssertTrue(vm.sqlQuery.contains("AND \"host\" = 'web-1'"))
242+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"level\" = 'error'"))
243+ }
244+
245+ func testAddColumnFilterIntValue() {
246+ let vm = QueryViewModel()
247+ vm.sqlQuery = "SELECT * FROM \"logs\""
248+ vm.addColumnFilter(column: "status", value: .int(200), exclude: false)
249+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"status\" = 200"))
250+ }
251+
252+ func testAddColumnFilterNoOpOnEmptyQuery() {
253+ let vm = QueryViewModel()
254+ vm.sqlQuery = ""
255+ vm.addColumnFilter(column: "level", value: .string("info"), exclude: false)
256+ XCTAssertEqual(vm.sqlQuery, "")
257+ }
258+
259+ func testAddColumnFilterQueryWithoutOrderBy() {
260+ let vm = QueryViewModel()
261+ vm.sqlQuery = "SELECT * FROM \"logs\""
262+ vm.addColumnFilter(column: "level", value: .string("warn"), exclude: true)
263+ XCTAssertTrue(vm.sqlQuery.contains("WHERE \"level\" <> 'warn'"))
264+ }
265+
266+ func testAddColumnFilterEscapesSingleQuotesInValue() {
267+ let vm = QueryViewModel()
268+ vm.sqlQuery = "SELECT * FROM \"logs\""
269+ vm.addColumnFilter(column: "msg", value: .string("it's a test"), exclude: false)
270+ XCTAssertTrue(vm.sqlQuery.contains("'it''s a test'"))
271+ }
204272}
205273