@smoothbricks/lmao 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/allocator.wasm +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/JsBufferStrategy.d.ts +4 -3
- package/dist/lib/JsBufferStrategy.d.ts.map +1 -1
- package/dist/lib/JsBufferStrategy.js +18 -11
- package/dist/lib/arrow/lease.d.ts +10 -0
- package/dist/lib/arrow/lease.d.ts.map +1 -0
- package/dist/lib/arrow/lease.js +33 -0
- package/dist/lib/arrow/utils.d.ts +3 -4
- package/dist/lib/arrow/utils.d.ts.map +1 -1
- package/dist/lib/arrow/utils.js +12 -13
- package/dist/lib/arrow/vocabularyDictionary.d.ts +21 -0
- package/dist/lib/arrow/vocabularyDictionary.d.ts.map +1 -0
- package/dist/lib/arrow/vocabularyDictionary.js +95 -0
- package/dist/lib/bufferStrategy.d.ts +3 -2
- package/dist/lib/bufferStrategy.d.ts.map +1 -1
- package/dist/lib/cloudflare/traceRows.d.ts.map +1 -1
- package/dist/lib/cloudflare/traceRows.js +3 -2
- package/dist/lib/codegen/evaluatorGenerator.d.ts.map +1 -1
- package/dist/lib/codegen/evaluatorGenerator.js +12 -11
- package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts +44 -82
- package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts.map +1 -1
- package/dist/lib/codegen/fixedPositionWriterGenerator.js +223 -162
- package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts +63 -0
- package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts.map +1 -0
- package/dist/lib/codegen/spanLoggerClosureMaterializer.js +447 -0
- package/dist/lib/codegen/spanLoggerGenerator.d.ts +36 -19
- package/dist/lib/codegen/spanLoggerGenerator.d.ts.map +1 -1
- package/dist/lib/codegen/spanLoggerGenerator.js +164 -158
- package/dist/lib/convertToArrow.d.ts +7 -2
- package/dist/lib/convertToArrow.d.ts.map +1 -1
- package/dist/lib/convertToArrow.js +499 -285
- package/dist/lib/defineOpContext.js +1 -1
- package/dist/lib/enumMetadata.d.ts +19 -0
- package/dist/lib/enumMetadata.d.ts.map +1 -0
- package/dist/lib/enumMetadata.js +86 -0
- package/dist/lib/flushScheduler.d.ts.map +1 -1
- package/dist/lib/flushScheduler.js +13 -4
- package/dist/lib/library.d.ts +16 -98
- package/dist/lib/library.d.ts.map +1 -1
- package/dist/lib/library.js +32 -525
- package/dist/lib/logBinding.d.ts +21 -27
- package/dist/lib/logBinding.d.ts.map +1 -1
- package/dist/lib/op.d.ts +30 -30
- package/dist/lib/op.d.ts.map +1 -1
- package/dist/lib/op.js +15 -22
- package/dist/lib/opContext/createOpGroup.d.ts +1 -1
- package/dist/lib/opContext/createOpGroup.d.ts.map +1 -1
- package/dist/lib/opContext/createOpGroup.js +22 -29
- package/dist/lib/opContext/defineOp.d.ts +4 -4
- package/dist/lib/opContext/defineOp.d.ts.map +1 -1
- package/dist/lib/opContext/defineOp.js +53 -10
- package/dist/lib/opContext/opGroupTypes.d.ts +2 -2
- package/dist/lib/opContext/opTypes.d.ts +1 -1
- package/dist/lib/opContext/opTypes.d.ts.map +1 -1
- package/dist/lib/opContext/spanContextTypes.d.ts +29 -14
- package/dist/lib/opContext/spanContextTypes.d.ts.map +1 -1
- package/dist/lib/opContext/types.d.ts +4 -4
- package/dist/lib/opContext/types.d.ts.map +1 -1
- package/dist/lib/physicalLayoutPlan.d.ts +89 -0
- package/dist/lib/physicalLayoutPlan.d.ts.map +1 -0
- package/dist/lib/physicalLayoutPlan.js +442 -0
- package/dist/lib/resolveMessage.d.ts +9 -0
- package/dist/lib/resolveMessage.d.ts.map +1 -0
- package/dist/lib/resolveMessage.js +63 -0
- package/dist/lib/result.d.ts +5 -7
- package/dist/lib/result.d.ts.map +1 -1
- package/dist/lib/result.js +19 -26
- package/dist/lib/runtimeHint.d.ts +35 -0
- package/dist/lib/runtimeHint.d.ts.map +1 -0
- package/dist/lib/runtimeHint.js +79 -0
- package/dist/lib/schema/types.d.ts +1 -1
- package/dist/lib/schema/types.d.ts.map +1 -1
- package/dist/lib/span-buffer/aot/v1.d.ts +2 -0
- package/dist/lib/span-buffer/aot/v1.d.ts.map +1 -0
- package/dist/lib/span-buffer/aot/v1.js +24 -0
- package/dist/lib/spanBuffer.d.ts +33 -4
- package/dist/lib/spanBuffer.d.ts.map +1 -1
- package/dist/lib/spanBuffer.js +583 -91
- package/dist/lib/spanBufferStats.d.ts +1 -1
- package/dist/lib/spanBufferStats.d.ts.map +1 -1
- package/dist/lib/spanContext.d.ts +27 -51
- package/dist/lib/spanContext.d.ts.map +1 -1
- package/dist/lib/spanContext.js +822 -344
- package/dist/lib/sqlite/sqlite-common.d.ts.map +1 -1
- package/dist/lib/sqlite/sqlite-common.js +20 -21
- package/dist/lib/testing/bun-harness.d.ts +8 -3
- package/dist/lib/testing/bun-harness.d.ts.map +1 -1
- package/dist/lib/testing/bun-harness.js +30 -18
- package/dist/lib/testing/extractFacts.d.ts +1 -1
- package/dist/lib/testing/extractFacts.d.ts.map +1 -1
- package/dist/lib/testing/extractFacts.js +55 -63
- package/dist/lib/testing/facts.d.ts +1 -1
- package/dist/lib/testing/facts.d.ts.map +1 -1
- package/dist/lib/testing/queryable-span.d.ts +1 -1
- package/dist/lib/testing/queryable-span.d.ts.map +1 -1
- package/dist/lib/testing/queryable-span.js +26 -28
- package/dist/lib/testing/span-query.d.ts.map +1 -1
- package/dist/lib/testing/span-query.js +19 -22
- package/dist/lib/testing/stdio-replay.d.ts.map +1 -1
- package/dist/lib/testing/stdio-replay.js +19 -8
- package/dist/lib/threadId.d.ts.map +1 -1
- package/dist/lib/threadId.js +6 -3
- package/dist/lib/traceRoot.d.ts +30 -8
- package/dist/lib/traceRoot.d.ts.map +1 -1
- package/dist/lib/traceRoot.es.d.ts +17 -15
- package/dist/lib/traceRoot.es.d.ts.map +1 -1
- package/dist/lib/traceRoot.es.js +68 -34
- package/dist/lib/traceRoot.js +11 -0
- package/dist/lib/traceRoot.node.d.ts +17 -19
- package/dist/lib/traceRoot.node.d.ts.map +1 -1
- package/dist/lib/traceRoot.node.js +66 -38
- package/dist/lib/traceTopology.d.ts +42 -0
- package/dist/lib/traceTopology.d.ts.map +1 -0
- package/dist/lib/traceTopology.js +260 -0
- package/dist/lib/tracer.d.ts +9 -5
- package/dist/lib/tracer.d.ts.map +1 -1
- package/dist/lib/tracer.js +65 -71
- package/dist/lib/tracers/ArrayQueueTracer.d.ts +1 -1
- package/dist/lib/tracers/ArrayQueueTracer.js +1 -1
- package/dist/lib/tracers/StdioTracer.d.ts.map +1 -1
- package/dist/lib/tracers/StdioTracer.js +15 -13
- package/dist/lib/tracers/TestTracer.d.ts +4 -6
- package/dist/lib/tracers/TestTracer.d.ts.map +1 -1
- package/dist/lib/tracers/TestTracer.js +6 -8
- package/dist/lib/types.d.ts +49 -32
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/vocabularyRegistry.d.ts +25 -0
- package/dist/lib/vocabularyRegistry.d.ts.map +1 -0
- package/dist/lib/vocabularyRegistry.js +372 -0
- package/dist/lib/wasm/WasmBufferStrategy.d.ts +6 -10
- package/dist/lib/wasm/WasmBufferStrategy.d.ts.map +1 -1
- package/dist/lib/wasm/WasmBufferStrategy.js +44 -37
- package/dist/lib/wasm/wasmAllocator.d.ts +27 -22
- package/dist/lib/wasm/wasmAllocator.d.ts.map +1 -1
- package/dist/lib/wasm/wasmAllocator.js +66 -44
- package/dist/lib/wasm/wasmPhysicalLayout.d.ts +67 -0
- package/dist/lib/wasm/wasmPhysicalLayout.d.ts.map +1 -0
- package/dist/lib/wasm/wasmPhysicalLayout.js +179 -0
- package/dist/lib/wasm/wasmSpanBuffer.d.ts +79 -31
- package/dist/lib/wasm/wasmSpanBuffer.d.ts.map +1 -1
- package/dist/lib/wasm/wasmSpanBuffer.js +442 -272
- package/dist/lib/wasm/wasmTraceRoot.d.ts +24 -22
- package/dist/lib/wasm/wasmTraceRoot.d.ts.map +1 -1
- package/dist/lib/wasm/wasmTraceRoot.js +110 -71
- package/dist/vocabulary/register/v1.d.ts +2 -0
- package/dist/vocabulary/register/v1.d.ts.map +1 -0
- package/dist/vocabulary/register/v1.js +1 -0
- package/dist/wasm.d.ts +5 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +4 -0
- package/package.json +54 -6
- package/src/index.ts +1 -0
- package/src/lib/JsBufferStrategy.ts +27 -9
- package/src/lib/__tests__/arrow-builder-integration/arrow-conversion.test.ts +335 -37
- package/src/lib/__tests__/arrow-builder-integration/binary-arrow-conversion.test.ts +97 -26
- package/src/lib/__tests__/arrow-builder-integration/binary-comparison.test.ts +75 -15
- package/src/lib/__tests__/arrow-builder-integration/binary-format-compliance.test.ts +30 -6
- package/src/lib/__tests__/arrow-builder-integration/buffer-chaining.test.ts +18 -21
- package/src/lib/__tests__/arrow-builder-integration/foundation.test.ts +5 -2
- package/src/lib/__tests__/arrow-builder-integration/integration.test.ts +25 -7
- package/src/lib/__tests__/arrow-builder-integration/lazy-columns.test.ts +3 -2
- package/src/lib/__tests__/buffer-overflow.test.ts +24 -18
- package/src/lib/__tests__/capability-span-context.test.ts +505 -0
- package/src/lib/__tests__/capacity-tuning.test.ts +5 -3
- package/src/lib/__tests__/eager-columns.test.ts +345 -0
- package/src/lib/__tests__/enum-metadata-aot.test.ts +153 -0
- package/src/lib/__tests__/enum-metadata.test.ts +177 -0
- package/src/lib/__tests__/fixtures/empty-vocabulary-runtime.fixture.ts +15 -0
- package/src/lib/__tests__/flushScheduler.test.ts +5 -1
- package/src/lib/__tests__/identity-view.property.test.ts +179 -0
- package/src/lib/__tests__/library.test.ts +19 -235
- package/src/lib/__tests__/message-buffer-families.test.ts +878 -0
- package/src/lib/__tests__/nested-library-tasks.test.ts +14 -15
- package/src/lib/__tests__/op-metadata.test.ts +1 -1
- package/src/lib/__tests__/op-type-safety.test.ts +5 -16
- package/src/lib/__tests__/physical-layout-plan.test.ts +333 -0
- package/src/lib/__tests__/remap-descriptor-integration.test.ts +381 -0
- package/src/lib/__tests__/root-context-construction.test.ts +414 -0
- package/src/lib/__tests__/span-auto-runtime.test.ts +386 -0
- package/src/lib/__tests__/span-buffer-segment-stats.test.ts +221 -0
- package/src/lib/__tests__/span-lifecycle.test.ts +53 -25
- package/src/lib/__tests__/spanbuffer-materializer-parity.test.ts +266 -0
- package/src/lib/__tests__/sync-async-trace.test.ts +24 -23
- package/src/lib/__tests__/tag-writer-aot.test.ts +214 -0
- package/src/lib/__tests__/test-helpers.ts +67 -12
- package/src/lib/__tests__/threadId.test.ts +62 -0
- package/src/lib/__tests__/timestamp.test.ts +221 -93
- package/src/lib/__tests__/traceTopology.property.test.ts +383 -0
- package/src/lib/__tests__/type-narrowing.test.ts +63 -0
- package/src/lib/__tests__/vocabulary-dense-decoding.test.ts +405 -0
- package/src/lib/__tests__/vocabularyRegistry.test.ts +313 -0
- package/src/lib/arrow/lease.ts +43 -0
- package/src/lib/arrow/utils.ts +10 -14
- package/src/lib/arrow/vocabularyDictionary.ts +123 -0
- package/src/lib/bufferStrategy.ts +9 -1
- package/src/lib/cloudflare/traceRows.ts +3 -2
- package/src/lib/codegen/__tests__/evaluatorGenerator.test.ts +21 -141
- package/src/lib/codegen/__tests__/fixedPositionWriter-parity.test.ts +273 -0
- package/src/lib/codegen/__tests__/fixedPositionWriter-uint64.test.ts +61 -34
- package/src/lib/codegen/__tests__/nullBitmap.test.ts +33 -37
- package/src/lib/codegen/__tests__/spanLoggerGenerator.test.ts +34 -18
- package/src/lib/codegen/__tests__/spanLoggerMaterializerParity.test.ts +503 -0
- package/src/lib/codegen/evaluatorGenerator.ts +18 -13
- package/src/lib/codegen/fixedPositionWriterGenerator.ts +329 -245
- package/src/lib/codegen/spanLoggerClosureMaterializer.ts +598 -0
- package/src/lib/codegen/spanLoggerGenerator.ts +241 -177
- package/src/lib/convertToArrow.ts +598 -311
- package/src/lib/defineOpContext.ts +1 -1
- package/src/lib/enumMetadata.ts +121 -0
- package/src/lib/flushScheduler.ts +11 -5
- package/src/lib/library.ts +37 -595
- package/src/lib/logBinding.ts +21 -27
- package/src/lib/op.ts +75 -18
- package/src/lib/opContext/__tests__/retry.test.ts +27 -21
- package/src/lib/opContext/createOpGroup.ts +33 -31
- package/src/lib/opContext/defineOp.ts +88 -11
- package/src/lib/opContext/opGroupTypes.ts +2 -2
- package/src/lib/opContext/opTypes.ts +1 -1
- package/src/lib/opContext/spanContextTypes.ts +98 -17
- package/src/lib/opContext/types.ts +7 -3
- package/src/lib/physicalLayoutPlan.ts +598 -0
- package/src/lib/resolveMessage.ts +67 -0
- package/src/lib/result.ts +20 -33
- package/src/lib/runtimeHint.ts +98 -0
- package/src/lib/schema/__tests__/featureFlags.test.ts +2 -1
- package/src/lib/schema/builder.ts +1 -1
- package/src/lib/schema/types.ts +1 -1
- package/src/lib/span-buffer/aot/v1.ts +26 -0
- package/src/lib/spanBuffer.ts +658 -92
- package/src/lib/spanBufferStats.ts +1 -1
- package/src/lib/spanContext.ts +1350 -861
- package/src/lib/sqlite/sqlite-common.ts +20 -31
- package/src/lib/testing/__tests__/topology-traversal.test.ts +174 -0
- package/src/lib/testing/__tests__/vitest-harness.test.ts +2 -1
- package/src/lib/testing/bun-harness.ts +94 -43
- package/src/lib/testing/extractFacts.ts +64 -78
- package/src/lib/testing/facts.ts +1 -1
- package/src/lib/testing/queryable-span.ts +23 -30
- package/src/lib/testing/span-query.ts +16 -20
- package/src/lib/testing/stdio-replay.ts +25 -8
- package/src/lib/testing/vitest-harness.ts +1 -1
- package/src/lib/threadId.ts +6 -3
- package/src/lib/traceRoot.es.ts +77 -42
- package/src/lib/traceRoot.node.ts +75 -46
- package/src/lib/traceRoot.ts +41 -14
- package/src/lib/traceTopology.ts +279 -0
- package/src/lib/tracer.ts +109 -98
- package/src/lib/tracers/ArrayQueueTracer.ts +1 -1
- package/src/lib/tracers/StdioTracer.ts +15 -13
- package/src/lib/tracers/TestTracer.ts +6 -8
- package/src/lib/tracers/__tests__/ArrayQueueTracer.test.ts +15 -10
- package/src/lib/tracers/__tests__/TestTracer.test.ts +23 -13
- package/src/lib/types.ts +64 -33
- package/src/lib/vocabularyRegistry.ts +439 -0
- package/src/lib/wasm/WasmBufferStrategy.ts +56 -48
- package/src/lib/wasm/__tests__/WasmBufferStrategy.test.ts +215 -11
- package/src/lib/wasm/__tests__/allocatorFreeBlockClamp.test.ts +86 -0
- package/src/lib/wasm/__tests__/wasm-identity-view.test.ts +311 -0
- package/src/lib/wasm/__tests__/wasm-integration.test.ts +417 -44
- package/src/lib/wasm/__tests__/wasm-message-buffer-families.test.ts +588 -0
- package/src/lib/wasm/__tests__/wasmAllocator.test.ts +100 -668
- package/src/lib/wasm/__tests__/wasmSpanBuffer.test.ts +419 -63
- package/src/lib/wasm/__tests__/wasmTraceRoot.test.ts +24 -29
- package/src/lib/wasm/__tests__/wasmTracer.test.ts.disabled +2 -2
- package/src/lib/wasm/wasmAllocator.ts +131 -96
- package/src/lib/wasm/wasmPhysicalLayout.ts +309 -0
- package/src/lib/wasm/wasmSpanBuffer.ts +597 -341
- package/src/lib/wasm/wasmTraceRoot.ts +140 -78
- package/src/vocabulary/register/v1.ts +1 -0
- package/src/wasm.ts +19 -0
- package/src/lib/__tests__/remapped-buffer-view-integration.test.ts +0 -319
- package/src/lib/__tests__/remapped-buffer-view.test.ts +0 -635
- package/src/lib/wasm/allocator.zig +0 -948
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import {
|
|
4
|
+
getVocabularyGeneration,
|
|
5
|
+
registerVocabularyFragment,
|
|
6
|
+
type VocabularyFragment,
|
|
7
|
+
type VocabularyGeneration,
|
|
8
|
+
VocabularyRegistrationError,
|
|
9
|
+
} from '../vocabularyRegistry.js';
|
|
10
|
+
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
const decoder = new TextDecoder();
|
|
13
|
+
|
|
14
|
+
type RecordField = { name: string; column: string };
|
|
15
|
+
type RecordInput = { text: string; fields?: readonly RecordField[] };
|
|
16
|
+
|
|
17
|
+
function encodeRecord({ text, fields = [] }: RecordInput): Uint8Array {
|
|
18
|
+
const textBytes = encoder.encode(text);
|
|
19
|
+
const encodedFields = fields.map(({ name, column }) => ({
|
|
20
|
+
name: encoder.encode(name),
|
|
21
|
+
column: encoder.encode(column),
|
|
22
|
+
}));
|
|
23
|
+
let byteLength = 4 + textBytes.length + 2;
|
|
24
|
+
for (const field of encodedFields) byteLength += 2 + field.name.length + 2 + field.column.length;
|
|
25
|
+
const record = new Uint8Array(byteLength);
|
|
26
|
+
const view = new DataView(record.buffer);
|
|
27
|
+
let offset = 0;
|
|
28
|
+
view.setUint32(offset, textBytes.length, true);
|
|
29
|
+
offset += 4;
|
|
30
|
+
record.set(textBytes, offset);
|
|
31
|
+
offset += textBytes.length;
|
|
32
|
+
view.setUint16(offset, encodedFields.length, true);
|
|
33
|
+
offset += 2;
|
|
34
|
+
for (const field of encodedFields) {
|
|
35
|
+
view.setUint16(offset, field.name.length, true);
|
|
36
|
+
offset += 2;
|
|
37
|
+
record.set(field.name, offset);
|
|
38
|
+
offset += field.name.length;
|
|
39
|
+
view.setUint16(offset, field.column.length, true);
|
|
40
|
+
offset += 2;
|
|
41
|
+
record.set(field.column, offset);
|
|
42
|
+
offset += field.column.length;
|
|
43
|
+
}
|
|
44
|
+
return record;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function stableId(kindTag: number, record: Uint8Array): number {
|
|
48
|
+
const digest = createHash('sha256').update(Uint8Array.of(kindTag)).update(record).digest();
|
|
49
|
+
return (digest[0] << 16) | (digest[1] << 8) | digest[2];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fragmentHash(fragment: Omit<VocabularyFragment, 'contentHash'>): string {
|
|
53
|
+
const algorithm = encoder.encode(fragment.idAlgorithm);
|
|
54
|
+
const byteLength =
|
|
55
|
+
1 +
|
|
56
|
+
2 +
|
|
57
|
+
algorithm.length +
|
|
58
|
+
4 +
|
|
59
|
+
fragment.ids.length * 4 +
|
|
60
|
+
4 +
|
|
61
|
+
fragment.kindTags.length +
|
|
62
|
+
4 +
|
|
63
|
+
fragment.utf8.length +
|
|
64
|
+
4 +
|
|
65
|
+
fragment.offsets.length * 4;
|
|
66
|
+
const bytes = new Uint8Array(byteLength);
|
|
67
|
+
const view = new DataView(bytes.buffer);
|
|
68
|
+
let offset = 0;
|
|
69
|
+
bytes[offset++] = fragment.schemaVersion;
|
|
70
|
+
view.setUint16(offset, algorithm.length, true);
|
|
71
|
+
offset += 2;
|
|
72
|
+
bytes.set(algorithm, offset);
|
|
73
|
+
offset += algorithm.length;
|
|
74
|
+
view.setUint32(offset, fragment.ids.length, true);
|
|
75
|
+
offset += 4;
|
|
76
|
+
for (const id of fragment.ids) {
|
|
77
|
+
view.setUint32(offset, id, true);
|
|
78
|
+
offset += 4;
|
|
79
|
+
}
|
|
80
|
+
view.setUint32(offset, fragment.kindTags.length, true);
|
|
81
|
+
offset += 4;
|
|
82
|
+
bytes.set(fragment.kindTags, offset);
|
|
83
|
+
offset += fragment.kindTags.length;
|
|
84
|
+
view.setUint32(offset, fragment.utf8.length, true);
|
|
85
|
+
offset += 4;
|
|
86
|
+
bytes.set(fragment.utf8, offset);
|
|
87
|
+
offset += fragment.utf8.length;
|
|
88
|
+
view.setUint32(offset, fragment.offsets.length, true);
|
|
89
|
+
offset += 4;
|
|
90
|
+
for (const boundary of fragment.offsets) {
|
|
91
|
+
view.setInt32(offset, boundary, true);
|
|
92
|
+
offset += 4;
|
|
93
|
+
}
|
|
94
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function makeFragment(
|
|
98
|
+
inputs: readonly RecordInput[],
|
|
99
|
+
kindTags: readonly number[] = inputs.map(() => 1),
|
|
100
|
+
): VocabularyFragment {
|
|
101
|
+
const records = inputs.map(encodeRecord);
|
|
102
|
+
const offsets = new Int32Array(records.length + 1);
|
|
103
|
+
let byteLength = 0;
|
|
104
|
+
for (let ordinal = 0; ordinal < records.length; ordinal++) {
|
|
105
|
+
byteLength += records[ordinal].length;
|
|
106
|
+
offsets[ordinal + 1] = byteLength;
|
|
107
|
+
}
|
|
108
|
+
const utf8 = new Uint8Array(byteLength);
|
|
109
|
+
let offset = 0;
|
|
110
|
+
for (const record of records) {
|
|
111
|
+
utf8.set(record, offset);
|
|
112
|
+
offset += record.length;
|
|
113
|
+
}
|
|
114
|
+
const withoutHash: Omit<VocabularyFragment, 'contentHash'> = {
|
|
115
|
+
schemaVersion: 1,
|
|
116
|
+
idAlgorithm: 'sha256-24-v1',
|
|
117
|
+
ids: Uint32Array.from(records, (record, ordinal) => stableId(kindTags[ordinal], record)),
|
|
118
|
+
kindTags: Uint8Array.from(kindTags),
|
|
119
|
+
utf8,
|
|
120
|
+
offsets,
|
|
121
|
+
};
|
|
122
|
+
return { ...withoutHash, contentHash: fragmentHash(withoutHash) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function textAt(generation: VocabularyGeneration, denseIndex: number): string {
|
|
126
|
+
const record = generation.records.subarray(generation.offsets[denseIndex], generation.offsets[denseIndex + 1]);
|
|
127
|
+
const textLength = new DataView(record.buffer, record.byteOffset, record.byteLength).getUint32(0, true);
|
|
128
|
+
return decoder.decode(record.subarray(4, 4 + textLength));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function decodedBinding(binding: Uint32Array): string[] {
|
|
132
|
+
const generation = getVocabularyGeneration();
|
|
133
|
+
return Array.from(binding, (denseIndex) => textAt(generation, denseIndex));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function expectRegistrationError(fragment: unknown, code: string): void {
|
|
137
|
+
let thrown: unknown;
|
|
138
|
+
try {
|
|
139
|
+
Reflect.apply(registerVocabularyFragment, undefined, [fragment]);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
thrown = error;
|
|
142
|
+
}
|
|
143
|
+
expect(thrown).toBeInstanceOf(VocabularyRegistrationError);
|
|
144
|
+
if (!(thrown instanceof VocabularyRegistrationError)) throw new Error(`expected ${code}`);
|
|
145
|
+
expect(thrown.message.startsWith(`${code}:`)).toBe(true);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
describe('vocabulary fragment registration', () => {
|
|
149
|
+
it('binds fragment ordinals directly while duplicate and overlapping registration preserve append-only generations', () => {
|
|
150
|
+
const alpha = { text: 'registry overlap alpha', fields: [{ name: 'request.id', column: 'request_id' }] };
|
|
151
|
+
const beta = { text: 'registry overlap beta' };
|
|
152
|
+
const gamma = { text: 'registry overlap gamma', fields: [{ name: 'user', column: 'user_name' }] };
|
|
153
|
+
const firstFragment = makeFragment([alpha, beta], [1, 2]);
|
|
154
|
+
const overlappingFragment = makeFragment([beta, gamma], [2, 1]);
|
|
155
|
+
|
|
156
|
+
const before = getVocabularyGeneration();
|
|
157
|
+
const firstBinding = registerVocabularyFragment(firstFragment);
|
|
158
|
+
const firstGeneration = getVocabularyGeneration();
|
|
159
|
+
expect(firstGeneration.generation).toBe(before.generation + 1);
|
|
160
|
+
expect(decodedBinding(firstBinding)).toEqual([alpha.text, beta.text]);
|
|
161
|
+
|
|
162
|
+
const firstSnapshot = {
|
|
163
|
+
ids: Array.from(firstGeneration.ids),
|
|
164
|
+
kinds: Array.from(firstGeneration.kindTags),
|
|
165
|
+
records: Array.from(firstGeneration.records),
|
|
166
|
+
offsets: Array.from(firstGeneration.offsets),
|
|
167
|
+
};
|
|
168
|
+
const overlapBinding = registerVocabularyFragment(overlappingFragment);
|
|
169
|
+
const secondGeneration = getVocabularyGeneration();
|
|
170
|
+
|
|
171
|
+
expect(overlapBinding[0]).toBe(firstBinding[1]);
|
|
172
|
+
expect(decodedBinding(overlapBinding)).toEqual([beta.text, gamma.text]);
|
|
173
|
+
expect(secondGeneration.generation).toBe(firstGeneration.generation + 1);
|
|
174
|
+
expect(Array.from(secondGeneration.ids.subarray(0, firstGeneration.ids.length))).toEqual(firstSnapshot.ids);
|
|
175
|
+
expect(Array.from(secondGeneration.kindTags.subarray(0, firstGeneration.kindTags.length))).toEqual(
|
|
176
|
+
firstSnapshot.kinds,
|
|
177
|
+
);
|
|
178
|
+
expect(Array.from(secondGeneration.records.subarray(0, firstGeneration.records.length))).toEqual(
|
|
179
|
+
firstSnapshot.records,
|
|
180
|
+
);
|
|
181
|
+
expect(Array.from(secondGeneration.offsets.subarray(0, firstGeneration.offsets.length))).toEqual(
|
|
182
|
+
firstSnapshot.offsets,
|
|
183
|
+
);
|
|
184
|
+
expect(Array.from(firstGeneration.ids)).toEqual(firstSnapshot.ids);
|
|
185
|
+
expect(Array.from(firstGeneration.kindTags)).toEqual(firstSnapshot.kinds);
|
|
186
|
+
expect(Array.from(firstGeneration.records)).toEqual(firstSnapshot.records);
|
|
187
|
+
expect(Array.from(firstGeneration.offsets)).toEqual(firstSnapshot.offsets);
|
|
188
|
+
|
|
189
|
+
const duplicateBinding = registerVocabularyFragment(firstFragment);
|
|
190
|
+
expect(duplicateBinding).toBe(firstBinding);
|
|
191
|
+
expect(getVocabularyGeneration()).toBe(secondGeneration);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('decodes each fragment in ordinal order regardless of the records registration order', () => {
|
|
195
|
+
const left = { text: 'registry ordinal left' };
|
|
196
|
+
const right = { text: 'registry ordinal right' };
|
|
197
|
+
const forward = makeFragment([left, right]);
|
|
198
|
+
const reverse = makeFragment([right, left]);
|
|
199
|
+
|
|
200
|
+
const forwardBinding = registerVocabularyFragment(forward);
|
|
201
|
+
const generationAfterForward = getVocabularyGeneration();
|
|
202
|
+
const reverseBinding = registerVocabularyFragment(reverse);
|
|
203
|
+
|
|
204
|
+
expect(decodedBinding(forwardBinding)).toEqual([left.text, right.text]);
|
|
205
|
+
expect(decodedBinding(reverseBinding)).toEqual([right.text, left.text]);
|
|
206
|
+
expect(Array.from(reverseBinding)).toEqual([forwardBinding[1], forwardBinding[0]]);
|
|
207
|
+
expect(getVocabularyGeneration()).toBe(generationAfterForward);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('rejects malformed ABI, offsets, kind tags, record-derived IDs, UTF-8, and payload hashes before mutation', () => {
|
|
211
|
+
const valid = makeFragment([{ text: 'registry validation sentinel' }]);
|
|
212
|
+
const generation = getVocabularyGeneration();
|
|
213
|
+
const malformedCases: Array<{ name: string; fragment: unknown; code: string }> = [
|
|
214
|
+
{
|
|
215
|
+
name: 'schema version',
|
|
216
|
+
fragment: { ...valid, schemaVersion: 2 },
|
|
217
|
+
code: 'LMAO_VOCABULARY_ABI_UNAVAILABLE',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'ID algorithm',
|
|
221
|
+
fragment: { ...valid, idAlgorithm: 'sha256-24-v2' },
|
|
222
|
+
code: 'LMAO_VOCABULARY_ABI_UNAVAILABLE',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'offset coverage',
|
|
226
|
+
fragment: { ...valid, offsets: new Int32Array([1, valid.utf8.length]) },
|
|
227
|
+
code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'nonmonotonic offsets',
|
|
231
|
+
fragment: (() => {
|
|
232
|
+
const malformed = makeFragment([
|
|
233
|
+
{ text: 'registry offset first' },
|
|
234
|
+
{ text: 'registry offset second' },
|
|
235
|
+
{ text: 'registry offset third' },
|
|
236
|
+
]);
|
|
237
|
+
return {
|
|
238
|
+
...malformed,
|
|
239
|
+
offsets: new Int32Array([0, malformed.offsets[2], malformed.offsets[1], malformed.utf8.length]),
|
|
240
|
+
};
|
|
241
|
+
})(),
|
|
242
|
+
code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'unknown kind',
|
|
246
|
+
fragment: makeFragment([{ text: 'registry unknown kind' }], [3]),
|
|
247
|
+
code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'record-derived ID mismatch',
|
|
251
|
+
fragment: (() => {
|
|
252
|
+
const mismatch = makeFragment([{ text: 'registry wrong stable ID' }]);
|
|
253
|
+
const withoutHash = { ...mismatch, ids: Uint32Array.of(mismatch.ids[0] ^ 1) };
|
|
254
|
+
return { ...withoutHash, contentHash: fragmentHash(withoutHash) };
|
|
255
|
+
})(),
|
|
256
|
+
code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'invalid UTF-8 text',
|
|
260
|
+
fragment: (() => {
|
|
261
|
+
const malformed = makeFragment([{ text: 'registry malformed UTF-8' }]);
|
|
262
|
+
const utf8 = malformed.utf8.slice();
|
|
263
|
+
utf8[4] = 0xff;
|
|
264
|
+
return { ...malformed, utf8 };
|
|
265
|
+
})(),
|
|
266
|
+
code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'payload hash mismatch',
|
|
270
|
+
fragment: { ...valid, contentHash: '0'.repeat(64) },
|
|
271
|
+
code: 'LMAO_VOCABULARY_CONTENT_HASH_MISMATCH',
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
for (const testCase of malformedCases) {
|
|
276
|
+
try {
|
|
277
|
+
expectRegistrationError(testCase.fragment, testCase.code);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
throw new Error(`case ${testCase.name} failed`, { cause: error });
|
|
280
|
+
}
|
|
281
|
+
expect(getVocabularyGeneration()).toBe(generation);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('rejects reuse of a registered content hash with a different payload before mutation', () => {
|
|
286
|
+
const registered = makeFragment([{ text: 'registry content hash owner' }]);
|
|
287
|
+
registerVocabularyFragment(registered);
|
|
288
|
+
const generation = getVocabularyGeneration();
|
|
289
|
+
const conflictingPayload = makeFragment([{ text: 'registry content hash impostor' }]);
|
|
290
|
+
|
|
291
|
+
expectRegistrationError(
|
|
292
|
+
{ ...conflictingPayload, contentHash: registered.contentHash },
|
|
293
|
+
'LMAO_VOCABULARY_CONTENT_HASH_COLLISION',
|
|
294
|
+
);
|
|
295
|
+
expect(getVocabularyGeneration()).toBe(generation);
|
|
296
|
+
expect(decodedBinding(registerVocabularyFragment(registered))).toEqual(['registry content hash owner']);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('rejects a stable-ID collision without partially registering the fragment', () => {
|
|
300
|
+
const firstRecord = { text: 'stable collision fixture 585' };
|
|
301
|
+
const secondRecord = { text: 'stable collision fixture 4248' };
|
|
302
|
+
const first = makeFragment([firstRecord]);
|
|
303
|
+
const second = makeFragment([secondRecord]);
|
|
304
|
+
|
|
305
|
+
expect(first.ids[0]).toBe(15_813_490);
|
|
306
|
+
expect(second.ids[0]).toBe(first.ids[0]);
|
|
307
|
+
registerVocabularyFragment(first);
|
|
308
|
+
const generation = getVocabularyGeneration();
|
|
309
|
+
expectRegistrationError(second, 'LMAO_VOCABULARY_ID_COLLISION');
|
|
310
|
+
expect(getVocabularyGeneration()).toBe(generation);
|
|
311
|
+
expect(decodedBinding(registerVocabularyFragment(first))).toEqual([firstRecord.text]);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Table } from '@uwdata/flechette';
|
|
2
|
+
|
|
3
|
+
export interface ArrowLease {
|
|
4
|
+
readonly table: Table;
|
|
5
|
+
readonly released: boolean;
|
|
6
|
+
release(): void;
|
|
7
|
+
[Symbol.dispose](): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Internal constructor for a lease over borrowed Arrow source chunks. */
|
|
11
|
+
export function createArrowLease(
|
|
12
|
+
table: Table,
|
|
13
|
+
pinned: readonly unknown[],
|
|
14
|
+
releases: readonly (() => void)[],
|
|
15
|
+
): ArrowLease {
|
|
16
|
+
let released = false;
|
|
17
|
+
// Retained by the closures for the full lease lifetime. Clearing this array on
|
|
18
|
+
// release makes the backing epochs and dictionary generation collectible.
|
|
19
|
+
const retained = [...pinned];
|
|
20
|
+
return Object.freeze({
|
|
21
|
+
table,
|
|
22
|
+
get released() {
|
|
23
|
+
return released;
|
|
24
|
+
},
|
|
25
|
+
release(): void {
|
|
26
|
+
if (released) return;
|
|
27
|
+
released = true;
|
|
28
|
+
let failure: unknown;
|
|
29
|
+
for (let index = releases.length - 1; index >= 0; index--) {
|
|
30
|
+
try {
|
|
31
|
+
releases[index]();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
failure ??= error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
retained.length = 0;
|
|
37
|
+
if (failure !== undefined) throw failure;
|
|
38
|
+
},
|
|
39
|
+
[Symbol.dispose](): void {
|
|
40
|
+
this.release();
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
package/src/lib/arrow/utils.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { copyBits, countNulls } from '@smoothbricks/arrow-builder';
|
|
6
|
+
import { Column } from '@uwdata/flechette';
|
|
6
7
|
import type { AnySpanBuffer } from '../types.js';
|
|
7
8
|
|
|
8
9
|
const UTF8_ENCODER = new TextEncoder();
|
|
@@ -104,23 +105,18 @@ export function concatenateNullBitmaps(buffers: AnySpanBuffer[], columnName: str
|
|
|
104
105
|
return { nullBitmap, nullCount };
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/** Combine Arrow-native batches by copying only batch references, never values. */
|
|
109
|
+
export function combineColumnChunks(chunks: readonly Column<unknown>[]): Column<unknown> {
|
|
110
|
+
if (chunks.length === 0) throw new Error('Cannot combine an empty Arrow chunk list');
|
|
111
|
+
if (chunks.length === 1) return chunks[0];
|
|
112
|
+
const batches = [];
|
|
113
|
+
for (const chunk of chunks) batches.push(...chunk.data);
|
|
114
|
+
return new Column(batches);
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
/**
|
|
108
118
|
* Get Arrow field name from LMAO field name
|
|
109
119
|
*/
|
|
110
120
|
export function getArrowFieldName(fieldName: string): string {
|
|
111
121
|
return fieldName;
|
|
112
122
|
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Walk a SpanBuffer tree (including overflow chains and children)
|
|
116
|
-
*/
|
|
117
|
-
export function walkSpanTree(root: AnySpanBuffer, visitor: (buffer: AnySpanBuffer) => void): void {
|
|
118
|
-
let current: AnySpanBuffer | undefined = root;
|
|
119
|
-
while (current) {
|
|
120
|
-
visitor(current);
|
|
121
|
-
current = current._overflow;
|
|
122
|
-
}
|
|
123
|
-
for (const child of root._children) {
|
|
124
|
-
walkSpanTree(child, visitor);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Column, utf8 } from '@uwdata/flechette';
|
|
2
|
+
import { ENTRY_TYPE_NAMES } from '../schema/systemSchema.js';
|
|
3
|
+
import type { VocabularyGeneration } from '../vocabularyRegistry.js';
|
|
4
|
+
import { makeArrowColumn } from './flechette.js';
|
|
5
|
+
|
|
6
|
+
const ENTRY_TYPE_UTF8 = new TextEncoder().encode(ENTRY_TYPE_NAMES.join(''));
|
|
7
|
+
const ENTRY_TYPE_OFFSETS = new Int32Array(ENTRY_TYPE_NAMES.length + 1);
|
|
8
|
+
for (let index = 0, offset = 0; index < ENTRY_TYPE_NAMES.length; index++) {
|
|
9
|
+
ENTRY_TYPE_OFFSETS[index] = offset;
|
|
10
|
+
offset += ENTRY_TYPE_NAMES[index].length;
|
|
11
|
+
ENTRY_TYPE_OFFSETS[index + 1] = offset;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function makeUtf8Dictionary(length: number, offsets: Int32Array, values: Uint8Array): Column<unknown> {
|
|
15
|
+
return makeArrowColumn({
|
|
16
|
+
type: utf8(),
|
|
17
|
+
length,
|
|
18
|
+
nullCount: 0,
|
|
19
|
+
offsets,
|
|
20
|
+
values,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const EMPTY_DICTIONARY_BYTES = new Uint8Array(0);
|
|
25
|
+
const EMPTY_DICTIONARY_OFFSETS = new Int32Array([0, 0]);
|
|
26
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
27
|
+
|
|
28
|
+
export interface VocabularyDictionaryPrefix {
|
|
29
|
+
readonly column: Column<unknown>;
|
|
30
|
+
readonly length: number;
|
|
31
|
+
readonly valueToDenseIndex: ReadonlyMap<string, number>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const vocabularyDictionaries = new WeakMap<VocabularyGeneration, VocabularyDictionaryPrefix>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Return the immutable Arrow dictionary prefix for one pinned vocabulary generation.
|
|
38
|
+
* Record text is decoded and copied into Arrow UTF-8 buffers once per generation;
|
|
39
|
+
* every flush thereafter reuses the same column and backing buffers.
|
|
40
|
+
*/
|
|
41
|
+
export function getVocabularyDictionaryPrefix(generation: VocabularyGeneration): VocabularyDictionaryPrefix {
|
|
42
|
+
const cached = vocabularyDictionaries.get(generation);
|
|
43
|
+
if (cached !== undefined) return cached;
|
|
44
|
+
|
|
45
|
+
if (generation.ids.length === 0) {
|
|
46
|
+
const emptyPrefix = Object.freeze({
|
|
47
|
+
column: makeUtf8Dictionary(1, EMPTY_DICTIONARY_OFFSETS, EMPTY_DICTIONARY_BYTES),
|
|
48
|
+
length: 1,
|
|
49
|
+
valueToDenseIndex: new Map<string, number>([['', 0]]),
|
|
50
|
+
});
|
|
51
|
+
emptyPrefix.column.cache();
|
|
52
|
+
vocabularyDictionaries.set(generation, emptyPrefix);
|
|
53
|
+
return emptyPrefix;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const count = generation.ids.length;
|
|
57
|
+
const offsets = new Int32Array(count + 1);
|
|
58
|
+
const textLengths = new Uint32Array(count);
|
|
59
|
+
let byteLength = 0;
|
|
60
|
+
for (let denseIndex = 0; denseIndex < count; denseIndex++) {
|
|
61
|
+
const recordOffset = generation.offsets[denseIndex];
|
|
62
|
+
const textLength = new DataView(
|
|
63
|
+
generation.records.buffer,
|
|
64
|
+
generation.records.byteOffset + recordOffset,
|
|
65
|
+
4,
|
|
66
|
+
).getUint32(0, true);
|
|
67
|
+
textLengths[denseIndex] = textLength;
|
|
68
|
+
byteLength += textLength;
|
|
69
|
+
offsets[denseIndex + 1] = byteLength;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const values = new Uint8Array(byteLength);
|
|
73
|
+
const valueToDenseIndex = new Map<string, number>();
|
|
74
|
+
for (let denseIndex = 0; denseIndex < count; denseIndex++) {
|
|
75
|
+
const textStart = generation.offsets[denseIndex] + 4;
|
|
76
|
+
const text = generation.records.subarray(textStart, textStart + textLengths[denseIndex]);
|
|
77
|
+
values.set(text, offsets[denseIndex]);
|
|
78
|
+
const value = decoder.decode(text);
|
|
79
|
+
if (!valueToDenseIndex.has(value)) valueToDenseIndex.set(value, denseIndex);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const prefix = Object.freeze({
|
|
83
|
+
column: makeUtf8Dictionary(count, offsets, values),
|
|
84
|
+
length: count,
|
|
85
|
+
valueToDenseIndex,
|
|
86
|
+
});
|
|
87
|
+
prefix.column.cache();
|
|
88
|
+
vocabularyDictionaries.set(generation, prefix);
|
|
89
|
+
return prefix;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Append one UTF-8 dictionary batch without copying the prefix buffers or
|
|
94
|
+
* scanning its cached strings. Flechette serializes later dictionary batches
|
|
95
|
+
* as Arrow dictionary deltas.
|
|
96
|
+
*/
|
|
97
|
+
export function appendVocabularyDictionarySuffix(
|
|
98
|
+
prefix: VocabularyDictionaryPrefix,
|
|
99
|
+
suffix: Column<unknown>,
|
|
100
|
+
suffixValues: readonly string[],
|
|
101
|
+
): Column<unknown> {
|
|
102
|
+
const dictionary = new Column([...prefix.column.data, ...suffix.data]);
|
|
103
|
+
const prefixCache = prefix.column.cache();
|
|
104
|
+
const cacheTarget = new Array<unknown>(prefix.length + suffixValues.length);
|
|
105
|
+
dictionary._cache = new Proxy(cacheTarget, {
|
|
106
|
+
get(target, property, receiver) {
|
|
107
|
+
if (typeof property === 'string') {
|
|
108
|
+
const index = Number(property);
|
|
109
|
+
if (Number.isInteger(index) && index >= 0 && index < target.length && String(index) === property) {
|
|
110
|
+
return index < prefix.length ? prefixCache[index] : suffixValues[index - prefix.length];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return Reflect.get(target, property, receiver);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return dictionary;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const PREBUILT_ENTRY_TYPE_DICTIONARY = makeUtf8Dictionary(
|
|
120
|
+
ENTRY_TYPE_NAMES.length,
|
|
121
|
+
ENTRY_TYPE_OFFSETS,
|
|
122
|
+
ENTRY_TYPE_UTF8,
|
|
123
|
+
);
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import type { Table } from '@uwdata/flechette';
|
|
23
23
|
import type { OpMetadata } from './opContext/opTypes.js';
|
|
24
24
|
import type { LogSchema } from './schema/LogSchema.js';
|
|
25
|
+
import type { SpanBufferConstructor } from './spanBuffer.js';
|
|
25
26
|
import type { ITraceRoot } from './traceRoot.js';
|
|
26
27
|
import type { AnySpanBuffer, SpanBuffer } from './types.js';
|
|
27
28
|
|
|
@@ -45,7 +46,13 @@ export interface BufferStrategy<T extends LogSchema = LogSchema> {
|
|
|
45
46
|
* @param capacity - Optional capacity override
|
|
46
47
|
* @returns New SpanBuffer for the root span
|
|
47
48
|
*/
|
|
48
|
-
createSpanBuffer(
|
|
49
|
+
createSpanBuffer(
|
|
50
|
+
schema: T,
|
|
51
|
+
traceRoot: ITraceRoot,
|
|
52
|
+
opMetadata: OpMetadata,
|
|
53
|
+
capacity?: number,
|
|
54
|
+
SpanBufferClass?: SpanBufferConstructor<T>,
|
|
55
|
+
): SpanBuffer<T>;
|
|
49
56
|
|
|
50
57
|
/**
|
|
51
58
|
* Create a child span buffer linked to a parent.
|
|
@@ -65,6 +72,7 @@ export interface BufferStrategy<T extends LogSchema = LogSchema> {
|
|
|
65
72
|
opMetadata: OpMetadata,
|
|
66
73
|
capacity?: number,
|
|
67
74
|
schema?: T,
|
|
75
|
+
SpanBufferClass?: SpanBufferConstructor<T>,
|
|
68
76
|
): SpanBuffer<T>;
|
|
69
77
|
|
|
70
78
|
/**
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* buffer's user schema columns, flattened so downstream SQL transforms need no unnesting.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { resolveEntryType, resolveMessage } from '../resolveMessage.js';
|
|
13
14
|
import { readSpanFieldValue, walkSpanSegments } from '../sqlite/sqlite-common.js';
|
|
14
15
|
import type { AnySpanBuffer } from '../types.js';
|
|
15
16
|
|
|
@@ -46,9 +47,9 @@ export function spanBufferToTraceRows(rootBuffer: AnySpanBuffer): TraceRow[] {
|
|
|
46
47
|
span_id: spanId,
|
|
47
48
|
parent_span_id: parentSpanId,
|
|
48
49
|
row_index: rowOffset + row,
|
|
49
|
-
entry_type: buffer
|
|
50
|
+
entry_type: resolveEntryType(buffer, row),
|
|
50
51
|
timestamp_ns: Number(buffer.timestamp[row]),
|
|
51
|
-
message: buffer
|
|
52
|
+
message: toTraceRowValue(resolveMessage(buffer, row)),
|
|
52
53
|
};
|
|
53
54
|
for (const fieldName of fieldNames) {
|
|
54
55
|
record[fieldName] = toTraceRowValue(readSpanFieldValue(buffer, fieldName, row));
|