diff --git a/README.md b/README.md index da3f24a..519f1f9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -## Loopback MySQL Connector +## loopback-connector-mysql -MySQL connector for LoopBack Data Source Juggler. +MySQL connector for [LoopBack Data Source Juggler](http://docs.strongloop.com/loopback-datasource-juggler/). ## Usage @@ -24,51 +24,48 @@ To use it you need `loopback-datasource-juggler`. ```javascript var DataSource = require('loopback-datasource-juggler').DataSource; var dataSource = new DataSource('mysql', { - database: 'myapp_test', - username: 'root' + database: 'mydb', + username: 'myuser', + password: 'mypass' }); ``` - You can optionally pass a few additional parameters supported by `node-mysql`, + You can optionally pass a few additional parameters supported by [`node-mysql`](https://github.com/felixge/node-mysql), most particularly `password` and `collation`. `Collation` currently defaults to `utf8_general_ci`. The `collation` value will also be used to derive the connection charset. -## Running tests - - npm test ## Using the `dataType` field/column option with MySQL -The loopback-datasource-juggler MySQL adapter now supports using the `dataType` -column/property attribute to specify what MySQL column type is used for many -loopback-datasource-juggler types. +The loopback-datasource-juggler MySQL connector now supports using the `dataType` column/property attribute to specify +what MySQL column type is used for many loopback-datasource-juggler types. The following type-dataType combinations are supported: -*

Number

- *
integer
- * tinyint - * smallint - * mediumint - * int - * bigint +- Number + - integer + - tinyint + - smallint + - mediumint + - int + - bigint Use the `limit` option to alter the display width. Example: `{ count : { type: Number, dataType: 'smallInt' }}` - *
floating point types
- * float - * double + - floating point types + - float + - double Use the `precision` and `scale` options to specify custom precision. Default is (16,8). Example: `{ average : { type: Number, dataType: 'float', precision: 20, scale: 4 }}` - *
fixed-point exact value types
- * decimal - * numeric + - fixed-point exact value types + - decimal + - numeric Use the `precision` and `scale` options to specify custom precision. Default is (9,2). @@ -77,13 +74,13 @@ The following type-dataType combinations are supported: Example: `{ stdDev : { type: Number, dataType: 'decimal', precision: 12, scale: 8 }}` -*

String / DataSource.Text / DataSource.JSON

- * varchar - * char - * text - * mediumtext - * tinytext - * longtext +- String / DataSource.Text / DataSource.JSON + - varchar + - char + - text + - mediumtext + - tinytext + - longtext Example: `{ userName : { type: String, dataType: 'char', limit: 24 }}` @@ -91,14 +88,14 @@ The following type-dataType combinations are supported: Example: `{ biography : { type: String, dataType: 'longtext' }}` -*

Date

- * datetime - * timestamp +- Date + - datetime + - timestamp Example: `{ startTime : { type: Date, dataType: 'timestamp' }}` -*

Enum

