From a86648ed7b7c259e04a7bbc308f08c4e53f8ef46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 23 May 2014 11:03:24 +0200 Subject: [PATCH 1/2] Replace connector base with loopback-connector Remove references to Connector and BaseSQL, connectors should require() loopback-connector instead of loopback-datasource-juggler. --- index.js | 8 - lib/connector.js | 170 ----------------- lib/connectors/memory.js | 2 +- lib/sql.js | 392 --------------------------------------- package.json | 1 + 5 files changed, 2 insertions(+), 571 deletions(-) delete mode 100644 lib/connector.js delete mode 100644 lib/sql.js diff --git a/index.js b/index.js index 0090da6b..e3eacac0 100644 --- a/index.js +++ b/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; }); diff --git a/lib/connector.js b/lib/connector.js deleted file mode 100644 index 86f0998c..00000000 --- a/lib/connector.js +++ /dev/null @@ -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; -}; - - - - diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index ce466221..b36d9d31 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -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'); diff --git a/lib/sql.js b/lib/sql.js deleted file mode 100644 index 1777602e..00000000 --- a/lib/sql.js +++ /dev/null @@ -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); -}; - diff --git a/package.json b/package.json index 333da546..f359c56c 100644 --- a/package.json +++ b/package.json @@ -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" From d8b4f5833aa66d576196a57437e9d9bd4509b57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 23 May 2014 13:18:06 +0200 Subject: [PATCH 2/2] ModelBuilder: add `prototype.defineValueType` Add a shortcut for registering a new value type. The current implementation registers the type in the singleton registry `ModelBuilder.schemaTypes`. The API should allow us to to change the implementation to register the type in the scope of ModelBuilder instance only. --- lib/model-builder.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/model-builder.js b/lib/model-builder.js index 786978ae..90601ccf 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -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 *