diff --git a/js/perf/index.js b/js/perf/index.js index 0e9c2bd689a..2129b9d983d 100644 --- a/js/perf/index.js +++ b/js/perf/index.js @@ -44,10 +44,12 @@ for (let { name, buffers } of require('./table_config')) { for (let {name, buffers, countBys, counts} of require('./table_config')) { const table = Table.from(buffers); + const tableIterateSuiteName = `Table Iterate "${name}"`; const dfCountBySuiteName = `DataFrame Count By "${name}"`; const dfFilterCountSuiteName = `DataFrame Filter-Scan Count "${name}"`; const dfDirectCountSuiteName = `DataFrame Direct Count "${name}"`; + suites.push(createTestSuite(tableIterateSuiteName, createTableIterateTest(table))); suites.push(...countBys.map((countBy) => createTestSuite(dfCountBySuiteName, createDataFrameCountByTest(table, countBy)))); suites.push(...counts.map(({ col, test, value }) => createTestSuite(dfFilterCountSuiteName, createDataFrameFilterCountTest(table, col, test, value)))); suites.push(...counts.map(({ col, test, value }) => createTestSuite(dfDirectCountSuiteName, createDataFrameDirectCountTest(table, col, test, value)))); @@ -135,6 +137,15 @@ function createGetByIndexTest(vector, name) { }; } +function createTableIterateTest(table) { + let value; + return { + async: true, + name: `length: ${table.length}\n`, + fn() { for (value of table) {} } + }; +} + function createDataFrameDirectCountTest(table, column, test, value) { let sum, colidx = table.schema.fields.findIndex((c)=>c.name === column); diff --git a/js/src/util/vector.ts b/js/src/util/vector.ts index 92f348dd3f0..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,60 +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 createArrayLikeComparator(search); + } + // Compare Rows + if (search instanceof Row) { + return createRowComparator(search); + } + // Compare Vectors + if (search instanceof Vector) { + 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 and Vectors - if ((search instanceof Row) || (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; } + 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 27c51a62270..54956d7ba41 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 { Row } from './row'; +import { RowProxyGenerator } 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: Row; - public get rowProxy(): Row { - return this._rowProxy || (this._rowProxy = Row.new(this.type.children || [], true)); + private _rowProxy: RowProxyGenerator; + public get rowProxy(): RowProxyGenerator { + return this._rowProxy || (this._rowProxy = RowProxyGenerator.new(this, this.type.children || [], true)); } } diff --git a/js/src/vector/row.ts b/js/src/vector/row.ts index 62fb3b608c2..54dcd7f61ee 100644 --- a/js/src/vector/row.ts +++ b/js/src/vector/row.ts @@ -17,84 +17,97 @@ import { Field } from '../schema'; import { MapVector } from '../vector/map'; -import { DataType, RowLike } from '../type'; +import { DataType } 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 */ const rowIndexDescriptor = { writable: false, enumerable: false, configurable: true, value: null as any }; +/** @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: false, value: -1 }; /** @ignore */ const rowParentDescriptor = { writable: false, enumerable: false, configurable: false, value: null as any }; -/** @ignore */ const row = { parent: rowParentDescriptor, rowIndex: rowIndexDescriptor }; -/** @ignore */ export class Row implements Iterable { [key: string]: T[keyof T]['TValue']; - /** @nocollapse */ - public static new(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(fields, fieldsAreEnumerable) as RowLike & Row; - } // @ts-ignore - private parent: TParent; + public [kParent]: MapVector | StructVector; // @ts-ignore - private rowIndex: number; + public [kRowIndex]: number; // @ts-ignore - public readonly length: number; - private constructor(fields: Field[], fieldsAreEnumerable: boolean) { - lengthDescriptor.value = fields.length; - Object.defineProperty(this, 'length', lengthDescriptor); - fields.forEach((field, columnIndex) => { - columnDescriptor.get = this._bindGetter(columnIndex); - // 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(this, field.name, columnDescriptor); - columnDescriptor.configurable = false; - columnDescriptor.enumerable = !fieldsAreEnumerable; - Object.defineProperty(this, columnIndex, columnDescriptor); - columnDescriptor.get = null as any; - }); - } - *[Symbol.iterator](this: RowLike) { - for (let i = -1, n = this.length; ++i < n;) { + public readonly [kLength]: number; + *[Symbol.iterator]() { + for (let i = -1, n = this[kLength]; ++i < n;) { yield this[i]; } } - private _bindGetter(colIndex: number) { - return function (this: Row) { - let child = this.parent.getChildAt(colIndex); - return child ? child.get(this.rowIndex) : null; - }; - } public get(key: K) { return (this as any)[key] as T[K]['TValue']; } - public bind | StructVector>(parent: TParent, rowIndex: number) { - rowIndexDescriptor.value = rowIndex; - rowParentDescriptor.value = parent; - const bound = Object.create(this, row); - rowIndexDescriptor.value = null; - rowParentDescriptor.value = null; - return bound as RowLike; - } public toJSON(): any { - return DataType.isStruct(this.parent.type) ? [...this] : + return DataType.isStruct(this[kParent].type) ? [...this] : Object.getOwnPropertyNames(this).reduce((props: any, prop: string) => { return (props[prop] = (this as any)[prop]) && props || props; }, {}); } public toString() { - return DataType.isStruct(this.parent.type) ? + return DataType.isStruct(this[kParent].type) ? [...this].map((x) => valueToString(x)).join(', ') : Object.getOwnPropertyNames(this).reduce((props: any, prop: string) => { return (props[prop] = valueToString((this as any)[prop])) && props || props; }, {}); } } + +/** @ignore */ +export class RowProxyGenerator { + /** @nocollapse */ + public static new(parent: MapVector | StructVector, 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(parent, fields, fieldsAreEnumerable); + } + + private rowPrototype: Row; + + private constructor(parent: MapVector | StructVector, fields: Field[], fieldsAreEnumerable: boolean) { + const proto = Object.create(Row.prototype); + + rowParentDescriptor.value = parent; + rowLengthDescriptor.value = fields.length; + Object.defineProperty(proto, kParent, rowParentDescriptor); + Object.defineProperty(proto, kLength, rowLengthDescriptor); + fields.forEach((field, columnIndex) => { + if (!proto.hasOwnProperty(field.name)) { + columnDescriptor.enumerable = fieldsAreEnumerable; + columnDescriptor.get || (columnDescriptor.get = this._bindGetter(columnIndex)); + Object.defineProperty(proto, field.name, columnDescriptor); + } + if (!proto.hasOwnProperty(columnIndex)) { + columnDescriptor.enumerable = !fieldsAreEnumerable; + columnDescriptor.get || (columnDescriptor.get = this._bindGetter(columnIndex)); + Object.defineProperty(proto, columnIndex, columnDescriptor); + } + columnDescriptor.get = null as any; + }); + + this.rowPrototype = proto; + } + + private _bindGetter(columnIndex: number) { + return function(this: Row) { + const child = this[kParent].getChildAt(columnIndex); + return child ? child.get(this[kRowIndex]) : null; + }; + } + public bind(rowIndex: number) { + const bound = Object.create(this.rowPrototype); + bound[kRowIndex] = rowIndex; + return bound; + //return new this.RowProxy(rowIndex); + } +} diff --git a/js/src/vector/struct.ts b/js/src/vector/struct.ts index 4ad57ff5113..e1596d6e1df 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 { Row } from './row'; +import { RowProxyGenerator } 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: Row; - public get rowProxy(): Row { - return this._rowProxy || (this._rowProxy = Row.new(this.type.children || [], false)); + private _rowProxy: RowProxyGenerator; + public get rowProxy(): RowProxyGenerator { + return this._rowProxy || (this._rowProxy = RowProxyGenerator.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 */