+* Enum Enums are special. Create an Enum using Enum factory: @@ -110,37 +107,165 @@ The following type-dataType combinations are supported: MOOD('sad'); // 'sad' ``` - * `{ mood: { type: MOOD }}` - * `{ choice: { type: dataSource.EnumFactory('yes', 'no', 'maybe'), null: false }}` + - `{ mood: { type: MOOD }}` + - `{ choice: { type: dataSource.EnumFactory('yes', 'no', 'maybe'), null: false }}` +## Discovering Models + +MySQL data sources allow you to discover model definition information from existing mysql databases. See the following APIs: + + - [dataSource.discoverModelDefinitions([username], fn)](https://github.com/strongloop/loopback#datasourcediscovermodeldefinitionsusername-fn) + - [dataSource.discoverSchema([owner], name, fn)](https://github.com/strongloop/loopback#datasourcediscoverschemaowner-name-fn) + +### Asynchronous APIs for discovery + +* MySQL.prototype.discoverModelDefinitions = function (options, cb) + - options: + - all: {Boolean} To include tables/views from all schemas/owners + - owner/schema: {String} The schema/owner name + - views: {Boolean} To include views + - cb: + - Get a list of table/view names, for example: + + {type: 'table', name: 'INVENTORY', owner: 'STRONGLOOP' } + {type: 'table', name: 'LOCATION', owner: 'STRONGLOOP' } + {type: 'view', name: 'INVENTORY_VIEW', owner: 'STRONGLOOP' } + + +* MySQL.prototype.discoverModelProperties = function (table, options, cb) + - table: {String} The name of a table or view + - options: + - owner/schema: {String} The schema/owner name + - cb: + - Get a list of model property definitions, for example: + + { owner: 'STRONGLOOP', + tableName: 'PRODUCT', + columnName: 'ID', + dataType: 'VARCHAR2', + dataLength: 20, + nullable: 'N', + type: 'String' } + { owner: 'STRONGLOOP', + tableName: 'PRODUCT', + columnName: 'NAME', + dataType: 'VARCHAR2', + dataLength: 64, + nullable: 'Y', + type: 'String' } + + +* MySQL.prototype.discoverPrimaryKeys= function(table, options, cb) + - table: {String} The name of a table or view + - options: + - owner/schema: {String} The schema/owner name + - cb: + - Get a list of primary key definitions, for example: + + { owner: 'STRONGLOOP', + tableName: 'INVENTORY', + columnName: 'PRODUCT_ID', + keySeq: 1, + pkName: 'ID_PK' } + { owner: 'STRONGLOOP', + tableName: 'INVENTORY', + columnName: 'LOCATION_ID', + keySeq: 2, + pkName: 'ID_PK' } + +* MySQL.prototype.discoverForeignKeys= function(table, options, cb) + - table: {String} The name of a table or view + - options: + - owner/schema: {String} The schema/owner name + - cb: + - Get a list of foreign key definitions, for example: + + { fkOwner: 'STRONGLOOP', + fkName: 'PRODUCT_FK', + fkTableName: 'INVENTORY', + fkColumnName: 'PRODUCT_ID', + keySeq: 1, + pkOwner: 'STRONGLOOP', + pkName: 'PRODUCT_PK', + pkTableName: 'PRODUCT', + pkColumnName: 'ID' } + + +* MySQL.prototype.discoverExportedForeignKeys= function(table, options, cb) + + - table: {String} The name of a table or view + - options: + - owner/schema: {String} The schema/owner name + - cb: + - Get a list of foreign key definitions that reference the primary key of the given table, for example: + + { fkName: 'PRODUCT_FK', + fkOwner: 'STRONGLOOP', + fkTableName: 'INVENTORY', + fkColumnName: 'PRODUCT_ID', + keySeq: 1, + pkName: 'PRODUCT_PK', + pkOwner: 'STRONGLOOP', + pkTableName: 'PRODUCT', + pkColumnName: 'ID' } + + +### Synchronous APIs for discovery + +* MySQL.prototype.discoverModelDefinitionsSync = function (options) +* MySQL.prototype.discoverModelPropertiesSync = function (table, options) +* MySQL.prototype.discoverPrimaryKeysSync= function(table, options) +* MySQL.prototype.discoverForeignKeysSync= function(table, options) +* MySQL.prototype.discoverExportedForeignKeysSync= function(table, options) + +### Discover/build/try the models + +The following example uses `discoverAndBuildModels` to discover, build and try the models: + + dataSource.discoverAndBuildModels('INVENTORY', { owner: 'STRONGLOOP', visited: {}, associations: true}, + function (err, models) { + // Show records from the models + for(var m in models) { + models[m].all(show); + }; + + // Find one record for inventory + models.Inventory.findOne({}, function(err, inv) { + console.log("\nInventory: ", inv); + // Follow the foreign key to navigate to the product + inv.product(function(err, prod) { + console.log("\nProduct: ", prod); + console.log("\n ------------- "); + }); + }); + } + ## License -Please see LICENSE. +[The MIT license](LICENSE). The project was initially forked from [mysql-adapter](https://github.com/jugglingdb/mysql-adapter) which carries the following copyright and permission notices: -```text -Copyright (C) 2012 by Anatoliy Chakkaev -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -``` + Copyright (C) 2012 by Anatoliy Chakkaev -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. -```text -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -``` diff --git a/docs.json b/docs.json new file mode 100644 index 0000000..2ada8c2 --- /dev/null +++ b/docs.json @@ -0,0 +1,10 @@ +{ +"content": [ + "README.md", + {"title": "LoopBack MySQL Connector API", "depth": 2}, + "lib/mysql.js", + "lib/discovery.js" + ], + "codeSectionDepth": 3 +} + diff --git a/lib/discovery.js b/lib/discovery.js index a42542a..56d8591 100644 --- a/lib/discovery.js +++ b/lib/discovery.js @@ -18,7 +18,7 @@ function mixinDiscovery(MySQL) { return sql + limit; } - /** + /*! * Build sql for listing tables * @param options {all: for all owners, owner: for a given owner} * @returns {string} The sql statement @@ -41,7 +41,7 @@ function mixinDiscovery(MySQL) { return sqlTables; } - /** + /*! * Build sql for listing views * @param options {all: for all owners, owner: for a given owner} * @returns {string} The sql statement @@ -70,7 +70,10 @@ function mixinDiscovery(MySQL) { } /** - * Discover the models + * 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') { @@ -101,10 +104,11 @@ function mixinDiscovery(MySQL) { cb(err, merged); } }); - } + }; /** * Discover the tables/views synchronously + * @param {Object} options The options for discovery */ MySQL.prototype.discoverModelDefinitionsSync = function (options) { options = options || {}; @@ -116,9 +120,9 @@ function mixinDiscovery(MySQL) { tables = tables.concat(views); } return tables; - } + }; - /** + /*! * Normalize the arguments * @param table string, required * @param options object, optional @@ -144,7 +148,7 @@ function mixinDiscovery(MySQL) { }; } - /** + /*! * Build the sql statement to query columns for a given table * @param owner * @param table @@ -169,6 +173,13 @@ function mixinDiscovery(MySQL) { 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; @@ -188,8 +199,15 @@ function mixinDiscovery(MySQL) { } }; this.query(sql, callback); - } + }; + /** + * Discover model properties from a table synchronously + * @param {String} table The table name + * @param {Object} options The options for discovery + * @return {Object[]} The results + * + */ MySQL.prototype.discoverModelPropertiesSync = function (table, options) { var args = getArgs(table, options); var owner = args.owner; @@ -203,9 +221,9 @@ function mixinDiscovery(MySQL) { r.type = mysqlDataTypeToJSONType(r.dataType, r.dataLength); }); return results; - } + }; - /** + /*! * Build the sql statement for querying primary keys of a given table * @param owner * @param table @@ -230,9 +248,9 @@ function mixinDiscovery(MySQL) { /** * Discover primary keys for a given table - * @param table - * @param options - * @param cb + * @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); @@ -243,13 +261,13 @@ function mixinDiscovery(MySQL) { var sql = queryForPrimaryKeys(owner, table); this.query(sql, cb); - } + }; /** * Discover primary keys synchronously for a given table - * @param table - * @param options - * @returns {*} + * @param {String} table + * @param {Object} options + * @returns {*} The list of primary key descriptions */ MySQL.prototype.discoverPrimaryKeysSync = function (table, options) { var args = getArgs(table, options); @@ -259,9 +277,9 @@ function mixinDiscovery(MySQL) { var sql = queryForPrimaryKeys(owner, table); return this.querySync(sql); - } + }; - /** + /*! * Build the sql statement for querying foreign keys of a given table * @param owner * @param table @@ -286,10 +304,10 @@ function mixinDiscovery(MySQL) { } /** - * Discover foreign kets for a given table - * @param table - * @param options - * @param cb + * 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); @@ -300,13 +318,13 @@ function mixinDiscovery(MySQL) { var sql = queryForeignKeys(owner, table); this.query(sql, cb); - } + }; /** * Discover foreign keys synchronously for a given table - * @param owner - * @param options - * @returns {*} + * @param {String} table The table name + * @param {Object} options The options for discovery + * @return {Object[]} The results */ MySQL.prototype.discoverForeignKeysSync = function (table, options) { var args = getArgs(table, options); @@ -316,9 +334,9 @@ function mixinDiscovery(MySQL) { var sql = queryForeignKeys(owner, table); return this.querySync(sql); - } + }; - /** + /*! * 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 @@ -354,6 +372,12 @@ function mixinDiscovery(MySQL) { 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; @@ -363,12 +387,12 @@ function mixinDiscovery(MySQL) { var sql = queryExportedForeignKeys(owner, table); this.query(sql, cb); - } + }; /** * Discover foreign keys synchronously for a given table - * @param owner - * @param table + * @param {String} owner The DB owner/schema name + * @param {Object} options The options for discovery * @returns {*} */ MySQL.prototype.discoverExportedForeignKeysSync = function (table, options) { @@ -379,7 +403,7 @@ function mixinDiscovery(MySQL) { var sql = queryExportedForeignKeys(owner, table); return this.querySync(sql); - } + }; function mysqlDataTypeToJSONType(mysqlType, dataLength) { var type = mysqlType.toUpperCase(); diff --git a/lib/mysql.js b/lib/mysql.js index a57f6bb..a90b983 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -1,4 +1,4 @@ -/** +/*! * Module dependencies */ var mysql = require('mysql'); @@ -6,8 +6,18 @@ var mysql = require('mysql'); var jdb = require('loopback-datasource-juggler'); var EnumFactory = require('./enumFactory').EnumFactory; +/** + * @module loopback-connector-mysql + * + * Initialize the MySQL connector against the given data source + * + * @param {DataSource} dataSource The loopback-datasource-juggler dataSource + * @param {Function} [callback] The callback function + */ exports.initialize = function initializeDataSource(dataSource, callback) { - if (!mysql) return; + if (!mysql) { + return; + } var s = dataSource.settings; @@ -69,7 +79,9 @@ exports.initialize = function initializeDataSource(dataSource, callback) { exports.MySQL = MySQL; /** - * MySQL connector + * @constructor + * Constructor for MySQL connector + * @param {Object} client The node-mysql client object */ function MySQL(client) { this.name = 'mysql'; @@ -79,6 +91,12 @@ function MySQL(client) { require('util').inherits(MySQL, jdb.BaseSQL); +/** + * Execute the sql statement + * + * @param {String} sql The SQL statement + * @param {Function} [callback] The callback after the SQL statement is executed + */ MySQL.prototype.query = function (sql, callback) { if (!this.dataSource.connected) { return this.dataSource.on('connected', function () { @@ -107,7 +125,11 @@ MySQL.prototype.query = function (sql, callback) { }; /** - * Must invoke callback(err, id) + * Create the data model in MySQL + * + * @param {String} model The model name + * @param {Object} data The model instance data + * @param {Function} [callback] The callback function */ MySQL.prototype.create = function (model, data, callback) { var fields = this.toFields(model, data); @@ -122,6 +144,13 @@ MySQL.prototype.create = function (model, data, callback) { }); }; +/** + * Update if the model instance exists with the same id or create a new instance + * + * @param {String} model The model name + * @param {Object} data The model instance data + * @param {Function} [callback] The callback function + */ MySQL.prototype.updateOrCreate = function (model, data, callback) { var mysql = this; var fieldsNames = []; @@ -182,6 +211,12 @@ function dateToMysql(val) { } } +/*! + * Convert property name/value to a DB column + * @param prop + * @param val + * @returns {*} + */ MySQL.prototype.toDatabase = function (prop, val) { if (val === null) return 'NULL'; if (val === undefined) return; @@ -217,6 +252,12 @@ MySQL.prototype.toDatabase = function (prop, val) { return this.client.escape(val.toString()); }; +/*! + * Convert the data from database + * @param model + * @param data + * @returns {*} + */ MySQL.prototype.fromDatabase = function (model, data) { if (!data) return null; var props = this._models[model].properties; @@ -349,6 +390,13 @@ function buildLimit(limit, offset) { return 'LIMIT ' + (offset ? (offset + ', ' + limit) : limit); } +/** + * Find matching model instances by the filter + * + * @param {String} model The model name + * @param {Object} filter The filter + * @param {Function} [callback] The callback function + */ MySQL.prototype.all = function all(model, filter, callback) { // Order by id if no order is specified filter = filter || {}; @@ -416,6 +464,11 @@ MySQL.prototype.destroyAll = function destroyAll(model, where, callback) { }.bind(this)); }; +/** + * Perform autoupdate for the given models + * @param {String[]} [models] A model name or an array of model names. If not present, apply to all models + * @param {Function} [cb] The callback function + */ MySQL.prototype.autoupdate = function (models, cb) { var self = this; var wait = 0; @@ -455,6 +508,11 @@ MySQL.prototype.autoupdate = function (models, cb) { } }; +/** + * Check if the models exist + * @param {String[]} [models] A model name or an array of model names. If not present, apply to all models + * @param {Function} [cb] The callback function + */ MySQL.prototype.isActual = function (cb) { var ok = false; var self = this;