Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions js/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export type IntBitWidth = 8 | 16 | 32 | 64;
export type IsSigned = { 'true': true; 'false': false };
/** @ignore */
export type RowLike<T extends { [key: string]: DataType; }> =
{ readonly length: number }
& ( Iterable<T[keyof T]['TValue']> )
( Iterable<T[keyof T]['TValue']> )
& { [P in keyof T]: T[P]['TValue'] }
& { get<K extends keyof T>(key: K): T[K]['TValue']; }
;
Expand Down
142 changes: 86 additions & 56 deletions js/src/util/vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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<any>) {
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<any>) {
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<any>) {
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;
};
}
8 changes: 4 additions & 4 deletions js/src/vector/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -25,8 +25,8 @@ export class MapVector<T extends { [key: string]: DataType } = any> extends Base
return Vector.new(this.data.clone(new Struct<T>(this.type.children)));
}
// @ts-ignore
private _rowProxy: RowProxyGenerator<T>;
public get rowProxy(): RowProxyGenerator<T> {
return this._rowProxy || (this._rowProxy = RowProxyGenerator.new<T>(this.type.children || [], true));
private _rowProxy: Row<T>;
public get rowProxy(): Row<T> {
return this._rowProxy || (this._rowProxy = Row.new<T>(this, this.type.children || [], true));
}
}
119 changes: 56 additions & 63 deletions js/src/vector/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends { [key: string]: DataType }> implements Iterable<T[keyof T]['TValue']> {
[key: string]: T[keyof T]['TValue'];
/** @nocollapse */
public static new<T extends { [key: string]: DataType }>(parent: MapVector<T> | StructVector<T>, schemaOrFields: T | Field[], fieldsAreEnumerable = false): RowLike<T> & Row<T> {
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<T>(parent, fields, fieldsAreEnumerable) as RowLike<T> & Row<T>;
}
// @ts-ignore
public parent: MapVector<T> | StructVector<T>;
private [kParent]: MapVector<T> | StructVector<T>;
// @ts-ignore
public rowIndex: number;
private [kLength]: number;
// @ts-ignore
public readonly length: number;
constructor(parent: MapVector<T> | StructVector<T>, rowIndex: number) {
this.parent = parent;
this.rowIndex = rowIndex;
private [kRowIndex]: number;
private constructor(parent: MapVector<T> | StructVector<T>, 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<T>) {
const child = this[kParent].getChildAt(colIndex);
return child ? child.get(this[kRowIndex]) : null;
};
}
public get<K extends keyof T>(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<T>;
}
public toJSON(): any {
return DataType.isStruct(this.parent.type) ? [...this] :
Object.getOwnPropertyNames(this).reduce((props: any, prop: string) => {
Expand All @@ -57,56 +102,4 @@ export class Row<T extends { [key: string]: DataType }> implements Iterable<T[ke
}
}

interface RowConstructor<T extends { [key: string]: DataType }> {
readonly prototype: Row<T>;
new(parent: MapVector<T> | StructVector<T>, rowIndex: number): T & Row<T>
}


/** @ignore */
export class RowProxyGenerator<T extends { [key: string]: DataType }> {
/** @nocollapse */
public static new<T extends { [key: string]: DataType }>(schemaOrFields: T | Field[], fieldsAreEnumerable = false): RowProxyGenerator<T> {
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<T>(fields, fieldsAreEnumerable);
}

private RowProxy: RowConstructor<T>;

private constructor(fields: Field[], fieldsAreEnumerable: boolean) {
class BoundRow extends Row<T> {}

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<T>).parent.getChildAt(columnIndex);
return child ? child.get((this as any as Row<T>).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<K extends keyof T>(key: K) { return (this as any)[key] as T[K]['TValue']; }
public bind<TParent extends MapVector<T> | StructVector<T>>(parent: TParent, rowIndex: number) {
return new this.RowProxy(parent, rowIndex);
}
}
Object.defineProperty(Row.prototype, kRowIndex, { writable: true, enumerable: false, configurable: false, value: -1 });
8 changes: 4 additions & 4 deletions js/src/vector/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -25,8 +25,8 @@ export class StructVector<T extends { [key: string]: DataType } = any> extends B
return Vector.new(this.data.clone(new Map_<T>(this.type.children, keysSorted)));
}
// @ts-ignore
private _rowProxy: RowProxyGenerator<T>;
public get rowProxy(): RowProxyGenerator<T> {
return this._rowProxy || (this._rowProxy = RowProxyGenerator.new<T>(this.type.children || [], false));
private _rowProxy: Row<T>;
public get rowProxy(): Row<T> {
return this._rowProxy || (this._rowProxy = Row.new<T>(this, this.type.children || [], false));
}
}
2 changes: 1 addition & 1 deletion js/src/visitor/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const getNested = <
S extends { [key: string]: DataType },
V extends Vector<Map_<S>> | Vector<Struct<S>>
>(vector: V, index: number): V['TValue'] => {
return vector.rowProxy.bind(vector, index);
return vector.rowProxy.bind(index);
};

/* istanbul ignore next */
Expand Down