From e54aa56ceeed6ffc9d51279491b828971464085f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 12 Feb 2014 16:57:06 -0800 Subject: [PATCH] Reformat code --- docs.json | 12 +- example/app.js | 30 +- lib/discovery.js | 662 +++++++------- lib/enumFactory.js | 68 +- lib/mysql.js | 1659 +++++++++++++++++------------------ test/connection.test.js | 163 ++-- test/datatypes.test.js | 196 ++--- test/imported.test.js | 12 +- test/init.js | 34 +- test/migration.test.js | 769 ++++++++-------- test/mysql.discover.test.js | 188 ++-- 11 files changed, 1888 insertions(+), 1905 deletions(-) diff --git a/docs.json b/docs.json index dd74bbf..339b0c1 100644 --- a/docs.json +++ b/docs.json @@ -1,8 +1,14 @@ { -"content": [ - {"title": "LoopBack MySQL Connector API", "depth": 2}, + "content": [ + { + "title": "LoopBack MySQL Connector API", + "depth": 2 + }, "lib/mysql.js", - {"title": "MySQL Discovery API", "depth": 2}, + { + "title": "MySQL Discovery API", + "depth": 2 + }, "lib/discovery.js" ], "codeSectionDepth": 3 diff --git a/example/app.js b/example/app.js index 7be9eaf..cf633f6 100644 --- a/example/app.js +++ b/example/app.js @@ -5,19 +5,18 @@ var config = require('rc')('loopback', {dev: {mysql: {}}}).dev.mysql; var ds = new DataSource(require('../'), config); function show(err, models) { - if (err) { - console.error(err); - } else { - console.log(models); - if (models) { - models.forEach(function (m) { - console.dir(m); - }); - } + if (err) { + console.error(err); + } else { + console.log(models); + if (models) { + models.forEach(function (m) { + console.dir(m); + }); } + } } - ds.discoverModelDefinitions({views: true, limit: 20}, show); ds.discoverModelProperties('customer', show); @@ -25,16 +24,15 @@ ds.discoverModelProperties('customer', show); ds.discoverModelProperties('location', {owner: 'strongloop'}, show); ds.discoverPrimaryKeys('customer', show); -ds.discoverForeignKeys('inventory', show); - -ds.discoverExportedForeignKeys('location', show); +ds.discoverForeignKeys('inventory', show); +ds.discoverExportedForeignKeys('location', show); ds.discoverAndBuildModels('weapon', {owner: 'strongloop', visited: {}, associations: true}, function (err, models) { - for (var m in models) { - models[m].all(show); - } + for (var m in models) { + models[m].all(show); + } }); diff --git a/lib/discovery.js b/lib/discovery.js index 4df3684..1f08894 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -1,357 +1,357 @@ module.exports = mixinDiscovery; function mixinDiscovery(MySQL) { - var async = require('async'); + var async = require('async'); - function paginateSQL(sql, orderBy, options) { - options = options || {}; - var limit = ''; - if (options.offset || options.skip || options.limit) { - limit = ' LIMIT ' + (options.offset || options.skip || 0); // Offset starts from 0 - if (options.limit) { - limit = limit + ',' + options.limit; - } - } - if(!orderBy) { - sql += ' ORDER BY ' + orderBy; - } - return sql + limit; + function paginateSQL(sql, orderBy, options) { + options = options || {}; + var limit = ''; + if (options.offset || options.skip || options.limit) { + limit = ' LIMIT ' + (options.offset || options.skip || 0); // Offset starts from 0 + if (options.limit) { + limit = limit + ',' + options.limit; + } } - - /*! - * Build sql for listing tables - * @param options {all: for all owners, owner: for a given owner} - * @returns {string} The sql statement - */ - function queryTables(options) { - var sqlTables = null; - var owner = options.owner || options.schema; - - if (options.all && !owner) { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' - + ' FROM information_schema.tables', 'table_schema, table_name', options); - } else if (owner) { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' - + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); - } else { - sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.tables', - 'table_name', options); - } - return sqlTables; + if (!orderBy) { + sql += ' ORDER BY ' + orderBy; } + return sql + limit; + } - /*! - * Build sql for listing views - * @param options {all: for all owners, owner: for a given owner} - * @returns {string} The sql statement - */ - function queryViews(options) { - var sqlViews = null; - if (options.views) { + /*! + * Build sql for listing tables + * @param options {all: for all owners, owner: for a given owner} + * @returns {string} The sql statement + */ + function queryTables(options) { + var sqlTables = null; + var owner = options.owner || options.schema; - var owner = options.owner || options.schema; - - if (options.all && !owner) { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.views', - 'table_schema, table_name', options); - } else if (owner) { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', - 'table_schema, table_name', options); - } else { - sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' - + ' table_schema AS "owner" FROM information_schema.views', - 'table_name', options); - } - } - return sqlViews; + if (options.all && !owner) { + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + ' FROM information_schema.tables', 'table_schema, table_name', options); + } else if (owner) { + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"' + + ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options); + } else { + sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.tables', + 'table_name', options); } + return sqlTables; + } - /** - * Discover model definitions - * - * @param {Object} options Options for discovery - * @param {Function} [cb] The callback function - */ - MySQL.prototype.discoverModelDefinitions = function (options, cb) { - if (!cb && typeof options === 'function') { - cb = options; - options = {}; + /*! + * Build sql for listing views + * @param options {all: for all owners, owner: for a given owner} + * @returns {string} The sql statement + */ + function queryViews(options) { + var sqlViews = null; + if (options.views) { + + var owner = options.owner || options.schema; + + if (options.all && !owner) { + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views', + 'table_schema, table_name', options); + } else if (owner) { + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'', + 'table_schema, table_name', options); + } else { + sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",' + + ' table_schema AS "owner" FROM information_schema.views', + 'table_name', options); + } + } + return sqlViews; + } + + /** + * Discover model definitions + * + * @param {Object} options Options for discovery + * @param {Function} [cb] The callback function + */ + MySQL.prototype.discoverModelDefinitions = function (options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + + var self = this; + var calls = [function (callback) { + self.query(queryTables(options), callback); + }]; + + if (options.views) { + calls.push(function (callback) { + self.query(queryViews(options), callback); + }); + } + async.parallel(calls, function (err, data) { + if (err) { + cb(err, data); + } else { + var merged = []; + merged = merged.concat(data.shift()); + if (data.length) { + merged = merged.concat(data.shift()); } - options = options || {}; + cb(err, merged); + } + }); + }; - var self = this; - var calls = [function (callback) { - self.query(queryTables(options), callback); - }]; + /*! + * Normalize the arguments + * @param table string, required + * @param options object, optional + * @param cb function, optional + */ + function getArgs(table, options, cb) { + if ('string' !== typeof table || !table) { + throw new Error('table is a required string argument: ' + table); + } + options = options || {}; + if (!cb && 'function' === typeof options) { + cb = options; + options = {}; + } + if (typeof options !== 'object') { + throw new Error('options must be an object: ' + options); + } + return { + owner: options.owner || options.schema, + table: table, + options: options, + cb: cb + }; + } - if (options.views) { - calls.push(function (callback) { - self.query(queryViews(options), callback); - }); - } - async.parallel(calls, function (err, data) { - if (err) { - cb(err, data); - } else { - var merged = []; - merged = merged.concat(data.shift()); - if (data.length) { - merged = merged.concat(data.shift()); - } - cb(err, merged); - } + /*! + * Build the sql statement to query columns for a given table + * @param owner + * @param table + * @returns {String} The sql statement + */ + function queryColumns(owner, table) { + var sql = null; + if (owner) { + sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + + ' character_octet_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + + ' FROM information_schema.columns' + + ' WHERE table_schema=\'' + owner + '\'' + + (table ? ' AND table_name=\'' + table + '\'' : ''), + 'table_name, ordinal_position', {}); + } else { + sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' + + ' character_octet_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' + + ' FROM information_schema.columns' + + (table ? ' WHERE table_name=\'' + table + '\'' : ''), + 'table_name, ordinal_position', {}); + } + return sql; + } + + /** + * Discover model properties from a table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + * + */ + MySQL.prototype.discoverModelProperties = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryColumns(owner, table); + var callback = function (err, results) { + if (err) { + cb(err, results); + } else { + results.map(function (r) { + r.type = mysqlDataTypeToJSONType(r.dataType, r.dataLength); }); + cb(err, results); + } }; + this.query(sql, callback); + }; - /*! - * Normalize the arguments - * @param table string, required - * @param options object, optional - * @param cb function, optional - */ - function getArgs(table, options, cb) { - if ('string' !== typeof table || !table) { - throw new Error('table is a required string argument: ' + table); - } - options = options || {}; - if (!cb && 'function' === typeof options) { - cb = options; - options = {}; - } - if (typeof options !== 'object') { - throw new Error('options must be an object: ' + options); - } - return { - owner: options.owner || options.schema, - table: table, - options: options, - cb: cb - }; - } - - /*! - * Build the sql statement to query columns for a given table - * @param owner - * @param table - * @returns {String} The sql statement - */ - function queryColumns(owner, table) { - var sql = null; - if (owner) { - sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' - + ' character_octet_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' - + ' FROM information_schema.columns' - + ' WHERE table_schema=\'' + owner + '\'' - + (table ? ' AND table_name=\'' + table + '\'' : ''), - 'table_name, ordinal_position', {}); - } else { - sql = paginateSQL('SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",' - + ' character_octet_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"' - + ' FROM information_schema.columns' - + (table ? ' WHERE table_name=\'' + table + '\'' : ''), - 'table_name, ordinal_position', {}); - } - return sql; - } - - /** - * Discover model properties from a table - * @param {String} table The table name - * @param {Object} options The options for discovery - * @param {Function} [cb] The callback function - * - */ - MySQL.prototype.discoverModelProperties = function (table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryColumns(owner, table); - var callback = function (err, results) { - if (err) { - cb(err, results); - } else { - results.map(function (r) { - r.type = mysqlDataTypeToJSONType(r.dataType, r.dataLength); - }); - cb(err, results); - } - }; - this.query(sql, callback); - }; - - /*! - * Build the sql statement for querying primary keys of a given table - * @param owner - * @param table - * @returns {string} - */ + /*! + * Build the sql statement for querying primary keys of a given table + * @param owner + * @param table + * @returns {string} + */ // http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) - function queryForPrimaryKeys(owner, table) { - var sql = 'SELECT table_schema AS "owner", ' - + 'table_name AS "tableName", column_name AS "columnName", ordinal_position AS "keySeq", constraint_name AS "pkName" FROM' - + ' information_schema.key_column_usage' - + ' WHERE constraint_name=\'PRIMARY\''; + function queryForPrimaryKeys(owner, table) { + var sql = 'SELECT table_schema AS "owner", ' + + 'table_name AS "tableName", column_name AS "columnName", ordinal_position AS "keySeq", constraint_name AS "pkName" FROM' + + ' information_schema.key_column_usage' + + ' WHERE constraint_name=\'PRIMARY\''; - if (owner) { - sql += ' AND table_schema=\'' + owner + '\''; - } - if (table) { - sql += ' AND table_name=\'' + table + '\''; - } - sql += ' ORDER BY table_schema, constraint_name, table_name, ordinal_position'; - return sql; + if (owner) { + sql += ' AND table_schema=\'' + owner + '\''; } - - /** - * Discover primary keys for a given table - * @param {String} table The table name - * @param {Object} options The options for discovery - * @param {Function} [cb] The callback function - */ - MySQL.prototype.discoverPrimaryKeys = function (table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryForPrimaryKeys(owner, table); - this.query(sql, cb); - }; - - /*! - * Build the sql statement for querying foreign keys of a given table - * @param owner - * @param table - * @returns {string} - */ - function queryForeignKeys(owner, table) { - var sql = - 'SELECT table_schema AS "fkOwner", constraint_name AS "fkName", table_name AS "fkTableName",' - + ' column_name AS "fkColumnName", ordinal_position AS "keySeq",' - + ' referenced_table_schema AS "pkOwner", \'PRIMARY\' AS "pkName", ' - + ' referenced_table_name AS "pkTableName", referenced_column_name AS "pkColumnName"' - + ' FROM information_schema.key_column_usage' - + ' WHERE' - + ' constraint_name!=\'PRIMARY\' and POSITION_IN_UNIQUE_CONSTRAINT IS NOT NULL'; - if (owner) { - sql += ' AND table_schema=\'' + owner + '\''; - } - if (table) { - sql += ' AND table_name=\'' + table + '\''; - } - return sql; + if (table) { + sql += ' AND table_name=\'' + table + '\''; } + sql += ' ORDER BY table_schema, constraint_name, table_name, ordinal_position'; + return sql; + } - /** - * Discover foreign keys for a given table - * @param {String} table The table name - * @param {Object} options The options for discovery - * @param {Function} [cb] The callback function - */ - MySQL.prototype.discoverForeignKeys = function (table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; + /** + * Discover primary keys for a given table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + */ + MySQL.prototype.discoverPrimaryKeys = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; - var sql = queryForeignKeys(owner, table); - this.query(sql, cb); - }; + var sql = queryForPrimaryKeys(owner, table); + this.query(sql, cb); + }; - /*! - * Retrieves a description of the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table). - * They are ordered by fkTableOwner, fkTableName, and keySeq. - * @param owner - * @param table - * @returns {string} - */ - function queryExportedForeignKeys(owner, table) { - var sql = 'SELECT a.constraint_name AS "fkName", a.table_schema AS "fkOwner", a.table_name AS "fkTableName",' - + ' a.column_name AS "fkColumnName", a.ordinal_position AS "keySeq",' - + ' NULL AS "pkName", a.referenced_table_schema AS "pkOwner",' - + ' a.referenced_table_name AS "pkTableName", a.referenced_column_name AS "pkColumnName"' - + ' FROM' - + ' information_schema.key_column_usage a' - + ' WHERE a.position_in_unique_constraint IS NOT NULL'; - if (owner) { - sql += ' and a.referenced_table_schema=\'' + owner + '\''; - } - if (table) { - sql += ' and a.referenced_table_name=\'' + table + '\''; - } - sql += ' order by a.table_schema, a.table_name, a.ordinal_position'; - - return sql; + /*! + * Build the sql statement for querying foreign keys of a given table + * @param owner + * @param table + * @returns {string} + */ + function queryForeignKeys(owner, table) { + var sql = + 'SELECT table_schema AS "fkOwner", constraint_name AS "fkName", table_name AS "fkTableName",' + + ' column_name AS "fkColumnName", ordinal_position AS "keySeq",' + + ' referenced_table_schema AS "pkOwner", \'PRIMARY\' AS "pkName", ' + + ' referenced_table_name AS "pkTableName", referenced_column_name AS "pkColumnName"' + + ' FROM information_schema.key_column_usage' + + ' WHERE' + + ' constraint_name!=\'PRIMARY\' and POSITION_IN_UNIQUE_CONSTRAINT IS NOT NULL'; + if (owner) { + sql += ' AND table_schema=\'' + owner + '\''; } - - /** - * Discover foreign keys that reference to the primary key of this table - * @param {String} table The table name - * @param {Object} options The options for discovery - * @param {Function} [cb] The callback function - */ - MySQL.prototype.discoverExportedForeignKeys = function (table, options, cb) { - var args = getArgs(table, options, cb); - var owner = args.owner; - table = args.table; - options = args.options; - cb = args.cb; - - var sql = queryExportedForeignKeys(owner, table); - this.query(sql, cb); - }; - - function mysqlDataTypeToJSONType(mysqlType, dataLength) { - var type = mysqlType.toUpperCase(); - switch (type) { - case 'CHAR': - if (dataLength === 1) { - // Treat char(1) as boolean - return 'Boolean'; - } else { - return 'String'; - } - - case 'VARCHAR': - case 'TINYTEXT': - case 'MEDIUMTEXT': - case 'LONGTEXT': - case 'TEXT': - case 'ENUM': - case 'SET': - return 'String'; - case 'TINYBLOB': - case 'MEDIUMBLOB': - case 'LONGBLOB': - case 'BLOB': - case 'BINARY': - case 'VARBINARY': - case 'BIT': - return 'Binary'; - case 'TINYINT': - case 'SMALLINT': - case 'INT': - case 'MEDIUMINT': - case 'YEAR': - case 'FLOAT': - case 'DOUBLE': - return 'Number'; - case 'DATE': - case 'TIMESTAMP': - case 'DATETIME': - return 'Date'; - case 'POINT' - return 'GeoPoint'; - default: - return 'String'; - } + if (table) { + sql += ' AND table_name=\'' + table + '\''; } + return sql; + } + + /** + * Discover foreign keys for a given table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + */ + MySQL.prototype.discoverForeignKeys = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryForeignKeys(owner, table); + this.query(sql, cb); + }; + + /*! + * Retrieves a description of the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table). + * They are ordered by fkTableOwner, fkTableName, and keySeq. + * @param owner + * @param table + * @returns {string} + */ + function queryExportedForeignKeys(owner, table) { + var sql = 'SELECT a.constraint_name AS "fkName", a.table_schema AS "fkOwner", a.table_name AS "fkTableName",' + + ' a.column_name AS "fkColumnName", a.ordinal_position AS "keySeq",' + + ' NULL AS "pkName", a.referenced_table_schema AS "pkOwner",' + + ' a.referenced_table_name AS "pkTableName", a.referenced_column_name AS "pkColumnName"' + + ' FROM' + + ' information_schema.key_column_usage a' + + ' WHERE a.position_in_unique_constraint IS NOT NULL'; + if (owner) { + sql += ' and a.referenced_table_schema=\'' + owner + '\''; + } + if (table) { + sql += ' and a.referenced_table_name=\'' + table + '\''; + } + sql += ' order by a.table_schema, a.table_name, a.ordinal_position'; + + return sql; + } + + /** + * Discover foreign keys that reference to the primary key of this table + * @param {String} table The table name + * @param {Object} options The options for discovery + * @param {Function} [cb] The callback function + */ + MySQL.prototype.discoverExportedForeignKeys = function (table, options, cb) { + var args = getArgs(table, options, cb); + var owner = args.owner; + table = args.table; + options = args.options; + cb = args.cb; + + var sql = queryExportedForeignKeys(owner, table); + this.query(sql, cb); + }; + + function mysqlDataTypeToJSONType(mysqlType, dataLength) { + var type = mysqlType.toUpperCase(); + switch (type) { + case 'CHAR': + if (dataLength === 1) { + // Treat char(1) as boolean + return 'Boolean'; + } else { + return 'String'; + } + + case 'VARCHAR': + case 'TINYTEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + case 'TEXT': + case 'ENUM': + case 'SET': + return 'String'; + case 'TINYBLOB': + case 'MEDIUMBLOB': + case 'LONGBLOB': + case 'BLOB': + case 'BINARY': + case 'VARBINARY': + case 'BIT': + return 'Binary'; + case 'TINYINT': + case 'SMALLINT': + case 'INT': + case 'MEDIUMINT': + case 'YEAR': + case 'FLOAT': + case 'DOUBLE': + return 'Number'; + case 'DATE': + case 'TIMESTAMP': + case 'DATETIME': + return 'Date'; + case 'POINT' + return 'GeoPoint'; + default: + return 'String'; + } + } } diff --git a/lib/enumFactory.js b/lib/enumFactory.js index baf5371..42c498a 100644 --- a/lib/enumFactory.js +++ b/lib/enumFactory.js @@ -1,42 +1,42 @@ -var EnumFactory = function() { - if(arguments.length > 0){ - var Enum = function Enum(arg){ - if(typeof arg === 'number' && arg % 1 == 0) { - return Enum._values[arg]; - } else if(Enum[arg]){ - return Enum[arg] - } else if (Enum._values.indexOf(arg) !== -1 ) { - return arg; - } else if (arg === null) { - return null; - } else { - return ''; - } - }; - var dxList = []; - dxList.push(''); // Want empty value to be at index 0 to match MySQL Enum values and MySQL non-strict behavior. - for(var arg in arguments){ - arg = String(arguments[arg]); - Object.defineProperty(Enum, arg.toUpperCase(), {configurable: false, enumerable: true, value: arg, writable: false}); - dxList.push(arg); - } - Object.defineProperty(Enum, '_values', {configurable: false, enumerable: false, value: dxList, writable: false}); - Object.defineProperty(Enum, '_string', {configurable: false, enumerable: false, value: stringified(Enum), writable: false}); - Object.freeze(Enum); - return Enum; - } else { - throw "No arguments - could not create Enum."; +var EnumFactory = function () { + if (arguments.length > 0) { + var Enum = function Enum(arg) { + if (typeof arg === 'number' && arg % 1 == 0) { + return Enum._values[arg]; + } else if (Enum[arg]) { + return Enum[arg] + } else if (Enum._values.indexOf(arg) !== -1) { + return arg; + } else if (arg === null) { + return null; + } else { + return ''; + } + }; + var dxList = []; + dxList.push(''); // Want empty value to be at index 0 to match MySQL Enum values and MySQL non-strict behavior. + for (var arg in arguments) { + arg = String(arguments[arg]); + Object.defineProperty(Enum, arg.toUpperCase(), {configurable: false, enumerable: true, value: arg, writable: false}); + dxList.push(arg); } + Object.defineProperty(Enum, '_values', {configurable: false, enumerable: false, value: dxList, writable: false}); + Object.defineProperty(Enum, '_string', {configurable: false, enumerable: false, value: stringified(Enum), writable: false}); + Object.freeze(Enum); + return Enum; + } else { + throw "No arguments - could not create Enum."; + } }; function stringified(anEnum) { - var s = []; - for(var i in anEnum._values){ - if(anEnum._values[i] != ''){ - s.push("'" + anEnum._values[i] + "'"); - } + var s = []; + for (var i in anEnum._values) { + if (anEnum._values[i] != '') { + s.push("'" + anEnum._values[i] + "'"); } - return s.join(','); + } + return s.join(','); } exports.EnumFactory = EnumFactory; diff --git a/lib/mysql.js b/lib/mysql.js index 1fe3d1b..3c4831e 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -15,68 +15,68 @@ var EnumFactory = require('./enumFactory').EnumFactory; * @param {Function} [callback] The callback function */ exports.initialize = function initializeDataSource(dataSource, callback) { - if (!mysql) { - return; - } + if (!mysql) { + return; + } - var s = dataSource.settings; + var s = dataSource.settings; - if (s.collation) { - s.charset = s.collation.substr(0, s.collation.indexOf('_')); // Charset should be first 'chunk' of collation. - } else { - s.collation = 'utf8_general_ci'; - s.charset = 'utf8'; - } + if (s.collation) { + s.charset = s.collation.substr(0, s.collation.indexOf('_')); // Charset should be first 'chunk' of collation. + } else { + s.collation = 'utf8_general_ci'; + s.charset = 'utf8'; + } - s.supportBigNumbers = (s.supportBigNumbers || false); - s.timezone = (s.timezone || 'local'); + s.supportBigNumbers = (s.supportBigNumbers || false); + s.timezone = (s.timezone || 'local'); - if(!isNaN(s.connectionLimit)) { - s.connectionLimit = 10; - } + if (!isNaN(s.connectionLimit)) { + s.connectionLimit = 10; + } - var options = { - host: s.host || s.hostname || 'localhost', - port: s.port || 3306, - user: s.username || s.user, - password: s.password, - timezone: s.timezone, - debug: s.debug, - socketPath: s.socketPath, - charset: s.collation.toUpperCase(), // Correct by docs despite seeming odd. - supportBigNumbers: s.supportBigNumbers, - connectionLimit: s.connectionLimit - }; + var options = { + host: s.host || s.hostname || 'localhost', + port: s.port || 3306, + user: s.username || s.user, + password: s.password, + timezone: s.timezone, + debug: s.debug, + socketPath: s.socketPath, + charset: s.collation.toUpperCase(), // Correct by docs despite seeming odd. + supportBigNumbers: s.supportBigNumbers, + connectionLimit: s.connectionLimit + }; - // Don't configure the DB if the pool can be used for multiple DBs - if(!s.createDatabase) { - options.database = s.database; - } + // Don't configure the DB if the pool can be used for multiple DBs + if (!s.createDatabase) { + options.database = s.database; + } - dataSource.client = mysql.createPool(options); + dataSource.client = mysql.createPool(options); - dataSource.client.on('error', function (err) { - dataSource.emit('error', err); - dataSource.connected = false; - dataSource.connecting = false; - }); + dataSource.client.on('error', function (err) { + dataSource.emit('error', err); + dataSource.connected = false; + dataSource.connecting = false; + }); - if (s.debug) { - console.log('Settings: ', s); - } + if (s.debug) { + console.log('Settings: ', s); + } - dataSource.connector = new MySQL(dataSource.client, s); - dataSource.connector.dataSource = dataSource; + dataSource.connector = new MySQL(dataSource.client, s); + dataSource.connector.dataSource = dataSource; - // MySQL specific column types - juggler.ModelBuilder.registerType(function Point() { - }); + // MySQL specific column types + juggler.ModelBuilder.registerType(function Point() { + }); - dataSource.EnumFactory = EnumFactory; // factory for Enums. Note that currently Enums can not be registered. + dataSource.EnumFactory = EnumFactory; // factory for Enums. Note that currently Enums can not be registered. - process.nextTick(function() { - callback && callback(); - }); + process.nextTick(function () { + callback && callback(); + }); }; exports.MySQL = MySQL; @@ -87,10 +87,10 @@ exports.MySQL = MySQL; * @param {Object} client The node-mysql client object */ function MySQL(client, settings) { - this.name = 'mysql'; - this._models = {}; - this.client = client; - this.settings = settings; + this.name = 'mysql'; + this._models = {}; + this.client = client; + this.settings = settings; } require('util').inherits(MySQL, juggler.BaseSQL); @@ -102,77 +102,76 @@ require('util').inherits(MySQL, juggler.BaseSQL); * @param {Function} [callback] The callback after the SQL statement is executed */ MySQL.prototype.query = function (sql, callback) { - var self = this; - if (!this.dataSource.connected) { - return this.dataSource.on('connected', function () { - this.query(sql, callback); - }.bind(this)); - } - var client = this.client; - var time = Date.now(); - var debug = this.settings.debug; - var db = this.settings.database; - var log = this.log; - if (typeof callback !== 'function') throw new Error('callback should be a function'); - if (debug) { - console.log('SQL:', sql); - } + var self = this; + if (!this.dataSource.connected) { + return this.dataSource.on('connected', function () { + this.query(sql, callback); + }.bind(this)); + } + var client = this.client; + var time = Date.now(); + var debug = this.settings.debug; + var db = this.settings.database; + var log = this.log; + if (typeof callback !== 'function') throw new Error('callback should be a function'); + if (debug) { + console.log('SQL:', sql); + } - function releaseConnectionAndCallback(connection, err, result) { - connection.release(); - callback && callback(err, result); - } + function releaseConnectionAndCallback(connection, err, result) { + connection.release(); + callback && callback(err, result); + } - function runQuery(connection) { - connection.query(sql, function (err, data) { - if (debug) { - if (err) { - console.error('Error:', err); - } - console.log('Data:', data); - } - if (log) log(sql, time); - releaseConnectionAndCallback(connection, err, data); - }); - } - - client.getConnection(function (err, connection) { + function runQuery(connection) { + connection.query(sql, function (err, data) { + if (debug) { if (err) { - callback && callback(err); - return; - } - if (self.settings.createDatabase) { - // Call USE db ... - connection.query('USE `' + db + '`', function (err) { - if (err) { - if (err && err.message.match(/(^|: )unknown database/i)) { - var charset = self.settings.charset; - var collation = self.settings.collation; - var q = 'CREATE DATABASE ' + db + ' CHARACTER SET ' + charset + ' COLLATE ' + collation; - connection.query(q, function (err) { - if (!err) { - connection.query('USE `' + db + '`', function (err) { - runQuery(connection); - }); - } else { - releaseConnectionAndCallback(connection, err); - } - }); - return; - } else { - releaseConnectionAndCallback(connection, err); - return; - } - } - runQuery(connection); - }); - } else { - // Bypass USE db - runQuery(connection); + console.error('Error:', err); } + console.log('Data:', data); + } + if (log) log(sql, time); + releaseConnectionAndCallback(connection, err, data); }); -}; + } + client.getConnection(function (err, connection) { + if (err) { + callback && callback(err); + return; + } + if (self.settings.createDatabase) { + // Call USE db ... + connection.query('USE `' + db + '`', function (err) { + if (err) { + if (err && err.message.match(/(^|: )unknown database/i)) { + var charset = self.settings.charset; + var collation = self.settings.collation; + var q = 'CREATE DATABASE ' + db + ' CHARACTER SET ' + charset + ' COLLATE ' + collation; + connection.query(q, function (err) { + if (!err) { + connection.query('USE `' + db + '`', function (err) { + runQuery(connection); + }); + } else { + releaseConnectionAndCallback(connection, err); + } + }); + return; + } else { + releaseConnectionAndCallback(connection, err); + return; + } + } + runQuery(connection); + }); + } else { + // Bypass USE db + runQuery(connection); + } + }); +}; /** * Create the data model in MySQL @@ -182,16 +181,16 @@ MySQL.prototype.query = function (sql, callback) { * @param {Function} [callback] The callback function */ MySQL.prototype.create = function (model, data, callback) { - var fields = this.toFields(model, data); - var sql = 'INSERT INTO ' + this.tableEscaped(model); - if (fields) { - sql += ' SET ' + fields; - } else { - sql += ' VALUES ()'; - } - this.query(sql, function (err, info) { - callback(err, info && info.insertId); - }); + var fields = this.toFields(model, data); + var sql = 'INSERT INTO ' + this.tableEscaped(model); + if (fields) { + sql += ' SET ' + fields; + } else { + sql += ' VALUES ()'; + } + this.query(sql, function (err, info) { + callback(err, info && info.insertId); + }); }; /** @@ -202,64 +201,64 @@ MySQL.prototype.create = function (model, data, callback) { * @param {Function} [callback] The callback function */ MySQL.prototype.updateOrCreate = function (model, data, callback) { - var mysql = this; - var fieldsNames = []; - var fieldValues = []; - var combined = []; - var props = this._models[model].properties; - Object.keys(data).forEach(function (key) { - if (props[key] || mysql.id(model, key)) { - var k = mysql.columnEscaped(model, key); - var v; - if (!mysql.id(model, key)) { - v = mysql.toDatabase(props[key], data[key]); - } else { - v = data[key]; - } - fieldsNames.push(k); - fieldValues.push(v); - if (!mysql.id(model, key)) combined.push(k + ' = ' + v); - } - }); + var mysql = this; + var fieldsNames = []; + var fieldValues = []; + var combined = []; + var props = this._models[model].properties; + Object.keys(data).forEach(function (key) { + if (props[key] || mysql.id(model, key)) { + var k = mysql.columnEscaped(model, key); + var v; + if (!mysql.id(model, key)) { + v = mysql.toDatabase(props[key], data[key]); + } else { + v = data[key]; + } + fieldsNames.push(k); + fieldValues.push(v); + if (!mysql.id(model, key)) combined.push(k + ' = ' + v); + } + }); - var sql = 'INSERT INTO ' + this.tableEscaped(model); - sql += ' (' + fieldsNames.join(', ') + ')'; - sql += ' VALUES (' + fieldValues.join(', ') + ')'; - sql += ' ON DUPLICATE KEY UPDATE ' + combined.join(', '); + var sql = 'INSERT INTO ' + this.tableEscaped(model); + sql += ' (' + fieldsNames.join(', ') + ')'; + sql += ' VALUES (' + fieldValues.join(', ') + ')'; + sql += ' ON DUPLICATE KEY UPDATE ' + combined.join(', '); - this.query(sql, function (err, info) { - if (!err && info && info.insertId) { - data.id = info.insertId; - } - callback(err, data); - }); + this.query(sql, function (err, info) { + if (!err && info && info.insertId) { + data.id = info.insertId; + } + callback(err, data); + }); }; MySQL.prototype.toFields = function (model, data) { - var self = this; - var fields = []; - var props = this._models[model].properties; - Object.keys(data).forEach(function (key) { - if (props[key]) { - var value = this.toDatabase(props[key], data[key]); - if ('undefined' === typeof value) return; - fields.push(self.columnEscaped(model, key) + ' = ' + value); - } - }.bind(this)); - return fields.join(','); + var self = this; + var fields = []; + var props = this._models[model].properties; + Object.keys(data).forEach(function (key) { + if (props[key]) { + var value = this.toDatabase(props[key], data[key]); + if ('undefined' === typeof value) return; + fields.push(self.columnEscaped(model, key) + ' = ' + value); + } + }.bind(this)); + return fields.join(','); }; function dateToMysql(val) { - return val.getUTCFullYear() + '-' + - fillZeros(val.getUTCMonth() + 1) + '-' + - fillZeros(val.getUTCDate()) + ' ' + - fillZeros(val.getUTCHours()) + ':' + - fillZeros(val.getUTCMinutes()) + ':' + - fillZeros(val.getUTCSeconds()); + return val.getUTCFullYear() + '-' + + fillZeros(val.getUTCMonth() + 1) + '-' + + fillZeros(val.getUTCDate()) + ' ' + + fillZeros(val.getUTCHours()) + ':' + + fillZeros(val.getUTCMinutes()) + ':' + + fillZeros(val.getUTCSeconds()); - function fillZeros(v) { - return v < 10 ? '0' + v : v; - } + function fillZeros(v) { + return v < 10 ? '0' + v : v; + } } /*! @@ -269,41 +268,41 @@ function dateToMysql(val) { * @returns {*} */ MySQL.prototype.toDatabase = function (prop, val) { - if (val === null) return 'NULL'; - if (val === undefined) return; - if (val.constructor.name === 'Object') { - var operator = Object.keys(val)[0] - val = val[operator]; - if (operator === 'between') { - return this.toDatabase(prop, val[0]) + - ' AND ' + - this.toDatabase(prop, val[1]); - } else if (operator == 'inq' || operator == 'nin') { - if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array - for (var i = 0; i < val.length; i++) { - val[i] = this.client.escape(val[i]); - } - return val.join(','); - } else { - return val; - } + if (val === null) return 'NULL'; + if (val === undefined) return; + if (val.constructor.name === 'Object') { + var operator = Object.keys(val)[0] + val = val[operator]; + if (operator === 'between') { + return this.toDatabase(prop, val[0]) + + ' AND ' + + this.toDatabase(prop, val[1]); + } else if (operator == 'inq' || operator == 'nin') { + if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array + for (var i = 0; i < val.length; i++) { + val[i] = this.client.escape(val[i]); } + return val.join(','); + } else { + return val; + } } - if (!prop) return val; - if (prop.type.name === 'Number') return Number(val); - if (prop.type.name === 'Date') { - if (!val) return 'NULL'; - if (!val.toUTCString) { - val = new Date(val); - } - return '"' + dateToMysql(val) + '"'; + } + if (!prop) return val; + if (prop.type.name === 'Number') return Number(val); + if (prop.type.name === 'Date') { + if (!val) return 'NULL'; + if (!val.toUTCString) { + val = new Date(val); } - if (prop.type.name == "Boolean") return val ? 1 : 0; - if (prop.type.name === 'GeoPoint') { - return val ? 'Point(' + val.lat + ',' + val.lng + ')' : 'NULL'; - } - if (typeof prop.type === 'function') return this.client.escape(prop.type(val)); - return this.client.escape(val.toString()); + return '"' + dateToMysql(val) + '"'; + } + if (prop.type.name == "Boolean") return val ? 1 : 0; + if (prop.type.name === 'GeoPoint') { + return val ? 'Point(' + val.lat + ',' + val.lng + ')' : 'NULL'; + } + if (typeof prop.type === 'function') return this.client.escape(prop.type(val)); + return this.client.escape(val.toString()); }; /*! @@ -313,148 +312,148 @@ MySQL.prototype.toDatabase = function (prop, val) { * @returns {*} */ MySQL.prototype.fromDatabase = function (model, data) { - if (!data) { - return null; + if (!data) { + return null; + } + var props = this._models[model].properties; + var json = {}; + for (var p in props) { + var key = this.column(model, p); + var val = data[key]; + if (typeof val === 'undefined' || val === null) { + continue; } - var props = this._models[model].properties; - var json = {}; - for (var p in props) { - var key = this.column(model, p); - var val = data[key]; - if (typeof val === 'undefined' || val === null) { - continue; - } - if (props[p]) { - switch (props[p].type.name) { - case 'Date': - val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); - break; - case 'Boolean': - val = Boolean(val); - break; - case 'GeoPoint': - case 'Point': - val = { - lat: val.x, - lng: val.y - }; - break; - } - } - json[p] = val; + if (props[p]) { + switch (props[p].type.name) { + case 'Date': + val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); + break; + case 'Boolean': + val = Boolean(val); + break; + case 'GeoPoint': + case 'Point': + val = { + lat: val.x, + lng: val.y + }; + break; + } } - return json; + json[p] = val; + } + return json; }; MySQL.prototype.escapeName = function (name) { - return '`' + name.replace(/\./g, '`.`') + '`'; + return '`' + name.replace(/\./g, '`.`') + '`'; }; MySQL.prototype.getColumns = function (model, props) { - var cols = this._models[model].properties; - if (!cols) { - return '*'; - } - var self = this; - var keys = Object.keys(cols); - if (Array.isArray(props) && props.length > 0) { - // No empty array, including all the fields - keys = props; - } else if ('object' === typeof props && Object.keys(props).length > 0) { - // { field1: boolean, field2: boolean ... } - var included = []; - var excluded = []; - keys.forEach(function (k) { - if (props[k]) { - included.push(k); - } else if ((k in props) && !props[k]) { - excluded.push(k); - } - }); - if (included.length > 0) { - keys = included; - } else if (excluded.length > 0) { - excluded.forEach(function (e) { - var index = keys.indexOf(e); - keys.splice(index, 1); - }); - } - } - var names = keys.map(function (c) { - return self.columnEscaped(model, c); + var cols = this._models[model].properties; + if (!cols) { + return '*'; + } + var self = this; + var keys = Object.keys(cols); + if (Array.isArray(props) && props.length > 0) { + // No empty array, including all the fields + keys = props; + } else if ('object' === typeof props && Object.keys(props).length > 0) { + // { field1: boolean, field2: boolean ... } + var included = []; + var excluded = []; + keys.forEach(function (k) { + if (props[k]) { + included.push(k); + } else if ((k in props) && !props[k]) { + excluded.push(k); + } }); - return names.join(', '); + if (included.length > 0) { + keys = included; + } else if (excluded.length > 0) { + excluded.forEach(function (e) { + var index = keys.indexOf(e); + keys.splice(index, 1); + }); + } + } + var names = keys.map(function (c) { + return self.columnEscaped(model, c); + }); + return names.join(', '); }; function buildWhere(self, model, conds) { - var props = self._models[model].properties; + var props = self._models[model].properties; - var cs = []; - Object.keys(conds).forEach(function (key) { - var keyEscaped = self.columnEscaped(model, key); - var val = self.toDatabase(props[key], conds[key]); - if (conds[key] === null || conds[key] === undefined) { - cs.push(keyEscaped + ' IS NULL'); - } else if (conds[key] && conds[key].constructor.name === 'Object') { - var condType = Object.keys(conds[key])[0]; - var sqlCond = keyEscaped; - if ((condType === 'inq' || condType === 'nin') && val.length === 0) { - cs.push(condType === 'inq' ? 0 : 1); - return true; - } - switch (condType) { - case 'gt': - sqlCond += ' > '; - break; - case 'gte': - sqlCond += ' >= '; - break; - case 'lt': - sqlCond += ' < '; - break; - case 'lte': - sqlCond += ' <= '; - break; - case 'between': - sqlCond += ' BETWEEN '; - break; - case 'inq': - sqlCond += ' IN '; - break; - case 'nin': - sqlCond += ' NOT IN '; - break; - case 'neq': - sqlCond += ' != '; - break; - } - sqlCond += (condType === 'inq' || condType === 'nin') ? '(' + val + ')' : val; - cs.push(sqlCond); - } else { - cs.push(keyEscaped + ' = ' + val); - } - }); - if (cs.length === 0) { - return ''; + var cs = []; + Object.keys(conds).forEach(function (key) { + var keyEscaped = self.columnEscaped(model, key); + var val = self.toDatabase(props[key], conds[key]); + if (conds[key] === null || conds[key] === undefined) { + cs.push(keyEscaped + ' IS NULL'); + } else if (conds[key] && conds[key].constructor.name === 'Object') { + var condType = Object.keys(conds[key])[0]; + var sqlCond = keyEscaped; + if ((condType === 'inq' || condType === 'nin') && val.length === 0) { + cs.push(condType === 'inq' ? 0 : 1); + return true; + } + switch (condType) { + case 'gt': + sqlCond += ' > '; + break; + case 'gte': + sqlCond += ' >= '; + break; + case 'lt': + sqlCond += ' < '; + break; + case 'lte': + sqlCond += ' <= '; + break; + case 'between': + sqlCond += ' BETWEEN '; + break; + case 'inq': + sqlCond += ' IN '; + break; + case 'nin': + sqlCond += ' NOT IN '; + break; + case 'neq': + sqlCond += ' != '; + break; + } + sqlCond += (condType === 'inq' || condType === 'nin') ? '(' + val + ')' : val; + cs.push(sqlCond); + } else { + cs.push(keyEscaped + ' = ' + val); } - return 'WHERE ' + cs.join(' AND '); + }); + if (cs.length === 0) { + return ''; + } + return 'WHERE ' + cs.join(' AND '); } function buildOrderBy(self, model, order) { - if (typeof order === 'string') { - order = [order]; + if (typeof order === 'string') { + order = [order]; + } + return 'ORDER BY ' + order.map(function (o) { + var t = o.split(/[\s,]+/); + if (t.length === 1) { + return self.columnEscaped(model, o); } - return 'ORDER BY ' + order.map(function (o) { - var t = o.split(/[\s,]+/); - if (t.length === 1) { - return self.columnEscaped(model, o); - } - return self.columnEscaped(model, t[0]) + ' ' + t[1]; - }).join(', '); + return self.columnEscaped(model, t[0]) + ' ' + t[1]; + }).join(', '); } function buildLimit(limit, offset) { - return 'LIMIT ' + (offset ? (offset + ', ' + limit) : limit); + return 'LIMIT ' + (offset ? (offset + ', ' + limit) : limit); } /** @@ -465,50 +464,50 @@ function buildLimit(limit, offset) { * @param {Function} [callback] The callback function */ MySQL.prototype.all = function all(model, filter, callback) { - var self = this; - // Order by id if no order is specified - filter = filter || {}; - if (!filter.order) { - var idNames = this.idNames(model); - if (idNames && idNames.length) { - filter.order = idNames; - } + var self = this; + // Order by id if no order is specified + filter = filter || {}; + if (!filter.order) { + var idNames = this.idNames(model); + if (idNames && idNames.length) { + filter.order = idNames; + } + } + + var sql = 'SELECT ' + this.getColumns(model, filter.fields) + ' FROM ' + this.tableEscaped(model); + + if (filter) { + + if (filter.where) { + sql += ' ' + buildWhere(self, model, filter.where); } - var sql = 'SELECT ' + this.getColumns(model, filter.fields) + ' FROM ' + this.tableEscaped(model); - - if (filter) { - - if (filter.where) { - sql += ' ' + buildWhere(self, model, filter.where); - } - - if (filter.order) { - sql += ' ' + buildOrderBy(self, model, filter.order); - } - - if (filter.limit) { - sql += ' ' + buildLimit(filter.limit, filter.skip || 0); - } - + if (filter.order) { + sql += ' ' + buildOrderBy(self, model, filter.order); } - this.query(sql, function (err, data) { - if (err) { - return callback(err, []); - } + if (filter.limit) { + sql += ' ' + buildLimit(filter.limit, filter.skip || 0); + } - var objs = data.map(function (obj) { - return self.fromDatabase(model, obj); - }); - if (filter && filter.include) { - this._models[model].model.include(objs, filter.include, callback); - } else { - callback(null, objs); - } - }.bind(this)); + } - return sql; + this.query(sql, function (err, data) { + if (err) { + return callback(err, []); + } + + var objs = data.map(function (obj) { + return self.fromDatabase(model, obj); + }); + if (filter && filter.include) { + this._models[model].model.include(objs, filter.include, callback); + } else { + callback(null, objs); + } + }.bind(this)); + + return sql; }; @@ -521,14 +520,14 @@ MySQL.prototype.all = function all(model, filter, callback) { * */ MySQL.prototype.destroyAll = function destroyAll(model, where, callback) { - if (!callback && 'function' === typeof where) { - callback = where; - where = undefined; - } - this.query('DELETE FROM ' - + this.tableEscaped(model) + ' ' + buildWhere(this, model, where || {}), function (err, data) { - callback && callback(err, data); - }.bind(this)); + if (!callback && 'function' === typeof where) { + callback = where; + where = undefined; + } + this.query('DELETE FROM ' + + this.tableEscaped(model) + ' ' + buildWhere(this, model, where || {}), function (err, data) { + callback && callback(err, data); + }.bind(this)); }; /** @@ -537,42 +536,42 @@ MySQL.prototype.destroyAll = function destroyAll(model, where, callback) { * @param {Function} [cb] The callback function */ MySQL.prototype.autoupdate = function (models, cb) { - var self = this; - var wait = 0; - if ((!cb) && ('function' === typeof models)) { - cb = models; - models = undefined; - } - // First argument is a model name - if ('string' === typeof models) { - models = [models]; - } + var self = this; + var wait = 0; + 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(this._models); + models = models || Object.keys(this._models); - models.forEach(function (model) { - if (model in self._models) { - wait++; - self.query('SHOW FIELDS FROM ' + self.tableEscaped(model), function (err, fields) { - self.query('SHOW INDEXES FROM ' + self.tableEscaped(model), function (err, indexes) { - if (!err && fields.length) { - self.alterTable(model, fields, indexes, done); - } else { - self.createTable(model, done); - } - }); - }); - } - }); - - function done(err) { - if (err) { - console.error(err); - } - if (--wait === 0 && cb) { - cb(); - } + models.forEach(function (model) { + if (model in self._models) { + wait++; + self.query('SHOW FIELDS FROM ' + self.tableEscaped(model), function (err, fields) { + self.query('SHOW INDEXES FROM ' + self.tableEscaped(model), function (err, indexes) { + if (!err && fields.length) { + self.alterTable(model, fields, indexes, done); + } else { + self.createTable(model, done); + } + }); + }); } + }); + + function done(err) { + if (err) { + console.error(err); + } + if (--wait === 0 && cb) { + cb(); + } + } }; /*! @@ -581,14 +580,14 @@ MySQL.prototype.autoupdate = function (models, cb) { * @param cb */ MySQL.prototype.createTable = function (model, cb) { - var metadata = this._models[model].settings[this.name]; - var engine = metadata && metadata.engine; - var sql = 'CREATE TABLE ' + this.tableEscaped(model) + - ' (\n ' + this.propertiesSQL(model) + '\n)'; - if (engine) { - sql += 'ENGINE=' + engine + '\n'; - } - this.query(sql, cb); + var metadata = this._models[model].settings[this.name]; + var engine = metadata && metadata.engine; + var sql = 'CREATE TABLE ' + this.tableEscaped(model) + + ' (\n ' + this.propertiesSQL(model) + '\n)'; + if (engine) { + sql += 'ENGINE=' + engine + '\n'; + } + this.query(sql, cb); }; /** @@ -597,412 +596,412 @@ MySQL.prototype.createTable = function (model, cb) { * @param {Function} [cb] The callback function */ MySQL.prototype.isActual = function (cb) { - var ok = false; - var self = this; - var wait = 0; - Object.keys(this._models).forEach(function (model) { - wait += 1; - self.query('SHOW FIELDS FROM ' + model, function (err, fields) { - self.query('SHOW INDEXES FROM ' + model, function (err, indexes) { - self.alterTable(model, fields, indexes, done, true); - }); - }); + var ok = false; + var self = this; + var wait = 0; + Object.keys(this._models).forEach(function (model) { + wait += 1; + self.query('SHOW FIELDS FROM ' + model, function (err, fields) { + self.query('SHOW INDEXES FROM ' + model, function (err, indexes) { + self.alterTable(model, fields, indexes, done, true); + }); }); + }); - function done(err, needAlter) { - if (err) { - console.log(err); - } - ok = ok || needAlter; - if (--wait === 0 && cb) { - cb(null, !ok); - } + function done(err, needAlter) { + if (err) { + console.log(err); } + ok = ok || needAlter; + if (--wait === 0 && cb) { + cb(null, !ok); + } + } }; MySQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) { - var self = this; - var m = this._models[model]; - var propNames = Object.keys(m.properties).filter(function (name) { - return !!m.properties[name]; + var self = this; + var m = this._models[model]; + var propNames = Object.keys(m.properties).filter(function (name) { + return !!m.properties[name]; + }); + var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { + return !!m.settings.indexes[name]; + }) : []; + var sql = []; + var ai = {}; + + if (actualIndexes) { + actualIndexes.forEach(function (i) { + var name = i.Key_name; + if (!ai[name]) { + ai[name] = { + info: i, + columns: [] + }; + } + ai[name].columns[i.Seq_in_index - 1] = i.Column_name; }); - var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) { - return !!m.settings.indexes[name]; - }) : []; - var sql = []; - var ai = {}; + } + var aiNames = Object.keys(ai); - if (actualIndexes) { - actualIndexes.forEach(function (i) { - var name = i.Key_name; - if (!ai[name]) { - ai[name] = { - info: i, - columns: [] - }; - } - ai[name].columns[i.Seq_in_index - 1] = i.Column_name; - }); - } - var aiNames = Object.keys(ai); - - // change/add new fields - propNames.forEach(function (propName) { - if (m.properties[propName] && self.id(model, propName)) return; - var found; - if (actualFields) { - actualFields.forEach(function (f) { - if (f.Field === propName) { - found = f; - } - }); - } - - if (found) { - actualize(propName, found); - } else { - sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); - } - }); - - // drop columns + // change/add new fields + propNames.forEach(function (propName) { + if (m.properties[propName] && self.id(model, propName)) return; + var found; if (actualFields) { - actualFields.forEach(function (f) { - var notFound = !~propNames.indexOf(f.Field); - if (m.properties[f.Field] && self.id(model, f.Field)) return; - if (notFound || !m.properties[f.Field]) { - sql.push('DROP COLUMN `' + f.Field + '`'); - } - }); + actualFields.forEach(function (f) { + if (f.Field === propName) { + found = f; + } + }); } - // remove indexes - aiNames.forEach(function (indexName) { - if (indexName === 'PRIMARY' || (m.properties[indexName] && self.id(model, indexName))) return; - if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { - sql.push('DROP INDEX `' + indexName + '`'); - } else { - // first: check single (only type and kind) - if (m.properties[indexName] && !m.properties[indexName].index) { - // TODO - return; - } - // second: check multiple indexes - var orderMatched = true; - if (indexNames.indexOf(indexName) !== -1) { - m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { - if (ai[indexName].columns[i] !== columnName) orderMatched = false; - }); - } - if (!orderMatched) { - sql.push('DROP INDEX `' + indexName + '`'); - delete ai[indexName]; - } - } - }); - - // add single-column indexes - propNames.forEach(function (propName) { - var i = m.properties[propName].index; - if (!i) { - return; - } - var found = ai[propName] && ai[propName].info; - if (!found) { - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - // kind = i.kind; - } - if (kind && type) { - sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type); - } else { - sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) '); - } - } - }); - - // add multi-column indexes - indexNames.forEach(function (indexName) { - var i = m.settings.indexes[indexName]; - var found = ai[indexName] && ai[indexName].info; - if (!found) { - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - kind = i.kind; - } - if (kind && type) { - sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type); - } else { - sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')'); - } - } - }); - - if (sql.length) { - var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',\n'); - if (checkOnly) { - done(null, true, {statements: sql, query: query}); - } else { - this.query(query, done); - } + if (found) { + actualize(propName, found); } else { - done(); + sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + } + }); + + // drop columns + if (actualFields) { + actualFields.forEach(function (f) { + var notFound = !~propNames.indexOf(f.Field); + if (m.properties[f.Field] && self.id(model, f.Field)) return; + if (notFound || !m.properties[f.Field]) { + sql.push('DROP COLUMN `' + f.Field + '`'); + } + }); + } + + // remove indexes + aiNames.forEach(function (indexName) { + if (indexName === 'PRIMARY' || (m.properties[indexName] && self.id(model, indexName))) return; + if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { + sql.push('DROP INDEX `' + indexName + '`'); + } else { + // first: check single (only type and kind) + if (m.properties[indexName] && !m.properties[indexName].index) { + // TODO + return; + } + // second: check multiple indexes + var orderMatched = true; + if (indexNames.indexOf(indexName) !== -1) { + m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) { + if (ai[indexName].columns[i] !== columnName) orderMatched = false; + }); + } + if (!orderMatched) { + sql.push('DROP INDEX `' + indexName + '`'); + delete ai[indexName]; + } + } + }); + + // add single-column indexes + propNames.forEach(function (propName) { + var i = m.properties[propName].index; + if (!i) { + return; + } + var found = ai[propName] && ai[propName].info; + if (!found) { + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + // kind = i.kind; + } + if (kind && type) { + sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type); + } else { + sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) '); + } + } + }); + + // add multi-column indexes + indexNames.forEach(function (indexName) { + var i = m.settings.indexes[indexName]; + var found = ai[indexName] && ai[indexName].info; + if (!found) { + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + if (kind && type) { + sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type); + } else { + sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')'); + } + } + }); + + if (sql.length) { + var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',\n'); + if (checkOnly) { + done(null, true, {statements: sql, query: query}); + } else { + this.query(query, done); + } + } else { + done(); + } + + function actualize(propName, oldSettings) { + var newSettings = m.properties[propName]; + if (newSettings && changed(newSettings, oldSettings)) { + sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName)); + } + } + + function changed(newSettings, oldSettings) { + if (oldSettings.Null === 'YES') { // Used to allow null and does not now. + if (newSettings.allowNull === false) return true; + if (newSettings.null === false) return true; + } + if (oldSettings.Null === 'NO') { // Did not allow null and now does. + if (newSettings.allowNull === true) return true; + if (newSettings.null === true) return true; + if (newSettings.null === undefined && newSettings.allowNull === undefined) return true; } - function actualize(propName, oldSettings) { - var newSettings = m.properties[propName]; - if (newSettings && changed(newSettings, oldSettings)) { - sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName)); - } - } - - function changed(newSettings, oldSettings) { - if (oldSettings.Null === 'YES') { // Used to allow null and does not now. - if (newSettings.allowNull === false) return true; - if (newSettings.null === false) return true; - } - if (oldSettings.Null === 'NO') { // Did not allow null and now does. - if (newSettings.allowNull === true) return true; - if (newSettings.null === true) return true; - if (newSettings.null === undefined && newSettings.allowNull === undefined) return true; - } - - if (oldSettings.Type.toUpperCase() !== datatype(newSettings).toUpperCase()) return true; - return false; - } + if (oldSettings.Type.toUpperCase() !== datatype(newSettings).toUpperCase()) return true; + return false; + } }; MySQL.prototype.propertiesSQL = function (model) { - var self = this; + var self = this; - var pks = this.idNames(model).map(function (i) { - return self.columnEscaped(model, i); - }); + var pks = this.idNames(model).map(function (i) { + return self.columnEscaped(model, i); + }); - var sql = []; - if (pks.length === 1) { - var idName = this.idName(model); - sql.push(self.columnEscaped(model, idName) + ' INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'); - } - Object.keys(this._models[model].properties).forEach(function (prop) { - if (self.id(model, prop) && pks.length === 1) { - return; - } - var colName = self.columnEscaped(model, prop); - sql.push(colName + ' ' + self.propertySettingsSQL(model, prop)); - }); - if (pks.length > 1) { - sql.push('PRIMARY KEY(' + pks.join(',') + ')'); + var sql = []; + if (pks.length === 1) { + var idName = this.idName(model); + sql.push(self.columnEscaped(model, idName) + ' INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'); + } + Object.keys(this._models[model].properties).forEach(function (prop) { + if (self.id(model, prop) && pks.length === 1) { + return; } + var colName = self.columnEscaped(model, prop); + sql.push(colName + ' ' + self.propertySettingsSQL(model, prop)); + }); + if (pks.length > 1) { + sql.push('PRIMARY KEY(' + pks.join(',') + ')'); + } - /* - var sql = ['`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY']; - Object.keys(this._models[model].properties).forEach(function (prop) { - if (self.id(model, prop)) return; - sql.push('`' + prop + '` ' + self.propertySettingsSQL(model, prop)); - }); - */ + /* + var sql = ['`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY']; + Object.keys(this._models[model].properties).forEach(function (prop) { + if (self.id(model, prop)) return; + sql.push('`' + prop + '` ' + self.propertySettingsSQL(model, prop)); + }); + */ - // Declared in model index property indexes. - Object.keys(this._models[model].properties).forEach(function (prop) { - var i = self._models[model].properties[prop].index; - if (i) { - sql.push(self.singleIndexSettingsSQL(model, prop)); - } - }); - // Settings might not have an indexes property. - var dxs = this._models[model].settings.indexes; - if (dxs) { - Object.keys(this._models[model].settings.indexes).forEach(function (prop) { - sql.push(self.indexSettingsSQL(model, prop)); - }); + // Declared in model index property indexes. + Object.keys(this._models[model].properties).forEach(function (prop) { + var i = self._models[model].properties[prop].index; + if (i) { + sql.push(self.singleIndexSettingsSQL(model, prop)); } - return sql.join(',\n '); + }); + // Settings might not have an indexes property. + var dxs = this._models[model].settings.indexes; + if (dxs) { + Object.keys(this._models[model].settings.indexes).forEach(function (prop) { + sql.push(self.indexSettingsSQL(model, prop)); + }); + } + return sql.join(',\n '); }; MySQL.prototype.singleIndexSettingsSQL = function (model, prop) { - // Recycled from alterTable single indexes above, more or less. - var i = this._models[model].properties[prop].index; - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - kind = i.kind; - } - var columnName = this.columnEscaped(model, prop); - if (kind && type) { - return (kind + ' INDEX ' + columnName + ' (' + columnName + ') ' + type); - } else { - return (kind + ' INDEX ' + columnName + ' ' + type + ' (' + columnName + ') '); - } + // Recycled from alterTable single indexes above, more or less. + var i = this._models[model].properties[prop].index; + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + var columnName = this.columnEscaped(model, prop); + if (kind && type) { + return (kind + ' INDEX ' + columnName + ' (' + columnName + ') ' + type); + } else { + return (kind + ' INDEX ' + columnName + ' ' + type + ' (' + columnName + ') '); + } }; MySQL.prototype.indexSettingsSQL = function (model, prop) { - // Recycled from alterTable multi-column indexes above, more or less. - var i = this._models[model].settings.indexes[prop]; - var type = ''; - var kind = ''; - if (i.type) { - type = 'USING ' + i.type; - } - if (i.kind) { - kind = i.kind; - } - var columnName = this.columnEscaped(model, prop); - if (kind && type) { - return (kind + ' INDEX ' + columnName + ' (' + i.columns + ') ' + type); - } else { - return (kind + ' INDEX ' + type + ' ' + columnName + ' (' + i.columns + ')'); - } + // Recycled from alterTable multi-column indexes above, more or less. + var i = this._models[model].settings.indexes[prop]; + var type = ''; + var kind = ''; + if (i.type) { + type = 'USING ' + i.type; + } + if (i.kind) { + kind = i.kind; + } + var columnName = this.columnEscaped(model, prop); + if (kind && type) { + return (kind + ' INDEX ' + columnName + ' (' + i.columns + ') ' + type); + } else { + return (kind + ' INDEX ' + type + ' ' + columnName + ' (' + i.columns + ')'); + } }; MySQL.prototype.propertySettingsSQL = function (model, prop) { - var p = this._models[model].properties[prop]; - var line = this.columnDataType(model, prop) + ' ' + - (p.nullable === false || p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); - return line; + var p = this._models[model].properties[prop]; + var line = this.columnDataType(model, prop) + ' ' + + (p.nullable === false || p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL'); + return line; }; MySQL.prototype.columnDataType = function (model, property) { - var columnMetadata = this.columnMetadata(model, property); - var colType = columnMetadata && columnMetadata.dataType; - if (colType) { - colType = colType.toUpperCase(); - } - var prop = this._models[model].properties[property]; - if (!prop) { - return null; - } - var colLength = columnMetadata && columnMetadata.dataLength || prop.length; - if (colType) { - return colType + (colLength ? '(' + colLength + ')' : ''); - } - return datatype(prop); + var columnMetadata = this.columnMetadata(model, property); + var colType = columnMetadata && columnMetadata.dataType; + if (colType) { + colType = colType.toUpperCase(); + } + var prop = this._models[model].properties[property]; + if (!prop) { + return null; + } + var colLength = columnMetadata && columnMetadata.dataLength || prop.length; + if (colType) { + return colType + (colLength ? '(' + colLength + ')' : ''); + } + return datatype(prop); }; function datatype(p) { - var dt = ''; - switch (p.type.name) { - default: - case 'String': - case 'JSON': - dt = columnType(p, 'VARCHAR'); - dt = stringOptionsByType(p, dt); - break; - case 'Text': - dt = columnType(p, 'TEXT'); - dt = stringOptionsByType(p, dt); - break; - case 'Number': - dt = columnType(p, 'INT'); - dt = numericOptionsByType(p, dt); - break; - case 'Date': - dt = columnType(p, 'DATETIME'); // Currently doesn't need options. - break; - case 'Boolean': - dt = 'TINYINT(1)'; - break; - case 'Point': - case 'GeoPoint': - dt = 'POINT'; - break; - case 'Enum': - dt = 'ENUM(' + p.type._string + ')'; - dt = stringOptions(p, dt); // Enum columns can have charset/collation. - break; - } - return dt; + var dt = ''; + switch (p.type.name) { + default: + case 'String': + case 'JSON': + dt = columnType(p, 'VARCHAR'); + dt = stringOptionsByType(p, dt); + break; + case 'Text': + dt = columnType(p, 'TEXT'); + dt = stringOptionsByType(p, dt); + break; + case 'Number': + dt = columnType(p, 'INT'); + dt = numericOptionsByType(p, dt); + break; + case 'Date': + dt = columnType(p, 'DATETIME'); // Currently doesn't need options. + break; + case 'Boolean': + dt = 'TINYINT(1)'; + break; + case 'Point': + case 'GeoPoint': + dt = 'POINT'; + break; + case 'Enum': + dt = 'ENUM(' + p.type._string + ')'; + dt = stringOptions(p, dt); // Enum columns can have charset/collation. + break; + } + return dt; } function columnType(p, defaultType) { - var dt = defaultType; - if (p.dataType) { - dt = String(p.dataType); - } - return dt; + var dt = defaultType; + if (p.dataType) { + dt = String(p.dataType); + } + return dt; } function stringOptionsByType(p, dt) { - switch (dt.toLowerCase()) { - default: - case 'varchar': - case 'char': - dt += '(' + (p.limit || 255) + ')'; - break; + switch (dt.toLowerCase()) { + default: + case 'varchar': + case 'char': + dt += '(' + (p.limit || 255) + ')'; + break; - case 'text': - case 'tinytext': - case 'mediumtext': - case 'longtext': + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': - break; - } - dt = stringOptions(p, dt); - return dt; + break; + } + dt = stringOptions(p, dt); + return dt; } function stringOptions(p, dt) { - if (p.charset) { - dt += " CHARACTER SET " + p.charset; - } - if (p.collation) { - dt += " COLLATE " + p.collation; - } - return dt; + if (p.charset) { + dt += " CHARACTER SET " + p.charset; + } + if (p.collation) { + dt += " COLLATE " + p.collation; + } + return dt; } function numericOptionsByType(p, dt) { - switch (dt.toLowerCase()) { - default: - case 'tinyint': - case 'smallint': - case 'mediumint': - case 'int': - case 'integer': - case 'bigint': - dt = integerOptions(p, dt); - break; + switch (dt.toLowerCase()) { + default: + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'integer': + case 'bigint': + dt = integerOptions(p, dt); + break; - case 'decimal': - case 'numeric': - dt = fixedPointOptions(p, dt); - break; + case 'decimal': + case 'numeric': + dt = fixedPointOptions(p, dt); + break; - case 'float': - case 'double': - dt = floatingPointOptions(p, dt); - break; - } - dt = unsigned(p, dt); - return dt; + case 'float': + case 'double': + dt = floatingPointOptions(p, dt); + break; + } + dt = unsigned(p, dt); + return dt; } function floatingPointOptions(p, dt) { - var precision = 16; - var scale = 8; - if (p.precision) { - precision = Number(p.precision); - } - if (p.scale) { - scale = Number(p.scale); - } - if (p.precision && p.scale) { - dt += '(' + precision + ',' + scale + ')'; - } else if (p.precision) { - dt += '(' + precision + ')'; - } - return dt; + var precision = 16; + var scale = 8; + if (p.precision) { + precision = Number(p.precision); + } + if (p.scale) { + scale = Number(p.scale); + } + if (p.precision && p.scale) { + dt += '(' + precision + ',' + scale + ')'; + } else if (p.precision) { + dt += '(' + precision + ')'; + } + return dt; } /* @TODO: Change fixed point to use an arbitrary precision arithmetic library. */ @@ -1010,84 +1009,84 @@ function floatingPointOptions(p, dt) { /* JS. Also, defaulting column to (9,2) and not allowing non-specified 'DECIMAL' */ /* declaration which would default to DECIMAL(10,0). Instead defaulting to (9,2). */ function fixedPointOptions(p, dt) { - var precision = 9; - var scale = 2; - if (p.precision) { - precision = Number(p.precision); - } - if (p.scale) { - scale = Number(p.scale); - } - dt += '(' + precision + ',' + scale + ')'; - return dt; + var precision = 9; + var scale = 2; + if (p.precision) { + precision = Number(p.precision); + } + if (p.scale) { + scale = Number(p.scale); + } + dt += '(' + precision + ',' + scale + ')'; + return dt; } function integerOptions(p, dt) { - var tmp = 0; - if (p.display || p.limit) { - tmp = Number(p.display || p.limit); + var tmp = 0; + if (p.display || p.limit) { + tmp = Number(p.display || p.limit); + } + if (tmp > 0) { + dt += '(' + tmp + ')'; + } else if (p.unsigned) { + switch (dt.toLowerCase()) { + default: + case 'int': + dt += '(10)'; + break; + case 'mediumint': + dt += '(8)'; + break; + case 'smallint': + dt += '(5)'; + break; + case 'tinyint': + dt += '(3)'; + break; + case 'bigint': + dt += '(20)'; + break; } - if (tmp > 0) { - dt += '(' + tmp + ')'; - } else if (p.unsigned) { - switch (dt.toLowerCase()) { - default: - case 'int': - dt += '(10)'; - break; - case 'mediumint': - dt += '(8)'; - break; - case 'smallint': - dt += '(5)'; - break; - case 'tinyint': - dt += '(3)'; - break; - case 'bigint': - dt += '(20)'; - break; - } - } else { - switch (dt.toLowerCase()) { - default: - case 'int': - dt += '(11)'; - break; - case 'mediumint': - dt += '(9)'; - break; - case 'smallint': - dt += '(6)'; - break; - case 'tinyint': - dt += '(4)'; - break; - case 'bigint': - dt += '(20)'; - break; - } + } else { + switch (dt.toLowerCase()) { + default: + case 'int': + dt += '(11)'; + break; + case 'mediumint': + dt += '(9)'; + break; + case 'smallint': + dt += '(6)'; + break; + case 'tinyint': + dt += '(4)'; + break; + case 'bigint': + dt += '(20)'; + break; } - return dt; + } + return dt; } function unsigned(p, dt) { - if (p.unsigned) { - dt += ' UNSIGNED'; - } - return dt; + if (p.unsigned) { + dt += ' UNSIGNED'; + } + return dt; } /** * Disconnect from MySQL */ MySQL.prototype.disconnect = function () { - if(this.debug) { - console.log('disconnect'); - } - if(this.client) { - this.client.end(); - } + if (this.debug) { + console.log('disconnect'); + } + if (this.client) { + this.client.end(); + } }; require('./discovery')(MySQL); diff --git a/test/connection.test.js b/test/connection.test.js index fae9810..0895083 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -3,105 +3,102 @@ var assert = require('assert'); var db, DummyModel, odb; -describe('migrations', function() { +describe('migrations', function () { - before(function() { - require('./init.js'); - - odb = getDataSource({collation: 'utf8_general_ci', createDatabase: true}); - db = odb; - }); - - - it('should use utf8 charset', function(done) { - - var test_set = /utf8/; - var test_collo = /utf8_general_ci/; - var test_set_str = 'utf8'; - var test_set_collo = 'utf8_general_ci'; - charsetTest(test_set, test_collo, test_set_str, test_set_collo, done); + before(function () { + require('./init.js'); + odb = getDataSource({collation: 'utf8_general_ci', createDatabase: true}); + db = odb; + }); + + it('should use utf8 charset', function (done) { + + var test_set = /utf8/; + var test_collo = /utf8_general_ci/; + var test_set_str = 'utf8'; + var test_set_collo = 'utf8_general_ci'; + charsetTest(test_set, test_collo, test_set_str, test_set_collo, done); + + }); + + it('should disconnect first db', function (done) { + db.client.end(function () { + odb = getSchema(); + done() }); - - it('should disconnect first db', function(done) { - db.client.end(function(){ - odb = getSchema(); - done() - }); - }); - - it('should use latin1 charset', function(done) { - - var test_set = /latin1/; - var test_collo = /latin1_general_ci/; - var test_set_str = 'latin1'; - var test_set_collo = 'latin1_general_ci'; - charsetTest(test_set, test_collo, test_set_str, test_set_collo, done); - - }); - - it('should drop db and disconnect all', function(done) { - db.connector.query('DROP DATABASE IF EXISTS ' + db.settings.database, function(err) { - db.client.end(function(){ - done(); - }); - }); + }); + + it('should use latin1 charset', function (done) { + + var test_set = /latin1/; + var test_collo = /latin1_general_ci/; + var test_set_str = 'latin1'; + var test_set_collo = 'latin1_general_ci'; + charsetTest(test_set, test_collo, test_set_str, test_set_collo, done); + + }); + + it('should drop db and disconnect all', function (done) { + db.connector.query('DROP DATABASE IF EXISTS ' + db.settings.database, function (err) { + db.client.end(function () { + done(); + }); }); + }); }); +function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done) { + query('DROP DATABASE IF EXISTS ' + odb.settings.database, function (err) { + assert.ok(!err); + odb.client.end(function () { -function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done){ - - query('DROP DATABASE IF EXISTS ' + odb.settings.database, function(err) { - assert.ok(!err); - odb.client.end(function(){ - - db = getSchema({collation: test_set_collo, createDatabase: true}); - DummyModel = db.define('DummyModel', {string: String}); - db.automigrate(function(){ - var q = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ' + db.client.escape(db.settings.database) + ' LIMIT 1'; - db.connector.query(q, function(err, r) { - assert.ok(!err); - assert.ok(r[0].DEFAULT_COLLATION_NAME.match(test_collo)); - db.connector.query('SHOW VARIABLES LIKE "character_set%"', function(err, r){ - assert.ok(!err); - var hit_all = 0; - for (var result in r) { - hit_all += matchResult(r[result], 'character_set_connection', test_set); - hit_all += matchResult(r[result], 'character_set_database', test_set); - hit_all += matchResult(r[result], 'character_set_results', test_set); - hit_all += matchResult(r[result], 'character_set_client', test_set); - } - assert.equal(hit_all, 4); - }); - db.connector.query('SHOW VARIABLES LIKE "collation%"', function(err, r){ - assert.ok(!err); - var hit_all = 0; - for (var result in r) { - hit_all += matchResult(r[result], 'collation_connection', test_set); - hit_all += matchResult(r[result], 'collation_database', test_set); - } - assert.equal(hit_all, 2); - done(); - }); - }); - }); + db = getSchema({collation: test_set_collo, createDatabase: true}); + DummyModel = db.define('DummyModel', {string: String}); + db.automigrate(function () { + var q = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ' + db.client.escape(db.settings.database) + ' LIMIT 1'; + db.connector.query(q, function (err, r) { + assert.ok(!err); + assert.ok(r[0].DEFAULT_COLLATION_NAME.match(test_collo)); + db.connector.query('SHOW VARIABLES LIKE "character_set%"', function (err, r) { + assert.ok(!err); + var hit_all = 0; + for (var result in r) { + hit_all += matchResult(r[result], 'character_set_connection', test_set); + hit_all += matchResult(r[result], 'character_set_database', test_set); + hit_all += matchResult(r[result], 'character_set_results', test_set); + hit_all += matchResult(r[result], 'character_set_client', test_set); + } + assert.equal(hit_all, 4); + }); + db.connector.query('SHOW VARIABLES LIKE "collation%"', function (err, r) { + assert.ok(!err); + var hit_all = 0; + for (var result in r) { + hit_all += matchResult(r[result], 'collation_connection', test_set); + hit_all += matchResult(r[result], 'collation_database', test_set); + } + assert.equal(hit_all, 2); + done(); + }); }); + }); }); - + }); + } function matchResult(result, variable_name, match) { - if(result.Variable_name == variable_name){ - assert.ok(result.Value.match(match)); - return 1; - } - return 0; + if (result.Variable_name == variable_name) { + assert.ok(result.Value.match(match)); + return 1; + } + return 0; } var query = function (sql, cb) { - odb.connector.query(sql, cb); + odb.connector.query(sql, cb); }; diff --git a/test/datatypes.test.js b/test/datatypes.test.js index 2a6433d..71673b7 100644 --- a/test/datatypes.test.js +++ b/test/datatypes.test.js @@ -3,129 +3,129 @@ var assert = require('assert'); var db, EnumModel, ANIMAL_ENUM; -describe('MySQL specific datatypes', function() { - - before(setup); - - it('should run migration', function(done) { - db.automigrate(function(){ - done(); - }); +describe('MySQL specific datatypes', function () { + + before(setup); + + it('should run migration', function (done) { + db.automigrate(function () { + done(); }); - - it('An enum should parse itself', function(done) { - assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM('cat')); - assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM('CAT')); - assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM(2)); - assert.equal(ANIMAL_ENUM.CAT, 'cat'); - assert.equal(ANIMAL_ENUM(null), null); - assert.equal(ANIMAL_ENUM(''), ''); - assert.equal(ANIMAL_ENUM(0), ''); + }); + + it('An enum should parse itself', function (done) { + assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM('cat')); + assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM('CAT')); + assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM(2)); + assert.equal(ANIMAL_ENUM.CAT, 'cat'); + assert.equal(ANIMAL_ENUM(null), null); + assert.equal(ANIMAL_ENUM(''), ''); + assert.equal(ANIMAL_ENUM(0), ''); + done(); + }); + + it('should create a model instance with Enums', function (done) { + var em = EnumModel.create({animal: ANIMAL_ENUM.CAT, condition: 'sleepy', mood: 'happy'}, function (err, obj) { + assert.ok(!err); + assert.equal(obj.condition, 'sleepy'); + EnumModel.findOne({where: {animal: ANIMAL_ENUM.CAT}}, function (err, found) { + assert.ok(!err); + assert.equal(found.mood, 'happy'); + assert.equal(found.animal, ANIMAL_ENUM.CAT); done(); + }); }); - - it('should create a model instance with Enums', function(done) { - var em = EnumModel.create({animal: ANIMAL_ENUM.CAT, condition: 'sleepy', mood: 'happy'}, function(err, obj) { - assert.ok(!err); - assert.equal(obj.condition, 'sleepy'); - EnumModel.findOne({where: {animal: ANIMAL_ENUM.CAT}}, function(err, found){ - assert.ok(!err); - assert.equal(found.mood, 'happy'); - assert.equal(found.animal, ANIMAL_ENUM.CAT); - done(); - }); - }); - }); - - it('should fail spectacularly with invalid enum values', function(done) { - var em = EnumModel.create({animal: 'horse', condition: 'sleepy', mood: 'happy'}, function(err, obj) { - assert.ok(!err); - EnumModel.findById(obj.id, function(err, found){ - assert.ok(!err); - assert.equal(found.animal, ''); // MySQL fun. - assert.equal(found.animal, 0); - done(); - }); - }); - }); - - it('should disconnect when done', function(done) { - db.disconnect(); - done() + }); + + it('should fail spectacularly with invalid enum values', function (done) { + var em = EnumModel.create({animal: 'horse', condition: 'sleepy', mood: 'happy'}, function (err, obj) { + assert.ok(!err); + EnumModel.findById(obj.id, function (err, found) { + assert.ok(!err); + assert.equal(found.animal, ''); // MySQL fun. + assert.equal(found.animal, 0); + done(); + }); }); + }); + + it('should disconnect when done', function (done) { + db.disconnect(); + done() + }); }); function setup(done) { - require('./init.js'); - - db = getSchema(); - - ANIMAL_ENUM = db.EnumFactory('dog', 'cat', 'mouse'); - - EnumModel = db.define('EnumModel', { - animal: { type: ANIMAL_ENUM, null: false }, - condition: { type: db.EnumFactory('hungry', 'sleepy', 'thirsty') }, - mood: { type: db.EnumFactory('angry', 'happy', 'sad') } - }); + require('./init.js'); - blankDatabase(db, done); + db = getSchema(); + + ANIMAL_ENUM = db.EnumFactory('dog', 'cat', 'mouse'); + + EnumModel = db.define('EnumModel', { + animal: { type: ANIMAL_ENUM, null: false }, + condition: { type: db.EnumFactory('hungry', 'sleepy', 'thirsty') }, + mood: { type: db.EnumFactory('angry', 'happy', 'sad') } + }); + + blankDatabase(db, done); } var query = function (sql, cb) { - db.adapter.query(sql, cb); + db.adapter.query(sql, cb); }; var blankDatabase = function (db, cb) { - var dbn = db.settings.database; - var cs = db.settings.charset; - var co = db.settings.collation; - query('DROP DATABASE IF EXISTS ' + dbn, function(err) { - var q = 'CREATE DATABASE ' + dbn; - if(cs){ - q += ' CHARACTER SET ' + cs; - } - if(co){ - q += ' COLLATE ' + co; - } - query(q, function(err) { - query('USE '+ dbn, cb); - }); + var dbn = db.settings.database; + var cs = db.settings.charset; + var co = db.settings.collation; + query('DROP DATABASE IF EXISTS ' + dbn, function (err) { + var q = 'CREATE DATABASE ' + dbn; + if (cs) { + q += ' CHARACTER SET ' + cs; + } + if (co) { + q += ' COLLATE ' + co; + } + query(q, function (err) { + query('USE ' + dbn, cb); }); + }); }; getFields = function (model, cb) { - query('SHOW FIELDS FROM ' + model, function(err, res) { - if (err) { - cb(err); - } else { - var fields = {}; - res.forEach(function(field){ - fields[field.Field] = field; - }); - cb(err, fields); - } - }); + query('SHOW FIELDS FROM ' + model, function (err, res) { + if (err) { + cb(err); + } else { + var fields = {}; + res.forEach(function (field) { + fields[field.Field] = field; + }); + cb(err, fields); + } + }); } getIndexes = function (model, cb) { - query('SHOW INDEXES FROM ' + model, function(err, res) { - if (err) { - console.log(err); - cb(err); - } else { - var indexes = {}; - // Note: this will only show the first key of compound keys - res.forEach(function(index) { - if (parseInt(index.Seq_in_index, 10) == 1) { - indexes[index.Key_name] = index - } - }); - cb(err, indexes); + query('SHOW INDEXES FROM ' + model, function (err, res) { + if (err) { + console.log(err); + cb(err); + } else { + var indexes = {}; + // Note: this will only show the first key of compound keys + res.forEach(function (index) { + if (parseInt(index.Seq_in_index, 10) == 1) { + indexes[index.Key_name] = index } - }); + }); + cb(err, indexes); + } + }); }; diff --git a/test/imported.test.js b/test/imported.test.js index 681325f..3ed5f88 100644 --- a/test/imported.test.js +++ b/test/imported.test.js @@ -1,10 +1,10 @@ -describe('mysql imported features', function() { +describe('mysql imported features', function () { - before(function() { - require('./init.js'); - }); + before(function () { + require('./init.js'); + }); - require('loopback-datasource-juggler/test/common.batch.js'); - require('loopback-datasource-juggler/test/include.test.js'); + require('loopback-datasource-juggler/test/common.batch.js'); + require('loopback-datasource-juggler/test/include.test.js'); }); diff --git a/test/init.js b/test/init.js index dac2e87..9959213 100644 --- a/test/init.js +++ b/test/init.js @@ -4,29 +4,29 @@ var DataSource = require('loopback-datasource-juggler').DataSource; var config = require('rc')('loopback', {test: {mysql: {}}}).test.mysql; -global.getConfig = function(options) { +global.getConfig = function (options) { - var dbConf = { - host: config.host || 'localhost', - port: config.port || 3306, - database: 'myapp_test', - username: config.username, - password: config.password, - createDatabase: true - }; + var dbConf = { + host: config.host || 'localhost', + port: config.port || 3306, + database: 'myapp_test', + username: config.username, + password: config.password, + createDatabase: true + }; - if (options) { - for (var el in options) { - dbConf[el] = options[el]; - } + if (options) { + for (var el in options) { + dbConf[el] = options[el]; } + } - return dbConf; + return dbConf; }; -global.getDataSource = global.getSchema = function(options) { - var db = new DataSource(require('../'), getConfig(options)); - return db; +global.getDataSource = global.getSchema = function (options) { + var db = new DataSource(require('../'), getConfig(options)); + return db; }; diff --git a/test/migration.test.js b/test/migration.test.js index d4a627a..137e552 100644 --- a/test/migration.test.js +++ b/test/migration.test.js @@ -2,428 +2,411 @@ var should = require('./init.js'); var assert = require('assert'); var Schema = require('loopback-datasource-juggler').Schema; -var db, UserData, StringData, NumberData, DateData; +var db, UserData, StringData, NumberData, DateData; -describe('migrations', function() { - - before(setup); - - it('should run migration', function(done) { - db.automigrate(function(){ - done(); - }); - }); - - it('UserData should have correct columns', function(done) { - getFields('UserData', function(err, fields) { - assert.deepEqual(fields, { - id: { - Field: 'id', - Type: 'int(11)', - Null: 'NO', - Key: 'PRI', - Default: null, - Extra: 'auto_increment' }, - email: { - Field: 'email', - Type: 'varchar(255)', - Null: 'NO', - Key: 'MUL', - Default: null, - Extra: '' }, - name: { - Field: 'name', - Type: 'varchar(255)', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - bio: { - Field: 'bio', - Type: 'text', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - birthDate: { - Field: 'birthDate', - Type: 'datetime', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - pendingPeriod: { - Field: 'pendingPeriod', - Type: 'int(11)', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - createdByAdmin: { - Field: 'createdByAdmin', - Type: 'tinyint(1)', - Null: 'YES', - Key: '', - Default: null, - Extra: '' } - }); - done(); - }); - }); - - it('UserData should have correct indexes', function(done) { - // Note: getIndexes truncates multi-key indexes to the first member. Hence index1 is correct. - getIndexes('UserData', function(err, fields) { - // console.log('....', fields); - assert.deepEqual(fields, { PRIMARY: - { Table: 'UserData', - Non_unique: 0, - Key_name: 'PRIMARY', - Seq_in_index: 1, - Column_name: 'id', - Collation: 'A', - Cardinality: 0, - Sub_part: null, - Packed: null, - Null: '', - Index_type: 'BTREE', - Comment: '' }, - email: - { Table: 'UserData', - Non_unique: 1, - Key_name: 'email', - Seq_in_index: 1, - Column_name: 'email', - Collation: 'A', - Cardinality: null, - Sub_part: null, - Packed: null, - Null: '', - Index_type: 'BTREE', - Comment: '' }, - index0: - { Table: 'UserData', - Non_unique: 1, - Key_name: 'index0', - Seq_in_index: 1, - Column_name: 'email', - Collation: 'A', - Cardinality: null, - Sub_part: null, - Packed: null, - Null: '', - Index_type: 'BTREE', - Comment: '' } - }); - done(); - }); - }); - - it('StringData should have correct columns', function(done) { - getFields('StringData', function(err, fields) { - assert.deepEqual(fields, { id: - { Field: 'id', - Type: 'int(11)', - Null: 'NO', - Key: 'PRI', - Default: null, - Extra: 'auto_increment' }, - smallString: - { Field: 'smallString', - Type: 'char(127)', - Null: 'NO', - Key: 'MUL', - Default: null, - Extra: '' }, - mediumString: - { Field: 'mediumString', - Type: 'varchar(255)', - Null: 'NO', - Key: '', - Default: null, - Extra: '' }, - tinyText: - { Field: 'tinyText', - Type: 'tinytext', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - giantJSON: - { Field: 'giantJSON', - Type: 'longtext', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - text: - { Field: 'text', - Type: 'varchar(1024)', - Null: 'YES', - Key: '', - Default: null, - Extra: '' } - }); - done(); - }); - }); - - it('NumberData should have correct columns', function(done) { - getFields('NumberData', function(err, fields) { - assert.deepEqual(fields, { - id: - { Field: 'id', - Type: 'int(11)', - Null: 'NO', - Key: 'PRI', - Default: null, - Extra: 'auto_increment' }, - number: - { Field: 'number', - Type: 'decimal(10,3) unsigned', - Null: 'NO', - Key: 'MUL', - Default: null, - Extra: '' }, - tinyInt: - { Field: 'tinyInt', - Type: 'tinyint(2)', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - mediumInt: - { Field: 'mediumInt', - Type: 'mediumint(8) unsigned', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - floater: - { Field: 'floater', - Type: 'double(14,6)', - Null: 'YES', - Key: '', - Default: null, - Extra: '' } - }); - done(); - }); - }); +describe('migrations', function () { - it('DateData should have correct columns', function(done) { - getFields('DateData', function(err, fields) { - assert.deepEqual(fields, { - id: - { Field: 'id', - Type: 'int(11)', - Null: 'NO', - Key: 'PRI', - Default: null, - Extra: 'auto_increment' }, - dateTime: - { Field: 'dateTime', - Type: 'datetime', - Null: 'YES', - Key: '', - Default: null, - Extra: '' }, - timestamp: - { Field: 'timestamp', - Type: 'timestamp', - Null: 'YES', - Key: '', - Default: null, - Extra: '' } - }); + before(setup); + + it('should run migration', function (done) { + db.automigrate(function () { + done(); + }); + }); + + it('UserData should have correct columns', function (done) { + getFields('UserData', function (err, fields) { + assert.deepEqual(fields, { + id: { + Field: 'id', + Type: 'int(11)', + Null: 'NO', + Key: 'PRI', + Default: null, + Extra: 'auto_increment' }, + email: { + Field: 'email', + Type: 'varchar(255)', + Null: 'NO', + Key: 'MUL', + Default: null, + Extra: '' }, + name: { + Field: 'name', + Type: 'varchar(255)', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + bio: { + Field: 'bio', + Type: 'text', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + birthDate: { + Field: 'birthDate', + Type: 'datetime', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + pendingPeriod: { + Field: 'pendingPeriod', + Type: 'int(11)', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + createdByAdmin: { + Field: 'createdByAdmin', + Type: 'tinyint(1)', + Null: 'YES', + Key: '', + Default: null, + Extra: '' } + }); + done(); + }); + }); + + it('UserData should have correct indexes', function (done) { + // Note: getIndexes truncates multi-key indexes to the first member. Hence index1 is correct. + getIndexes('UserData', function (err, fields) { + // console.log('....', fields); + assert.deepEqual(fields, { PRIMARY: { Table: 'UserData', + Non_unique: 0, + Key_name: 'PRIMARY', + Seq_in_index: 1, + Column_name: 'id', + Collation: 'A', + Cardinality: 0, + Sub_part: null, + Packed: null, + Null: '', + Index_type: 'BTREE', + Comment: '' }, + email: { Table: 'UserData', + Non_unique: 1, + Key_name: 'email', + Seq_in_index: 1, + Column_name: 'email', + Collation: 'A', + Cardinality: null, + Sub_part: null, + Packed: null, + Null: '', + Index_type: 'BTREE', + Comment: '' }, + index0: { Table: 'UserData', + Non_unique: 1, + Key_name: 'index0', + Seq_in_index: 1, + Column_name: 'email', + Collation: 'A', + Cardinality: null, + Sub_part: null, + Packed: null, + Null: '', + Index_type: 'BTREE', + Comment: '' } + }); + done(); + }); + }); + + it('StringData should have correct columns', function (done) { + getFields('StringData', function (err, fields) { + assert.deepEqual(fields, { id: { Field: 'id', + Type: 'int(11)', + Null: 'NO', + Key: 'PRI', + Default: null, + Extra: 'auto_increment' }, + smallString: { Field: 'smallString', + Type: 'char(127)', + Null: 'NO', + Key: 'MUL', + Default: null, + Extra: '' }, + mediumString: { Field: 'mediumString', + Type: 'varchar(255)', + Null: 'NO', + Key: '', + Default: null, + Extra: '' }, + tinyText: { Field: 'tinyText', + Type: 'tinytext', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + giantJSON: { Field: 'giantJSON', + Type: 'longtext', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + text: { Field: 'text', + Type: 'varchar(1024)', + Null: 'YES', + Key: '', + Default: null, + Extra: '' } + }); + done(); + }); + }); + + it('NumberData should have correct columns', function (done) { + getFields('NumberData', function (err, fields) { + assert.deepEqual(fields, { + id: { Field: 'id', + Type: 'int(11)', + Null: 'NO', + Key: 'PRI', + Default: null, + Extra: 'auto_increment' }, + number: { Field: 'number', + Type: 'decimal(10,3) unsigned', + Null: 'NO', + Key: 'MUL', + Default: null, + Extra: '' }, + tinyInt: { Field: 'tinyInt', + Type: 'tinyint(2)', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + mediumInt: { Field: 'mediumInt', + Type: 'mediumint(8) unsigned', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + floater: { Field: 'floater', + Type: 'double(14,6)', + Null: 'YES', + Key: '', + Default: null, + Extra: '' } + }); + done(); + }); + }); + + it('DateData should have correct columns', function (done) { + getFields('DateData', function (err, fields) { + assert.deepEqual(fields, { + id: { Field: 'id', + Type: 'int(11)', + Null: 'NO', + Key: 'PRI', + Default: null, + Extra: 'auto_increment' }, + dateTime: { Field: 'dateTime', + Type: 'datetime', + Null: 'YES', + Key: '', + Default: null, + Extra: '' }, + timestamp: { Field: 'timestamp', + Type: 'timestamp', + Null: 'YES', + Key: '', + Default: null, + Extra: '' } + }); + done(); + }); + }); + + it('should autoupgrade', function (done) { + var userExists = function (cb) { + query('SELECT * FROM UserData', function (err, res) { + cb(!err && res[0].email == 'test@example.com'); + }); + } + + UserData.create({email: 'test@example.com'}, function (err, user) { + assert.ok(!err, 'Could not create user: ' + err); + userExists(function (yep) { + assert.ok(yep, 'User does not exist'); + }); + UserData.defineProperty('email', { type: String }); + UserData.defineProperty('name', {type: String, dataType: 'char', limit: 50}); + UserData.defineProperty('newProperty', {type: Number, unsigned: true, dataType: 'bigInt'}); + // UserData.defineProperty('pendingPeriod', false); This will not work as expected. + db.autoupdate(function (err) { + getFields('UserData', function (err, fields) { + // change nullable for email + assert.equal(fields.email.Null, 'YES', 'Email does not allow null'); + // change type of name + assert.equal(fields.name.Type, 'char(50)', 'Name is not char(50)'); + // add new column + assert.ok(fields.newProperty, 'New column was not added'); + if (fields.newProperty) { + assert.equal(fields.newProperty.Type, 'bigint(20) unsigned', 'New column type is not bigint(20) unsigned'); + } + // drop column - will not happen. + // assert.ok(!fields.pendingPeriod, 'Did not drop column pendingPeriod'); + // user still exists + userExists(function (yep) { + assert.ok(yep, 'User does not exist'); done(); + }); }); + }); }); - - it('should autoupgrade', function(done) { - var userExists = function(cb) { - query('SELECT * FROM UserData', function(err, res) { - cb(!err && res[0].email == 'test@example.com'); - }); - } + }); - UserData.create({email: 'test@example.com'}, function(err, user) { - assert.ok(!err, 'Could not create user: ' + err); - userExists(function(yep) { - assert.ok(yep, 'User does not exist'); - }); - UserData.defineProperty('email', { type: String }); - UserData.defineProperty('name', {type: String, dataType: 'char', limit: 50}); - UserData.defineProperty('newProperty', {type: Number, unsigned: true, dataType: 'bigInt'}); - // UserData.defineProperty('pendingPeriod', false); This will not work as expected. - db.autoupdate( function(err) { - getFields('UserData', function(err, fields) { - // change nullable for email - assert.equal(fields.email.Null, 'YES', 'Email does not allow null'); - // change type of name - assert.equal(fields.name.Type, 'char(50)', 'Name is not char(50)'); - // add new column - assert.ok(fields.newProperty, 'New column was not added'); - if (fields.newProperty) { - assert.equal(fields.newProperty.Type, 'bigint(20) unsigned', 'New column type is not bigint(20) unsigned'); - } - // drop column - will not happen. - // assert.ok(!fields.pendingPeriod, 'Did not drop column pendingPeriod'); - // user still exists - userExists(function(yep) { - assert.ok(yep, 'User does not exist'); - done(); - }); - }); - }); - }); - }); - - it('should check actuality of dataSource', function(done) { - // 'drop column' - UserData.dataSource.isActual(function(err, ok) { - assert.ok(ok, 'dataSource is not actual (should be)'); - UserData.defineProperty('essay', {type: Schema.Text}); - // UserData.defineProperty('email', false); Can't undefine currently. - UserData.dataSource.isActual(function(err, ok) { - assert.ok(!ok, 'dataSource is actual (shouldn\t be)'); - done() - }); - }); - }); - - it('should allow numbers with decimals', function(done) { - NumberData.create({number: 1.1234567, tinyInt: 123456, mediumInt: -1234567, floater: 123456789.1234567 }, function(err, obj) { - assert.ok(!err); - assert.ok(obj); - NumberData.findById(obj.id, function(err, found) { - assert.equal(found.number, 1.123); - assert.equal(found.tinyInt, 127); - assert.equal(found.mediumInt, 0); - assert.equal(found.floater, 99999999.999999); - done(); - }); - }); - }); - - it('should allow both kinds of date columns', function(done) { - DateData.create({ - dateTime: new Date('Aug 9 1996 07:47:33 GMT'), - timestamp: new Date('Sep 22 2007 17:12:22 GMT') - }, function(err, obj){ - assert.ok(!err); - assert.ok(obj); - DateData.findById(obj.id, function(err, found){ - assert.equal(found.dateTime.toGMTString(), 'Fri, 09 Aug 1996 07:47:33 GMT'); - assert.equal(found.timestamp.toGMTString(), 'Sat, 22 Sep 2007 17:12:22 GMT'); - done(); - }); - }); - }); - - it('should disconnect when done', function(done) { - db.disconnect(); + it('should check actuality of dataSource', function (done) { + // 'drop column' + UserData.dataSource.isActual(function (err, ok) { + assert.ok(ok, 'dataSource is not actual (should be)'); + UserData.defineProperty('essay', {type: Schema.Text}); + // UserData.defineProperty('email', false); Can't undefine currently. + UserData.dataSource.isActual(function (err, ok) { + assert.ok(!ok, 'dataSource is actual (shouldn\t be)'); done() + }); }); + }); + + it('should allow numbers with decimals', function (done) { + NumberData.create({number: 1.1234567, tinyInt: 123456, mediumInt: -1234567, floater: 123456789.1234567 }, function (err, obj) { + assert.ok(!err); + assert.ok(obj); + NumberData.findById(obj.id, function (err, found) { + assert.equal(found.number, 1.123); + assert.equal(found.tinyInt, 127); + assert.equal(found.mediumInt, 0); + assert.equal(found.floater, 99999999.999999); + done(); + }); + }); + }); + + it('should allow both kinds of date columns', function (done) { + DateData.create({ + dateTime: new Date('Aug 9 1996 07:47:33 GMT'), + timestamp: new Date('Sep 22 2007 17:12:22 GMT') + }, function (err, obj) { + assert.ok(!err); + assert.ok(obj); + DateData.findById(obj.id, function (err, found) { + assert.equal(found.dateTime.toGMTString(), 'Fri, 09 Aug 1996 07:47:33 GMT'); + assert.equal(found.timestamp.toGMTString(), 'Sat, 22 Sep 2007 17:12:22 GMT'); + done(); + }); + }); + }); + + it('should disconnect when done', function (done) { + db.disconnect(); + done() + }); }); function setup(done) { - require('./init.js'); - - db = getSchema(); - - UserData = db.define('UserData', { - email: { type: String, null: false, index: true }, - name: String, - bio: Schema.Text, - birthDate: Date, - pendingPeriod: Number, - createdByAdmin: Boolean, - } , { indexes: { - index0: { - columns: 'email, createdByAdmin' - } - } - }); + require('./init.js'); - StringData = db.define('StringData', { - smallString: {type: String, null: false, index: true, dataType: 'char', limit: 127}, - mediumString: {type: String, null: false, dataType: 'varchar', limit: 255}, - tinyText: {type: String, dataType: 'tinyText'}, - giantJSON: {type: Schema.JSON, dataType: 'longText'}, - text: {type: Schema.Text, dataType: 'varchar', limit: 1024} - }); - - NumberData = db.define('NumberData', { - number: {type: Number, null: false, index: true, unsigned: true, dataType: 'decimal', precision: 10, scale: 3}, - tinyInt: {type: Number, dataType: 'tinyInt', display: 2}, - mediumInt: {type: Number, dataType: 'mediumInt', unsigned: true}, - floater: {type: Number, dataType: 'double', precision: 14, scale: 6} - }); - - DateData = db.define('DateData', { - dateTime: {type: Date, dataType: 'datetime'}, - timestamp: {type: Date, dataType: 'timestamp'} - }); + db = getSchema(); + + UserData = db.define('UserData', { + email: { type: String, null: false, index: true }, + name: String, + bio: Schema.Text, + birthDate: Date, + pendingPeriod: Number, + createdByAdmin: Boolean, + }, { indexes: { + index0: { + columns: 'email, createdByAdmin' + } + } + }); + + StringData = db.define('StringData', { + smallString: {type: String, null: false, index: true, dataType: 'char', limit: 127}, + mediumString: {type: String, null: false, dataType: 'varchar', limit: 255}, + tinyText: {type: String, dataType: 'tinyText'}, + giantJSON: {type: Schema.JSON, dataType: 'longText'}, + text: {type: Schema.Text, dataType: 'varchar', limit: 1024} + }); + + NumberData = db.define('NumberData', { + number: {type: Number, null: false, index: true, unsigned: true, dataType: 'decimal', precision: 10, scale: 3}, + tinyInt: {type: Number, dataType: 'tinyInt', display: 2}, + mediumInt: {type: Number, dataType: 'mediumInt', unsigned: true}, + floater: {type: Number, dataType: 'double', precision: 14, scale: 6} + }); + + DateData = db.define('DateData', { + dateTime: {type: Date, dataType: 'datetime'}, + timestamp: {type: Date, dataType: 'timestamp'} + }); + + blankDatabase(db, done); - blankDatabase(db, done); - } var query = function (sql, cb) { - db.adapter.query(sql, cb); + db.adapter.query(sql, cb); }; var blankDatabase = function (db, cb) { - var dbn = db.settings.database; - var cs = db.settings.charset; - var co = db.settings.collation; - query('DROP DATABASE IF EXISTS ' + dbn, function(err) { - var q = 'CREATE DATABASE ' + dbn; - if(cs){ - q += ' CHARACTER SET ' + cs; - } - if(co){ - q += ' COLLATE ' + co; - } - query(q, function(err) { - query('USE '+ dbn, cb); - }); + var dbn = db.settings.database; + var cs = db.settings.charset; + var co = db.settings.collation; + query('DROP DATABASE IF EXISTS ' + dbn, function (err) { + var q = 'CREATE DATABASE ' + dbn; + if (cs) { + q += ' CHARACTER SET ' + cs; + } + if (co) { + q += ' COLLATE ' + co; + } + query(q, function (err) { + query('USE ' + dbn, cb); }); + }); }; getFields = function (model, cb) { - query('SHOW FIELDS FROM ' + model, function(err, res) { - if (err) { - cb(err); - } else { - var fields = {}; - res.forEach(function(field){ - fields[field.Field] = field; - }); - cb(err, fields); - } - }); + query('SHOW FIELDS FROM ' + model, function (err, res) { + if (err) { + cb(err); + } else { + var fields = {}; + res.forEach(function (field) { + fields[field.Field] = field; + }); + cb(err, fields); + } + }); } getIndexes = function (model, cb) { - query('SHOW INDEXES FROM ' + model, function(err, res) { - if (err) { - console.log(err); - cb(err); - } else { - var indexes = {}; - // Note: this will only show the first key of compound keys - res.forEach(function(index) { - if (parseInt(index.Seq_in_index, 10) == 1) { - indexes[index.Key_name] = index - } - }); - cb(err, indexes); + query('SHOW INDEXES FROM ' + model, function (err, res) { + if (err) { + console.log(err); + cb(err); + } else { + var indexes = {}; + // Note: this will only show the first key of compound keys + res.forEach(function (index) { + if (parseInt(index.Seq_in_index, 10) == 1) { + indexes[index.Key_name] = index } - }); + }); + cb(err, indexes); + } + }); }; diff --git a/test/mysql.discover.test.js b/test/mysql.discover.test.js index 0e0125b..7597e74 100644 --- a/test/mysql.discover.test.js +++ b/test/mysql.discover.test.js @@ -5,26 +5,26 @@ var assert = require('assert'); var DataSource = require('loopback-datasource-juggler').DataSource; var db; -before(function() { - var config = require('rc')('loopback', {dev: {mysql: {}}}).dev.mysql; - config.database = 'STRONGLOOP'; - db = new DataSource(require('../'), config); +before(function () { + var config = require('rc')('loopback', {dev: {mysql: {}}}).dev.mysql; + config.database = 'STRONGLOOP'; + db = new DataSource(require('../'), config); }); -describe('discoverModels', function() { - describe('Discover models including views', function() { - it('should return an array of tables and views', function(done) { +describe('discoverModels', function () { + describe('Discover models including views', function () { + it('should return an array of tables and views', function (done) { db.discoverModelDefinitions({ - views : true, - limit : 3 - }, function(err, models) { + views: true, + limit: 3 + }, function (err, models) { if (err) { console.error(err); done(err); } else { var views = false; - models.forEach(function(m) { + models.forEach(function (m) { // console.dir(m); if (m.type === 'view') { views = true; @@ -37,19 +37,19 @@ describe('discoverModels', function() { }); }); - describe('Discover models excluding views', function() { - it('should return an array of only tables', function(done) { + describe('Discover models excluding views', function () { + it('should return an array of only tables', function (done) { db.discoverModelDefinitions({ - views : false, - limit : 3 - }, function(err, models) { + views: false, + limit: 3 + }, function (err, models) { if (err) { console.error(err); done(err); } else { var views = false; - models.forEach(function(m) { + models.forEach(function (m) { // console.dir(m); if (m.type === 'view') { views = true; @@ -64,19 +64,19 @@ describe('discoverModels', function() { }); }); -describe('Discover models including other users', function() { - it('should return an array of all tables and views', function(done) { +describe('Discover models including other users', function () { + it('should return an array of all tables and views', function (done) { db.discoverModelDefinitions({ - all : true, - limit : 3 - }, function(err, models) { + all: true, + limit: 3 + }, function (err, models) { if (err) { console.error(err); done(err); } else { var others = false; - models.forEach(function(m) { + models.forEach(function (m) { // console.dir(m); if (m.owner !== 'STRONGLOOP') { others = true; @@ -89,15 +89,15 @@ describe('Discover models including other users', function() { }); }); -describe('Discover model properties', function() { - describe('Discover a named model', function() { - it('should return an array of columns for PRODUCT', function(done) { - db.discoverModelProperties('PRODUCT', function(err, models) { +describe('Discover model properties', function () { + describe('Discover a named model', function () { + it('should return an array of columns for PRODUCT', function (done) { + db.discoverModelProperties('PRODUCT', function (err, models) { if (err) { console.error(err); done(err); } else { - models.forEach(function(m) { + models.forEach(function (m) { // console.dir(m); assert(m.tableName === 'PRODUCT'); }); @@ -110,88 +110,88 @@ describe('Discover model properties', function() { }); describe('Discover model primary keys', function () { - it('should return an array of primary keys for PRODUCT', function (done) { - db.discoverPrimaryKeys('PRODUCT',function (err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function (m) { - // console.dir(m); - assert(m.tableName === 'PRODUCT'); - }); - done(null, models); - } + it('should return an array of primary keys for PRODUCT', function (done) { + db.discoverPrimaryKeys('PRODUCT', function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.tableName === 'PRODUCT'); }); + done(null, models); + } }); + }); - it('should return an array of primary keys for STRONGLOOP.PRODUCT', function (done) { - db.discoverPrimaryKeys('PRODUCT', {owner: 'STRONGLOOP'}, function (err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function (m) { - // console.dir(m); - assert(m.tableName === 'PRODUCT'); - }); - done(null, models); - } + it('should return an array of primary keys for STRONGLOOP.PRODUCT', function (done) { + db.discoverPrimaryKeys('PRODUCT', {owner: 'STRONGLOOP'}, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.tableName === 'PRODUCT'); }); + done(null, models); + } }); + }); }); describe('Discover model foreign keys', function () { - it('should return an array of foreign keys for INVENTORY', function (done) { - db.discoverForeignKeys('INVENTORY',function (err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function (m) { - // console.dir(m); - assert(m.fkTableName === 'INVENTORY'); - }); - done(null, models); - } + it('should return an array of foreign keys for INVENTORY', function (done) { + db.discoverForeignKeys('INVENTORY', function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.fkTableName === 'INVENTORY'); }); + done(null, models); + } }); - it('should return an array of foreign keys for STRONGLOOP.INVENTORY', function (done) { - db.discoverForeignKeys('INVENTORY', {owner: 'STRONGLOOP'}, function (err, models) { - if (err) { - console.error(err); - done(err); - } else { - models.forEach(function (m) { - // console.dir(m); - assert(m.fkTableName === 'INVENTORY'); - }); - done(null, models); - } + }); + it('should return an array of foreign keys for STRONGLOOP.INVENTORY', function (done) { + db.discoverForeignKeys('INVENTORY', {owner: 'STRONGLOOP'}, function (err, models) { + if (err) { + console.error(err); + done(err); + } else { + models.forEach(function (m) { + // console.dir(m); + assert(m.fkTableName === 'INVENTORY'); }); + done(null, models); + } }); + }); }); describe('Discover LDL schema from a table', function () { - it('should return an LDL schema for INVENTORY', function (done) { - db.discoverSchema('INVENTORY', {owner: 'STRONGLOOP'}, function (err, schema) { - // console.log('%j', schema); - assert(schema.name === 'Inventory'); - assert(schema.options.mysql.schema === 'STRONGLOOP'); - assert(schema.options.mysql.table === 'INVENTORY'); - assert(schema.properties.productId); - assert(schema.properties.productId.type === 'String'); - assert(schema.properties.productId.mysql.columnName === 'PRODUCT_ID'); - assert(schema.properties.locationId); - assert(schema.properties.locationId.type === 'String'); - assert(schema.properties.locationId.mysql.columnName === 'LOCATION_ID'); - assert(schema.properties.available); - assert(schema.properties.available.type === 'Number'); - assert(schema.properties.total); - assert(schema.properties.total.type === 'Number'); - done(null, schema); - }); + it('should return an LDL schema for INVENTORY', function (done) { + db.discoverSchema('INVENTORY', {owner: 'STRONGLOOP'}, function (err, schema) { + // console.log('%j', schema); + assert(schema.name === 'Inventory'); + assert(schema.options.mysql.schema === 'STRONGLOOP'); + assert(schema.options.mysql.table === 'INVENTORY'); + assert(schema.properties.productId); + assert(schema.properties.productId.type === 'String'); + assert(schema.properties.productId.mysql.columnName === 'PRODUCT_ID'); + assert(schema.properties.locationId); + assert(schema.properties.locationId.type === 'String'); + assert(schema.properties.locationId.mysql.columnName === 'LOCATION_ID'); + assert(schema.properties.available); + assert(schema.properties.available.type === 'Number'); + assert(schema.properties.total); + assert(schema.properties.total.type === 'Number'); + done(null, schema); }); + }); }); describe('Discover and build models', function () {