Skip to content

Commit 26a30fe

Browse files
Fix TS composite btree range scans
1 parent 8f76e01 commit 26a30fe

2 files changed

Lines changed: 61 additions & 4 deletions

File tree

crates/bindings-typescript/src/server/runtime.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,15 @@ export function makeTableView(
896896
} as PointIndex<any, any>;
897897
} else {
898898
// numColumns != 1
899+
const isCompleteScalarKey = (range: any[]): boolean => {
900+
// Preserve the point-scan path only for complete scalar keys.
901+
// A complete key with a Range in the final position is still a range
902+
// scan over that column with equality over the preceding prefix.
903+
return (
904+
range.length === numColumns && !(range[range.length - 1] instanceof Range)
905+
);
906+
};
907+
899908
const serializeRange = (
900909
buffer: ResizableBuffer,
901910
range: any[]
@@ -920,12 +929,12 @@ export function makeTableView(
920929
writeBound(term.from);
921930
const rstartLen = writer.offset - rstartOffset;
922931
writeBound(term.to);
923-
const rendLen = writer.offset - rstartLen;
932+
const rendLen = writer.offset - rstartOffset - rstartLen;
924933
return [rstartOffset, prefix_elems, rstartLen, rendLen];
925934
} else {
926935
writer.writeU8(0);
927936
serializeTerm(writer, term);
928-
const rstartLen = writer.offset;
937+
const rstartLen = writer.offset - rstartOffset;
929938
const rendLen = 0;
930939
return [rstartOffset, prefix_elems, rstartLen, rendLen];
931940
}
@@ -936,7 +945,7 @@ export function makeTableView(
936945
// one-column prefix scan; normalize it to a single-element array so
937946
// `.length` and `serializeRange` see a prefix rather than NaN.
938947
if (!Array.isArray(range)) range = [range];
939-
if (range.length === numColumns) {
948+
if (isCompleteScalarKey(range)) {
940949
const buf = LEAF_BUF;
941950
const point_len = serializePoint(buf, range);
942951
const iter_id = sys.datastore_index_scan_point_bsatn(
@@ -958,7 +967,7 @@ export function makeTableView(
958967
},
959968
delete: (range: any[]): u32 => {
960969
if (!Array.isArray(range)) range = [range];
961-
if (range.length === numColumns) {
970+
if (isCompleteScalarKey(range)) {
962971
const buf = LEAF_BUF;
963972
const point_len = serializePoint(buf, range);
964973
return sys.datastore_delete_by_index_scan_point_bsatn(

crates/bindings-typescript/tests/index_prefix_filter.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,32 @@ function tallyView() {
5656
return view.by_board_def;
5757
}
5858

59+
function tallyCountView() {
60+
const ctx = new ModuleContext();
61+
const tally = table(
62+
{
63+
name: 'tally',
64+
indexes: [
65+
{
66+
accessor: 'by_board_count',
67+
algorithm: 'btree',
68+
columns: ['boardId', 'count'] as const,
69+
},
70+
] as const,
71+
},
72+
{
73+
id: t.u64().primaryKey().autoInc(),
74+
boardId: t.u64(),
75+
defId: t.string(),
76+
count: t.u64(),
77+
}
78+
);
79+
80+
const rawTableDef = tally.tableDef(ctx, 'tally');
81+
const view = makeTableView(ctx.typespace, rawTableDef) as any;
82+
return view.by_board_count;
83+
}
84+
5985
describe('multi-column index one-column prefix scan', () => {
6086
it('full two-column key works (point scan)', () => {
6187
const index = tallyView();
@@ -81,9 +107,31 @@ describe('multi-column index one-column prefix scan', () => {
81107
expect([...index.filter(range)]).toEqual([]);
82108
});
83109

110+
it('full key with Range in final column works (range scan)', () => {
111+
const index = tallyCountView();
112+
const range = new Range<bigint>(
113+
{ tag: 'included', value: 10n },
114+
{ tag: 'included', value: 20n }
115+
);
116+
117+
expect(() => [...index.filter([1n, range])]).not.toThrow();
118+
expect([...index.filter([1n, range])]).toEqual([]);
119+
});
120+
84121
it('delete() accepts a bare-scalar one-column prefix', () => {
85122
const index = tallyView();
86123
expect(() => index.delete(1n)).not.toThrow();
87124
expect(index.delete(1n)).toBe(0);
88125
});
126+
127+
it('delete() accepts a full key with Range in final column', () => {
128+
const index = tallyCountView();
129+
const range = new Range<bigint>(
130+
{ tag: 'included', value: 10n },
131+
{ tag: 'included', value: 20n }
132+
);
133+
134+
expect(() => index.delete([1n, range])).not.toThrow();
135+
expect(index.delete([1n, range])).toBe(0);
136+
});
89137
});

0 commit comments

Comments
 (0)