diff --git a/Music/Services/DatabaseService.swift b/Music/Services/DatabaseService.swift index fc00464..f3cf6d0 100644 --- a/Music/Services/DatabaseService.swift +++ b/Music/Services/DatabaseService.swift @@ -499,8 +499,12 @@ nonisolated final class DatabaseService: Sendable { fragments.append("LOWER(\(col)) = LOWER(?)") args.append(s) case (.startsWith, .string(let s)): - fragments.append("LOWER(\(col)) LIKE LOWER(?) || '%'") - args.append(s) + let escaped = s + .replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "%", with: "\\%") + .replacingOccurrences(of: "_", with: "\\_") + fragments.append("LOWER(\(col)) LIKE LOWER(?) || '%' ESCAPE '\\'") + args.append(escaped) case (.equals, .int(let i)): fragments.append("\(col) = ?"); args.append(i) case (.greaterThan, .int(let i)): diff --git a/MusicTests/SmartPlaylistTests.swift b/MusicTests/SmartPlaylistTests.swift index 2155e0c..f5f63d3 100644 --- a/MusicTests/SmartPlaylistTests.swift +++ b/MusicTests/SmartPlaylistTests.swift @@ -248,6 +248,25 @@ struct SmartPlaylistTests { } } + // Verifies that lessThan on an integer field returns only tracks strictly + // below the threshold value. + @Test func fetchTracksWithLessThanCondition() throws { + // Step 1: Insert tracks with years 1990, 2010, 2020 + // Step 2: Fetch with year < 2000 + // Step 3: Verify only the 1990 track is returned + let db = try DatabaseService(inMemory: true) + var t1 = Track.fixture(fileURL: "/a.mp3", year: 1990) + var t2 = Track.fixture(fileURL: "/b.mp3", year: 2010) + var t3 = Track.fixture(fileURL: "/c.mp3", year: 2020) + try db.insert(&t1) + try db.insert(&t2) + try db.insert(&t3) + let conditions = [SmartPlaylistCondition(field: .year, op: .lessThan, value: .int(2000))] + let results = try db.fetchTracks(conditions: conditions, sortColumn: "title", ascending: true) + #expect(results.count == 1) + #expect((results[0].year ?? 0) < 2000) + } + // Updates the conditions of a structured smart playlist and verifies the updated // conditions are persisted and fetch back correctly. @Test func updateSmartPlaylistConditionsPersists() throws {