blob: a9c2869f5bda6b0ce0c0b749b7171923367ccf56 [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.
/* eslint-disable jest/no-identical-title */
import {
util,
Vector, makeVector, vectorFromArray, makeData,
Float, Float16, Float32, Float64,
Int, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64, DataType
} from 'apache-arrow';
import type {
TypedArray,
TypedArrayConstructor,
BigIntArray,
BigIntArrayConstructor
} from 'apache-arrow/interfaces';
const { joinUint8Arrays, float64ToUint16, uint16ToFloat64 } = util;
const uint16ToFloat64Array = (b: ArrayBuffer) => new Float64Array([...new Uint16Array(b)].map(x => uint16ToFloat64(x)));
const randomBytes = (n: number) => new Uint16Array([
...Uint16Array.from([0, 65_535]),
...Uint16Array.from({ length: (n / 2) - 2 }, () => Math.trunc(Math.random() * 65_536)),
]).buffer;
const testValueBuffers = Array.from({ length: 5 }, () => randomBytes(64));
const testValuesBuffer = joinUint8Arrays(testValueBuffers.map((b) => new Uint8Array(b)))[0].buffer;
const checkDtype = <T extends DataType>(Ctor: new (...args: any) => T, v: Vector<T>) => expect(v.type).toBeInstanceOf(Ctor);
const valuesArray = <T extends TypedArray>(ArrayType: TypedArrayConstructor<T>) => [...valuesTyped<T>(ArrayType)];
const valuesTyped = <T extends TypedArray>(ArrayType: TypedArrayConstructor<T>) => new ArrayType(testValuesBuffer);
const bigIntValuesTyped = <T extends BigIntArray>(ArrayType: BigIntArrayConstructor<T>) => new ArrayType(testValuesBuffer);
const bigIntValuesArray = <T extends BigIntArray>(ArrayType: BigIntArrayConstructor<T>) => [...bigIntValuesTyped<T>(ArrayType)];
describe(`FloatVector`, () => {
describe(`makeVector infers the type from the input TypedArray`, () => {
const u16s = valuesTyped(Uint16Array).map((x) => float64ToUint16(uint16ToFloat64(x)));
const f16s = valuesArray(Uint16Array).map(x => uint16ToFloat64(x));
const f32s = valuesTyped(Float32Array);
const f64s = valuesTyped(Float64Array);
const f16Vec = new Vector([makeData({ type: new Float16, data: u16s })]);
const f32Vec = makeVector(valuesTyped(Float32Array));
const f64Vec = makeVector(valuesTyped(Float64Array));
// test strong typing at compile-time
test(`return type is correct`, () => checkDtype(Float16, f16Vec));
test(`return type is correct`, () => checkDtype(Float32, f32Vec));
test(`return type is correct`, () => checkDtype(Float64, f64Vec));
test(`throws on bad input`, () => {
expect(() => makeVector(<any>{})).toThrow('Unrecognized input');
});
testAndValidateVector(f16Vec, u16s, f16s);
testAndValidateVector(f32Vec, f32s);
testAndValidateVector(f64Vec, f64s);
});
describe(`Vector<Float16>`, () => {
testFloatVector(Float16, valuesArray(Uint16Array).map(x => uint16ToFloat64(x)));
describe(`vectorFromArray accepts regular Arrays to construct Vector<Float16>`, () => {
const u16s = valuesTyped(Uint16Array).map((x) => float64ToUint16(uint16ToFloat64(x)));
const f16s = valuesArray(Uint16Array).map(x => uint16ToFloat64(x));
const vector = vectorFromArray(f16s, new Float16);
testAndValidateVector(vector, u16s, f16s);
test(`return type is correct`, () => checkDtype(Float16, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 2); });
});
});
describe(`Vector<Float32>`, () => {
testFloatVector(Float32);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Float32>`, () => {
const values = valuesArray(Float32Array);
const vector = vectorFromArray(values, new Float32);
testAndValidateVector(vector, valuesTyped(Float32Array), values);
test(`return type is correct`, () => checkDtype(Float32, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 4); });
});
});
describe(`Vector<Float64>`, () => {
testFloatVector(Float64);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Float64>`, () => {
const values = valuesArray(Float64Array);
const vector = vectorFromArray(values);
testAndValidateVector(vector, valuesTyped(Float64Array), values);
test(`return type is correct`, () => checkDtype(Float64, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 8); });
});
});
});
describe(`IntVector`, () => {
describe(`makeVector infers the type from the input TypedArray`, () => {
const i8s = valuesTyped(Int8Array);
const i16s = valuesTyped(Int16Array);
const i32s = valuesTyped(Int32Array);
const i64s = bigIntValuesTyped(BigInt64Array);
const u8s = valuesTyped(Uint8Array);
const u16s = valuesTyped(Uint16Array);
const u32s = valuesTyped(Uint32Array);
const u64s = bigIntValuesTyped(BigUint64Array);
const i8Vec = makeVector(i8s);
const i16Vec = makeVector(i16s);
const i32Vec = makeVector(i32s);
const i64Vec = makeVector(i64s);
const u8Vec = makeVector(u8s);
const u16Vec = makeVector(u16s);
const u32Vec = makeVector(u32s);
const u64Vec = makeVector(u64s);
// test strong typing at compile-time
test(`return type is correct`, () => checkDtype(Int8, i8Vec));
test(`return type is correct`, () => checkDtype(Int16, i16Vec));
test(`return type is correct`, () => checkDtype(Int32, i32Vec));
test(`return type is correct`, () => checkDtype(Int64, i64Vec));
test(`return type is correct`, () => checkDtype(Uint8, u8Vec));
test(`return type is correct`, () => checkDtype(Uint16, u16Vec));
test(`return type is correct`, () => checkDtype(Uint32, u32Vec));
test(`return type is correct`, () => checkDtype(Uint64, u64Vec));
test(`throws on bad input`, () => {
expect(() => makeVector(<any>{})).toThrow('Unrecognized input');
});
testAndValidateVector(i8Vec, i8s);
testAndValidateVector(i16Vec, i16s);
testAndValidateVector(i32Vec, i32s);
testAndValidateVector(i64Vec, i64s);
testAndValidateVector(u8Vec, u8s);
testAndValidateVector(u16Vec, u16s);
testAndValidateVector(u32Vec, u32s);
testAndValidateVector(u64Vec, u64s);
});
describe(`Vector<Int8>`, () => {
testIntVector(Int8);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Int8>`, () => {
const values = valuesArray(Int8Array);
const vector = vectorFromArray(values, new Int8);
testAndValidateVector(vector, valuesTyped(Int8Array), values);
test(`return type is correct`, () => checkDtype(Int8, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length); });
});
});
describe(`Vector<Int16>`, () => {
testIntVector(Int16);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Int16>`, () => {
const values = valuesArray(Int16Array);
const vector = vectorFromArray(values, new Int16);
testAndValidateVector(vector, valuesTyped(Int16Array), values);
test(`return type is correct`, () => checkDtype(Int16, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 2); });
});
});
describe(`Vector<Int32>`, () => {
testIntVector(Int32);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Int32>`, () => {
const values = valuesArray(Int32Array);
const vector = vectorFromArray(values, new Int32);
testAndValidateVector(vector, valuesTyped(Int32Array), values);
test(`return type is correct`, () => checkDtype(Int32, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 4); });
});
});
describe(`Vector<Int64>`, () => {
testIntVector(Int64);
testIntVector(Int64, bigIntValuesArray(BigInt64Array));
describe(`vectorFromArray accepts regular Arrays to construct Vector<Int64>`, () => {
const values = bigIntValuesArray(BigInt64Array);
const vector = vectorFromArray(values);
testAndValidateVector(vector, bigIntValuesTyped(BigInt64Array), values);
test(`return type is correct`, () => checkDtype(Int64, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 8); });
});
});
describe(`Vector<Uint8>`, () => {
testIntVector(Uint8);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Uint8>`, () => {
const values = valuesArray(Uint8Array);
const vector = vectorFromArray(values, new Uint8);
testAndValidateVector(vector, valuesTyped(Uint8Array), values);
test(`return type is correct`, () => checkDtype(Uint8, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length); });
});
});
describe(`Vector<Uint16>`, () => {
testIntVector(Uint16);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Uint16>`, () => {
const values = valuesArray(Uint16Array);
const vector = vectorFromArray(values, new Uint16);
testAndValidateVector(vector, valuesTyped(Uint16Array), values);
test(`return type is correct`, () => checkDtype(Uint16, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 2); });
});
});
describe(`Vector<Uint32>`, () => {
testIntVector(Uint32);
describe(`vectorFromArray accepts regular Arrays to construct Vector<Uint32>`, () => {
const values = valuesArray(Uint32Array);
const vector = vectorFromArray(values, new Uint32);
testAndValidateVector(vector, valuesTyped(Uint32Array), values);
test(`return type is correct`, () => checkDtype(Uint32, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 4); });
});
});
describe(`Vector<Uint64>`, () => {
testIntVector(Uint64);
testIntVector(Uint64, bigIntValuesArray(BigUint64Array));
describe(`vectorFromArray accepts regular Arrays to construct Vector<Uint64>`, () => {
const values = bigIntValuesArray(BigUint64Array);
const vector = vectorFromArray(values, new Uint64);
testAndValidateVector(vector, bigIntValuesTyped(BigUint64Array), values);
test(`return type is correct`, () => checkDtype(Uint64, vector));
test(`has correct byteLength`, () => { expect(vector.byteLength).toBe(vector.length * 8); });
});
});
});
function testIntVector<T extends Int>(DataType: new () => T, values?: Array<any>) {
const type = new DataType();
const ArrayType = type.ArrayType;
const typed: TypedArray | BigIntArray = new ArrayType(testValuesBuffer);
const jsArray = values || [...typed];
const vector = new Vector([makeData({ type, offset: 0, length: typed.length, nullCount: 0, nullBitmap: null, data: typed })]);
const chunked = testValueBuffers.map((b) => new ArrayType(b))
.map((b) => new Vector([makeData({ type, offset: 0, length: b.length, nullCount: 0, nullBitmap: null, data: b })]))
.reduce((v: any, v2) => v.concat(v2));
const vectorBegin = Math.trunc(vector.length * .25);
const vectorEnd = Math.trunc(vector.length * .75);
const typedBegin = vectorBegin * (typed.length / vector.length);
const typedEnd = vectorEnd * (typed.length / vector.length);
const jsArrayBegin = vectorBegin * (jsArray.length / vector.length);
const jsArrayEnd = vectorEnd * (jsArray.length / vector.length);
const combos = [[`vector`, vector], [`chunked`, chunked]] as [string, Vector<T>][];
for (const [chunksType, vector] of combos) {
describe(chunksType, () => {
// test base case no slicing
describe(`base case no slicing`, () => { testAndValidateVector(vector, typed, jsArray); });
// test slicing without args
describe(`slicing without args`, () => { testAndValidateVector(vector.slice(), typed.slice(), jsArray.slice()); });
// test slicing the middle half
describe(`slice the middle half`, () => {
testAndValidateVector(
vector.slice(vectorBegin, vectorEnd),
typed.slice(typedBegin, typedEnd),
jsArray.slice(jsArrayBegin, jsArrayEnd)
);
});
// test splicing out the middle half
describe(`splicing out the middle half`, () => {
testAndValidateVector(
vector.slice(0, vectorBegin).concat(vector.slice(vectorEnd)),
new ArrayType([...typed.slice(0, typedBegin), ...typed.slice(typedEnd)] as any[]),
[...jsArray.slice(0, jsArrayBegin), ...jsArray.slice(jsArrayEnd)]
);
});
});
}
}
function testFloatVector<T extends Float>(DataType: new () => T, values?: Array<any>) {
const type = new DataType();
const ArrayType = type.ArrayType;
const typed = valuesTyped(ArrayType);
const jsArray = values || [...typed];
const vector = new Vector([makeData({ type, offset: 0, length: typed.length, nullCount: 0, nullBitmap: null, data: typed })]);
const chunked = testValueBuffers.map((b) => new ArrayType(b))
.map((b) => new Vector([makeData({ type, offset: 0, length: b.length, nullCount: 0, nullBitmap: null, data: b })]))
.reduce((v: any, v2) => v.concat(v2));
const begin = Math.trunc(vector.length * .25);
const end = Math.trunc(vector.length * .75);
const combos = [[`vector`, vector], [`chunked`, chunked]] as [string, Vector<T>][];
for (const [chunksType, vector] of combos) {
describe(chunksType, () => {
// test base case no slicing
describe(`base case no slicing`, () => { testAndValidateVector(vector, typed, jsArray); });
// test slicing without args
describe(`slicing without args`, () => { testAndValidateVector(vector.slice(), typed.slice(), jsArray.slice()); });
// test slicing the middle half
describe(`slice the middle half`, () => {
testAndValidateVector(
vector.slice(begin, end),
typed.slice(begin, end),
jsArray.slice(begin, end)
);
});
// test splicing out the middle half
describe(`splicing out the middle half`, () => {
testAndValidateVector(
vector.slice(0, begin).concat(vector.slice(end)),
new ArrayType([...typed.slice(0, begin), ...typed.slice(end)]),
[...jsArray.slice(0, begin), ...jsArray.slice(end)]
);
});
});
}
}
function testAndValidateVector<T extends Int | Float>(vector: Vector<T>, typed: T['TArray'], values: any[] = [...typed]) {
gets_expected_values(vector, typed, values);
iterates_expected_values(vector, typed, values);
indexof_returns_expected_values(vector, typed, values);
slice_returns_a_typedarray(vector);
slices_the_entire_array(vector, typed);
slices_from_minus_20_to_length(vector, typed);
slices_from_0_to_minus_20(vector, typed);
slices_the_array_from_0_to_length_minus_20(vector, typed);
slices_the_array_from_0_to_length_plus_20(vector, typed);
}
function gets_expected_values<T extends Int | Float>(vector: Vector<T>, typed: T['TArray'], values: any[] = [...typed]) {
test(`gets expected values`, () => {
expect.hasAssertions();
let i = -1;
try {
while (++i < vector.length) {
expect(vector.get(i)).toEqual(values[i]);
expect(vector.at(i)).toEqual(values.at(i));
expect(vector.at(-i)).toEqual(values.at(-i));
}
} catch (e) { throw new Error(`${i}: ${e}`); }
});
}
function iterates_expected_values<T extends Int | Float>(vector: Vector<T>, typed: T['TArray'], values: any[] = [...typed]) {
test(`iterates expected values`, () => {
expect.hasAssertions();
let i = -1;
const n = vector.length;
try {
for (const v of vector) {
expect(++i).toBeLessThan(n);
expect(v).toEqual(values[i]);
}
} catch (e) { throw new Error(`${i}: ${e}`); }
});
}
function indexof_returns_expected_values<T extends Int | Float>(vector: Vector<T>, typed: T['TArray'], values: any = [...typed]) {
test(`indexOf returns expected values`, () => {
expect.hasAssertions();
const BPE = vector.ArrayType.BYTES_PER_ELEMENT;
const isFloat16 = util.compareTypes(vector.type, new Float16());
// Create a few random values
let missing: any = new vector.ArrayType(randomBytes(8 * 2 * BPE));
// Special cases convert the values and/or missing to the
// representations that indexOf() expects to receive
if (isFloat16) {
missing = uint16ToFloat64Array(missing);
}
const original = values.slice();
// Combine with the expected values and shuffle the order
const shuffled = shuffle(values.concat([...missing]));
let i = -1, j: number, k: number;
const n = shuffled.length;
try {
while (++i < n) {
const search = shuffled[i];
if (typeof search !== 'number' || !Number.isNaN(search)) {
expect(vector.indexOf(search)).toEqual(original.indexOf(search));
} else {
for (j = -1, k = original.length; ++j < k;) {
if (Number.isNaN(original[j])) { break; }
}
expect(vector.indexOf(search)).toEqual(j < k ? j : -1);
}
}
} catch (e) { throw new Error(`${i} (${shuffled[i]}): ${e}`); }
});
}
function slice_returns_a_typedarray<T extends Int | Float>(vector: Vector<T>) {
test(`slice returns a TypedArray`, () => {
expect.hasAssertions();
expect((vector.slice().toArray())).toBeInstanceOf(vector.ArrayType);
});
}
function slices_the_entire_array<T extends Int | Float>(vector: Vector<T>, values: T['TArray']) {
test(`slices the entire array`, () => {
expect.hasAssertions();
expect(vector.slice().toArray()).toEqual(values);
});
}
function slices_from_minus_20_to_length<T extends Int | Float>(vector: Vector<T>, values: T['TArray']) {
test(`slices from -20 to length`, () => {
expect.hasAssertions();
expect(vector.slice(-20).toArray()).toEqual(values.slice(-(20)));
});
}
function slices_from_0_to_minus_20<T extends Int | Float>(vector: Vector<T>, values: T['TArray']) {
test(`slices from 0 to -20`, () => {
expect.hasAssertions();
expect(vector.slice(0, -20).toArray()).toEqual(values.slice(0, -(20)));
});
}
function slices_the_array_from_0_to_length_minus_20<T extends Int | Float>(vector: Vector<T>, values: T['TArray']) {
test(`slices the array from 0 to length - 20`, () => {
expect.hasAssertions();
expect(vector.slice(0, - 20).toArray()).toEqual(values.slice(0, - 20));
});
}
function slices_the_array_from_0_to_length_plus_20<T extends Int | Float>(vector: Vector<T>, values: T['TArray']) {
test(`slices the array from 0 to length + 20`, () => {
expect.hasAssertions();
expect(vector.slice(0, vector.length + 20).toArray()).toEqual(values.slice(0, values.length + (20)));
});
}
function shuffle(input: any[]) {
const result = input.slice();
let j, tmp, i = result.length;
while (--i > 0) {
j = Math.trunc(Math.random() * (i + 1));
tmp = result[i];
result[i] = result[j];
result[j] = tmp;
}
return result;
}