jones-ndb
Version:
Native NDB (MySQL Cluster) Service Provider for Database Jones
298 lines (252 loc) • 9.58 kB
JavaScript
/*
Copyright (c) 2013, 2016, 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 conf = require("./path_config"),
assert = require("assert"),
adapter = require(conf.binary),
NdbInterpretedCode = adapter.ndb.ndbapi.NdbInterpretedCode,
NdbScanFilter = adapter.ndb.ndbapi.NdbScanFilter,
udebug = unified_debug.getLogger("NdbScanFilter.js");
function QueryTerm(offset, column, param) {
this.param = param;
this.column = column;
this.offset = offset;
this.constBuffer = null;
}
/* Encode value into buffer.
If params are supplied, then this.param is assumed to be a QueryParameter.
Otherwise, this.param is treated as a query constant term, and we retain
a reference to the buffer.
*/
QueryTerm.prototype.encode = function(buffer, params) {
var value;
if(params) {
value = params[this.param.name]; // a QueryParameter (from Jones Query.js)
} else {
value = this.param; // a query constant
this.constBuffer = buffer;
}
adapter.ndb.impl.encoderWrite(this.column, value, buffer, this.offset);
};
/* BufferSchema points to a Buffer and describes the size and layout of
values in that buffer.
*/
function BufferSchema() {
this.layout = []; // an array of QueryTerm
this.size = 0; // length of the buffer
}
/* Create a buffer;
encode each spec in layout into the buffer using the supplied params;
return the buffer.
*/
BufferSchema.prototype.encode = function(params) {
var i, buffer;
buffer = null;
if(this.size > 0) {
buffer = new Buffer(this.size);
for(i = 0; i < this.layout.length ; i++) {
this.layout[i].encode(buffer, params);
}
}
return buffer;
};
/* Add a query term to layout, and return it
*/
BufferSchema.prototype.addTerm = function(column, param) {
var term = new QueryTerm(this.size, column, param);
this.layout.push(term);
this.size += column.columnSpace;
return term;
};
/* Make a note in a node of the predicate tree.
The note will be used to store all NDB-related analysis.
Copy the node's operator or comparator code into the ndb section.
This should be called in the first-pass visitor.
*/
function markNode(node) {
var opcode = node.operationCode || null;
node.ndb = {
"opcode" : opcode,
"layout" : null,
"intervals" : {}
};
}
/*************************************** BufferManagerVisitor ************
*
* This is the first pass, run when the operation is declared.
*
* Visit nodes, marking them for NdbScanFilter. Calculate buffer layout
* and needed space.
*/
function BufferManagerVisitor(filterSpec) {
this.spec = filterSpec;
}
/** Handle nodes QueryAnd, QueryOr */
BufferManagerVisitor.prototype.visitQueryNaryPredicate = function(node) {
var i;
markNode(node);
for(i = 0 ; i < node.predicates.length ; i++) {
node.predicates[i].visit(this);
}
};
/** Handle nodes QueryEq, QueryNe, QueryLt, QueryLe, QueryGt, QueryGe */
BufferManagerVisitor.prototype.visitQueryComparator = function(node) {
var colId = node.queryField.field.columnNumber;
var col = this.spec.dbTable.columns[colId];
var schema = node.constants ? this.spec.constSchema : this.spec.paramSchema;
markNode(node);
node.ndb.layout = schema.addTerm(col, node.parameter);
};
/** Handle node QueryNot */
BufferManagerVisitor.prototype.visitQueryUnaryPredicate = function(node) {
markNode(node);
node.predicates[0].visit(this);
};
/** Handle node QueryBetween */
BufferManagerVisitor.prototype.visitQueryBetweenOperator = function(node) {
var colId, col, spec1, spec2, schema1, schema2;
colId = node.queryField.field.columnNumber;
col = this.spec.dbTable.columns[colId];
schema1 = node.constants & 1 ? this.spec.constSchema : this.spec.paramSchema;
schema2 = node.constants & 2 ? this.spec.constSchema : this.spec.paramSchema;
spec1 = schema1.addTerm(col, node.parameter1);
spec2 = schema2.addTerm(col, node.parameter2);
markNode(node);
node.ndb.layout = { "between" : [ spec1 , spec2 ] };
};
/** Handle nodes QueryIsNull, QueryIsNotNull */
BufferManagerVisitor.prototype.visitQueryUnaryOperator = function(node) {
markNode(node);
node.ndb.layout = { "columnNumber" : node.queryField.field.columnNumber };
};
/************************************** FilterBuildingVisitor ************
*
* This is the second pass, run each time the operation is executed.
*
* Visit nodes and build NdbScanFilter.
*/
function FilterBuildingVisitor(dbTable, paramBuffer) {
this.paramBuffer = paramBuffer;
this.ndbInterpretedCode = NdbInterpretedCode.create(dbTable);
this.ndbScanFilter = NdbScanFilter.create(this.ndbInterpretedCode);
this.ndbScanFilter.begin(1); // implicit top-level AND group
}
/** Handle nodes QueryAnd, QueryOr */
FilterBuildingVisitor.prototype.visitQueryNaryPredicate = function(node) {
var i = 0;
this.ndbScanFilter.begin(node.ndb.opcode);
for(i = 0 ; i < node.predicates.length ; i++) {
node.predicates[i].visit(this);
}
udebug.log(node.operator);
this.ndbScanFilter.end();
};
/** Handle nodes QueryEq, QueryNe, QueryLt, QueryLe, QueryGt, QueryGe */
FilterBuildingVisitor.prototype.visitQueryComparator = function(node) {
var opcode = node.ndb.opcode;
var layout = node.ndb.layout;
this.ndbScanFilter.cmp(opcode, layout.column.columnNumber,
layout.constBuffer || this.paramBuffer,
layout.offset, layout.column.columnSpace);
udebug.log(node.queryField.field.fieldName, node.comparator, "value");
};
/** Handle nodes QueryNot */
FilterBuildingVisitor.prototype.visitQueryUnaryPredicate = function(node) {
this.ndbScanFilter.begin(node.ndb.opcode); // A 1-member NAND group
node.predicates[0].visit(this);
udebug.log("NOT");
this.ndbScanFilter.end();
};
/** Handle nodes QueryIsNull, QueryIsNotNull */
FilterBuildingVisitor.prototype.visitQueryUnaryOperator = function(node) {
var opcode = node.ndb.opcode;
var colId = node.ndb.layout.columnNumber;
if(opcode === 7) {
this.ndbScanFilter.isnull(colId);
}
else {
assert(opcode === 8);
this.ndbScanFilter.isnotnull(colId);
}
udebug.log(node.queryField.field.fieldName, node.operator);
};
/** Handle node QueryBetween */
FilterBuildingVisitor.prototype.visitQueryBetweenOperator = function(node) {
var col1 = node.ndb.layout.between[0];
var col2 = node.ndb.layout.between[1];
this.ndbScanFilter.begin(1); // AND
this.ndbScanFilter.cmp(2, col1.column.columnNumber,
col1.constBuffer || this.paramBuffer,
col1.offset, col1.column.columnSpace); // >= col1
this.ndbScanFilter.cmp(0, col2.column.columnNumber,
col2.constBuffer || this.paramBuffer,
col2.offset, col2.column.columnSpace); // <= col2
this.ndbScanFilter.end();
udebug.log(node.queryField.field.fieldName, "BETWEEN values");
};
FilterBuildingVisitor.prototype.finalise = function() {
this.ndbScanFilter.end();
};
/*************************************************/
/* FilterSpec describes filter implementation; will be stored in QueryHandler
*/
function FilterSpec(queryHandler) {
this.predicate = queryHandler.predicate;
this.dbTable = queryHandler.dbTableHandler.dbTable;
this.constSchema = new BufferSchema();
this.paramSchema = new BufferSchema();
this.constFilter = null;
this.constBuffer = null;
this.markQuery();
}
FilterSpec.prototype.markQuery = function() {
/* 1st pass. Mark tree and calculate buffer sizes. */
this.predicate.visit(new BufferManagerVisitor(this));
/* Encode buffer for constant query terms */
if(this.predicate.constants) {
this.constBuffer = this.constSchema.encode();
/* If paramSchema.size is zero, then the query uses *only* constant terms.
Optimize by building a filter just once in advance.
*/
if(this.paramSchema.size === 0) {
this.constFilter = this.buildFilter(null);
}
}
};
FilterSpec.prototype.buildFilter = function(paramBuffer) {
var visitor = new FilterBuildingVisitor(this.dbTable, paramBuffer);
this.predicate.visit(visitor);
visitor.finalise();
return visitor;
};
FilterSpec.prototype.getScanFilterCode = function(params) {
var paramBuffer;
if(this.constFilter) {
udebug.log("getScanFilterCode: ScanFilter is const");
return this.constFilter.ndbInterpretedCode;
}
/* Encode the parameters */
paramBuffer = this.paramSchema.encode(params);
/* Build the NdbScanFilter for this operation */
return this.buildFilter(paramBuffer).ndbInterpretedCode;
};
function prepareFilterSpec(queryHandler) {
if(queryHandler.predicate && !queryHandler.ndbFilterSpec) {
queryHandler.ndbFilterSpec = new FilterSpec(queryHandler);
}
}
exports.prepareFilterSpec = prepareFilterSpec;