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
18 changes: 14 additions & 4 deletions crates/bindings-typescript/src/server/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,16 @@ export function makeTableView(
} as PointIndex<any, any>;
} else {
// numColumns != 1
const isCompleteScalarKey = (range: any[]): boolean => {
// Preserve the point-scan path only for complete scalar keys.
// A complete key with a Range in the final position is still a range
// scan over that column with equality over the preceding prefix.
return (
range.length === numColumns &&
!(range[range.length - 1] instanceof Range)
);
};

const serializeRange = (
buffer: ResizableBuffer,
range: any[]
Expand All @@ -920,12 +930,12 @@ export function makeTableView(
writeBound(term.from);
const rstartLen = writer.offset - rstartOffset;
writeBound(term.to);
const rendLen = writer.offset - rstartLen;
const rendLen = writer.offset - rstartOffset - rstartLen;
return [rstartOffset, prefix_elems, rstartLen, rendLen];
} else {
writer.writeU8(0);
serializeTerm(writer, term);
const rstartLen = writer.offset;
const rstartLen = writer.offset - rstartOffset;
const rendLen = 0;
return [rstartOffset, prefix_elems, rstartLen, rendLen];
}
Expand All @@ -936,7 +946,7 @@ export function makeTableView(
// one-column prefix scan; normalize it to a single-element array so
// `.length` and `serializeRange` see a prefix rather than NaN.
if (!Array.isArray(range)) range = [range];
if (range.length === numColumns) {
if (isCompleteScalarKey(range)) {
const buf = LEAF_BUF;
const point_len = serializePoint(buf, range);
const iter_id = sys.datastore_index_scan_point_bsatn(
Expand All @@ -958,7 +968,7 @@ export function makeTableView(
},
delete: (range: any[]): u32 => {
if (!Array.isArray(range)) range = [range];
if (range.length === numColumns) {
if (isCompleteScalarKey(range)) {
const buf = LEAF_BUF;
const point_len = serializePoint(buf, range);
return sys.datastore_delete_by_index_scan_point_bsatn(
Expand Down
48 changes: 48 additions & 0 deletions crates/bindings-typescript/tests/index_prefix_filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,32 @@ function tallyView() {
return view.by_board_def;
}

function tallyCountView() {
const ctx = new ModuleContext();
const tally = table(
{
name: 'tally',
indexes: [
{
accessor: 'by_board_count',
algorithm: 'btree',
columns: ['boardId', 'count'] as const,
},
] as const,
},
{
id: t.u64().primaryKey().autoInc(),
boardId: t.u64(),
defId: t.string(),
count: t.u64(),
}
);

const rawTableDef = tally.tableDef(ctx, 'tally');
const view = makeTableView(ctx.typespace, rawTableDef) as any;
return view.by_board_count;
}

describe('multi-column index one-column prefix scan', () => {
it('full two-column key works (point scan)', () => {
const index = tallyView();
Expand All @@ -81,9 +107,31 @@ describe('multi-column index one-column prefix scan', () => {
expect([...index.filter(range)]).toEqual([]);
});

it('full key with Range in final column works (range scan)', () => {
const index = tallyCountView();
const range = new Range<bigint>(
{ tag: 'included', value: 10n },
{ tag: 'included', value: 20n }
);

expect(() => [...index.filter([1n, range])]).not.toThrow();
expect([...index.filter([1n, range])]).toEqual([]);
});

it('delete() accepts a bare-scalar one-column prefix', () => {
const index = tallyView();
expect(() => index.delete(1n)).not.toThrow();
expect(index.delete(1n)).toBe(0);
});

it('delete() accepts a full key with Range in final column', () => {
const index = tallyCountView();
const range = new Range<bigint>(
{ tag: 'included', value: 10n },
{ tag: 'included', value: 20n }
);

expect(() => index.delete([1n, range])).not.toThrow();
expect(index.delete([1n, range])).toBe(0);
});
});
Loading