blob: 002bfc362274c4add7940478e78346190d539555 [file]
// 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<TType, TNull> {
protected _views: BufferBuilder<Uint8Array>;
protected _variadicBuffers: Uint8Array[] = [];
protected _currentBuffer: BufferBuilder<Uint8Array> | null = null;
protected _currentBufferIndex = 0;
protected _currentBufferOffset = 0;
protected readonly _bufferSize = 32 * 1024 * 1024; // 32MB per buffer as per spec recommendation
constructor(opts: BuilderOptions<TType, TNull>) {
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<TType>(props as unknown as DataProps<TType>);
}
public finish() {
this.finished = true;
return this;
}
}