diff --git a/js/src/type.ts b/js/src/type.ts index f44af74f086..c7d73623a34 100644 --- a/js/src/type.ts +++ b/js/src/type.ts @@ -38,8 +38,7 @@ export type IntBitWidth = 8 | 16 | 32 | 64; export type IsSigned = { 'true': true; 'false': false }; /** @ignore */ export type RowLike = - { readonly length: number } - & ( Iterable ) + ( Iterable ) & { [P in keyof T]: T[P]['TValue'] } & { get(key: K): T[K]['TValue']; } ; diff --git a/js/src/util/vector.ts b/js/src/util/vector.ts index 747ba8edafe..8acf42e1222 100644 --- a/js/src/util/vector.ts +++ b/js/src/util/vector.ts @@ -16,7 +16,7 @@ // under the License. import { Vector } from '../vector'; -import { Row } from '../vector/row'; +import { Row, kLength } from '../vector/row'; import { compareArrayLike } from '../util/buffer'; /** @ignore */ @@ -75,75 +75,105 @@ export function createElementComparator(search: any) { } // Compare Array-likes if (Array.isArray(search)) { - const n = (search as any).length; - const fns = [] as ((x: any) => boolean)[]; - for (let i = -1; ++i < n;) { - fns[i] = createElementComparator((search as any)[i]); - } - return (value: any) => { - if (!value || value.length !== n) { return false; } - // Handle the case where the search element is an Array, but the - // values are Rows or Vectors, e.g. list.indexOf(['foo', 'bar']) - if ((value instanceof Row) || (value instanceof Vector)) { - for (let i = -1, n = value.length; ++i < n;) { - if (!(fns[i]((value as any).get(i)))) { return false; } - } - return true; - } - for (let i = -1, n = value.length; ++i < n;) { - if (!(fns[i](value[i]))) { return false; } - } - return true; - }; + return createArrayLikeComparator(search); + } + // Compare Rows + if (search instanceof Row) { + return createRowComparator(search); } // Compare Vectors if (search instanceof Vector) { - const n = search.length; - const C = search.constructor as any; - const fns = [] as ((x: any) => boolean)[]; - for (let i = -1; ++i < n;) { - fns[i] = createElementComparator((search as any).get(i)); - } - return (value: any) => { - if (!(value instanceof C)) { return false; } - if (!(value.length === n)) { return false; } + return createVectorComparator(search); + } + // Compare non-empty Objects + const keys = Object.keys(search); + if (keys.length > 0) { + return createObjectKeysComparator(search, keys); + } + // No valid comparator + return () => false; +} + +/** @ignore */ +function createArrayLikeComparator(search: ArrayLike) { + const n = search.length; + const fns = [] as ((x: any) => boolean)[]; + for (let i = -1; ++i < n;) { + fns[i] = createElementComparator((search as any)[i]); + } + return (value: any) => { + if (!value) { return false; } + // Handle the case where the search element is an Array, but the + // values are Rows or Vectors, e.g. list.indexOf(['foo', 'bar']) + if (value instanceof Row) { + if (value[kLength] !== n) { return false; } for (let i = -1; ++i < n;) { if (!(fns[i](value.get(i)))) { return false; } } return true; - }; - } - // Compare Rows - if (search instanceof Row) { - const n = search.length; - const fns = [] as ((x: any) => boolean)[]; - for (let i = -1; ++i < n;) { - fns[i] = createElementComparator((search as any).get(i)); } - return (value: any) => { - if (!(value.length === n)) { return false; } + if (value.length !== n) { return false; } + if (value instanceof Vector) { for (let i = -1; ++i < n;) { if (!(fns[i](value.get(i)))) { return false; } } return true; - }; + } + for (let i = -1; ++i < n;) { + if (!(fns[i](value[i]))) { return false; } + } + return true; + }; +} + +/** @ignore */ +function createRowComparator(search: Row) { + const n = search[kLength]; + const C = search.constructor as any; + const fns = [] as ((x: any) => boolean)[]; + for (let i = -1; ++i < n;) { + fns[i] = createElementComparator(search.get(i)); } - // Compare non-empty Objects - const keys = Object.keys(search); - if (keys.length > 0) { - const n = keys.length; - const fns = [] as ((x: any) => boolean)[]; + return (value: any) => { + if (!(value instanceof C)) { return false; } + if (!(value[kLength] === n)) { return false; } for (let i = -1; ++i < n;) { - fns[i] = createElementComparator(search[keys[i]]); + if (!(fns[i](value.get(i)))) { return false; } } - return (value: any) => { - if (!value || typeof value !== 'object') { return false; } - for (let i = -1; ++i < n;) { - if (!(fns[i](value[keys[i]]))) { return false; } - } - return true; - }; + return true; + }; +} + +/** @ignore */ +function createVectorComparator(search: Vector) { + const n = search.length; + const C = search.constructor as any; + const fns = [] as ((x: any) => boolean)[]; + for (let i = -1; ++i < n;) { + fns[i] = createElementComparator((search as any).get(i)); } - // No valid comparator - return () => false; + return (value: any) => { + if (!(value instanceof C)) { return false; } + if (!(value.length === n)) { return false; } + for (let i = -1; ++i < n;) { + if (!(fns[i](value.get(i)))) { return false; } + } + return true; + }; +} + +/** @ignore */ +function createObjectKeysComparator(search: any, keys: string[]) { + const n = keys.length; + const fns = [] as ((x: any) => boolean)[]; + for (let i = -1; ++i < n;) { + fns[i] = createElementComparator(search[keys[i]]); + } + return (value: any) => { + if (!value || typeof value !== 'object') { return false; } + for (let i = -1; ++i < n;) { + if (!(fns[i](value[keys[i]]))) { return false; } + } + return true; + }; } diff --git a/js/src/vector/map.ts b/js/src/vector/map.ts index 55a195be900..84532725811 100644 --- a/js/src/vector/map.ts +++ b/js/src/vector/map.ts @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { RowProxyGenerator } from './row'; +import { Row } from './row'; import { Vector } from '../vector'; import { BaseVector } from './base'; import { DataType, Map_, Struct } from '../type'; @@ -25,8 +25,8 @@ export class MapVector extends Base return Vector.new(this.data.clone(new Struct(this.type.children))); } // @ts-ignore - private _rowProxy: RowProxyGenerator; - public get rowProxy(): RowProxyGenerator { - return this._rowProxy || (this._rowProxy = RowProxyGenerator.new(this.type.children || [], true)); + private _rowProxy: Row; + public get rowProxy(): Row { + return this._rowProxy || (this._rowProxy = Row.new(this, this.type.children || [], true)); } } diff --git a/js/src/vector/row.ts b/js/src/vector/row.ts index 5c12c77b844..396eb3d5742 100644 --- a/js/src/vector/row.ts +++ b/js/src/vector/row.ts @@ -17,31 +17,76 @@ import { Field } from '../schema'; import { MapVector } from '../vector/map'; -import { DataType } from '../type'; +import { DataType, RowLike } from '../type'; import { valueToString } from '../util/pretty'; import { StructVector } from '../vector/struct'; -/** @ignore */ const columnDescriptor = { enumerable: true, configurable: false, get: () => {} }; -/** @ignore */ const lengthDescriptor = { writable: false, enumerable: false, configurable: false, value: -1 }; +/** @ignore */ export const kLength = Symbol.for('length'); +/** @ignore */ export const kParent = Symbol.for('parent'); +/** @ignore */ export const kRowIndex = Symbol.for('rowIndex'); +/** @ignore */ const columnDescriptor = { enumerable: true, configurable: false, get: null as any }; +/** @ignore */ const rowLengthDescriptor = { writable: false, enumerable: false, configurable: true, value: null as any }; +/** @ignore */ const rowParentDescriptor = { writable: false, enumerable: false, configurable: false, value: null as any }; +/** @ignore */ export class Row implements Iterable { [key: string]: T[keyof T]['TValue']; + /** @nocollapse */ + public static new(parent: MapVector | StructVector, schemaOrFields: T | Field[], fieldsAreEnumerable = false): RowLike & Row { + let schema: T, fields: Field[]; + if (Array.isArray(schemaOrFields)) { + fields = schemaOrFields; + } else { + schema = schemaOrFields; + fieldsAreEnumerable = true; + fields = Object.keys(schema).map((x) => new Field(x, schema[x])); + } + return new Row(parent, fields, fieldsAreEnumerable) as RowLike & Row; + } // @ts-ignore - public parent: MapVector | StructVector; + private [kParent]: MapVector | StructVector; // @ts-ignore - public rowIndex: number; + private [kLength]: number; // @ts-ignore - public readonly length: number; - constructor(parent: MapVector | StructVector, rowIndex: number) { - this.parent = parent; - this.rowIndex = rowIndex; + private [kRowIndex]: number; + private constructor(parent: MapVector | StructVector, fields: Field[], fieldsAreEnumerable: boolean) { + rowParentDescriptor.value = parent; + rowLengthDescriptor.value = fields.length; + Object.defineProperty(this, kParent, rowParentDescriptor); + Object.defineProperty(this, kLength, rowLengthDescriptor); + fields.forEach((field, columnIndex) => { + if (!this.hasOwnProperty(field.name)) { + columnDescriptor.enumerable = fieldsAreEnumerable; + columnDescriptor.get || (columnDescriptor.get = this._bindGetter(columnIndex)); + Object.defineProperty(this, field.name, columnDescriptor); + } + if (!this.hasOwnProperty(columnIndex)) { + columnDescriptor.enumerable = !fieldsAreEnumerable; + columnDescriptor.get || (columnDescriptor.get = this._bindGetter(columnIndex)); + Object.defineProperty(this, columnIndex, columnDescriptor); + } + columnDescriptor.get = null as any; + }); + rowParentDescriptor.value = null as any; + rowLengthDescriptor.value = null as any; } *[Symbol.iterator]() { - for (let i = -1, n = this.length; ++i < n;) { + for (let i = -1, n = this[kLength]; ++i < n;) { yield this[i]; } } + private _bindGetter(colIndex: number) { + return function (this: Row) { + const child = this[kParent].getChildAt(colIndex); + return child ? child.get(this[kRowIndex]) : null; + }; + } public get(key: K) { return (this as any)[key] as T[K]['TValue']; } + public bind(rowIndex: number) { + const bound = Object.create(this); + bound[kRowIndex] = rowIndex; + return bound as RowLike; + } public toJSON(): any { return DataType.isStruct(this.parent.type) ? [...this] : Object.getOwnPropertyNames(this).reduce((props: any, prop: string) => { @@ -57,56 +102,4 @@ export class Row implements Iterable { - readonly prototype: Row; - new(parent: MapVector | StructVector, rowIndex: number): T & Row -} - - -/** @ignore */ -export class RowProxyGenerator { - /** @nocollapse */ - public static new(schemaOrFields: T | Field[], fieldsAreEnumerable = false): RowProxyGenerator { - let schema: T, fields: Field[]; - if (Array.isArray(schemaOrFields)) { - fields = schemaOrFields; - } else { - schema = schemaOrFields; - fieldsAreEnumerable = true; - fields = Object.keys(schema).map((x) => new Field(x, schema[x])); - } - return new RowProxyGenerator(fields, fieldsAreEnumerable); - } - - private RowProxy: RowConstructor; - - private constructor(fields: Field[], fieldsAreEnumerable: boolean) { - class BoundRow extends Row {} - - const proto = BoundRow.prototype; - - lengthDescriptor.value = fields.length; - Object.defineProperty(proto, 'length', lengthDescriptor); - fields.forEach((field, columnIndex) => { - columnDescriptor.get = function() { - const child = (this as any as Row).parent.getChildAt(columnIndex); - return child ? child.get((this as any as Row).rowIndex) : null; - } - // set configurable to true to ensure Object.defineProperty - // doesn't throw in the case of duplicate column names - columnDescriptor.configurable = true; - columnDescriptor.enumerable = fieldsAreEnumerable; - Object.defineProperty(proto, field.name, columnDescriptor); - columnDescriptor.configurable = false; - columnDescriptor.enumerable = !fieldsAreEnumerable; - Object.defineProperty(proto, columnIndex, columnDescriptor); - columnDescriptor.get = null as any; - }); - - this.RowProxy = (BoundRow as any) - } - public get(key: K) { return (this as any)[key] as T[K]['TValue']; } - public bind | StructVector>(parent: TParent, rowIndex: number) { - return new this.RowProxy(parent, rowIndex); - } -} +Object.defineProperty(Row.prototype, kRowIndex, { writable: true, enumerable: false, configurable: false, value: -1 }); diff --git a/js/src/vector/struct.ts b/js/src/vector/struct.ts index 1d4b73f7c0e..adb39816e9b 100644 --- a/js/src/vector/struct.ts +++ b/js/src/vector/struct.ts @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { RowProxyGenerator } from './row'; +import { Row } from './row'; import { Vector } from '../vector'; import { BaseVector } from './base'; import { DataType, Map_, Struct } from '../type'; @@ -25,8 +25,8 @@ export class StructVector extends B return Vector.new(this.data.clone(new Map_(this.type.children, keysSorted))); } // @ts-ignore - private _rowProxy: RowProxyGenerator; - public get rowProxy(): RowProxyGenerator { - return this._rowProxy || (this._rowProxy = RowProxyGenerator.new(this.type.children || [], false)); + private _rowProxy: Row; + public get rowProxy(): Row { + return this._rowProxy || (this._rowProxy = Row.new(this, this.type.children || [], false)); } } diff --git a/js/src/visitor/get.ts b/js/src/visitor/get.ts index 67909eacfce..4aa134b8fb9 100644 --- a/js/src/visitor/get.ts +++ b/js/src/visitor/get.ts @@ -211,7 +211,7 @@ const getNested = < S extends { [key: string]: DataType }, V extends Vector> | Vector> >(vector: V, index: number): V['TValue'] => { - return vector.rowProxy.bind(vector, index); + return vector.rowProxy.bind(index); }; /* istanbul ignore next */