Skip to content

Commit 205a0e0

Browse files
committed
fix: MaterializedExecutor uses matchesFilter for all filter ops + filterGroups
- Replace inline filter switch with shared matchesFilter() from decode.ts - Fixes LIKE, NOT IN, NOT BETWEEN, NOT LIKE returning wrong results - Add filterGroups (OR) support to MaterializedExecutor - Add SQL test files to vitest config (compiler, evaluator, integration) - Test count: 173 → 255 (82 new SQL tests now in CI)
1 parent cea7292 commit 205a0e0

File tree

2 files changed

+30
-19
lines changed

2 files changed

+30
-19
lines changed

src/client.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
WindowSpec,
1616
} from "./types.js";
1717
import type { Operator, RowBatch } from "./operators.js";
18+
import { matchesFilter } from "./decode.js";
1819

1920
// ---------------------------------------------------------------------------
2021
// Progress callback for collect()
@@ -778,25 +779,32 @@ export class MaterializedExecutor implements QueryExecutor {
778779
async execute(query: QueryDescriptor): Promise<QueryResult> {
779780
let rows = [...this.result.rows];
780781

781-
// Apply filters
782-
for (const f of query.filters) {
783-
rows = rows.filter(row => {
784-
const v = row[f.column];
785-
if (f.op === "is_null") return v === null || v === undefined;
786-
if (f.op === "is_not_null") return v !== null && v !== undefined;
787-
if (v === null) return false;
788-
const fv = f.value;
789-
switch (f.op) {
790-
case "eq": return v === fv;
791-
case "neq": return v !== fv;
792-
case "gt": return typeof v === "number" && typeof fv === "number" ? v > fv : String(v) > String(fv);
793-
case "gte": return typeof v === "number" && typeof fv === "number" ? v >= fv : String(v) >= String(fv);
794-
case "lt": return typeof v === "number" && typeof fv === "number" ? v < fv : String(v) < String(fv);
795-
case "lte": return typeof v === "number" && typeof fv === "number" ? v <= fv : String(v) <= String(fv);
796-
case "in": return Array.isArray(fv) && (fv as unknown[]).includes(v);
797-
default: return true;
798-
}
799-
});
782+
// Apply AND filters
783+
if (query.filters.length > 0) {
784+
rows = rows.filter(row =>
785+
query.filters.every(f => {
786+
const v = row[f.column];
787+
if (f.op === "is_null") return v === null || v === undefined;
788+
if (f.op === "is_not_null") return v !== null && v !== undefined;
789+
if (v === null || v === undefined) return false;
790+
return matchesFilter(v as Row[string], f);
791+
}),
792+
);
793+
}
794+
795+
// Apply OR filter groups (each group is AND-connected, groups are OR'd)
796+
if (query.filterGroups && query.filterGroups.length > 0) {
797+
rows = rows.filter(row =>
798+
query.filterGroups!.some(group =>
799+
group.every(f => {
800+
const v = row[f.column];
801+
if (f.op === "is_null") return v === null || v === undefined;
802+
if (f.op === "is_not_null") return v !== null && v !== undefined;
803+
if (v === null || v === undefined) return false;
804+
return matchesFilter(v as Row[string], f);
805+
}),
806+
),
807+
);
800808
}
801809

802810
// Apply projections

vitest.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export default defineConfig({
2020
"src/query-do.integration.test.ts",
2121
"src/sql/lexer.test.ts",
2222
"src/sql/parser.test.ts",
23+
"src/sql/compiler.test.ts",
24+
"src/sql/evaluator.test.ts",
25+
"src/sql/integration.test.ts",
2326
],
2427
hookTimeout: 300_000,
2528
testTimeout: 300_000,

0 commit comments

Comments
 (0)