786 lines
27 KiB
JavaScript
786 lines
27 KiB
JavaScript
/*
|
|
Copyright (c) 2012, 2014, 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 util = require("util");
|
|
var BitMask = require(mynode.common.BitMask);
|
|
var udebug = unified_debug.getLogger("Query.js");
|
|
var userContext = require("./UserContext.js");
|
|
|
|
var keywords = ['param', 'where', 'field', 'execute'];
|
|
|
|
var QueryParameter;
|
|
var QueryHandler;
|
|
var QueryEq, QueryNe, QueryLe, QueryLt, QueryGe, QueryGt, QueryBetween, QueryIn, QueryIsNull, QueryIsNotNull;
|
|
var QueryNot, QueryAnd, QueryOr;
|
|
|
|
/** QueryDomainType function param */
|
|
var param = function(name) {
|
|
return new QueryParameter(this, name);
|
|
};
|
|
|
|
/** QueryDomainType function where */
|
|
var where = function(predicate) {
|
|
var mynode = this.mynode_query_domain_type;
|
|
mynode.predicate = predicate;
|
|
mynode.queryHandler = new QueryHandler(mynode.dbTableHandler, predicate);
|
|
mynode.queryType = mynode.queryHandler.queryType;
|
|
this.prototype = {};
|
|
return this;
|
|
};
|
|
|
|
/** QueryDomainType function execute */
|
|
var execute = function() {
|
|
var session = this.mynode_query_domain_type.session;
|
|
var context = new userContext.UserContext(arguments, 2, 2, session, session.sessionFactory);
|
|
// delegate to context's execute for execution
|
|
return context.executeQuery(this);
|
|
};
|
|
|
|
var queryDomainTypeFunctions = {};
|
|
queryDomainTypeFunctions.where = where;
|
|
queryDomainTypeFunctions.param = param;
|
|
queryDomainTypeFunctions.execute = execute;
|
|
|
|
/**
|
|
* QueryField represents a mapped field in a domain object. QueryField is used to build
|
|
* QueryPredicates by comparing the field to parameters.
|
|
* @param queryDomainType
|
|
* @param field
|
|
* @return
|
|
*/
|
|
var QueryField = function(queryDomainType, field) {
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryField<ctor>', field.fieldName);
|
|
// this.class = 'QueryField'; // useful for debugging
|
|
// this.fieldName = field.fieldName; // useful for debugging
|
|
this.queryDomainType = queryDomainType;
|
|
this.field = field;
|
|
};
|
|
|
|
QueryField.prototype.eq = function(queryParameter) {
|
|
return new QueryEq(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.le = function(queryParameter) {
|
|
return new QueryLe(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.ge = function(queryParameter) {
|
|
return new QueryGe(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.lt = function(queryParameter) {
|
|
return new QueryLt(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.gt = function(queryParameter) {
|
|
return new QueryGt(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.ne = function(queryParameter) {
|
|
return new QueryNe(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.between = function(queryParameter1, queryParameter2) {
|
|
return new QueryBetween(this, queryParameter1, queryParameter2);
|
|
};
|
|
|
|
// 'in' is a keyword so use alternate syntax
|
|
QueryField.prototype['in'] = function(queryParameter) {
|
|
return new QueryIn(this, queryParameter);
|
|
};
|
|
|
|
QueryField.prototype.isNull = function() {
|
|
return new QueryIsNull(this);
|
|
};
|
|
|
|
QueryField.prototype.isNotNull = function() {
|
|
return new QueryIsNotNull(this);
|
|
};
|
|
|
|
QueryField.prototype.inspect = function() {
|
|
return this.field.fieldName;
|
|
};
|
|
|
|
/** Query Domain Type represents a domain object that can be used to create and execute queries.
|
|
* It encapsulates the dbTableHandler (obtained from the domain object or table name),
|
|
* the session (required to execute the query), and the filter which limits the result.
|
|
* @param session the user Session
|
|
* @param dbTableHandler the dbTableHandler
|
|
* @param domainObject true if the query results are domain objects
|
|
*/
|
|
var QueryDomainType = function(session, dbTableHandler, domainObject) {
|
|
udebug.log('QueryDomainType<ctor>', dbTableHandler.dbTable.name);
|
|
// avoid most name conflicts: put all implementation artifacts into the property mynode_query_domain_type
|
|
this.mynode_query_domain_type = {};
|
|
this.field = {};
|
|
var mynode = this.mynode_query_domain_type;
|
|
mynode.session = session;
|
|
mynode.dbTableHandler = dbTableHandler;
|
|
mynode.domainObject = domainObject;
|
|
var queryDomainType = this;
|
|
// initialize the functions (may be overridden below if a field has the name of a keyword)
|
|
queryDomainType.where = where;
|
|
queryDomainType.param = param;
|
|
queryDomainType.execute = execute;
|
|
|
|
var fieldName, queryField;
|
|
// add a property for each field in the table mapping
|
|
mynode.dbTableHandler.fieldNumberToFieldMap.forEach(function(field) {
|
|
fieldName = field.fieldName;
|
|
queryField = new QueryField(queryDomainType, field);
|
|
if (keywords.indexOf(fieldName) === -1) {
|
|
// field name is not a keyword
|
|
queryDomainType[fieldName] = queryField;
|
|
} else {
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryDomainType<ctor> field', fieldName, 'is a keyword.');
|
|
// field name is a keyword
|
|
// allow e.g. qdt.where.id
|
|
if (fieldName !== 'field') {
|
|
// if field is a reserved word but not a function, skip setting the function
|
|
queryDomainType[fieldName] = queryDomainTypeFunctions[fieldName];
|
|
queryDomainType[fieldName].eq = QueryField.prototype.eq;
|
|
queryDomainType[fieldName].field = queryField.field;
|
|
}
|
|
// allow e.g. qdt.field.where
|
|
queryDomainType.field[fieldName] = queryField;
|
|
}
|
|
});
|
|
};
|
|
|
|
QueryDomainType.prototype.inspect = function() {
|
|
var mynode = this.mynode_query_domain_type;
|
|
return "[[API Query on table: " + mynode.dbTableHandler.dbTable.name +
|
|
", type: " + mynode.queryType + ", predicate: " +
|
|
util.inspect(mynode.predicate) + "]]\n";
|
|
};
|
|
|
|
QueryDomainType.prototype.not = function(queryPredicate) {
|
|
return new QueryNot(queryPredicate);
|
|
};
|
|
|
|
/**
|
|
* QueryParameter represents a named parameter for a query. The QueryParameter marker is used
|
|
* as the comparand for QueryField.
|
|
* @param queryDomainType
|
|
* @param name
|
|
* @return
|
|
*/
|
|
QueryParameter = function(queryDomainType, name) {
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryParameter<ctor>', name);
|
|
this.queryDomainType = queryDomainType;
|
|
this.name = name;
|
|
};
|
|
|
|
QueryParameter.prototype.inspect = function() {
|
|
return '?' + this.name;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* SQL VISITOR
|
|
*****************************************************************************/
|
|
var SQLVisitor = function(rootPredicateNode) {
|
|
this.rootPredicateNode = rootPredicateNode;
|
|
rootPredicateNode.sql = {};
|
|
rootPredicateNode.sql.formalParameters = [];
|
|
rootPredicateNode.sql.sqlText = 'initialized';
|
|
this.parameterIndex = 0;
|
|
};
|
|
|
|
/** Handle nodes QueryEq, QueryNe, QueryLt, QueryLe, QueryGt, QueryGe */
|
|
SQLVisitor.prototype.visitQueryComparator = function(node) {
|
|
// set up the sql text in the node
|
|
var columnName = node.queryField.field.fieldName;
|
|
node.sql.sqlText = columnName + node.comparator + '?';
|
|
// assign ordered list of parameters to the top node
|
|
this.rootPredicateNode.sql.formalParameters[this.parameterIndex++] = node.parameter;
|
|
};
|
|
|
|
/** Handle nodes QueryAnd, QueryOr */
|
|
SQLVisitor.prototype.visitQueryNaryPredicate = function(node) {
|
|
var i;
|
|
// all n-ary predicates have at least two
|
|
node.predicates[0].visit(this); // sets up the sql.sqlText in the node
|
|
node.sql.sqlText = '(' + node.predicates[0].sql.sqlText + ')';
|
|
for (i = 1; i < node.predicates.length; ++i) {
|
|
node.sql.sqlText += node.operator;
|
|
node.predicates[i].visit(this);
|
|
node.sql.sqlText += '(' + node.predicates[i].sql.sqlText + ')';
|
|
}
|
|
};
|
|
|
|
/** Handle nodes QueryNot */
|
|
SQLVisitor.prototype.visitQueryUnaryPredicate = function(node) {
|
|
node.predicates[0].visit(this); // sets up the sql.sqlText in the node
|
|
node.sql.sqlText = node.operator + '(' + node.predicates[0].sql.sqlText + ')';
|
|
};
|
|
|
|
/** Handle nodes QueryIsNull, QueryIsNotNull */
|
|
SQLVisitor.prototype.visitQueryUnaryOperator = function(node) {
|
|
var columnName = node.queryField.field.fieldName;
|
|
node.sql.sqlText = columnName + node.operator;
|
|
};
|
|
|
|
/** Handle node QueryBetween */
|
|
SQLVisitor.prototype.visitQueryBetweenOperator = function(node) {
|
|
var columnName = node.queryField.field.fieldName;
|
|
node.sql.sqlText = columnName + ' BETWEEN ? AND ?';
|
|
this.rootPredicateNode.sql.formalParameters[this.parameterIndex++] = node.formalParameters[0];
|
|
this.rootPredicateNode.sql.formalParameters[this.parameterIndex++] = node.formalParameters[1];
|
|
};
|
|
|
|
/******************************************************************************
|
|
* MARKS COLUMN MASKS IN QUERY NODES
|
|
*****************************************************************************/
|
|
function MaskMarkerVisitor() {
|
|
}
|
|
|
|
/** Set column number in usedColumnMask */
|
|
function markUsed(node) {
|
|
node.usedColumnMask = new BitMask();
|
|
node.equalColumnMask = new BitMask();
|
|
node.usedColumnMask.set(node.queryField.field.columnNumber);
|
|
}
|
|
|
|
/** Handle nodes QueryEq, QueryNe, QueryLt, QueryLe, QueryGt, QueryGe */
|
|
MaskMarkerVisitor.prototype.visitQueryComparator = function(node) {
|
|
markUsed(node);
|
|
if(node.operationCode === 4) { // QueryEq
|
|
node.equalColumnMask.set(node.queryField.field.columnNumber);
|
|
}
|
|
};
|
|
|
|
/** Nodes Between, IsNotNull, IsNotNull are all handled by markUsed() */
|
|
MaskMarkerVisitor.prototype.visitQueryUnaryOperator = markUsed;
|
|
MaskMarkerVisitor.prototype.visitQueryBetweenOperator = markUsed;
|
|
|
|
/** Handle QueryNot */
|
|
MaskMarkerVisitor.prototype.visitQueryUnaryPredicate = function(node) {
|
|
node.predicates[0].visit(this);
|
|
node.equalColumnMask = new BitMask(); // Set to zero
|
|
node.usedColumnMask = node.predicates[0].usedColumnMask;
|
|
};
|
|
|
|
/** Handle nodes QueryAnd, QueryOr */
|
|
MaskMarkerVisitor.prototype.visitQueryNaryPredicate = function(node) {
|
|
var i;
|
|
node.usedColumnMask = new BitMask();
|
|
node.equalColumnMask = new BitMask();
|
|
for(i = 0 ; i < node.predicates.length ; i++) {
|
|
node.predicates[i].visit(this);
|
|
node.usedColumnMask.orWith(node.predicates[i].usedColumnMask);
|
|
if(this.operationCode === 1) { // QueryAnd
|
|
node.equalColumnMask.orWith(node.predicates[i].equalColumnMask);
|
|
}
|
|
}
|
|
};
|
|
|
|
var theMaskMarkerVisitor = new MaskMarkerVisitor(); // Singleton
|
|
|
|
|
|
/******************************************************************************
|
|
* TOP LEVEL ABSTRACT QUERY PREDICATE
|
|
*****************************************************************************/
|
|
var AbstractQueryPredicate = function() {
|
|
this.sql = {};
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.inspect = function() {
|
|
var str = this.operator + "(";
|
|
this.predicates.forEach(function(value,index) {
|
|
if(index) str += " , ";
|
|
str += value.inspect();
|
|
});
|
|
str += ")";
|
|
return str;
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.and = function(predicate) {
|
|
// TODO validate parameter
|
|
return new QueryAnd(this, predicate);
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.andNot = function(predicate) {
|
|
// TODO validate parameter
|
|
return new QueryAnd(this, new QueryNot(predicate));
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.or = function(predicate) {
|
|
// TODO validate parameter for OR
|
|
return new QueryOr(this, predicate);
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.orNot = function(predicate) {
|
|
// TODO validate parameter
|
|
return new QueryOr(this, new QueryNot(predicate));
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.not = function() {
|
|
// TODO validate parameter
|
|
return new QueryNot(this);
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.getTopLevelPredicates = function() {
|
|
return [this];
|
|
};
|
|
|
|
AbstractQueryPredicate.prototype.getSQL = function() {
|
|
var visitor = new SQLVisitor(this);
|
|
this.visit(visitor);
|
|
return this.sql;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* ABSTRACT QUERY N-ARY PREDICATE
|
|
* AND and OR
|
|
*****************************************************************************/
|
|
var AbstractQueryNaryPredicate = function() {
|
|
};
|
|
|
|
AbstractQueryNaryPredicate.prototype = new AbstractQueryPredicate();
|
|
|
|
AbstractQueryNaryPredicate.prototype.getTopLevelPredicates = function() {
|
|
return this.predicates;
|
|
};
|
|
|
|
AbstractQueryNaryPredicate.prototype.visit = function(visitor) {
|
|
if (typeof(visitor.visitQueryNaryPredicate) === 'function') {
|
|
visitor.visitQueryNaryPredicate(this);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************
|
|
* ABSTRACT QUERY UNARY PREDICATE
|
|
* NOT
|
|
*****************************************************************************/
|
|
var AbstractQueryUnaryPredicate = function() {
|
|
};
|
|
|
|
AbstractQueryUnaryPredicate.prototype = new AbstractQueryPredicate();
|
|
|
|
AbstractQueryUnaryPredicate.prototype.visit = function(visitor) {
|
|
if (typeof(visitor.visitQueryUnaryPredicate) === 'function') {
|
|
visitor.visitQueryUnaryPredicate(this);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************
|
|
* ABSTRACT QUERY COMPARATOR
|
|
* eq, ne, gt, lt, ge, le
|
|
*****************************************************************************/
|
|
var AbstractQueryComparator = function() {
|
|
};
|
|
|
|
/** AbstractQueryComparator inherits AbstractQueryPredicate */
|
|
AbstractQueryComparator.prototype = new AbstractQueryPredicate();
|
|
|
|
AbstractQueryComparator.prototype.inspect = function() {
|
|
return this.queryField.field.fieldName + this.comparator + this.parameter.inspect();
|
|
};
|
|
|
|
AbstractQueryComparator.prototype.visit = function(visitor) {
|
|
if (typeof(visitor.visitQueryComparator) === 'function') {
|
|
visitor.visitQueryComparator(this);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************
|
|
* QUERY EQUAL
|
|
*****************************************************************************/
|
|
QueryEq = function(queryField, parameter) {
|
|
this.comparator = ' = ';
|
|
this.operationCode = 4;
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryEq.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* QUERY LESS THAN OR EQUAL
|
|
*****************************************************************************/
|
|
QueryLe = function(queryField, parameter) {
|
|
this.comparator = ' <= ';
|
|
this.operationCode = 0;
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryLe.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* QUERY GREATER THAN OR EQUAL
|
|
*****************************************************************************/
|
|
QueryGe = function(queryField, parameter) {
|
|
this.comparator = ' >= ';
|
|
this.operationCode = 2;
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryGe.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* QUERY LESS THAN
|
|
*****************************************************************************/
|
|
QueryLt = function(queryField, parameter) {
|
|
this.comparator = ' < ';
|
|
this.operationCode = 1;
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryLt.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* QUERY GREATER THAN
|
|
*****************************************************************************/
|
|
QueryGt = function(queryField, parameter) {
|
|
this.comparator = ' > ';
|
|
this.operationCode = 3;
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryGt.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* QUERY BETWEEN
|
|
*****************************************************************************/
|
|
QueryBetween = function(queryField, parameter1, parameter2) {
|
|
this.comparator = ' BETWEEN ';
|
|
this.queryField = queryField;
|
|
this.formalParameters = [];
|
|
this.formalParameters[0] = parameter1;
|
|
this.formalParameters[1] = parameter2;
|
|
this.parameter1 = parameter1;
|
|
this.parameter2 = parameter2;
|
|
};
|
|
|
|
QueryBetween.prototype = new AbstractQueryComparator();
|
|
|
|
QueryBetween.prototype.inspect = function() {
|
|
return this.queryField.inspect() + ' BETWEEN ' + this.parameter1.inspect() +
|
|
' AND ' + this.parameter2.inspect();
|
|
};
|
|
|
|
QueryBetween.prototype.visit = function(visitor) {
|
|
if (typeof(visitor.visitQueryBetweenOperator) === 'function') {
|
|
visitor.visitQueryBetweenOperator(this);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************
|
|
* QUERY NOT EQUAL
|
|
*****************************************************************************/
|
|
QueryNe = function(queryField, parameter) {
|
|
this.comparator = ' != ';
|
|
this.operationCode = 5;
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryNe.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* QUERY IN
|
|
*****************************************************************************/
|
|
QueryIn = function(queryField, parameter) {
|
|
this.comparator = ' IN ';
|
|
this.queryField = queryField;
|
|
this.parameter = parameter;
|
|
};
|
|
|
|
QueryIn.prototype = new AbstractQueryComparator();
|
|
|
|
/******************************************************************************
|
|
* ABSTRACT QUERY UNARY OPERATOR
|
|
*****************************************************************************/
|
|
var AbstractQueryUnaryOperator = function() {
|
|
};
|
|
|
|
AbstractQueryUnaryOperator.prototype = new AbstractQueryPredicate();
|
|
|
|
AbstractQueryUnaryOperator.prototype.inspect = function() {
|
|
return util.format(this);
|
|
// return this.queryField.inspect() + this.comparator + this.parameter.inspect();
|
|
};
|
|
|
|
AbstractQueryUnaryOperator.prototype.visit = function(visitor) {
|
|
if (typeof(visitor.visitQueryUnaryOperator) === 'function') {
|
|
visitor.visitQueryUnaryOperator(this);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************
|
|
* QUERY IS NULL
|
|
*****************************************************************************/
|
|
QueryIsNull = function(queryField) {
|
|
this.operator = ' IS NULL';
|
|
this.operationCode = 7;
|
|
this.queryField = queryField;
|
|
};
|
|
|
|
QueryIsNull.prototype = new AbstractQueryUnaryOperator();
|
|
|
|
/******************************************************************************
|
|
* QUERY IS NOT NULL
|
|
*****************************************************************************/
|
|
QueryIsNotNull = function(queryField) {
|
|
this.operator = ' IS NOT NULL';
|
|
this.operationCode = 8;
|
|
this.queryField = queryField;
|
|
};
|
|
|
|
QueryIsNotNull.prototype = new AbstractQueryUnaryOperator();
|
|
|
|
/******************************************************************************
|
|
* QUERY AND
|
|
*****************************************************************************/
|
|
QueryAnd = function(left, right) {
|
|
this.operator = ' AND ';
|
|
this.operationCode = 1;
|
|
this.predicates = [left, right];
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryAnd<ctor>', this);
|
|
};
|
|
|
|
QueryAnd.prototype = new AbstractQueryNaryPredicate();
|
|
|
|
QueryAnd.prototype.getTopLevelPredicates = function() {
|
|
return this.predicates;
|
|
};
|
|
|
|
/** Override the "and" function to collect all predicates in one variable. */
|
|
QueryAnd.prototype.and = function(predicate) {
|
|
this.predicates.push(predicate);
|
|
return this;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* QUERY OR
|
|
*****************************************************************************/
|
|
QueryOr = function(left, right) {
|
|
this.operator = ' OR ';
|
|
this.operationCode = 2;
|
|
this.predicates = [left, right];
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryOr<ctor>', this);
|
|
};
|
|
|
|
QueryOr.prototype = new AbstractQueryNaryPredicate();
|
|
|
|
QueryOr.prototype.getTopLevelPredicates = function() {
|
|
return [];
|
|
};
|
|
|
|
/** Override the "or" function to collect all predicates in one variable. */
|
|
QueryOr.prototype.or = function(predicate) {
|
|
this.predicates.push(predicate);
|
|
return this;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* QUERY NOT
|
|
*****************************************************************************/
|
|
QueryNot = function(left) {
|
|
this.operator = ' NOT ';
|
|
this.operationCode = 3;
|
|
this.predicates = [left];
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryNot<ctor>', this, 'parameter', left);
|
|
};
|
|
|
|
QueryNot.prototype = new AbstractQueryUnaryPredicate();
|
|
|
|
|
|
/******************************************************************************
|
|
* CANDIDATE INDEX
|
|
*****************************************************************************/
|
|
// CandidateIndex is almost stateless now.
|
|
// For future consideration: move mask into DbIndexHandler, then eliminate
|
|
// CandidateIndex completely.
|
|
|
|
var CandidateIndex = function(dbTableHandler, indexNumber) {
|
|
this.dbIndexHandler = dbTableHandler.dbIndexHandlers[indexNumber];
|
|
if(! this.dbIndexHandler) {
|
|
console.log("indexNumber", typeof(indexNumber));
|
|
console.trace("not an index handler");
|
|
throw new Error('Query.CandidateIndex<ctor> indexNumber is not found');
|
|
}
|
|
this.isOrdered = this.dbIndexHandler.dbIndex.isOrdered;
|
|
this.isUnique = this.dbIndexHandler.dbIndex.isUnique;
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('CandidateIndex<ctor> for index', this.dbIndexHandler.dbIndex.name,
|
|
'isUnique', this.isUnique, 'isOrdered', this.isOrdered);
|
|
this.indexColumns = this.dbIndexHandler.dbIndex.columnNumbers;
|
|
var mask = new BitMask(dbTableHandler.dbTable.columns.length);
|
|
this.indexColumns.forEach(function(columnNumber) {
|
|
mask.set(columnNumber);
|
|
});
|
|
this.mask = mask;
|
|
};
|
|
|
|
CandidateIndex.prototype.isUsable = function(predicate) {
|
|
var usable;
|
|
if (this.isUnique) {
|
|
usable = predicate.equalColumnMask.and(this.mask).isEqualTo(this.mask);
|
|
} else if(this.isOrdered) {
|
|
usable = predicate.usedColumnMask.bitIsSet(this.indexColumns[0]);
|
|
}
|
|
return usable;
|
|
};
|
|
|
|
|
|
// This is used in Primary Key & Unique Key queries.
|
|
// param predicate: query predicate
|
|
// param parameterValues: the parameters object passed to query.execute()
|
|
// It returns an array, in key-column order, of the key values from the
|
|
// parameter object.
|
|
CandidateIndex.prototype.getKeys = function(predicate, parameterValues) {
|
|
|
|
function getParameterNameForColumn(node, columnNumber) {
|
|
var i, name;
|
|
if(node.equalColumnMask.bitIsSet(columnNumber)) {
|
|
if(node.queryField && node.queryField.field.columnNumber == columnNumber) {
|
|
return node.parameter.name;
|
|
}
|
|
if(node.predicates) {
|
|
for(i = 0 ; i < node.predicates.length ; i++) {
|
|
name = getParameterNameForColumn(node, columnNumber);
|
|
if(name !== null) return name;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
var result = [];
|
|
var candidateIndex = this;
|
|
this.indexColumns.forEach(function(columnNumber) {
|
|
result.push(parameterValues[getParameterNameForColumn(predicate, columnNumber)]);
|
|
});
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('CandidateIndex.getKeys parameters:', parameterValues,
|
|
'key:', result);
|
|
return result;
|
|
};
|
|
|
|
/** Evaluate candidate indexes.
|
|
Score 1 point for each consecutive key part used plus 1 more point
|
|
if the column is in QueryEq. */
|
|
CandidateIndex.prototype.score = function(predicate) {
|
|
var score = 0;
|
|
var point, i;
|
|
i = 0;
|
|
do {
|
|
point = predicate.usedColumnMask.bitIsSet(this.indexColumns[i]);
|
|
if(point) {
|
|
score += 1;
|
|
if(predicate.usedColumnMask.bitIsSet(this.indexColumns[i])) {
|
|
score += 1;
|
|
}
|
|
}
|
|
i++;
|
|
} while(point && i < this.indexColumns.length);
|
|
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('score', this.dbIndexHandler.dbIndex.name, 'is', score);
|
|
return score;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* QUERY HANDLER
|
|
*****************************************************************************/
|
|
/* QueryHandler constructor
|
|
* IMMEDIATE
|
|
*
|
|
* statically analyze the predicate to decide whether:
|
|
* all primary key fields are specified ==> use primary key lookup;
|
|
* all unique key fields are specified ==> use unique key lookup;
|
|
* some (leading) index fields are specified ==> use index scan;
|
|
* none of the above ==> use table scan
|
|
* Get the query handler for a given query predicate.
|
|
*
|
|
*/
|
|
var QueryHandler = function(dbTableHandler, predicate) {
|
|
if(udebug.is_detail()) if(udebug.is_debug()) udebug.log('QueryHandler<ctor>', util.inspect(predicate));
|
|
this.dbTableHandler = dbTableHandler;
|
|
this.predicate = predicate;
|
|
var indexes = dbTableHandler.dbTable.indexes;
|
|
|
|
// Mark the usedColumnMask and equalColumnMask in each query node
|
|
predicate.visit(theMaskMarkerVisitor);
|
|
|
|
// create a CandidateIndex object for each index
|
|
// if the primary index is usable, choose it
|
|
var primaryCandidateIndex = new CandidateIndex(dbTableHandler, 0);
|
|
|
|
if(primaryCandidateIndex.isUsable(predicate)) {
|
|
// we're done!
|
|
this.candidateIndex = primaryCandidateIndex;
|
|
this.queryType = 0; // primary key lookup
|
|
return;
|
|
}
|
|
// otherwise, look for a usable unique index
|
|
var uniqueCandidateIndex, orderedCandidateIndexes = [];
|
|
var i, index;
|
|
for (i = 1; i < indexes.length; ++i) {
|
|
index = indexes[i];
|
|
if (index.isUnique) {
|
|
// create a candidate index for unique index
|
|
uniqueCandidateIndex = new CandidateIndex(dbTableHandler, i);
|
|
if (uniqueCandidateIndex.isUsable(predicate)) {
|
|
this.candidateIndex = uniqueCandidateIndex;
|
|
this.queryType = 1; // unique key lookup
|
|
// we're done!
|
|
return;
|
|
}
|
|
} else if (index.isOrdered) {
|
|
// create an array of candidate indexes for ordered indexes to be evaluated later
|
|
orderedCandidateIndexes.push(new CandidateIndex(dbTableHandler, i));
|
|
} else {
|
|
throw new Error('FatalInternalException: index is not unique or ordered... so what is it?');
|
|
}
|
|
}
|
|
// otherwise, look for the best ordered index (largest number of usable query terms)
|
|
// choose the index with the biggest score
|
|
var topScore = 0, candidateScore = 0;
|
|
var bestCandidateIndex = null;
|
|
orderedCandidateIndexes.forEach(function(candidateIndex) {
|
|
candidateScore = candidateIndex.score(predicate);
|
|
if (candidateScore > topScore) {
|
|
topScore = candidateScore;
|
|
bestCandidateIndex = candidateIndex;
|
|
}
|
|
});
|
|
udebug.log("Best score is", topScore);
|
|
if (topScore > 0) {
|
|
this.candidateIndex = bestCandidateIndex;
|
|
this.dbIndexHandler = bestCandidateIndex.dbIndexHandler;
|
|
this.queryType = 2; // index scan
|
|
} else {
|
|
this.queryType = 3; // table scan
|
|
}
|
|
};
|
|
|
|
/** Get key values from candidate indexes and parameters */
|
|
QueryHandler.prototype.getKeys = function(parameterValues) {
|
|
return this.candidateIndex.getKeys(this.predicate, parameterValues);
|
|
};
|
|
|
|
exports.QueryDomainType = QueryDomainType;
|
|
exports.QueryHandler = QueryHandler;
|