blob: 4329cf108cf056df98348a277e1ee19b7f5c924a [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 { Data, makeData } from '../data.js';
import * as type from '../type.js';
import { Field } from '../schema.js';
import { Vector } from '../vector.js';
import { DataType } from '../type.js';
import { Visitor } from '../visitor.js';
import { packBools } from '../util/bit.js';
import { encodeUtf8 } from '../util/utf8.js';
import { Int64, Int128 } from '../util/int.js';
import { UnionMode, DateUnit, MetadataVersion, IntervalUnit } from '../enum.js';
import { toArrayBufferView } from '../util/buffer.js';
import { BufferRegion, FieldNode } from '../ipc/metadata/message.js';
import { toIntervalDayTimeInt32Array, toIntervalMonthDayNanoInt32Array } from '../util/interval.js';
/** @ignore */
export interface VectorLoader extends Visitor {
visit<T extends DataType>(node: Field<T> | T): Data<T>;
visitMany<T extends DataType>(nodes: (Field<T> | T)[]): Data<T>[];
}
/** @ignore */
export class VectorLoader extends Visitor {
private bytes: Uint8Array;
private nodes: FieldNode[];
private nodesIndex = -1;
private buffers: BufferRegion[];
protected buffersIndex = -1;
private dictionaries: Map<number, Vector<any>>;
private readonly metadataVersion: MetadataVersion;
private variadicBufferCounts: number[];
private variadicBufferIndex = -1;
constructor(bytes: Uint8Array, nodes: FieldNode[], buffers: BufferRegion[], dictionaries: Map<number, Vector<any>>, metadataVersion = MetadataVersion.V5, variadicBufferCounts: number[] = []) {
super();
this.bytes = bytes;
this.nodes = nodes;
this.buffers = buffers;
this.dictionaries = dictionaries;
this.metadataVersion = metadataVersion;
this.variadicBufferCounts = variadicBufferCounts;
}
public visit<T extends DataType>(node: Field<T> | T): Data<T> {
return super.visit(node instanceof Field ? node.type : node);
}
public visitNull<T extends type.Null>(type: T, { length } = this.nextFieldNode()) {
return makeData({ type, length });
}
public visitBool<T extends type.Bool>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitInt<T extends type.Int>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitFloat<T extends type.Float>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitUtf8<T extends type.Utf8>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), data: this.readData(type) });
}
public visitLargeUtf8<T extends type.LargeUtf8>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), data: this.readData(type) });
}
public visitUtf8View<T extends type.Utf8View>(type: T, { length, nullCount } = this.nextFieldNode()) {
const nullBitmap = this.readNullBitmap(type, nullCount);
const views = this.readData(type);
const variadicBuffers = this.readVariadicBuffers(this.nextVariadicBufferCount());
return makeData({
type,
length,
nullCount,
nullBitmap,
['views']: views,
['variadicBuffers']: variadicBuffers
});
}
public visitBinary<T extends type.Binary>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), data: this.readData(type) });
}
public visitLargeBinary<T extends type.LargeBinary>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), data: this.readData(type) });
}
public visitBinaryView<T extends type.BinaryView>(type: T, { length, nullCount } = this.nextFieldNode()) {
const nullBitmap = this.readNullBitmap(type, nullCount);
const views = this.readData(type);
const variadicBuffers = this.readVariadicBuffers(this.nextVariadicBufferCount());
return makeData({
type,
length,
nullCount,
nullBitmap,
['views']: views,
['variadicBuffers']: variadicBuffers
});
}
public visitFixedSizeBinary<T extends type.FixedSizeBinary>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitDate<T extends type.Date_>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitTimestamp<T extends type.Timestamp>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitTime<T extends type.Time>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitDecimal<T extends type.Decimal>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitList<T extends type.List>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), 'child': this.visit(type.children[0]) });
}
public visitLargeList<T extends type.LargeList>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), 'child': this.visit(type.children[0]) });
}
public visitStruct<T extends type.Struct>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), children: this.visitMany(type.children) });
}
public visitUnion<T extends type.Union>(type: T, { length, nullCount } = this.nextFieldNode()) {
if (this.metadataVersion < MetadataVersion.V5) {
this.readNullBitmap(type, nullCount);
}
return type.mode === UnionMode.Sparse
? this.visitSparseUnion(type as type.SparseUnion, { length, nullCount })
: this.visitDenseUnion(type as type.DenseUnion, { length, nullCount });
}
public visitDenseUnion<T extends type.DenseUnion>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, typeIds: this.readTypeIds(type), valueOffsets: this.readOffsets(type), children: this.visitMany(type.children) });
}
public visitSparseUnion<T extends type.SparseUnion>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, typeIds: this.readTypeIds(type), children: this.visitMany(type.children) });
}
public visitDictionary<T extends type.Dictionary>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type.indices), dictionary: this.readDictionary(type) });
}
public visitInterval<T extends type.Interval>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitDuration<T extends type.Duration>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), data: this.readData(type) });
}
public visitFixedSizeList<T extends type.FixedSizeList>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), 'child': this.visit(type.children[0]) });
}
public visitMap<T extends type.Map_>(type: T, { length, nullCount } = this.nextFieldNode()) {
return makeData({ type, length, nullCount, nullBitmap: this.readNullBitmap(type, nullCount), valueOffsets: this.readOffsets(type), 'child': this.visit(type.children[0]) });
}
protected nextFieldNode() { return this.nodes[++this.nodesIndex]; }
protected nextBufferRange() { return this.buffers[++this.buffersIndex]; }
protected readNullBitmap<T extends DataType>(type: T, nullCount: number, buffer = this.nextBufferRange()) {
return nullCount > 0 && this.readData(type, buffer) || new Uint8Array(0);
}
protected readOffsets<T extends DataType>(type: T, buffer?: BufferRegion) { return this.readData(type, buffer); }
protected readTypeIds<T extends DataType>(type: T, buffer?: BufferRegion) { return this.readData(type, buffer); }
protected readData<T extends DataType>(_type: T, { length, offset } = this.nextBufferRange()) {
return this.bytes.subarray(offset, offset + length);
}
protected readVariadicBuffers(length: number) {
return Array.from({ length }, () => this.readData(null as any));
}
protected nextVariadicBufferCount() {
return this.variadicBufferCounts[++this.variadicBufferIndex] ?? 0;
}
protected readDictionary<T extends type.Dictionary>(type: T): Vector<T['dictionary']> {
return this.dictionaries.get(type.id)!;
}
}
/** @ignore */
export class JSONVectorLoader extends VectorLoader {
private sources: any[][];
constructor(sources: any[][], nodes: FieldNode[], buffers: BufferRegion[], dictionaries: Map<number, Vector<any>>, metadataVersion: MetadataVersion, variadicBufferCounts: number[] = []) {
super(new Uint8Array(0), nodes, buffers, dictionaries, metadataVersion, variadicBufferCounts);
this.sources = sources;
}
protected readNullBitmap<T extends DataType>(_type: T, nullCount: number, { offset } = this.nextBufferRange()) {
return nullCount <= 0 ? new Uint8Array(0) : packBools(this.sources[offset]);
}
protected readOffsets<T extends DataType>(_type: T, { offset } = this.nextBufferRange()) {
return toArrayBufferView(Uint8Array, toArrayBufferView(_type.OffsetArrayType, this.sources[offset]));
}
protected readTypeIds<T extends DataType>(type: T, { offset } = this.nextBufferRange()) {
return toArrayBufferView(Uint8Array, toArrayBufferView(type.ArrayType, this.sources[offset]));
}
protected readData<T extends DataType>(type: T, { offset } = this.nextBufferRange()) {
const { sources } = this;
if (DataType.isTimestamp(type)) {
return toArrayBufferView(Uint8Array, Int64.convertArray(sources[offset] as string[]));
} else if ((DataType.isInt(type) || DataType.isTime(type)) && type.bitWidth === 64 || DataType.isDuration(type)) {
return toArrayBufferView(Uint8Array, Int64.convertArray(sources[offset] as string[]));
} else if (DataType.isDate(type) && type.unit === DateUnit.MILLISECOND) {
return toArrayBufferView(Uint8Array, Int64.convertArray(sources[offset] as string[]));
} else if (DataType.isDecimal(type)) {
return toArrayBufferView(Uint8Array, Int128.convertArray(sources[offset] as string[]));
} else if (DataType.isBinary(type) || DataType.isLargeBinary(type) || DataType.isFixedSizeBinary(type)) {
return binaryDataFromJSON(sources[offset] as string[]);
} else if (DataType.isBinaryView(type)) {
return binaryViewDataFromJSON(sources[offset] as any[]);
} else if (DataType.isUtf8View(type)) {
return utf8ViewDataFromJSON(sources[offset] as any[]);
} else if (DataType.isBool(type)) {
return packBools(sources[offset] as number[]);
} else if (DataType.isUtf8(type) || DataType.isLargeUtf8(type)) {
return encodeUtf8((sources[offset] as string[]).join(''));
} else if (DataType.isInterval(type)) {
switch (type.unit) {
case IntervalUnit.DAY_TIME:
return toIntervalDayTimeInt32Array(sources[offset]);
case IntervalUnit.MONTH_DAY_NANO:
return toIntervalMonthDayNanoInt32Array(sources[offset]);
default:
break;
}
}
return toArrayBufferView(Uint8Array, toArrayBufferView(type.ArrayType, sources[offset].map((x) => +x)));
}
protected readVariadicBuffers(length: number) {
// Per Arrow C++ reference implementation (cpp/src/arrow/ipc/reader.cc),
// each variadic buffer is stored as a separate buffer region, matching
// the IPC format where each is accessed via separate GetBuffer() calls.
// VARIADIC_DATA_BUFFERS in JSON is an array, but flattenDataSources spreads
// it so each hex string gets its own sources entry, maintaining 1:1
// correspondence with BufferRegion entries.
const buffers: Uint8Array[] = [];
for (let i = 0; i < length; i++) {
const { offset } = this.nextBufferRange();
// sources[offset] is 'any[]' but for variadic buffers it's actually a string
// after spreading in flattenDataSources. Cast necessary due to heterogeneous
// sources array structure (most fields are arrays, variadic elements are strings).
const hexString = this.sources[offset] as unknown as string;
buffers.push(hexStringToBytes(hexString));
}
return buffers;
}
}
/** @ignore */
function hexStringToBytes(hexString: string): Uint8Array {
// Parse hex string per Arrow JSON integration format (uppercase hex encoding).
// Used for: VARIADIC_DATA_BUFFERS elements, Binary DATA (after join),
// BinaryView PREFIX_HEX and INLINED fields.
const data = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
data[i >> 1] = Number.parseInt(hexString.slice(i, i + 2), 16);
}
return data;
}
/** @ignore */
function binaryDataFromJSON(values: string[]): Uint8Array {
// Arrow JSON Binary/LargeBinary/FixedSizeBinary format:
// "DATA": ["49BC7D5B6C47D2","3F5FB6D9322026"] (array of hex strings, one per value)
// Join all values into one continuous hex string, then parse to bytes.
return hexStringToBytes(values.join(''));
}
/** @ignore */
function parseViewDataFromJSON(views: any[], parseInlined: (inlined: string) => Uint8Array) {
// Each view is a 16-byte struct: [length: i32, prefix/inlined: 12 bytes, buffer_index: i32, offset: i32]
const data = new Uint8Array(views.length * 16);
const dataView = new DataView(data.buffer);
for (const [i, view] of views.entries()) {
const offset = i * 16;
const size = view['SIZE'];
// Write size (int32 at byte 0)
dataView.setInt32(offset, size, true);
if (view['INLINED'] !== undefined) {
// Inline view: parse INLINED field using provided callback
const bytes = parseInlined(view['INLINED']);
for (let j = 0; j < bytes.length && j < 12; j++) {
data[offset + 4 + j] = bytes[j];
}
} else {
// Out-of-line view: write prefix, buffer_index, offset
const prefix = view['PREFIX_HEX'];
// Write 4-byte prefix at bytes 4-7
for (let j = 0; j < 8 && j < prefix.length; j += 2) {
data[offset + 4 + (j >> 1)] = Number.parseInt(prefix.slice(j, j + 2), 16);
}
// Write buffer_index (int32 at byte 8)
dataView.setInt32(offset + 8, view['BUFFER_INDEX'], true);
// Write offset (int32 at byte 12)
dataView.setInt32(offset + 12, view['OFFSET'], true);
}
}
return data;
}
/** @ignore */
function binaryViewDataFromJSON(views: any[]) {
return parseViewDataFromJSON(views, (inlined: string) => {
// BinaryView: INLINED is hex-encoded string
const bytes = new Uint8Array(inlined.length / 2);
for (let i = 0; i < inlined.length; i += 2) {
bytes[i >> 1] = Number.parseInt(inlined.slice(i, i + 2), 16);
}
return bytes;
});
}
/** @ignore */
function utf8ViewDataFromJSON(views: any[]) {
return parseViewDataFromJSON(views, (inlined: string) => {
// Utf8View: INLINED is UTF-8 string - encode to bytes
const encoder = new TextEncoder();
return encoder.encode(inlined);
});
}
export class CompressedVectorLoader extends VectorLoader {
private bodyChunks: Uint8Array[];
constructor(bodyChunks: Uint8Array[], nodes: FieldNode[], buffers: BufferRegion[], dictionaries: Map<number, Vector<any>>, metadataVersion: MetadataVersion, variadicBufferCounts: number[] = []) {
super(new Uint8Array(0), nodes, buffers, dictionaries, metadataVersion, variadicBufferCounts);
this.bodyChunks = bodyChunks;
}
protected readData<T extends DataType>(_type: T, _buffer = this.nextBufferRange()) {
return this.bodyChunks[this.buffersIndex];
}
}