961 lines
26 KiB
JavaScript
961 lines
26 KiB
JavaScript
/*
|
|
Copyright (c) 2013, Oracle and/or its affiliates. All rights
|
|
reserved.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; version 2 of
|
|
the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
02110-1301 USA
|
|
*/
|
|
|
|
"use strict";
|
|
var assert = require("assert");
|
|
var util = require("util");
|
|
var BitMask = require("./BitMask.js");
|
|
var udebug = unified_debug.getLogger("IndexBounds.js");
|
|
|
|
|
|
/* Evaluation of Column Bounds from a Query
|
|
|
|
An expression like "age < 30" defines a boundary on the value of age.
|
|
We can express that boundary as an interval (-Infinity, 30).
|
|
The expression "state = 'SC'" does not define a boundary on age,
|
|
but nonetheless can be evaluated (with regard to age) as the interval
|
|
(-Infinity, +Infinity). Knowing this, we can evaluate a query tree with
|
|
respect to "age" and generate an interval from every comparator node.
|
|
|
|
If expressions are represented as intervals, then logical operations on
|
|
them can be translated into operations on the intervals: conjunction as
|
|
intersection, disjunction as union, and negation as a complement.
|
|
If comparator A returns interval Ia, and comparator B returns interval Ib,
|
|
then the conjuction (A AND B) evaluates to the intersection of Ia and Ib.
|
|
The disjunction (A OR B) evaluates to the union of Ia and Ib. If Ia and Ib
|
|
do not intersect, this union is the set {Ia, Ib}; if not, it is the one
|
|
segment that spans from the least lower bound to the greater upper bound.
|
|
|
|
The NOT operator evaluates to the complement of an interval. If Ia is
|
|
a finite inclusive interval [0, 30], then its complement is the pair of
|
|
exclusive intervals (-Infinity, 0) and (30, +Infinity).
|
|
|
|
Of course -Infinity and +Infinity don't really represent infinite values,
|
|
only the lowest and highest values of the index. NULLs sort low, so
|
|
JavaScript null is equivalent to -Infinity.
|
|
|
|
The end result is that a predicate tree, evaluated with regard to an
|
|
index column, is transformed into the set of ranges (segments) of the index
|
|
which must be scanned to evaluate the predicate.
|
|
|
|
Calculating the intersections and unions of intervals requires us to
|
|
compare two values for an indexed column. Implementing this would be
|
|
easy if all indexed columns were numbers, but in fact we also have
|
|
at least strings and dates to deal with. JavaScript has a funny answer
|
|
when you compare strings and dates to Infinity:
|
|
("a" < Infinity) == false
|
|
(Date() < Infinity) == false
|
|
isFinite() also cannot be used with strings and dates.
|
|
|
|
In some cases, JavaScript simply cannot compare two values at all:
|
|
for instance, it cannot compare two strings according to particular
|
|
MySQL collation rules. So, we introduce the concept of an EncodedValue,
|
|
for values that JavaScript cannot compare and whose comparison is delegated
|
|
elsewhere.
|
|
*/
|
|
|
|
/* Utility functions
|
|
*/
|
|
function blah() {
|
|
console.log("BLAH");
|
|
console.log.apply(null, arguments);
|
|
console.trace();
|
|
process.exit();
|
|
}
|
|
|
|
//////// EncodedValue /////////////////
|
|
|
|
/*
|
|
EncodedValue.compare(otherValue) should return -1, 0, or +1 according to
|
|
whether the stored value compares less, equal, or greater than otherValue.
|
|
We don't implement compare() here.
|
|
*/
|
|
function EncodedValue() {
|
|
}
|
|
|
|
EncodedValue.prototype = {
|
|
isNonSimple : true,
|
|
isEncodedValue : true,
|
|
inspect : function() { return "<ENCODED>"; },
|
|
compare : function() { blah("EncodedValue must implement compare()"); }
|
|
};
|
|
|
|
|
|
/* compareValues() takes two values which are either JS Values or EncodedValues.
|
|
Returns -1, 0, or +1 as the first is less, equal to, or greater than the
|
|
second.
|
|
*/
|
|
function compareValues(a, b) {
|
|
var cmp;
|
|
|
|
/* First compare to infinity */
|
|
if(a === -Infinity || a === null || b === Infinity) {
|
|
return -1;
|
|
}
|
|
|
|
if(a === Infinity || b === -Infinity || b === null) {
|
|
return 1;
|
|
}
|
|
|
|
if(typeof a === 'object' && a.isEncodedValue) {
|
|
cmp = a.compare(b);
|
|
}
|
|
else {
|
|
/* Compare JavaScript values */
|
|
if(a == b) cmp = 0;
|
|
else if (a < b) cmp = -1;
|
|
else cmp = 1;
|
|
}
|
|
|
|
return cmp;
|
|
}
|
|
|
|
|
|
//////// IndexValue /////////////////
|
|
|
|
/* IndexValue represents the multi-part value of an index.
|
|
It is implemented as an array where each member is either a JS value or an
|
|
EncodedValue. Like other values, it can be used in endpoints and
|
|
segments.
|
|
*/
|
|
|
|
function IndexValue() {
|
|
this.size = 0;
|
|
this.parts = [];
|
|
}
|
|
|
|
IndexValue.prototype = {
|
|
isNonSimple : true,
|
|
isIndexValue : true
|
|
};
|
|
|
|
IndexValue.prototype.pushColumnValue = function(v) {
|
|
this.size++;
|
|
this.parts.push(v);
|
|
};
|
|
|
|
IndexValue.prototype.copy = function() {
|
|
var that = new IndexValue();
|
|
that.size = this.size;
|
|
that.parts = this.parts.slice();
|
|
return that;
|
|
};
|
|
|
|
IndexValue.prototype.compare = function(that) {
|
|
var n, len, cmp, v1, v2;
|
|
|
|
if(! that.isIndexValue) blah(this, that);
|
|
assert(that.isIndexValue);
|
|
len = this.size < that.size ? this.size : that.size;
|
|
|
|
for(n = 0 ; n < len ; n++) {
|
|
v1 = this.parts[n];
|
|
v2 = that.parts[n];
|
|
cmp = compareValues(v1, v2);
|
|
if(cmp != 0) {
|
|
return cmp;
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
IndexValue.prototype.isFinite = function() {
|
|
var v;
|
|
if(this.size == 0) return false;
|
|
v = this.parts[this.size - 1];
|
|
return (typeof v === 'number') ? isFinite(v) : true;
|
|
};
|
|
|
|
IndexValue.prototype.inspect = function() {
|
|
var i, result = "Index:";
|
|
for(i = 0 ; i < this.size ; i++) {
|
|
if(i) result += ",";
|
|
result += this.parts[i];
|
|
}
|
|
return result;
|
|
};
|
|
|
|
|
|
//////// Endpoint /////////////////
|
|
|
|
/* An Endpoint holds a value (plain JavaScript value, EncodedValue,
|
|
or IndexValue), and, as the endpoint of a range of values, is either
|
|
inclusive of the point value itself or not. "inclusive" defaults to true.
|
|
*/
|
|
function Endpoint(value, inclusive) {
|
|
this.value = value;
|
|
this.inclusive = (inclusive === false) ? false : true;
|
|
if(value.isIndexValue) {
|
|
this.isFinite = value.isFinite();
|
|
}
|
|
else {
|
|
this.isFinite = (typeof value === 'number') ? isFinite(value) : true;
|
|
}
|
|
}
|
|
|
|
Endpoint.prototype.isEndpoint = true;
|
|
|
|
Endpoint.prototype.copy = function() {
|
|
/* used only when value is an IndexValue */
|
|
return new Endpoint(this.value.copy(), this.inclusive);
|
|
};
|
|
|
|
Endpoint.prototype.asString = function(close) {
|
|
var s = "";
|
|
var value = util.inspect(this.value);
|
|
if(close) {
|
|
s += value;
|
|
s += (this.inclusive ? "]" : ")");
|
|
}
|
|
else {
|
|
s += (this.inclusive ? "[" : "(");
|
|
s += value;
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/* Compare two points.
|
|
Returns:
|
|
-1 if this point's value is less than the other's
|
|
+1 if this point's value is greater than the other's
|
|
true if the values are equal and both points are inclusive
|
|
false if the values are equal and either point is exclusive
|
|
*/
|
|
Endpoint.prototype.compare = function(that) {
|
|
var cmp;
|
|
assert(that.isEndpoint);
|
|
|
|
if(this.value.isNonSimple) {
|
|
cmp = this.value.compare(that.value);
|
|
}
|
|
else {
|
|
cmp = compareValues(this.value, that.value);
|
|
}
|
|
|
|
return (cmp === 0) ? (this.inclusive && that.inclusive) : cmp;
|
|
};
|
|
|
|
/* complement flips Endpoint between inclusive and exclusive.
|
|
Used in complementing number lines.
|
|
e.g. the complement of [4,10] is [-Inf, 4) and (10, Inf]
|
|
*/
|
|
Endpoint.prototype.complement = function() {
|
|
if(this.isFinite) {
|
|
this.inclusive = ! this.inclusive;
|
|
}
|
|
};
|
|
|
|
/* push() is used only for endpoints that contain IndexValues
|
|
*/
|
|
Endpoint.prototype.push = function(e) {
|
|
this.value.pushColumnValue(e.value);
|
|
this.isFinite = e.isFinite;
|
|
this.inclusive = e.inclusive;
|
|
};
|
|
|
|
/* Create (non-inclusive) endpoints for negative and positive infinity.
|
|
*/
|
|
var negInf = new Endpoint(-Infinity);
|
|
var posInf = new Endpoint(Infinity);
|
|
|
|
|
|
/* Functions to compare two endpoints and return one
|
|
*/
|
|
function pointGreater(e1, e2, preferInclusive) {
|
|
switch(e1.compare(e2)) {
|
|
case -1:
|
|
return e2;
|
|
case +1:
|
|
return e1;
|
|
case true:
|
|
return e1;
|
|
case false:
|
|
return (e1.inclusive === preferInclusive) ? e1 : e2;
|
|
}
|
|
}
|
|
|
|
function pointLesser(e1, e2, preferInclusive) {
|
|
switch(e1.compare(e2)) {
|
|
case -1:
|
|
return e1;
|
|
case +1:
|
|
return e2;
|
|
case true:
|
|
return e1;
|
|
case false:
|
|
return (e1.inclusive === preferInclusive) ? e1 : e2;
|
|
}
|
|
}
|
|
|
|
|
|
//////// Segment /////////////////
|
|
|
|
/* A Segment is created from two endpoints on the line.
|
|
*/
|
|
function Segment(point1, point2) {
|
|
assert(point1.isEndpoint && point2.isEndpoint);
|
|
|
|
if(point1.compare(point2) === -1) {
|
|
this.low = point1;
|
|
this.high = point2;
|
|
}
|
|
else {
|
|
this.low = point2;
|
|
this.high = point1;
|
|
}
|
|
}
|
|
|
|
Segment.prototype.isSegment = true;
|
|
|
|
Segment.prototype.inspect = function() {
|
|
var s = "";
|
|
s += this.low.asString(0) + " -- " + this.high.asString(1);
|
|
return s;
|
|
};
|
|
|
|
Segment.prototype.copy = function() {
|
|
return new Segment(this.low.copy(), this.high.copy());
|
|
};
|
|
|
|
Segment.prototype.contains = function(point) {
|
|
assert(point.isEndpoint);
|
|
|
|
var hi = this.high.compare(point);
|
|
var lo = this.low.compare(point);
|
|
return( ((lo === true) || (lo === -1))
|
|
&& ((hi === true) || (hi === 1)));
|
|
};
|
|
|
|
Segment.prototype.intersects = function(that) {
|
|
assert(that.isSegment);
|
|
|
|
return ( this.contains(that.low) || this.contains(that.high)
|
|
|| that.contains(this.low) || that.contains(this.high));
|
|
};
|
|
|
|
/* compare() returns 0 if segments intersect; otherwise like Endpoint.compare()
|
|
*/
|
|
Segment.prototype.compare = function(that) {
|
|
var r = 0;
|
|
assert(that.isSegment);
|
|
if(! this.intersects(that)) {
|
|
r = this.low.compare(that.low);
|
|
}
|
|
return r;
|
|
};
|
|
|
|
/* intersection: greatest lower bound & least upper bound
|
|
*/
|
|
Segment.prototype.intersection = function(that) {
|
|
var lp, hp, s;
|
|
s = null;
|
|
assert(that.isSegment);
|
|
if(this.intersects(that)) {
|
|
lp = pointGreater(this.low, that.low, false);
|
|
hp = pointLesser(this.high, that.high, false);
|
|
s = new Segment(lp, hp);
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/* span of intersecting segments: least lower bound & greatest upper bound
|
|
*/
|
|
Segment.prototype.span = function(that) {
|
|
var s = null;
|
|
var lp, hp;
|
|
if(this.intersects(that)) {
|
|
lp = pointLesser(this.low, that.low, true);
|
|
hp = pointGreater(this.high, that.high, true);
|
|
s = new Segment(lp, hp);
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/* "Iterate" over the one segment of a Segment,
|
|
(for compatibility with NumberLine.getIterator()
|
|
*/
|
|
function SegmentIterator(segment) {
|
|
this.item = segment;
|
|
}
|
|
|
|
SegmentIterator.prototype.next = function() {
|
|
var s = this.item;
|
|
this.item = null;
|
|
return s;
|
|
};
|
|
|
|
Segment.prototype.getIterator = function() {
|
|
return new SegmentIterator(this);
|
|
};
|
|
|
|
/* Create a segment between two points (inclusively)
|
|
*/
|
|
function createSegmentBetween(a, b) {
|
|
var p1 = new Endpoint(a);
|
|
var p2 = new Endpoint(b);
|
|
return new Segment(p1, p2);
|
|
}
|
|
|
|
/* Create a segment for a comparison expression */
|
|
function createSegmentForComparator(operator, value) {
|
|
var segment;
|
|
switch(operator) { // operation codes are from api/Query.js
|
|
case 0: // LE
|
|
return new Segment(negInf, new Endpoint(value, true));
|
|
case 1: // LT
|
|
return new Segment(negInf, new Endpoint(value, false));
|
|
case 2: // GE
|
|
return new Segment(new Endpoint(value, true), posInf);
|
|
case 3: // GT
|
|
return new Segment(new Endpoint(value, false), posInf);
|
|
case 4: // EQ
|
|
return new Segment(new Endpoint(value), new Endpoint(value));
|
|
case 5: // NE
|
|
return new Segment(new Endpoint(value), new Endpoint(value)).complement();
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/* A segment from -Inf to +Inf
|
|
*/
|
|
var boundingSegment = new Segment(negInf, posInf);
|
|
|
|
|
|
//////// NumberLine /////////////////
|
|
|
|
/* A number line represents an ordered set of line segments
|
|
on a key space stretching from -Infinity to +Infinity
|
|
|
|
The line is stored as an ordered list of transition points.
|
|
|
|
The segments on the line are from P0 to P1, P2 to P3, etc.
|
|
|
|
The constructor "new NumberLine()" returns an empty NumberLine
|
|
*/
|
|
|
|
function NumberLine() {
|
|
this.transitions = [];
|
|
}
|
|
|
|
NumberLine.prototype.isNumberLine = true;
|
|
|
|
NumberLine.prototype.inspect = function() {
|
|
var it, str, segment;
|
|
it = this.getIterator();
|
|
str = "{ ";
|
|
while((segment = it.next()) !== null) {
|
|
if(it.n > 2) str += ",";
|
|
str += segment.inspect();
|
|
}
|
|
str += " }";
|
|
return str;
|
|
};
|
|
|
|
NumberLine.prototype.isEmpty = function() {
|
|
return (this.transitions.length == 0);
|
|
};
|
|
|
|
NumberLine.prototype.setAll = function() {
|
|
this.transitions = [ negInf, posInf ];
|
|
return this;
|
|
};
|
|
|
|
NumberLine.prototype.setEqualTo = function(that) {
|
|
this.transitions = that.transitions;
|
|
};
|
|
|
|
NumberLine.prototype.upperBound = function() {
|
|
if(this.isEmpty()) return negInf;
|
|
return this.transitions[this.transitions.length - 1];
|
|
};
|
|
|
|
NumberLine.prototype.lowerBound = function() {
|
|
if(this.isEmpty()) return posInf;
|
|
return this.transitions[0];
|
|
};
|
|
|
|
/* A NumberLineIterator can iterate over the segments of a NumberLine
|
|
*/
|
|
function NumberLineIterator(numberLine) {
|
|
this.line = numberLine;
|
|
this.list = numberLine.transitions;
|
|
this.n = 0;
|
|
}
|
|
|
|
NumberLineIterator.prototype.next = function() {
|
|
var s = null;
|
|
if(this.n < this.list.length) {
|
|
s = new Segment(this.list[this.n], this.list[this.n+1]);
|
|
this.n += 2;
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/* An iterator's splice point is the index into a NumberLine's transition list
|
|
just prior to the most recently read segment.
|
|
Returns null if the iterator has not yet been read.
|
|
*/
|
|
NumberLineIterator.prototype.getSplicePoint = function() {
|
|
var idx = null;
|
|
if(this.n >= 2) {
|
|
idx = this.n - 2;
|
|
}
|
|
return idx;
|
|
};
|
|
|
|
NumberLine.prototype.getIterator = function() {
|
|
return new NumberLineIterator(this);
|
|
};
|
|
|
|
|
|
/* Complement of a number line (Negation of an expression)
|
|
*/
|
|
NumberLine.prototype.complement = function() {
|
|
if(! this.lowerBound().isFinite) {
|
|
this.transitions.shift();
|
|
}
|
|
else {
|
|
this.transitions.unshift(negInf);
|
|
}
|
|
|
|
if(! this.upperBound().isFinite) {
|
|
this.transitions.pop();
|
|
}
|
|
else {
|
|
this.transitions.push(posInf);
|
|
}
|
|
|
|
assert(this.transitions.length % 2 == 0);
|
|
|
|
this.transitions.forEach(function(p) { p.complement(); });
|
|
|
|
return this;
|
|
};
|
|
|
|
/* Insert a segment into a number line.
|
|
Assume as a given that the segment does not intersect any existing one.
|
|
*/
|
|
NumberLine.prototype.insertSegment = function(segment) {
|
|
var i, s, sp;
|
|
/* Segment is above the current upper bound */
|
|
if(this.upperBound().compare(segment.low) < 0) {
|
|
this.transitions.push(segment.low);
|
|
this.transitions.push(segment.high);
|
|
}
|
|
else {
|
|
/* Sorted Insertion */
|
|
i = this.getIterator();
|
|
while((s = i.next()) !== null) {
|
|
if(s.compare(segment) == 1) {
|
|
sp = i.getSplicePoint();
|
|
this.transitions =
|
|
this.transitions.slice(0, sp) .
|
|
concat(segment.low) .
|
|
concat(segment.high) .
|
|
concat(this.transitions.slice(sp));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Mutable: sets line equal to the intersection of line with segment
|
|
*/
|
|
NumberLine.prototype.intersectWithSegment = function(segment) {
|
|
var it, line, s;
|
|
it = this.getIterator();
|
|
line = new NumberLine();
|
|
while((s = it.next()) !== null) {
|
|
if(s.intersects(segment)) {
|
|
line.insertSegment(s.intersection(segment));
|
|
}
|
|
}
|
|
this.setEqualTo(line);
|
|
};
|
|
|
|
/* Mutable: sets line equal to the intersection of line and that
|
|
*/
|
|
NumberLine.prototype.intersection = function(that) {
|
|
var it, s;
|
|
it = that.getIterator();
|
|
while((s = it.next()) !== null) {
|
|
this.intersectWithSegment(s);
|
|
}
|
|
};
|
|
|
|
/* UNION, "X < 4 OR X > 6"
|
|
Mutable.
|
|
*/
|
|
NumberLine.prototype.unionWithSegment = function(segment) {
|
|
var it, line, s, overlaps;
|
|
it = this.getIterator();
|
|
line = new NumberLine();
|
|
overlaps = [];
|
|
/* Sort our own segments into two groups:
|
|
those that overlap segment, and those that don't
|
|
*/
|
|
while((s = it.next()) !== null) {
|
|
if(s.intersects(segment)) {
|
|
overlaps.push(s);
|
|
}
|
|
else {
|
|
line.insertSegment(s);
|
|
}
|
|
}
|
|
s = segment;
|
|
if(overlaps.length) s = s.span(overlaps.shift()); /* leftmost */
|
|
if(overlaps.length) s = s.span(overlaps.pop()); /* rightmost */
|
|
line.insertSegment(s);
|
|
|
|
this.setEqualTo(line);
|
|
};
|
|
|
|
/* Mutable: changes this into Union (this, that)
|
|
*/
|
|
NumberLine.prototype.union = function(that) {
|
|
var it, s;
|
|
it = that.getIterator();
|
|
while((s = it.next()) !== null) {
|
|
this.unionWithSegment(s);
|
|
}
|
|
};
|
|
|
|
/* createNumberLineFromSegment()
|
|
*/
|
|
function createNumberLineFromSegment(segment) {
|
|
var line = new NumberLine();
|
|
line.transitions[0] = segment.low;
|
|
line.transitions[1] = segment.high;
|
|
return line;
|
|
}
|
|
|
|
/* Complement a Segment by turning it into a NumberLine
|
|
*/
|
|
Segment.prototype.complement = function() {
|
|
return createNumberLineFromSegment(this).complement();
|
|
};
|
|
|
|
|
|
/* Returns the columnBound already stored in the node,
|
|
or the boundingSegment.
|
|
*/
|
|
function evaluateNodeForColumn(node, columnNumber) {
|
|
var segment = node.columnBound[columnNumber];
|
|
if(typeof segment === 'undefined') {
|
|
segment = boundingSegment;
|
|
}
|
|
return segment;
|
|
}
|
|
|
|
/* Returns a NumberLine
|
|
*/
|
|
function intersectionForColumn(predicates, columnNumber) {
|
|
var i, line;
|
|
line = new NumberLine().setAll();
|
|
|
|
for(i = 0 ; i < predicates.length ; i++) {
|
|
line.intersection(evaluateNodeForColumn(predicates[i], columnNumber));
|
|
}
|
|
return line;
|
|
}
|
|
|
|
/* Returns a NumberLine
|
|
*/
|
|
function unionForColumn(predicates, columnNumber) {
|
|
var i, line;
|
|
line = new NumberLine();
|
|
|
|
for(i = 0 ; i < predicates.length ; i++) {
|
|
line.union(evaluateNodeForColumn(predicates[i], columnNumber));
|
|
}
|
|
return line;
|
|
}
|
|
|
|
/******** This is a map operationCode => function
|
|
*/
|
|
var queryNaryFunctions = [ null , intersectionForColumn , unionForColumn ] ;
|
|
|
|
|
|
/****************************************** ColumnBoundVisitor ************
|
|
*
|
|
* Given a set of actual parameter values, visit the query tree and store
|
|
* a segment for every comparator, BETWEEN, and IS NULL / NOT NULL node.
|
|
*
|
|
* For grouping nodes AND, OR, NOT, store the intersection, union, or complement
|
|
* over the nodes grouped, for every column referenced in that group.
|
|
*
|
|
*
|
|
*/
|
|
function ColumnBoundVisitor(params) {
|
|
this.params = params;
|
|
}
|
|
|
|
/* Store a segment at a node
|
|
*/
|
|
ColumnBoundVisitor.prototype.store = function(node, segment) {
|
|
node.columnBound = {};
|
|
node.columnBound[node.queryField.field.columnNumber] = segment;
|
|
};
|
|
|
|
/** AND/OR nodes */
|
|
ColumnBoundVisitor.prototype.visitQueryNaryPredicate = function(node) {
|
|
var i, c, unionOrIntersection, doColumns;
|
|
unionOrIntersection = queryNaryFunctions[node.operationCode];
|
|
|
|
for(i = 0 ; i < node.predicates.length ; i++) {
|
|
node.predicates[i].visit(this);
|
|
}
|
|
node.columnBound = {};
|
|
doColumns = node.usedColumnMask.toArray();
|
|
while(doColumns.length) {
|
|
c = doColumns.pop();
|
|
node.columnBound[c] = unionOrIntersection(node.predicates, c);
|
|
}
|
|
};
|
|
|
|
/** NOT node */
|
|
ColumnBoundVisitor.prototype.visitQueryUnaryPredicate = function(node) {
|
|
var c, doColumns;
|
|
node.predicates[0].visit(this);
|
|
doColumns = node.usedColumnMask.toArray();
|
|
node.columnBound = {};
|
|
while(doColumns.length) {
|
|
c = doColumns.pop();
|
|
node.columnBound[c] = node.predicates[0].columnBound[c].complement();
|
|
}
|
|
};
|
|
|
|
/** Handle nodes QueryEq, QueryNe, QueryLt, QueryLe, QueryGt, QueryGe */
|
|
ColumnBoundVisitor.prototype.visitQueryComparator = function(node) {
|
|
var segment = createSegmentForComparator(node.operationCode,
|
|
this.params[node.parameter.name]);
|
|
this.store(node, segment);
|
|
};
|
|
|
|
/** Handle node QueryBetween */
|
|
ColumnBoundVisitor.prototype.visitQueryBetweenOperator = function(node) {
|
|
var ep1, ep2, segment;
|
|
ep1 = this.params[node.parameter1.name];
|
|
ep2 = this.params[node.parameter2.name];
|
|
segment = createSegmentBetween(ep1, ep2);
|
|
this.store(node, segment);
|
|
};
|
|
|
|
/** Handle nodes QueryIsNull, QueryIsNotNull */
|
|
ColumnBoundVisitor.prototype.visitQueryUnaryOperator = function(node) {
|
|
var pt, segment;
|
|
if(node.operationCode == 7) { // NULL
|
|
pt = new Endpoint(null);
|
|
segment = new Segment(pt, pt);
|
|
}
|
|
else { // NOT NULL
|
|
assert(node.operationCode == 8);
|
|
pt = new Endpoint(null, false);
|
|
segment = new Segment(pt, posInf);
|
|
}
|
|
this.store(node, segment);
|
|
};
|
|
|
|
|
|
|
|
/****************************************** IndexBoundVisitor ************
|
|
*
|
|
* Visit a tree that has already been marked by a ColumnBoundVisitor.
|
|
* Construct a set of IndexBounds for a particular index.
|
|
*/
|
|
function IndexBoundVisitor(queryHandler, dbIndex) {
|
|
this.index = dbIndex;
|
|
this.ncol = this.index.columnNumbers.length;
|
|
this.mask = new BitMask(queryHandler.dbTableHandler.dbTable.columns.length);
|
|
if(this.ncol > 1) {
|
|
this.mask.set(this.index.columnNumbers[0]);
|
|
this.mask.set(this.index.columnNumbers[1]);
|
|
}
|
|
}
|
|
|
|
/* A consolidated index bound for a multi-column index is a set of
|
|
segments built from IndexValues. Each single-column value is
|
|
represented by a Consolidator that builds its part of the
|
|
conslidated index bound.
|
|
|
|
Take the partially completed bounds object that is passed in.
|
|
For each segment in this column's own bound, make a copy of
|
|
the partialBounds, and try to add the segment to it. If the
|
|
segment endpoint is exclusive or infinite, stop there; otherwise,
|
|
pass the partialBounds along to the next column.
|
|
*/
|
|
|
|
var initialIndexBounds =
|
|
new Segment(new Endpoint(new IndexValue()), new Endpoint(new IndexValue()));
|
|
|
|
IndexBoundVisitor.prototype.consolidate = function(node) {
|
|
var i, allBounds, columnBounds, thisColumn, nextColumn;
|
|
|
|
function Consolidator(columnBounds, nextColumnConsolidator) {
|
|
this.columnBounds = columnBounds; // bounds of this column
|
|
this.nextColumnConsolidator = nextColumnConsolidator;
|
|
}
|
|
|
|
Consolidator.prototype.consolidate = function(partialBounds, doLow, doHigh) {
|
|
var boundsIterator, segment, idxBounds, doNextLow, doNextHigh;
|
|
|
|
doNextLow = doLow;
|
|
doNextHigh = doHigh;
|
|
|
|
boundsIterator = this.columnBounds.getIterator();
|
|
segment = boundsIterator.next();
|
|
while(segment) {
|
|
idxBounds = partialBounds.copy();
|
|
|
|
if(doLow) {
|
|
idxBounds.low.push(segment.low);
|
|
doNextLow = segment.low.inclusive && segment.low.isFinite;
|
|
}
|
|
if(doHigh) {
|
|
idxBounds.high.push(segment.high);
|
|
doNextHigh = segment.high.inclusive && segment.high.isFinite;
|
|
}
|
|
|
|
if(this.nextColumnConsolidator && (doNextLow || doNextHigh)) {
|
|
this.nextColumnConsolidator.consolidate(idxBounds, doNextLow, doNextHigh);
|
|
}
|
|
else {
|
|
allBounds.insertSegment(idxBounds);
|
|
}
|
|
|
|
segment = boundsIterator.next();
|
|
}
|
|
};
|
|
|
|
if(! node.indexRange) {
|
|
allBounds = new NumberLine();
|
|
nextColumn = null;
|
|
|
|
for(i = this.ncol - 1; i >= 0 ; i--) {
|
|
columnBounds = evaluateNodeForColumn(node, this.index.columnNumbers[i]);
|
|
udebug.log("consolidate predicate:", node);
|
|
udebug.log("consolidate in:", i, columnBounds);
|
|
thisColumn = new Consolidator(columnBounds, nextColumn);
|
|
nextColumn = thisColumn;
|
|
}
|
|
/* nextColumn is now the first column */
|
|
nextColumn.consolidate(initialIndexBounds, true, true);
|
|
udebug.log("consolidate out:", allBounds);
|
|
node.indexRange = allBounds;
|
|
}
|
|
};
|
|
|
|
|
|
/** Handle nodes QueryAnd, QueryOr
|
|
OperationCode = 1 for AND, 2 for OR
|
|
*/
|
|
IndexBoundVisitor.prototype.visitQueryNaryPredicate = function(node) {
|
|
var i, indexRange;
|
|
var buildChildNodeIndexBounds = false;
|
|
|
|
if(node.usedColumnMask.and(this.mask).isEqualTo(this.mask)) {
|
|
for(i = 0 ; i < node.predicates.length ; i++) {
|
|
node.predicates[i].visit(this);
|
|
if(node.predicates[i].indexRange) { buildChildNodeIndexBounds = true; }
|
|
}
|
|
if(buildChildNodeIndexBounds) {
|
|
// If any child node is consolidated, consolidate all of them,
|
|
// then construct the union or intersection over the consolidated bounds.
|
|
indexRange = new NumberLine();
|
|
for(i = 0 ; i < node.predicates.length ; i++) {
|
|
this.consolidate(node.predicates[i]);
|
|
switch(node.operationCode) {
|
|
case 1:
|
|
indexRange.intersection(node.predicates[i].indexRange);
|
|
udebug.log("Intersection: ", indexRange);
|
|
break;
|
|
case 2:
|
|
indexRange.union(node.predicates[i].indexRange);
|
|
udebug.log("Union: ", indexRange);
|
|
break;
|
|
}
|
|
}
|
|
node.indexRange = indexRange;
|
|
} else {
|
|
this.consolidate(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Handle node QueryNot */
|
|
IndexBoundVisitor.prototype.visitQueryUnaryPredicate = function(node) {
|
|
if(node.usedColumnMask.and(this.mask).isEqualTo(this.mask)) {
|
|
node.predicates[0].visit(this);
|
|
if(node.predicates[0].indexRange) {
|
|
node.indexRange = node.predicates[0].indexRange.complement();
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Construct an exported IndexBound from a segment
|
|
*/
|
|
function IndexBound(segment) {
|
|
function IndexBoundEndpoint(endpoint) {
|
|
this.inclusive = endpoint.inclusive;
|
|
this.key = endpoint.value.parts;
|
|
}
|
|
this.low = new IndexBoundEndpoint(segment.low);
|
|
this.high = new IndexBoundEndpoint(segment.high);
|
|
}
|
|
|
|
/* getIndexBounds()
|
|
|
|
For each column in the index, evaluate the predicate for that column.
|
|
Then consolidate the column bounds into a set of bounds on the index.
|
|
|
|
@arg queryHandler: query
|
|
@arg dbIndex: IndexMetadata of index to evaluate
|
|
@arg params: params to substitute into query
|
|
|
|
Returns an array of IndexBounds
|
|
*/
|
|
function getIndexBounds(queryHandler, dbIndex, params) {
|
|
var indexVisitor, queryIndexRange, it, segment, bounds;
|
|
|
|
/* Evaluate the query tree using the actual parameters */
|
|
queryHandler.predicate.visit(new ColumnBoundVisitor(params));
|
|
|
|
/* Then analyze it for this particular index */
|
|
indexVisitor = new IndexBoundVisitor(queryHandler, dbIndex);
|
|
queryHandler.predicate.visit(indexVisitor);
|
|
|
|
/* Build a consolidated index bound for the top-level predicate */
|
|
indexVisitor.consolidate(queryHandler.predicate);
|
|
queryIndexRange = queryHandler.predicate.indexRange;
|
|
udebug.log("Index range for query:", queryIndexRange);
|
|
|
|
/* Transform NumberLine to array of IndexBound */
|
|
bounds = [];
|
|
it = queryIndexRange.getIterator();
|
|
while((segment = it.next()) !== null) {
|
|
bounds.push(new IndexBound(segment));
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
exports.getIndexBounds = getIndexBounds;
|
|
|