748 lines
24 KiB
JavaScript
748 lines
24 KiB
JavaScript
/*
|
|
Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights
|
|
reserved.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; version 2 of
|
|
the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
02110-1301 USA
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
var stats = {
|
|
"constructor_calls" : 0,
|
|
"created" : {},
|
|
"default_mappings" : 0,
|
|
"explicit_mappings" : 0,
|
|
"return_null" : 0,
|
|
"result_objects_created" : 0,
|
|
"DBIndexHandler_created" : 0
|
|
};
|
|
|
|
var assert = require("assert"),
|
|
TableMapping = require(mynode.api.TableMapping).TableMapping,
|
|
FieldMapping = require(mynode.api.TableMapping).FieldMapping,
|
|
stats_module = require(mynode.api.stats),
|
|
util = require("util"),
|
|
udebug = unified_debug.getLogger("DBTableHandler.js");
|
|
|
|
// forward declaration of DBIndexHandler to avoid lint issue
|
|
var DBIndexHandler;
|
|
|
|
stats_module.register(stats,"spi","DBTableHandler");
|
|
|
|
/* A DBTableHandler (DBT) combines dictionary metadata with user mappings.
|
|
It manages setting and getting of columns based on the fields of a
|
|
user's domain object. It can also choose an index access path by
|
|
comapring user-supplied key fields of a domain object with a table's indexes.
|
|
|
|
These are the structural parts of a DBT:
|
|
* mapping, an API TableMapping, either created explicitly or by default.
|
|
* A TableMetadata object, obtained from the data dictionary.
|
|
* An internal set of maps between Fields and Columns
|
|
|
|
The mapping and TableMetadata are supplied as arguments to the
|
|
constructor, which creates the maps.
|
|
|
|
Some terms:
|
|
column number: column order in table as supplied by DataDictionary
|
|
field number: an arbitrary ordering of only the mapped fields
|
|
*/
|
|
|
|
|
|
/* getColumnByName() is a utility function used in the building of maps.
|
|
*/
|
|
function getColumnByName(dbTable, colName) {
|
|
udebug.log_detail("getColumnByName", colName);
|
|
var i, col;
|
|
|
|
for(i = 0 ; i < dbTable.columns.length ; i++) {
|
|
col = dbTable.columns[i];
|
|
if(col.name === colName) {
|
|
return col;
|
|
}
|
|
}
|
|
udebug.log("getColumnByName", colName, "NOT FOUND.");
|
|
return null;
|
|
}
|
|
|
|
|
|
/* DBTableHandler() constructor
|
|
IMMEDIATE
|
|
|
|
Create a DBTableHandler for a table and a mapping.
|
|
|
|
The TableMetadata may not be null.
|
|
|
|
If the TableMapping is null, default mapping behavior will be used.
|
|
Default mapping behavior is to:
|
|
select all columns when reading
|
|
use default domainTypeConverters for all data types
|
|
perform no remapping between field names and column names
|
|
*/
|
|
function DBTableHandler(dbtable, tablemapping, ctor) {
|
|
assert(arguments.length === 3);
|
|
var i, // an iterator
|
|
f, // a FieldMapping
|
|
c, // a ColumnMetadata
|
|
n, // a field or column number
|
|
index, // a DBIndex
|
|
stubFields, // fields created through default mapping
|
|
foreignKey, // foreign key object from dbTable
|
|
nMappedFields;
|
|
|
|
stats.constructor_calls++;
|
|
|
|
if(! ( dbtable && dbtable.columns)) {
|
|
stats.return_null++;
|
|
return null;
|
|
}
|
|
|
|
if(typeof stats.created[dbtable.name] === 'undefined') {
|
|
stats.created[dbtable.name] = 1;
|
|
} else {
|
|
stats.created[dbtable.name]++;
|
|
}
|
|
|
|
this.dbTable = dbtable;
|
|
|
|
if(ctor) {
|
|
this.newObjectConstructor = ctor;
|
|
}
|
|
|
|
if(tablemapping) {
|
|
stats.explicit_mappings++;
|
|
this.mapping = tablemapping;
|
|
}
|
|
else { // Create a default mapping
|
|
stats.default_mappings++;
|
|
this.mapping = new TableMapping(this.dbTable.name);
|
|
this.mapping.database = this.dbTable.database;
|
|
}
|
|
|
|
/* Default properties */
|
|
this.resolvedMapping = null;
|
|
this.ValueObject = null;
|
|
this.errorMessages = '\n';
|
|
this.isValid = true;
|
|
this.autoIncFieldName = null;
|
|
this.autoIncColumnNumber = null;
|
|
this.numberOfLobColumns = 0;
|
|
this.numberOfNotPersistentFields = 0;
|
|
|
|
/* New Arrays */
|
|
this.columnNumberToFieldMap = [];
|
|
this.fieldNumberToColumnMap = [];
|
|
this.fieldNumberToFieldMap = [];
|
|
this.fieldNameToFieldMap = {};
|
|
this.foreignKeyMap = {};
|
|
this.dbIndexHandlers = [];
|
|
this.relationshipFields = [];
|
|
|
|
/* Build the first draft of the columnNumberToFieldMap, using only the
|
|
explicitly mapped fields. */
|
|
if (typeof(this.mapping.fields) === 'undefined') {
|
|
this.mapping.fields = [];
|
|
}
|
|
for(i = 0 ; i < this.mapping.fields.length ; i++) {
|
|
f = this.mapping.fields[i];
|
|
udebug.log_detail('DBTableHandler<ctor> field:', f, 'persistent', f.persistent, 'relationship', f.relationship);
|
|
if(f && f.persistent) {
|
|
if (!f.relationship) {
|
|
c = getColumnByName(this.dbTable, f.columnName);
|
|
if(c) {
|
|
n = c.columnNumber;
|
|
this.columnNumberToFieldMap[n] = f;
|
|
f.columnNumber = n;
|
|
f.defaultValue = c.defaultValue;
|
|
f.databaseTypeConverter = c.databaseTypeConverter;
|
|
// use converter or default domain type converter
|
|
if (f.converter) {
|
|
udebug.log_detail('domain type converter for ', f.columnName, ' is user-specified ', f.converter);
|
|
f.domainTypeConverter = f.converter;
|
|
} else {
|
|
udebug.log_detail('domain type converter for ', f.columnName, ' is system-specified ', c.domainTypeConverter);
|
|
f.domainTypeConverter = c.domainTypeConverter;
|
|
}
|
|
} else {
|
|
this.appendErrorMessage(
|
|
'for table ' + dbtable.name + ', field ' + f.fieldName + ': column ' + f.columnName + ' does not exist.');
|
|
}
|
|
} else {
|
|
// relationship field
|
|
this.relationshipFields.push(f);
|
|
}
|
|
} else {
|
|
// increment not-persistent field count
|
|
++this.numberOfNotPersistentFields;
|
|
}
|
|
}
|
|
|
|
/* Now build the implicitly mapped fields and add them to the map */
|
|
stubFields = [];
|
|
if(this.mapping.mapAllColumns) {
|
|
for(i = 0 ; i < this.dbTable.columns.length ; i++) {
|
|
if(! this.columnNumberToFieldMap[i]) {
|
|
c = this.dbTable.columns[i];
|
|
f = new FieldMapping(c.name);
|
|
stubFields.push(f);
|
|
this.columnNumberToFieldMap[i] = f;
|
|
f.columnNumber = i;
|
|
f.defaultValue = c.defaultValue;
|
|
f.databaseTypeConverter = c.databaseTypeConverter;
|
|
// use converter or default domain type converter
|
|
if (f.converter) {
|
|
udebug.log_detail('domain type converter for ', f.columnName, ' is user-specified ', f.converter);
|
|
f.domainTypeConverter = f.converter;
|
|
} else {
|
|
udebug.log_detail('domain type converter for ', f.columnName, ' is system-specified ', c.domainTypeConverter);
|
|
f.domainTypeConverter = c.domainTypeConverter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Total number of mapped fields */
|
|
nMappedFields = this.mapping.fields.length + stubFields.length - this.numberOfNotPersistentFields;
|
|
|
|
/* Create the resolved mapping to be returned by getMapping() */
|
|
this.resolvedMapping = {};
|
|
this.resolvedMapping.database = this.dbTable.database;
|
|
this.resolvedMapping.table = this.dbTable.name;
|
|
this.resolvedMapping.fields = [];
|
|
|
|
/* Build fieldNumberToColumnMap, establishing field order.
|
|
Detect the autoincrement column.
|
|
Also build the remaining fieldNameToFieldMap and fieldNumberToFieldMap. */
|
|
for(i = 0 ; i < this.dbTable.columns.length ; i++) {
|
|
c = this.dbTable.columns[i];
|
|
f = this.columnNumberToFieldMap[i];
|
|
if(c.isAutoincrement) {
|
|
this.autoIncColumnNumber = i;
|
|
this.autoIncFieldName = f.fieldName;
|
|
}
|
|
if(c.isLob) {
|
|
this.numberOfLobColumns++;
|
|
}
|
|
this.resolvedMapping.fields[i] = {};
|
|
if(f) {
|
|
f.fieldNumber = i;
|
|
this.fieldNumberToColumnMap.push(c);
|
|
this.fieldNumberToFieldMap.push(f);
|
|
this.fieldNameToFieldMap[f.fieldName] = f;
|
|
this.resolvedMapping.fields[i].columnName = f.columnName;
|
|
this.resolvedMapping.fields[i].fieldName = f.fieldName;
|
|
this.resolvedMapping.fields[i].persistent = true;
|
|
}
|
|
}
|
|
var map = this.fieldNameToFieldMap;
|
|
// add the relationship fields that are not mapped to columns
|
|
this.relationshipFields.forEach(function(relationship) {
|
|
map[relationship.fieldName] = relationship;
|
|
});
|
|
|
|
if (nMappedFields !== this.fieldNumberToColumnMap.length + this.relationshipFields.length) {
|
|
this.appendErrorMessage('Mismatch between number of mapped fields and columns for ' + ctor.prototype.constructor.name);
|
|
}
|
|
|
|
// build dbIndexHandlers; one for each dbIndex, starting with primary key index 0
|
|
for (i = 0; i < this.dbTable.indexes.length; ++i) {
|
|
// a little fix-up for primary key unique index:
|
|
index = this.dbTable.indexes[i];
|
|
udebug.log_detail('DbTableHandler<ctor> creating DBIndexHandler for', index);
|
|
if (typeof(index.name) === 'undefined') {
|
|
index.name = 'PRIMARY';
|
|
}
|
|
this.dbIndexHandlers.push(new DBIndexHandler(this, index));
|
|
}
|
|
// build foreign key map
|
|
for (i = 0; i < this.dbTable.foreignKeys.length; ++i) {
|
|
foreignKey = this.dbTable.foreignKeys[i];
|
|
this.foreignKeyMap[foreignKey.name] = foreignKey;
|
|
}
|
|
|
|
if (!this.isValid) {
|
|
this.err = new Error(this.errorMessages);
|
|
}
|
|
|
|
if (ctor) {
|
|
// cache this in ctor.prototype.mynode.dbTableHandler
|
|
if (!ctor.prototype.mynode) {
|
|
ctor.prototype.mynode = {};
|
|
}
|
|
if (!ctor.prototype.mynode.dbTableHandler) {
|
|
ctor.prototype.mynode.dbTableHandler = this;
|
|
}
|
|
}
|
|
udebug.log("new completed");
|
|
udebug.log_detail("DBTableHandler<ctor>:\n", this);
|
|
}
|
|
|
|
|
|
/** Append an error message and mark this DBTableHandler as invalid.
|
|
*/
|
|
DBTableHandler.prototype.appendErrorMessage = function(msg) {
|
|
this.errorMessages += '\n' + msg;
|
|
this.isValid = false;
|
|
};
|
|
|
|
|
|
/* DBTableHandler.newResultObject
|
|
IMMEDIATE
|
|
|
|
Create a new object using the constructor function (if set).
|
|
*/
|
|
DBTableHandler.prototype.newResultObject = function(values, adapter) {
|
|
udebug.log("newResultObject");
|
|
stats.result_objects_created++;
|
|
var newDomainObj;
|
|
|
|
if(this.newObjectConstructor && this.newObjectConstructor.prototype) {
|
|
newDomainObj = Object.create(this.newObjectConstructor.prototype);
|
|
}
|
|
else {
|
|
newDomainObj = {};
|
|
}
|
|
|
|
if(this.newObjectConstructor) {
|
|
udebug.log("newResultObject calling user constructor");
|
|
this.newObjectConstructor.call(newDomainObj);
|
|
}
|
|
|
|
if (typeof(values) === 'object') {
|
|
// copy values into the new domain object
|
|
this.setFields(newDomainObj, values, adapter);
|
|
}
|
|
udebug.log("newResultObject done", newDomainObj);
|
|
return newDomainObj;
|
|
};
|
|
|
|
|
|
/* DBTableHandler.newResultObjectFromRow
|
|
* IMMEDIATE
|
|
|
|
* Create a new object using the constructor function (if set).
|
|
* Values for the object's fields come from the row; first the key fields
|
|
* and then the non-key fields. The row contains items named '0', '1', etc.
|
|
* The value for the first key field is in row[offset]. Values obtained
|
|
* from the row are first processed by the db converter and type converter
|
|
* if present.
|
|
*/
|
|
DBTableHandler.prototype.newResultObjectFromRow = function(row, adapter,
|
|
offset, keyFields, nonKeyFields) {
|
|
var fieldIndex;
|
|
var rowValue;
|
|
var field;
|
|
var newDomainObj;
|
|
|
|
udebug.log("newResultObjectFromRow");
|
|
stats.result_objects_created++;
|
|
|
|
if(this.newObjectConstructor && this.newObjectConstructor.prototype) {
|
|
newDomainObj = Object.create(this.newObjectConstructor.prototype);
|
|
} else {
|
|
newDomainObj = {};
|
|
}
|
|
|
|
if(this.newObjectConstructor) {
|
|
udebug.log("newResultObject calling user constructor");
|
|
this.newObjectConstructor.call(newDomainObj);
|
|
}
|
|
// set key field values from row using type converters
|
|
|
|
for (fieldIndex = 0; fieldIndex < keyFields.length; ++fieldIndex) {
|
|
rowValue = row[offset + fieldIndex];
|
|
field = keyFields[fieldIndex];
|
|
this.set(newDomainObj, field.fieldNumber, rowValue, adapter);
|
|
}
|
|
|
|
// set non-key field values from row using type converters
|
|
offset += keyFields.length;
|
|
for (fieldIndex = 0; fieldIndex < nonKeyFields.length; ++fieldIndex) {
|
|
rowValue = row[offset + fieldIndex];
|
|
field = nonKeyFields[fieldIndex];
|
|
this.set(newDomainObj, field.fieldNumber, rowValue, adapter);
|
|
}
|
|
|
|
udebug.log("newResultObjectFromRow done", newDomainObj.constructor.name, newDomainObj);
|
|
return newDomainObj;
|
|
};
|
|
|
|
/** applyMappingToResult(object)
|
|
* IMMEDIATE
|
|
* Apply the table mapping to the result object. The result object
|
|
* has properties corresponding to field names whose values came
|
|
* from the database. If a domain object is needed, a new domain
|
|
* object is created and values are copied from the result object.
|
|
* The result (either the original result object or a new domain
|
|
* object) is returned.
|
|
* @param obj the object to which to apply mapping
|
|
* @return the object to return to the user
|
|
*/
|
|
DBTableHandler.prototype.applyMappingToResult = function(obj, adapter) {
|
|
if (this.newObjectConstructor) {
|
|
// create the domain object from the result
|
|
obj = this.newResultObject(obj, adapter);
|
|
} else {
|
|
this.applyFieldConverters(obj, adapter);
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
|
|
/** applyFieldConverters(object)
|
|
* IMMEDIATE
|
|
* Apply the field converters to an existing object
|
|
*/
|
|
DBTableHandler.prototype.applyFieldConverters = function(obj, adapter) {
|
|
var i, f, value, convertedValue;
|
|
|
|
for (i = 0; i < this.fieldNumberToFieldMap.length; i++) {
|
|
f = this.fieldNumberToFieldMap[i];
|
|
var databaseTypeConverter = f.databaseTypeConverter && f.databaseTypeConverter[adapter];
|
|
if (databaseTypeConverter) {
|
|
value = obj[f.fieldName];
|
|
convertedValue = databaseTypeConverter.fromDB(value);
|
|
obj[f.fieldName] = convertedValue;
|
|
}
|
|
if(f.domainTypeConverter) {
|
|
value = obj[f.fieldName];
|
|
convertedValue = f.domainTypeConverter.fromDB(value, obj, f);
|
|
obj[f.fieldName] = convertedValue;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/* setAutoincrement(object, autoincrementValue)
|
|
* IMMEDIATE
|
|
* Store autoincrement value into object
|
|
*/
|
|
DBTableHandler.prototype.setAutoincrement = function(object, autoincrementValue) {
|
|
if(typeof this.autoIncColumnNumber === 'number') {
|
|
object[this.autoIncFieldName] = autoincrementValue;
|
|
udebug.log("setAutoincrement", this.autoIncFieldName, ":=", autoincrementValue);
|
|
}
|
|
};
|
|
|
|
|
|
/* getMappedFieldCount()
|
|
IMMEDIATE
|
|
Returns the number of fields mapped to columns in the table
|
|
*/
|
|
DBTableHandler.prototype.getMappedFieldCount = function() {
|
|
udebug.log_detail("getMappedFieldCount");
|
|
return this.fieldNumberToColumnMap.length;
|
|
};
|
|
|
|
|
|
/* allColumnsMapped()
|
|
IMMEDIATE
|
|
Boolean: returns True if all columns are mapped
|
|
*/
|
|
DBTableHandler.prototype.allColumnsMapped = function() {
|
|
return (this.dbTable.columns.length === this.fieldNumberToColumnMap.length);
|
|
};
|
|
|
|
/** allFieldsIncluded(values)
|
|
* IMMEDIATE
|
|
* returns array of indexes of fields included in values
|
|
*/
|
|
DBTableHandler.prototype.allFieldsIncluded = function(values) {
|
|
// return a list of fields indexes that are found
|
|
// the caller can easily construct the appropriate database statement
|
|
var i, f, result = [];
|
|
for (i = 0; i < this.fieldNumberToFieldMap.length; ++i) {
|
|
f = this.fieldNumberToFieldMap[i];
|
|
if (typeof(values[i]) !== 'undefined') {
|
|
result.push(i);
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/* getColumnMetadata()
|
|
IMMEDIATE
|
|
|
|
Returns an array containing ColumnMetadata objects in field order
|
|
*/
|
|
DBTableHandler.prototype.getColumnMetadata = function() {
|
|
return this.fieldNumberToColumnMap;
|
|
};
|
|
|
|
|
|
/* IndexMetadata chooseIndex(dbTableHandler, keys)
|
|
Returns the index number to use as an access path.
|
|
From API Context.find():
|
|
* The parameter "keys" may be of any type. Keys must uniquely identify
|
|
* a single row in the database. If keys is a simple type
|
|
* (number or string), then the parameter type must be the
|
|
* same type as or compatible with the primary key type of the mapped object.
|
|
* Otherwise, properties are taken
|
|
* from the parameter and matched against property names in the
|
|
* mapping.
|
|
*/
|
|
function chooseIndex(self, keys, uniqueOnly) {
|
|
udebug.log("chooseIndex");
|
|
var idxs = self.dbTable.indexes;
|
|
var keyFieldNames, firstIdxFieldName;
|
|
var i, j, f, n, index, nmatches, x;
|
|
|
|
udebug.log_detail("chooseIndex for:", JSON.stringify(keys));
|
|
|
|
if(typeof keys === 'number' || typeof keys === 'string') {
|
|
if(idxs[0].columnNumbers.length === 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
/* Keys is an object */
|
|
keyFieldNames = [];
|
|
for (x in keys) {
|
|
// only include properties of the keys itself that are defined and not null
|
|
if (keys.hasOwnProperty(x) && keys[x]) {
|
|
keyFieldNames.push(x);
|
|
}
|
|
}
|
|
|
|
/* First look for a unique index. All columns must match. */
|
|
for(i = 0 ; i < idxs.length ; i++) {
|
|
index = idxs[i];
|
|
if(index.isUnique) {
|
|
udebug.log_detail("Considering:", (index.name || "primary key ") + " for " + JSON.stringify(keys));
|
|
// Each key field resolves to a column, which must be in the index
|
|
nmatches = 0;
|
|
for(j = 0 ; j < index.columnNumbers.length ; j++) {
|
|
n = index.columnNumbers[j];
|
|
f = self.columnNumberToFieldMap[n];
|
|
udebug.log_detail("index part", j, "is column", n, ":", f.fieldName);
|
|
if(typeof keys[f.fieldName] !== 'undefined') {
|
|
nmatches++;
|
|
udebug.log_detail("match! ", nmatches);
|
|
}
|
|
}
|
|
if(nmatches === index.columnNumbers.length) {
|
|
udebug.log("chooseIndex picked unique index", i);
|
|
return i; // all columns are found in the key object
|
|
}
|
|
}
|
|
}
|
|
|
|
// if unique only, return failure
|
|
if (uniqueOnly) {
|
|
udebug.log("chooseIndex for unique index FAILED");
|
|
return -1;
|
|
}
|
|
|
|
/* Then look for an ordered index. A prefix match is OK. */
|
|
/* Return the first suitable index we find (which might not be the best) */
|
|
/* TODO: A better algorithm might be to return the one with the longest train of matches */
|
|
for(i = 0 ; i < idxs.length ; i++) {
|
|
index = idxs[i];
|
|
if(index.isOrdered) {
|
|
// f is the field corresponding to the first column in the index
|
|
f = self.columnNumberToFieldMap[index.columnNumbers[0]];
|
|
if(keyFieldNames.indexOf(f.fieldName) >= 0) {
|
|
udebug.log("chooseIndex picked ordered index", i);
|
|
return i; // this is an ordered index scan
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
udebug.log("chooseIndex FAILED");
|
|
return -1; // didn't find a suitable index
|
|
}
|
|
|
|
|
|
/** Return the property of obj corresponding to fieldNumber.
|
|
* If a domain type converter and/or database type converter is defined, convert the value here.
|
|
* If a fieldValueDefinedListener is passed, notify it via setDefined or setUndefined for each column.
|
|
* Call setDefined if a column value is defined in the object and setUndefined if not.
|
|
*/
|
|
DBTableHandler.prototype.get = function(obj, fieldNumber, adapter, fieldValueDefinedListener) {
|
|
udebug.log_detail("get", fieldNumber);
|
|
if (typeof(obj) === 'string' || typeof(obj) === 'number') {
|
|
if (fieldValueDefinedListener) {
|
|
fieldValueDefinedListener.setDefined(fieldNumber);
|
|
}
|
|
return obj;
|
|
}
|
|
var f = this.fieldNumberToFieldMap[fieldNumber];
|
|
var result;
|
|
if (!f) {
|
|
throw new Error('FatalInternalError: field number does not exist: ' + fieldNumber);
|
|
}
|
|
if(f.domainTypeConverter) {
|
|
result = f.domainTypeConverter.toDB(obj[f.fieldName], obj, f);
|
|
}
|
|
else {
|
|
result = obj[f.fieldName];
|
|
}
|
|
var databaseTypeConverter = f.databaseTypeConverter && f.databaseTypeConverter[adapter];
|
|
if (databaseTypeConverter && result !== undefined) {
|
|
result = databaseTypeConverter.toDB(result);
|
|
}
|
|
if (fieldValueDefinedListener) {
|
|
if (typeof(result) === 'undefined') {
|
|
fieldValueDefinedListener.setUndefined(fieldNumber);
|
|
} else {
|
|
if (this.fieldNumberToColumnMap[fieldNumber].isBinary && result.constructor && result.constructor.name !== 'Buffer') {
|
|
var err = new Error('Binary field with non-Buffer data for field ' + f.fieldName);
|
|
err.sqlstate = '22000';
|
|
fieldValueDefinedListener.err = err;
|
|
}
|
|
fieldValueDefinedListener.setDefined(fieldNumber);
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
|
|
/** Return the property of obj corresponding to fieldNumber.
|
|
*/
|
|
DBTableHandler.prototype.getFieldsSimple = function(obj, fieldNumber) {
|
|
var f;
|
|
f = this.fieldNumberToFieldMap[fieldNumber];
|
|
if(f.domainTypeConverter) {
|
|
return f.domainTypeConverter.toDB(obj[f.fieldName], obj, f);
|
|
}
|
|
return obj[f.fieldName];
|
|
};
|
|
|
|
|
|
/* Return an array of values in field order */
|
|
DBTableHandler.prototype.getFields = function(obj) {
|
|
var i, n, fields;
|
|
fields = [];
|
|
n = this.getMappedFieldCount();
|
|
switch(typeof obj) {
|
|
case 'number':
|
|
case 'string':
|
|
fields.push(obj);
|
|
break;
|
|
default:
|
|
for(i = 0 ; i < n ; i++) { fields.push(this.getFieldsSimple(obj, i)); }
|
|
}
|
|
return fields;
|
|
};
|
|
|
|
|
|
/* Return an array of values in field order */
|
|
DBTableHandler.prototype.getFieldsWithListener = function(obj, adapter, fieldValueDefinedListener) {
|
|
var i, fields = [];
|
|
for( i = 0 ; i < this.getMappedFieldCount() ; i ++) {
|
|
fields[i] = this.get(obj, i, adapter, fieldValueDefinedListener);
|
|
}
|
|
return fields;
|
|
};
|
|
|
|
|
|
/* Set field to value */
|
|
DBTableHandler.prototype.set = function(obj, fieldNumber, value, adapter) {
|
|
udebug.log_detail("set", fieldNumber);
|
|
var f = this.fieldNumberToFieldMap[fieldNumber];
|
|
var userValue = value;
|
|
var databaseTypeConverter;
|
|
if(f) {
|
|
databaseTypeConverter = f.databaseTypeConverter && f.databaseTypeConverter[adapter];
|
|
if (databaseTypeConverter) {
|
|
userValue = databaseTypeConverter.fromDB(value);
|
|
}
|
|
if(f.domainTypeConverter) {
|
|
userValue = f.domainTypeConverter.fromDB(userValue, obj, f);
|
|
}
|
|
obj[f.fieldName] = userValue;
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
/* Set all member values of object from a value object, which
|
|
* has properties corresponding to field names.
|
|
* User-defined column conversion is handled in the set method.
|
|
*/
|
|
DBTableHandler.prototype.setFields = function(obj, values, adapter) {
|
|
var i, f, value, columnName, fieldName;
|
|
for (i = 0; i < this.fieldNumberToFieldMap.length; ++i) {
|
|
f = this.fieldNumberToFieldMap[i];
|
|
columnName = f.columnName;
|
|
fieldName = f.fieldName;
|
|
value = values[fieldName];
|
|
if (value !== undefined) {
|
|
this.set(obj, i, value, adapter);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/* DBIndexHandler constructor and prototype */
|
|
DBIndexHandler = function (parent, dbIndex) {
|
|
udebug.log("DBIndexHandler constructor");
|
|
stats.DBIndexHandler_created++;
|
|
var i, colNo;
|
|
|
|
this.tableHandler = parent;
|
|
this.dbIndex = dbIndex;
|
|
this.fieldNumberToColumnMap = [];
|
|
this.fieldNumberToFieldMap = [];
|
|
|
|
for(i = 0 ; i < dbIndex.columnNumbers.length ; i++) {
|
|
colNo = dbIndex.columnNumbers[i];
|
|
this.fieldNumberToFieldMap[i] = parent.columnNumberToFieldMap[colNo];
|
|
this.fieldNumberToColumnMap[i] = parent.dbTable.columns[colNo];
|
|
}
|
|
|
|
if(i === 1) { // One-column index
|
|
this.singleColumn = this.fieldNumberToColumnMap[0];
|
|
} else {
|
|
this.singleColumn = null;
|
|
}
|
|
};
|
|
|
|
/* DBIndexHandler inherits some methods from DBTableHandler */
|
|
DBIndexHandler.prototype = {
|
|
getMappedFieldCount : DBTableHandler.prototype.getMappedFieldCount,
|
|
get : DBTableHandler.prototype.get,
|
|
getFieldsSimple : DBTableHandler.prototype.getFieldsSimple,
|
|
getFields : DBTableHandler.prototype.getFields,
|
|
getColumnMetadata : DBTableHandler.prototype.getColumnMetadata
|
|
};
|
|
|
|
|
|
/* DBIndexHandler getIndexHandler(Object keys)
|
|
IMMEDIATE
|
|
|
|
Given an object containing keys as defined in API Context.find(),
|
|
choose an index to use as an access path for the operation,
|
|
and return a DBIndexHandler for that index.
|
|
*/
|
|
DBTableHandler.prototype.getIndexHandler = function(keys, uniqueOnly) {
|
|
udebug.log("getIndexHandler");
|
|
var idx = chooseIndex(this, keys, uniqueOnly);
|
|
var handler = null;
|
|
if (idx !== -1) {
|
|
handler = this.dbIndexHandlers[idx];
|
|
}
|
|
return handler;
|
|
};
|
|
|
|
DBTableHandler.prototype.getForeignKey = function(foreignKeyName) {
|
|
return this.foreignKeyMap[foreignKeyName];
|
|
};
|
|
|
|
|
|
exports.DBTableHandler = DBTableHandler;
|