Skip to content

Commit 4ff3da5

Browse files
ldirerclaude
andauthored
fix(db): like not matching newline characters (#1263)
* tests: confirm ilike/like newline matching bug Add failing tests that demonstrate % and _ wildcards in like/ilike do not match across newline characters. * tests: add ilike with newline e2e test * fix: make like % match newline characters * changeset --------- Co-authored-by: Claude <[email protected]>
1 parent 802550f commit 4ff3da5

6 files changed

Lines changed: 47 additions & 2 deletions

File tree

.changeset/nice-months-nail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/db': patch
3+
---
4+
5+
Fix like/ilike `%` and `_` not matching newline characters

packages/db-collection-e2e/src/fixtures/seed-data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export function generateSeedData(): SeedDataResult {
6868
`Rose ${i}`,
6969
`sam ${i}`,
7070
`Tina ${i}`,
71+
`Ursula ${i}\nNewline`,
7172
]
7273
const name = names[i % names.length]
7374

packages/db-collection-e2e/src/suites/predicates.suite.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,27 @@ export function createPredicatesTestSuite(
412412
await query.cleanup()
413413
})
414414

415+
it(`should filter with like() with wildcard pattern matching newline`, async () => {
416+
const config = await getConfig()
417+
const usersCollection = config.collections.onDemand.users
418+
419+
const query = createLiveQueryCollection((q) =>
420+
q
421+
.from({ user: usersCollection })
422+
.where(({ user }) => like(user.name, `Ursula%`)),
423+
)
424+
425+
await query.preload()
426+
await waitForQueryData(query, { minSize: 1 })
427+
428+
const results = Array.from(query.state.values())
429+
expect(results.length).toBeGreaterThan(0)
430+
// should match names starting with "Ursula" even if it contains a newline character
431+
assertAllItemsMatch(query, (u) => u.name.startsWith(`Ursula`))
432+
433+
await query.cleanup()
434+
})
435+
415436
it(`should filter with like() with lower() function`, async () => {
416437
const config = await getConfig()
417438
const usersCollection = config.collections.onDemand.users

packages/db/src/query/compiler/evaluators.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ function evaluateLike(
516516
regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence
517517
regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char
518518

519-
const regex = new RegExp(`^${regexPattern}$`)
519+
// 's' (dotAll flag) makes '.' match all characters including line terminations
520+
const regex = new RegExp(`^${regexPattern}$`, 's')
520521
return regex.test(searchValue)
521522
}

packages/db/tests/query/compiler/evaluators.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,23 @@ describe(`evaluators`, () => {
374374
// In 3-valued logic, ilike with undefined pattern returns UNKNOWN (null)
375375
expect(compiled({})).toBe(null)
376376
})
377+
378+
it(`like % wildcard matches across newline characters`, () => {
379+
const func = new Func(`like`, [
380+
new Value(`hello\nworld`),
381+
new Value(`%world`),
382+
])
383+
const compiled = compileExpression(func)
384+
385+
expect(compiled({})).toBe(true)
386+
})
387+
388+
it(`like _ wildcard matches a newline character`, () => {
389+
const func = new Func(`like`, [new Value(`a\nb`), new Value(`a_b`)])
390+
const compiled = compileExpression(func)
391+
392+
expect(compiled({})).toBe(true)
393+
})
377394
})
378395

379396
describe(`comparison operators`, () => {

packages/query-db-collection/e2e/query-filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ function evaluateLike(
556556
regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence
557557
regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char
558558

559-
const regex = new RegExp(`^${regexPattern}$`)
559+
const regex = new RegExp(`^${regexPattern}$`, 's')
560560
return regex.test(searchValue)
561561
}
562562

0 commit comments

Comments
 (0)