Merge pull request #127 from strongloop/feature/refactor-connector-base
[2.0] Feature/refactor connector base
This commit is contained in:
commit
9a03450e76
8
index.js
8
index.js
|
@ -1,16 +1,8 @@
|
|||
exports.ModelBuilder = exports.LDL = require('./lib/model-builder.js').ModelBuilder;
|
||||
exports.DataSource = exports.Schema = require('./lib/datasource.js').DataSource;
|
||||
exports.ModelBaseClass = require('./lib/model.js');
|
||||
exports.Connector = require('./lib/connector.js');
|
||||
exports.ValidationError = require('./lib/validations.js').ValidationError;
|
||||
|
||||
var baseSQL = './lib/sql';
|
||||
|
||||
exports.__defineGetter__('BaseSQL', function () {
|
||||
return require(baseSQL);
|
||||
});
|
||||
|
||||
|
||||
exports.__defineGetter__('version', function () {
|
||||
return require('./package.json').version;
|
||||
});
|
||||
|
|
170
lib/connector.js
170
lib/connector.js
|
@ -1,170 +0,0 @@
|
|||
module.exports = Connector;
|
||||
|
||||
/**
|
||||
* Base class for LooopBack connector. This is more a collection of useful
|
||||
* methods for connectors than a super class
|
||||
* @constructor
|
||||
*/
|
||||
function Connector(name, settings) {
|
||||
this._models = {};
|
||||
this.name = name;
|
||||
this.settings = settings || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relational property to indicate the backend is a relational DB
|
||||
* @type {boolean}
|
||||
*/
|
||||
Connector.prototype.relational = false;
|
||||
|
||||
/**
|
||||
* Get types associated with the connector
|
||||
* @returns {String[]} The types for the connector
|
||||
*/
|
||||
Connector.prototype.getTypes = function() {
|
||||
return ['db', 'nosql'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the default data type for ID
|
||||
* @returns {Function} The default type for ID
|
||||
*/
|
||||
Connector.prototype.getDefaultIdType = function() {
|
||||
return String;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metadata for the connector
|
||||
* @returns {Object} The metadata object
|
||||
* @property {String} type The type for the backend
|
||||
* @property {Function} defaultIdType The default id type
|
||||
* @property {Boolean} [isRelational] If the connector represents a relational database
|
||||
* @property {Object} schemaForSettings The schema for settings object
|
||||
*/
|
||||
Connector.prototype.getMedadata = function () {
|
||||
if (!this._metadata) {
|
||||
this._metadata = {
|
||||
types: this.getTypes(),
|
||||
defaultIdType: this.getDefaultIdType(),
|
||||
isRelational: this.isRelational || (this.getTypes().indexOf('rdbms') !== -1),
|
||||
schemaForSettings: {}
|
||||
};
|
||||
}
|
||||
return this._metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a command with given parameters
|
||||
* @param {String} command The command such as SQL
|
||||
* @param {Object[]} [params] An array of parameters
|
||||
* @param {Function} [callback] The callback function
|
||||
*/
|
||||
Connector.prototype.execute = function (command, params, callback) {
|
||||
throw new Error('query method should be declared in connector');
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up the data source by model name
|
||||
* @param {String} model The model name
|
||||
* @returns {DataSource} The data source
|
||||
*/
|
||||
Connector.prototype.getDataSource = function (model) {
|
||||
var m = this._models[model];
|
||||
if (!m) {
|
||||
console.trace('Model not found: ' + model);
|
||||
}
|
||||
return m && m.model.dataSource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id property name
|
||||
* @param {String} model The model name
|
||||
* @returns {String} The id property name
|
||||
*/
|
||||
Connector.prototype.idName = function (model) {
|
||||
return this.getDataSource(model).idName(model);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id property names
|
||||
* @param {String} model The model name
|
||||
* @returns {[String]} The id property names
|
||||
*/
|
||||
Connector.prototype.idNames = function (model) {
|
||||
return this.getDataSource(model).idNames(model);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id index (sequence number, starting from 1)
|
||||
* @param {String} model The model name
|
||||
* @param {String} prop The property name
|
||||
* @returns {Number} The id index, undefined if the property is not part of the primary key
|
||||
*/
|
||||
Connector.prototype.id = function (model, prop) {
|
||||
var p = this._models[model].properties[prop];
|
||||
if (!p) {
|
||||
console.trace('Property not found: ' + model + '.' + prop);
|
||||
}
|
||||
return p.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to be called by DataSource for defining a model
|
||||
* @param {Object} modelDefinition The model definition
|
||||
*/
|
||||
Connector.prototype.define = function (modelDefinition) {
|
||||
if (!modelDefinition.settings) {
|
||||
modelDefinition.settings = {};
|
||||
}
|
||||
this._models[modelDefinition.model.modelName] = modelDefinition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to be called by DataSource for defining a model property
|
||||
* @param {String} model The model name
|
||||
* @param {String} propertyName The property name
|
||||
* @param {Object} propertyDefinition The object for property metadata
|
||||
*/
|
||||
Connector.prototype.defineProperty = function (model, propertyName, propertyDefinition) {
|
||||
this._models[model].properties[propertyName] = propertyDefinition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from the connector
|
||||
*/
|
||||
Connector.prototype.disconnect = function disconnect(cb) {
|
||||
// NO-OP
|
||||
cb && process.nextTick(cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id value for the given model
|
||||
* @param {String} model The model name
|
||||
* @param {Object} data The model instance data
|
||||
* @returns {*} The id value
|
||||
*
|
||||
*/
|
||||
Connector.prototype.getIdValue = function (model, data) {
|
||||
return data && data[this.idName(model)];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the id value for the given model
|
||||
* @param {String} model The model name
|
||||
* @param {Object} data The model instance data
|
||||
* @param {*} value The id value
|
||||
*
|
||||
*/
|
||||
Connector.prototype.setIdValue = function (model, data, value) {
|
||||
if (data) {
|
||||
data[this.idName(model)] = value;
|
||||
}
|
||||
};
|
||||
|
||||
Connector.prototype.getType = function () {
|
||||
return this.type;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
var util = require('util');
|
||||
var Connector = require('../connector');
|
||||
var Connector = require('loopback-connector').Connector;
|
||||
var geo = require('../geo');
|
||||
var utils = require('../utils');
|
||||
var fs = require('fs');
|
||||
|
|
|
@ -432,6 +432,15 @@ ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyD
|
|||
this.models[model].registerProperty(propertyName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Define a new value type that can be used in model schemas as a property type.
|
||||
* @param {function()} type Type constructor.
|
||||
* @param {string[]=} aliases Optional list of alternative names for this type.
|
||||
*/
|
||||
ModelBuilder.prototype.defineValueType = function(type, aliases) {
|
||||
ModelBuilder.registerType(type, aliases);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extend existing model with bunch of properties
|
||||
*
|
||||
|
|
392
lib/sql.js
392
lib/sql.js
|
@ -1,392 +0,0 @@
|
|||
var util = require('util');
|
||||
var async = require('async');
|
||||
var assert = require('assert');
|
||||
var Connector = require('./connector');
|
||||
|
||||
module.exports = BaseSQL;
|
||||
|
||||
/**
|
||||
* Base class for connectors that are backed by relational databases/SQL
|
||||
* @class
|
||||
*/
|
||||
function BaseSQL() {
|
||||
Connector.apply(this, [].slice.call(arguments));
|
||||
}
|
||||
|
||||
util.inherits(BaseSQL, Connector);
|
||||
|
||||
/**
|
||||
* Set the relational property to indicate the backend is a relational DB
|
||||
* @type {boolean}
|
||||
*/
|
||||
BaseSQL.prototype.relational = true;
|
||||
|
||||
/**
|
||||
* Get types associated with the connector
|
||||
* Returns {String[]} The types for the connector
|
||||
*/
|
||||
BaseSQL.prototype.getTypes = function() {
|
||||
return ['db', 'rdbms', 'sql'];
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the default data type for ID
|
||||
* Returns {Function}
|
||||
*/
|
||||
BaseSQL.prototype.getDefaultIdType = function() {
|
||||
return Number;
|
||||
};
|
||||
|
||||
BaseSQL.prototype.query = function () {
|
||||
throw new Error('query method should be declared in connector');
|
||||
};
|
||||
|
||||
BaseSQL.prototype.command = function (sql, params, callback) {
|
||||
return this.query(sql, params, callback);
|
||||
};
|
||||
|
||||
BaseSQL.prototype.queryOne = function (sql, callback) {
|
||||
return this.query(sql, function (err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(err, data && data[0]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the table name for a given model.
|
||||
* Returns the table name (String).
|
||||
* @param {String} model The model name
|
||||
*/
|
||||
BaseSQL.prototype.table = function (model) {
|
||||
var name = this.getDataSource(model).tableName(model);
|
||||
var dbName = this.dbName;
|
||||
if (typeof dbName === 'function') {
|
||||
name = dbName(name);
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the column name for given model property
|
||||
* @param {String} model The model name
|
||||
* @param {String} property The property name
|
||||
* @returns {String} The column name
|
||||
*/
|
||||
BaseSQL.prototype.column = function (model, property) {
|
||||
var name = this.getDataSource(model).columnName(model, property);
|
||||
var dbName = this.dbName;
|
||||
if (typeof dbName === 'function') {
|
||||
name = dbName(name);
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the column name for given model property
|
||||
* @param {String} model The model name
|
||||
* @param {String} property The property name
|
||||
* @returns {Object} The column metadata
|
||||
*/
|
||||
BaseSQL.prototype.columnMetadata = function (model, property) {
|
||||
return this.getDataSource(model).columnMetadata(model, property);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the corresponding property name for a given column name
|
||||
* @param {String} model The model name
|
||||
* @param {String} column The column name
|
||||
* @returns {String} The property name for a given column
|
||||
*/
|
||||
BaseSQL.prototype.propertyName = function (model, column) {
|
||||
var props = this._models[model].properties;
|
||||
for (var p in props) {
|
||||
if (this.column(model, p) === column) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id column name
|
||||
* @param {String} model The model name
|
||||
* @returns {String} The column name
|
||||
*/
|
||||
BaseSQL.prototype.idColumn = function (model) {
|
||||
var name = this.getDataSource(model).idColumnName(model);
|
||||
var dbName = this.dbName;
|
||||
if (typeof dbName === 'function') {
|
||||
name = dbName(name);
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the escaped id column name
|
||||
* @param {String} model The model name
|
||||
* @returns {String} the escaped id column name
|
||||
*/
|
||||
BaseSQL.prototype.idColumnEscaped = function (model) {
|
||||
return this.escapeName(this.getDataSource(model).idColumnName(model));
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape the name for the underlying database
|
||||
* @param {String} name The name
|
||||
*/
|
||||
BaseSQL.prototype.escapeName = function (name) {
|
||||
throw new Error('escapeName method should be declared in connector');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the escaped table name
|
||||
* @param {String} model The model name
|
||||
* @returns {String} the escaped table name
|
||||
*/
|
||||
BaseSQL.prototype.tableEscaped = function (model) {
|
||||
return this.escapeName(this.table(model));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the escaped column name for a given model property
|
||||
* @param {String} model The model name
|
||||
* @param {String} property The property name
|
||||
* @returns {String} The escaped column name
|
||||
*/
|
||||
BaseSQL.prototype.columnEscaped = function (model, property) {
|
||||
return this.escapeName(this.column(model, property));
|
||||
};
|
||||
|
||||
function isIdValuePresent(idValue, callback, returningNull) {
|
||||
try {
|
||||
assert(idValue !== null && idValue !== undefined, 'id value is required');
|
||||
return true;
|
||||
} catch (err) {
|
||||
process.nextTick(function () {
|
||||
callback && callback(returningNull ? null: err);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Save the model instance into the backend store
|
||||
* @param {String} model The model name
|
||||
* @param {Object} data The model instance data
|
||||
* @param {Function} callback The callback function
|
||||
*/
|
||||
BaseSQL.prototype.save = function (model, data, callback) {
|
||||
var idName = this.getDataSource(model).idName(model);
|
||||
var idValue = data[idName];
|
||||
|
||||
if (!isIdValuePresent(idValue, callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
idValue = this._escapeIdValue(model, idValue);
|
||||
var sql = 'UPDATE ' + this.tableEscaped(model) + ' SET '
|
||||
+ this.toFields(model, data) + ' WHERE ' + this.idColumnEscaped(model) + ' = '
|
||||
+ idValue;
|
||||
|
||||
this.query(sql, function (err, result) {
|
||||
callback && callback(err, result);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a model instance exists for the given id value
|
||||
* @param {String} model The model name
|
||||
* @param {*} id The id value
|
||||
* @param {Function} callback The callback function
|
||||
*/
|
||||
BaseSQL.prototype.exists = function (model, id, callback) {
|
||||
if (!isIdValuePresent(id, callback, true)) {
|
||||
return;
|
||||
}
|
||||
var sql = 'SELECT 1 FROM ' +
|
||||
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = '
|
||||
+ this._escapeIdValue(model, id) + ' LIMIT 1';
|
||||
|
||||
this.query(sql, function (err, data) {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
callback && callback(null, data.length >= 1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find a model instance by id
|
||||
* @param {String} model The model name
|
||||
* @param {*} id The id value
|
||||
* @param {Function} callback The callback function
|
||||
*/
|
||||
BaseSQL.prototype.find = function find(model, id, callback) {
|
||||
if (!isIdValuePresent(id, callback, true)) {
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
var idQuery = this.idColumnEscaped(model) + ' = ' + this._escapeIdValue(model, id);
|
||||
var sql = 'SELECT * FROM ' +
|
||||
this.tableEscaped(model) + ' WHERE ' + idQuery + ' LIMIT 1';
|
||||
|
||||
this.query(sql, function (err, data) {
|
||||
var result = (data && data.length >= 1) ? data[0] : null;
|
||||
callback && callback(err, self.fromDatabase(model, result));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a model instance by id value
|
||||
* @param {String} model The model name
|
||||
* @param {*} id The id value
|
||||
* @param {Function} callback The callback function
|
||||
*/
|
||||
BaseSQL.prototype.delete = BaseSQL.prototype.destroy = function destroy(model, id, callback) {
|
||||
if (!isIdValuePresent(id, callback, true)) {
|
||||
return;
|
||||
}
|
||||
var sql = 'DELETE FROM ' +
|
||||
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = '
|
||||
+ this._escapeIdValue(model, id);
|
||||
|
||||
this.command(sql, function (err, result) {
|
||||
callback && callback(err, result);
|
||||
});
|
||||
};
|
||||
|
||||
BaseSQL.prototype._escapeIdValue = function(model, idValue) {
|
||||
var idProp = this.getDataSource(model).idProperty(model);
|
||||
if(typeof this.toDatabase === 'function') {
|
||||
return this.toDatabase(idProp, idValue);
|
||||
} else {
|
||||
if(idProp.type === Number) {
|
||||
return idValue;
|
||||
} else {
|
||||
return "'" + idValue + "'";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all model instances
|
||||
*
|
||||
* @param {String} model The model name
|
||||
* @param {Function} callback The callback function
|
||||
*/
|
||||
BaseSQL.prototype.deleteAll = BaseSQL.prototype.destroyAll = function destroyAll(model, callback) {
|
||||
this.command('DELETE FROM ' + this.tableEscaped(model), function (err, result) {
|
||||
callback && callback(err, result);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Count all model instances by the where filter
|
||||
*
|
||||
* @param {String} model The model name
|
||||
* @param {Function} callback The callback function
|
||||
* @param {Object} where The where clause
|
||||
*/
|
||||
BaseSQL.prototype.count = function count(model, callback, where) {
|
||||
var self = this;
|
||||
var props = this._models[model].properties;
|
||||
|
||||
this.queryOne('SELECT count(*) as cnt FROM ' +
|
||||
this.tableEscaped(model) + ' ' + buildWhere(where), function (err, res) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(err, res && res.cnt);
|
||||
});
|
||||
|
||||
function buildWhere(conds) {
|
||||
var cs = [];
|
||||
Object.keys(conds || {}).forEach(function (key) {
|
||||
var keyEscaped = self.columnEscaped(model, key);
|
||||
if (conds[key] === null) {
|
||||
cs.push(keyEscaped + ' IS NULL');
|
||||
} else {
|
||||
cs.push(keyEscaped + ' = ' + self.toDatabase(props[key], conds[key]));
|
||||
}
|
||||
});
|
||||
return cs.length ? ' WHERE ' + cs.join(' AND ') : '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update attributes for a given model instance
|
||||
* @param {String} model The model name
|
||||
* @param {*} id The id value
|
||||
* @param {Object} data The model data instance containing all properties to be updated
|
||||
* @param {Function} cb The callback function
|
||||
*/
|
||||
BaseSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) {
|
||||
if (!isIdValuePresent(id, cb)) {
|
||||
return;
|
||||
}
|
||||
var idName = this.getDataSource(model).idName(model);
|
||||
data[idName] = id;
|
||||
this.save(model, data, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from the connector
|
||||
*/
|
||||
BaseSQL.prototype.disconnect = function disconnect() {
|
||||
// No operation
|
||||
};
|
||||
|
||||
/**
|
||||
* Recreate the tables for the given models
|
||||
* @param {[String]|String} [models] A model name or an array of model names,
|
||||
* if not present, apply to all models defined in the connector
|
||||
* @param {Function} [cb] The callback function
|
||||
*/
|
||||
BaseSQL.prototype.automigrate = function (models, cb) {
|
||||
var self = this;
|
||||
|
||||
if ((!cb) && ('function' === typeof models)) {
|
||||
cb = models;
|
||||
models = undefined;
|
||||
}
|
||||
// First argument is a model name
|
||||
if ('string' === typeof models) {
|
||||
models = [models];
|
||||
}
|
||||
|
||||
models = models || Object.keys(self._models);
|
||||
async.each(models, function (model, callback) {
|
||||
if (model in self._models) {
|
||||
self.dropTable(model, function (err, result) {
|
||||
self.createTable(model, function (err, result) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
callback(err, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Drop the table for the given model from the database
|
||||
* @param {String} model The model name
|
||||
* @param {Function} [cb] The callback function
|
||||
*/
|
||||
BaseSQL.prototype.dropTable = function (model, cb) {
|
||||
this.command('DROP TABLE IF EXISTS ' + this.tableEscaped(model), cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the table for the given model
|
||||
* @param {String} model The model name
|
||||
* @param {Function} [cb] The callback function
|
||||
*/
|
||||
|
||||
BaseSQL.prototype.createTable = function (model, cb) {
|
||||
this.command('CREATE TABLE ' + this.tableEscaped(model) +
|
||||
' (\n ' + this.propertiesSQL(model) + '\n)', cb);
|
||||
};
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
"dependencies": {
|
||||
"async": "~0.8.0",
|
||||
"inflection": "~1.3.5",
|
||||
"loopback-connector": "1.x",
|
||||
"traverse": "~0.6.6",
|
||||
"qs": "~0.6.6",
|
||||
"debug": "~0.8.1"
|
||||
|
|
Loading…
Reference in New Issue