Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/like-newline-dotall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@triplit/db': patch
---

Fix `like`/`nlike` so the `%` and `_` wildcards match newline characters, matching SQL LIKE semantics. Previously the pattern was compiled to a regular expression without the dotall flag, so a value containing a newline would not match patterns it should (e.g. `'a\nb'` did not match `'a%b'` or `'a_b'`, and `'%'` failed to match any multi-line value).
7 changes: 5 additions & 2 deletions packages/db/src/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,11 @@ function ilike(text: string, pattern: string): boolean {
// Replace SQL LIKE single-character wildcards (_) with equivalent regex wildcards (.)
pattern = pattern.replace(/_/g, '.');

// Create a RegExp object from the pattern
const regex = new RegExp(`^${pattern}$`, 'i');
// Create a RegExp object from the pattern.
// The `s` (dotall) flag makes `.` (from `_`) and `.*` (from `%`) match newline
// characters too, matching SQL LIKE semantics where `%` matches any sequence of
// characters and `_` any single character — newlines included.
const regex = new RegExp(`^${pattern}$`, 'is');

// Test the text against the regex
return regex.test(text);
Expand Down
54 changes: 51 additions & 3 deletions packages/db/test/filters/type-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,9 +518,57 @@ describe.each(TEST_OPTIONS)('$engine', (options) => {
);
});
});
// TODO: move over like / nlike tests from db.spec.ts
describe.todo('like');
describe.todo('nlike');
// TODO: move over the remaining like / nlike cases from db.spec.ts
describe('like', () => {
it('matches newline characters with % and _ (dotall semantics)', async () => {
const data = genData(['a\nb', 'axb', 'ab', 'a\nb\nc']);
shuffleArray(data);
// `%` matches any sequence of characters, including newlines
await testFilterOp(
'like',
requiredSchema,
data,
{ cmp: 'a%b', expected: [0, 1, 2] },
options
);
// `_` matches any single character, including a newline
await testFilterOp(
'like',
requiredSchema,
data,
{ cmp: 'a_b', expected: [0, 1] },
options
);
// `%` alone matches every value, even ones containing newlines
await testFilterOp(
'like',
requiredSchema,
data,
{ cmp: '%', expected: [0, 1, 2, 3] },
options
);
});
});
describe('nlike', () => {
it('matches newline characters with % and _ (dotall semantics)', async () => {
const data = genData(['a\nb', 'axb', 'ab']);
shuffleArray(data);
await testFilterOp(
'nlike',
requiredSchema,
data,
{ cmp: 'a%b', expected: [] },
options
);
await testFilterOp(
'nlike',
requiredSchema,
data,
{ cmp: 'a_b', expected: [2] },
options
);
});
});
describe('in', () => {
it('required', async () => {
const data = genData(['a', 'b', 'b', 'c', 'd']);
Expand Down