// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import { BinaryView, Utf8View } from '../type.js'; import { Builder, BuilderOptions } from '../builder.js'; import { BufferBuilder } from './buffer.js'; import { toUint8Array } from '../util/buffer.js'; import { makeData } from '../data.js'; import type { DataProps } from '../data.js'; /** @ignore */ export class BinaryViewBuilder< TType extends BinaryView | Utf8View = BinaryView, TNull = any > extends Builder { protected _views: BufferBuilder; protected _variadicBuffers: Uint8Array[] = []; protected _currentBuffer: BufferBuilder | null = null; protected _currentBufferIndex = 0; protected _currentBufferOffset = 0; protected readonly _bufferSize = 32 * 1024 * 1024; // 32MB per buffer as per spec recommendation constructor(opts: BuilderOptions) { super(opts); this._views = new BufferBuilder(Uint8Array); } public get byteLength(): number { let size = 0; this._views && (size += this._views.byteLength); this._nulls && (size += this._nulls.byteLength); for (const buffer of this._variadicBuffers) { size += buffer.byteLength; } this._currentBuffer && (size += this._currentBuffer.byteLength); return size; } public setValue(index: number, value: TType['TValue']) { return this.writeBinaryValue(index, this.encodeValue(value)); } protected writeBinaryValue(index: number, data: Uint8Array) { const length = data.length; // Ensure views buffer has space up to this index (similar to FixedWidthBuilder) const bytesNeeded = (index + 1) * BinaryView.ELEMENT_WIDTH; const currentBytes = this._views.length; if (bytesNeeded > currentBytes) { this._views.reserve(bytesNeeded - currentBytes); } const viewBuffer = this._views.buffer; const viewOffset = index * BinaryView.ELEMENT_WIDTH; const view = new DataView(viewBuffer.buffer, viewBuffer.byteOffset + viewOffset, BinaryView.ELEMENT_WIDTH); // Write length (4 bytes, little-endian) view.setInt32(BinaryView.LENGTH_OFFSET, length, true); if (length <= BinaryView.INLINE_CAPACITY) { // Inline: store data directly in view struct (up to 12 bytes) viewBuffer.set(data, viewOffset + BinaryView.INLINE_OFFSET); // Zero out remaining bytes for (let i = length; i < BinaryView.INLINE_CAPACITY; i++) { viewBuffer[viewOffset + BinaryView.INLINE_OFFSET + i] = 0; } } else { // Out-of-line: store in variadic buffer // Write prefix (first 4 bytes of data) const prefix = new DataView(data.buffer, data.byteOffset, Math.min(4, length)); view.setUint32(BinaryView.INLINE_OFFSET, prefix.getUint32(0, true), true); // Allocate space in variadic buffer if (!this._currentBuffer || this._currentBufferOffset + length > this._bufferSize) { // Start a new buffer if (this._currentBuffer) { this._variadicBuffers.push(this._currentBuffer.buffer.slice(0, this._currentBufferOffset)); } this._currentBuffer = new BufferBuilder(Uint8Array); this._currentBufferIndex = this._variadicBuffers.length; this._currentBufferOffset = 0; } // Write data to current buffer const bufferData = this._currentBuffer.reserve(length).buffer; bufferData.set(data, this._currentBufferOffset); // Write buffer index and offset to view struct view.setInt32(BinaryView.BUFFER_INDEX_OFFSET, this._currentBufferIndex, true); view.setInt32(BinaryView.BUFFER_OFFSET_OFFSET, this._currentBufferOffset, true); this._currentBufferOffset += length; } return this; } protected encodeValue(value: TType['TValue']): Uint8Array { return toUint8Array(value as unknown as Uint8Array); } public setValid(index: number, isValid: boolean) { // Ensure space is allocated in the views buffer for this index const bytesNeeded = (index + 1) * BinaryView.ELEMENT_WIDTH; const currentBytes = this._views.length; if (bytesNeeded > currentBytes) { this._views.reserve(bytesNeeded - currentBytes); } const result = super.setValid(index, isValid); if (!result) { // For null values, zero out the view struct const viewBuffer = this._views.buffer; const viewOffset = index * BinaryView.ELEMENT_WIDTH; for (let i = 0; i < BinaryView.ELEMENT_WIDTH; i++) { viewBuffer[viewOffset + i] = 0; } } return result; } public clear() { this._variadicBuffers = []; this._currentBuffer = null; this._currentBufferIndex = 0; this._currentBufferOffset = 0; this._views.clear(); return super.clear(); } public flush() { const { type, length, nullCount, _views, _nulls } = this; // Finalize current buffer if it exists if (this._currentBuffer && this._currentBufferOffset > 0) { this._variadicBuffers.push(this._currentBuffer.buffer.slice(0, this._currentBufferOffset)); this._currentBuffer = null; this._currentBufferOffset = 0; } const views = _views.flush(length * BinaryView.ELEMENT_WIDTH); const nullBitmap = nullCount > 0 ? _nulls.flush(length) : undefined; const variadicBuffers = this._variadicBuffers.slice(); // Reset variadic buffers for next batch this._variadicBuffers = []; this._currentBufferIndex = 0; this.clear(); const props = { type, length, nullCount, nullBitmap, ['views']: views, ['variadicBuffers']: variadicBuffers }; return makeData(props as unknown as DataProps); } public finish() { this.finished = true; return this; } }