blob: b177b4fa76459cac98dd0186b071a89b6238a969 [file] [log] [blame]
// 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 { RecordBatch } from './recordbatch';
import { Vector, DictionaryVector } from './vector';
export type ValueFunc<T> = (idx: number, cols: RecordBatch) => T | null;
export type PredicateFunc = (idx: number, cols: RecordBatch) => boolean;
export abstract class Value<T> {
eq(other: Value<T> | T): Predicate {
if (!(other instanceof Value)) { other = new Literal(other); }
return new Equals(this, other);
}
lteq(other: Value<T> | T): Predicate {
if (!(other instanceof Value)) { other = new Literal(other); }
return new LTeq(this, other);
}
gteq(other: Value<T> | T): Predicate {
if (!(other instanceof Value)) { other = new Literal(other); }
return new GTeq(this, other);
}
}
export class Literal<T= any> extends Value<T> {
constructor(public v: T) { super(); }
}
export class Col<T= any> extends Value<T> {
// @ts-ignore
public vector: Vector;
// @ts-ignore
public colidx: number;
constructor(public name: string) { super(); }
bind(batch: RecordBatch) {
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}"`); }
}
this.vector = batch.getChildAt(this.colidx)!;
return this.vector.get.bind(this.vector);
}
}
export abstract class Predicate {
abstract bind(batch: RecordBatch): PredicateFunc;
and(expr: Predicate): Predicate { return new And(this, expr); }
or(expr: Predicate): Predicate { return new Or(this, expr); }
ands(): Predicate[] { return [this]; }
}
export abstract class ComparisonPredicate<T= any> extends Predicate {
constructor(public readonly left: Value<T>, public readonly right: Value<T>) {
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;
}
export abstract class CombinationPredicate extends Predicate {
constructor(public readonly left: Predicate, public readonly right: Predicate) {
super();
}
}
export class And extends CombinationPredicate {
bind(batch: RecordBatch) {
const left = this.left.bind(batch);
const right = this.right.bind(batch);
return (idx: number, batch: RecordBatch) => left(idx, batch) && right(idx, batch);
}
ands(): Predicate[] { return this.left.ands().concat(this.right.ands()); }
}
export class Or extends CombinationPredicate {
bind(batch: RecordBatch) {
const left = this.left.bind(batch);
const right = this.right.bind(batch);
return (idx: number, batch: RecordBatch) => left(idx, batch) || right(idx, batch);
}
}
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 comutative
return this._bindColLit(batch, col, lit);
}
}
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);
}
}
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);
}
}
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<any> { return new Literal(v); }
export function col(n: string): Col<any> { return new Col(n); }
export function custom(next: PredicateFunc, bind: (batch: RecordBatch) => void) {
return new CustomPredicate(next, bind);
}