diff --git a/js/perf/config.ts b/js/perf/config.ts index bbf68de8fc1..6ffe37fa2e2 100644 --- a/js/perf/config.ts +++ b/js/perf/config.ts @@ -28,8 +28,9 @@ console.time('Prepare Data'); const LENGTH = 100000; const NUM_BATCHES = 10; +const cities = ['Charlottesville', 'New York', 'San Francisco', 'Seattle', 'Terre Haute', 'Washington, DC']; -const values = Arrow.vectorFromArray(['Charlottesville', 'New York', 'San Francisco', 'Seattle', 'Terre Haute', 'Washington, DC']); +const values = Arrow.vectorFromArray(cities); const batches = Array.from({ length: NUM_BATCHES }).map(() => { const lat = Float32Array.from( @@ -57,6 +58,27 @@ const batches = Array.from({ length: NUM_BATCHES }).map(() => { }); }); +export const typedArrays = { + uint8Array: Uint8Array.from({ length: LENGTH }, () => Math.random() * 255), + uint16Array: Uint16Array.from({ length: LENGTH }, () => Math.random() * 255), + uint32Array: Uint32Array.from({ length: LENGTH }, () => Math.random() * 255), + uint64Array: BigUint64Array.from({ length: LENGTH }, () => 42n), + + int8Array: Int8Array.from({ length: LENGTH }, () => Math.random() * 255), + int16Array: Int16Array.from({ length: LENGTH }, () => Math.random() * 255), + int32Array: Int32Array.from({ length: LENGTH }, () => Math.random() * 255), + int64Array: BigInt64Array.from({ length: LENGTH }, () => 42n), + + float32Array: Float32Array.from({ length: LENGTH }, () => Math.random() * 255), + float64Array: Float64Array.from({ length: LENGTH }, () => Math.random() * 255) +}; + +export const arrays = { + numbers: Array.from({ length: LENGTH }, () => Math.random() * 255), + booleans: Array.from({ length: LENGTH }, () => Math.random() > 0.5), + strings: Array.from({ length: LENGTH }, () => cities[Math.floor(Math.random() * cities.length)]) +}; + const tracks = new Arrow.Table(batches[0].schema, batches); console.timeEnd('Prepare Data'); @@ -64,7 +86,7 @@ console.timeEnd('Prepare Data'); export default [ { name: 'tracks', - df: tracks, + table: tracks, ipc: Arrow.RecordBatchStreamWriter.writeAll(tracks).toUint8Array(true), countBys: ['origin', 'destination'], counts: [ diff --git a/js/perf/index.ts b/js/perf/index.ts index 97097221f40..ca89da33c9f 100644 --- a/js/perf/index.ts +++ b/js/perf/index.ts @@ -23,14 +23,12 @@ import * as Arrow from '../src/Arrow'; -import config from './config'; +import config, { arrays, typedArrays } from './config'; import b from 'benny'; import { CaseResult, Summary } from 'benny/lib/internal/common-types'; import kleur from 'kleur'; const { RecordBatchReader, RecordBatchStreamWriter } = Arrow; -// const { predicate } = Arrow; -// const { col } = Arrow.predicate; const args = process.argv.slice(2); @@ -55,7 +53,23 @@ function cycle(result: CaseResult, _summary: Summary) { ); } -for (const { name, ipc, df } of config) { +b.suite( + `Vector`, + + ...Object.entries(typedArrays).map(([name, array]) => + b.add(`make vector from ${name}`, () => { + Arrow.makeVector(array); + })), + + ...Object.entries(arrays).map(([name, array]) => + b.add(`make vector from ${name}`, () => { + Arrow.vectorFromArray(array as any); + })), + + b.cycle(cycle) +); + +for (const { name, ipc, table } of config) { b.suite( `Parse`, @@ -64,13 +78,13 @@ for (const { name, ipc, df } of config) { }), b.add(`dataset: ${name}, function: write recordBatches`, () => { - RecordBatchStreamWriter.writeAll(df).toUint8Array(true); + RecordBatchStreamWriter.writeAll(table).toUint8Array(true); }), b.cycle(cycle) ); - const schema = df.schema; + const schema = table.schema; const suites = [{ suite_name: `Get values by index`, @@ -95,7 +109,7 @@ for (const { name, ipc, df } of config) { suite_name, ...schema.fields.map((f, i) => { - const vector = df.getChildAt(i)!; + const vector = table.getChildAt(i)!; return b.add(`dataset: ${name}, column: ${f.name}, length: ${formatNumber(vector.length)}, type: ${vector.type}`, () => { fn(vector); }); @@ -107,87 +121,29 @@ for (const { name, ipc, df } of config) { } -for (const { name, df, counts } of config) { - // for (const { name, df, countBys, counts } of config) { +for (const { name, table, counts } of config) { b.suite( - `DataFrame Iterate`, + `Table Iterate`, - b.add(`dataset: ${name}, numRows: ${formatNumber(df.numRows)}`, () => { - for (const _value of df) { } + b.add(`dataset: ${name}, numRows: ${formatNumber(table.numRows)}`, () => { + for (const _value of table) { } }), b.cycle(cycle) ); - // b.suite( - // `DataFrame Count By`, - - // ...countBys.map((column: string) => b.add( - // `dataset: ${name}, column: ${column}, numRows: ${formatNumber(df.numRows)}, type: ${df.schema.fields.find((c) => c.name === column)!.type}`, - // () => df.countBy(column) - // )), - - // b.cycle(cycle) - // ); - - // b.suite( - // `DataFrame Filter-Scan Count`, - - // ...counts.map(({ column, test, value }: { column: string; test: 'gt' | 'eq'; value: number | string }) => b.add( - // `dataset: ${name}, column: ${column}, numRows: ${formatNumber(df.numRows)}, type: ${df.schema.fields.find((c) => c.name === column)!.type}, test: ${test}, value: ${value}`, - // () => { - // let filteredDf: Arrow.FilteredDataFrame; - // if (test == 'gt') { - // filteredDf = df.filter(col(column).gt(value)); - // } else if (test == 'eq') { - // filteredDf = df.filter(col(column).eq(value)); - // } else { - // throw new Error(`Unrecognized test "${test}"`); - // } - - // return () => filteredDf.count(); - // } - // )), - - // b.cycle(cycle) - // ); - - // b.suite( - // `DataFrame Filter-Iterate`, - - // ...counts.map(({ column, test, value }: { column: string; test: 'gt' | 'eq'; value: number | string }) => b.add( - // `dataset: ${name}, column: ${column}, numRows: ${formatNumber(df.numRows)}, type: ${df.schema.fields.find((c) => c.name === column)!.type}, test: ${test}, value: ${value}`, - // () => { - // let filteredDf: Arrow.FilteredDataFrame; - // if (test == 'gt') { - // filteredDf = df.filter(col(column).gt(value)); - // } else if (test == 'eq') { - // filteredDf = df.filter(col(column).eq(value)); - // } else { - // throw new Error(`Unrecognized test "${test}"`); - // } - - // return () => { - // for (const _value of filteredDf) { } - // }; - // } - // )), - - // b.cycle(cycle) - // ); - b.suite( - `DataFrame Direct Count`, + `Table Direct Count`, ...counts.map(({ column, test, value }: { column: string; test: 'gt' | 'eq'; value: number | string }) => b.add( - `dataset: ${name}, column: ${column}, numRows: ${formatNumber(df.numRows)}, type: ${df.schema.fields.find((c) => c.name === column)!.type}, test: ${test}, value: ${value}`, + `dataset: ${name}, column: ${column}, numRows: ${formatNumber(table.numRows)}, type: ${table.schema.fields.find((c) => c.name === column)!.type}, test: ${test}, value: ${value}`, () => { - const colidx = df.schema.fields.findIndex((c) => c.name === column); + const colidx = table.schema.fields.findIndex((c) => c.name === column); if (test == 'gt') { return () => { let sum = 0; - const batches = df.batches; + const batches = table.batches; const numBatches = batches.length; for (let batchIndex = -1; ++batchIndex < numBatches;) { // load batches @@ -203,7 +159,7 @@ for (const { name, df, counts } of config) { } else if (test == 'eq') { return () => { let sum = 0; - const batches = df.batches; + const batches = table.batches; const numBatches = batches.length; for (let batchIndex = -1; ++batchIndex < numBatches;) { // load batches diff --git a/js/src/Arrow.dom.ts b/js/src/Arrow.dom.ts index a639830ce0e..9e26d34e3ab 100644 --- a/js/src/Arrow.dom.ts +++ b/js/src/Arrow.dom.ts @@ -56,30 +56,9 @@ export { FixedSizeList, Map_, MapRow, Table, - // Column, Schema, Field, Visitor, Vector, makeVector, vectorFromArray, - // BaseVector, - // BinaryVector, - // BoolVector, - // Chunked, - // DateVector, DateDayVector, DateMillisecondVector, - // DecimalVector, - // DictionaryVector, - // FixedSizeBinaryVector, - // FixedSizeListVector, - // FloatVector, Float16Vector, Float32Vector, Float64Vector, - // IntervalVector, IntervalDayTimeVector, IntervalYearMonthVector, - // IntVector, Int8Vector, Int16Vector, Int32Vector, Int64Vector, Uint8Vector, Uint16Vector, Uint32Vector, Uint64Vector, - // ListVector, - // MapVector, - // NullVector, - // StructVector, - // TimestampVector, TimestampSecondVector, TimestampMillisecondVector, TimestampMicrosecondVector, TimestampNanosecondVector, - // TimeVector, TimeSecondVector, TimeMillisecondVector, TimeMicrosecondVector, TimeNanosecondVector, - // UnionVector, DenseUnionVector, SparseUnionVector, - // Utf8Vector, ByteStream, AsyncByteStream, AsyncByteQueue, ReadableSource, WritableSink, RecordBatchReader, RecordBatchFileReader, RecordBatchStreamReader, AsyncRecordBatchFileReader, AsyncRecordBatchStreamReader, RecordBatchWriter, RecordBatchFileWriter, RecordBatchStreamWriter, RecordBatchJSONWriter, @@ -87,8 +66,6 @@ export { Message, RecordBatch, ArrowJSONLike, FileHandle, Readable, Writable, ReadableWritable, ReadableDOMStreamOptions, - // DataFrame, FilteredDataFrame, CountByResult, BindFunc, NextFunc, - // predicate, util, Builder, BinaryBuilder, diff --git a/js/src/Arrow.ts b/js/src/Arrow.ts index ac69c721f51..2ba900fb744 100644 --- a/js/src/Arrow.ts +++ b/js/src/Arrow.ts @@ -88,7 +88,6 @@ export { MessageReader, AsyncMessageReader, JSONMessageReader } from './ipc/mess export { Message } from './ipc/metadata/message'; export { RecordBatch } from './recordbatch'; export { ArrowJSONLike, FileHandle, Readable, Writable, ReadableWritable, ReadableDOMStreamOptions } from './io/interfaces'; -// export { DataFrame, FilteredDataFrame, CountByResult, BindFunc, NextFunc } from './compute/dataframe'; import * as util_bn_ from './util/bn'; import * as util_int_ from './util/int'; @@ -96,10 +95,8 @@ import * as util_bit_ from './util/bit'; import * as util_math_ from './util/math'; import * as util_buffer_ from './util/buffer'; import * as util_vector_ from './util/vector'; -// import * as predicate from './compute/predicate'; import { compareSchemas, compareFields, compareTypes } from './visitor/typecomparator'; -// export { predicate }; /** @ignore */ export const util = { ...util_bn_, diff --git a/js/src/compute/dataframe.ts b/js/src/compute/dataframe.ts deleted file mode 100644 index ea6a075e836..00000000000 --- a/js/src/compute/dataframe.ts +++ /dev/null @@ -1,287 +0,0 @@ -// // 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 { Table } from '../table'; -// import { Vector } from '../vector'; -// import { IntVector } from '../vector/int'; -// import { Field, Schema } from '../schema'; -// import { Predicate, Col, PredicateFunc } from './predicate'; -// import { RecordBatch } from '../recordbatch'; -// import { DataType, Int, Struct, Dictionary } from '../type'; - -// /** @ignore */ -// export type BindFunc = (batch: RecordBatch) => void; -// /** @ignore */ -// export type NextFunc = (idx: number, batch: RecordBatch) => void; - -// /** -// * `DataFrame` extends {@link Table} with support for predicate filtering. -// * -// * You can construct `DataFrames` like tables or convert a `Table` to a `DataFrame` -// * with the constructor. -// * -// * ```ts -// * const df = new DataFrame(table); -// * ``` -// */ -// export class DataFrame extends Table { -// public filter(predicate: Predicate): FilteredDataFrame { -// return new FilteredDataFrame(this.chunks, predicate); -// } -// public scan(next: NextFunc, bind?: BindFunc) { -// const batches = this.chunks, numBatches = batches.length; -// for (let batchIndex = -1; ++batchIndex < numBatches;) { -// // load batches -// const batch = batches[batchIndex]; -// if (bind) { bind(batch); } -// // yield all indices -// for (let index = -1, numRows = batch.length; ++index < numRows;) { -// next(index, batch); -// } -// } -// } -// public scanReverse(next: NextFunc, bind?: BindFunc) { -// const batches = this.chunks, numBatches = batches.length; -// for (let batchIndex = numBatches; --batchIndex >= 0;) { -// // load batches -// const batch = batches[batchIndex]; -// if (bind) { bind(batch); } -// // yield all indices -// for (let index = batch.length; --index >= 0;) { -// next(index, batch); -// } -// } -// } -// public countBy(name: Col | string) { -// const batches = this.chunks, numBatches = batches.length; -// const count_by = typeof name === 'string' ? new Col(name) : name as Col; -// // Assume that all dictionary batches are deltas, which means that the -// // last record batch has the most complete dictionary -// count_by.bind(batches[numBatches - 1]); -// const vector = count_by.vector as V; -// if (!DataType.isDictionary(vector.type)) { -// throw new Error('countBy currently only supports dictionary-encoded columns'); -// } - -// const countByteLength = Math.ceil(Math.log(vector.length) / Math.log(256)); -// const CountsArrayType = countByteLength == 4 ? Uint32Array : -// countByteLength >= 2 ? Uint16Array : Uint8Array; - -// const counts = new CountsArrayType(vector.dictionary.length); -// for (let batchIndex = -1; ++batchIndex < numBatches;) { -// // load batches -// const batch = batches[batchIndex]; -// // rebind the countBy Col -// count_by.bind(batch); -// const keys = (count_by.vector as V).indices; -// // yield all indices -// for (let index = -1, numRows = batch.length; ++index < numRows;) { -// const key = keys.get(index); -// if (key !== null) { counts[key]++; } -// } -// } -// return new CountByResult(vector.dictionary, IntVector.from(counts)); -// } -// } - -// /** @ignore */ -// export class CountByResult extends Table<{ values: T; counts: TCount }> { -// constructor(values: Vector, counts: V) { -// type R = { values: T; counts: TCount }; -// const schema = new Schema([ -// new Field('values', values.type), -// new Field('counts', counts.type) -// ]); -// super(new RecordBatch(schema, counts.length, [values, counts])); -// } -// public toJSON(): Record { -// const values = this.getColumnAt(0)!; -// const counts = this.getColumnAt(1)!; -// const result = {} as { [k: string]: number | null }; -// for (let i = -1; ++i < this.length;) { -// result[values.get(i)] = counts.get(i); -// } -// return result; -// } -// } - -// /** @ignore */ -// class FilteredBatchIterator implements IterableIterator['TValue']> { -// private batchIndex = 0; -// private batch: RecordBatch; -// private index = 0; -// private predicateFunc: PredicateFunc; - -// constructor( -// private batches: RecordBatch[], -// private predicate: Predicate -// ) { -// // TODO: bind batches lazily -// // If predicate doesn't match anything in the batch we don't need -// // to bind the callback -// this.batch = this.batches[this.batchIndex]; -// this.predicateFunc = this.predicate.bind(this.batch); -// } - -// next(): IteratorResult['TValue']> { -// while (this.batchIndex < this.batches.length) { -// while (this.index < this.batch.length) { -// if (this.predicateFunc(this.index, this.batch)) { -// return { -// value: this.batch.get(this.index++) as any, -// }; -// } -// this.index++; -// } - -// if (++this.batchIndex < this.batches.length) { -// this.index = 0; -// this.batch = this.batches[this.batchIndex]; -// this.predicateFunc = this.predicate.bind(this.batch); -// } -// } - -// return {done: true, value: null}; -// } - -// [Symbol.iterator]() { -// return this; -// } -// } - -// /** @ignore */ -// export class FilteredDataFrame extends DataFrame { -// private _predicate: Predicate; -// constructor (batches: RecordBatch[], predicate: Predicate) { -// super(batches); -// this._predicate = predicate; -// } -// public scan(next: NextFunc, bind?: BindFunc) { -// // inlined version of this: -// // this.parent.scan((idx, columns) => { -// // if (this.predicate(idx, columns)) next(idx, columns); -// // }); -// const batches = this._chunks; -// const numBatches = batches.length; -// for (let batchIndex = -1; ++batchIndex < numBatches;) { -// // load batches -// const batch = batches[batchIndex]; -// const predicate = this._predicate.bind(batch); -// let isBound = false; -// // yield all indices -// for (let index = -1, numRows = batch.length; ++index < numRows;) { -// if (predicate(index, batch)) { -// // bind batches lazily - if predicate doesn't match anything -// // in the batch we don't need to call bind on the batch -// if (bind && !isBound) { -// bind(batch); -// isBound = true; -// } -// next(index, batch); -// } -// } -// } -// } -// public scanReverse(next: NextFunc, bind?: BindFunc) { -// const batches = this._chunks; -// const numBatches = batches.length; -// for (let batchIndex = numBatches; --batchIndex >= 0;) { -// // load batches -// const batch = batches[batchIndex]; -// const predicate = this._predicate.bind(batch); -// let isBound = false; -// // yield all indices -// for (let index = batch.length; --index >= 0;) { -// if (predicate(index, batch)) { -// // bind batches lazily - if predicate doesn't match anything -// // in the batch we don't need to call bind on the batch -// if (bind && !isBound) { -// bind(batch); -// isBound = true; -// } -// next(index, batch); -// } -// } -// } -// } -// public count(): number { -// // inlined version of this: -// // let sum = 0; -// // this.parent.scan((idx, columns) => { -// // if (this.predicate(idx, columns)) ++sum; -// // }); -// // return sum; -// let sum = 0; -// const batches = this._chunks; -// const numBatches = batches.length; -// for (let batchIndex = -1; ++batchIndex < numBatches;) { -// // load batches -// const batch = batches[batchIndex]; -// const predicate = this._predicate.bind(batch); -// for (let index = -1, numRows = batch.length; ++index < numRows;) { -// if (predicate(index, batch)) { ++sum; } -// } -// } -// return sum; -// } - -// public [Symbol.iterator](): IterableIterator['TValue']> { -// // inlined version of this: -// // this.parent.scan((idx, columns) => { -// // if (this.predicate(idx, columns)) next(idx, columns); -// // }); -// return new FilteredBatchIterator(this._chunks, this._predicate); -// } -// public filter(predicate: Predicate): FilteredDataFrame { -// return new FilteredDataFrame( -// this._chunks, -// this._predicate.and(predicate) -// ); -// } -// public countBy(name: Col | string) { -// const batches = this._chunks, numBatches = batches.length; -// const count_by = typeof name === 'string' ? new Col(name) : name as Col; -// // Assume that all dictionary batches are deltas, which means that the -// // last record batch has the most complete dictionary -// count_by.bind(batches[numBatches - 1]); -// const vector = count_by.vector as V; -// if (!DataType.isDictionary(vector.type)) { -// throw new Error('countBy currently only supports dictionary-encoded columns'); -// } - -// const countByteLength = Math.ceil(Math.log(vector.length) / Math.log(256)); -// const CountsArrayType = countByteLength == 4 ? Uint32Array : -// countByteLength >= 2 ? Uint16Array : Uint8Array; - -// const counts = new CountsArrayType(vector.dictionary.length); - -// for (let batchIndex = -1; ++batchIndex < numBatches;) { -// // load batches -// const batch = batches[batchIndex]; -// const predicate = this._predicate.bind(batch); -// // rebind the countBy Col -// count_by.bind(batch); -// const keys = (count_by.vector as V).indices; -// // yield all indices -// for (let index = -1, numRows = batch.length; ++index < numRows;) { -// const key = keys.get(index); -// if (key !== null && predicate(index, batch)) { counts[key]++; } -// } -// } -// return new CountByResult(vector.dictionary, IntVector.from(counts)); -// } -// } diff --git a/js/src/compute/predicate.ts b/js/src/compute/predicate.ts deleted file mode 100644 index 775cbf9a13a..00000000000 --- a/js/src/compute/predicate.ts +++ /dev/null @@ -1,292 +0,0 @@ -// // 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 { Vector } from '../vector'; -// import { RecordBatch } from '../recordbatch'; -// import { DictionaryVector } from '../vector/dictionary'; - -// /** @ignore */ -// export type ValueFunc = (idx: number, cols: RecordBatch) => T | null; -// /** @ignore */ -// export type PredicateFunc = (idx: number, cols: RecordBatch) => boolean; - -// /** @ignore */ -// export abstract class Value { -// eq(other: Value | T): Predicate { -// if (!(other instanceof Value)) { other = new Literal(other); } -// return new Equals(this, other); -// } -// le(other: Value | T): Predicate { -// if (!(other instanceof Value)) { other = new Literal(other); } -// return new LTeq(this, other); -// } -// ge(other: Value | T): Predicate { -// if (!(other instanceof Value)) { other = new Literal(other); } -// return new GTeq(this, other); -// } -// lt(other: Value | T): Predicate { -// return new Not(this.ge(other)); -// } -// gt(other: Value | T): Predicate { -// return new Not(this.le(other)); -// } -// ne(other: Value | T): Predicate { -// return new Not(this.eq(other)); -// } -// } - -// /** @ignore */ -// export class Literal extends Value { -// constructor(public v: T) { super(); } -// } - -// /** @ignore */ -// export class Col extends Value { -// public vector!: Vector; -// public colidx!: number; - -// constructor(public name: string) { super(); } -// bind(batch: RecordBatch): (idx: number, batch?: RecordBatch) => any { -// if (!this.colidx) { -// // Assume column index doesn't change between calls to bind -// //this.colidx = cols.findIndex(v => v.name.indexOf(this.name) != -1); -// this.colidx = -1; -// const fields = batch.schema.fields; -// for (let idx = -1; ++idx < fields.length;) { -// if (fields[idx].name === this.name) { -// this.colidx = idx; -// break; -// } -// } -// if (this.colidx < 0) { throw new Error(`Failed to bind Col "${this.name}"`); } -// } - -// const vec = this.vector = batch.getChildAt(this.colidx)!; -// return (idx: number) => vec.get(idx); -// } -// } - -// /** @ignore */ -// export abstract class Predicate { -// abstract bind(batch: RecordBatch): PredicateFunc; -// and(...expr: Predicate[]): And { return new And(this, ...expr); } -// or(...expr: Predicate[]): Or { return new Or(this, ...expr); } -// not(): Predicate { return new Not(this); } -// } - -// /** @ignore */ -// export abstract class ComparisonPredicate extends Predicate { -// constructor(public readonly left: Value, public readonly right: Value) { -// super(); -// } - -// bind(batch: RecordBatch) { -// if (this.left instanceof Literal) { -// if (this.right instanceof Literal) { -// return this._bindLitLit(batch, this.left, this.right); -// } else { // right is a Col - -// return this._bindLitCol(batch, this.left, this.right as Col); -// } -// } else { // left is a Col -// if (this.right instanceof Literal) { -// return this._bindColLit(batch, this.left as Col, this.right); -// } else { // right is a Col -// return this._bindColCol(batch, this.left as Col, this.right as Col); -// } -// } -// } - -// protected abstract _bindLitLit(batch: RecordBatch, left: Literal, right: Literal): PredicateFunc; -// protected abstract _bindColCol(batch: RecordBatch, left: Col, right: Col): PredicateFunc; -// protected abstract _bindColLit(batch: RecordBatch, col: Col, lit: Literal): PredicateFunc; -// protected abstract _bindLitCol(batch: RecordBatch, lit: Literal, col: Col): PredicateFunc; -// } - -// /** @ignore */ -// export abstract class CombinationPredicate extends Predicate { -// readonly children: Predicate[]; -// constructor(...children: Predicate[]) { -// super(); -// this.children = children; -// } -// } -// // add children to prototype so it doesn't get mangled in es2015/umd -// ( CombinationPredicate.prototype).children = Object.freeze([]); // freeze for safety - -// /** @ignore */ -// export class And extends CombinationPredicate { -// constructor(...children: Predicate[]) { -// // Flatten any Ands -// children = children.reduce((accum: Predicate[], p: Predicate): Predicate[] => { -// return accum.concat(p instanceof And ? p.children : p); -// }, []); -// super(...children); -// } -// bind(batch: RecordBatch) { -// const bound = this.children.map((p) => p.bind(batch)); -// return (idx: number, batch: RecordBatch) => bound.every((p) => p(idx, batch)); -// } -// } - -// /** @ignore */ -// export class Or extends CombinationPredicate { -// constructor(...children: Predicate[]) { -// // Flatten any Ors -// children = children.reduce((accum: Predicate[], p: Predicate): Predicate[] => { -// return accum.concat(p instanceof Or ? p.children : p); -// }, []); -// super(...children); -// } -// bind(batch: RecordBatch) { -// const bound = this.children.map((p) => p.bind(batch)); -// return (idx: number, batch: RecordBatch) => bound.some((p) => p(idx, batch)); -// } -// } - -// /** @ignore */ -// export class Equals extends ComparisonPredicate { -// // Helpers used to cache dictionary reverse lookups between calls to bind -// private lastDictionary: Vector|undefined; -// private lastKey: number|undefined; - -// protected _bindLitLit(_batch: RecordBatch, left: Literal, right: Literal): PredicateFunc { -// const rtrn: boolean = left.v == right.v; -// return () => rtrn; -// } - -// protected _bindColCol(batch: RecordBatch, left: Col, right: Col): PredicateFunc { -// const left_func = left.bind(batch); -// const right_func = right.bind(batch); -// return (idx: number, batch: RecordBatch) => left_func(idx, batch) == right_func(idx, batch); -// } - -// protected _bindColLit(batch: RecordBatch, col: Col, lit: Literal): PredicateFunc { -// const col_func = col.bind(batch); -// if (col.vector instanceof DictionaryVector) { -// let key: any; -// const vector = col.vector as DictionaryVector; -// if (vector.dictionary !== this.lastDictionary) { -// key = vector.reverseLookup(lit.v); -// this.lastDictionary = vector.dictionary; -// this.lastKey = key; -// } else { -// key = this.lastKey; -// } - -// if (key === -1) { -// // the value doesn't exist in the dictionary - always return -// // false -// // TODO: special-case of PredicateFunc that encapsulates this -// // "always false" behavior. That way filtering operations don't -// // have to bother checking -// return () => false; -// } else { -// return (idx: number) => { -// return vector.getKey(idx) === key; -// }; -// } -// } else { -// return (idx: number, cols: RecordBatch) => col_func(idx, cols) == lit.v; -// } -// } - -// protected _bindLitCol(batch: RecordBatch, lit: Literal, col: Col) { -// // Equals is commutative -// return this._bindColLit(batch, col, lit); -// } -// } - -// /** @ignore */ -// export class LTeq extends ComparisonPredicate { -// protected _bindLitLit(_batch: RecordBatch, left: Literal, right: Literal): PredicateFunc { -// const rtrn: boolean = left.v <= right.v; -// return () => rtrn; -// } - -// protected _bindColCol(batch: RecordBatch, left: Col, right: Col): PredicateFunc { -// const left_func = left.bind(batch); -// const right_func = right.bind(batch); -// return (idx: number, cols: RecordBatch) => left_func(idx, cols) <= right_func(idx, cols); -// } - -// protected _bindColLit(batch: RecordBatch, col: Col, lit: Literal): PredicateFunc { -// const col_func = col.bind(batch); -// return (idx: number, cols: RecordBatch) => col_func(idx, cols) <= lit.v; -// } - -// protected _bindLitCol(batch: RecordBatch, lit: Literal, col: Col) { -// const col_func = col.bind(batch); -// return (idx: number, cols: RecordBatch) => lit.v <= col_func(idx, cols); -// } -// } - -// /** @ignore */ -// export class GTeq extends ComparisonPredicate { -// protected _bindLitLit(_batch: RecordBatch, left: Literal, right: Literal): PredicateFunc { -// const rtrn: boolean = left.v >= right.v; -// return () => rtrn; -// } - -// protected _bindColCol(batch: RecordBatch, left: Col, right: Col): PredicateFunc { -// const left_func = left.bind(batch); -// const right_func = right.bind(batch); -// return (idx: number, cols: RecordBatch) => left_func(idx, cols) >= right_func(idx, cols); -// } - -// protected _bindColLit(batch: RecordBatch, col: Col, lit: Literal): PredicateFunc { -// const col_func = col.bind(batch); -// return (idx: number, cols: RecordBatch) => col_func(idx, cols) >= lit.v; -// } - -// protected _bindLitCol(batch: RecordBatch, lit: Literal, col: Col) { -// const col_func = col.bind(batch); -// return (idx: number, cols: RecordBatch) => lit.v >= col_func(idx, cols); -// } -// } - -// /** @ignore */ -// export class Not extends Predicate { -// constructor(public readonly child: Predicate) { -// super(); -// } - -// bind(batch: RecordBatch) { -// const func = this.child.bind(batch); -// return (idx: number, batch: RecordBatch) => !func(idx, batch); -// } -// } - -// /** @ignore */ -// export class CustomPredicate extends Predicate { -// constructor(private next: PredicateFunc, private bind_: (batch: RecordBatch) => void) { -// super(); -// } - -// bind(batch: RecordBatch) { -// this.bind_(batch); -// return this.next; -// } -// } - -// export function lit(v: any): Value { return new Literal(v); } -// export function col(n: string): Col { return new Col(n); } -// export function and(...p: Predicate[]): And { return new And(...p); } -// export function or(...p: Predicate[]): Or { return new Or(...p); } -// export function custom(next: PredicateFunc, bind: (batch: RecordBatch) => void) { -// return new CustomPredicate(next, bind); -// } diff --git a/js/src/util/bn.ts b/js/src/util/bn.ts index 7c71969a419..db77d084e95 100644 --- a/js/src/util/bn.ts +++ b/js/src/util/bn.ts @@ -36,10 +36,10 @@ function BigNum(this: any, x: any, ...xs: any) { } BigNum.prototype[isArrowBigNumSymbol] = true; -BigNum.prototype.toJSON = function>(this: T) { return `"${bignumToString(this)}"`; }; -BigNum.prototype.valueOf = function>(this: T) { return bignumToNumber(this); }; -BigNum.prototype.toString = function>(this: T) { return bignumToString(this); }; -BigNum.prototype[Symbol.toPrimitive] = function>(this: T, hint: 'string' | 'number' | 'default' = 'default') { +BigNum.prototype.toJSON = function >(this: T) { return `"${bignumToString(this)}"`; }; +BigNum.prototype.valueOf = function >(this: T) { return bignumToNumber(this); }; +BigNum.prototype.toString = function >(this: T) { return bignumToString(this); }; +BigNum.prototype[Symbol.toPrimitive] = function >(this: T, hint: 'string' | 'number' | 'default' = 'default') { switch (hint) { case 'number': return bignumToNumber(this); case 'string': return bignumToString(this); @@ -53,7 +53,7 @@ BigNum.prototype[Symbol.toPrimitive] = function>(this: type TypedArrayConstructorArgs = [number | void] | [Iterable | Iterable] | - [ArrayBufferLike, number | void, number | void] ; + [ArrayBufferLike, number | void, number | void]; /** @ignore */ function SignedBigNum(this: any, ...args: TypedArrayConstructorArgs) { return BigNum.apply(this, args); } @@ -62,12 +62,12 @@ function UnsignedBigNum(this: any, ...args: TypedArrayConstructorArgs) { return /** @ignore */ function DecimalBigNum(this: any, ...args: TypedArrayConstructorArgs) { return BigNum.apply(this, args); } -Object.setPrototypeOf(SignedBigNum.prototype, Object.create(Int32Array.prototype)); +Object.setPrototypeOf(SignedBigNum.prototype, Object.create(Int32Array.prototype)); Object.setPrototypeOf(UnsignedBigNum.prototype, Object.create(Uint32Array.prototype)); -Object.setPrototypeOf(DecimalBigNum.prototype, Object.create(Uint32Array.prototype)); -Object.assign(SignedBigNum.prototype, BigNum.prototype, { 'constructor': SignedBigNum, 'signed': true, 'TypedArray': Int32Array, 'BigIntArray': BigInt64Array }); +Object.setPrototypeOf(DecimalBigNum.prototype, Object.create(Uint32Array.prototype)); +Object.assign(SignedBigNum.prototype, BigNum.prototype, { 'constructor': SignedBigNum, 'signed': true, 'TypedArray': Int32Array, 'BigIntArray': BigInt64Array }); Object.assign(UnsignedBigNum.prototype, BigNum.prototype, { 'constructor': UnsignedBigNum, 'signed': false, 'TypedArray': Uint32Array, 'BigIntArray': BigUint64Array }); -Object.assign(DecimalBigNum.prototype, BigNum.prototype, { 'constructor': DecimalBigNum, 'signed': true, 'TypedArray': Uint32Array, 'BigIntArray': BigUint64Array }); +Object.assign(DecimalBigNum.prototype, BigNum.prototype, { 'constructor': DecimalBigNum, 'signed': true, 'TypedArray': Uint32Array, 'BigIntArray': BigUint64Array }); /** @ignore */ function bignumToNumber>(bn: T) { @@ -92,7 +92,7 @@ export let bignumToBigInt: { >(a: T): bigint }; if (!BigIntAvailable) { bignumToString = decimalToString; - bignumToBigInt = bignumToString; + bignumToBigInt = bignumToString; } else { bignumToBigInt = (>(a: T) => a.byteLength === 8 ? new a['BigIntArray'](a.buffer, a.byteOffset, 1)[0] : decimalToString(a)); bignumToString = (>(a: T) => a.byteLength === 8 ? `${new a['BigIntArray'](a.buffer, a.byteOffset, 1)[0]}` : decimalToString(a)); @@ -123,32 +123,32 @@ export class BN { /** @nocollapse */ public static new(num: T, isSigned?: boolean): (T & BN) { switch (isSigned) { - case true: return new ( SignedBigNum)(num) as (T & BN); - case false: return new ( UnsignedBigNum)(num) as (T & BN); + case true: return new (SignedBigNum)(num) as (T & BN); + case false: return new (UnsignedBigNum)(num) as (T & BN); } switch (num.constructor) { case Int8Array: case Int16Array: case Int32Array: case BigInt64Array: - return new ( SignedBigNum)(num) as (T & BN); + return new (SignedBigNum)(num) as (T & BN); } if (num.byteLength === 16) { - return new ( DecimalBigNum)(num) as (T & BN); + return new (DecimalBigNum)(num) as (T & BN); } - return new ( UnsignedBigNum)(num) as (T & BN); + return new (UnsignedBigNum)(num) as (T & BN); } /** @nocollapse */ public static signed(num: T): (T & BN) { - return new ( SignedBigNum)(num) as (T & BN); + return new (SignedBigNum)(num) as (T & BN); } /** @nocollapse */ public static unsigned(num: T): (T & BN) { - return new ( UnsignedBigNum)(num) as (T & BN); + return new (UnsignedBigNum)(num) as (T & BN); } /** @nocollapse */ public static decimal(num: T): (T & BN) { - return new ( DecimalBigNum)(num) as (T & BN); + return new (DecimalBigNum)(num) as (T & BN); } constructor(num: T, isSigned?: boolean) { return BN.new(num, isSigned) as any; @@ -158,20 +158,20 @@ export class BN { /** @ignore */ export interface BN extends TypedArrayLike { - new(buffer: T, signed?: boolean): T; + new (buffer: T, signed?: boolean): T; readonly signed: boolean; readonly TypedArray: TypedArrayConstructor; readonly BigIntArray: BigIntArrayConstructor; [Symbol.toStringTag]: - 'Int8Array' | - 'Int16Array' | - 'Int32Array' | - 'Uint8Array' | - 'Uint16Array' | - 'Uint32Array' | - 'Uint8ClampedArray'; + 'Int8Array' | + 'Int16Array' | + 'Int32Array' | + 'Uint8Array' | + 'Uint16Array' | + 'Uint32Array' | + 'Uint8ClampedArray'; /** * Convert the bytes to their (positive) decimal representation for printing diff --git a/js/test/unit/dataframe-tests.ts b/js/test/unit/dataframe-tests.ts deleted file mode 100644 index 61f53f8d221..00000000000 --- a/js/test/unit/dataframe-tests.ts +++ /dev/null @@ -1,282 +0,0 @@ -// 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 '../jest-extensions'; -// import { -// predicate, DataFrame, RecordBatch -// } from 'apache-arrow'; -// import { test_data } from './table-tests'; -// import { jest } from '@jest/globals'; - -// const { col, lit, custom, and, or, And, Or } = predicate; - -// const F32 = 0, I32 = 1, DICT = 2; - -// describe(`DataFrame`, () => { - -// for (let datum of test_data) { -// describe(datum.name, () => { - -// describe(`scan()`, () => { -// test(`yields all values`, () => { -// const df = new DataFrame(datum.table()); -// let expected_idx = 0; -// df.scan((idx, batch) => { -// const columns = batch.schema.fields.map((_, i) => batch.getChildAt(i)!); -// expect(columns.map((c) => c.get(idx))).toEqual(values[expected_idx++]); -// }); -// }); -// test(`calls bind function with every batch`, () => { -// const df = new DataFrame(datum.table()); -// let bind = jest.fn(); -// df.scan(() => { }, bind); -// for (let batch of df.chunks) { -// expect(bind).toHaveBeenCalledWith(batch); -// } -// }); -// }); -// describe(`scanReverse()`, () => { -// test(`yields all values`, () => { -// const df = new DataFrame(datum.table()); -// let expected_idx = values.length; -// df.scanReverse((idx, batch) => { -// const columns = batch.schema.fields.map((_, i) => batch.getChildAt(i)!); -// expect(columns.map((c) => c.get(idx))).toEqual(values[--expected_idx]); -// }); -// }); -// test(`calls bind function with every batch`, () => { -// const df = new DataFrame(datum.table()); -// let bind = jest.fn(); -// df.scanReverse(() => { }, bind); -// for (let batch of df.chunks) { -// expect(bind).toHaveBeenCalledWith(batch); -// } -// }); -// }); -// test(`count() returns the correct length`, () => { -// const df = new DataFrame(datum.table()); -// const values = datum.values(); -// expect(df.count()).toEqual(values.length); -// }); -// test(`getColumnIndex`, () => { -// const df = new DataFrame(datum.table()); -// expect(df.getColumnIndex('i32')).toEqual(I32); -// expect(df.getColumnIndex('f32')).toEqual(F32); -// expect(df.getColumnIndex('dictionary')).toEqual(DICT); -// }); -// const df = new DataFrame(datum.table()); -// const values = datum.values(); -// let get_i32: (idx: number) => number, get_f32: (idx: number) => number; -// const filter_tests = [ -// { -// name: `filter on f32 >= 0`, -// filtered: df.filter(col('f32').ge(0)), -// expected: values.filter((row) => row[F32] >= 0) -// }, { -// name: `filter on 0 <= f32`, -// filtered: df.filter(lit(0).le(col('f32'))), -// expected: values.filter((row) => 0 <= row[F32]) -// }, { -// name: `filter on i32 <= 0`, -// filtered: df.filter(col('i32').le(0)), -// expected: values.filter((row) => row[I32] <= 0) -// }, { -// name: `filter on 0 >= i32`, -// filtered: df.filter(lit(0).ge(col('i32'))), -// expected: values.filter((row) => 0 >= row[I32]) -// }, { -// name: `filter on f32 < 0`, -// filtered: df.filter(col('f32').lt(0)), -// expected: values.filter((row) => row[F32] < 0) -// }, { -// name: `filter on i32 > 1 (empty)`, -// filtered: df.filter(col('i32').gt(0)), -// expected: values.filter((row) => row[I32] > 0) -// }, { -// name: `filter on f32 <= -.25 || f3 >= .25`, -// filtered: df.filter(col('f32').le(-.25).or(col('f32').ge(.25))), -// expected: values.filter((row) => row[F32] <= -.25 || row[F32] >= .25) -// }, { -// name: `filter on !(f32 <= -.25 || f3 >= .25) (not)`, -// filtered: df.filter(col('f32').le(-.25).or(col('f32').ge(.25)).not()), -// expected: values.filter((row) => !(row[F32] <= -.25 || row[F32] >= .25)) -// }, { -// name: `filter method combines predicates (f32 >= 0 && i32 <= 0)`, -// filtered: df.filter(col('i32').le(0)).filter(col('f32').ge(0)), -// expected: values.filter((row) => row[I32] <= 0 && row[F32] >= 0) -// }, { -// name: `filter on dictionary == 'a'`, -// filtered: df.filter(col('dictionary').eq('a')), -// expected: values.filter((row) => row[DICT] === 'a') -// }, { -// name: `filter on 'a' == dictionary (commutativity)`, -// filtered: df.filter(lit('a').eq(col('dictionary'))), -// expected: values.filter((row) => row[DICT] === 'a') -// }, { -// name: `filter on dictionary != 'b'`, -// filtered: df.filter(col('dictionary').ne('b')), -// expected: values.filter((row) => row[DICT] !== 'b') -// }, { -// name: `filter on f32 >= i32`, -// filtered: df.filter(col('f32').ge(col('i32'))), -// expected: values.filter((row) => row[F32] >= row[I32]) -// }, { -// name: `filter on f32 <= i32`, -// filtered: df.filter(col('f32').le(col('i32'))), -// expected: values.filter((row) => row[F32] <= row[I32]) -// }, { -// name: `filter on f32*i32 > 0 (custom predicate)`, -// filtered: df.filter(custom( -// (idx: number) => (get_f32(idx) * get_i32(idx) > 0), -// (batch: RecordBatch) => { -// get_f32 = col('f32').bind(batch); -// get_i32 = col('i32').bind(batch); -// })), -// expected: values.filter((row) => (row[F32] as number) * (row[I32] as number) > 0) -// }, { -// name: `filter out all records`, -// filtered: df.filter(lit(1).eq(0)), -// expected: [] -// } -// ]; -// for (let this_test of filter_tests) { -// const { name, filtered, expected } = this_test; -// describe(name, () => { -// test(`count() returns the correct length`, () => { -// expect(filtered.count()).toEqual(expected.length); -// }); -// describe(`scan()`, () => { -// test(`iterates over expected values`, () => { -// let expected_idx = 0; -// filtered.scan((idx, batch) => { -// const columns = batch.schema.fields.map((_, i) => batch.getChildAt(i)!); -// expect(columns.map((c) => c.get(idx))).toEqual(expected[expected_idx++]); -// }); -// }); -// test(`calls bind function lazily`, () => { -// let bind = jest.fn(); -// filtered.scan(() => { }, bind); -// if (expected.length) { -// expect(bind).toHaveBeenCalled(); -// } else { -// expect(bind).not.toHaveBeenCalled(); -// } -// }); -// }); -// describe(`scanReverse()`, () => { -// test(`iterates over expected values in reverse`, () => { -// let expected_idx = expected.length; -// filtered.scanReverse((idx, batch) => { -// const columns = batch.schema.fields.map((_, i) => batch.getChildAt(i)!); -// expect(columns.map((c) => c.get(idx))).toEqual(expected[--expected_idx]); -// }); -// }); -// test(`calls bind function lazily`, () => { -// let bind = jest.fn(); -// filtered.scanReverse(() => { }, bind); -// if (expected.length) { -// expect(bind).toHaveBeenCalled(); -// } else { -// expect(bind).not.toHaveBeenCalled(); -// } -// }); -// }); -// }); -// } -// test(`countBy on dictionary returns the correct counts`, () => { -// // Make sure countBy works both with and without the Col wrapper -// // class -// let expected: { [key: string]: number } = { 'a': 0, 'b': 0, 'c': 0 }; -// for (let row of values) { -// expected[row[DICT]] += 1; -// } - -// expect(df.countBy(col('dictionary')).toJSON()).toEqual(expected); -// expect(df.countBy('dictionary').toJSON()).toEqual(expected); -// }); -// test(`countBy on dictionary with filter returns the correct counts`, () => { -// let expected: { [key: string]: number } = { 'a': 0, 'b': 0, 'c': 0 }; -// for (let row of values) { -// if (row[I32] === 1) { expected[row[DICT]] += 1; } -// } - -// expect(df.filter(col('i32').eq(1)).countBy('dictionary').toJSON()).toEqual(expected); -// }); -// test(`countBy on non dictionary column throws error`, () => { -// expect(() => { df.countBy('i32'); }).toThrow(); -// expect(() => { df.filter(col('dict').eq('a')).countBy('i32'); }).toThrow(); -// }); -// test(`countBy on non-existent column throws error`, () => { -// expect(() => { df.countBy('FAKE' as any); }).toThrow(); -// }); -// test(`table.select() basic tests`, () => { -// let selected = df.select('f32', 'dictionary'); -// expect(selected.schema.fields).toHaveLength(2); -// expect(selected.schema.fields[0]).toEqual(df.schema.fields[0]); -// expect(selected.schema.fields[1]).toEqual(df.schema.fields[2]); - -// expect(selected.numRows).toEqual(values.length); -// let idx = 0, expected_row; -// for (let row of selected) { -// expected_row = values[idx++]; -// expect(row.f32).toEqual(expected_row[F32]); -// expect(row.dictionary).toEqual(expected_row[DICT]); -// } -// }); -// test(`table.filter(..).count() on always false predicates returns 0`, () => { -// expect(df.filter(col('i32').ge(100)).count()).toEqual(0); -// expect(df.filter(col('dictionary').eq('z')).count()).toEqual(0); -// }); -// describe(`lit-lit comparison`, () => { -// test(`always-false count() returns 0`, () => { -// expect(df.filter(lit('abc').eq('def')).count()).toEqual(0); -// expect(df.filter(lit(0).ge(1)).count()).toEqual(0); -// }); -// test(`always-true count() returns length`, () => { -// expect(df.filter(lit('abc').eq('abc')).count()).toEqual(df.length); -// expect(df.filter(lit(-100).le(0)).count()).toEqual(df.length); -// }); -// }); -// describe(`col-col comparison`, () => { -// test(`always-false count() returns 0`, () => { -// expect(df.filter(col('dictionary').eq(col('i32'))).count()).toEqual(0); -// }); -// test(`always-true count() returns length`, () => { -// expect(df.filter(col('dictionary').eq(col('dictionary'))).count()).toEqual(df.length); -// }); -// }); -// }); -// } -// }); - -// describe(`Predicate`, () => { -// const p1 = col('a').gt(100); -// const p2 = col('a').lt(1000); -// const p3 = col('b').eq('foo'); -// const p4 = col('c').eq('bar'); -// const expected = [p1, p2, p3, p4]; -// test(`and flattens children`, () => { -// expect(and(p1, p2, p3, p4).children).toEqual(expected); -// expect(and(p1.and(p2), new And(p3, p4)).children).toEqual(expected); -// expect(and(p1.and(p2, p3, p4)).children).toEqual(expected); -// }); -// test(`or flattens children`, () => { -// expect(or(p1, p2, p3, p4).children).toEqual(expected); -// expect(or(p1.or(p2), new Or(p3, p4)).children).toEqual(expected); -// expect(or(p1.or(p2, p3, p4)).children).toEqual(expected); -// }); -// });