Skip to content

Commit b07eb81

Browse files
committed
test: add direct tests for readColumnValue, sliceColumnarBatch, columnarKWayMerge
These functions are now public API exports but had zero direct test coverage (only tested indirectly via merge.test.ts). Adds 17 tests: - readColumnValue: f64, i64, i32, string, bool single-element reads - sliceColumnarBatch: offset, offset+limit, string/bool slicing, edge cases - columnarKWayMerge: asc/desc merge, limit, multi-column, 3-way, empty batches
1 parent 05fe15e commit b07eb81

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

src/columnar.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
encodeColumnarBatch,
66
columnarBatchToRows,
77
concatQMCBBatches,
8+
columnarKWayMerge,
9+
sliceColumnarBatch,
10+
readColumnValue,
811
DTYPE_F64,
912
DTYPE_I64,
1013
DTYPE_I32,
@@ -440,4 +443,148 @@ describe("columnar", () => {
440443
expect(arr[n - 1]).toBe((n - 1) * 1.5);
441444
});
442445
});
446+
447+
describe("readColumnValue", () => {
448+
it("reads f64 values", () => {
449+
const batch = makeBatch([{ name: "x", type: 1, values: [1.5, 2.5, 3.5] }]);
450+
expect(readColumnValue(batch.columns[0], 0)).toBe(1.5);
451+
expect(readColumnValue(batch.columns[0], 2)).toBe(3.5);
452+
});
453+
454+
it("reads i64 values as bigint", () => {
455+
const batch = makeBatch([{ name: "id", type: 0, values: [100n, 200n] }]);
456+
expect(readColumnValue(batch.columns[0], 0)).toBe(100n);
457+
expect(readColumnValue(batch.columns[0], 1)).toBe(200n);
458+
});
459+
460+
it("reads i32 values", () => {
461+
const batch = makeBatch([{ name: "n", type: 4, values: [10, 20, 30] }]);
462+
expect(readColumnValue(batch.columns[0], 1)).toBe(20);
463+
});
464+
465+
it("reads string values", () => {
466+
const batch = makeBatch([{ name: "s", type: 2, values: ["hello", "world"] }]);
467+
expect(readColumnValue(batch.columns[0], 0)).toBe("hello");
468+
expect(readColumnValue(batch.columns[0], 1)).toBe("world");
469+
});
470+
471+
it("reads bool values", () => {
472+
const batch = makeBatch([{ name: "b", type: 3, values: [true, false, true] }]);
473+
expect(readColumnValue(batch.columns[0], 0)).toBe(true);
474+
expect(readColumnValue(batch.columns[0], 1)).toBe(false);
475+
expect(readColumnValue(batch.columns[0], 2)).toBe(true);
476+
});
477+
});
478+
479+
describe("sliceColumnarBatch", () => {
480+
it("slices numeric batch with offset", () => {
481+
const batch = makeBatch([{ name: "x", type: 1, values: [10, 20, 30, 40, 50] }]);
482+
const sliced = sliceColumnarBatch(batch, 2);
483+
expect(sliced.rowCount).toBe(3);
484+
const rows = columnarBatchToRows(sliced);
485+
expect(rows.map(r => r.x)).toEqual([30, 40, 50]);
486+
});
487+
488+
it("slices with offset and limit", () => {
489+
const batch = makeBatch([{ name: "x", type: 1, values: [10, 20, 30, 40, 50] }]);
490+
const sliced = sliceColumnarBatch(batch, 1, 2);
491+
expect(sliced.rowCount).toBe(2);
492+
const rows = columnarBatchToRows(sliced);
493+
expect(rows.map(r => r.x)).toEqual([20, 30]);
494+
});
495+
496+
it("slices string batch correctly", () => {
497+
const batch = makeBatch([{ name: "s", type: 2, values: ["a", "bb", "ccc", "dddd"] }]);
498+
const sliced = sliceColumnarBatch(batch, 1, 2);
499+
const rows = columnarBatchToRows(sliced);
500+
expect(rows.map(r => r.s)).toEqual(["bb", "ccc"]);
501+
});
502+
503+
it("slices bool batch correctly", () => {
504+
const batch = makeBatch([{ name: "b", type: 3, values: [true, false, true, false, true] }]);
505+
const sliced = sliceColumnarBatch(batch, 2, 2);
506+
const rows = columnarBatchToRows(sliced);
507+
expect(rows.map(r => r.b)).toEqual([true, false]);
508+
});
509+
510+
it("returns same batch when offset=0 and no limit", () => {
511+
const batch = makeBatch([{ name: "x", type: 1, values: [1, 2] }]);
512+
expect(sliceColumnarBatch(batch, 0)).toBe(batch);
513+
});
514+
515+
it("returns empty batch when offset exceeds rowCount", () => {
516+
const batch = makeBatch([{ name: "x", type: 1, values: [1, 2] }]);
517+
const sliced = sliceColumnarBatch(batch, 10);
518+
expect(sliced.rowCount).toBe(0);
519+
});
520+
});
521+
522+
describe("columnarKWayMerge", () => {
523+
it("merges two sorted numeric batches ascending", () => {
524+
const b1 = makeBatch([{ name: "x", type: 1, values: [1, 3, 5] }]);
525+
const b2 = makeBatch([{ name: "x", type: 1, values: [2, 4, 6] }]);
526+
const merged = columnarKWayMerge([b1, b2], "x", "asc", 100);
527+
const rows = columnarBatchToRows(merged);
528+
expect(rows.map(r => r.x)).toEqual([1, 2, 3, 4, 5, 6]);
529+
});
530+
531+
it("merges descending", () => {
532+
const b1 = makeBatch([{ name: "x", type: 1, values: [5, 3, 1] }]);
533+
const b2 = makeBatch([{ name: "x", type: 1, values: [6, 4, 2] }]);
534+
const merged = columnarKWayMerge([b1, b2], "x", "desc", 100);
535+
const rows = columnarBatchToRows(merged);
536+
expect(rows.map(r => r.x)).toEqual([6, 5, 4, 3, 2, 1]);
537+
});
538+
539+
it("respects limit", () => {
540+
const b1 = makeBatch([{ name: "x", type: 1, values: [1, 3, 5] }]);
541+
const b2 = makeBatch([{ name: "x", type: 1, values: [2, 4, 6] }]);
542+
const merged = columnarKWayMerge([b1, b2], "x", "asc", 3);
543+
expect(merged.rowCount).toBe(3);
544+
const rows = columnarBatchToRows(merged);
545+
expect(rows.map(r => r.x)).toEqual([1, 2, 3]);
546+
});
547+
548+
it("preserves non-sort columns during merge", () => {
549+
const b1 = makeBatch([
550+
{ name: "id", type: 1, values: [1, 3] },
551+
{ name: "name", type: 2, values: ["alice", "charlie"] },
552+
]);
553+
const b2 = makeBatch([
554+
{ name: "id", type: 1, values: [2, 4] },
555+
{ name: "name", type: 2, values: ["bob", "dave"] },
556+
]);
557+
const merged = columnarKWayMerge([b1, b2], "id", "asc", 100);
558+
const rows = columnarBatchToRows(merged);
559+
expect(rows).toEqual([
560+
{ id: 1, name: "alice" },
561+
{ id: 2, name: "bob" },
562+
{ id: 3, name: "charlie" },
563+
{ id: 4, name: "dave" },
564+
]);
565+
});
566+
567+
it("merges three batches", () => {
568+
const b1 = makeBatch([{ name: "x", type: 1, values: [1, 4] }]);
569+
const b2 = makeBatch([{ name: "x", type: 1, values: [2, 5] }]);
570+
const b3 = makeBatch([{ name: "x", type: 1, values: [3, 6] }]);
571+
const merged = columnarKWayMerge([b1, b2, b3], "x", "asc", 100);
572+
const rows = columnarBatchToRows(merged);
573+
expect(rows.map(r => r.x)).toEqual([1, 2, 3, 4, 5, 6]);
574+
});
575+
576+
it("handles empty batches", () => {
577+
const b1 = makeBatch([{ name: "x", type: 1, values: [1, 2] }]);
578+
const empty: ColumnarBatch = { columns: [{ name: "x", dtype: DTYPE_F64, data: new ArrayBuffer(0), rowCount: 0 }], rowCount: 0 };
579+
const merged = columnarKWayMerge([b1, empty], "x", "asc", 100);
580+
expect(merged.rowCount).toBe(2);
581+
});
582+
});
443583
});
584+
585+
/** Helper: build a decoded ColumnarBatch from WASM result format. */
586+
function makeBatch(columns: { name: string; type: number; values: (number | bigint | string | boolean)[] }[]): ColumnarBatch {
587+
const wasm = buildWasmResult(columns);
588+
const qmcb = wasmResultToQMCB(wasm, 0, wasm.byteLength)!;
589+
return decodeColumnarBatch(qmcb)!;
590+
}

0 commit comments

Comments
 (0)