@smoothbricks/lmao 0.1.0
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/README.md +269 -0
- package/dist/allocator.wasm +0 -0
- package/dist/bun/preload.d.ts +11 -0
- package/dist/bun/preload.d.ts.map +1 -0
- package/dist/bun/preload.js +10 -0
- package/dist/bun/trace-preload.d.ts +8 -0
- package/dist/bun/trace-preload.d.ts.map +1 -0
- package/dist/bun/trace-preload.js +12 -0
- package/dist/es.d.ts +8 -0
- package/dist/es.d.ts.map +1 -0
- package/dist/es.js +9 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/lib/JsBufferStrategy.d.ts +49 -0
- package/dist/lib/JsBufferStrategy.d.ts.map +1 -0
- package/dist/lib/JsBufferStrategy.js +60 -0
- package/dist/lib/archive/chunkCompaction.d.ts +15 -0
- package/dist/lib/archive/chunkCompaction.d.ts.map +1 -0
- package/dist/lib/archive/chunkCompaction.js +24 -0
- package/dist/lib/archive/chunkEnvelope.d.ts +21 -0
- package/dist/lib/archive/chunkEnvelope.d.ts.map +1 -0
- package/dist/lib/archive/chunkEnvelope.js +55 -0
- package/dist/lib/archive/chunkRouting.d.ts +10 -0
- package/dist/lib/archive/chunkRouting.d.ts.map +1 -0
- package/dist/lib/archive/chunkRouting.js +47 -0
- package/dist/lib/archive/chunkStats.d.ts +12 -0
- package/dist/lib/archive/chunkStats.d.ts.map +1 -0
- package/dist/lib/archive/chunkStats.js +10 -0
- package/dist/lib/archive/index.d.ts +5 -0
- package/dist/lib/archive/index.d.ts.map +1 -0
- package/dist/lib/archive/index.js +4 -0
- package/dist/lib/arrow/capacityStats.d.ts +9 -0
- package/dist/lib/arrow/capacityStats.d.ts.map +1 -0
- package/dist/lib/arrow/capacityStats.js +231 -0
- package/dist/lib/arrow/dictionaries.d.ts +27 -0
- package/dist/lib/arrow/dictionaries.d.ts.map +1 -0
- package/dist/lib/arrow/dictionaries.js +135 -0
- package/dist/lib/arrow/flechette.d.ts +40 -0
- package/dist/lib/arrow/flechette.d.ts.map +1 -0
- package/dist/lib/arrow/flechette.js +67 -0
- package/dist/lib/arrow/utils.d.ts +38 -0
- package/dist/lib/arrow/utils.d.ts.map +1 -0
- package/dist/lib/arrow/utils.js +112 -0
- package/dist/lib/buffer/Nanoseconds.d.ts +109 -0
- package/dist/lib/buffer/Nanoseconds.d.ts.map +1 -0
- package/dist/lib/buffer/Nanoseconds.js +86 -0
- package/dist/lib/bufferStrategy.d.ts +95 -0
- package/dist/lib/bufferStrategy.d.ts.map +1 -0
- package/dist/lib/bufferStrategy.js +1 -0
- package/dist/lib/capacityTuning.d.ts +50 -0
- package/dist/lib/capacityTuning.d.ts.map +1 -0
- package/dist/lib/capacityTuning.js +65 -0
- package/dist/lib/codegen/evaluatorGenerator.d.ts +35 -0
- package/dist/lib/codegen/evaluatorGenerator.d.ts.map +1 -0
- package/dist/lib/codegen/evaluatorGenerator.js +199 -0
- package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts +139 -0
- package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts.map +1 -0
- package/dist/lib/codegen/fixedPositionWriterGenerator.js +289 -0
- package/dist/lib/codegen/spanLoggerGenerator.d.ts +115 -0
- package/dist/lib/codegen/spanLoggerGenerator.d.ts.map +1 -0
- package/dist/lib/codegen/spanLoggerGenerator.js +575 -0
- package/dist/lib/convertToArrow.d.ts +40 -0
- package/dist/lib/convertToArrow.d.ts.map +1 -0
- package/dist/lib/convertToArrow.js +1467 -0
- package/dist/lib/defineOpContext.d.ts +96 -0
- package/dist/lib/defineOpContext.d.ts.map +1 -0
- package/dist/lib/defineOpContext.js +227 -0
- package/dist/lib/errors/Blocked.d.ts +113 -0
- package/dist/lib/errors/Blocked.d.ts.map +1 -0
- package/dist/lib/errors/Blocked.js +68 -0
- package/dist/lib/errors/CodeError.d.ts +101 -0
- package/dist/lib/errors/CodeError.d.ts.map +1 -0
- package/dist/lib/errors/CodeError.js +42 -0
- package/dist/lib/errors/RetriesExhausted.d.ts +67 -0
- package/dist/lib/errors/RetriesExhausted.d.ts.map +1 -0
- package/dist/lib/errors/RetriesExhausted.js +31 -0
- package/dist/lib/errors/Transient.d.ts +115 -0
- package/dist/lib/errors/Transient.d.ts.map +1 -0
- package/dist/lib/errors/Transient.js +54 -0
- package/dist/lib/errors/index.d.ts +14 -0
- package/dist/lib/errors/index.d.ts.map +1 -0
- package/dist/lib/errors/index.js +16 -0
- package/dist/lib/errors/retry-policy.d.ts +60 -0
- package/dist/lib/errors/retry-policy.d.ts.map +1 -0
- package/dist/lib/errors/retry-policy.js +54 -0
- package/dist/lib/flushScheduler.d.ts +162 -0
- package/dist/lib/flushScheduler.d.ts.map +1 -0
- package/dist/lib/flushScheduler.js +388 -0
- package/dist/lib/library.d.ts +149 -0
- package/dist/lib/library.d.ts.map +1 -0
- package/dist/lib/library.js +588 -0
- package/dist/lib/logApi.d.ts +61 -0
- package/dist/lib/logApi.d.ts.map +1 -0
- package/dist/lib/logApi.js +12 -0
- package/dist/lib/logBinding.d.ts +63 -0
- package/dist/lib/logBinding.d.ts.map +1 -0
- package/dist/lib/logBinding.js +1 -0
- package/dist/lib/op.d.ts +105 -0
- package/dist/lib/op.d.ts.map +1 -0
- package/dist/lib/op.js +59 -0
- package/dist/lib/opContext/contextTypes.d.ts +88 -0
- package/dist/lib/opContext/contextTypes.d.ts.map +1 -0
- package/dist/lib/opContext/contextTypes.js +34 -0
- package/dist/lib/opContext/createOpGroup.d.ts +57 -0
- package/dist/lib/opContext/createOpGroup.d.ts.map +1 -0
- package/dist/lib/opContext/createOpGroup.js +341 -0
- package/dist/lib/opContext/defineOp.d.ts +83 -0
- package/dist/lib/opContext/defineOp.d.ts.map +1 -0
- package/dist/lib/opContext/defineOp.js +197 -0
- package/dist/lib/opContext/featureFlagTypes.d.ts +58 -0
- package/dist/lib/opContext/featureFlagTypes.d.ts.map +1 -0
- package/dist/lib/opContext/featureFlagTypes.js +1 -0
- package/dist/lib/opContext/index.d.ts +13 -0
- package/dist/lib/opContext/index.d.ts.map +1 -0
- package/dist/lib/opContext/index.js +15 -0
- package/dist/lib/opContext/opGroupTypes.d.ts +250 -0
- package/dist/lib/opContext/opGroupTypes.d.ts.map +1 -0
- package/dist/lib/opContext/opGroupTypes.js +31 -0
- package/dist/lib/opContext/opTypes.d.ts +48 -0
- package/dist/lib/opContext/opTypes.d.ts.map +1 -0
- package/dist/lib/opContext/opTypes.js +7 -0
- package/dist/lib/opContext/spanContextTypes.d.ts +347 -0
- package/dist/lib/opContext/spanContextTypes.d.ts.map +1 -0
- package/dist/lib/opContext/spanContextTypes.js +1 -0
- package/dist/lib/opContext/types.d.ts +297 -0
- package/dist/lib/opContext/types.d.ts.map +1 -0
- package/dist/lib/opContext/types.js +16 -0
- package/dist/lib/result.d.ts +308 -0
- package/dist/lib/result.d.ts.map +1 -0
- package/dist/lib/result.js +359 -0
- package/dist/lib/schema/LogSchema.d.ts +62 -0
- package/dist/lib/schema/LogSchema.d.ts.map +1 -0
- package/dist/lib/schema/LogSchema.js +134 -0
- package/dist/lib/schema/builder.d.ts +12 -0
- package/dist/lib/schema/builder.d.ts.map +1 -0
- package/dist/lib/schema/builder.js +169 -0
- package/dist/lib/schema/defineFeatureFlags.d.ts +60 -0
- package/dist/lib/schema/defineFeatureFlags.d.ts.map +1 -0
- package/dist/lib/schema/defineFeatureFlags.js +29 -0
- package/dist/lib/schema/defineLogSchema.d.ts +87 -0
- package/dist/lib/schema/defineLogSchema.d.ts.map +1 -0
- package/dist/lib/schema/defineLogSchema.js +171 -0
- package/dist/lib/schema/evaluator.d.ts +267 -0
- package/dist/lib/schema/evaluator.d.ts.map +1 -0
- package/dist/lib/schema/evaluator.js +169 -0
- package/dist/lib/schema/extend.d.ts +14 -0
- package/dist/lib/schema/extend.d.ts.map +1 -0
- package/dist/lib/schema/extend.js +51 -0
- package/dist/lib/schema/systemSchema.d.ts +112 -0
- package/dist/lib/schema/systemSchema.d.ts.map +1 -0
- package/dist/lib/schema/systemSchema.js +260 -0
- package/dist/lib/schema/typeGuards.d.ts +52 -0
- package/dist/lib/schema/typeGuards.d.ts.map +1 -0
- package/dist/lib/schema/typeGuards.js +127 -0
- package/dist/lib/schema/types.d.ts +156 -0
- package/dist/lib/schema/types.d.ts.map +1 -0
- package/dist/lib/schema/types.js +4 -0
- package/dist/lib/spanBuffer.d.ts +133 -0
- package/dist/lib/spanBuffer.d.ts.map +1 -0
- package/dist/lib/spanBuffer.js +442 -0
- package/dist/lib/spanBufferHelpers.d.ts +53 -0
- package/dist/lib/spanBufferHelpers.d.ts.map +1 -0
- package/dist/lib/spanBufferHelpers.js +65 -0
- package/dist/lib/spanBufferStats.d.ts +38 -0
- package/dist/lib/spanBufferStats.d.ts.map +1 -0
- package/dist/lib/spanBufferStats.js +1 -0
- package/dist/lib/spanContext.d.ts +141 -0
- package/dist/lib/spanContext.d.ts.map +1 -0
- package/dist/lib/spanContext.js +763 -0
- package/dist/lib/sqlite/index.d.ts +14 -0
- package/dist/lib/sqlite/index.d.ts.map +1 -0
- package/dist/lib/sqlite/index.js +12 -0
- package/dist/lib/sqlite/sqlite-async-writer.d.ts +24 -0
- package/dist/lib/sqlite/sqlite-async-writer.d.ts.map +1 -0
- package/dist/lib/sqlite/sqlite-async-writer.js +65 -0
- package/dist/lib/sqlite/sqlite-common.d.ts +37 -0
- package/dist/lib/sqlite/sqlite-common.d.ts.map +1 -0
- package/dist/lib/sqlite/sqlite-common.js +207 -0
- package/dist/lib/sqlite/sqlite-d1.d.ts +25 -0
- package/dist/lib/sqlite/sqlite-d1.d.ts.map +1 -0
- package/dist/lib/sqlite/sqlite-d1.js +67 -0
- package/dist/lib/sqlite/sqlite-db.d.ts +29 -0
- package/dist/lib/sqlite/sqlite-db.d.ts.map +1 -0
- package/dist/lib/sqlite/sqlite-db.js +1 -0
- package/dist/lib/sqlite/sqlite-node.d.ts +16 -0
- package/dist/lib/sqlite/sqlite-node.d.ts.map +1 -0
- package/dist/lib/sqlite/sqlite-node.js +68 -0
- package/dist/lib/sqlite/sqlite-writer.d.ts +39 -0
- package/dist/lib/sqlite/sqlite-writer.d.ts.map +1 -0
- package/dist/lib/sqlite/sqlite-writer.js +78 -0
- package/dist/lib/testing/bun-harness.d.ts +150 -0
- package/dist/lib/testing/bun-harness.d.ts.map +1 -0
- package/dist/lib/testing/bun-harness.js +729 -0
- package/dist/lib/testing/extractFacts.d.ts +73 -0
- package/dist/lib/testing/extractFacts.d.ts.map +1 -0
- package/dist/lib/testing/extractFacts.js +237 -0
- package/dist/lib/testing/facts.d.ts +235 -0
- package/dist/lib/testing/facts.d.ts.map +1 -0
- package/dist/lib/testing/facts.js +209 -0
- package/dist/lib/testing/index.d.ts +58 -0
- package/dist/lib/testing/index.d.ts.map +1 -0
- package/dist/lib/testing/index.js +74 -0
- package/dist/lib/testing/queryable-span.d.ts +39 -0
- package/dist/lib/testing/queryable-span.d.ts.map +1 -0
- package/dist/lib/testing/queryable-span.js +58 -0
- package/dist/lib/testing/span-query.d.ts +21 -0
- package/dist/lib/testing/span-query.d.ts.map +1 -0
- package/dist/lib/testing/span-query.js +47 -0
- package/dist/lib/testing/stdio-replay.d.ts +4 -0
- package/dist/lib/testing/stdio-replay.d.ts.map +1 -0
- package/dist/lib/testing/stdio-replay.js +42 -0
- package/dist/lib/testing/trace-query.d.ts +59 -0
- package/dist/lib/testing/trace-query.d.ts.map +1 -0
- package/dist/lib/testing/trace-query.js +462 -0
- package/dist/lib/testing/vitest-harness.d.ts +135 -0
- package/dist/lib/testing/vitest-harness.d.ts.map +1 -0
- package/dist/lib/testing/vitest-harness.js +486 -0
- package/dist/lib/threadId.d.ts +58 -0
- package/dist/lib/threadId.d.ts.map +1 -0
- package/dist/lib/threadId.js +123 -0
- package/dist/lib/traceContext.d.ts +116 -0
- package/dist/lib/traceContext.d.ts.map +1 -0
- package/dist/lib/traceContext.js +37 -0
- package/dist/lib/traceId.d.ts +78 -0
- package/dist/lib/traceId.d.ts.map +1 -0
- package/dist/lib/traceId.js +87 -0
- package/dist/lib/traceRoot.d.ts +133 -0
- package/dist/lib/traceRoot.d.ts.map +1 -0
- package/dist/lib/traceRoot.es.d.ts +75 -0
- package/dist/lib/traceRoot.es.d.ts.map +1 -0
- package/dist/lib/traceRoot.es.js +129 -0
- package/dist/lib/traceRoot.js +19 -0
- package/dist/lib/traceRoot.node.d.ts +77 -0
- package/dist/lib/traceRoot.node.d.ts.map +1 -0
- package/dist/lib/traceRoot.node.js +133 -0
- package/dist/lib/traceRoot.universal.d.ts +3 -0
- package/dist/lib/traceRoot.universal.d.ts.map +1 -0
- package/dist/lib/traceRoot.universal.js +17 -0
- package/dist/lib/tracer.d.ts +296 -0
- package/dist/lib/tracer.d.ts.map +1 -0
- package/dist/lib/tracer.js +346 -0
- package/dist/lib/tracers/ArrayQueueTracer.d.ts +56 -0
- package/dist/lib/tracers/ArrayQueueTracer.d.ts.map +1 -0
- package/dist/lib/tracers/ArrayQueueTracer.js +67 -0
- package/dist/lib/tracers/CompositeTracer.d.ts +24 -0
- package/dist/lib/tracers/CompositeTracer.d.ts.map +1 -0
- package/dist/lib/tracers/CompositeTracer.js +57 -0
- package/dist/lib/tracers/NoOpTracer.d.ts +58 -0
- package/dist/lib/tracers/NoOpTracer.d.ts.map +1 -0
- package/dist/lib/tracers/NoOpTracer.js +41 -0
- package/dist/lib/tracers/SQLiteTracer.d.ts +42 -0
- package/dist/lib/tracers/SQLiteTracer.d.ts.map +1 -0
- package/dist/lib/tracers/SQLiteTracer.js +84 -0
- package/dist/lib/tracers/StdioTracer.d.ts +83 -0
- package/dist/lib/tracers/StdioTracer.d.ts.map +1 -0
- package/dist/lib/tracers/StdioTracer.js +270 -0
- package/dist/lib/tracers/TestTracer.d.ts +95 -0
- package/dist/lib/tracers/TestTracer.d.ts.map +1 -0
- package/dist/lib/tracers/TestTracer.js +87 -0
- package/dist/lib/tracers/index.d.ts +12 -0
- package/dist/lib/tracers/index.d.ts.map +1 -0
- package/dist/lib/tracers/index.js +11 -0
- package/dist/lib/types.d.ts +366 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utf8Cache.d.ts +111 -0
- package/dist/lib/utf8Cache.d.ts.map +1 -0
- package/dist/lib/utf8Cache.js +177 -0
- package/dist/lib/wasm/WasmBufferStrategy.d.ts +88 -0
- package/dist/lib/wasm/WasmBufferStrategy.d.ts.map +1 -0
- package/dist/lib/wasm/WasmBufferStrategy.js +138 -0
- package/dist/lib/wasm/wasmAllocator.d.ts +109 -0
- package/dist/lib/wasm/wasmAllocator.d.ts.map +1 -0
- package/dist/lib/wasm/wasmAllocator.js +227 -0
- package/dist/lib/wasm/wasmSpanBuffer.d.ts +123 -0
- package/dist/lib/wasm/wasmSpanBuffer.d.ts.map +1 -0
- package/dist/lib/wasm/wasmSpanBuffer.js +772 -0
- package/dist/lib/wasm/wasmTraceRoot.d.ts +167 -0
- package/dist/lib/wasm/wasmTraceRoot.d.ts.map +1 -0
- package/dist/lib/wasm/wasmTraceRoot.js +264 -0
- package/dist/node.d.ts +7 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +8 -0
- package/dist/testing.d.ts +18 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +17 -0
- package/package.json +193 -0
- package/src/bun/preload.ts +11 -0
- package/src/bun/trace-preload.ts +14 -0
- package/src/es.ts +11 -0
- package/src/index.ts +194 -0
- package/src/lib/JsBufferStrategy.ts +102 -0
- package/src/lib/__tests__/arrow-builder-integration/arrow-conversion.test.ts +485 -0
- package/src/lib/__tests__/arrow-builder-integration/binary-arrow-conversion.test.ts +390 -0
- package/src/lib/__tests__/arrow-builder-integration/binary-comparison.test.ts +647 -0
- package/src/lib/__tests__/arrow-builder-integration/binary-format-compliance.test.ts +292 -0
- package/src/lib/__tests__/arrow-builder-integration/buffer-chaining.test.ts +354 -0
- package/src/lib/__tests__/arrow-builder-integration/foundation.test.ts +127 -0
- package/src/lib/__tests__/arrow-builder-integration/integration.test.ts +392 -0
- package/src/lib/__tests__/arrow-builder-integration/lazy-columns.test.ts +389 -0
- package/src/lib/__tests__/arrow-conversion-algorithms.test.ts +54 -0
- package/src/lib/__tests__/arrow-test-helpers.ts +71 -0
- package/src/lib/__tests__/binary-e2e.test.ts +203 -0
- package/src/lib/__tests__/buffer-overflow-op-api.test.ts +608 -0
- package/src/lib/__tests__/buffer-overflow.test.ts +612 -0
- package/src/lib/__tests__/capacity-tuning.test.ts +544 -0
- package/src/lib/__tests__/effective-schema.test.ts +439 -0
- package/src/lib/__tests__/flechette.test.ts +42 -0
- package/src/lib/__tests__/flushScheduler.test.ts +537 -0
- package/src/lib/__tests__/integration.test.ts +600 -0
- package/src/lib/__tests__/library.test.ts +934 -0
- package/src/lib/__tests__/nested-library-tasks.test.ts +849 -0
- package/src/lib/__tests__/op-context-binding.test.ts +135 -0
- package/src/lib/__tests__/op-metadata.test.ts +198 -0
- package/src/lib/__tests__/op-type-safety.test.ts +71 -0
- package/src/lib/__tests__/package-exports.test.ts +57 -0
- package/src/lib/__tests__/remapped-buffer-view-integration.test.ts +319 -0
- package/src/lib/__tests__/remapped-buffer-view.test.ts +635 -0
- package/src/lib/__tests__/scope-attributes.test.ts +348 -0
- package/src/lib/__tests__/span-lifecycle.test.ts +680 -0
- package/src/lib/__tests__/sync-async-trace.test.ts +435 -0
- package/src/lib/__tests__/test-helpers.ts +318 -0
- package/src/lib/__tests__/timestamp.test.ts +259 -0
- package/src/lib/__tests__/trace-archive-primitives.test.ts +98 -0
- package/src/lib/__tests__/traceContext-type-enforcement.test.ts +271 -0
- package/src/lib/__tests__/tracer.test.ts +211 -0
- package/src/lib/__tests__/type-narrowing.test.ts +137 -0
- package/src/lib/archive/chunkCompaction.ts +44 -0
- package/src/lib/archive/chunkEnvelope.ts +79 -0
- package/src/lib/archive/chunkRouting.ts +70 -0
- package/src/lib/archive/chunkStats.ts +25 -0
- package/src/lib/archive/index.ts +4 -0
- package/src/lib/arrow/capacityStats.ts +340 -0
- package/src/lib/arrow/dictionaries.ts +159 -0
- package/src/lib/arrow/flechette.ts +114 -0
- package/src/lib/arrow/utils.ts +126 -0
- package/src/lib/buffer/Nanoseconds.ts +131 -0
- package/src/lib/bufferStrategy.ts +106 -0
- package/src/lib/capacityTuning.ts +102 -0
- package/src/lib/codegen/__tests__/__snapshots__/evaluatorGenerator.test.ts.snap +160 -0
- package/src/lib/codegen/__tests__/__snapshots__/scopeGenerator.test.ts.snap +196 -0
- package/src/lib/codegen/__tests__/__snapshots__/spanLoggerGenerator.test.ts.snap +1965 -0
- package/src/lib/codegen/__tests__/evaluatorGenerator.test.ts +516 -0
- package/src/lib/codegen/__tests__/nullBitmap.test.ts +207 -0
- package/src/lib/codegen/__tests__/spanLoggerGenerator.test.ts +237 -0
- package/src/lib/codegen/evaluatorGenerator.ts +309 -0
- package/src/lib/codegen/fixedPositionWriterGenerator.ts +444 -0
- package/src/lib/codegen/spanLoggerGenerator.ts +715 -0
- package/src/lib/convertToArrow.ts +1805 -0
- package/src/lib/defineOpContext.ts +398 -0
- package/src/lib/errors/Blocked.ts +165 -0
- package/src/lib/errors/CodeError.ts +132 -0
- package/src/lib/errors/RetriesExhausted.ts +78 -0
- package/src/lib/errors/Transient.ts +168 -0
- package/src/lib/errors/__tests__/Blocked-config.test.ts +152 -0
- package/src/lib/errors/__tests__/Transient.test.ts +129 -0
- package/src/lib/errors/__tests__/retry-policy.test.ts +85 -0
- package/src/lib/errors/index.ts +24 -0
- package/src/lib/errors/retry-policy.ts +87 -0
- package/src/lib/flushScheduler.ts +509 -0
- package/src/lib/library.ts +682 -0
- package/src/lib/logApi.ts +70 -0
- package/src/lib/logBinding.ts +67 -0
- package/src/lib/op.ts +103 -0
- package/src/lib/opContext/__tests__/retry.test.ts +764 -0
- package/src/lib/opContext/contextTypes.ts +130 -0
- package/src/lib/opContext/createOpGroup.ts +456 -0
- package/src/lib/opContext/defineOp.ts +281 -0
- package/src/lib/opContext/featureFlagTypes.ts +54 -0
- package/src/lib/opContext/index.ts +16 -0
- package/src/lib/opContext/opGroupTypes.ts +342 -0
- package/src/lib/opContext/opTypes.ts +80 -0
- package/src/lib/opContext/spanContextTypes.ts +398 -0
- package/src/lib/opContext/types.ts +403 -0
- package/src/lib/result.ts +550 -0
- package/src/lib/schema/LogSchema.ts +153 -0
- package/src/lib/schema/__tests__/defineLogSchema.test.ts +342 -0
- package/src/lib/schema/__tests__/featureFlags.test.ts +443 -0
- package/src/lib/schema/builder.ts +201 -0
- package/src/lib/schema/defineFeatureFlags.ts +91 -0
- package/src/lib/schema/defineLogSchema.ts +291 -0
- package/src/lib/schema/evaluator.ts +404 -0
- package/src/lib/schema/extend.ts +69 -0
- package/src/lib/schema/systemSchema.ts +339 -0
- package/src/lib/schema/typeGuards.ts +163 -0
- package/src/lib/schema/types.ts +265 -0
- package/src/lib/spanBuffer.ts +571 -0
- package/src/lib/spanBufferHelpers.ts +73 -0
- package/src/lib/spanBufferStats.ts +40 -0
- package/src/lib/spanContext.ts +1566 -0
- package/src/lib/sqlite/__tests__/sqlite-common.test.ts +89 -0
- package/src/lib/sqlite/index.ts +26 -0
- package/src/lib/sqlite/sqlite-async-writer.ts +101 -0
- package/src/lib/sqlite/sqlite-common.ts +309 -0
- package/src/lib/sqlite/sqlite-d1.ts +104 -0
- package/src/lib/sqlite/sqlite-db.ts +32 -0
- package/src/lib/sqlite/sqlite-node.ts +83 -0
- package/src/lib/sqlite/sqlite-writer.ts +128 -0
- package/src/lib/testing/TRACE-TESTING.md +100 -0
- package/src/lib/testing/__tests__/bun-harness.test.ts +108 -0
- package/src/lib/testing/__tests__/extractFacts.test.ts +279 -0
- package/src/lib/testing/__tests__/facts.test.ts +324 -0
- package/src/lib/testing/__tests__/vitest-harness.test.ts +32 -0
- package/src/lib/testing/bun-harness.ts +990 -0
- package/src/lib/testing/extractFacts.ts +362 -0
- package/src/lib/testing/facts.ts +470 -0
- package/src/lib/testing/index.ts +141 -0
- package/src/lib/testing/queryable-span.ts +108 -0
- package/src/lib/testing/span-query.ts +65 -0
- package/src/lib/testing/stdio-replay.ts +50 -0
- package/src/lib/testing/trace-query.ts +329 -0
- package/src/lib/testing/vitest-harness.ts +691 -0
- package/src/lib/threadId.ts +135 -0
- package/src/lib/traceContext.ts +182 -0
- package/src/lib/traceId.ts +142 -0
- package/src/lib/traceRoot.es.ts +157 -0
- package/src/lib/traceRoot.node.ts +170 -0
- package/src/lib/traceRoot.ts +154 -0
- package/src/lib/traceRoot.universal.ts +22 -0
- package/src/lib/tracer.ts +896 -0
- package/src/lib/tracers/ArrayQueueTracer.ts +76 -0
- package/src/lib/tracers/CompositeTracer.ts +76 -0
- package/src/lib/tracers/NoOpTracer.ts +73 -0
- package/src/lib/tracers/SQLiteTracer.ts +115 -0
- package/src/lib/tracers/StdioTracer.ts +380 -0
- package/src/lib/tracers/TestTracer.ts +146 -0
- package/src/lib/tracers/__tests__/ArrayQueueTracer.test.ts +184 -0
- package/src/lib/tracers/__tests__/NoOpTracer.test.ts +204 -0
- package/src/lib/tracers/__tests__/StdioTracer.test.ts +313 -0
- package/src/lib/tracers/__tests__/TestTracer.test.ts +200 -0
- package/src/lib/tracers/index.ts +12 -0
- package/src/lib/types.ts +498 -0
- package/src/lib/utf8Cache.ts +201 -0
- package/src/lib/wasm/WasmBufferStrategy.ts +219 -0
- package/src/lib/wasm/__tests__/WasmBufferStrategy.test.ts +256 -0
- package/src/lib/wasm/__tests__/wasm-integration.test.ts +476 -0
- package/src/lib/wasm/__tests__/wasmAllocator.test.ts +699 -0
- package/src/lib/wasm/__tests__/wasmSpanBuffer.test.ts +449 -0
- package/src/lib/wasm/__tests__/wasmTraceRoot.test.ts +390 -0
- package/src/lib/wasm/__tests__/wasmTracer.test.ts.disabled +444 -0
- package/src/lib/wasm/allocator.zig +945 -0
- package/src/lib/wasm/wasmAllocator.ts +469 -0
- package/src/lib/wasm/wasmSpanBuffer.ts +1014 -0
- package/src/lib/wasm/wasmTraceRoot.ts +315 -0
- package/src/node.ts +10 -0
- package/src/testing.ts +18 -0
|
@@ -0,0 +1,1805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-copy conversion from SpanBuffer to Flechette tables
|
|
3
|
+
*
|
|
4
|
+
* Per specs/lmao/01f_arrow_table_structure.md:
|
|
5
|
+
* - Enum columns: Dictionary with compile-time values
|
|
6
|
+
* - Category columns: Dictionary with runtime-built values
|
|
7
|
+
* - Text columns: Plain strings without dictionary
|
|
8
|
+
* - Zero-copy wrap TypedArrays as Arrow vectors
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
clearBitRange,
|
|
13
|
+
compareStrings,
|
|
14
|
+
createSortedDictionary,
|
|
15
|
+
DictionaryBuilder,
|
|
16
|
+
type FinalizedDictionary,
|
|
17
|
+
getMaskTransform,
|
|
18
|
+
type PreEncodedEntry,
|
|
19
|
+
sortInPlace,
|
|
20
|
+
} from '@smoothbricks/arrow-builder';
|
|
21
|
+
import {
|
|
22
|
+
batchType,
|
|
23
|
+
binary,
|
|
24
|
+
bool,
|
|
25
|
+
Column,
|
|
26
|
+
type DataType,
|
|
27
|
+
dictionary,
|
|
28
|
+
float64,
|
|
29
|
+
type IntType,
|
|
30
|
+
int8,
|
|
31
|
+
type Table,
|
|
32
|
+
TimeUnit,
|
|
33
|
+
tableFromColumns,
|
|
34
|
+
timestamp,
|
|
35
|
+
uint8,
|
|
36
|
+
uint16,
|
|
37
|
+
uint32,
|
|
38
|
+
uint64,
|
|
39
|
+
utf8,
|
|
40
|
+
} from '@uwdata/flechette';
|
|
41
|
+
import { type CapacityStatsEntry, createCapacityStatsTable } from './arrow/capacityStats.js';
|
|
42
|
+
import { buildSortedCategoryDictionary, buildTextDictionary } from './arrow/dictionaries.js';
|
|
43
|
+
import {
|
|
44
|
+
type DictionaryColumnData,
|
|
45
|
+
type GenericColumnData,
|
|
46
|
+
getArrowIndexArrayConstructorOr,
|
|
47
|
+
getArrowIndexTypeOr,
|
|
48
|
+
makeArrowColumn,
|
|
49
|
+
type Utf8ColumnData,
|
|
50
|
+
} from './arrow/flechette.js';
|
|
51
|
+
import {
|
|
52
|
+
calculateUtf8Offsets,
|
|
53
|
+
concatenateFloat64Arrays,
|
|
54
|
+
concatenateNullBitmaps,
|
|
55
|
+
concatenateUint8Arrays,
|
|
56
|
+
encodeUtf8Strings,
|
|
57
|
+
getArrowFieldName,
|
|
58
|
+
walkSpanTree,
|
|
59
|
+
} from './arrow/utils.js';
|
|
60
|
+
import { ENTRY_TYPE_NAMES, SYSTEM_SCHEMA_FIELD_NAMES } from './schema/systemSchema.js';
|
|
61
|
+
import { getBinaryEncoder, getEnumUtf8, getEnumValues, getSchemaType } from './schema/typeGuards.js';
|
|
62
|
+
import type { LogSchema } from './schema/types.js';
|
|
63
|
+
import type { AnySpanBuffer, OpMetadata } from './types.js';
|
|
64
|
+
import { globalUtf8Cache } from './utf8Cache.js';
|
|
65
|
+
|
|
66
|
+
const DictBuilder = DictionaryBuilder;
|
|
67
|
+
const F64Array = Float64Array;
|
|
68
|
+
|
|
69
|
+
type BuiltArrowColumn = { column: Column<unknown>; nullCount: number };
|
|
70
|
+
type BuiltMetadataColumns = { fields: string[]; vectors: Column<unknown>[] };
|
|
71
|
+
|
|
72
|
+
export type SystemColumnBuilder = (
|
|
73
|
+
buffer: AnySpanBuffer,
|
|
74
|
+
buffers: AnySpanBuffer[],
|
|
75
|
+
totalRows: number,
|
|
76
|
+
) => BuiltMetadataColumns;
|
|
77
|
+
|
|
78
|
+
const EMPTY_VALIDITY = new Uint8Array(0);
|
|
79
|
+
const BINARY_TYPE = binary();
|
|
80
|
+
type ArrowColumnDataBase<TType, TValues = unknown> = {
|
|
81
|
+
type: TType;
|
|
82
|
+
length: number;
|
|
83
|
+
nullCount: number;
|
|
84
|
+
data?: TValues;
|
|
85
|
+
nullBitmap?: Uint8Array;
|
|
86
|
+
};
|
|
87
|
+
type DictionaryColumnBuildData = ArrowColumnDataBase<ReturnType<typeof dictionary>> & {
|
|
88
|
+
dictionary: Column<unknown>;
|
|
89
|
+
};
|
|
90
|
+
type Utf8ColumnBuildData = ArrowColumnDataBase<ReturnType<typeof utf8>, Uint8Array> & {
|
|
91
|
+
valueOffsets: Int32Array;
|
|
92
|
+
};
|
|
93
|
+
type GenericColumnBuildData<TType extends DataType = DataType, TValues = unknown> = ArrowColumnDataBase<
|
|
94
|
+
TType,
|
|
95
|
+
TValues
|
|
96
|
+
> & {
|
|
97
|
+
dictionary?: never;
|
|
98
|
+
valueOffsets?: never;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function buildData<T extends Record<string, unknown>>(data: T): T {
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildColumn(data: DictionaryColumnBuildData): Column<unknown>;
|
|
106
|
+
function buildColumn(data: Utf8ColumnBuildData): Column<unknown>;
|
|
107
|
+
function buildColumn(data: GenericColumnBuildData): Column<unknown>;
|
|
108
|
+
function buildColumn(data: DictionaryColumnBuildData | Utf8ColumnBuildData | GenericColumnBuildData): Column<unknown> {
|
|
109
|
+
if (isDictionaryColumnBuildData(data)) {
|
|
110
|
+
const dictionaryData: DictionaryColumnData = {
|
|
111
|
+
type: data.type,
|
|
112
|
+
length: data.length,
|
|
113
|
+
nullCount: data.nullCount,
|
|
114
|
+
values: data.data,
|
|
115
|
+
validity: data.nullBitmap,
|
|
116
|
+
dictionary: data.dictionary,
|
|
117
|
+
};
|
|
118
|
+
return makeArrowColumn(dictionaryData);
|
|
119
|
+
}
|
|
120
|
+
if (isUtf8ColumnBuildData(data)) {
|
|
121
|
+
const utf8Data: Utf8ColumnData = {
|
|
122
|
+
type: data.type,
|
|
123
|
+
length: data.length,
|
|
124
|
+
nullCount: data.nullCount,
|
|
125
|
+
values: data.data,
|
|
126
|
+
validity: data.nullBitmap,
|
|
127
|
+
offsets: data.valueOffsets,
|
|
128
|
+
};
|
|
129
|
+
return makeArrowColumn(utf8Data);
|
|
130
|
+
}
|
|
131
|
+
const genericData: GenericColumnData<DataType> = {
|
|
132
|
+
type: data.type,
|
|
133
|
+
length: data.length,
|
|
134
|
+
nullCount: data.nullCount,
|
|
135
|
+
values: data.data,
|
|
136
|
+
validity: data.nullBitmap,
|
|
137
|
+
};
|
|
138
|
+
return makeArrowColumn(genericData);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isDictionaryColumnBuildData(
|
|
142
|
+
data: DictionaryColumnBuildData | Utf8ColumnBuildData | GenericColumnBuildData,
|
|
143
|
+
): data is DictionaryColumnBuildData {
|
|
144
|
+
return 'dictionary' in data && data.dictionary instanceof Column;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isUtf8ColumnBuildData(
|
|
148
|
+
data: DictionaryColumnBuildData | Utf8ColumnBuildData | GenericColumnBuildData,
|
|
149
|
+
): data is Utf8ColumnBuildData {
|
|
150
|
+
return 'valueOffsets' in data && data.valueOffsets instanceof Int32Array;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildTable(fields: string[], vectors: Column<unknown>[]): Table {
|
|
154
|
+
if (fields.length === 0) return tableFromColumns({});
|
|
155
|
+
const columns: Record<string, Column<unknown>> = {};
|
|
156
|
+
for (let index = 0; index < fields.length; index++) {
|
|
157
|
+
columns[fields[index]] = vectors[index];
|
|
158
|
+
}
|
|
159
|
+
return tableFromColumns(columns);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function appendTables(base: Table, extra: Table): Table {
|
|
163
|
+
const columns: Record<string, Column<unknown>> = {};
|
|
164
|
+
for (let i = 0; i < base.numCols; i++) {
|
|
165
|
+
const name = base.names[i];
|
|
166
|
+
const left = base.getChildAt(i);
|
|
167
|
+
const right = extra.getChild(name);
|
|
168
|
+
if (!left || !right) {
|
|
169
|
+
throw new Error(`Cannot append tables: missing column '${name}'`);
|
|
170
|
+
}
|
|
171
|
+
columns[name] = new Column([...left.data, ...right.data]);
|
|
172
|
+
}
|
|
173
|
+
return tableFromColumns(columns);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getAllocatedStringColumn(buffer: AnySpanBuffer, columnName: string): string[] | undefined {
|
|
177
|
+
const column = buffer.getColumnIfAllocated(columnName);
|
|
178
|
+
if (!Array.isArray(column)) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
return isStringArray(column) ? column : undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getAllocatedBinaryColumn(buffer: AnySpanBuffer, columnName: string): unknown[] | undefined {
|
|
185
|
+
const column = buffer.getColumnIfAllocated(columnName);
|
|
186
|
+
return Array.isArray(column) ? column : undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getStringScopeValue(buffer: AnySpanBuffer, fieldName: string): string | undefined {
|
|
190
|
+
const value = buffer._scopeValues?.[fieldName];
|
|
191
|
+
return typeof value === 'string' ? value : undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getNumberScopeValue(buffer: AnySpanBuffer, fieldName: string): number | undefined {
|
|
195
|
+
const value = buffer._scopeValues?.[fieldName];
|
|
196
|
+
return typeof value === 'number' ? value : undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getBooleanScopeValue(buffer: AnySpanBuffer, fieldName: string): boolean | undefined {
|
|
200
|
+
const value = buffer._scopeValues?.[fieldName];
|
|
201
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function requireUint8Array(value: unknown, message: string): Uint8Array {
|
|
205
|
+
if (!(value instanceof Uint8Array)) {
|
|
206
|
+
throw new Error(message);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isStringArray(value: unknown): value is string[] {
|
|
212
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string' || entry == null);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function indexTypeForCount(count: number): IntType {
|
|
216
|
+
if (count <= 255) return uint8();
|
|
217
|
+
if (count <= 65535) return uint16();
|
|
218
|
+
return uint32();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
222
|
+
// Shared metadata column helpers (used by both buildMetadataColumnsWithFields
|
|
223
|
+
// and convertBuffersWithSharedDicts to avoid duplication)
|
|
224
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Build non-dictionary identity columns: timestamp, thread_id, span_id, parent_thread_id, parent_span_id.
|
|
228
|
+
* These are identical in both single-batch and shared-dict paths.
|
|
229
|
+
*
|
|
230
|
+
* Callers are responsible for inserting trace_id (dictionary column) at the correct position
|
|
231
|
+
* and entry_type (dictionary column) after these columns.
|
|
232
|
+
*/
|
|
233
|
+
function buildNonDictIdentityColumns(
|
|
234
|
+
buffers: AnySpanBuffer[],
|
|
235
|
+
totalRows: number,
|
|
236
|
+
fields: string[],
|
|
237
|
+
vectors: Column<unknown>[],
|
|
238
|
+
): void {
|
|
239
|
+
// thread_id (BigUint64Array, constant per buffer)
|
|
240
|
+
const threadIds = new BigUint64Array(totalRows);
|
|
241
|
+
let offset = 0;
|
|
242
|
+
for (const buf of buffers) {
|
|
243
|
+
threadIds.fill(buf.thread_id, offset, offset + buf._writeIndex);
|
|
244
|
+
offset += buf._writeIndex;
|
|
245
|
+
}
|
|
246
|
+
fields.push('thread_id');
|
|
247
|
+
vectors.push(buildColumn(buildData({ type: uint64(), offset: 0, length: totalRows, nullCount: 0, data: threadIds })));
|
|
248
|
+
|
|
249
|
+
// span_id (Uint32Array, constant per buffer)
|
|
250
|
+
const spanIds = new Uint32Array(totalRows);
|
|
251
|
+
offset = 0;
|
|
252
|
+
for (const buf of buffers) {
|
|
253
|
+
spanIds.fill(buf.span_id, offset, offset + buf._writeIndex);
|
|
254
|
+
offset += buf._writeIndex;
|
|
255
|
+
}
|
|
256
|
+
fields.push('span_id');
|
|
257
|
+
vectors.push(buildColumn(buildData({ type: uint32(), offset: 0, length: totalRows, nullCount: 0, data: spanIds })));
|
|
258
|
+
|
|
259
|
+
// parent_thread_id (nullable BigUint64Array)
|
|
260
|
+
const parentThreadIds = new BigUint64Array(totalRows);
|
|
261
|
+
const parentThreadIdNulls = new Uint8Array(Math.ceil(totalRows / 8));
|
|
262
|
+
parentThreadIdNulls.fill(0xff);
|
|
263
|
+
let parentThreadIdNullCount = 0;
|
|
264
|
+
offset = 0;
|
|
265
|
+
for (const buf of buffers) {
|
|
266
|
+
if (buf._parent) {
|
|
267
|
+
parentThreadIds.fill(buf.parent_thread_id, offset, offset + buf._writeIndex);
|
|
268
|
+
} else {
|
|
269
|
+
clearBitRange(parentThreadIdNulls, offset, buf._writeIndex);
|
|
270
|
+
parentThreadIdNullCount += buf._writeIndex;
|
|
271
|
+
}
|
|
272
|
+
offset += buf._writeIndex;
|
|
273
|
+
}
|
|
274
|
+
fields.push('parent_thread_id');
|
|
275
|
+
vectors.push(
|
|
276
|
+
buildColumn(
|
|
277
|
+
buildData({
|
|
278
|
+
type: uint64(),
|
|
279
|
+
offset: 0,
|
|
280
|
+
length: totalRows,
|
|
281
|
+
nullCount: parentThreadIdNullCount,
|
|
282
|
+
data: parentThreadIds,
|
|
283
|
+
nullBitmap: parentThreadIdNullCount > 0 ? parentThreadIdNulls : undefined,
|
|
284
|
+
}),
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// parent_span_id (nullable Uint32Array)
|
|
289
|
+
const parentSpanIds = new Uint32Array(totalRows);
|
|
290
|
+
const parentSpanIdNulls = new Uint8Array(Math.ceil(totalRows / 8));
|
|
291
|
+
parentSpanIdNulls.fill(0xff);
|
|
292
|
+
let parentSpanIdNullCount = 0;
|
|
293
|
+
offset = 0;
|
|
294
|
+
for (const buf of buffers) {
|
|
295
|
+
if (buf._hasParent) {
|
|
296
|
+
parentSpanIds.fill(buf.parent_span_id, offset, offset + buf._writeIndex);
|
|
297
|
+
} else {
|
|
298
|
+
clearBitRange(parentSpanIdNulls, offset, buf._writeIndex);
|
|
299
|
+
parentSpanIdNullCount += buf._writeIndex;
|
|
300
|
+
}
|
|
301
|
+
offset += buf._writeIndex;
|
|
302
|
+
}
|
|
303
|
+
fields.push('parent_span_id');
|
|
304
|
+
vectors.push(
|
|
305
|
+
buildColumn(
|
|
306
|
+
buildData({
|
|
307
|
+
type: uint32(),
|
|
308
|
+
offset: 0,
|
|
309
|
+
length: totalRows,
|
|
310
|
+
nullCount: parentSpanIdNullCount,
|
|
311
|
+
data: parentSpanIds,
|
|
312
|
+
nullBitmap: parentSpanIdNullCount > 0 ? parentSpanIdNulls : undefined,
|
|
313
|
+
}),
|
|
314
|
+
),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Build timestamp column from buffers.
|
|
320
|
+
*/
|
|
321
|
+
function buildTimestampColumn(
|
|
322
|
+
buffers: AnySpanBuffer[],
|
|
323
|
+
totalRows: number,
|
|
324
|
+
fields: string[],
|
|
325
|
+
vectors: Column<unknown>[],
|
|
326
|
+
): void {
|
|
327
|
+
const allTimestamps = new BigInt64Array(totalRows);
|
|
328
|
+
let offset = 0;
|
|
329
|
+
for (const buf of buffers) {
|
|
330
|
+
if (!buf.timestamp) {
|
|
331
|
+
throw new Error(`Buffer missing timestamps property (_writeIndex: ${buf._writeIndex})`);
|
|
332
|
+
}
|
|
333
|
+
allTimestamps.set(buf.timestamp.subarray(0, buf._writeIndex), offset);
|
|
334
|
+
offset += buf._writeIndex;
|
|
335
|
+
}
|
|
336
|
+
fields.push('timestamp');
|
|
337
|
+
vectors.push(
|
|
338
|
+
buildColumn(
|
|
339
|
+
buildData({
|
|
340
|
+
type: timestamp(TimeUnit.NANOSECOND),
|
|
341
|
+
offset: 0,
|
|
342
|
+
length: totalRows,
|
|
343
|
+
nullCount: 0,
|
|
344
|
+
data: allTimestamps,
|
|
345
|
+
}),
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Build entry_type dictionary column.
|
|
352
|
+
*/
|
|
353
|
+
function buildEntryTypeColumn(
|
|
354
|
+
buffers: AnySpanBuffer[],
|
|
355
|
+
totalRows: number,
|
|
356
|
+
fields: string[],
|
|
357
|
+
vectors: Column<unknown>[],
|
|
358
|
+
dictType?: ReturnType<typeof dictionary>,
|
|
359
|
+
): void {
|
|
360
|
+
const entryTypeIndices = new Int8Array(totalRows);
|
|
361
|
+
let offset = 0;
|
|
362
|
+
for (const buf of buffers) {
|
|
363
|
+
if (!buf.entry_type) {
|
|
364
|
+
throw new Error(`Buffer missing operations property (_writeIndex: ${buf._writeIndex})`);
|
|
365
|
+
}
|
|
366
|
+
entryTypeIndices.set(buf.entry_type.subarray(0, buf._writeIndex), offset);
|
|
367
|
+
offset += buf._writeIndex;
|
|
368
|
+
}
|
|
369
|
+
const entryTypeDictData = buildData({
|
|
370
|
+
type: utf8(),
|
|
371
|
+
offset: 0,
|
|
372
|
+
length: ENTRY_TYPE_NAMES.length,
|
|
373
|
+
nullCount: 0,
|
|
374
|
+
valueOffsets: calculateUtf8Offsets(ENTRY_TYPE_NAMES),
|
|
375
|
+
data: encodeUtf8Strings(ENTRY_TYPE_NAMES),
|
|
376
|
+
});
|
|
377
|
+
fields.push('entry_type');
|
|
378
|
+
vectors.push(
|
|
379
|
+
buildColumn(
|
|
380
|
+
buildData({
|
|
381
|
+
type: dictType ?? dictionary(utf8(), int8()),
|
|
382
|
+
offset: 0,
|
|
383
|
+
length: totalRows,
|
|
384
|
+
nullCount: 0,
|
|
385
|
+
data: entryTypeIndices,
|
|
386
|
+
dictionary: buildColumn(entryTypeDictData),
|
|
387
|
+
}),
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Build a per-buffer metadata dictionary column (package_name, package_file, git_sha).
|
|
394
|
+
* Row 0 uses callsiteMetadata, rows 1+ use opMetadata (per context flow spec).
|
|
395
|
+
*/
|
|
396
|
+
function buildPerBufferDictColumn(
|
|
397
|
+
buffers: AnySpanBuffer[],
|
|
398
|
+
totalRows: number,
|
|
399
|
+
fieldName: string,
|
|
400
|
+
metadataGetter: (m: OpMetadata) => string,
|
|
401
|
+
dictType: ReturnType<typeof dictionary>,
|
|
402
|
+
dictValues: string[],
|
|
403
|
+
dictMap: Map<string, number>,
|
|
404
|
+
dictUtf8Offsets: Int32Array,
|
|
405
|
+
dictUtf8Data: Uint8Array,
|
|
406
|
+
fields: string[],
|
|
407
|
+
vectors: Column<unknown>[],
|
|
408
|
+
): void {
|
|
409
|
+
const uniqueCount = dictValues.length;
|
|
410
|
+
const IndexArrayCtor = uniqueCount <= 255 ? Uint8Array : uniqueCount <= 65535 ? Uint16Array : Uint32Array;
|
|
411
|
+
const indices = new IndexArrayCtor(totalRows);
|
|
412
|
+
let offset = 0;
|
|
413
|
+
for (const buf of buffers) {
|
|
414
|
+
const callsiteValue = metadataGetter(buf._callsiteMetadata ?? buf._opMetadata);
|
|
415
|
+
const callsiteIdx = dictMap.get(callsiteValue) ?? 0;
|
|
416
|
+
const opIdx = dictMap.get(metadataGetter(buf._opMetadata)) ?? 0;
|
|
417
|
+
indices[offset] = callsiteIdx;
|
|
418
|
+
if (buf._writeIndex > 1) {
|
|
419
|
+
indices.fill(opIdx, offset + 1, offset + buf._writeIndex);
|
|
420
|
+
}
|
|
421
|
+
offset += buf._writeIndex;
|
|
422
|
+
}
|
|
423
|
+
fields.push(fieldName);
|
|
424
|
+
vectors.push(
|
|
425
|
+
buildColumn(
|
|
426
|
+
buildData({
|
|
427
|
+
type: dictType,
|
|
428
|
+
offset: 0,
|
|
429
|
+
length: totalRows,
|
|
430
|
+
nullCount: 0,
|
|
431
|
+
data: indices,
|
|
432
|
+
dictionary: buildColumn(
|
|
433
|
+
buildData({
|
|
434
|
+
type: utf8(),
|
|
435
|
+
offset: 0,
|
|
436
|
+
length: dictValues.length,
|
|
437
|
+
nullCount: 0,
|
|
438
|
+
valueOffsets: dictUtf8Offsets,
|
|
439
|
+
data: dictUtf8Data,
|
|
440
|
+
}),
|
|
441
|
+
),
|
|
442
|
+
}),
|
|
443
|
+
),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Build a binary Arrow column from SpanBuffer data.
|
|
449
|
+
* Shared by both convertBuffersToTable and convertBuffersWithSharedDicts.
|
|
450
|
+
*
|
|
451
|
+
* Binary columns store arbitrary bytes (raw Uint8Array or encoder-encoded objects).
|
|
452
|
+
* At flush time, the encoder (if present) transforms stored values into Uint8Array.
|
|
453
|
+
*/
|
|
454
|
+
function buildBinaryColumnFromBuffers(
|
|
455
|
+
buffers: AnySpanBuffer[],
|
|
456
|
+
columnName: string,
|
|
457
|
+
totalRows: number,
|
|
458
|
+
fieldSchema: unknown,
|
|
459
|
+
): BuiltArrowColumn {
|
|
460
|
+
const encoder = getBinaryEncoder(fieldSchema);
|
|
461
|
+
|
|
462
|
+
// Collect all values from buffers, encode each if encoder is present
|
|
463
|
+
const allValues: (Uint8Array | null)[] = [];
|
|
464
|
+
for (const buf of buffers) {
|
|
465
|
+
const col = getAllocatedBinaryColumn(buf, columnName);
|
|
466
|
+
const srcNulls = buf.getNullsIfAllocated(columnName);
|
|
467
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
468
|
+
if (srcNulls) {
|
|
469
|
+
const isValid = (srcNulls[i >>> 3] & (1 << (i & 7))) !== 0;
|
|
470
|
+
if (isValid && col) {
|
|
471
|
+
const raw = col[i];
|
|
472
|
+
allValues.push(
|
|
473
|
+
encoder
|
|
474
|
+
? encoder.encode(raw)
|
|
475
|
+
: requireUint8Array(raw, `Binary column '${columnName}' must store Uint8Array values`),
|
|
476
|
+
);
|
|
477
|
+
} else {
|
|
478
|
+
allValues.push(null);
|
|
479
|
+
}
|
|
480
|
+
} else if (col) {
|
|
481
|
+
// No null bitmap -- column allocated but check for undefined/null values
|
|
482
|
+
const raw = col[i];
|
|
483
|
+
if (raw != null) {
|
|
484
|
+
allValues.push(
|
|
485
|
+
encoder
|
|
486
|
+
? encoder.encode(raw)
|
|
487
|
+
: requireUint8Array(raw, `Binary column '${columnName}' must store Uint8Array values`),
|
|
488
|
+
);
|
|
489
|
+
} else {
|
|
490
|
+
allValues.push(null);
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
allValues.push(null);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Build offsets and concatenated data buffer
|
|
499
|
+
const offsets = new Int32Array(totalRows + 1);
|
|
500
|
+
let dataLength = 0;
|
|
501
|
+
for (let i = 0; i < allValues.length; i++) {
|
|
502
|
+
offsets[i] = dataLength;
|
|
503
|
+
const value = allValues[i];
|
|
504
|
+
if (value !== null) {
|
|
505
|
+
dataLength += value.length;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
offsets[allValues.length] = dataLength;
|
|
509
|
+
|
|
510
|
+
const data = new Uint8Array(dataLength);
|
|
511
|
+
let dataOffset = 0;
|
|
512
|
+
let nullCount = 0;
|
|
513
|
+
const nullBitmapSize = Math.ceil(totalRows / 8);
|
|
514
|
+
const nullBitmap = new Uint8Array(nullBitmapSize);
|
|
515
|
+
for (let i = 0; i < allValues.length; i++) {
|
|
516
|
+
const val = allValues[i];
|
|
517
|
+
if (val !== null) {
|
|
518
|
+
data.set(val, dataOffset);
|
|
519
|
+
dataOffset += val.length;
|
|
520
|
+
// Mark valid in null bitmap
|
|
521
|
+
nullBitmap[i >>> 3] |= 1 << (i & 7);
|
|
522
|
+
} else {
|
|
523
|
+
nullCount++;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Build Arrow Binary column via flechette
|
|
528
|
+
const batch = new (batchType(BINARY_TYPE))({
|
|
529
|
+
type: BINARY_TYPE,
|
|
530
|
+
length: totalRows,
|
|
531
|
+
nullCount,
|
|
532
|
+
validity: nullCount > 0 ? nullBitmap : EMPTY_VALIDITY,
|
|
533
|
+
offsets,
|
|
534
|
+
values: data,
|
|
535
|
+
});
|
|
536
|
+
return { column: new Column([batch]), nullCount };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Convert SpanBuffer (and its overflow chain) to an Arrow table.
|
|
541
|
+
*
|
|
542
|
+
* **Dictionary handling**: Each table batch has its own dictionary data.
|
|
543
|
+
* Dictionaries are built from the data in this batch only and are not shared
|
|
544
|
+
* with other batches, even when combined into a Table.
|
|
545
|
+
*/
|
|
546
|
+
export function convertToTable(buffer: AnySpanBuffer, systemColumnBuilder?: SystemColumnBuilder): Table {
|
|
547
|
+
const buffers: AnySpanBuffer[] = [];
|
|
548
|
+
let currentBuffer: AnySpanBuffer | undefined = buffer;
|
|
549
|
+
|
|
550
|
+
while (currentBuffer) {
|
|
551
|
+
buffers.push(currentBuffer);
|
|
552
|
+
currentBuffer = currentBuffer._overflow;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return convertBuffersToTable(buffers, systemColumnBuilder);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Convert multiple buffers to a single table.
|
|
560
|
+
*
|
|
561
|
+
* **Dictionary handling**: Each table batch has its own dictionary data.
|
|
562
|
+
* Dictionaries are built from the data in this batch only and are not shared
|
|
563
|
+
* with other batches, even when combined into a Table.
|
|
564
|
+
*/
|
|
565
|
+
function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: SystemColumnBuilder): Table {
|
|
566
|
+
if (buffers.length === 0) return tableFromColumns({});
|
|
567
|
+
|
|
568
|
+
const totalRows = buffers.reduce((sum, buf) => sum + buf._writeIndex, 0);
|
|
569
|
+
if (totalRows === 0) return tableFromColumns({});
|
|
570
|
+
|
|
571
|
+
const schema: LogSchema = buffers[0]._logSchema;
|
|
572
|
+
|
|
573
|
+
// Build vectors first, then derive schema from them
|
|
574
|
+
// This ensures Field types and vector data types are identical (Arrow IPC requirement)
|
|
575
|
+
const fields: string[] = [];
|
|
576
|
+
const vectors: Column<unknown>[] = [];
|
|
577
|
+
|
|
578
|
+
if (systemColumnBuilder) {
|
|
579
|
+
const systemColumns = systemColumnBuilder(buffers[0], buffers, totalRows);
|
|
580
|
+
fields.push(...systemColumns.fields);
|
|
581
|
+
vectors.push(...systemColumns.vectors);
|
|
582
|
+
} else {
|
|
583
|
+
// Build metadata columns - returns both fields and vectors with matching types
|
|
584
|
+
const metadataResult = buildMetadataColumnsWithFields(buffers, totalRows);
|
|
585
|
+
fields.push(...metadataResult.fields);
|
|
586
|
+
vectors.push(...metadataResult.vectors);
|
|
587
|
+
|
|
588
|
+
// System attribute column: message (eager category)
|
|
589
|
+
const maskTransform = undefined; // message has no masking
|
|
590
|
+
let {
|
|
591
|
+
dictionary: dictValues,
|
|
592
|
+
indices,
|
|
593
|
+
arrowIndexType,
|
|
594
|
+
nullBitmap,
|
|
595
|
+
} = buildSortedCategoryDictionary(buffers, 'message', maskTransform);
|
|
596
|
+
// Ensure dictionary has at least one entry (empty string) if no messages were written
|
|
597
|
+
if (dictValues.length === 0) {
|
|
598
|
+
dictValues = [''];
|
|
599
|
+
}
|
|
600
|
+
// Ensure nullBitmap is all 1s (all valid) for eager column
|
|
601
|
+
if (!nullBitmap) {
|
|
602
|
+
const bitmapBytes = Math.ceil(totalRows / 8);
|
|
603
|
+
nullBitmap = new Uint8Array(bitmapBytes);
|
|
604
|
+
nullBitmap.fill(0xff);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// CRITICAL: Create ONE Dictionary type instance for field and batch data
|
|
608
|
+
// Arrow Schema validates that fields with same dictionary ID have identical type references
|
|
609
|
+
const messageDictType = dictionary(utf8(), arrowIndexType);
|
|
610
|
+
fields.push('message');
|
|
611
|
+
|
|
612
|
+
const { data: messageUtf8Data, offsets: messageUtf8Offsets } = globalUtf8Cache.encodeMany(dictValues);
|
|
613
|
+
|
|
614
|
+
const messageDictData = buildData({
|
|
615
|
+
type: utf8(),
|
|
616
|
+
offset: 0,
|
|
617
|
+
length: dictValues.length,
|
|
618
|
+
nullCount: 0,
|
|
619
|
+
valueOffsets: messageUtf8Offsets,
|
|
620
|
+
data: messageUtf8Data,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
const messageData = buildData({
|
|
624
|
+
type: messageDictType,
|
|
625
|
+
offset: 0,
|
|
626
|
+
length: totalRows,
|
|
627
|
+
nullCount: 0, // message is eager, never null
|
|
628
|
+
data: indices,
|
|
629
|
+
nullBitmap, // Always include nullBitmap for message (even though nullCount is 0)
|
|
630
|
+
dictionary: buildColumn(messageDictData),
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
vectors.push(buildColumn(messageData));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Build user attribute vectors
|
|
637
|
+
// Skip system schema fields - they are handled separately as system columns
|
|
638
|
+
// (message is handled above, line/error_code/exception_stack/ff_value/uint64_value below)
|
|
639
|
+
const userSchemaFields = schema._columns.filter(([fieldName]) => !SYSTEM_SCHEMA_FIELD_NAMES.has(fieldName));
|
|
640
|
+
for (const [fieldName, fieldSchema] of userSchemaFields) {
|
|
641
|
+
const lmaoType = getSchemaType(fieldSchema);
|
|
642
|
+
const arrowFieldName = getArrowFieldName(fieldName);
|
|
643
|
+
const columnName = fieldName; // User columns have no prefix
|
|
644
|
+
|
|
645
|
+
if (lmaoType === 'enum') {
|
|
646
|
+
const enumValues = getEnumValues(fieldSchema) || [];
|
|
647
|
+
const enumUtf8 = getEnumUtf8(fieldSchema);
|
|
648
|
+
// Get constructors from schema metadata
|
|
649
|
+
const indexArrayCtor = getArrowIndexArrayConstructorOr(fieldSchema);
|
|
650
|
+
const arrowIndexType = getArrowIndexTypeOr(fieldSchema, uint8());
|
|
651
|
+
// Collect value arrays - need to handle different TypedArray types
|
|
652
|
+
const valueArrays: (Uint8Array | Uint16Array | Uint32Array)[] = [];
|
|
653
|
+
for (const buf of buffers) {
|
|
654
|
+
const column = buf.getColumnIfAllocated(columnName);
|
|
655
|
+
if (column && column instanceof indexArrayCtor) {
|
|
656
|
+
valueArrays.push(column.subarray(0, buf._writeIndex));
|
|
657
|
+
} else {
|
|
658
|
+
valueArrays.push(new indexArrayCtor(buf._writeIndex));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Concatenate arrays based on type
|
|
663
|
+
let allIndices: Uint8Array | Uint16Array | Uint32Array;
|
|
664
|
+
if (indexArrayCtor === Uint8Array) {
|
|
665
|
+
const uint8ValueArrays: Uint8Array[] = [];
|
|
666
|
+
for (const array of valueArrays) {
|
|
667
|
+
if (!(array instanceof Uint8Array)) {
|
|
668
|
+
throw new Error(`Enum column '${columnName}' expected Uint8Array chunks`);
|
|
669
|
+
}
|
|
670
|
+
uint8ValueArrays.push(array);
|
|
671
|
+
}
|
|
672
|
+
allIndices = concatenateUint8Arrays(uint8ValueArrays);
|
|
673
|
+
} else {
|
|
674
|
+
const totalLength = valueArrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
675
|
+
allIndices = new indexArrayCtor(totalLength);
|
|
676
|
+
let offset = 0;
|
|
677
|
+
for (const arr of valueArrays) {
|
|
678
|
+
allIndices.set(arr, offset);
|
|
679
|
+
offset += arr.length;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const { nullBitmap, nullCount } = concatenateNullBitmaps(buffers, columnName);
|
|
684
|
+
|
|
685
|
+
// CRITICAL: Create ONE Dictionary type instance for field and batch data
|
|
686
|
+
const enumDictType = dictionary(utf8(), arrowIndexType);
|
|
687
|
+
fields.push(arrowFieldName);
|
|
688
|
+
|
|
689
|
+
const enumDictData = buildData({
|
|
690
|
+
type: utf8(),
|
|
691
|
+
offset: 0,
|
|
692
|
+
length: enumValues.length,
|
|
693
|
+
nullCount: 0,
|
|
694
|
+
valueOffsets: enumUtf8?.offsets ?? calculateUtf8Offsets(enumValues),
|
|
695
|
+
data: enumUtf8?.concatenated ?? encodeUtf8Strings(enumValues),
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const enumData = buildData({
|
|
699
|
+
type: enumDictType,
|
|
700
|
+
offset: 0,
|
|
701
|
+
length: totalRows,
|
|
702
|
+
nullCount,
|
|
703
|
+
data: allIndices,
|
|
704
|
+
nullBitmap,
|
|
705
|
+
dictionary: buildColumn(enumDictData),
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
vectors.push(buildColumn(enumData));
|
|
709
|
+
} else if (lmaoType === 'category') {
|
|
710
|
+
// Get mask transform from schema metadata (if present)
|
|
711
|
+
const maskTransform = getMaskTransform(fieldSchema);
|
|
712
|
+
const {
|
|
713
|
+
dictionary: dictValues,
|
|
714
|
+
indices,
|
|
715
|
+
arrowIndexType,
|
|
716
|
+
nullBitmap,
|
|
717
|
+
nullCount,
|
|
718
|
+
} = buildSortedCategoryDictionary(buffers, columnName, maskTransform);
|
|
719
|
+
|
|
720
|
+
// CRITICAL: Create ONE Dictionary type instance for field and batch data
|
|
721
|
+
const categoryDictType = dictionary(utf8(), arrowIndexType);
|
|
722
|
+
fields.push(arrowFieldName);
|
|
723
|
+
|
|
724
|
+
const { data: categoryUtf8Data, offsets: categoryUtf8Offsets } = globalUtf8Cache.encodeMany(dictValues);
|
|
725
|
+
|
|
726
|
+
const categoryDictData = buildData({
|
|
727
|
+
type: utf8(),
|
|
728
|
+
offset: 0,
|
|
729
|
+
length: dictValues.length,
|
|
730
|
+
nullCount: 0,
|
|
731
|
+
valueOffsets: categoryUtf8Offsets,
|
|
732
|
+
data: categoryUtf8Data,
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
const categoryData = buildData({
|
|
736
|
+
type: categoryDictType,
|
|
737
|
+
offset: 0,
|
|
738
|
+
length: totalRows,
|
|
739
|
+
nullCount,
|
|
740
|
+
data: indices,
|
|
741
|
+
nullBitmap,
|
|
742
|
+
dictionary: buildColumn(categoryDictData),
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
vectors.push(buildColumn(categoryData));
|
|
746
|
+
} else if (lmaoType === 'text') {
|
|
747
|
+
// Get mask transform from schema metadata (if present)
|
|
748
|
+
const maskTransform = getMaskTransform(fieldSchema);
|
|
749
|
+
const result = buildTextDictionary(buffers, columnName, maskTransform);
|
|
750
|
+
if (result) {
|
|
751
|
+
const { dictionary: dictValues, indices, arrowIndexType, nullBitmap, nullCount } = result;
|
|
752
|
+
|
|
753
|
+
// CRITICAL: Create ONE Dictionary type instance for field and batch data
|
|
754
|
+
const textDictType = dictionary(utf8(), arrowIndexType);
|
|
755
|
+
fields.push(arrowFieldName);
|
|
756
|
+
|
|
757
|
+
const { data: textUtf8Data, offsets: textUtf8Offsets } = globalUtf8Cache.encodeMany(dictValues);
|
|
758
|
+
|
|
759
|
+
const textDictData = buildData({
|
|
760
|
+
type: utf8(),
|
|
761
|
+
offset: 0,
|
|
762
|
+
length: dictValues.length,
|
|
763
|
+
nullCount: 0,
|
|
764
|
+
valueOffsets: textUtf8Offsets,
|
|
765
|
+
data: textUtf8Data,
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const textData = buildData({
|
|
769
|
+
type: textDictType,
|
|
770
|
+
offset: 0,
|
|
771
|
+
length: totalRows,
|
|
772
|
+
nullCount,
|
|
773
|
+
data: indices,
|
|
774
|
+
nullBitmap,
|
|
775
|
+
dictionary: buildColumn(textDictData),
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
vectors.push(buildColumn(textData));
|
|
779
|
+
}
|
|
780
|
+
} else if (lmaoType === 'number') {
|
|
781
|
+
const valueArrays: Float64Array[] = [];
|
|
782
|
+
|
|
783
|
+
for (const buf of buffers) {
|
|
784
|
+
const column = buf.getColumnIfAllocated(columnName);
|
|
785
|
+
if (column && column instanceof Float64Array) {
|
|
786
|
+
valueArrays.push(column.subarray(0, buf._writeIndex));
|
|
787
|
+
} else {
|
|
788
|
+
valueArrays.push(new F64Array(buf._writeIndex));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const allValues = concatenateFloat64Arrays(valueArrays);
|
|
793
|
+
const { nullBitmap, nullCount } = concatenateNullBitmaps(buffers, columnName);
|
|
794
|
+
|
|
795
|
+
const numberType = float64();
|
|
796
|
+
fields.push(arrowFieldName);
|
|
797
|
+
|
|
798
|
+
const numberData = buildData({
|
|
799
|
+
type: numberType,
|
|
800
|
+
offset: 0,
|
|
801
|
+
length: totalRows,
|
|
802
|
+
nullCount,
|
|
803
|
+
data: allValues,
|
|
804
|
+
nullBitmap,
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
vectors.push(buildColumn(numberData));
|
|
808
|
+
} else if (lmaoType === 'boolean') {
|
|
809
|
+
const valueArrays: Uint8Array[] = [];
|
|
810
|
+
|
|
811
|
+
for (const buf of buffers) {
|
|
812
|
+
const column = buf.getColumnIfAllocated(columnName);
|
|
813
|
+
if (column && column instanceof Uint8Array) {
|
|
814
|
+
const requiredBytes = Math.ceil(buf._writeIndex / 8);
|
|
815
|
+
valueArrays.push(column.subarray(0, requiredBytes));
|
|
816
|
+
} else {
|
|
817
|
+
const requiredBytes = Math.ceil(buf._writeIndex / 8);
|
|
818
|
+
valueArrays.push(new Uint8Array(requiredBytes));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const allValues = concatenateUint8Arrays(valueArrays);
|
|
823
|
+
const { nullBitmap, nullCount } = concatenateNullBitmaps(buffers, columnName);
|
|
824
|
+
|
|
825
|
+
const boolType = bool();
|
|
826
|
+
fields.push(arrowFieldName);
|
|
827
|
+
|
|
828
|
+
const boolData = buildData({
|
|
829
|
+
type: boolType,
|
|
830
|
+
offset: 0,
|
|
831
|
+
length: totalRows,
|
|
832
|
+
nullCount,
|
|
833
|
+
data: allValues,
|
|
834
|
+
nullBitmap,
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
vectors.push(buildColumn(boolData));
|
|
838
|
+
} else if (lmaoType === 'binary') {
|
|
839
|
+
// Binary columns: raw Uint8Array or encoder-wrapped values (e.g. msgpack)
|
|
840
|
+
const { column } = buildBinaryColumnFromBuffers(buffers, columnName, totalRows, fieldSchema);
|
|
841
|
+
fields.push(arrowFieldName);
|
|
842
|
+
vectors.push(column);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return buildTable(fields, vectors);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Build metadata columns with matching Field definitions.
|
|
851
|
+
* Returns both fields and vectors with identical Dictionary type instances.
|
|
852
|
+
* This is required for Arrow IPC serialization - Schema validates that
|
|
853
|
+
* fields with same dictionary ID have identical type object references.
|
|
854
|
+
*/
|
|
855
|
+
function buildMetadataColumnsWithFields(buffers: AnySpanBuffer[], totalRows: number): BuiltMetadataColumns {
|
|
856
|
+
const fields: string[] = [];
|
|
857
|
+
const vectors: Column<unknown>[] = [];
|
|
858
|
+
|
|
859
|
+
// Column order: timestamp, trace_id, thread_id, span_id, parent_thread_id, parent_span_id,
|
|
860
|
+
// entry_type, package_name, package_file, git_sha
|
|
861
|
+
|
|
862
|
+
// 1. Timestamp
|
|
863
|
+
buildTimestampColumn(buffers, totalRows, fields, vectors);
|
|
864
|
+
|
|
865
|
+
// 2. Trace ID dictionary (built inline from buffers)
|
|
866
|
+
const traceIdSet = new Set<string>();
|
|
867
|
+
for (const buf of buffers) traceIdSet.add(buf.trace_id);
|
|
868
|
+
const traceIdArray = Array.from(traceIdSet);
|
|
869
|
+
const traceIdMap = new Map(traceIdArray.map((id, idx) => [id, idx]));
|
|
870
|
+
const traceIdArrowIndexType = indexTypeForCount(traceIdArray.length);
|
|
871
|
+
const TraceIdIndexArrayCtor =
|
|
872
|
+
traceIdArray.length <= 255 ? Uint8Array : traceIdArray.length <= 65535 ? Uint16Array : Uint32Array;
|
|
873
|
+
|
|
874
|
+
const traceIdIndices = new TraceIdIndexArrayCtor(totalRows);
|
|
875
|
+
let rowOffset = 0;
|
|
876
|
+
for (const buf of buffers) {
|
|
877
|
+
const traceIdIndex = traceIdMap.get(buf.trace_id);
|
|
878
|
+
if (traceIdIndex === undefined) {
|
|
879
|
+
throw new Error(`TraceId index not found for: ${buf.trace_id}`);
|
|
880
|
+
}
|
|
881
|
+
traceIdIndices.fill(traceIdIndex, rowOffset, rowOffset + buf._writeIndex);
|
|
882
|
+
rowOffset += buf._writeIndex;
|
|
883
|
+
}
|
|
884
|
+
const traceIdDictType = dictionary(utf8(), traceIdArrowIndexType);
|
|
885
|
+
fields.push('trace_id');
|
|
886
|
+
vectors.push(
|
|
887
|
+
buildColumn(
|
|
888
|
+
buildData({
|
|
889
|
+
type: traceIdDictType,
|
|
890
|
+
offset: 0,
|
|
891
|
+
length: totalRows,
|
|
892
|
+
nullCount: 0,
|
|
893
|
+
data: traceIdIndices,
|
|
894
|
+
dictionary: buildColumn(
|
|
895
|
+
buildData({
|
|
896
|
+
type: utf8(),
|
|
897
|
+
offset: 0,
|
|
898
|
+
length: traceIdArray.length,
|
|
899
|
+
nullCount: 0,
|
|
900
|
+
valueOffsets: calculateUtf8Offsets(traceIdArray),
|
|
901
|
+
data: encodeUtf8Strings(traceIdArray),
|
|
902
|
+
}),
|
|
903
|
+
),
|
|
904
|
+
}),
|
|
905
|
+
),
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
// 3-6. thread_id, span_id, parent_thread_id, parent_span_id
|
|
909
|
+
buildNonDictIdentityColumns(buffers, totalRows, fields, vectors);
|
|
910
|
+
|
|
911
|
+
// 7. Entry type
|
|
912
|
+
buildEntryTypeColumn(buffers, totalRows, fields, vectors);
|
|
913
|
+
|
|
914
|
+
// 8-10. Per-buffer metadata columns (package_name, package_file, git_sha)
|
|
915
|
+
for (const [fieldName, metadataGetter] of [
|
|
916
|
+
['package_name', (m: OpMetadata) => m.package_name],
|
|
917
|
+
['package_file', (m: OpMetadata) => m.package_file],
|
|
918
|
+
['git_sha', (m: OpMetadata) => m.git_sha],
|
|
919
|
+
] as [string, (m: OpMetadata) => string][]) {
|
|
920
|
+
const valueSet = new Set<string>();
|
|
921
|
+
for (const buf of buffers) {
|
|
922
|
+
valueSet.add(metadataGetter(buf._opMetadata));
|
|
923
|
+
if (buf._callsiteMetadata) valueSet.add(metadataGetter(buf._callsiteMetadata));
|
|
924
|
+
}
|
|
925
|
+
const dictValues = Array.from(valueSet);
|
|
926
|
+
const dictMap = new Map(dictValues.map((v, i) => [v, i]));
|
|
927
|
+
const arrowIndexType = indexTypeForCount(dictValues.length);
|
|
928
|
+
const dictType = dictionary(utf8(), arrowIndexType);
|
|
929
|
+
|
|
930
|
+
buildPerBufferDictColumn(
|
|
931
|
+
buffers,
|
|
932
|
+
totalRows,
|
|
933
|
+
fieldName,
|
|
934
|
+
metadataGetter,
|
|
935
|
+
dictType,
|
|
936
|
+
dictValues,
|
|
937
|
+
dictMap,
|
|
938
|
+
calculateUtf8Offsets(dictValues),
|
|
939
|
+
encodeUtf8Strings(dictValues),
|
|
940
|
+
fields,
|
|
941
|
+
vectors,
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return { fields, vectors };
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Convert SpanBuffer (and its overflow chain) to Arrow Table
|
|
950
|
+
*/
|
|
951
|
+
export function convertToArrowTable<_T extends LogSchema = LogSchema>(
|
|
952
|
+
buffer: AnySpanBuffer,
|
|
953
|
+
systemColumnBuilder?: SystemColumnBuilder,
|
|
954
|
+
): Table {
|
|
955
|
+
const batch = convertToTable(buffer, systemColumnBuilder);
|
|
956
|
+
if (batch.numRows === 0) return tableFromColumns({});
|
|
957
|
+
return batch;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
961
|
+
// Tree Conversion with Shared Dictionaries
|
|
962
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Convert SpanBuffer tree to Arrow Table
|
|
966
|
+
*
|
|
967
|
+
* Two-pass conversion with shared dictionaries:
|
|
968
|
+
* - Pass 1: Walk tree, build dictionaries (collect unique strings, cache UTF-8)
|
|
969
|
+
* - Pass 2: Walk tree, convert each buffer to a table using shared dictionaries
|
|
970
|
+
*/
|
|
971
|
+
export function convertSpanTreeToArrowTable(
|
|
972
|
+
rootBuffer: AnySpanBuffer,
|
|
973
|
+
_systemColumnBuilder?: SystemColumnBuilder,
|
|
974
|
+
modulesToLogStats?: CapacityStatsEntry[],
|
|
975
|
+
periodStartNs?: bigint,
|
|
976
|
+
): Table {
|
|
977
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
978
|
+
// PASS 0: Collect ALL unique schema fields from ALL buffers in the tree
|
|
979
|
+
// Per specs/lmao/01k_tree_walker_and_arrow_conversion.md - child spans may have different schemas
|
|
980
|
+
// (e.g., library prefixed schemas like db_query, http_status, etc.)
|
|
981
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
982
|
+
const mergedSchemaFields = new Map<string, unknown>();
|
|
983
|
+
|
|
984
|
+
walkSpanTree(rootBuffer, (buffer) => {
|
|
985
|
+
// Use buffer._columns directly - works for both SpanBuffer and RemappedBufferView
|
|
986
|
+
const fields = buffer._columns;
|
|
987
|
+
for (const [fieldName, fieldSchema] of fields) {
|
|
988
|
+
// Skip system schema fields - they are handled separately as system columns
|
|
989
|
+
// (message, line, error_code, exception_stack, ff_value, uint64_value)
|
|
990
|
+
if (SYSTEM_SCHEMA_FIELD_NAMES.has(fieldName)) {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
// Only add if not already present (first buffer with this field wins)
|
|
994
|
+
if (!mergedSchemaFields.has(fieldName)) {
|
|
995
|
+
mergedSchemaFields.set(fieldName, fieldSchema);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Convert merged schema fields to array format for iteration
|
|
1001
|
+
const schemaFields: Array<[string, unknown]> = Array.from(mergedSchemaFields.entries());
|
|
1002
|
+
|
|
1003
|
+
// Create merged schema object from collected fields (for capacity stats and other uses)
|
|
1004
|
+
const mergedSchema: Record<string, unknown> = Object.fromEntries(mergedSchemaFields);
|
|
1005
|
+
|
|
1006
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1007
|
+
// PASS 1: Build dictionaries using DictionaryBuilder from arrow-builder
|
|
1008
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1009
|
+
const categoryBuilders = new Map<string, DictionaryBuilder>();
|
|
1010
|
+
const textBuilders = new Map<string, DictionaryBuilder>();
|
|
1011
|
+
// Store mask transforms and original->masked mappings per column
|
|
1012
|
+
const categoryMaskTransforms = new Map<string, ((value: string) => string) | undefined>();
|
|
1013
|
+
const textMaskTransforms = new Map<string, ((value: string) => string) | undefined>();
|
|
1014
|
+
const categoryOriginalToMasked = new Map<string, Map<string, string>>();
|
|
1015
|
+
const textOriginalToMasked = new Map<string, Map<string, string>>();
|
|
1016
|
+
|
|
1017
|
+
// Use cached schemaFields to filter out methods (extend, validate, parse, safeParse)
|
|
1018
|
+
for (const [fieldName, fieldSchema] of schemaFields) {
|
|
1019
|
+
const lmaoType = getSchemaType(fieldSchema);
|
|
1020
|
+
if (lmaoType === 'category') {
|
|
1021
|
+
categoryBuilders.set(fieldName, new DictBuilder(globalUtf8Cache));
|
|
1022
|
+
categoryMaskTransforms.set(fieldName, getMaskTransform(fieldSchema));
|
|
1023
|
+
categoryOriginalToMasked.set(fieldName, new Map());
|
|
1024
|
+
}
|
|
1025
|
+
if (lmaoType === 'text') {
|
|
1026
|
+
textBuilders.set(fieldName, new DictBuilder(globalUtf8Cache));
|
|
1027
|
+
textMaskTransforms.set(fieldName, getMaskTransform(fieldSchema));
|
|
1028
|
+
textOriginalToMasked.set(fieldName, new Map());
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// System attribute column: message (eager category, not in user schema)
|
|
1033
|
+
// message is always present (eager allocation), so we need to handle it explicitly
|
|
1034
|
+
categoryBuilders.set('message', new DictBuilder(globalUtf8Cache));
|
|
1035
|
+
categoryMaskTransforms.set('message', undefined); // message has no masking
|
|
1036
|
+
categoryOriginalToMasked.set('message', new Map());
|
|
1037
|
+
|
|
1038
|
+
// Metadata column dictionaries
|
|
1039
|
+
// For traceId (metadata), use DictionaryBuilder (values not pre-encoded)
|
|
1040
|
+
const traceIdBuilder = new DictBuilder(globalUtf8Cache);
|
|
1041
|
+
|
|
1042
|
+
// For package, modulePath, gitSha - collect pre-encoded entries for direct dictionary creation
|
|
1043
|
+
// Use a Set to deduplicate by OpMetadata identity (same module = same entries)
|
|
1044
|
+
const uniqueModules = new Set<OpMetadata>();
|
|
1045
|
+
|
|
1046
|
+
let spanRows = 0;
|
|
1047
|
+
walkSpanTree(rootBuffer, (buffer) => {
|
|
1048
|
+
spanRows += buffer._writeIndex;
|
|
1049
|
+
traceIdBuilder.add(buffer.trace_id);
|
|
1050
|
+
|
|
1051
|
+
// Collect unique OpMetadata (for package_name, package_file, git_sha)
|
|
1052
|
+
// Include both buffer._opMetadata (for rows 1+) and callsiteMetadata (for row 0) if present
|
|
1053
|
+
uniqueModules.add(buffer._opMetadata);
|
|
1054
|
+
if (buffer._callsiteMetadata) {
|
|
1055
|
+
uniqueModules.add(buffer._callsiteMetadata);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
for (const [fieldName, builder] of categoryBuilders) {
|
|
1059
|
+
const col = getAllocatedStringColumn(buffer, fieldName);
|
|
1060
|
+
const maskTransform = categoryMaskTransforms.get(fieldName);
|
|
1061
|
+
const originalToMasked = categoryOriginalToMasked.get(fieldName);
|
|
1062
|
+
if (!originalToMasked) {
|
|
1063
|
+
throw new Error(`Category originalToMasked map not found for field: ${fieldName}`);
|
|
1064
|
+
}
|
|
1065
|
+
if (col) {
|
|
1066
|
+
for (let i = 0; i < buffer._writeIndex; i++) {
|
|
1067
|
+
const originalValue = col[i];
|
|
1068
|
+
if (originalValue != null) {
|
|
1069
|
+
let maskedValue = originalToMasked.get(originalValue);
|
|
1070
|
+
if (maskedValue === undefined) {
|
|
1071
|
+
maskedValue = maskTransform ? maskTransform(originalValue) : originalValue;
|
|
1072
|
+
originalToMasked.set(originalValue, maskedValue);
|
|
1073
|
+
}
|
|
1074
|
+
builder.add(maskedValue);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
// Also add scope values to dictionary (they may not be in columns)
|
|
1079
|
+
// Per specs/lmao/01i_span_scope_attributes.md: scope values fill NULL cells at Arrow conversion
|
|
1080
|
+
const scopeValue = getStringScopeValue(buffer, fieldName);
|
|
1081
|
+
if (scopeValue !== undefined) {
|
|
1082
|
+
let maskedScopeValue = originalToMasked.get(scopeValue);
|
|
1083
|
+
if (maskedScopeValue === undefined) {
|
|
1084
|
+
maskedScopeValue = maskTransform ? maskTransform(scopeValue) : scopeValue;
|
|
1085
|
+
originalToMasked.set(scopeValue, maskedScopeValue);
|
|
1086
|
+
}
|
|
1087
|
+
builder.add(maskedScopeValue);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
for (const [fieldName, builder] of textBuilders) {
|
|
1092
|
+
const col = getAllocatedStringColumn(buffer, fieldName);
|
|
1093
|
+
const maskTransform = textMaskTransforms.get(fieldName);
|
|
1094
|
+
const originalToMasked = textOriginalToMasked.get(fieldName);
|
|
1095
|
+
if (!originalToMasked) {
|
|
1096
|
+
throw new Error(`Text originalToMasked map not found for field: ${fieldName}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (col) {
|
|
1099
|
+
for (let i = 0; i < buffer._writeIndex; i++) {
|
|
1100
|
+
const originalValue = col[i];
|
|
1101
|
+
if (originalValue != null) {
|
|
1102
|
+
let maskedValue = originalToMasked.get(originalValue);
|
|
1103
|
+
if (maskedValue === undefined) {
|
|
1104
|
+
maskedValue = maskTransform ? maskTransform(originalValue) : originalValue;
|
|
1105
|
+
originalToMasked.set(originalValue, maskedValue);
|
|
1106
|
+
}
|
|
1107
|
+
builder.add(maskedValue);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
// Also add scope values to dictionary (they may not be in columns)
|
|
1112
|
+
// Per specs/lmao/01i_span_scope_attributes.md: scope values fill NULL cells at Arrow conversion
|
|
1113
|
+
const scopeValue = getStringScopeValue(buffer, fieldName);
|
|
1114
|
+
if (scopeValue !== undefined) {
|
|
1115
|
+
let maskedScopeValue = originalToMasked.get(scopeValue);
|
|
1116
|
+
if (maskedScopeValue === undefined) {
|
|
1117
|
+
maskedScopeValue = maskTransform ? maskTransform(scopeValue) : scopeValue;
|
|
1118
|
+
originalToMasked.set(scopeValue, maskedScopeValue);
|
|
1119
|
+
}
|
|
1120
|
+
builder.add(maskedScopeValue);
|
|
1121
|
+
}
|
|
1122
|
+
// Scope values are now pre-filled in columns at allocation time
|
|
1123
|
+
// (via lazy getters and _prefillScopedAttributes on overflow),
|
|
1124
|
+
// so they'll be added to dictionary via the column iteration above
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
if (spanRows === 0 && (!modulesToLogStats || modulesToLogStats.length === 0)) {
|
|
1129
|
+
return tableFromColumns({});
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Finalize dictionaries
|
|
1133
|
+
const traceIdDict = traceIdBuilder.finalize(false);
|
|
1134
|
+
|
|
1135
|
+
// Build dictionaries from OpMetadata entries
|
|
1136
|
+
// Use pre-encoded entries from OpMetadata (interned at Op definition time)
|
|
1137
|
+
const moduleCount = uniqueModules.size;
|
|
1138
|
+
const packageEntries: PreEncodedEntry[] = new Array(moduleCount);
|
|
1139
|
+
const packagePathEntries: PreEncodedEntry[] = new Array(moduleCount);
|
|
1140
|
+
const gitShaEntries: PreEncodedEntry[] = new Array(moduleCount);
|
|
1141
|
+
let idx = 0;
|
|
1142
|
+
for (const m of uniqueModules) {
|
|
1143
|
+
packageEntries[idx] = m.package_name_entry;
|
|
1144
|
+
packagePathEntries[idx] = m.package_file_entry;
|
|
1145
|
+
gitShaEntries[idx] = m.git_sha_entry;
|
|
1146
|
+
idx++;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const cmp = (a: PreEncodedEntry, b: PreEncodedEntry) => compareStrings(a.str, b.str);
|
|
1150
|
+
const packageDict = createSortedDictionary(sortInPlace(packageEntries, cmp));
|
|
1151
|
+
const packagePathDict = createSortedDictionary(sortInPlace(packagePathEntries, cmp));
|
|
1152
|
+
const gitShaDict = createSortedDictionary(sortInPlace(gitShaEntries, cmp));
|
|
1153
|
+
const categoryDicts = new Map<string, FinalizedDictionary>();
|
|
1154
|
+
const textDicts = new Map<string, FinalizedDictionary>();
|
|
1155
|
+
|
|
1156
|
+
for (const [name, builder] of categoryBuilders) {
|
|
1157
|
+
categoryDicts.set(name, builder.finalize(true)); // sorted for binary search
|
|
1158
|
+
}
|
|
1159
|
+
for (const [name, builder] of textBuilders) {
|
|
1160
|
+
textDicts.set(name, builder.finalize(false)); // not sorted
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Get messageDict from categoryDicts (message is handled as a category column)
|
|
1164
|
+
// If no messages were written, create an empty dictionary
|
|
1165
|
+
let messageDict = categoryDicts.get('message');
|
|
1166
|
+
if (!messageDict) {
|
|
1167
|
+
const messageBuilder = new DictBuilder(globalUtf8Cache);
|
|
1168
|
+
messageBuilder.add(''); // Single empty string entry
|
|
1169
|
+
messageDict = messageBuilder.finalize(true); // sorted
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1173
|
+
// PASS 2: Collect all buffers with non-zero rows
|
|
1174
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1175
|
+
const allBuffers: AnySpanBuffer[] = [];
|
|
1176
|
+
|
|
1177
|
+
walkSpanTree(rootBuffer, (buffer) => {
|
|
1178
|
+
if (buffer._writeIndex > 0) {
|
|
1179
|
+
allBuffers.push(buffer);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// Build a single table from all buffers
|
|
1184
|
+
const batch =
|
|
1185
|
+
allBuffers.length > 0
|
|
1186
|
+
? convertBuffersWithSharedDicts(
|
|
1187
|
+
allBuffers,
|
|
1188
|
+
traceIdDict,
|
|
1189
|
+
packageDict,
|
|
1190
|
+
packagePathDict,
|
|
1191
|
+
gitShaDict,
|
|
1192
|
+
categoryDicts,
|
|
1193
|
+
textDicts,
|
|
1194
|
+
schemaFields,
|
|
1195
|
+
categoryOriginalToMasked,
|
|
1196
|
+
textOriginalToMasked,
|
|
1197
|
+
)
|
|
1198
|
+
: undefined;
|
|
1199
|
+
|
|
1200
|
+
// Create capacity stats table if needed
|
|
1201
|
+
if (modulesToLogStats && modulesToLogStats.length > 0) {
|
|
1202
|
+
// periodStartNs must be provided by the caller (from FlushScheduler's tracked period start)
|
|
1203
|
+
// If not provided, use 0n as a fallback (indicates period tracking not enabled)
|
|
1204
|
+
const effectivePeriodStartNs = periodStartNs ?? 0n;
|
|
1205
|
+
const capacityStatsBatch = createCapacityStatsTable(modulesToLogStats, effectivePeriodStartNs, mergedSchema);
|
|
1206
|
+
if (batch) {
|
|
1207
|
+
return appendTables(batch, capacityStatsBatch);
|
|
1208
|
+
}
|
|
1209
|
+
return capacityStatsBatch;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if (batch) {
|
|
1213
|
+
return batch;
|
|
1214
|
+
}
|
|
1215
|
+
return tableFromColumns({});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Convert multiple buffers to a single table using pre-built dictionaries.
|
|
1220
|
+
*
|
|
1221
|
+
* **Dictionary handling**: Each table batch has its own dictionary data.
|
|
1222
|
+
* While dictionaries are built in a first pass from all buffers, they are used to create
|
|
1223
|
+
* a single table. The resulting batches contain their own dictionary data and
|
|
1224
|
+
* are not shared with other batches, even when combined into a Table.
|
|
1225
|
+
*/
|
|
1226
|
+
function convertBuffersWithSharedDicts(
|
|
1227
|
+
buffers: AnySpanBuffer[],
|
|
1228
|
+
traceIdDict: FinalizedDictionary,
|
|
1229
|
+
packageDict: FinalizedDictionary,
|
|
1230
|
+
packagePathDict: FinalizedDictionary,
|
|
1231
|
+
gitShaDict: FinalizedDictionary,
|
|
1232
|
+
categoryDicts: Map<string, FinalizedDictionary>,
|
|
1233
|
+
textDicts: Map<string, FinalizedDictionary>,
|
|
1234
|
+
schemaFields: Array<[string, unknown]>,
|
|
1235
|
+
categoryOriginalToMasked: Map<string, Map<string, string>>,
|
|
1236
|
+
textOriginalToMasked: Map<string, Map<string, string>>,
|
|
1237
|
+
): Table {
|
|
1238
|
+
const totalRows = buffers.reduce((sum, buf) => sum + buf._writeIndex, 0);
|
|
1239
|
+
const fields: string[] = [];
|
|
1240
|
+
const vectors: Column<unknown>[] = [];
|
|
1241
|
+
|
|
1242
|
+
// Dictionary types with explicit IDs for IPC serialization
|
|
1243
|
+
const traceIdType = dictionary(utf8(), traceIdDict.arrowIndexType, false, 0);
|
|
1244
|
+
const entryTypeDictType = dictionary(utf8(), int8(), false, 1);
|
|
1245
|
+
const packageType = dictionary(utf8(), packageDict.arrowIndexType, false, 2);
|
|
1246
|
+
const packagePathType = dictionary(utf8(), packagePathDict.arrowIndexType, false, 3);
|
|
1247
|
+
const gitShaType = dictionary(utf8(), gitShaDict.arrowIndexType, false, 4);
|
|
1248
|
+
|
|
1249
|
+
// Column order: timestamp, trace_id, thread_id, span_id, parent_thread_id, parent_span_id,
|
|
1250
|
+
// entry_type, package_name, package_file, git_sha
|
|
1251
|
+
|
|
1252
|
+
// 1. Timestamp
|
|
1253
|
+
buildTimestampColumn(buffers, totalRows, fields, vectors);
|
|
1254
|
+
|
|
1255
|
+
// 2. Trace ID (using shared pre-built dictionary)
|
|
1256
|
+
const traceIdIndices = new traceIdDict.indexArrayCtor(totalRows);
|
|
1257
|
+
let offset = 0;
|
|
1258
|
+
for (const buf of buffers) {
|
|
1259
|
+
const idx = traceIdDict.indexMap.get(buf.trace_id) ?? 0;
|
|
1260
|
+
traceIdIndices.fill(idx, offset, offset + buf._writeIndex);
|
|
1261
|
+
offset += buf._writeIndex;
|
|
1262
|
+
}
|
|
1263
|
+
fields.push('trace_id');
|
|
1264
|
+
vectors.push(
|
|
1265
|
+
buildColumn(
|
|
1266
|
+
buildData({
|
|
1267
|
+
type: traceIdType,
|
|
1268
|
+
offset: 0,
|
|
1269
|
+
length: totalRows,
|
|
1270
|
+
nullCount: 0,
|
|
1271
|
+
data: traceIdIndices,
|
|
1272
|
+
dictionary: buildColumn(
|
|
1273
|
+
buildData({
|
|
1274
|
+
type: utf8(),
|
|
1275
|
+
offset: 0,
|
|
1276
|
+
length: traceIdDict.indexMap.size,
|
|
1277
|
+
nullCount: 0,
|
|
1278
|
+
valueOffsets: traceIdDict.offsets,
|
|
1279
|
+
data: traceIdDict.data,
|
|
1280
|
+
}),
|
|
1281
|
+
),
|
|
1282
|
+
}),
|
|
1283
|
+
),
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
// 3-6. thread_id, span_id, parent_thread_id, parent_span_id
|
|
1287
|
+
buildNonDictIdentityColumns(buffers, totalRows, fields, vectors);
|
|
1288
|
+
|
|
1289
|
+
// 7. Entry type (with explicit dictionary ID for IPC)
|
|
1290
|
+
buildEntryTypeColumn(buffers, totalRows, fields, vectors, entryTypeDictType);
|
|
1291
|
+
|
|
1292
|
+
// 8-10. Per-buffer metadata columns using shared pre-built dictionaries
|
|
1293
|
+
// Per specs/lmao/01c_context_flow_and_op_wrappers.md:
|
|
1294
|
+
// - Row 0 (span-start): uses callsiteModule for gitSha/packageName/packagePath
|
|
1295
|
+
// - Rows 1+ (logs, span-end): uses task._module
|
|
1296
|
+
for (const [fieldName, metadataGetter, dictType, dict] of [
|
|
1297
|
+
['package_name', (m: OpMetadata) => m.package_name, packageType, packageDict],
|
|
1298
|
+
['package_file', (m: OpMetadata) => m.package_file, packagePathType, packagePathDict],
|
|
1299
|
+
['git_sha', (m: OpMetadata) => m.git_sha, gitShaType, gitShaDict],
|
|
1300
|
+
] as [string, (m: OpMetadata) => string, ReturnType<typeof dictionary>, FinalizedDictionary][]) {
|
|
1301
|
+
const dictValues = Array.from(dict.indexMap.keys());
|
|
1302
|
+
buildPerBufferDictColumn(
|
|
1303
|
+
buffers,
|
|
1304
|
+
totalRows,
|
|
1305
|
+
fieldName,
|
|
1306
|
+
metadataGetter,
|
|
1307
|
+
dictType,
|
|
1308
|
+
dictValues,
|
|
1309
|
+
dict.indexMap,
|
|
1310
|
+
dict.offsets,
|
|
1311
|
+
dict.data,
|
|
1312
|
+
fields,
|
|
1313
|
+
vectors,
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1318
|
+
// System attribute and user attribute columns - concatenate from all buffers
|
|
1319
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1320
|
+
|
|
1321
|
+
// System attribute column: message (at fixed position 10, before user attributes)
|
|
1322
|
+
const messageDict = categoryDicts.get('message');
|
|
1323
|
+
const messageOriginalToMasked = categoryOriginalToMasked.get('message');
|
|
1324
|
+
if (!messageDict || !messageOriginalToMasked) {
|
|
1325
|
+
throw new Error('Message dictionary or mapping not found');
|
|
1326
|
+
}
|
|
1327
|
+
const messageFieldType = dictionary(utf8(), messageDict.arrowIndexType, false, 5);
|
|
1328
|
+
const messageIndices = new messageDict.indexArrayCtor(totalRows);
|
|
1329
|
+
const messageNullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
|
|
1330
|
+
let messageRowOffset = 0;
|
|
1331
|
+
for (const buf of buffers) {
|
|
1332
|
+
const col = getAllocatedStringColumn(buf, 'message');
|
|
1333
|
+
if (col) {
|
|
1334
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1335
|
+
const v = col[i];
|
|
1336
|
+
const rowIdx = messageRowOffset + i;
|
|
1337
|
+
if (v != null) {
|
|
1338
|
+
const maskedValue = messageOriginalToMasked.get(v) ?? v;
|
|
1339
|
+
messageIndices[rowIdx] = messageDict.indexMap.get(maskedValue) ?? 0;
|
|
1340
|
+
messageNullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
messageRowOffset += buf._writeIndex;
|
|
1345
|
+
}
|
|
1346
|
+
vectors.push(
|
|
1347
|
+
buildColumn(
|
|
1348
|
+
buildData({
|
|
1349
|
+
type: messageFieldType,
|
|
1350
|
+
offset: 0,
|
|
1351
|
+
length: totalRows,
|
|
1352
|
+
nullCount: 0, // message is eager, never null
|
|
1353
|
+
data: messageIndices,
|
|
1354
|
+
nullBitmap: messageNullBitmap,
|
|
1355
|
+
dictionary: buildColumn(
|
|
1356
|
+
buildData({
|
|
1357
|
+
type: utf8(),
|
|
1358
|
+
offset: 0,
|
|
1359
|
+
length: messageDict.indexMap.size,
|
|
1360
|
+
nullCount: 0,
|
|
1361
|
+
valueOffsets: messageDict.offsets,
|
|
1362
|
+
data: messageDict.data,
|
|
1363
|
+
}),
|
|
1364
|
+
),
|
|
1365
|
+
}),
|
|
1366
|
+
),
|
|
1367
|
+
);
|
|
1368
|
+
fields.push('message');
|
|
1369
|
+
|
|
1370
|
+
// System attribute column: uint64_value (for buffer metrics - op durations, counts, etc.)
|
|
1371
|
+
// For span data, this is all null - only buffer metrics use it
|
|
1372
|
+
const uint64ValueNullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
|
|
1373
|
+
// All zeros = all null (Arrow null bitmap: 1 = valid, 0 = null)
|
|
1374
|
+
vectors.push(
|
|
1375
|
+
buildColumn(
|
|
1376
|
+
buildData({
|
|
1377
|
+
type: uint64(),
|
|
1378
|
+
offset: 0,
|
|
1379
|
+
length: totalRows,
|
|
1380
|
+
nullCount: totalRows,
|
|
1381
|
+
data: new BigUint64Array(totalRows),
|
|
1382
|
+
nullBitmap: uint64ValueNullBitmap,
|
|
1383
|
+
}),
|
|
1384
|
+
),
|
|
1385
|
+
);
|
|
1386
|
+
fields.push('uint64_value');
|
|
1387
|
+
|
|
1388
|
+
for (const [fieldName, fieldSchema] of schemaFields) {
|
|
1389
|
+
const lmaoType = getSchemaType(fieldSchema);
|
|
1390
|
+
const columnName = fieldName; // User columns have no prefix
|
|
1391
|
+
const arrowFieldName = getArrowFieldName(fieldName);
|
|
1392
|
+
|
|
1393
|
+
if (lmaoType === 'category') {
|
|
1394
|
+
const dict = categoryDicts.get(fieldName);
|
|
1395
|
+
const originalToMasked = categoryOriginalToMasked.get(fieldName);
|
|
1396
|
+
if (!dict || !originalToMasked) {
|
|
1397
|
+
throw new Error(`Category dictionary or mapping not found for field: ${fieldName}`);
|
|
1398
|
+
}
|
|
1399
|
+
const indices = new dict.indexArrayCtor(totalRows);
|
|
1400
|
+
const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
|
|
1401
|
+
let nullCount = 0;
|
|
1402
|
+
let rowOffset = 0;
|
|
1403
|
+
|
|
1404
|
+
// Per specs/lmao/01i_span_scope_attributes.md: scope values fill NULL cells at Arrow conversion
|
|
1405
|
+
for (const buf of buffers) {
|
|
1406
|
+
const col = getAllocatedStringColumn(buf, columnName);
|
|
1407
|
+
// Check if this buffer has a scope value for this field
|
|
1408
|
+
const scopeValue = getStringScopeValue(buf, fieldName);
|
|
1409
|
+
let scopeEncodedValue: number | undefined;
|
|
1410
|
+
if (scopeValue !== undefined) {
|
|
1411
|
+
const maskedScopeValue = originalToMasked.get(scopeValue) ?? scopeValue;
|
|
1412
|
+
scopeEncodedValue = dict.indexMap.get(maskedScopeValue);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
if (col) {
|
|
1416
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1417
|
+
const v = col[i];
|
|
1418
|
+
const rowIdx = rowOffset + i;
|
|
1419
|
+
if (v != null) {
|
|
1420
|
+
// Direct write wins
|
|
1421
|
+
const maskedValue = originalToMasked.get(v) ?? v;
|
|
1422
|
+
indices[rowIdx] = dict.indexMap.get(maskedValue) ?? 0;
|
|
1423
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1424
|
+
} else if (scopeEncodedValue !== undefined) {
|
|
1425
|
+
// No direct write, but have scope value - use it
|
|
1426
|
+
indices[rowIdx] = scopeEncodedValue;
|
|
1427
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1428
|
+
} else {
|
|
1429
|
+
nullCount++;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
} else if (scopeEncodedValue !== undefined) {
|
|
1433
|
+
// Column not allocated but we have scope - fill all rows with scope value
|
|
1434
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1435
|
+
const rowIdx = rowOffset + i;
|
|
1436
|
+
indices[rowIdx] = scopeEncodedValue;
|
|
1437
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1438
|
+
}
|
|
1439
|
+
} else {
|
|
1440
|
+
// No column and no scope - all nulls
|
|
1441
|
+
nullCount += buf._writeIndex;
|
|
1442
|
+
}
|
|
1443
|
+
rowOffset += buf._writeIndex;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
vectors.push(
|
|
1447
|
+
buildColumn(
|
|
1448
|
+
buildData({
|
|
1449
|
+
type: dictionary(utf8(), dict.arrowIndexType),
|
|
1450
|
+
offset: 0,
|
|
1451
|
+
length: totalRows,
|
|
1452
|
+
nullCount,
|
|
1453
|
+
data: indices,
|
|
1454
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
1455
|
+
dictionary: buildColumn(
|
|
1456
|
+
buildData({
|
|
1457
|
+
type: utf8(),
|
|
1458
|
+
offset: 0,
|
|
1459
|
+
length: dict.indexMap.size,
|
|
1460
|
+
nullCount: 0,
|
|
1461
|
+
valueOffsets: dict.offsets,
|
|
1462
|
+
data: dict.data,
|
|
1463
|
+
}),
|
|
1464
|
+
),
|
|
1465
|
+
}),
|
|
1466
|
+
),
|
|
1467
|
+
);
|
|
1468
|
+
fields.push(arrowFieldName);
|
|
1469
|
+
} else if (lmaoType === 'text') {
|
|
1470
|
+
const dict = textDicts.get(fieldName);
|
|
1471
|
+
const originalToMasked = textOriginalToMasked.get(fieldName);
|
|
1472
|
+
if (!dict || !originalToMasked) {
|
|
1473
|
+
throw new Error(`Text dictionary or mapping not found for field: ${fieldName}`);
|
|
1474
|
+
}
|
|
1475
|
+
const indices = new dict.indexArrayCtor(totalRows);
|
|
1476
|
+
const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
|
|
1477
|
+
let nullCount = 0;
|
|
1478
|
+
let rowOffset = 0;
|
|
1479
|
+
|
|
1480
|
+
for (const buf of buffers) {
|
|
1481
|
+
const col = getAllocatedStringColumn(buf, columnName);
|
|
1482
|
+
// Check if this buffer has a scope value for this field
|
|
1483
|
+
const scopeValue = getStringScopeValue(buf, fieldName);
|
|
1484
|
+
let scopeEncodedValue: number | undefined;
|
|
1485
|
+
if (scopeValue !== undefined) {
|
|
1486
|
+
const maskedScopeValue = originalToMasked.get(scopeValue) ?? scopeValue;
|
|
1487
|
+
scopeEncodedValue = dict.indexMap.get(maskedScopeValue);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
if (col) {
|
|
1491
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1492
|
+
const v = col[i];
|
|
1493
|
+
const rowIdx = rowOffset + i;
|
|
1494
|
+
if (v != null) {
|
|
1495
|
+
// Direct write wins - look up the masked value
|
|
1496
|
+
const maskedValue = originalToMasked.get(v) ?? v;
|
|
1497
|
+
indices[rowIdx] = dict.indexMap.get(maskedValue) ?? 0;
|
|
1498
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1499
|
+
} else if (scopeEncodedValue !== undefined) {
|
|
1500
|
+
// No direct write, but have scope value - use it
|
|
1501
|
+
indices[rowIdx] = scopeEncodedValue;
|
|
1502
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1503
|
+
} else {
|
|
1504
|
+
nullCount++;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
} else if (scopeEncodedValue !== undefined) {
|
|
1508
|
+
// Column not allocated but we have scope - fill all rows with scope value
|
|
1509
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1510
|
+
const rowIdx = rowOffset + i;
|
|
1511
|
+
indices[rowIdx] = scopeEncodedValue;
|
|
1512
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1513
|
+
}
|
|
1514
|
+
} else {
|
|
1515
|
+
// No column and no scope - all nulls
|
|
1516
|
+
nullCount += buf._writeIndex;
|
|
1517
|
+
}
|
|
1518
|
+
rowOffset += buf._writeIndex;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
vectors.push(
|
|
1522
|
+
buildColumn(
|
|
1523
|
+
buildData({
|
|
1524
|
+
type: dictionary(utf8(), dict.arrowIndexType),
|
|
1525
|
+
offset: 0,
|
|
1526
|
+
length: totalRows,
|
|
1527
|
+
nullCount,
|
|
1528
|
+
data: indices,
|
|
1529
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
1530
|
+
dictionary: buildColumn(
|
|
1531
|
+
buildData({
|
|
1532
|
+
type: utf8(),
|
|
1533
|
+
offset: 0,
|
|
1534
|
+
length: dict.indexMap.size,
|
|
1535
|
+
nullCount: 0,
|
|
1536
|
+
valueOffsets: dict.offsets,
|
|
1537
|
+
data: dict.data,
|
|
1538
|
+
}),
|
|
1539
|
+
),
|
|
1540
|
+
}),
|
|
1541
|
+
),
|
|
1542
|
+
);
|
|
1543
|
+
fields.push(arrowFieldName);
|
|
1544
|
+
} else if (lmaoType === 'number') {
|
|
1545
|
+
const allValues = new F64Array(totalRows);
|
|
1546
|
+
const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
|
|
1547
|
+
// Start with all nulls (bits cleared) - we'll set bits as we find values
|
|
1548
|
+
let nullCount = 0;
|
|
1549
|
+
let rowOffset = 0;
|
|
1550
|
+
|
|
1551
|
+
for (const buf of buffers) {
|
|
1552
|
+
const col = buf.getColumnIfAllocated(columnName);
|
|
1553
|
+
const srcNulls = buf.getNullsIfAllocated(columnName);
|
|
1554
|
+
// Check if this buffer has a scope value for this field
|
|
1555
|
+
const scopeValue = getNumberScopeValue(buf, fieldName);
|
|
1556
|
+
|
|
1557
|
+
if (col instanceof Float64Array) {
|
|
1558
|
+
allValues.set(col.subarray(0, buf._writeIndex), rowOffset);
|
|
1559
|
+
if (srcNulls) {
|
|
1560
|
+
// Process each row - direct write wins, then scope, then null
|
|
1561
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1562
|
+
const srcByte = i >>> 3;
|
|
1563
|
+
const srcBit = i & 7;
|
|
1564
|
+
const isValid = (srcNulls[srcByte] & (1 << srcBit)) !== 0;
|
|
1565
|
+
const rowIdx = rowOffset + i;
|
|
1566
|
+
if (isValid) {
|
|
1567
|
+
// Direct write - mark as valid
|
|
1568
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1569
|
+
} else if (scopeValue !== undefined) {
|
|
1570
|
+
// No direct write, but have scope value - use it
|
|
1571
|
+
allValues[rowIdx] = scopeValue;
|
|
1572
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1573
|
+
} else {
|
|
1574
|
+
// No direct write and no scope - null
|
|
1575
|
+
nullCount++;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
} else {
|
|
1579
|
+
// No null bitmap means column was allocated but no writes - check scope
|
|
1580
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1581
|
+
const rowIdx = rowOffset + i;
|
|
1582
|
+
if (scopeValue !== undefined) {
|
|
1583
|
+
allValues[rowIdx] = scopeValue;
|
|
1584
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1585
|
+
} else {
|
|
1586
|
+
nullCount++;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
} else if (scopeValue !== undefined) {
|
|
1591
|
+
// Column not allocated but we have scope - fill all rows with scope value
|
|
1592
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1593
|
+
const rowIdx = rowOffset + i;
|
|
1594
|
+
allValues[rowIdx] = scopeValue;
|
|
1595
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1596
|
+
}
|
|
1597
|
+
} else {
|
|
1598
|
+
// Column doesn't exist for this buffer and no scope - all nulls
|
|
1599
|
+
nullCount += buf._writeIndex;
|
|
1600
|
+
}
|
|
1601
|
+
rowOffset += buf._writeIndex;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
vectors.push(
|
|
1605
|
+
buildColumn(
|
|
1606
|
+
buildData({
|
|
1607
|
+
type: float64(),
|
|
1608
|
+
offset: 0,
|
|
1609
|
+
length: totalRows,
|
|
1610
|
+
nullCount,
|
|
1611
|
+
data: allValues,
|
|
1612
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
1613
|
+
}),
|
|
1614
|
+
),
|
|
1615
|
+
);
|
|
1616
|
+
fields.push(arrowFieldName);
|
|
1617
|
+
} else if (lmaoType === 'boolean') {
|
|
1618
|
+
const requiredBytes = Math.ceil(totalRows / 8);
|
|
1619
|
+
const allValues = new Uint8Array(requiredBytes);
|
|
1620
|
+
const nullBitmap = new Uint8Array(requiredBytes);
|
|
1621
|
+
// Start with all nulls (bits cleared) - we'll set bits as we find values
|
|
1622
|
+
let nullCount = 0;
|
|
1623
|
+
let rowOffset = 0;
|
|
1624
|
+
|
|
1625
|
+
for (const buf of buffers) {
|
|
1626
|
+
const col = buf.getColumnIfAllocated(columnName);
|
|
1627
|
+
const srcNulls = buf.getNullsIfAllocated(columnName);
|
|
1628
|
+
// Check if this buffer has a scope value for this field
|
|
1629
|
+
const scopeValue = getBooleanScopeValue(buf, fieldName);
|
|
1630
|
+
|
|
1631
|
+
if (col instanceof Uint8Array) {
|
|
1632
|
+
// Copy boolean values bit by bit - can't avoid loop for bit-level operations
|
|
1633
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1634
|
+
const srcByte = i >>> 3;
|
|
1635
|
+
const srcBit = i & 7;
|
|
1636
|
+
const rowIdx = rowOffset + i;
|
|
1637
|
+
|
|
1638
|
+
if (srcNulls) {
|
|
1639
|
+
const isValid = (srcNulls[srcByte] & (1 << srcBit)) !== 0;
|
|
1640
|
+
if (isValid) {
|
|
1641
|
+
// Direct write - copy the value
|
|
1642
|
+
const value = (col[srcByte] & (1 << srcBit)) !== 0;
|
|
1643
|
+
if (value) {
|
|
1644
|
+
allValues[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1645
|
+
}
|
|
1646
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1647
|
+
} else if (scopeValue !== undefined) {
|
|
1648
|
+
// No direct write, but have scope value - use it
|
|
1649
|
+
if (scopeValue) {
|
|
1650
|
+
allValues[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1651
|
+
}
|
|
1652
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1653
|
+
} else {
|
|
1654
|
+
// No direct write and no scope - null
|
|
1655
|
+
nullCount++;
|
|
1656
|
+
}
|
|
1657
|
+
} else {
|
|
1658
|
+
// No null bitmap - check scope
|
|
1659
|
+
if (scopeValue !== undefined) {
|
|
1660
|
+
if (scopeValue) {
|
|
1661
|
+
allValues[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1662
|
+
}
|
|
1663
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1664
|
+
} else {
|
|
1665
|
+
nullCount++;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
} else if (scopeValue !== undefined) {
|
|
1670
|
+
// Column not allocated but we have scope - fill all rows with scope value
|
|
1671
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1672
|
+
const rowIdx = rowOffset + i;
|
|
1673
|
+
if (scopeValue) {
|
|
1674
|
+
allValues[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1675
|
+
}
|
|
1676
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1677
|
+
}
|
|
1678
|
+
} else {
|
|
1679
|
+
// Column doesn't exist for this buffer and no scope - all nulls
|
|
1680
|
+
nullCount += buf._writeIndex;
|
|
1681
|
+
}
|
|
1682
|
+
rowOffset += buf._writeIndex;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
vectors.push(
|
|
1686
|
+
buildColumn(
|
|
1687
|
+
buildData({
|
|
1688
|
+
type: bool(),
|
|
1689
|
+
offset: 0,
|
|
1690
|
+
length: totalRows,
|
|
1691
|
+
nullCount,
|
|
1692
|
+
data: allValues,
|
|
1693
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
1694
|
+
}),
|
|
1695
|
+
),
|
|
1696
|
+
);
|
|
1697
|
+
fields.push(arrowFieldName);
|
|
1698
|
+
} else if (lmaoType === 'enum') {
|
|
1699
|
+
const enumValues = getEnumValues(fieldSchema) || [];
|
|
1700
|
+
const enumUtf8 = getEnumUtf8(fieldSchema);
|
|
1701
|
+
// Get constructors from schema metadata
|
|
1702
|
+
const indexArrayCtor = getArrowIndexArrayConstructorOr(fieldSchema);
|
|
1703
|
+
const arrowIndexType = getArrowIndexTypeOr(fieldSchema, uint8());
|
|
1704
|
+
const allIndices = new indexArrayCtor(totalRows);
|
|
1705
|
+
const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
|
|
1706
|
+
// Start with all nulls (bits cleared) - we'll set bits as we find values
|
|
1707
|
+
let nullCount = 0;
|
|
1708
|
+
let rowOffset = 0;
|
|
1709
|
+
|
|
1710
|
+
// Build enum value to index mapping for scope lookup
|
|
1711
|
+
const enumValueToIndex = new Map<string, number>();
|
|
1712
|
+
for (let i = 0; i < enumValues.length; i++) {
|
|
1713
|
+
enumValueToIndex.set(enumValues[i], i);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
for (const buf of buffers) {
|
|
1717
|
+
const col = buf.getColumnIfAllocated(columnName);
|
|
1718
|
+
const srcNulls = buf.getNullsIfAllocated(columnName);
|
|
1719
|
+
// Check if this buffer has a scope value for this field
|
|
1720
|
+
const scopeValue = getStringScopeValue(buf, fieldName);
|
|
1721
|
+
let scopeEncodedValue: number | undefined;
|
|
1722
|
+
if (scopeValue !== undefined) {
|
|
1723
|
+
scopeEncodedValue = enumValueToIndex.get(scopeValue);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
if (col instanceof indexArrayCtor) {
|
|
1727
|
+
allIndices.set(col.subarray(0, buf._writeIndex), rowOffset);
|
|
1728
|
+
if (srcNulls) {
|
|
1729
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1730
|
+
const srcByte = i >>> 3;
|
|
1731
|
+
const srcBit = i & 7;
|
|
1732
|
+
const isValid = (srcNulls[srcByte] & (1 << srcBit)) !== 0;
|
|
1733
|
+
const rowIdx = rowOffset + i;
|
|
1734
|
+
if (isValid) {
|
|
1735
|
+
// Direct write - mark as valid
|
|
1736
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1737
|
+
} else if (scopeEncodedValue !== undefined) {
|
|
1738
|
+
// No direct write, but have scope value - use it
|
|
1739
|
+
allIndices[rowIdx] = scopeEncodedValue;
|
|
1740
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1741
|
+
} else {
|
|
1742
|
+
// No direct write and no scope - null
|
|
1743
|
+
nullCount++;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
} else {
|
|
1747
|
+
// No null bitmap - check scope for each row
|
|
1748
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1749
|
+
const rowIdx = rowOffset + i;
|
|
1750
|
+
if (scopeEncodedValue !== undefined) {
|
|
1751
|
+
allIndices[rowIdx] = scopeEncodedValue;
|
|
1752
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1753
|
+
} else {
|
|
1754
|
+
nullCount++;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
} else if (scopeEncodedValue !== undefined) {
|
|
1759
|
+
// Column not allocated but we have scope - fill all rows with scope value
|
|
1760
|
+
for (let i = 0; i < buf._writeIndex; i++) {
|
|
1761
|
+
const rowIdx = rowOffset + i;
|
|
1762
|
+
allIndices[rowIdx] = scopeEncodedValue;
|
|
1763
|
+
nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
|
|
1764
|
+
}
|
|
1765
|
+
} else {
|
|
1766
|
+
// Column doesn't exist for this buffer and no scope - all nulls
|
|
1767
|
+
nullCount += buf._writeIndex;
|
|
1768
|
+
}
|
|
1769
|
+
rowOffset += buf._writeIndex;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const enumDictData = buildData({
|
|
1773
|
+
type: utf8(),
|
|
1774
|
+
offset: 0,
|
|
1775
|
+
length: enumValues.length,
|
|
1776
|
+
nullCount: 0,
|
|
1777
|
+
valueOffsets: enumUtf8?.offsets ?? calculateUtf8Offsets(enumValues),
|
|
1778
|
+
data: enumUtf8?.concatenated ?? encodeUtf8Strings(enumValues),
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
vectors.push(
|
|
1782
|
+
buildColumn(
|
|
1783
|
+
buildData({
|
|
1784
|
+
type: dictionary(utf8(), arrowIndexType),
|
|
1785
|
+
offset: 0,
|
|
1786
|
+
length: totalRows,
|
|
1787
|
+
nullCount,
|
|
1788
|
+
data: allIndices,
|
|
1789
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
1790
|
+
dictionary: buildColumn(enumDictData),
|
|
1791
|
+
}),
|
|
1792
|
+
),
|
|
1793
|
+
);
|
|
1794
|
+
fields.push(arrowFieldName);
|
|
1795
|
+
} else if (lmaoType === 'binary') {
|
|
1796
|
+
// Binary columns: raw Uint8Array or encoder-wrapped values (e.g. msgpack)
|
|
1797
|
+
// Binary columns don't use dictionaries and are not scope-fillable
|
|
1798
|
+
const { column } = buildBinaryColumnFromBuffers(buffers, columnName, totalRows, fieldSchema);
|
|
1799
|
+
fields.push(arrowFieldName);
|
|
1800
|
+
vectors.push(column);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
return buildTable(fields, vectors);
|
|
1805
|
+
}
|