2262 lines
94 KiB
JavaScript
2262 lines
94 KiB
JavaScript
/*
|
|
Copyright (c) 2013, 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 stats = {
|
|
"TableHandlerFactory" : 0,
|
|
"TableHandler" : {
|
|
"success" : 0,
|
|
"idempotent" : 0,
|
|
"cache_hit" : 0
|
|
}
|
|
};
|
|
|
|
var util = require("util"),
|
|
mynode = require("./mynode.js"),
|
|
DBTableHandler = require(mynode.common.DBTableHandler).DBTableHandler,
|
|
apiSession = require("./Session.js"),
|
|
sessionFactory = require("./SessionFactory.js"),
|
|
query = require("./Query.js"),
|
|
spi = require(mynode.spi),
|
|
udebug = unified_debug.getLogger("UserContext.js"),
|
|
stats_module = require(mynode.api.stats);
|
|
|
|
stats_module.register(stats, "api", "UserContext");
|
|
|
|
function Promise() {
|
|
// implement Promises/A+ http://promises-aplus.github.io/promises-spec/
|
|
// until then is called, this is an empty promise with no performance impact
|
|
}
|
|
|
|
function emptyFulfilledCallback(result) {
|
|
return result;
|
|
}
|
|
function emptyRejectedCallback(err) {
|
|
throw err;
|
|
}
|
|
/** Fulfill or reject the original promise via "The Promise Resolution Procedure".
|
|
* original_promise is the Promise from this implementation on which "then" was called
|
|
* new_promise is the Promise from this implementation returned by "then"
|
|
* if the fulfilled or rejected callback provided by "then" returns a promise, wire the new_result (thenable)
|
|
* to fulfill the new_promise when new_result is fulfilled
|
|
* or reject the new_promise when new_result is rejected
|
|
* otherwise, if the callback provided by "then" returns a value, fulfill the new_promise with that value
|
|
* if the callback provided by "then" throws an Error, reject the new_promise with that Error
|
|
*/
|
|
var thenPromiseFulfilledOrRejected = function(original_promise, fulfilled_or_rejected_callback, new_promise, result, isRejected) {
|
|
var new_result;
|
|
try {
|
|
if(udebug.is_detail()) { udebug.log(original_promise.name, 'thenPromiseFulfilledOrRejected before'); }
|
|
if (fulfilled_or_rejected_callback) {
|
|
new_result = fulfilled_or_rejected_callback.call(undefined, result);
|
|
} else {
|
|
if (isRejected) {
|
|
// 2.2.7.4 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason.
|
|
new_promise.reject(result);
|
|
} else {
|
|
// 2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value.
|
|
new_promise.fulfill(result);
|
|
}
|
|
return;
|
|
}
|
|
if(udebug.is_detail()) { udebug.log(original_promise.name, 'thenPromiseFulfilledOrRejected after', new_result); }
|
|
var new_result_type = typeof new_result;
|
|
if ((new_result_type === 'object' && new_result_type != null) | new_result_type === 'function') {
|
|
// 2.3.3 if result is an object or function
|
|
// 2.3 The Promise Resolution Procedure
|
|
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
|
|
if (new_result === original_promise) {
|
|
throw new Error('TypeError: Promise Resolution Procedure 2.3.1');
|
|
}
|
|
// 2.3.2 If x is a promise, adopt its state; but we don't care since it's also a thenable
|
|
var then;
|
|
try {
|
|
then = new_result.then;
|
|
} catch (thenE) {
|
|
// 2.2.3.2 If retrieving the property x.then results in a thrown exception e,
|
|
// reject promise with e as the reason.
|
|
new_promise.reject(thenE);
|
|
return;
|
|
}
|
|
if (typeof then === 'function') {
|
|
// 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise,
|
|
// and second argument rejectPromise
|
|
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called,
|
|
// or multiple calls to the same argument are made, the first call takes precedence,
|
|
// and any further calls are ignored.
|
|
try {
|
|
then.call(new_result,
|
|
// 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
|
|
function(result) {
|
|
if(udebug.is_detail()) { udebug.log(original_promise.name, 'thenPromiseFulfilledOrRejected deferred fulfill callback', new_result); }
|
|
if (!new_promise.resolved) {
|
|
new_promise.fulfill(result);
|
|
}
|
|
},
|
|
// 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
|
|
function(err) {
|
|
if(udebug.is_detail()) { udebug.log(original_promise.name, 'thenPromiseFulfilledOrRejected deferred reject callback', new_result); }
|
|
if (!new_promise.resolved) {
|
|
new_promise.reject(err);
|
|
}
|
|
}
|
|
);
|
|
} catch (callE) {
|
|
// 2.3.3.3.4 If calling then throws an exception e,
|
|
// 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
|
|
if (!new_promise.resolved) {
|
|
// 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
|
|
new_promise.reject(callE);
|
|
}
|
|
}
|
|
} else {
|
|
// 2.3.3.4 If then is not a function, fulfill promise with x.
|
|
new_promise.fulfill(new_result);
|
|
}
|
|
} else {
|
|
// 2.3.4 If x is not an object or function, fulfill promise with x.
|
|
new_promise.fulfill(new_result);
|
|
}
|
|
} catch (fulfillE) {
|
|
// 2.2.7.2 If either onFulfilled or onRejected throws an exception e,
|
|
// promise2 must be rejected with e as the reason.
|
|
new_promise.reject(fulfillE);
|
|
}
|
|
|
|
};
|
|
|
|
Promise.prototype.then = function(fulfilled_callback, rejected_callback, progress_callback) {
|
|
var self = this;
|
|
if (!self) udebug.log('Promise.then called with no this');
|
|
// create a new promise to return from the "then" method
|
|
var new_promise = new Promise();
|
|
if (typeof self.fulfilled_callbacks === 'undefined') {
|
|
self.fulfilled_callbacks = [];
|
|
self.rejected_callbacks = [];
|
|
self.progress_callbacks = [];
|
|
}
|
|
if (self.resolved) {
|
|
var resolved_result;
|
|
if(udebug.is_detail()) { udebug.log(this.name, 'UserContext.Promise.then resolved; err:', self.err); }
|
|
if (self.err) {
|
|
// this promise was already rejected
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then resolved calling (delayed) rejected_callback', rejected_callback); }
|
|
global.setImmediate(function() {
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then resolved calling rejected_callback', fulfilled_callback); }
|
|
thenPromiseFulfilledOrRejected(self, rejected_callback, new_promise, self.err, true);
|
|
});
|
|
} else {
|
|
// this promise was already fulfilled, possibly with a null or undefined result
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then resolved calling (delayed) fulfilled_callback', fulfilled_callback); }
|
|
global.setImmediate(function() {
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then resolved calling fulfilled_callback', fulfilled_callback); }
|
|
thenPromiseFulfilledOrRejected(self, fulfilled_callback, new_promise, self.result);
|
|
});
|
|
}
|
|
return new_promise;
|
|
}
|
|
// create a closure for each fulfilled_callback
|
|
// the closure is a function that when called, calls setImmediate to call the fulfilled_callback with the result
|
|
if (typeof fulfilled_callback === 'function') {
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then with fulfilled_callback', fulfilled_callback); }
|
|
// the following function closes (this, fulfilled_callback, new_promise)
|
|
// and is called asynchronously when this promise is fulfilled
|
|
this.fulfilled_callbacks.push(function(result) {
|
|
global.setImmediate(function() {
|
|
thenPromiseFulfilledOrRejected(self, fulfilled_callback, new_promise, result);
|
|
});
|
|
});
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then with no fulfilled_callback'); }
|
|
// create a dummy function for a missing fulfilled callback per 2.2.7.3
|
|
// If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value.
|
|
this.fulfilled_callbacks.push(function(result) {
|
|
global.setImmediate(function() {
|
|
thenPromiseFulfilledOrRejected(self, emptyFulfilledCallback, new_promise, result);
|
|
});
|
|
});
|
|
}
|
|
|
|
// create a closure for each rejected_callback
|
|
// the closure is a function that when called, calls setImmediate to call the rejected_callback with the error
|
|
if (typeof rejected_callback === 'function') {
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then with rejected_callback', rejected_callback); }
|
|
this.rejected_callbacks.push(function(err) {
|
|
global.setImmediate(function() {
|
|
thenPromiseFulfilledOrRejected(self, rejected_callback, new_promise, err);
|
|
});
|
|
});
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log(self.name, 'UserContext.Promise.then with no rejected_callback'); }
|
|
// create a dummy function for a missing rejected callback per 2.2.7.4
|
|
// If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason.
|
|
this.rejected_callbacks.push(function(err) {
|
|
global.setImmediate(function() {
|
|
thenPromiseFulfilledOrRejected(self, emptyRejectedCallback, new_promise, err);
|
|
});
|
|
});
|
|
}
|
|
// todo: progress_callbacks
|
|
if (typeof progress_callback === 'function') {
|
|
this.progress_callbacks.push(progress_callback);
|
|
}
|
|
|
|
return new_promise;
|
|
};
|
|
|
|
Promise.prototype.fulfill = function(result) {
|
|
var name = this?this.name: 'no this';
|
|
if (udebug.is_detail()) {
|
|
udebug.log_detail(new Error(name, 'Promise.fulfill').stack);
|
|
}
|
|
if (this.resolved) {
|
|
throw new Error('Fatal User Exception: fulfill called after fulfill or reject');
|
|
}
|
|
if(udebug.is_detail()) {
|
|
udebug.log(name, 'Promise.fulfill with result', result, 'fulfilled_callbacks length:',
|
|
this.fulfilled_callbacks? this.fulfilled_callbacks.length: 0);
|
|
}
|
|
this.resolved = true;
|
|
this.result = result;
|
|
var fulfilled_callback;
|
|
if (this.fulfilled_callbacks) {
|
|
while(this.fulfilled_callbacks.length > 0) {
|
|
fulfilled_callback = this.fulfilled_callbacks.shift();
|
|
if(udebug.is_detail()) { udebug.log('Promise.fulfill for', result); }
|
|
fulfilled_callback(result);
|
|
}
|
|
}
|
|
};
|
|
|
|
Promise.prototype.reject = function(err) {
|
|
if (this.resolved) {
|
|
throw new Error('Fatal User Exception: reject called after fulfill or reject');
|
|
}
|
|
var name = this?this.name: 'no this';
|
|
if(udebug.is_detail()) {
|
|
udebug.log(name, 'Promise.reject with err', err, 'rejected_callbacks length:',
|
|
this.rejected_callbacks? this.rejected_callbacks.length: 0);
|
|
}
|
|
this.resolved = true;
|
|
this.err = err;
|
|
var rejected_callback;
|
|
if (this.rejected_callbacks) {
|
|
while(this.rejected_callbacks.length > 0) {
|
|
rejected_callback = this.rejected_callbacks.shift();
|
|
if(udebug.is_detail()) { udebug.log('Promise.reject for', err); }
|
|
rejected_callback(err);
|
|
}
|
|
}
|
|
// throw err;
|
|
};
|
|
|
|
/** Create a function to manage the context of a user's asynchronous call.
|
|
* All asynchronous user functions make a callback passing
|
|
* the user's extra parameters from the original call as extra parameters
|
|
* beyond the specified parameters of the call. For example, the persist function
|
|
* is specified to take two parameters: the data object itself and the callback.
|
|
* The result of persist is to call back with parameters of an error object,
|
|
* and the same data object which was passed.
|
|
* If extra parameters are passed to the persist function, the user's function
|
|
* will be called with the specified parameters plus all extra parameters from
|
|
* the original call.
|
|
* The constructor remembers the original user callback function and the original
|
|
* parameters of the function.
|
|
* The user callback function is always the last required parameter of the function call.
|
|
* Additional context is added as the function progresses.
|
|
* @param user_arguments the original arguments as supplied by the user
|
|
* @param required_parameter_count the number of required parameters
|
|
* NOTE: the user callback function must be the last of the required parameters
|
|
* @param returned_parameter_count the number of required parameters returned to the callback
|
|
* @param session the Session which may be null for SessionFactory functions
|
|
* @param session_factory the SessionFactory which may be null for Session functions
|
|
* @param execute (optional; defaults to true) whether to execute the operation immediately;
|
|
* if execute is false, the operation is constructed and is available via the "operation"
|
|
* property of the user context.
|
|
*/
|
|
exports.UserContext = function(user_arguments, required_parameter_count, returned_parameter_count,
|
|
session, session_factory, execute) {
|
|
this.execute = (typeof execute === 'boolean' ? execute : true);
|
|
this.user_arguments = user_arguments;
|
|
this.user_callback = user_arguments[required_parameter_count - 1];
|
|
this.required_parameter_count = required_parameter_count;
|
|
this.extra_arguments_count = user_arguments.length - required_parameter_count;
|
|
this.returned_parameter_count = returned_parameter_count;
|
|
this.session = session;
|
|
this.session_factory = session_factory;
|
|
/* indicates that batch.clear was called before this context had executed */
|
|
this.clear = false;
|
|
if (this.session) {
|
|
this.autocommit = ! this.session.tx.isActive();
|
|
}
|
|
this.errorMessages = '';
|
|
this.promise = new Promise();
|
|
};
|
|
|
|
exports.UserContext.prototype.appendErrorMessage = function(message) {
|
|
this.errorMessages += '\n' + message;
|
|
};
|
|
|
|
/** Get table metadata.
|
|
* Delegate to DBConnectionPool.getTableMetadata.
|
|
*/
|
|
exports.UserContext.prototype.getTableMetadata = function() {
|
|
var userContext = this;
|
|
var err, databaseName, tableName, dbSession;
|
|
var getTableMetadataOnTableMetadata = function(metadataErr, tableMetadata) {
|
|
udebug.log('UserContext.getTableMetadata.getTableMetadataOnTableMetadata with err', metadataErr);
|
|
userContext.applyCallback(metadataErr, tableMetadata);
|
|
};
|
|
databaseName = userContext.user_arguments[0];
|
|
tableName = userContext.user_arguments[1];
|
|
dbSession = (userContext.session)?userContext.session.dbSession:null;
|
|
if (typeof databaseName !== 'string' || typeof tableName !== 'string') {
|
|
err = new Error('getTableMetadata(databaseName, tableName) illegal argument types (' +
|
|
typeof databaseName + ', ' + typeof tableName + ')');
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
this.session_factory.dbConnectionPool.getTableMetadata(
|
|
databaseName, tableName, dbSession, getTableMetadataOnTableMetadata);
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** List all tables in the default database.
|
|
* Delegate to DBConnectionPool.listTables.
|
|
*/
|
|
exports.UserContext.prototype.listTables = function() {
|
|
var userContext = this;
|
|
var listTablesOnTableList = function(err, tableList) {
|
|
userContext.applyCallback(err, tableList);
|
|
};
|
|
|
|
var databaseName = this.user_arguments[0];
|
|
var dbSession = (this.session)?this.session.dbSession:null;
|
|
this.session_factory.dbConnectionPool.listTables(databaseName, dbSession, listTablesOnTableList);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Create schema from a table mapping.
|
|
* promise = createTable(tableMapping, callback);
|
|
*/
|
|
exports.UserContext.prototype.createTable = function() {
|
|
var userContext = this;
|
|
var tableMapping, dbSession;
|
|
function createTableOnTableCreated(err) {
|
|
userContext.applyCallback(err);
|
|
}
|
|
|
|
// createTable starts here
|
|
tableMapping = userContext.user_arguments[0];
|
|
userContext.session_factory.dbConnectionPool.createTable(tableMapping, userContext.session, userContext.session_factory,
|
|
createTableOnTableCreated);
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Resolve properties. Properties might be an object, a name, or null.
|
|
* If null, use all default properties. If a name, use default properties
|
|
* of the named service provider. Otherwise, return the properties object.
|
|
*/
|
|
var resolveProperties = function(properties) {
|
|
// Properties can be a string adapter name. It defaults to 'ndb'.
|
|
if(typeof properties === 'string') {
|
|
properties = spi.getDBServiceProvider(properties).getDefaultConnectionProperties();
|
|
}
|
|
else if (properties === null) {
|
|
properties = spi.getDBServiceProvider('ndb').getDefaultConnectionProperties();
|
|
}
|
|
return properties;
|
|
};
|
|
|
|
function getTableSpecification(defaultDatabaseName, tableName) {
|
|
var split = tableName.split('\.');
|
|
var result = {};
|
|
if (split.length == 2) {
|
|
result.dbName = split[0];
|
|
result.unqualifiedTableName = split[1];
|
|
result.qualifiedTableName = tableName;
|
|
} else {
|
|
// if split.length is not 1 then this error will be caught later
|
|
result.dbName = defaultDatabaseName;
|
|
result.unqualifiedTableName = tableName;
|
|
result.qualifiedTableName = defaultDatabaseName + '.' + tableName;
|
|
}
|
|
udebug.log_detail('getTableSpecification for', defaultDatabaseName, ',', tableName, 'returned', result);
|
|
return result;
|
|
}
|
|
/** Construct the table name from possibly empty database name and table name.
|
|
*/
|
|
function constructDatabaseDotTable(databaseName, tableName) {
|
|
var result = databaseName ? databaseName + '.' + tableName : tableName;
|
|
return result;
|
|
}
|
|
|
|
/** Get the table handler for a domain object, table name, or constructor.
|
|
*/
|
|
var getTableHandler = function(domainObjectTableNameOrConstructor, session, onTableHandler) {
|
|
|
|
// the table name might be qualified if the mapping specified a qualified table name
|
|
// if unqualified, use sessionFactory.properties.database to qualify the table name
|
|
var TableHandlerFactory = function(mynode, tableSpecification,
|
|
sessionFactory, dbSession, mapping, ctor, onTableHandler) {
|
|
this.sessionFactory = sessionFactory;
|
|
this.dbSession = dbSession;
|
|
this.onTableHandler = onTableHandler;
|
|
this.mapping = mapping;
|
|
this.mynode = mynode;
|
|
this.ctor = ctor;
|
|
this.tableSpecification = tableSpecification;
|
|
stats.TableHandlerFactory++;
|
|
|
|
this.createTableHandler = function() {
|
|
var tableHandlerFactory = this;
|
|
var tableHandler;
|
|
var tableMetadata;
|
|
var tableMapping;
|
|
|
|
var onTableMetadata = function(err, tableMetadata) {
|
|
var tableHandler;
|
|
var tableKey = tableHandlerFactory.tableSpecification.qualifiedTableName;
|
|
if(udebug.is_detail()) {
|
|
udebug.log('TableHandlerFactory.onTableMetadata for ',
|
|
tableHandlerFactory.tableSpecification.qualifiedTableName + ' with err: ' + err);
|
|
}
|
|
if (err) {
|
|
tableHandlerFactory.onTableHandler(err, null);
|
|
} else {
|
|
// check to see if the metadata has already been processed
|
|
if (typeof tableHandlerFactory.sessionFactory.tableMetadatas[tableKey] === 'undefined') {
|
|
// put the table metadata into the table metadata map
|
|
tableHandlerFactory.sessionFactory.tableMetadatas[tableKey] = tableMetadata;
|
|
}
|
|
// we have the table metadata; now create the table handler if needed
|
|
// put the table handler into the session factory
|
|
if (typeof(tableHandlerFactory.sessionFactory.tableHandlers[tableKey]) === 'undefined') {
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext caching the table handler in the sessionFactory for ',
|
|
tableHandlerFactory.tableName);
|
|
}
|
|
tableHandler = new DBTableHandler(tableMetadata, tableHandlerFactory.mapping,
|
|
tableHandlerFactory.ctor);
|
|
tableHandlerFactory.sessionFactory.tableHandlers[tableKey] = tableHandler;
|
|
} else {
|
|
tableHandler = tableHandlerFactory.sessionFactory.tableHandlers[tableKey];
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext got tableHandler but someone else put it in the cache first for ',
|
|
tableHandlerFactory.tableName);
|
|
}
|
|
}
|
|
if (tableHandlerFactory.ctor) {
|
|
if (typeof(tableHandlerFactory.ctor.prototype.mynode.tableHandler) === 'undefined') {
|
|
// if a domain object mapping, cache the table handler in the prototype
|
|
stats.TableHandler.success++;
|
|
tableHandler = new DBTableHandler(tableMetadata, tableHandlerFactory.mapping,
|
|
tableHandlerFactory.ctor);
|
|
if (tableHandler.isValid) {
|
|
tableHandlerFactory.ctor.prototype.mynode.tableHandler = tableHandler;
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext caching the table handler in the prototype for constructor.');
|
|
}
|
|
} else {
|
|
tableHandlerFactory.err = tableHandler.err;
|
|
if(udebug.is_detail()) { udebug.log('UserContext got invalid tableHandler', tableHandler.errorMessages); }
|
|
}
|
|
} else {
|
|
tableHandler = tableHandlerFactory.ctor.prototype.mynode.tableHandler;
|
|
stats.TableHandler.idempotent++;
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext got tableHandler but someone else put it in the prototype first.');
|
|
}
|
|
}
|
|
}
|
|
tableHandlerFactory.onTableHandler(tableHandlerFactory.err, tableHandler);
|
|
}
|
|
};
|
|
|
|
function tableHandlerFactoryOnCreateTable(err) {
|
|
if (err) {
|
|
onTableMetadata(err, null);
|
|
} else {
|
|
sessionFactory.dbConnectionPool.getTableMetadata(tableHandlerFactory.tableSpecification.dbName,
|
|
tableHandlerFactory.tableSpecification.unqualifiedTableName, session.dbSession, onTableMetadata);
|
|
}
|
|
}
|
|
|
|
// start of createTableHandler
|
|
|
|
// get the table metadata from the cache of table metadatas in session factory
|
|
tableMetadata =
|
|
tableHandlerFactory.sessionFactory.tableMetadatas[tableHandlerFactory.tableSpecification.qualifiedTableName];
|
|
if (tableMetadata) {
|
|
// we already have cached the table metadata
|
|
onTableMetadata(null, tableMetadata);
|
|
} else {
|
|
// create the schema if it does not already exist
|
|
tableMapping = sessionFactory.tableMappings[tableSpecification.qualifiedTableName];
|
|
if (tableMapping) {
|
|
udebug.log('tableHandlerFactory.createTableHandler creating schema using tableMapping: ', tableMapping);
|
|
sessionFactory.createTable(tableMapping, tableHandlerFactoryOnCreateTable);
|
|
return;
|
|
}
|
|
// get the table metadata from the db connection pool
|
|
// getTableMetadata(dbSession, databaseName, tableName, callback(error, DBTable));
|
|
udebug.log('TableHandlerFactory.createTableHandler for ',
|
|
tableHandlerFactory.tableSpecification.dbName,
|
|
tableHandlerFactory.tableSpecification.unqualifiedTableName);
|
|
this.sessionFactory.dbConnectionPool.getTableMetadata(
|
|
tableHandlerFactory.tableSpecification.dbName,
|
|
tableHandlerFactory.tableSpecification.unqualifiedTableName, session.dbSession, onTableMetadata);
|
|
}
|
|
};
|
|
};
|
|
|
|
// start of getTableHandler
|
|
var err, mynode, tableHandler, tableMapping, tableHandlerFactory, tableIndicatorType, tableSpecification, databaseDotTable;
|
|
|
|
tableIndicatorType = typeof(domainObjectTableNameOrConstructor);
|
|
if (tableIndicatorType === 'string') {
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext.getTableHandler for table ', domainObjectTableNameOrConstructor);
|
|
}
|
|
tableSpecification = getTableSpecification(session.sessionFactory.properties.database,
|
|
domainObjectTableNameOrConstructor);
|
|
|
|
// parameter is a table name; look up in table name to table handler hash
|
|
tableHandler = session.sessionFactory.tableHandlers[tableSpecification.qualifiedTableName];
|
|
if (typeof(tableHandler) === 'undefined') {
|
|
udebug.log('UserContext.getTableHandler did not find cached tableHandler for table ',
|
|
tableSpecification.qualifiedTableName);
|
|
// get a table mapping from session factory
|
|
tableMapping = session.sessionFactory.tableMappings[tableSpecification.qualifiedTableName];
|
|
// create a new table handler for a table name with no mapping
|
|
// create a closure to create the table handler
|
|
tableHandlerFactory = new TableHandlerFactory(
|
|
null, tableSpecification, session.sessionFactory, session.dbSession,
|
|
tableMapping, null, onTableHandler);
|
|
tableHandlerFactory.createTableHandler(null);
|
|
} else {
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext.getTableHandler found cached tableHandler for table ',
|
|
tableSpecification.qualifiedTableName);
|
|
}
|
|
// send back the tableHandler
|
|
onTableHandler(null, tableHandler);
|
|
}
|
|
} else if (tableIndicatorType === 'function') {
|
|
if(udebug.is_detail()) { udebug.log('UserContext.getTableHandler for constructor.'); }
|
|
mynode = domainObjectTableNameOrConstructor.prototype.mynode;
|
|
// parameter is a constructor; it must have been annotated already
|
|
if (typeof(mynode) === 'undefined') {
|
|
err = new Error('User exception: constructor for ' + domainObjectTableNameOrConstructor.prototype.constructor.name +
|
|
' must have been annotated (call TableMapping.applyToClass).');
|
|
onTableHandler(err, null);
|
|
} else {
|
|
tableHandler = mynode.tableHandler;
|
|
if (typeof(tableHandler) === 'undefined') {
|
|
udebug.log('UserContext.getTableHandler did not find cached tableHandler for constructor.',
|
|
domainObjectTableNameOrConstructor);
|
|
// create the tableHandler
|
|
// getTableMetadata(dbSession, databaseName, tableName, callback(error, DBTable));
|
|
databaseDotTable = constructDatabaseDotTable(mynode.mapping.database, mynode.mapping.table);
|
|
tableSpecification = getTableSpecification(session.sessionFactory.properties.database, databaseDotTable);
|
|
tableHandlerFactory = new TableHandlerFactory(
|
|
mynode, tableSpecification, session.sessionFactory, session.dbSession,
|
|
mynode.mapping, domainObjectTableNameOrConstructor, onTableHandler);
|
|
tableHandlerFactory.createTableHandler();
|
|
} else {
|
|
stats.TableHandler.cache_hit++;
|
|
if(udebug.is_detail()) { udebug.log('UserContext.getTableHandler found cached tableHandler for constructor.'); }
|
|
// prototype has been annotated; return the table handler
|
|
onTableHandler(null, tableHandler);
|
|
}
|
|
}
|
|
} else if (tableIndicatorType === 'object') {
|
|
if(udebug.is_detail()) { udebug.log('UserContext.getTableHandler for domain object.'); }
|
|
// parameter is a domain object; it must have been mapped already
|
|
mynode = domainObjectTableNameOrConstructor.constructor.prototype.mynode;
|
|
if (typeof(mynode) === 'undefined') {
|
|
err = new Error('User exception: constructor for ' + domainObjectTableNameOrConstructor.constructor.name +
|
|
' must have been annotated (call TableMapping.applyToClass).');
|
|
onTableHandler(err, null);
|
|
} else {
|
|
tableHandler = mynode.tableHandler;
|
|
if (typeof(tableHandler) === 'undefined') {
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext.getTableHandler did not find cached tableHandler for object\n',
|
|
util.inspect(domainObjectTableNameOrConstructor),
|
|
'constructor\n', domainObjectTableNameOrConstructor.constructor);
|
|
}
|
|
databaseDotTable = constructDatabaseDotTable(mynode.mapping.database, mynode.mapping.table);
|
|
tableSpecification = getTableSpecification(session.sessionFactory.properties.database, databaseDotTable);
|
|
// create the tableHandler
|
|
// getTableMetadata(dbSession, databaseName, tableName, callback(error, DBTable));
|
|
tableHandlerFactory = new TableHandlerFactory(
|
|
mynode, tableSpecification, session.sessionFactory, session.dbSession,
|
|
mynode.mapping, domainObjectTableNameOrConstructor.constructor, onTableHandler);
|
|
tableHandlerFactory.createTableHandler();
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log('UserContext.getTableHandler found cached tableHandler for constructor.'); }
|
|
// prototype has been annotated; return the table handler
|
|
onTableHandler(null, tableHandler);
|
|
}
|
|
}
|
|
} else {
|
|
err = new Error('User error: parameter must be a domain object, string, or constructor function.');
|
|
onTableHandler(err, null);
|
|
}
|
|
};
|
|
|
|
/** Try to find an existing session factory by looking up the connection string
|
|
* and database name. Failing that, create a db connection pool and create a session factory.
|
|
* Multiple session factories share the same db connection pool.
|
|
* This function is used by both connect and openSession.
|
|
*/
|
|
var getSessionFactory = function(userContext, properties, tableMappings, callback) {
|
|
var database;
|
|
var connectionKey;
|
|
var connection;
|
|
var factory;
|
|
var newSession;
|
|
var sp;
|
|
var i;
|
|
var m;
|
|
|
|
var resolveTableMappingsOnSession = function(err, session) {
|
|
var mappings = [];
|
|
var mappingBeingResolved = 0;
|
|
|
|
var resolveTableMappingsOnTableHandler = function(err, tableHandler) {
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext.resolveTableMappinsgOnTableHandler', mappingBeingResolved + 1,
|
|
'of', mappings.length, mappings[mappingBeingResolved]);
|
|
}
|
|
if (err) {
|
|
userContext.appendErrorMessage(err);
|
|
}
|
|
if (++mappingBeingResolved === mappings.length || mappingBeingResolved > 10) {
|
|
// close the session the hard way (not using UserContext)
|
|
session.dbSession.close(function(err) {
|
|
if (err) {
|
|
callback(err, null);
|
|
} else {
|
|
// now remove the session from the session factory's open connections
|
|
session.sessionFactory.closeSession(session.index);
|
|
// mark this session as unusable
|
|
session.closed = true;
|
|
// if any errors during table mapping, report them
|
|
if (userContext.errorMessages) {
|
|
err = new Error(userContext.errorMessages);
|
|
callback(err, null);
|
|
} else {
|
|
// no errors
|
|
callback(null, factory);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// get the table handler for the next one, and so on until all are done
|
|
getTableHandler(mappings[mappingBeingResolved], session, resolveTableMappingsOnTableHandler);
|
|
}
|
|
};
|
|
|
|
// resolveTableMappingsOnSession begins here
|
|
|
|
var tableMappingsType = typeof(tableMappings);
|
|
var tableMapping;
|
|
var tableMappingType;
|
|
switch (tableMappingsType) {
|
|
case 'string':
|
|
mappings.push(tableMappings);
|
|
break;
|
|
case 'function':
|
|
mappings.push(tableMappings);
|
|
break;
|
|
case 'object':
|
|
if (tableMappings.length) {
|
|
for (m = 0; m < tableMappings.length; ++m) {
|
|
tableMapping = tableMappings[m];
|
|
tableMappingType = typeof(tableMapping);
|
|
if (tableMappingType === 'function' || tableMappingType === 'string') {
|
|
mappings.push(tableMapping);
|
|
} else {
|
|
userContext.appendErrorMessage('unknown table mapping' + util.inspect(tableMapping));
|
|
}
|
|
}
|
|
} else {
|
|
userContext.appendErrorMessage('unknown table mappings' + util.inspect(tableMappings));
|
|
}
|
|
break;
|
|
default:
|
|
userContext.appendErrorMessage('unknown table mappings' + util.inspect(tableMappings));
|
|
break;
|
|
}
|
|
if (mappings.length === 0) {
|
|
if(udebug.is_detail()) { udebug.log('resolveTableMappingsOnSession no mappings!'); }
|
|
callback(null, factory);
|
|
}
|
|
// get table handler for the first; the callback will then do the next one...
|
|
if(udebug.is_detail()) { udebug.log('getSessionFactory resolving mappings:', mappings); }
|
|
getTableHandler(mappings[0], session, resolveTableMappingsOnTableHandler);
|
|
};
|
|
|
|
var resolveTableMappingsAndCallback = function() {
|
|
if (!tableMappings) {
|
|
callback(null, factory);
|
|
} else {
|
|
// get a session the hard way (not using UserContext) to resolve mappings
|
|
var sessionSlot = factory.allocateSessionSlot();
|
|
factory.dbConnectionPool.getDBSession(userContext.session_index, function(err, dbSession) {
|
|
if (err) {
|
|
// report error
|
|
userContext.appendErrorMessage(err);
|
|
err = new Error(userContext.errorMessages);
|
|
callback(err, null);
|
|
} else {
|
|
var newSession = new apiSession.Session(sessionSlot, factory, dbSession);
|
|
factory.sessions[sessionSlot] = newSession;
|
|
resolveTableMappingsOnSession(err, newSession);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
var createFactory = function(dbConnectionPool) {
|
|
var newFactory;
|
|
udebug.log('connect createFactory creating factory for', connectionKey, 'database', database);
|
|
newFactory = new sessionFactory.SessionFactory(connectionKey, dbConnectionPool,
|
|
properties, tableMappings, mynode.deleteFactory);
|
|
return newFactory;
|
|
};
|
|
|
|
var dbConnectionPoolCreated_callback = function(error, dbConnectionPool) {
|
|
if (connection.isConnecting) {
|
|
// the first requester for this connection
|
|
connection.isConnecting = false;
|
|
if (error) {
|
|
callback(error, null);
|
|
} else {
|
|
udebug.log('dbConnectionPool created for', connectionKey, 'database', database);
|
|
connection.dbConnectionPool = dbConnectionPool;
|
|
factory = createFactory(dbConnectionPool);
|
|
connection.factories[database] = factory;
|
|
connection.count++;
|
|
resolveTableMappingsAndCallback();
|
|
}
|
|
// notify all others that the connection is now ready (or an error was signaled)
|
|
for (i = 0; i < connection.waitingForConnection.length; ++i) {
|
|
if(udebug.is_detail()) { udebug.log('dbConnectionPoolCreated_callback notifying...'); }
|
|
udebug.log('dbConnectionPoolCreated', error, dbConnectionPool);
|
|
connection.waitingForConnection[i](error, dbConnectionPool);
|
|
}
|
|
} else {
|
|
// another user request created the dbConnectionPool and session factory
|
|
if (error) {
|
|
callback(error, null);
|
|
} else {
|
|
udebug.log('dbConnectionPoolCreated_callback', database, connection.factories);
|
|
factory = connection.factories[database];
|
|
if (!factory) {
|
|
factory = createFactory(dbConnectionPool);
|
|
connection.factories[database] = factory;
|
|
connection.count++;
|
|
}
|
|
resolveTableMappingsAndCallback();
|
|
}
|
|
}
|
|
};
|
|
|
|
// getSessionFactory starts here
|
|
database = properties.database;
|
|
connectionKey = mynode.getConnectionKey(properties);
|
|
connection = mynode.getConnection(connectionKey);
|
|
|
|
if(typeof(connection) === 'undefined') {
|
|
// there is no connection yet using this connection key
|
|
udebug.log('connect connection does not exist; creating factory for',
|
|
connectionKey, 'database', database);
|
|
connection = mynode.newConnection(connectionKey);
|
|
sp = spi.getDBServiceProvider(properties.implementation);
|
|
sp.connect(properties, dbConnectionPoolCreated_callback);
|
|
} else {
|
|
// there is a connection, but is it already connected?
|
|
if (connection.isConnecting) {
|
|
// wait until the first requester for this connection completes
|
|
udebug.log('connect waiting for db connection by another for', connectionKey, 'database', database);
|
|
connection.waitingForConnection.push(dbConnectionPoolCreated_callback);
|
|
} else {
|
|
// there is a connection, but is there a SessionFactory for this database?
|
|
factory = connection.factories[database];
|
|
if (typeof(factory) === 'undefined') {
|
|
// create a SessionFactory for the existing dbConnectionPool
|
|
udebug.log('connect creating factory with existing', connectionKey, 'database', database);
|
|
factory = createFactory(connection.dbConnectionPool);
|
|
connection.factories[database] = factory;
|
|
connection.count++;
|
|
}
|
|
// resolve all table mappings before returning
|
|
resolveTableMappingsAndCallback();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
exports.UserContext.prototype.connect = function() {
|
|
var userContext = this;
|
|
// properties might be null, a name, or a properties object
|
|
this.user_arguments[0] = resolveProperties(this.user_arguments[0]);
|
|
|
|
var connectOnSessionFactory = function(err, factory) {
|
|
userContext.applyCallback(err, factory);
|
|
};
|
|
|
|
getSessionFactory(this, this.user_arguments[0], this.user_arguments[1], connectOnSessionFactory);
|
|
return userContext.promise;
|
|
};
|
|
|
|
function checkOperation(err, dbOperation) {
|
|
var sqlstate, message, result, result_code;
|
|
result = null;
|
|
result_code = null;
|
|
message = 'Unknown Error';
|
|
sqlstate = '22000';
|
|
if (err) {
|
|
udebug.log('checkOperation returning existing err:', err);
|
|
return err;
|
|
}
|
|
if (dbOperation.result.success !== true) {
|
|
if(dbOperation.result.error) {
|
|
sqlstate = dbOperation.result.error.sqlstate;
|
|
message = dbOperation.result.error.message || 'Operation error';
|
|
result_code = dbOperation.result.error.code;
|
|
}
|
|
result = new Error(message);
|
|
result.code = result_code;
|
|
result.sqlstate = sqlstate;
|
|
udebug.log('checkOperation returning new err:', result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Create a sector object for a domain object in a projection.
|
|
* The topmost outer loop projection's sectors are all created, creating one sector
|
|
* for each inner loop projection, then the outer projection for the next level down.
|
|
* For each inner loop projection, a sector is constructed
|
|
* and then the sector for the included relationship is constructed by recursion.
|
|
* The sector contains a list of primary key fields and a list of non-primary key fields,
|
|
* and if this is not the root sector, the name of the field and the field in the previous sector.
|
|
* The fields are references to the field objects in DBTableHandler and contain names, types,
|
|
* and converters.
|
|
* This function is synchronous. When complete, this function returns to the caller.
|
|
* @param inout parameter: outerLoopProjection the projection at the outer loop
|
|
* which is modified by this function to add the sectors field
|
|
* @param inout parameter: innerLoopProjection the projection at the inner loop
|
|
* which is modified by this function to add the sectors field
|
|
* @param sectors the outer loop projection.sectors which will grow as createSector is called recursively
|
|
* @param index the index into sectors for the sector being constructed
|
|
* @param offset the number of fields in all sectors already processed
|
|
*/
|
|
function createSector(outerLoopProjection, innerLoopProjection, sectors, index, offset) {
|
|
udebug.log('createSector ' + outerLoopProjection.name + ' for ' + outerLoopProjection.domainObject.name +
|
|
' inner: ' + innerLoopProjection.name + ' for ' + innerLoopProjection.domainObject.name +
|
|
' index: ' + index + ' offset: ' + offset);
|
|
var projection = innerLoopProjection;
|
|
var innerNestedProjection, outerNestedProjection;
|
|
var sector = {};
|
|
var tableHandler, relationships;
|
|
var keyFieldCount, nonKeyFieldCount;
|
|
var fieldNames, field;
|
|
var indexHandler;
|
|
var relationshipName;
|
|
var relatedFieldMapping, relatedSector, relatedTableHandler, relatedTargetFieldName, relatedTargetField;
|
|
var thisFieldMapping;
|
|
var joinTable, joinTableHandler;
|
|
var foreignKeys, foreignKey, fkIndex, foreignKeyName;
|
|
var i, fkFound;
|
|
|
|
// initialize sector
|
|
sector.keyFields = [];
|
|
sector.nonKeyFields = [];
|
|
sector.keyFieldNames = [];
|
|
sector.projection = projection;
|
|
sector.offset = offset;
|
|
tableHandler = projection.domainObject.prototype.mynode.tableHandler;
|
|
udebug.log_detail('createSector for table handler', tableHandler.dbTable.name);
|
|
sector.tableHandler = tableHandler;
|
|
|
|
// relatedFieldMapping is the field mapping for the sector to the "left" of this sector
|
|
// it contains the field in the "left" sector and mapping information including join columns
|
|
relatedFieldMapping = projection.relatedFieldMapping;
|
|
sector.relatedFieldMapping = relatedFieldMapping;
|
|
udebug.log_detail('createSector thisDBTable name', tableHandler.dbTable.name, index, sectors);
|
|
if (relatedFieldMapping && index !== 0) {
|
|
// only perform related field mapping for nested projections
|
|
relatedSector = sectors[index - 1];
|
|
relatedTableHandler = relatedSector.tableHandler;
|
|
sector.relatedTableHandler = relatedTableHandler;
|
|
// get this optional field mapping that corresponds to the related field mapping
|
|
// it may be needed to find the foreign key or join table
|
|
relatedTargetFieldName = relatedFieldMapping.targetField;
|
|
relatedTargetField = sector.tableHandler.fieldNameToFieldMap[relatedTargetFieldName];
|
|
if (relatedFieldMapping.toMany && relatedFieldMapping.manyTo) {
|
|
// this is a many-to-many relationship using a join table
|
|
joinTable = relatedFieldMapping.joinTable;
|
|
// joinTableHandler is the DBTableHandler for the join table resolved during validateProjection
|
|
if (joinTable) {
|
|
// join table is defined on the related side
|
|
joinTableHandler = relatedFieldMapping.joinTableHandler;
|
|
} else {
|
|
// join table must be defined on this side
|
|
thisFieldMapping = tableHandler.fieldNameToFieldMap[relatedFieldMapping.targetField];
|
|
joinTable = thisFieldMapping.joinTable;
|
|
if (!joinTable) {
|
|
// error; neither side defined the join table
|
|
projection.error += '\nMappingError: ' + relatedTableHandler.newObjectConstructor.name +
|
|
' field ' + relatedFieldMapping.fieldName + ' neither side defined the join table.';
|
|
}
|
|
joinTableHandler = thisFieldMapping.joinTableHandler;
|
|
}
|
|
sector.joinTableHandler = joinTableHandler;
|
|
// many to many relationship has a join table with at least two foreign keys;
|
|
// one to each table mapped to the two domain objects
|
|
if (joinTable) {
|
|
for (foreignKeyName in joinTableHandler.foreignKeyMap) {
|
|
if (joinTableHandler.foreignKeyMap.hasOwnProperty(foreignKeyName)) {
|
|
foreignKey = joinTableHandler.foreignKeyMap[foreignKeyName];
|
|
// is this foreign key for this table?
|
|
if (foreignKey.targetDatabase === tableHandler.dbTable.database &&
|
|
foreignKey.targetTable === tableHandler.dbTable.name) {
|
|
// this foreign key is for the other table
|
|
relatedFieldMapping.otherForeignKey = foreignKey;
|
|
}
|
|
if (foreignKey.targetDatabase === relatedTableHandler.dbTable.database &&
|
|
foreignKey.targetTable === relatedTableHandler.dbTable.name) {
|
|
relatedFieldMapping.thisForeignKey = foreignKey;
|
|
}
|
|
}
|
|
}
|
|
if (!(relatedFieldMapping.thisForeignKey && relatedFieldMapping.otherForeignKey)) {
|
|
// error must have foreign keys to both this table and related table
|
|
projection.error += '\nMappingError: ' + relatedTableHandler.newObjectConstructor.name +
|
|
' field ' + relatedFieldMapping.fieldName + ' join table must include foreign keys for both sides.';
|
|
}
|
|
}
|
|
} else {
|
|
// this is a relationship using a foreign key
|
|
// resolve the columns involved in the join to the related field
|
|
// there is either a foreign key or a target field that has a foreign key
|
|
// the related field mapping is the field mapping on the other side
|
|
// the field mapping on this side is not used in this projection
|
|
foreignKeyName = relatedFieldMapping.foreignKey;
|
|
if (foreignKeyName) {
|
|
// foreign key is defined on the other side
|
|
foreignKey = relatedTableHandler.getForeignKey(foreignKeyName);
|
|
sector.thisJoinColumns = foreignKey.targetColumnNames;
|
|
sector.otherJoinColumns = foreignKey.columnNames;
|
|
} else {
|
|
// foreign key is defined on this side
|
|
// get the fieldMapping for this relationship field
|
|
relatedTargetField = sector.tableHandler.fieldNameToFieldMap[relatedTargetFieldName];
|
|
foreignKeyName = relatedTargetField.foreignKey;
|
|
if (foreignKeyName) {
|
|
foreignKey = tableHandler.getForeignKey(foreignKeyName);
|
|
sector.thisJoinColumns = foreignKey.columnNames;
|
|
sector.otherJoinColumns = foreignKey.targetColumnNames;
|
|
} else {
|
|
// error: neither side defined the foreign key
|
|
projection.error += 'MappingError: ' + relatedTableHandler.newObjectConstructor.name +
|
|
' field ' + relatedFieldMapping.fieldName + ' neither side defined the foreign key.';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// create key fields from primary key index handler
|
|
indexHandler = tableHandler.dbIndexHandlers[0];
|
|
keyFieldCount = indexHandler.fieldNumberToFieldMap.length;
|
|
sector.keyFieldCount = keyFieldCount;
|
|
for (i = 0; i < keyFieldCount; ++i) {
|
|
field = indexHandler.fieldNumberToFieldMap[i];
|
|
sector.keyFields.push(field);
|
|
sector.keyFieldNames.push(field.fieldName);
|
|
}
|
|
// create non-key fields from projection fields excluding key fields
|
|
fieldNames = projection.fields;
|
|
fieldNames.forEach(function(fieldName) {
|
|
// is this field in key fields?
|
|
if (sector.keyFieldNames.indexOf(fieldName) == -1) {
|
|
// non-key field; add it to non-key fields
|
|
field = tableHandler.fieldNameToFieldMap[fieldName];
|
|
sector.nonKeyFields.push(field);
|
|
}
|
|
});
|
|
sector.nonKeyFieldCount = sector.nonKeyFields.length;
|
|
udebug.log_detail('createSector created new sector for index', index, 'sector', sector);
|
|
sectors.push(sector);
|
|
|
|
innerNestedProjection = projection.firstNestedProjection;
|
|
if (innerNestedProjection) {
|
|
// recurse at the same outer loop, creating the next sector for the inner loop projection
|
|
createSector(outerLoopProjection, innerNestedProjection,
|
|
sectors, index + 1, offset + keyFieldCount + sector.nonKeyFieldCount);
|
|
} else {
|
|
// we are done at this outer projection level;
|
|
if (outerLoopProjection.name) {
|
|
udebug.log('createSector for ' + outerLoopProjection.name +
|
|
' created ' + outerLoopProjection.sectors.length + ' sectors for ' + outerLoopProjection.domainObject.name);
|
|
}
|
|
// now go to the outer projection next level down and do it all over again
|
|
outerNestedProjection = outerLoopProjection.firstNestedProjection;
|
|
if (outerNestedProjection) {
|
|
outerNestedProjection.sectors = [];
|
|
createSector(outerNestedProjection, outerNestedProjection, outerNestedProjection.sectors, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Mark all projections reachable from this projection as validated. */
|
|
function markValidated(projections) {
|
|
var projection, relationships, relationshipName;
|
|
if (projections.length > 0) {
|
|
// "pop" the top projection
|
|
projection = projections.shift();
|
|
// mark the top projection validated
|
|
projection.validated = true;
|
|
// if any relationships, add them to the list of projections to validate
|
|
relationships = projection.relationships;
|
|
if (relationships) {
|
|
for (relationshipName in relationships) {
|
|
if (relationships.hasOwnProperty(relationshipName)) {
|
|
projections.push(relationships[relationshipName]);
|
|
}
|
|
}
|
|
}
|
|
// recursively mark related projections
|
|
markValidated(projections);
|
|
}
|
|
}
|
|
|
|
/** Collect errors from all projections reachable from this projection */
|
|
function collectErrors(projections, errors) {
|
|
var projection, relationships, relationshipName;
|
|
if (projections.length > 0) {
|
|
// "pop" the top projection
|
|
projection = projections.shift();
|
|
// check the top projection for errors
|
|
errors += projection.error;
|
|
// if any relationships, add them to the list of projections to validate
|
|
relationships = projection.relationships;
|
|
if (relationships) {
|
|
for (relationshipName in relationships) {
|
|
if (relationships.hasOwnProperty(relationshipName)) {
|
|
projections.push(relationships[relationshipName]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return errors;
|
|
}
|
|
return collectErrors(projections, errors);
|
|
}
|
|
|
|
/** Validate the projection for find and query operations on the domain object.
|
|
* this.user_arguments[0] contains the projection for this operation
|
|
* (first parameter of find or createQuery).
|
|
* Validation occurs in two phases. The first phase individually validates
|
|
* each domain object associated with a projection. The second phase,
|
|
* implemented as createSector, validates relationships among the domain objects.
|
|
*
|
|
* In the first phase, get the table handler for the domain object and validate
|
|
* that it is mapped. Then validate each field in the projection.
|
|
* For relationships, validate the name of the relationship. Validate that there
|
|
* is no projected domain object that would cause an infinite recursion.
|
|
* If there is a join table that implements the relationship, validate that the
|
|
* join table exists by loading its metadata.
|
|
* Store the field mapping for this relationship in the related projection.
|
|
* The related field mapping will be further processed in the second phase,
|
|
* once the table metadata for both domain objects has been loaded.
|
|
* Recursively validate the projection that is defined as the relationship.
|
|
*
|
|
* In the second phase, validate that the relationship is mapped with valid
|
|
* foreign keys and join tables in the database.
|
|
* After all projections have been validated, call the callback with any errors.
|
|
*/
|
|
exports.UserContext.prototype.validateProjection = function(callback) {
|
|
var userContext = this;
|
|
var session = userContext.session;
|
|
var err;
|
|
var domainObject, domainObjectName;
|
|
var projections, projection;
|
|
var mappingIds, mappingId;
|
|
var relationships, relationshipProjection;
|
|
var fieldMapping;
|
|
var index, offset;
|
|
var errors;
|
|
var foreignKeyNames, foreignKeyName;
|
|
var toBeChecked, toBeValidated, allValidated;
|
|
var domainObjectMynode;
|
|
var joinTableRelationshipField, joinTableRelationshipFields = [];
|
|
var continueValidation;
|
|
|
|
function validateJoinTableOnTableHandler(err, joinTableHandler) {
|
|
udebug.log_detail('validateJoinTableOnTableHandler for', joinTableRelationshipField.joinTable, 'err:', err);
|
|
if (err) {
|
|
// mark the projection as broken
|
|
errors += '\nBad projection for ' + domainObjectName + ': field ' + joinTableRelationshipField.fieldName +
|
|
' join table ' + joinTableRelationshipField.joinTable + ' failed: ' + err.message;
|
|
} else {
|
|
// continue validating projections
|
|
// we cannot do any more until both sides have their table handlers
|
|
udebug.log_detail('validateJoinTableOnTableHandler resolved table handler for ', domainObjectName,
|
|
': field', joinTableRelationshipField.fieldName,
|
|
'join table', joinTableRelationshipField.joinTable);
|
|
// store the join table handler in the related field mapping
|
|
joinTableRelationshipField.joinTableHandler = joinTableHandler;
|
|
}
|
|
// finished this join table; continue with more join tables or more tables mapped to domain objects
|
|
joinTableRelationshipField = joinTableRelationshipFields.shift();
|
|
if (joinTableRelationshipField) {
|
|
getTableHandler(joinTableRelationshipField.joinTable, session, validateJoinTableOnTableHandler);
|
|
} else {
|
|
continueValidation();
|
|
}
|
|
}
|
|
|
|
function validateProjectionOnTableHandler(err, dbTableHandler) {
|
|
// currently validating projections[index] with the tableHandler for the domain object
|
|
projection = projections[index];
|
|
// keep track of how many times this projection has been changed so adapters know when to re-validate
|
|
projection.id = (projection.id + 1) % (2^24);
|
|
|
|
domainObject = projection.domainObject;
|
|
domainObjectName = domainObject.prototype.constructor.name;
|
|
domainObjectMynode = domainObject.prototype.mynode;
|
|
if (domainObjectMynode && domainObjectMynode.mapping.error) {
|
|
// remember errors in mapping
|
|
errors += domainObjectMynode.mapping.error;
|
|
}
|
|
if (!err) {
|
|
projection.dbTableHandler = dbTableHandler;
|
|
// validate using table handler
|
|
if (typeof(domainObject) === 'function' &&
|
|
typeof(domainObject.prototype.mynode) === 'object' &&
|
|
typeof(domainObject.prototype.mynode.mapping) === 'object') {
|
|
// good domainObject; have we seen this one before?
|
|
mappingId = domainObject.prototype.mynode.mappingId;
|
|
if (mappingIds.indexOf(mappingId) === -1) {
|
|
// have not seen this one before; add its mappingId to list of mappingIds to prevent cycles (recursion)
|
|
mappingIds.push(mappingId);
|
|
// validate all fields in projection are mapped
|
|
if (projection.fields) { // field names
|
|
projection.fields.forEach(function(fieldName) {
|
|
fieldMapping = dbTableHandler.fieldNameToFieldMap[fieldName];
|
|
if (fieldMapping) {
|
|
if (fieldMapping.relationship) {
|
|
errors += '\nBad projection for ' + domainObjectName + ': field' + fieldName + ' must not be a relationship';
|
|
}
|
|
} else {
|
|
// error: fields must be mapped
|
|
errors += '\nBad projection for ' + domainObjectName + ': field ' + fieldName + ' is not mapped';
|
|
}
|
|
});
|
|
}
|
|
// validate all relationships in mapping regardless of whether they are in this projection
|
|
|
|
dbTableHandler.relationshipFields.forEach(function(relationshipField) {
|
|
// get the name and projection for each relationship
|
|
foreignKeyName = relationshipField.foreignKey;
|
|
if (foreignKeyName) {
|
|
// make sure the foreign key exists
|
|
if (!dbTableHandler.foreignKeyMap.hasOwnProperty(foreignKeyName)) {
|
|
errors += '\nBad relationship field mapping; foreign key ' + foreignKeyName +
|
|
' does not exist in table; possible foreign keys are: ' + Object.keys(dbTableHandler.foreignKeyMap);
|
|
}
|
|
}
|
|
// remember this relationship in order to resolve table mapping for join table
|
|
if (relationshipField.joinTable) {
|
|
joinTableRelationshipFields.push(relationshipField);
|
|
}
|
|
});
|
|
// add relationship domain objects to the list of domain objects
|
|
relationships = projection.relationships;
|
|
if (relationships) {
|
|
Object.keys(relationships).forEach(function(key) {
|
|
// each key is the name of a relationship that must be a field in the table handler
|
|
fieldMapping = dbTableHandler.fieldNameToFieldMap[key];
|
|
if (fieldMapping) {
|
|
if (fieldMapping.relationship) {
|
|
relationshipProjection = relationships[key];
|
|
relationshipProjection.relatedFieldMapping = fieldMapping;
|
|
// add each relationship to list of projections
|
|
projections.push(relationshipProjection);
|
|
// record the first (currently the only) projection that is nested in this projection
|
|
if (!projection.firstNestedProjection) {
|
|
projection.firstNestedProjection = relationshipProjection;
|
|
}
|
|
} else {
|
|
// error: field is not a relationship
|
|
errors += '\nBad relationship for ' + domainObjectName + ': field ' + key + ' is not a relationship.';
|
|
}
|
|
} else {
|
|
// error: relationships must be mapped
|
|
errors += '\nBad relationship for ' + domainObjectName + ': field ' + key + ' is not mapped.';
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// recursive projection
|
|
errors += '\nRecursive projection for ' + domainObjectName;
|
|
}
|
|
} else {
|
|
// domainObject was not mapped
|
|
errors += '\nBad domain object: ' + domainObjectName + ' is not mapped.';
|
|
}
|
|
} else {
|
|
// table does not exist
|
|
errors += '\nUnable to acquire tableHandler for ' + domainObjectName + ' : ' + err.message;
|
|
}
|
|
// finished validating this projection; do we have a join table to validate?
|
|
if (joinTableRelationshipFields.length > 0) {
|
|
// get the table handler for the first join table
|
|
joinTableRelationshipField = joinTableRelationshipFields.shift();
|
|
getTableHandler(joinTableRelationshipField.joinTable, session, validateJoinTableOnTableHandler);
|
|
} else {
|
|
continueValidation();
|
|
}
|
|
}
|
|
|
|
// continue validation from either projection domain object or relationship join table
|
|
continueValidation = function() {
|
|
// are there any more?
|
|
if (projections.length > ++index) {
|
|
// do the next projection
|
|
if (projections[index].domainObject.prototype.mynode.dbTableHandler) {
|
|
udebug.log('validateProjection with cached tableHandler for', projections[index].domainObject.name);
|
|
validateProjectionOnTableHandler(null, projections[index].domainObject.prototype.mynode.dbTableHandler);
|
|
} else {
|
|
udebug.log('validateProjection with no cached tableHandler for', projections[index].domainObject.name);
|
|
getTableHandler(projections[index].domainObject, session, validateProjectionOnTableHandler);
|
|
}
|
|
} else {
|
|
// there are no more projections to validate -- did another user finish table handling first?
|
|
if (!userContext.user_arguments[0].validated) {
|
|
// we are the first to validate table handling -- check for errors
|
|
if (!errors) {
|
|
projection = projections[0];
|
|
// no errors yet
|
|
// we are done getting all of the table handlers for the projection; now create the sectors
|
|
projection.sectors = [];
|
|
index = 0;
|
|
offset = 0;
|
|
// create the first sector; additional sectors will be created recursively
|
|
createSector(projection, projection, projection.sectors, index, offset);
|
|
// now look for errors found during createSector
|
|
errors = collectErrors([userContext.user_arguments[0]], '');
|
|
// mark all projections reachable from this projections as validated
|
|
// projections will grow at the end as validated marking proceeds
|
|
if (!errors) {
|
|
// no errors in createSector
|
|
toBeValidated = [userContext.user_arguments[0]];
|
|
markValidated(toBeValidated);
|
|
udebug.log('validateProjection complete for', projections[0].domainObject.name);
|
|
callback(null);
|
|
return;
|
|
}
|
|
}
|
|
// report errors and call back user
|
|
if (errors) {
|
|
udebug.log('validateProjection had errors:\n', errors);
|
|
err = new Error(errors);
|
|
err.sqlstate = 'HY000';
|
|
}
|
|
}
|
|
callback(err);
|
|
}
|
|
};
|
|
|
|
|
|
// validateProjection starts here
|
|
// projection: {domainObject:<constructor>, fields: [field, field], relationships: {field: {projection}, field: {projection}}
|
|
// first check to see if the projection is already validated. If so, we are done.
|
|
// the entire projection including all referenced relationships must be checked because a relationship
|
|
// might have changed since it was last validated.
|
|
// projections will grow at the end as validation checking proceeds
|
|
// construct a new array which will grow as validation checking proceeds
|
|
if (userContext.user_arguments[0].validated) {
|
|
callback(null);
|
|
} else {
|
|
// set up to iteratively validate projection starting with the user parameter
|
|
projections = [this.user_arguments[0]]; // projections will grow at the end as validation proceeds
|
|
index = 0; // index into projections for the projection being validated
|
|
offset = 0; // the number of fields in previous projections
|
|
errors = ''; // errors in validation
|
|
mappingIds = []; // mapping ids seen so far
|
|
|
|
// the projection is not already validated; check to see if the domain object already has its dbTableHandler
|
|
domainObjectMynode = projections[0].domainObject.prototype.mynode;
|
|
if (domainObjectMynode && domainObjectMynode.dbTableHandler) {
|
|
udebug.log('validateProjection with cached tableHandler for', projections[0].domainObject.name);
|
|
validateProjectionOnTableHandler(null, domainObjectMynode.dbTableHandler);
|
|
} else {
|
|
// get the dbTableHandler the hard way
|
|
udebug.log('validateProjection with no tableHandler for', projections[0].domainObject.name);
|
|
getTableHandler(projections[index].domainObject, userContext.session, validateProjectionOnTableHandler);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Use the projection to find a domain object. This is only valid in a session, not a batch.
|
|
* Multiple operations may be needed to resolve the complete projection.
|
|
* Take the user's projection and see if it has been resolved. For an unresolved projection,
|
|
* load table mappings for all included domain objects and verify the projection against
|
|
* the resolved table mappings.
|
|
* Once the projection has been resolved, get the db index to use for the operation,
|
|
* call db session to create a read with projection operation, and execute the operation.
|
|
* The db session will process the projection to populate the result.
|
|
*/
|
|
exports.UserContext.prototype.findWithProjection = function() {
|
|
var userContext = this;
|
|
var session = userContext.session;
|
|
var dbSession = session.dbSession;
|
|
var projection = userContext.user_arguments[0];
|
|
var keys = userContext.user_arguments[1];
|
|
var dbTableHandler;
|
|
var indexHandler;
|
|
var transactionHandler;
|
|
|
|
function findWithProjectionOnResult(err, dbOperation) {
|
|
udebug.log('find.findWithProjectionOnResult');
|
|
var result, values;
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error && dbOperation.result.error.sqlstate !== '02000') {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log('findOnResult returning ', dbOperation.result.value); }
|
|
userContext.applyCallback(null, dbOperation.result.value);
|
|
}
|
|
}
|
|
|
|
|
|
function onValidatedProjection(err) {
|
|
if (err) {
|
|
udebug.log('UserContext.onValidProjection err: ', err);
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
dbTableHandler = projection.dbTableHandler;
|
|
userContext.dbTableHandler = dbTableHandler;
|
|
keys = userContext.user_arguments[1];
|
|
indexHandler = dbTableHandler.getIndexHandler(keys, true);
|
|
if (indexHandler === null) {
|
|
err = new Error('UserContext.find unable to get an index for ' + dbTableHandler.dbTable.name +
|
|
' to use with ' + JSON.stringify(keys));
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
// create the find operation and execute it
|
|
dbSession = userContext.session.dbSession;
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
userContext.operation = dbSession.buildReadProjectionOperation(indexHandler, keys, projection,
|
|
transactionHandler, findWithProjectionOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('find transactionHandler.execute callback.'); }
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// findWithProjection starts here
|
|
// validate the projection and construct the sectors
|
|
userContext.validateProjection(onValidatedProjection);
|
|
// the caller will return userContext.promise
|
|
};
|
|
|
|
/** Find the object by key.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.find = function() {
|
|
var userContext = this;
|
|
var tableHandler;
|
|
if (typeof(this.user_arguments[0]) === 'function') {
|
|
userContext.domainObject = true;
|
|
}
|
|
|
|
function findOnResult(err, dbOperation) {
|
|
udebug.log('find.findOnResult');
|
|
var result, values;
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error && dbOperation.result.error.sqlstate !== '02000') {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log('findOnResult returning ', dbOperation.result.value); }
|
|
userContext.applyCallback(null, dbOperation.result.value);
|
|
}
|
|
}
|
|
|
|
function findOnTableHandler(err, dbTableHandler) {
|
|
var dbSession, keys, index, transactionHandler;
|
|
if (userContext.clear) {
|
|
// if batch has been cleared, user callback has already been called
|
|
return;
|
|
}
|
|
if (err) {
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
userContext.dbTableHandler = dbTableHandler;
|
|
keys = userContext.user_arguments[1];
|
|
index = dbTableHandler.getIndexHandler(keys, true);
|
|
if (index === null) {
|
|
err = new Error('UserContext.find unable to get an index to use for ' + JSON.stringify(keys));
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
// create the find operation and execute it
|
|
dbSession = userContext.session.dbSession;
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
userContext.operation = dbSession.buildReadOperation(index, keys,
|
|
transactionHandler, findOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('find transactionHandler.execute callback.'); }
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// find starts here
|
|
// session.find(projectionOrPrototypeOrTableName, key, callback)
|
|
// validate first two parameters must be defined
|
|
if (userContext.user_arguments[0] === undefined || userContext.user_arguments[1] === undefined) {
|
|
userContext.applyCallback(new Error('User error: find must have at least two arguments.'), null);
|
|
} else {
|
|
if (userContext.user_arguments[0].constructor.name === 'Projection' &&
|
|
typeof userContext.user_arguments[0].constructor.prototype.addRelationship === 'function') {
|
|
// this is a projection
|
|
userContext.findWithProjection();
|
|
} else {
|
|
// get DBTableHandler for prototype/tableName
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, findOnTableHandler);
|
|
}
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Create a query object.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.createQuery = function() {
|
|
var userContext = this;
|
|
var tableHandler;
|
|
var queryDomainType;
|
|
|
|
function createQueryOnTableHandler(err, dbTableHandler) {
|
|
if (err) {
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
// create the query domain type and bind it to this session
|
|
queryDomainType = new query.QueryDomainType(userContext.session, dbTableHandler, userContext.domainObject);
|
|
if(udebug.is_detail()) { udebug.log('UserContext.createQuery queryDomainType:', queryDomainType); }
|
|
userContext.applyCallback(null, queryDomainType);
|
|
}
|
|
}
|
|
|
|
// createQuery starts here
|
|
// session.createQuery(constructorOrTableName, callback)
|
|
// if the first parameter is a query object then copy the interesting bits and create a new object
|
|
if (this.user_arguments[0].mynode_query_domain_type) {
|
|
// TODO make sure this sessionFactory === other.sessionFactory
|
|
queryDomainType = new query.QueryDomainType(userContext.session);
|
|
}
|
|
// if the first parameter is a table name the query results will be literals
|
|
// if not (constructor or domain object) the query results will be domain objects
|
|
userContext.domainObject = typeof(this.user_arguments[0]) !== 'string';
|
|
// get DBTableHandler for constructor/tableName
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, createQueryOnTableHandler);
|
|
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** maximum skip and limit parameters are some large number */
|
|
var MAX_SKIP = Math.pow(2, 52);
|
|
var MAX_LIMIT = Math.pow(2, 52);
|
|
|
|
/** Execute a query.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.executeQuery = function(queryDomainType) {
|
|
var userContext = this;
|
|
var dbSession, transactionHandler, queryType;
|
|
userContext.queryDomainType = queryDomainType;
|
|
|
|
// transform query result
|
|
function executeQueryKeyOnResult(err, dbOperation) {
|
|
udebug.log('executeQuery.executeQueryPKOnResult');
|
|
var result, values, resultList;
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
userContext.applyCallback(error, null);
|
|
} else {
|
|
if (userContext.queryDomainType.mynode_query_domain_type.domainObject) {
|
|
values = dbOperation.result.value;
|
|
result = userContext.queryDomainType.mynode_query_domain_type.dbTableHandler.newResultObject(values);
|
|
} else {
|
|
result = dbOperation.result.value;
|
|
}
|
|
if (result !== null) {
|
|
// TODO: filter in memory if the adapter didn't filter all conditions
|
|
resultList = [result];
|
|
} else {
|
|
resultList = [];
|
|
}
|
|
userContext.applyCallback(null, resultList);
|
|
}
|
|
}
|
|
|
|
// transform query result
|
|
function executeQueryScanOnResult(err, dbOperation) {
|
|
if(udebug.is_detail()) { udebug.log('executeQuery.executeQueryScanOnResult'); }
|
|
var result, values, resultList;
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
userContext.applyCallback(error, null);
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log('executeQuery.executeQueryScanOnResult', dbOperation.result.value); }
|
|
// TODO: filter in memory if the adapter didn't filter all conditions
|
|
userContext.applyCallback(null, dbOperation.result.value);
|
|
}
|
|
}
|
|
|
|
// executeScanQuery is used by both index scan and table scan
|
|
var executeScanQuery = function() {
|
|
// validate order, skip, and limit parameters
|
|
var params = userContext.user_arguments[0];
|
|
var orderToUpperCase;
|
|
var order = params.order, skip = params.skip, limit = params.limit;
|
|
var error;
|
|
if (typeof(limit) !== 'undefined') {
|
|
if (limit < 0 || limit > MAX_LIMIT) {
|
|
// limit is out of valid range
|
|
error = new Error('Bad limit parameter \'' + limit + '\'; limit must be >= 0 and <= ' + MAX_LIMIT + '.');
|
|
}
|
|
}
|
|
if (typeof(skip) !== 'undefined') {
|
|
if (skip < 0 || skip > MAX_SKIP) {
|
|
// skip is out of valid range
|
|
error = new Error('Bad skip parameter \'' + skip + '\'; skip must be >= 0 and <= ' + MAX_SKIP + '.');
|
|
} else {
|
|
if (!order) {
|
|
// skip is in range but order is not specified
|
|
error = new Error('Bad skip parameter \'' + skip + '\'; if skip is specified, then order must be specified.');
|
|
}
|
|
}
|
|
}
|
|
if (typeof(order) !== 'undefined') {
|
|
if (typeof(order) !== 'string') {
|
|
error = new Error('Bad order parameter \'' + order + '\'; order must be ignoreCase asc or desc.');
|
|
} else {
|
|
orderToUpperCase = order.toUpperCase();
|
|
if (!(orderToUpperCase === 'ASC' || orderToUpperCase === 'DESC')) {
|
|
error = new Error('Bad order parameter \'' + order + '\'; order must be ignoreCase asc or desc.');
|
|
}
|
|
}
|
|
}
|
|
if (error) {
|
|
userContext.applyCallback(error, null);
|
|
} else {
|
|
dbSession = userContext.session.dbSession;
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
userContext.operation = dbSession.buildScanOperation(
|
|
queryDomainType, userContext.user_arguments[0], transactionHandler,
|
|
executeQueryScanOnResult);
|
|
// TODO: this currently does not support batching
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('executeQueryPK transactionHandler.execute callback.'); }
|
|
});
|
|
}
|
|
// if (userContext.execute) {
|
|
// transactionHandler.execute([userContext.operation], function() {
|
|
// if(udebug.is_detail()) udebug.log('find transactionHandler.execute callback.');
|
|
// });
|
|
//} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
// userContext.operationDefinedCallback(1);
|
|
//}
|
|
};
|
|
|
|
// executeKeyQuery is used by both primary key and unique key
|
|
var executeKeyQuery = function() {
|
|
// create the find operation and execute it
|
|
dbSession = userContext.session.dbSession;
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
var dbIndexHandler = queryDomainType.mynode_query_domain_type.queryHandler.candidateIndex.dbIndexHandler;
|
|
var keys = queryDomainType.mynode_query_domain_type.queryHandler.getKeys(userContext.user_arguments[0]);
|
|
userContext.operation = dbSession.buildReadOperation(dbIndexHandler, keys, transactionHandler,
|
|
executeQueryKeyOnResult);
|
|
// TODO: this currently does not support batching
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('executeQueryPK transactionHandler.execute callback.'); }
|
|
});
|
|
// if (userContext.execute) {
|
|
// transactionHandler.execute([userContext.operation], function() {
|
|
// if(udebug.is_detail()) udebug.log('find transactionHandler.execute callback.');
|
|
// });
|
|
// } else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
// userContext.operationDefinedCallback(1);
|
|
// }
|
|
};
|
|
|
|
// executeQuery starts here
|
|
// query.execute(parameters, callback)
|
|
udebug.log('QueryDomainType.execute', queryDomainType.mynode_query_domain_type.predicate,
|
|
'with parameters', userContext.user_arguments[0]);
|
|
// execute the query and call back user
|
|
queryType = queryDomainType.mynode_query_domain_type.queryType;
|
|
switch(queryType) {
|
|
case 0: // primary key
|
|
executeKeyQuery();
|
|
break;
|
|
|
|
case 1: // unique key
|
|
executeKeyQuery();
|
|
break;
|
|
|
|
case 2: // index scan
|
|
executeScanQuery();
|
|
break;
|
|
|
|
case 3: // table scan
|
|
executeScanQuery();
|
|
break;
|
|
|
|
default:
|
|
throw new Error('FatalInternalException: queryType: ' + queryType + ' not supported');
|
|
}
|
|
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Persist the object.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.persist = function() {
|
|
var userContext = this;
|
|
var object;
|
|
|
|
function persistOnResult(err, dbOperation) {
|
|
udebug.log('persist.persistOnResult');
|
|
// return any error code
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(error);
|
|
} else {
|
|
if (dbOperation.result.autoincrementValue) {
|
|
// put returned autoincrement value into object
|
|
userContext.dbTableHandler.setAutoincrement(userContext.values, dbOperation.result.autoincrementValue);
|
|
}
|
|
userContext.applyCallback(null);
|
|
}
|
|
}
|
|
|
|
function persistOnTableHandler(err, dbTableHandler) {
|
|
userContext.dbTableHandler = dbTableHandler;
|
|
if(udebug.is_detail()){ udebug.log('UserContext.persist.persistOnTableHandler ' + err); }
|
|
var transactionHandler;
|
|
var dbSession = userContext.session.dbSession;
|
|
if (userContext.clear) {
|
|
// if batch has been cleared, user callback has already been called
|
|
return;
|
|
}
|
|
if (err) {
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
userContext.operation = dbSession.buildInsertOperation(dbTableHandler, userContext.values, transactionHandler,
|
|
persistOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('persist transactionHandler.execute callback.'); }
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// persist starts here
|
|
if (userContext.required_parameter_count === 2) {
|
|
// persist(object, callback)
|
|
userContext.values = userContext.user_arguments[0];
|
|
} else if (userContext.required_parameter_count === 3) {
|
|
// persist(tableNameOrConstructor, values, callback)
|
|
userContext.values = userContext.user_arguments[1];
|
|
} else {
|
|
throw new Error(
|
|
'Fatal internal error; wrong required_parameter_count ' + userContext.required_parameter_count);
|
|
}
|
|
// get DBTableHandler for table indicator (domain object, constructor, or table name)
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, persistOnTableHandler);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Save the object. If the row already exists, overwrite non-pk columns.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.save = function() {
|
|
var userContext = this;
|
|
var tableHandler, object, indexHandler;
|
|
|
|
function saveOnResult(err, dbOperation) {
|
|
// return any error code
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(error);
|
|
} else {
|
|
userContext.applyCallback(null);
|
|
}
|
|
}
|
|
|
|
function saveOnTableHandler(err, dbTableHandler) {
|
|
var transactionHandler;
|
|
var dbSession = userContext.session.dbSession;
|
|
if (userContext.clear) {
|
|
// if batch has been cleared, user callback has already been called
|
|
return;
|
|
}
|
|
if (err) {
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
indexHandler = dbTableHandler.getIndexHandler(userContext.values);
|
|
if (!indexHandler.dbIndex.isPrimaryKey) {
|
|
userContext.applyCallback(
|
|
new Error('Illegal argument: parameter of save must include all primary key columns.'));
|
|
return;
|
|
}
|
|
userContext.operation = dbSession.buildWriteOperation(indexHandler, userContext.values, transactionHandler,
|
|
saveOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// save starts here
|
|
|
|
if (userContext.required_parameter_count === 2) {
|
|
// save(object, callback)
|
|
userContext.values = userContext.user_arguments[0];
|
|
} else if (userContext.required_parameter_count === 3) {
|
|
// save(tableNameOrConstructor, values, callback)
|
|
userContext.values = userContext.user_arguments[1];
|
|
} else {
|
|
throw new Error(
|
|
'Fatal internal error; wrong required_parameter_count ' + userContext.required_parameter_count);
|
|
}
|
|
// get DBTableHandler for table indicator (domain object, constructor, or table name)
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, saveOnTableHandler);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Update the object.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.update = function() {
|
|
var userContext = this;
|
|
var tableHandler, object, indexHandler;
|
|
|
|
function updateOnResult(err, dbOperation) {
|
|
// return any error code
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(error);
|
|
} else {
|
|
userContext.applyCallback(null);
|
|
}
|
|
}
|
|
|
|
function updateOnTableHandler(err, dbTableHandler) {
|
|
var transactionHandler;
|
|
var dbSession = userContext.session.dbSession;
|
|
if (userContext.clear) {
|
|
// if batch has been cleared, user callback has already been called
|
|
return;
|
|
}
|
|
if (err) {
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
indexHandler = dbTableHandler.getIndexHandler(userContext.keys);
|
|
// for variant update(object, callback) the object must include all primary keys
|
|
if (userContext.required_parameter_count === 2 && !indexHandler.dbIndex.isPrimaryKey) {
|
|
userContext.applyCallback(
|
|
new Error('Illegal argument: parameter of update must include all primary key columns.'));
|
|
return;
|
|
}
|
|
userContext.operation = dbSession.buildUpdateOperation(indexHandler, userContext.keys,
|
|
userContext.values, transactionHandler, updateOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// update starts here
|
|
|
|
if (userContext.required_parameter_count === 2) {
|
|
// update(object, callback)
|
|
userContext.keys = userContext.user_arguments[0];
|
|
userContext.values = userContext.user_arguments[0];
|
|
} else if (userContext.required_parameter_count === 4) {
|
|
// update(tableNameOrConstructor, keys, values, callback)
|
|
userContext.keys = userContext.user_arguments[1];
|
|
userContext.values = userContext.user_arguments[2];
|
|
} else {
|
|
throw new Error(
|
|
'Fatal internal error; wrong required_parameter_count ' + userContext.required_parameter_count);
|
|
}
|
|
// get DBTableHandler for table indicator (domain object, constructor, or table name)
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, updateOnTableHandler);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Load the object.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.load = function() {
|
|
var userContext = this;
|
|
var tableHandler;
|
|
|
|
function loadOnResult(err, dbOperation) {
|
|
udebug.log('load.loadOnResult');
|
|
var result, values;
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(err);
|
|
return;
|
|
}
|
|
values = dbOperation.result.value;
|
|
// apply the values to the parameter domain object
|
|
userContext.dbTableHandler.setFields(userContext.user_arguments[0], values);
|
|
userContext.applyCallback(null);
|
|
}
|
|
|
|
function loadOnTableHandler(err, dbTableHandler) {
|
|
var dbSession, keys, index, transactionHandler;
|
|
if (userContext.clear) {
|
|
// if batch has been cleared, user callback has already been called
|
|
return;
|
|
}
|
|
if (err) {
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
userContext.dbTableHandler = dbTableHandler;
|
|
// the domain object must provide PRIMARY or unique key
|
|
keys = userContext.user_arguments[0];
|
|
// ask getIndexHandler for only unique key indexes
|
|
index = dbTableHandler.getIndexHandler(keys, true);
|
|
if (index === null) {
|
|
err = new Error('Illegal argument: load unable to get a unique index to use for ' + JSON.stringify(keys));
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
// create the load operation and execute it
|
|
dbSession = userContext.session.dbSession;
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
userContext.operation = dbSession.buildReadOperation(index, keys, transactionHandler,
|
|
loadOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('load transactionHandler.execute callback.'); }
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// load starts here
|
|
// session.load(instance, callback)
|
|
// get DBTableHandler for instance constructor
|
|
if (typeof(userContext.user_arguments[0].mynode) !== 'object') {
|
|
userContext.applyCallback(new Error('Illegal argument: load requires a mapped domain object.'));
|
|
return;
|
|
}
|
|
var ctor = userContext.user_arguments[0].mynode.constructor;
|
|
getTableHandler(ctor, userContext.session, loadOnTableHandler);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Remove the object.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.remove = function() {
|
|
var userContext = this;
|
|
var tableHandler, object;
|
|
|
|
function removeOnResult(err, dbOperation) {
|
|
udebug.log('remove.removeOnResult');
|
|
// return any error code plus the original user object
|
|
var error = checkOperation(err, dbOperation);
|
|
if (error) {
|
|
if (userContext.session.tx.isActive()) {
|
|
userContext.session.tx.setRollbackOnly();
|
|
}
|
|
userContext.applyCallback(error);
|
|
} else {
|
|
var result = dbOperation.result.value;
|
|
userContext.applyCallback(null);
|
|
}
|
|
}
|
|
|
|
function removeOnTableHandler(err, dbTableHandler) {
|
|
var transactionHandler, object, dbIndexHandler;
|
|
var dbSession = userContext.session.dbSession;
|
|
if (userContext.clear) {
|
|
// if batch has been cleared, user callback has already been called
|
|
return;
|
|
}
|
|
if (err) {
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
dbIndexHandler = dbTableHandler.getIndexHandler(userContext.keys, true);
|
|
if (dbIndexHandler === null) {
|
|
err = new Error('UserContext.remove unable to get an index to use for ' + JSON.stringify(userContext.keys));
|
|
userContext.applyCallback(err);
|
|
} else {
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
userContext.operation = dbSession.buildDeleteOperation(
|
|
dbIndexHandler, userContext.keys, transactionHandler, removeOnResult);
|
|
if (userContext.execute) {
|
|
transactionHandler.execute([userContext.operation], function() {
|
|
if(udebug.is_detail()) { udebug.log('remove transactionHandler.execute callback.'); }
|
|
});
|
|
} else if (typeof(userContext.operationDefinedCallback) === 'function') {
|
|
userContext.operationDefinedCallback(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove starts here
|
|
|
|
if (userContext.required_parameter_count === 2) {
|
|
// remove(object, callback)
|
|
userContext.keys = userContext.user_arguments[0];
|
|
} else if (userContext.required_parameter_count === 3) {
|
|
// remove(tableNameOrConstructor, values, callback)
|
|
userContext.keys = userContext.user_arguments[1];
|
|
} else {
|
|
throw new Error(
|
|
'Fatal internal error; wrong required_parameter_count ' + userContext.required_parameter_count);
|
|
}
|
|
// get DBTableHandler for table indicator (domain object, constructor, or table name)
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, removeOnTableHandler);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Get Mapping
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.getMapping = function() {
|
|
var userContext = this;
|
|
function getMappingOnTableHandler(err, dbTableHandler) {
|
|
if (err) {
|
|
userContext.applyCallback(err, null);
|
|
return;
|
|
}
|
|
var mapping = dbTableHandler.resolvedMapping;
|
|
userContext.applyCallback(null, mapping);
|
|
}
|
|
// getMapping starts here
|
|
getTableHandler(userContext.user_arguments[0], userContext.session, getMappingOnTableHandler);
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Execute a batch
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.executeBatch = function(operationContexts) {
|
|
var userContext = this;
|
|
userContext.operationContexts = operationContexts;
|
|
userContext.numberOfOperations = operationContexts.length;
|
|
userContext.numberOfOperationsDefined = 0;
|
|
|
|
// all operations have been executed and their user callbacks called
|
|
// now call the Batch.execute callback
|
|
var executeBatchOnExecute = function(err) {
|
|
userContext.applyCallback(err);
|
|
};
|
|
|
|
// wait here until all operations have been defined
|
|
// if operations are not yet defined, the onTableHandler callback
|
|
// will call this function after the operation is defined
|
|
var executeBatchOnOperationDefined = function(definedOperationCount) {
|
|
userContext.numberOfOperationsDefined += definedOperationCount;
|
|
if(udebug.is_detail()) {
|
|
udebug.log('UserContext.executeBatch expecting', userContext.numberOfOperations,
|
|
'operations with', userContext.numberOfOperationsDefined, 'already defined.');
|
|
}
|
|
if (userContext.numberOfOperationsDefined === userContext.numberOfOperations) {
|
|
var operations = [];
|
|
// collect all operations from the operation contexts
|
|
userContext.operationContexts.forEach(function(operationContext) {
|
|
operations.push(operationContext.operation);
|
|
});
|
|
// execute the batch
|
|
var transactionHandler;
|
|
var dbSession;
|
|
dbSession = userContext.session.dbSession;
|
|
transactionHandler = dbSession.getTransactionHandler();
|
|
transactionHandler.execute(operations, executeBatchOnExecute);
|
|
}
|
|
};
|
|
|
|
// executeBatch starts here
|
|
// if no operations in the batch, just call the user callback
|
|
if (operationContexts.length == 0) {
|
|
executeBatchOnExecute(null);
|
|
} else {
|
|
// make sure all operations are defined
|
|
operationContexts.forEach(function(operationContext) {
|
|
// is the operation already defined?
|
|
if (typeof(operationContext.operation) !== 'undefined') {
|
|
userContext.numberOfOperationsDefined++;
|
|
} else {
|
|
// the operation has not been defined yet; set a callback for when the operation is defined
|
|
operationContext.operationDefinedCallback = executeBatchOnOperationDefined;
|
|
}
|
|
});
|
|
// now execute the operations
|
|
executeBatchOnOperationDefined(0);
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Commit an active transaction.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.commit = function() {
|
|
var userContext = this;
|
|
|
|
var commitOnCommit = function(err) {
|
|
udebug.log('UserContext.commitOnCommit.');
|
|
userContext.session.tx.setState(userContext.session.tx.idle);
|
|
userContext.applyCallback(err);
|
|
};
|
|
|
|
// commit begins here
|
|
if (userContext.session.tx.isActive()) {
|
|
udebug.log('UserContext.commit tx is active.');
|
|
userContext.session.dbSession.commit(commitOnCommit);
|
|
} else {
|
|
userContext.applyCallback(
|
|
new Error('Fatal Internal Exception: UserContext.commit with no active transaction.'));
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Roll back an active transaction.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.rollback = function() {
|
|
var userContext = this;
|
|
|
|
var rollbackOnRollback = function(err) {
|
|
udebug.log('UserContext.rollbackOnRollback.');
|
|
userContext.session.tx.setState(userContext.session.tx.idle);
|
|
userContext.applyCallback(err);
|
|
};
|
|
|
|
// rollback begins here
|
|
if (userContext.session.tx.isActive()) {
|
|
udebug.log('UserContext.rollback tx is active.');
|
|
var transactionHandler = userContext.session.dbSession.getTransactionHandler();
|
|
transactionHandler.rollback(rollbackOnRollback);
|
|
} else {
|
|
userContext.applyCallback(
|
|
new Error('Fatal Internal Exception: UserContext.rollback with no active transaction.'));
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Open a session. Allocate a slot in the session factory sessions array.
|
|
* Call the DBConnectionPool to create a new DBSession.
|
|
* Wrap the DBSession in a new Session and return it to the user.
|
|
* This function is called by both mynode.openSession (without a session factory)
|
|
* and SessionFactory.openSession (with a session factory).
|
|
*/
|
|
exports.UserContext.prototype.openSession = function() {
|
|
var userContext = this;
|
|
|
|
var openSessionOnSession = function(err, dbSession) {
|
|
if (err) {
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
userContext.session = new apiSession.Session(userContext.session_index, userContext.session_factory, dbSession);
|
|
userContext.session_factory.sessions[userContext.session_index] = userContext.session;
|
|
userContext.applyCallback(err, userContext.session);
|
|
}
|
|
};
|
|
|
|
var openSessionOnSessionFactory = function(err, factory) {
|
|
if (err) {
|
|
userContext.applyCallback(err, null);
|
|
} else {
|
|
userContext.session_factory = factory;
|
|
// allocate a new session slot in sessions
|
|
userContext.session_index = userContext.session_factory.allocateSessionSlot();
|
|
// get a new DBSession from the DBConnectionPool
|
|
userContext.session_factory.dbConnectionPool.getDBSession(userContext.session_index,
|
|
openSessionOnSession);
|
|
}
|
|
};
|
|
|
|
// openSession starts here
|
|
if (userContext.session_factory) {
|
|
openSessionOnSessionFactory(null, userContext.session_factory);
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log('openSession for', util.inspect(userContext)); }
|
|
// properties might be null, a name, or a properties object
|
|
userContext.user_arguments[0] = resolveProperties(userContext.user_arguments[0]);
|
|
getSessionFactory(userContext, userContext.user_arguments[0], userContext.user_arguments[1],
|
|
openSessionOnSessionFactory);
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
/** Close a session. Close the dbSession which might put the underlying connection
|
|
* back into the connection pool. Then, remove the session from the session factory's
|
|
* open connections.
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.closeSession = function() {
|
|
var userContext = this;
|
|
|
|
var closeSessionOnDBSessionClose = function(err) {
|
|
// now remove the session from the session factory's open connections
|
|
userContext.session_factory.closeSession(userContext.session.index);
|
|
// mark this session as unusable
|
|
userContext.session.closed = true;
|
|
userContext.applyCallback(err);
|
|
};
|
|
// first, close the dbSession
|
|
userContext.session.dbSession.close(closeSessionOnDBSessionClose);
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Close all open SessionFactories
|
|
*
|
|
*/
|
|
exports.UserContext.prototype.closeAllOpenSessionFactories = function() {
|
|
var userContext, openFactories, nToClose;
|
|
|
|
userContext = this;
|
|
openFactories = mynode.getOpenSessionFactories();
|
|
nToClose = openFactories.length;
|
|
|
|
function onFactoryClose() {
|
|
nToClose--;
|
|
if(nToClose === 0) {
|
|
userContext.applyCallback(null);
|
|
}
|
|
}
|
|
|
|
if(nToClose > 0) {
|
|
while(openFactories[0]) {
|
|
openFactories[0].close(onFactoryClose);
|
|
openFactories.shift();
|
|
}
|
|
} else {
|
|
userContext.applyCallback(null);
|
|
}
|
|
return userContext.promise;
|
|
};
|
|
|
|
|
|
/** Complete the user function by calling back the user with the results of the function.
|
|
* Apply the user callback using the current arguments and the extra parameters from the original function.
|
|
* Create the args for the callback by copying the current arguments to this function. Then, copy
|
|
* the extra parameters from the original function. Finally, call the user callback.
|
|
* If there is no user callback, and there is an error (first argument to applyCallback)
|
|
* throw the error.
|
|
*/
|
|
exports.UserContext.prototype.applyCallback = function(err, result) {
|
|
if (arguments.length !== this.returned_parameter_count) {
|
|
throw new Error(
|
|
'Fatal internal exception: wrong parameter count ' + arguments.length +' for UserContext applyCallback' +
|
|
'; expected ' + this.returned_parameter_count);
|
|
}
|
|
// notify (either fulfill or reject) the promise
|
|
if (err) {
|
|
if(udebug.is_detail()) { udebug.log('UserContext.applyCallback.reject', err); }
|
|
this.promise.reject(err);
|
|
} else {
|
|
if(udebug.is_detail()) { udebug.log('UserContext.applyCallback.fulfill', result); }
|
|
this.promise.fulfill(result);
|
|
}
|
|
if (typeof(this.user_callback) === 'undefined') {
|
|
if(udebug.is_detail()) udebug.log('UserContext.applyCallback with no user_callback.');
|
|
return;
|
|
}
|
|
var args = [];
|
|
var i, j;
|
|
for (i = 0; i < arguments.length; ++i) {
|
|
args.push(arguments[i]);
|
|
}
|
|
for (j = this.required_parameter_count; j < this.user_arguments.length; ++j) {
|
|
args.push(this.user_arguments[j]);
|
|
}
|
|
this.user_callback.apply(null, args);
|
|
};
|
|
|
|
exports.Promise = Promise; |