jones-ndb
Version:
Native NDB (MySQL Cluster) Service Provider for Database Jones
1,024 lines (883 loc) • 33 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";
/* This corresponds to OperationCodes */
var op_stats = {
"read" : 0,
"insert" : 0,
"update" : 0,
"write" : 0,
"delete" : 0,
"scan" : 0,
"scan_read" : 0,
"scan_count" : 0,
"scan_delete" : 0,
"projection_read" : 0
};
var index_stats = {};
var path = require("path"),
assert = require("assert"),
conf = require("./path_config"),
adapter = require(conf.binary).ndb,
jones = require("database-jones"),
doc = require(jones.spi_doc.DBOperation),
stats_module = require(jones.api.stats),
QueuedAsyncCall = require(jones.common.QueuedAsyncCall).QueuedAsyncCall,
prepareFilterSpec = require("./NdbScanFilter.js").prepareFilterSpec,
getIndexBounds = require(jones.common.IndexBounds).getIndexBounds,
markQuery = require(jones.common.IndexBounds).markQuery,
bufferForText = adapter.impl.bufferForText,
textFromBuffer = adapter.impl.textFromBuffer,
COMMIT = adapter.ndbapi.Commit,
NOCOMMIT = adapter.ndbapi.NoCommit,
ROLLBACK = adapter.ndbapi.Rollback,
constants = adapter.impl,
OpHelper = constants.OpHelper,
ScanHelper = constants.Scan.helper,
BoundHelper = constants.IndexBound.helper,
opcodes = doc.OperationCodes,
NdbProjection = require("./NdbProjection"),
udebug = unified_debug.getLogger("NdbOperation.js");
stats_module.register(op_stats, "spi","ndb","DBOperation","created");
stats_module.register(index_stats, "spi","ndb","key_access");
stats_module.register(adapter.impl.encoder_stats, "spi","ndb","encoder");
var storeNativeConstructorInMapping;
var DBResult = function() {
this.success = null;
this.error = null;
this.value = null;
this.insert_id = null;
};
// DBOperationError
var errorClassificationMap = {
"ConstraintViolation" : "23000",
"NoDataFound" : "02000",
"UnknownResultError" : "08000"
};
var sqlStateMessages = {
"22000" : "Data error",
"22001" : "String too long",
"22003" : "Numeric value out of range",
"22007" : "Invalid datetime",
"23000" : "Column cannot be null",
"HY000" : "Incorrect numeric value",
"0F001" : "BLOB or BINARY value is not a Buffer",
"WCTOR" :
"A Domain Object Constructor has overwritten persistent properties "+
"that were read from the database. The Domain Object Constructor "+
"is called with no arguments and its ``this'' parameter set to the "+
"newly read object."
};
function DBOperationError(message) {
this.message = message || "";
this.ndb_error = null;
}
DBOperationError.prototype = {
sqlstate : "NDB00",
cause : null
};
DBOperationError.prototype.fromNdbError = function(ndb_error) {
this.message = ndb_error.message + " [" + ndb_error.code + "]";
this.sqlstate = errorClassificationMap[ndb_error.classification];
this.ndb_error = ndb_error;
return this;
};
DBOperationError.prototype.fromSqlState = function(sqlstate) {
this.message = sqlStateMessages[sqlstate];
this.sqlstate = sqlstate;
/* Some exceptional behavior: */
if(sqlstate == "0F001") {
this.sqlstate = "22000";
}
return this;
};
DBOperationError.prototype.cascading = function(cause) {
udebug.log("Adding indirect error from", cause);
this.message = "Cascading Error";
this.sqlstate = cause.sqlstate;
this.cause = cause;
return this;
};
function keepIndexStatistics(dbTable, index) {
var i, idxStats, keyName;
if(index_stats[dbTable.name] === undefined) {
idxStats = { "PrimaryKey" : 0 };
for(i = 1 ; i < dbTable.indexes.length ; i++) {
idxStats[dbTable.indexes[i].name] = 0;
}
index_stats[dbTable.name] = idxStats;
}
keyName = (index.isPrimaryKey ? "PrimaryKey" : index.name);
index_stats[dbTable.name][keyName]++;
}
function storeResultRecord(dbTableHandler) {
if(! dbTableHandler.resultRecord) {
// getRecordForMapping(table, ndb, nColumns, columnsArray)
dbTableHandler.resultRecord =
adapter.impl.DBDictionary.getRecordForMapping(
dbTableHandler.dbTable,
dbTableHandler.dbTable.per_table_ndb,
dbTableHandler.getNumberOfColumns(),
dbTableHandler.getAllColumnMetadata()
);
}
return dbTableHandler.resultRecord;
}
var DBOperation = function(opcode, tx, indexHandler, tableHandler) {
assert(tx);
this.opcode = opcode;
this.userCallback = null;
this.transaction = tx;
this.keys = {};
this.values = {};
this.lockMode = "";
this.result = new DBResult();
this.indexHandler = indexHandler;
if(indexHandler) {
this.tableHandler = indexHandler.tableHandler;
this.index = indexHandler.dbIndex;
keepIndexStatistics(this.tableHandler.dbTable, this.index);
}
else {
this.tableHandler = tableHandler;
this.index = null;
}
storeResultRecord(this.tableHandler);
/* NDB Impl-specific properties */
this.encoderError = null;
this.query = null;
this.scanOp = null;
this.needAutoInc = false;
this.buffers = { 'row' : null, 'key' : null };
this.columnMask = [];
this.scan = {};
this.blobs = null;
this.connProperties = tx.dbSession.parentPool.properties;
op_stats[opcodes[opcode]]++;
};
function allocateKeyBuffer(op) {
assert(op.buffers.key === null);
op.buffers.key = new Buffer(op.index.record.getBufferSize());
}
function releaseKeyBuffer(op) {
if(op.opcode !== 2) { /* all but insert use a key */
op.buffers.key = null;
}
}
/* If an error occurs while encoding,
encodeColumnsInBuffer() returns a DBOperationError
*/
function encodeColumnsInBuffer(fields, ncolumns, metadata,
record, buffer, definedColumnList) {
var i, column, value, encoderError, error;
error = null;
function addError(value) {
udebug.log("encoderWrite error:", encoderError, "for", value);
if(error) { // More than one column error, so use the generic code
error.sqlstate = "22000";
error.message += "; [" + column.name + "]";
} else {
error = new DBOperationError().fromSqlState(encoderError);
error.message += " [" + column.name + "]";
}
}
/* encodeColumnsInBuffer starts here */
for(i = 0 ; i < ncolumns ; i++) {
column = metadata[i];
value = fields[i];
if(value !== undefined) {
definedColumnList.push(column.columnNumber);
if(value === null) {
if(column.isNullable) { record.setNull(i, buffer); }
else { encoderError = "23000"; addError(); }
}
else {
encoderError = record.encoderWrite(i, buffer, value);
if(encoderError) { addError(value); }
}
}
}
return error;
}
function encodeKeyBuffer(op) {
var oneCol = op.indexHandler.singleColumn; // single-column index
if(oneCol && op.keys[0] !== null && op.keys[0] !== undefined) {
return op.index.record.encoderWrite(0, op.buffers.key, op.keys[0]);
}
return encodeColumnsInBuffer(op.keys,
op.indexHandler.getNumberOfColumns(),
op.indexHandler.getAllColumnMetadata(),
op.index.record,
op.buffers.key, []);
}
function defineBlobs(ncolumns, metadata, values) {
var i, blobs, col;
blobs = [];
for(i = 0 ; i < ncolumns ; i++) {
col = metadata[i];
if(col.isLob) {
blobs[i] = col.isBinary ? values[i] : bufferForText(col, values[i]) ;
}
}
return blobs;
}
function allocateRowBuffer(op) {
assert(op.buffers.row === null);
op.buffers.row = new Buffer(op.tableHandler.resultRecord.getBufferSize());
}
function releaseRowBuffer(op) {
op.buffers.row = null;
}
function encodeRowBuffer(op) {
udebug.log("encodeRowBuffer");
var valuesArray = op.tableHandler.getColumns(op.values);
var ncolumns = op.tableHandler.getNumberOfColumns();
var columnMetadata = op.tableHandler.getAllColumnMetadata();
if(op.tableHandler.numberOfLobColumns) {
op.blobs = defineBlobs(ncolumns, columnMetadata, valuesArray);
}
return encodeColumnsInBuffer(valuesArray,
ncolumns,
columnMetadata,
op.tableHandler.resultRecord,
op.buffers.row,
op.columnMask);
}
function HelperSpec() {
this.clear();
}
HelperSpec.prototype.clear = function() {
this[0] = null; // row_buffer
this[1] = null; // key_buffer
this[2] = null; // row_record
this[3] = null; // key_record
this[4] = null; // lock_mode
this[5] = null; // column_mask
this[6] = null; // value_obj
this[7] = null; // opcode
this[8] = null; // is_value_obj
this[9] = null; // blobs
this[10] = null; // is_valid
};
var helperSpec = new HelperSpec();
function ScanHelperSpec() {
this.clear();
}
ScanHelperSpec.prototype.clear = function() {
this[ScanHelper.table_record] = null;
this[ScanHelper.index_record] = null;
this[ScanHelper.lock_mode] = null;
this[ScanHelper.bounds] = null;
this[ScanHelper.flags] = 0;
this[ScanHelper.batch_size] = null;
this[ScanHelper.parallel] = null;
this[ScanHelper.filter_code] = null;
};
var scanSpec = new ScanHelperSpec();
function BoundHelperSpec() {
this[BoundHelper.low_key] = null;
this[BoundHelper.low_key_count] = 0;
this[BoundHelper.low_inclusive] = true;
this[BoundHelper.high_key] = null;
this[BoundHelper.high_key_count] = 0;
this[BoundHelper.high_inclusive] = true;
this[BoundHelper.range_no] = 0;
}
/* Create part of of a bound spec
*/
BoundHelperSpec.prototype.buildPartialSpec = function(base, bound,
dbIndexHandler, buffer) {
var nparts, err, columns;
columns = dbIndexHandler.getAllColumnMetadata();
err = null;
/* count finite key parts.
IndexBounds has assumed all columns are nullable, so we may have to
transform a NULL bound to a -Infinity.
*/
for(nparts = 0 ; nparts < bound.key.length; nparts++) {
if((bound.key[nparts] == Infinity) ||
(bound.key[nparts] == -Infinity) ||
(bound.key[nparts] === null && ! columns[nparts].isNullable))
{
break;
}
}
if(nparts > 0) {
err = encodeColumnsInBuffer(bound.key, nparts, columns,
dbIndexHandler.dbIndex.record, buffer, []);
}
udebug.log("Encoded", nparts, "parts for", (base ? "high" : "low"), "bound");
this[base] = (nparts > 0 ? buffer : null);
this[base + 1] = nparts;
this[base + 2] = bound.inclusive;
return err;
};
BoundHelperSpec.prototype.setLow = function(bound, dbIndexHandler, buffer) {
return this.buildPartialSpec(BoundHelper.low_key, bound.low, dbIndexHandler, buffer);
};
BoundHelperSpec.prototype.setHigh = function(bound, dbIndexHandler, buffer) {
return this.buildPartialSpec(BoundHelper.high_key, bound.high, dbIndexHandler, buffer);
};
/* Takes an array of IndexBounds;
Returns an array of BoundHelpers which will be used to build NdbIndexBounds.
Builds a buffer of encoded parameters used in index bounds and
stores a reference to it in op.scan.
*/
DBOperation.prototype.buildBoundHelpers = function(indexBounds) {
var dbIndexHandler, bound, sz, n, helper, allHelpers, mainBuffer, offset, i;
dbIndexHandler = this.indexHandler;
sz = dbIndexHandler.dbIndex.record.getBufferSize();
n = indexBounds.length;
if(sz && n) {
allHelpers = [];
mainBuffer = new Buffer(sz * n * 2);
offset = 0;
this.scan.bound_param_buffer = mainBuffer; // maintain a reference!
for(i = 0 ; i < n ; i++) {
bound = indexBounds[i];
helper = new BoundHelperSpec();
helper.setLow(bound, dbIndexHandler, mainBuffer.slice(offset, offset+sz));
offset += sz;
helper.setHigh(bound, dbIndexHandler, mainBuffer.slice(offset, offset+sz));
offset += sz;
helper[BoundHelper.range_no] = i;
allHelpers.push(helper);
}
}
this.scan.index_bound_helpers = allHelpers; // maintain a reference
return allHelpers;
};
DBOperation.prototype.buildOpHelper = function(helper) {
var code = this.opcode;
var isVOwrite = (this.values && adapter.impl.isValueObject(this.values));
var error = null;
/* All operations but insert use a key. */
if(code !== 2) {
allocateKeyBuffer(this);
encodeKeyBuffer(this);
helper[OpHelper.key_record] = this.index.record;
helper[OpHelper.key_buffer] = this.buffers.key;
}
/* If this is an update-after-read operation on a Value Object,
DBOperationHelper only needs the VO.
*/
if(isVOwrite) {
error = adapter.impl.prepareForUpdate(this.values);
if(error) {
this.encoderError = new DBOperationError().fromSqlState(error);
} else {
helper[OpHelper.value_obj] = this.values;
}
}
else {
/* All non-VO operations get a row record */
helper[OpHelper.row_record] = this.tableHandler.resultRecord;
/* All but delete get an allocated row buffer, and column mask */
if(code !== 16) {
allocateRowBuffer(this);
helper[OpHelper.row_buffer] = this.buffers.row;
helper[OpHelper.column_mask] = this.columnMask;
/* Read gets a lock mode, and possibly a blobs array.
writes get the data encoded into the row buffer. */
if(code === 1) {
helper[OpHelper.lock_mode] = constants.LockModes[this.lockMode];
if(this.tableHandler.numberOfLobColumns) {
this.blobs = [];
}
}
else {
this.encoderError = encodeRowBuffer(this);
}
}
}
helper[OpHelper.opcode] = code;
helper[OpHelper.is_value_obj] = isVOwrite;
helper[OpHelper.blobs] = this.blobs;
helper[OpHelper.is_valid] = this.encoderError ? false : true;
};
function prepareOperations(dbTransactionContext, dbOperationList, recycleWrapper) {
assert(dbTransactionContext);
var n, length, specs;
length = dbOperationList.length;
if(length == 1) {
specs = [ helperSpec ]; /* Reuse the global helperSpec */
helperSpec.clear();
dbOperationList[0].buildOpHelper(helperSpec);
}
else {
specs = new Array(length);
for(n = 0 ; n < dbOperationList.length ; n++) {
specs[n] = new HelperSpec();
dbOperationList[n].buildOpHelper(specs[n]);
}
}
return adapter.impl.DBOperationHelper(length, specs, dbTransactionContext, recycleWrapper);
}
/* Prepare a scan operation.
This produces the scan filter and index bounds, and then a ScanOperation,
which is returned back to NdbTransactionHandler for execution.
*/
DBOperation.prototype.prepareScan = function(dbTransactionContext) {
var indexBounds = null;
var boundsHelpers, dbIndex, skipFilterForTesting;
/* There is one global ScanHelperSpec */
scanSpec.clear();
scanSpec[ScanHelper.table_record] = this.query.dbTableHandler.resultRecord;
if(this.query.queryType == 2) { /* Index Scan */
dbIndex = this.query.dbIndexHandler.dbIndex;
scanSpec[ScanHelper.index_record] = dbIndex.record;
indexBounds = getIndexBounds(this.query, dbIndex, this.params);
udebug.log("index bounds:", indexBounds.length);
if(indexBounds.length) {
boundsHelpers = this.buildBoundHelpers(indexBounds);
scanSpec[ScanHelper.bounds] = [];
if(indexBounds.length > 1) {
scanSpec[ScanHelper.flags] |= constants.Scan.flags.SF_MultiRange;
}
boundsHelpers.forEach(function(helper) {
var b = adapter.impl.IndexBound.create(helper);
scanSpec[ScanHelper.bounds].push(b);
});
}
}
scanSpec[ScanHelper.lock_mode] = constants.LockModes[this.lockMode];
if(this.params.order !== undefined) {
scanSpec[ScanHelper.flags] |= constants.Scan.flags.SF_OrderBy;
if(this.params.order.toLocaleLowerCase() == 'desc') {
scanSpec[ScanHelper.flags] |= constants.Scan.flags.SF_Descending;
}
}
skipFilterForTesting = false;
if(this.query.ndbFilterSpec && ! skipFilterForTesting) {
scanSpec[ScanHelper.filter_code] =
this.query.ndbFilterSpec.getScanFilterCode(this.params);
this.scan.filter = scanSpec[ScanHelper.filter_code];
udebug.log("Using Scan Filter");
}
udebug.log("Flags", scanSpec[ScanHelper.flags]);
this.scanOp = adapter.impl.Scan.create(scanSpec, 33, dbTransactionContext);
return this.scanOp;
};
DBOperation.prototype.isQueryOperation = function() {
return (this.opcode == 97);
};
DBOperation.prototype.isScanOperation = function() {
return (this.opcode >= 32);
};
function buildResultRow_nonVO(op, dbt, buffer, blobs) {
udebug.log("buildResultRow");
var i, value;
var record = dbt.resultRecord;
var ncolumns = dbt.getNumberOfColumns();
var col = dbt.getAllColumnMetadata();
var resultRow = op.result.value || dbt.newResultObject();
for(i = 0 ; i < ncolumns ; i++) {
if(col[i].isLob) {
value = col[i].isBinary ? blobs[i] : textFromBuffer(col[i], blobs[i]);
} else if(record.isNull(i, buffer)) {
value = null;
} else {
value = record.encoderRead(i, buffer);
}
dbt.set(resultRow, i, value);
}
return resultRow;
}
function buildValueObject(op, tableHandler, buffer, blobs) {
udebug.log("buildValueObject");
var VOC = tableHandler.ValueObject; // NDB Value Object Constructor
var DOC = tableHandler.newObjectConstructor; // User's Domain Object Ctor
var nWritesPre, nWritesPost, value, i;
if(VOC) {
/* Turn the buffer into a Value Object */
value = new VOC(buffer, blobs);
/* Allow DBT to apply converters if it has them */
for(i = 0 ; i < tableHandler.getNumberOfColumns(); i++) {
if(tableHandler.columnHasConverter(i)) {
tableHandler.set(value, i, adapter.impl.getValueObjectFieldByNumber(value, i));
}
}
/* Finally the user's constructor is called on the new value: */
if(DOC) {
nWritesPre = adapter.impl.getValueObjectWriteCount(value);
DOC.call(value);
nWritesPost = adapter.impl.getValueObjectWriteCount(value);
if(nWritesPost > nWritesPre) {
op.result.error = new DBOperationError().fromSqlState("WCTOR");
op.result.success = false;
}
}
}
return value;
}
function getResultValue(op, tableHandler, buffer, blobs) {
// workaround: currently NdbRecordObject will not correctly hide
// the sparse field container from the user
var use_nro = (op.connProperties.use_mapped_ndb_record &&
tableHandler.is1to1 &&
op.result.value === null);
return use_nro ? buildValueObject(op, tableHandler, buffer, blobs) :
buildResultRow_nonVO(op, tableHandler, buffer, blobs);
}
function getScanResults(scanop, userCallback) {
var buffer,results,dbSession,postScanCallback,nSkip,maxRow,i,recordSize,gather;
dbSession = scanop.transaction.dbSession;
postScanCallback = {
fn : userCallback,
arg0: null,
arg1: null
};
i = 0;
nSkip = 0;
maxRow = 100000000000;
if(scanop.params) {
if(scanop.params.skip > 0) { nSkip = scanop.params.skip; }
if(scanop.params.limit >= 0) { maxRow = nSkip + scanop.params.limit; }
}
if(udebug.is_debug()) {
udebug.log("skip", nSkip, "+ limit", scanop.params.limit, "=", maxRow);
}
recordSize = scanop.tableHandler.resultRecord.getBufferSize();
function fetchResults(dbSession, ndb_scan_op, buffer) {
var apiCall = new QueuedAsyncCall(dbSession.execQueue, null);
var force_send = true;
apiCall.preCallback = gather;
apiCall.ndb_scan_op = ndb_scan_op;
apiCall.description = "fetchResults" + scanop.transaction.moniker + i;
apiCall.buffer = buffer;
apiCall.run = function runFetchResults() {
this.ndb_scan_op.fetchResults(this.buffer, force_send, this.callback);
};
apiCall.enqueue();
i++;
}
function pushNewResult() {
var blobs, result;
blobs = scanop.scanOp.readBlobResults();
udebug.log("pushNewResult",i,blobs);
result = getResultValue(scanop, scanop.tableHandler, buffer, blobs);
results.push(result);
}
function fetch() {
buffer = new Buffer(recordSize);
fetchResults(dbSession, scanop.scanOp, buffer); // gather() is the callback
}
/* <0: ERROR, 0: RESULTS_READY, 1: SCAN_FINISHED, 2: CACHE_EMPTY */
/* gather runs as a preCallback */
gather = function(error, status) {
udebug.log("gather() status", status);
if(status < 0) { // error
if(udebug.is_debug()) { udebug.log("gather() error", error); }
postScanCallback.arg0 = error;
return postScanCallback;
}
/* Gather more results. */
while(status === 0 && results.length < maxRow) {
pushNewResult();
buffer = new Buffer(recordSize);
status = scanop.scanOp.nextResult(buffer);
}
if(status == 2 && results.length < maxRow) { // Cache empty
fetch();
}
else { // end of scan.
/* Now remove the rows that should have been skipped
(fixme: do something more efficient) */
for(i = 0 ; i < nSkip ; i++) { results.shift(); }
udebug.log("gather() 1 End_Of_Scan. Final length:", results.length);
scanop.result.success = true;
scanop.result.value = results;
postScanCallback.arg1 = results;
return postScanCallback;
}
};
/* start here */
results = [];
fetch();
}
function getQueryResults(op, userCallback) {
var i = 0;
var sectors = [];
var ndbProjection = op.query;
while(ndbProjection) {
sectors[i++] = ndbProjection;
ndbProjection = ndbProjection.next;
}
op.scanOp.fetchAllResults(function(err, nresults) {
var wrapper, level, current, parentLevel, resultObject;
current = []; // current values for each sector
current[0] = null;
wrapper = {}; // the wrapper is reused in each call to getResult()
function setValueInRelatedTable(relatedField, resultValue) {
if(relatedField.toMany) {
if(current[parentLevel][relatedField.fieldName] === undefined) {
current[parentLevel][relatedField.fieldName] = [];
}
if(resultValue !== null) {
current[parentLevel][relatedField.fieldName].push(resultValue);
}
} else { // toOne
current[parentLevel][relatedField.fieldName] = resultValue;
}
}
function assemble() {
current[level] = resultObject;
if(level > 0) {
setValueInRelatedTable(sectors[level].relatedField, resultObject);
}
}
function assembleSpecial(tag) {
udebug.log_detail("assembleSpecial table", level, "tag", tag);
if(tag & 2) { /* This row came from a many-to-many join table but
is not itself part of the user's result object. */
current[level] = current[parentLevel];
}
if(tag & 1) { /* Row is null */
current[level] = null;
if(level > 0) {
setValueInRelatedTable(sectors[level].relatedField, null);
}
}
if(tag & 8) {
udebug.log_detail("Filtered - row is duplicate");
}
}
udebug.log("fetchAllResults returns", err, nresults);
if(err) {
op.result.success = false;
op.result.error = new DBOperationError().fromNdbError(err);
} else if (nresults == 0) {
op.result.success = false;
op.result.error = new DBOperationError().fromSqlState("02000");
} else {
for(i = 0 ; i < nresults ; i++) {
op.scanOp.getResult(i, wrapper);
level = wrapper.level;
if(level > 0) { parentLevel = sectors[level].parent.serial; }
if(udebug.is_detail) {
udebug.log("TABLE", level, sectors[level].tableHandler.dbTable.name,
"PARENT TABLE", parentLevel);
}
if(wrapper.tag) {
assembleSpecial(wrapper.tag);
} else {
resultObject = getResultValue(op, sectors[level].tableHandler,
wrapper.data, null);
assemble();
}
}
op.result.success = true;
op.result.value = current[0];
}
udebug.log("Join result:", current[0]);
userCallback(err, op.result.value);
});
}
function buildOperationResult(transactionHandler, op, op_ndb_error, execMode) {
udebug.log("buildOperationResult");
/* Summarize Operation Error */
if(op.encoderError) {
udebug.log("Operation has encoder error");
op.result.success = false;
op.result.error = op.encoderError;
} else if(op_ndb_error === null) {
op.result.success = true;
} else {
op.result.success = false;
if(op_ndb_error !== true) { // TRUE here means NdbOperation is null
op.result.error = new DBOperationError().fromNdbError(op_ndb_error);
}
}
/* Handle Transaction Error */
if(execMode !== ROLLBACK) {
if(op.result.success) {
if(transactionHandler.error) {
/* This operation has no error, but the transaction failed. */
udebug.log("Case txErr + opOK", transactionHandler.moniker);
op.result.success = false;
op.result.error = new DBOperationError().cascading(transactionHandler.error);
}
}
else {
/* This operation has an error. */
if(transactionHandler.error) {
udebug.log("Case txErr + OpErr", transactionHandler.moniker);
}
else {
if(op.opcode === opcodes.OP_READ || execMode === NOCOMMIT) {
udebug.log("Case txOK + OpErr [READ | NOCOMMIT]", transactionHandler.moniker);
}
else {
udebug.log("Case txOK + OpErr", transactionHandler.moniker);
transactionHandler.error = new DBOperationError().cascading(op.result.error);
}
}
}
if(op.result.success && op.opcode === opcodes.OP_READ) {
op.result.value = getResultValue(op, op.tableHandler, op.buffers.row, op.blobs);
}
}
if(udebug.is_detail()) { udebug.log("buildOperationResult finished:", op.result); }
}
function completeExecutedOps(dbTxHandler, execMode, operations) {
/* operations is an object:
{
"operationList" : operationList,
"pendingOperationSet" : pendingOpsSet
};
*/
if(udebug.is_debug()) { udebug.log("completeExecutedOps mode:", execMode,
"operations: ", operations.operationList.length); }
var n, op, op_err;
for(n = 0 ; n < operations.operationList.length ; n++) {
op = operations.operationList[n];
if(! op.isScanOperation()) {
op_err = operations.pendingOperationSet.getOperationError(n);
releaseKeyBuffer(op);
op.blobs = operations.pendingOperationSet.readBlobResults(n);
buildOperationResult(dbTxHandler, op, op_err, execMode);
releaseRowBuffer(op);
}
dbTxHandler.executedOperations.push(op);
if(typeof op.userCallback === 'function') {
op.userCallback(op.result.error, op);
}
}
udebug.log("completeExecutedOps done");
}
storeNativeConstructorInMapping = function(dbTableHandler) {
var i, ncolumns, record, fieldNames, proto;
var VOC, DOC; // Value Object Constructor, Domain Object Constructor
record = dbTableHandler.resultRecord || storeResultRecord(dbTableHandler);
if(dbTableHandler.ValueObject || ! dbTableHandler.is1to1) {
return;
}
ncolumns = dbTableHandler.getNumberOfColumns();
fieldNames = {};
for(i = 0 ; i < ncolumns ; i++) {
fieldNames[i] = dbTableHandler.getColumnMapping(i).fieldNames[0];
}
/* The user's constructor and prototype */
DOC = dbTableHandler.newObjectConstructor;
proto = (DOC && DOC.prototype) ? DOC.prototype : null;
/* Get the Value Object Constructor
getValueObjectConstructor(record, fieldNames, prototype)
Store it in the TableHandler
*/
VOC = adapter.impl.getValueObjectConstructor(record, fieldNames, proto);
dbTableHandler.ValueObject = VOC;
};
function verifyIndexHandler(dbIndexHandler) {
if(! dbIndexHandler.tableHandler) { throw ("Invalid dbIndexHandler"); }
}
function newReadOperation(tx, dbIndexHandler, keys, lockMode, isLoad) {
verifyIndexHandler(dbIndexHandler);
var op = new DBOperation(opcodes.OP_READ, tx, dbIndexHandler, null);
op.keys = Array.isArray(keys) ? keys : dbIndexHandler.getColumns(keys);
if (isLoad === true && typeof keys === 'object') {
op.result.value = keys; // Reuse keys as result for session.load()
} else if(! dbIndexHandler.tableHandler.ValueObject) {
storeNativeConstructorInMapping(dbIndexHandler.tableHandler);
}
assert(doc.LockModes.indexOf(lockMode) !== -1);
if(op.index.isPrimaryKey || lockMode === "EXCLUSIVE") {
op.lockMode = lockMode;
}
else {
op.lockMode = "SHARED";
}
return op;
}
function newProjectionOperation(sessionImpl, tx, indexHandler, keys, projection) {
var op = new DBOperation(opcodes.OP_PROJ_READ, tx, indexHandler, null);
/* Encode keys for operation */
op.keys = Array.isArray(keys) ? keys : indexHandler.getColumns(keys);
allocateKeyBuffer(op);
encodeKeyBuffer(op);
/* Create Value Object Constructors for all tables */
projection.sectors.forEach(function(sector) {
storeNativeConstructorInMapping(sector.tableHandler);
});
/* Create NdbProjections from sectors, then create a QueryOperation */
op.query = NdbProjection.initialize(projection.sectors, indexHandler);
if(op.query.error) { /* TODO: Report this error back to the user
rather than attempting to execute the operation */
op.result.error = new DBOperationError(op.query.error);
op.result.success = false;
} else {
op.scanOp = adapter.impl.QueryOperation.create(op.query, op.buffers.key, op.query.size);
}
return op;
}
function newInsertOperation(tx, tableHandler, row) {
var op = new DBOperation(opcodes.OP_INSERT, tx, null, tableHandler);
// Test row for VO?
op.values = row;
if((op.tableHandler.autoIncFieldName) &&
(row[op.tableHandler.autoIncFieldName] === undefined)) {
// we need autoincrement services because the user did not supply the value for the autoincrement column
op.needAutoInc = true;
}
return op;
}
function newDeleteOperation(tx, dbIndexHandler, keys) {
verifyIndexHandler(dbIndexHandler);
var op = new DBOperation(opcodes.OP_DELETE, tx, dbIndexHandler, null);
op.keys = dbIndexHandler.getColumns(keys);
return op;
}
function newWriteOperation(tx, dbIndexHandler, row) {
verifyIndexHandler(dbIndexHandler);
var op = new DBOperation(opcodes.OP_WRITE, tx, dbIndexHandler, null);
// Test row for VO
op.keys = dbIndexHandler.getColumns(row);
op.values = row;
return op;
}
function newUpdateOperation(tx, dbIndexHandler, keys, row) {
verifyIndexHandler(dbIndexHandler);
var op = new DBOperation(opcodes.OP_UPDATE, tx, dbIndexHandler, null);
op.keys = dbIndexHandler.getColumns(keys);
op.values = row;
return op;
}
function newScanOperation(tx, QueryTree, properties) {
var queryHandler = QueryTree.jones_query_domain_type.queryHandler;
var op = new DBOperation(opcodes.OP_SCAN, tx,
queryHandler.dbIndexHandler,
queryHandler.dbTableHandler);
prepareFilterSpec(queryHandler); // sets query.ndbFilterSpec
op.query = queryHandler;
op.params = properties;
if(! queryHandler.dbTableHandler.ValueObject) {
storeNativeConstructorInMapping(queryHandler.dbTableHandler);
}
return op;
}
exports.DBOperation = DBOperation;
exports.DBOperationError = DBOperationError;
exports.newReadOperation = newReadOperation;
exports.newInsertOperation = newInsertOperation;
exports.newDeleteOperation = newDeleteOperation;
exports.newUpdateOperation = newUpdateOperation;
exports.newWriteOperation = newWriteOperation;
exports.newScanOperation = newScanOperation;
exports.newProjectionOperation = newProjectionOperation;
exports.completeExecutedOps = completeExecutedOps;
exports.getScanResults = getScanResults;
exports.prepareOperations = prepareOperations;
exports.getQueryResults = getQueryResults;