Merge pull request #6 from strongloop/mysql-1.0.0
Merge mysql-1.0.0 into master
This commit is contained in:
commit
65d81f051f
141
README.md
141
README.md
|
@ -1,6 +1,6 @@
|
|||
## loopback-connector-mysql
|
||||
|
||||
MySQL connector for [LoopBack Data Source Juggler](http://docs.strongloop.com/loopback-datasource-juggler/).
|
||||
`loopback-connector-mysql` is the MySQL connector module for [loopback-datasource-juggler](http://docs.strongloop.com/loopback-datasource-juggler/).
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -24,6 +24,8 @@ To use it you need `loopback-datasource-juggler`.
|
|||
```javascript
|
||||
var DataSource = require('loopback-datasource-juggler').DataSource;
|
||||
var dataSource = new DataSource('mysql', {
|
||||
host: 'localhost',
|
||||
port: 3306,
|
||||
database: 'mydb',
|
||||
username: 'myuser',
|
||||
password: 'mypass'
|
||||
|
@ -34,11 +36,50 @@ To use it you need `loopback-datasource-juggler`.
|
|||
to `utf8_general_ci`. The `collation` value will also be used to derive the
|
||||
connection charset.
|
||||
|
||||
|
||||
|
||||
## Data type mappings
|
||||
|
||||
`loopback-connector-mysql` uses the following rules to map between JSON types and MySQL data types.
|
||||
|
||||
### JSON to MySQL types
|
||||
|
||||
- String/JSON: VARCHAR
|
||||
- Text: TEXT
|
||||
- Number: INT
|
||||
- Date: DATETIME
|
||||
- BOOLEAN: TINYINT(1)
|
||||
- Point/GeoPoint: POINT
|
||||
- Enum: ENUM
|
||||
|
||||
### MySQL to JSON types
|
||||
|
||||
- CHAR: String
|
||||
- CHAR(1): Boolean
|
||||
- VARCHAR/TINYTEXT/MEDIUMTEXT/LONGTEXT/TEXT/ENUM/SET: String
|
||||
- TINYBLOB/MEDIUMBLOB/LONGBLOB/BLOB/BINARY/VARBINARY/BIT: Binary
|
||||
- TINYINT/SMALLINT/INT/MEDIUMINT/YEAR/FLOAT/DOUBLE/NUMERIC/DECIMAL: Number
|
||||
- DATE/TIMESTAMP/DATETIME: Date
|
||||
|
||||
## Using the `dataType` field/column option with MySQL
|
||||
|
||||
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.
|
||||
`loopback-connector-mysql` allows mapping of LoopBack model properties to MYSQL columns using the 'mysql' property of the
|
||||
property definition. For example,
|
||||
|
||||
"locationId":{
|
||||
"type":"String",
|
||||
"required":true,
|
||||
"length":20,
|
||||
"mysql":
|
||||
{
|
||||
"columnName":"LOCATION_ID",
|
||||
"dataType":"VARCHAR2",
|
||||
"dataLength":20,
|
||||
"nullable":"N"
|
||||
}
|
||||
}
|
||||
|
||||
`loopback-connector-mysql` also 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
|
||||
|
@ -115,7 +156,7 @@ The following type-dataType combinations are supported:
|
|||
|
||||
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.discoverModelDefinitions([owner], 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
|
||||
|
@ -221,7 +262,95 @@ MySQL data sources allow you to discover model definition information from exist
|
|||
|
||||
### Discover/build/try the models
|
||||
|
||||
The following example uses `discoverAndBuildModels` to discover, build and try the models:
|
||||
#### Build a LDL schema by discovery
|
||||
|
||||
Data sources backed by the MySQL connector can discover LDL models from the database using the `discoverSchema` API. For
|
||||
example,
|
||||
|
||||
dataSource.discoverSchema('INVENTORY', {owner: 'STRONGLOOP'}, function (err, schema) {
|
||||
...
|
||||
}
|
||||
|
||||
Here is the sample result. Please note there are 'mysql' properties in addition to the regular LDL model options and
|
||||
properties. The 'mysql' objects contain the MySQL specific mappings.
|
||||
|
||||
{
|
||||
"name":"Inventory",
|
||||
"options":{
|
||||
"idInjection":false,
|
||||
"mysql":{
|
||||
"schema":"STRONGLOOP",
|
||||
"table":"INVENTORY"
|
||||
}
|
||||
},
|
||||
"properties":{
|
||||
"productId":{
|
||||
"type":"String",
|
||||
"required":false,
|
||||
"length":60,
|
||||
"precision":null,
|
||||
"scale":null,
|
||||
"id":1,
|
||||
"mysql":{
|
||||
"columnName":"PRODUCT_ID",
|
||||
"dataType":"varchar",
|
||||
"dataLength":60,
|
||||
"dataPrecision":null,
|
||||
"dataScale":null,
|
||||
"nullable":"NO"
|
||||
}
|
||||
},
|
||||
"locationId":{
|
||||
"type":"String",
|
||||
"required":false,
|
||||
"length":60,
|
||||
"precision":null,
|
||||
"scale":null,
|
||||
"id":2,
|
||||
"mysql":{
|
||||
"columnName":"LOCATION_ID",
|
||||
"dataType":"varchar",
|
||||
"dataLength":60,
|
||||
"dataPrecision":null,
|
||||
"dataScale":null,
|
||||
"nullable":"NO"
|
||||
}
|
||||
},
|
||||
"available":{
|
||||
"type":"Number",
|
||||
"required":false,
|
||||
"length":null,
|
||||
"precision":10,
|
||||
"scale":0,
|
||||
"mysql":{
|
||||
"columnName":"AVAILABLE",
|
||||
"dataType":"int",
|
||||
"dataLength":null,
|
||||
"dataPrecision":10,
|
||||
"dataScale":0,
|
||||
"nullable":"YES"
|
||||
}
|
||||
},
|
||||
"total":{
|
||||
"type":"Number",
|
||||
"required":false,
|
||||
"length":null,
|
||||
"precision":10,
|
||||
"scale":0,
|
||||
"mysql":{
|
||||
"columnName":"TOTAL",
|
||||
"dataType":"int",
|
||||
"dataLength":null,
|
||||
"dataPrecision":10,
|
||||
"dataScale":0,
|
||||
"nullable":"YES"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
We can also discover and build model classes in one shot. The following example uses `discoverAndBuildModels` to discover,
|
||||
build and try the models:
|
||||
|
||||
dataSource.discoverAndBuildModels('INVENTORY', { owner: 'STRONGLOOP', visited: {}, associations: true},
|
||||
function (err, models) {
|
||||
|
|
|
@ -35,7 +35,7 @@ function mixinDiscovery(MySQL) {
|
|||
+ ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'', 'table_schema, table_name', options);
|
||||
} else {
|
||||
sqlTables = paginateSQL('SELECT \'table\' AS "type", table_name AS "name",'
|
||||
+ ' SUBSTRING_INDEX(USER(), \'@\', 1) AS "owner" FROM information_schema.tables',
|
||||
+ ' table_schema AS "owner" FROM information_schema.tables',
|
||||
'table_name', options);
|
||||
}
|
||||
return sqlTables;
|
||||
|
@ -62,7 +62,7 @@ function mixinDiscovery(MySQL) {
|
|||
'table_schema, table_name', options);
|
||||
} else {
|
||||
sqlViews = paginateSQL('SELECT \'view\' AS "type", table_name AS "name",'
|
||||
+ ' SUBSTRING_INDEX(USER(), \'@\', 1) AS "owner" FROM information_schema.views',
|
||||
+ ' table_schema AS "owner" FROM information_schema.views',
|
||||
'table_name', options);
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ function mixinDiscovery(MySQL) {
|
|||
+ (table ? ' AND table_name=\'' + table + '\'' : ''),
|
||||
'table_name, ordinal_position', {});
|
||||
} else {
|
||||
sql = paginateSQL('SELECT SUBSTRING_INDEX(USER(), \'@\', 1) AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",'
|
||||
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 + '\'' : ''),
|
||||
|
|
73
lib/mysql.js
73
lib/mysql.js
|
@ -176,7 +176,7 @@ MySQL.prototype.updateOrCreate = function (model, data, callback) {
|
|||
var props = this._models[model].properties;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (props[key] || mysql.id(model, key)) {
|
||||
var k = '`' + key + '`';
|
||||
var k = mysql.columnEscaped(model, key);
|
||||
var v;
|
||||
if (!mysql.id(model, key)) {
|
||||
v = mysql.toDatabase(props[key], data[key]);
|
||||
|
@ -203,13 +203,14 @@ MySQL.prototype.updateOrCreate = function (model, data, callback) {
|
|||
};
|
||||
|
||||
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('`' + key.replace(/\./g, '`.`') + '` = ' + value);
|
||||
fields.push(self.columnEscaped(model, key) + ' = ' + value);
|
||||
}
|
||||
}.bind(this));
|
||||
return fields.join(',');
|
||||
|
@ -279,15 +280,19 @@ 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;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
var json = {};
|
||||
for(var p in props) {
|
||||
var key = this.column(model, p);
|
||||
var val = data[key];
|
||||
if (typeof val === 'undefined' || val === null) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if (props[key]) {
|
||||
switch(props[key].type.name) {
|
||||
if (props[p]) {
|
||||
switch(props[p].type.name) {
|
||||
case 'Date':
|
||||
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
|
||||
break;
|
||||
|
@ -303,9 +308,9 @@ MySQL.prototype.fromDatabase = function (model, data) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
data[key] = val;
|
||||
});
|
||||
return data;
|
||||
json[p] = val;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
MySQL.prototype.escapeName = function (name) {
|
||||
|
@ -353,7 +358,7 @@ function buildWhere(self, model, conds) {
|
|||
|
||||
var cs = [];
|
||||
Object.keys(conds).forEach(function (key) {
|
||||
var keyEscaped = '`' + key.replace(/\./g, '`.`') + '`';
|
||||
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');
|
||||
|
@ -402,14 +407,16 @@ function buildWhere(self, model, conds) {
|
|||
return 'WHERE ' + cs.join(' AND ');
|
||||
}
|
||||
|
||||
function buildOrderBy(order) {
|
||||
function buildOrderBy(self, model, order) {
|
||||
if (typeof order === 'string') {
|
||||
order = [order];
|
||||
}
|
||||
return 'ORDER BY ' + order.map(function(o) {
|
||||
var t = o.split(/[\s,]+/);
|
||||
if (t.length === 1) return '`' + o + '`';
|
||||
return '`' + t[0] + '` ' + t[1];
|
||||
if (t.length === 1) {
|
||||
return self.columnEscaped(model, o);
|
||||
}
|
||||
return self.columnEscaped(model, t[0]) + ' ' + t[1];
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
|
@ -425,6 +432,7 @@ 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) {
|
||||
|
@ -435,7 +443,6 @@ MySQL.prototype.all = function all(model, filter, callback) {
|
|||
}
|
||||
|
||||
var sql = 'SELECT '+ this.getColumns(model, filter.fields) + ' FROM ' + this.tableEscaped(model);
|
||||
var self = this;
|
||||
|
||||
if (filter) {
|
||||
|
||||
|
@ -444,7 +451,7 @@ MySQL.prototype.all = function all(model, filter, callback) {
|
|||
}
|
||||
|
||||
if (filter.order) {
|
||||
sql += ' ' + buildOrderBy(filter.order);
|
||||
sql += ' ' + buildOrderBy(self, model, filter.order);
|
||||
}
|
||||
|
||||
if (filter.limit) {
|
||||
|
@ -742,11 +749,35 @@ MySQL.prototype.alterTable = function (model, actualFields, actualIndexes, done,
|
|||
|
||||
MySQL.prototype.propertiesSQL = function (model) {
|
||||
var self = this;
|
||||
|
||||
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 = ['`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;
|
||||
|
@ -775,10 +806,11 @@ MySQL.prototype.singleIndexSettingsSQL = function (model, prop) {
|
|||
if (i.kind) {
|
||||
kind = i.kind;
|
||||
}
|
||||
var columnName = this.columnEscaped(model, prop);
|
||||
if (kind && type) {
|
||||
return (kind + ' INDEX `' + prop + '` (`' + prop + '`) ' + type);
|
||||
return (kind + ' INDEX ' + columnName + ' (' + columnName + ') ' + type);
|
||||
} else {
|
||||
return (kind + ' INDEX `' + prop + '` ' + type + ' (`' + prop + '`) ');
|
||||
return (kind + ' INDEX ' + columnName + ' ' + type + ' (' + columnName + ') ');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -793,10 +825,11 @@ MySQL.prototype.indexSettingsSQL = function (model, prop) {
|
|||
if (i.kind) {
|
||||
kind = i.kind;
|
||||
}
|
||||
var columnName = this.columnEscaped(model, prop);
|
||||
if (kind && type) {
|
||||
return (kind + ' INDEX `' + prop + '` (' + i.columns + ') ' + type);
|
||||
return (kind + ' INDEX ' + columnName + ' (' + i.columns + ') ' + type);
|
||||
} else {
|
||||
return (kind + ' INDEX ' + type + ' `' + prop + '` (' + i.columns + ')');
|
||||
return (kind + ' INDEX ' + type + ' ' + columnName + ' (' + i.columns + ')');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -11,12 +11,11 @@
|
|||
"coverage": "mocha -r blanket -R html-cov > coverage_loopback-connector-mysql.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"loopback-datasource-juggler": "git+ssh://git@github.com:strongloop/loopback-datasource-juggler.git",
|
||||
"mysql": ">=2.0.0-alpha9",
|
||||
"loopback-datasource-juggler": "~1.0.0",
|
||||
"mysql": "~2.0.0-alpha9",
|
||||
"async": "~0.2.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coffee-script": "~1.6.3",
|
||||
"should": "~1.3.0",
|
||||
"mocha": "~1.13.0",
|
||||
"blanket": "~1.1.5",
|
||||
|
@ -27,4 +26,4 @@
|
|||
"url": "https://github.com/strongloop/loopback-connector-mysql.git"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,8 +171,8 @@ describe('Discover model foreign keys', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Discover ADL schema from a table', function () {
|
||||
it('should return an ADL schema for INVENTORY', function (done) {
|
||||
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');
|
||||
|
|
Loading…
Reference in New Issue