diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..e702b2b9
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,13 @@
+{
+ "extends": "loopback",
+ "rules": {
+ "max-len": ["error", 120, 4, {
+ "ignoreComments": true,
+ "ignoreUrls": true,
+ "ignorePattern": "^\\s*var\\s.=\\s*(require\\s*\\()|(/)"
+ }],
+ "camelcase": 0,
+ "one-var": "off",
+ "no-unused-expressions": "off"
+ }
+ }
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..ccc915a7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,36 @@
+
+
+### Bug or feature request
+
+
+
+- [ ] Bug
+- [ ] Feature request
+
+### Description of feature (or steps to reproduce if bug)
+
+
+
+### Link to sample repo to reproduce issue (if bug)
+
+
+
+### Expected result
+
+
+
+### Actual result (if bug)
+
+
+
+### Additional information (Node.js version, LoopBack version, etc)
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..d2b240f5
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,24 @@
+### Description
+
+
+#### Related issues
+
+
+
+- None
+
+### Checklist
+
+
+
+- [ ] New tests added or existing tests modified to cover all changes
+- [ ] Code conforms with the [style
+ guide](http://loopback.io/doc/en/contrib/style-guide.html)
diff --git a/.gitignore b/.gitignore
index 5b2da1dc..8011db9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ coverage
*.xml
.loopbackrc
.idea
+
diff --git a/CHANGES.md b/CHANGES.md
index 137976d0..e7e35dcc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,125 @@
+2017-01-13, Version 3.0.0
+=========================
+
+ * Follow mysql recommendations for handling booleans (Carl Fürstenberg)
+
+ * Fix readme glitch (#231) (Rand McKinney)
+
+ * Update readme w info from docs (#229) (Rand McKinney)
+
+ * Fix expected column name when autoupdate (muhammad hasan)
+
+ * Update paid support URL (Siddhi Pai)
+
+ * Fix CI Failures (Loay Gewily)
+
+ * Drop support for Node v0.10 and v0.12 (Siddhi Pai)
+
+ * Start the development of the next major version (Siddhi Pai)
+
+ * Update README with correct doc links, etc (Amir Jafarian)
+
+
+2016-10-17, Version 2.4.0
+=========================
+
+ * Add connectorCapabilities global object (#201) (Nicholas Duffy)
+
+ * Remove unused prefix for test env vars (#203) (Simon Ho)
+
+ * Update translation files - round#2 (#199) (Candy)
+
+ * Add CI fixes (#197) (Loay)
+
+ * Add translated files (gunjpan)
+
+ * Update deps to loopback 3.0.0 RC (Miroslav Bajtoš)
+
+ * Remove Makefile in favour of NPM test scripts (Simon Ho)
+
+ * Fixing lint errors (Ron Lloyd)
+
+ * Autoupdate mysql.columnName bug fix (Ron Lloyd)
+
+ * Tests for autoupdate mysql.columnName bug fix (Ron Lloyd)
+
+ * Use juggler@3 for running the tests (Miroslav Bajtoš)
+
+ * Explictly set forceId:false in test model (Miroslav Bajtoš)
+
+ * Fix pretest and init test configs (Simon Ho)
+
+ * Fix to configure model index in keys field (deepakrkris)
+
+ * Update eslint infrastructure (Loay)
+
+ * test: use dump of original test DB as seed (Ryan Graham)
+
+ * test: skip cardinality, update sub_part (Ryan Graham)
+
+ * test: accept alternate test db credentials (Ryan Graham)
+
+ * test: use should for easier debugging (Ryan Graham)
+
+ * test: account for mysql version differences (Ryan Graham)
+
+ * test: match case with example/table.sql (Ryan Graham)
+
+ * test: separate assertions from test flow control (Ryan Graham)
+
+ * test: update tests to use example DB (Ryan Graham)
+
+ * test: seed test DB with example (Ryan Graham)
+
+ * test: fix undefined password (Ryan Graham)
+
+ * Add special handling of zero date/time entries (Carl Fürstenberg)
+
+ * Add globalization (Candy)
+
+ * Update URLs in CONTRIBUTING.md (#176) (Ryan Graham)
+
+
+2016-06-21, Version 2.3.0
+=========================
+
+ * Add function connect (juehou)
+
+ * insert/update copyright notices (Ryan Graham)
+
+ * relicense as MIT only (Ryan Graham)
+
+ * Override other settings if url provided (juehou)
+
+ * Add `connectorCapabilities ` (Amir Jafarian)
+
+ * Implement ReplaceOrCreate (Amir Jafarian)
+
+
+2016-02-19, Version 2.2.1
+=========================
+
+ * Remove sl-blip from dependencies (Miroslav Bajtoš)
+
+ * Upgrade `should` module (Amir Jafarian)
+
+ * removed console.log (cgole)
+
+ * seperate env variable for test db (cgole)
+
+ * Changed username to user (cgole)
+
+ * Added db username password (cgole)
+
+ * Add mysql CI host (cgole)
+
+ * Refer to licenses with a link (Sam Roberts)
+
+ * Pass options to the execute command. (Diogo Correia)
+
+ * Use strongloop conventions for licensing (Sam Roberts)
+
+
2015-07-30, Version 2.2.0
=========================
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 01dbb547..5687a637 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -147,5 +147,5 @@ Contributing to `loopback-connector-mysql` is easy. In a few simple steps:
inaccurate in any respect. Email us at callback@strongloop.com.
```
-[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
-[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
+[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
+[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..4be35208
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) IBM Corp. 2012,2016. All Rights Reserved.
+Node module: loopback-connector-mysql
+This project is licensed under the MIT License, full text below.
+
+--------
+
+MIT license
+
+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.
diff --git a/LICENSE.md b/LICENSE.md
deleted file mode 100644
index 4de8f1e2..00000000
--- a/LICENSE.md
+++ /dev/null
@@ -1,9 +0,0 @@
-Copyright (c) 2013-2015 StrongLoop, Inc.
-
-loopback-connector-mysql uses a dual license model.
-
-You may use this library under the terms of the [MIT License][],
-or under the terms of the [StrongLoop Subscription Agreement][].
-
-[MIT License]: http://opensource.org/licenses/MIT
-[StrongLoop Subscription Agreement]: http://strongloop.com/license
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 3d1e31a3..00000000
--- a/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-## TESTS
-
-TESTER = ./node_modules/.bin/mocha
-OPTS = --growl --globals getSchema --timeout 15000
-TESTS = test/*.test.js
-
-test:
- $(TESTER) $(OPTS) $(TESTS)
-test-verbose:
- $(TESTER) $(OPTS) --reporter spec $(TESTS)
-testing:
- $(TESTER) $(OPTS) --watch $(TESTS)
-.PHONY: test docs coverage
diff --git a/README.md b/README.md
index 8c0c6cde..74c87f03 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,361 @@
-## loopback-connector-mysql
+# loopback-connector-mysql
-`loopback-connector-mysql` is the MySQL connector module for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler/).
+[MySQL](https://www.mysql.com/) is a popular open-source relational database management system (RDBMS). The `loopback-connector-mysql` module provides the MySQL connector module for the LoopBack framework.
-For complete documentation, see [StrongLoop Documentation | MySQL Connector](http://docs.strongloop.com/display/LB/MySQL+connector).
+
## Installation
-````sh
+In your application root directory, enter this command to install the connector:
+
+```sh
npm install loopback-connector-mysql --save
-````
+```
+
+This installs the module from npm and adds it as a dependency to the application's `package.json` file.
+
+If you create a MySQL data source using the data source generator as described below, you don't have to do this, since the generator will run `npm install` for you.
+
+## Creating a MySQL data source
+
+Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a MySQL data source to your application.
+The generator will prompt for the database server hostname, port, and other settings
+required to connect to a MySQL database. It will also run the `npm install` command above for you.
+
+The entry in the application's `/server/datasources.json` will look like this:
+
+{% include code-caption.html content="/server/datasources.json" %}
+```javascript
+"mydb": {
+ "name": "mydb",
+ "connector": "mysql",
+ "host": "myserver",
+ "port": 3306,
+ "database": "mydb",
+ "password": "mypassword",
+ "user": "admin"
+ }
+```
+
+Edit `datasources.json` to add any other additional properties that you require.
+
+### Properties
+
+
+
+
+ | Property |
+ Type |
+ Description |
+
+
+
+
+ | collation |
+ String |
+ Determines the charset for the connection. Default is utf8_general_ci. |
+
+
+ | connector |
+ String |
+ Connector name, either “loopback-connector-mysql” or “mysql”. |
+
+
+ | connectionLimit |
+ Number |
+ The maximum number of connections to create at once. Default is 10. |
+
+
+ | database |
+ String |
+ Database name |
+
+
+ | debug |
+ Boolean |
+ If true, turn on verbose mode to debug database queries and lifecycle. |
+
+
+ | host |
+ String |
+ Database host name |
+
+
+ | password |
+ String |
+ Password to connect to database |
+
+
+ | port |
+ Number |
+ Database TCP port |
+
+
+ | socketPath |
+ String |
+ The path to a unix domain socket to connect to. When used host and port are ignored. |
+
+
+ | supportBigNumbers |
+ Boolean |
+ Enable this option to deal with big numbers (BIGINT and DECIMAL columns) in the database. Default is false. |
+
+
+ | timeZone |
+ String |
+ The timezone used to store local dates. Default is ‘local’. |
+
+
+ | url |
+ String |
+ Connection URL of form mysql://user:password@host/db. Overrides other connection settings. |
+
+
+ | username |
+ String |
+ Username to connect to database |
+
+
+
+
+**NOTE**: In addition to these properties, you can use additional parameters supported by [`node-mysql`](https://github.com/felixge/node-mysql).
+
+## Type mappings
+
+See [LoopBack types](http://loopback.io/doc/en/lb3/LoopBack-types.html) for details on LoopBack's data types.
-## Basic use
+### LoopBack to MySQL types
-To use it you need `loopback-datasource-juggler`.
+
+
+
+ | LoopBack Type |
+ MySQL Type |
+
+
+
+
+ | String/JSON |
+ VARCHAR |
+
+
+ | Text |
+ TEXT |
+
+
+ | Number |
+ INT |
+
+
+ | Date |
+ DATETIME |
+
+
+ | Boolean |
+ TINYINT(1) |
+
+
+ | GeoPoint object |
+ POINT |
+
+
+ Custom Enum type (See Enum below) |
+ ENUM |
+
+
+
-1. Setup dependencies in `package.json`:
+### MySQL to LoopBack types
- ```json
+
+
+
+ | MySQL Type |
+ LoopBack Type |
+
+
+ | CHAR |
+ String |
+
+
+ | CHAR(1) |
+ Boolean |
+
+
+ VARCHAR TINYTEXT MEDIUMTEXT LONGTEXT TEXT ENUM SET |
+ String |
+
+
+ TINYBLOB MEDIUMBLOB LONGBLOB BLOB BINARY VARBINARY BIT |
+ Node.js Buffer object |
+
+
+ TINYINT SMALLINT INT MEDIUMINT YEAR FLOAT DOUBLE NUMERIC DECIMAL |
+
+ Number For FLOAT and DOUBLE, see Floating-point types.
+ For NUMERIC and DECIMAL, see Fixed-point exact value types
+ |
+
+
+ DATE TIMESTAMP DATETIME |
+ Date |
+
+
+
+
+## Using the datatype field/column option with MySQL
+
+Use the `mysql` model property to specify additional MySQL-specific properties for a LoopBack model.
+
+For example:
+
+{% include code-caption.html content="/common/models/model.json" %}
+```javascript
+"locationId":{
+ "type":"String",
+ "required":true,
+ "length":20,
+ "mysql":
{
- ...
- "dependencies": {
- "loopback-datasource-juggler": "latest",
- "loopback-connector-mysql": "latest"
- },
- ...
+ "columnName":"LOCATION_ID",
+ "dataType":"VARCHAR2",
+ "dataLength":20,
+ "nullable":"N"
}
- ```
-
-2. Use:
-
- ```javascript
- var DataSource = require('loopback-datasource-juggler').DataSource;
- var dataSource = new DataSource('mysql', {
- host: 'localhost',
- port: 3306,
- database: 'mydb',
- username: 'myuser',
- password: 'mypass'
- });
- ```
- 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
+}
+```
+
+You can also use the dataType column/property attribute to specify what MySQL column type to use for many loopback-datasource-juggler types.
+The following type-dataType combinations are supported:
+
+* Number
+* integer
+* tinyint
+* smallint
+* mediumint
+* int
+* bigint
+
+Use the `limit` option to alter the display width. Example:
+
+```javascript
+{ userName : {
+ type: String,
+ dataType: 'char',
+ limit: 24
+ }
+}
+```
+
+### Floating-point types
+
+For Float and Double data types, use the `precision` and `scale` options to specify custom precision. Default is (16,8). For example:
+
+```javascript
+{ average :
+ { type: Number,
+ dataType: 'float',
+ precision: 20,
+ scale: 4
+ }
+}
+```
+
+### Fixed-point exact value types
+
+For Decimal and Numeric types, use the `precision` and `scale` options to specify custom precision. Default is (9,2).
+These aren't likely to function as true fixed-point.
+
+Example:
+
+```javascript
+{ stdDev :
+ { type: Number,
+ dataType: 'decimal',
+ precision: 12,
+ scale: 8
+ }
+}
+```
+
+### Other types
+
+Convert String / DataSource.Text / DataSource.JSON to the following MySQL types:
+
+* varchar
+* char
+* text
+* mediumtext
+* tinytext
+* longtext
+
+Example:
+
+```javascript
+{ userName :
+ { type: String,
+ dataType: 'char',
+ limit: 24
+ }
+}
+```
+
+Example:
+
+```javascript
+{ biography :
+ { type: String,
+ dataType: 'longtext'
+ }
+}
+```
+
+Convert JSON Date types to datetime or timestamp
+
+Example:
+
+```javascript
+{ startTime :
+ { type: Date,
+ dataType: 'timestamp'
+ }
+}
+```
+
+### Enum
+
+Enums are special. Create an Enum using Enum factory:
+
+```javascript
+var MOOD = dataSource.EnumFactory('glad', 'sad', 'mad');
+MOOD.SAD; // 'sad'
+MOOD(2); // 'sad'
+MOOD('SAD'); // 'sad'
+MOOD('sad'); // 'sad'
+{ mood: { type: MOOD }}
+{ choice: { type: dataSource.EnumFactory('yes', 'no', 'maybe'), null: false }}
+```
+
+## Discovery and auto-migration
+
+### Model discovery
+
+The MySQL connector supports _model discovery_ that enables you to create LoopBack models
+based on an existing database schema using the unified [database discovery API](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-discoverandbuildmodels). For more information on discovery, see [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html).
+
+### Auto-migratiion
+
+The MySQL connector also supports _auto-migration_ that enables you to create a database schema
+from LoopBack models using the [LoopBack automigrate method](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-automigrate).
+
+For more information on auto-migration, see [Creating a database schema from models](https://loopback.io/doc/en/lb3/Creating-a-database-schema-from-models.html) for more information.
+
+Destroying models may result in errors due to foreign key integrity. First delete any related models first calling delete on models with relationships.
+
+## Running tests
The tests in this repository are mainly integration tests, meaning you will need to run them using our preconfigured test server.
diff --git a/example/app.js b/example/app.js
index cf633f69..f4e68b27 100644
--- a/example/app.js
+++ b/example/app.js
@@ -1,3 +1,9 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var DataSource = require('loopback-datasource-juggler').DataSource;
var config = require('rc')('loopback', {dev: {mysql: {}}}).dev.mysql;
@@ -10,7 +16,7 @@ function show(err, models) {
} else {
console.log(models);
if (models) {
- models.forEach(function (m) {
+ models.forEach(function(m) {
console.dir(m);
});
}
@@ -28,13 +34,8 @@ ds.discoverForeignKeys('inventory', show);
ds.discoverExportedForeignKeys('location', show);
-ds.discoverAndBuildModels('weapon', {owner: 'strongloop', visited: {}, associations: true}, function (err, models) {
-
+ds.discoverAndBuildModels('weapon', {owner: 'strongloop', visited: {}, associations: true}, function(err, models) {
for (var m in models) {
models[m].all(show);
}
-
});
-
-
-
diff --git a/index.js b/index.js
index 5983399f..03f1307c 100644
--- a/index.js
+++ b/index.js
@@ -1 +1,10 @@
+// Copyright IBM Corp. 2012. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+var SG = require('strong-globalize');
+SG.SetRootDir(__dirname);
+
module.exports = require('./lib/mysql.js');
diff --git a/intl/de/messages.json b/intl/de/messages.json
new file mode 100644
index 00000000..c6614df0
--- /dev/null
+++ b/intl/de/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} muss ein {{object}} sein: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} ist ein erforderliches Zeichenfolgeargument: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "Keine Argumente - {{Enum}} konnte nicht erstellt werden.",
+ "80a32e80cbed65eba2103201a7c94710": "Modell nicht gefunden: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}}-Syntax berücksichtigt nicht das {{`g`}}-Flag",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}}-Syntax berücksichtigt nicht das {{`i`}}-Flag",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}}-Syntax berücksichtigt nicht das {{`m`}}-Flag",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} sollte eine Funktion sein"
+}
+
diff --git a/intl/en/messages.json b/intl/en/messages.json
new file mode 100644
index 00000000..7e4acdfb
--- /dev/null
+++ b/intl/en/messages.json
@@ -0,0 +1,10 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} must be an {{object}}: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} is a required string argument: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "No arguments - could not create {{Enum}}.",
+ "80a32e80cbed65eba2103201a7c94710": "Model not found: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}} syntax does not respect the {{`g`}} flag",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}} syntax does not respect the {{`i`}} flag",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}} syntax does not respect the {{`m`}} flag",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} should be a function"
+}
diff --git a/intl/es/messages.json b/intl/es/messages.json
new file mode 100644
index 00000000..fbadd85b
--- /dev/null
+++ b/intl/es/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} debe ser un {{object}}: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} es un argumento de serie necesario: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "No hay argumentos - no se ha podido crear {{Enum}}.",
+ "80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "la sintaxis de {{MySQL}} {{regex}} no respeta el distintivo {{`g`}}",
+ "0ac9f848b934332210bb27747d12a033": "la sintaxis de {{MySQL}} {{regex}} no respeta el distintivo {{`i`}}",
+ "4e9e35876bfb1511205456b52c6659d0": "la sintaxis de {{MySQL}} {{regex}} no respeta el distintivo {{`m`}}",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} debe ser una función"
+}
+
diff --git a/intl/fr/messages.json b/intl/fr/messages.json
new file mode 100644
index 00000000..266ade9d
--- /dev/null
+++ b/intl/fr/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} doit être un {{object}} : {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} est un argument de chaîne obligatoire : {0}",
+ "b7c60421de706ca1e050f2a86953745e": "Aucun argument - impossible de créer {{Enum}}.",
+ "80a32e80cbed65eba2103201a7c94710": "Modèle introuvable : {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "La syntaxe {{MySQL}} {{regex}} ne respecte pas l'indicateur {{`g`}}",
+ "0ac9f848b934332210bb27747d12a033": "La syntaxe {{MySQL}} {{regex}} ne respecte pas l'indicateur {{`i`}}",
+ "4e9e35876bfb1511205456b52c6659d0": "La syntaxe {{MySQL}} {{regex}} ne respecte pas l'indicateur {{`m`}}",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} doit être une fonction"
+}
+
diff --git a/intl/it/messages.json b/intl/it/messages.json
new file mode 100644
index 00000000..3a23a10d
--- /dev/null
+++ b/intl/it/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} deve essere un {{object}}: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} è un argomento stringa obbligatorio: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "Nessun argomento - impossibile creare {{Enum}}.",
+ "80a32e80cbed65eba2103201a7c94710": "Modello non trovato: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "La sintassi {{MySQL}} {{regex}} non rispetta l'indicatore {{`g`}}",
+ "0ac9f848b934332210bb27747d12a033": "La sintassi {{MySQL}} {{regex}} non rispetta l'indicatore {{`i`}}",
+ "4e9e35876bfb1511205456b52c6659d0": "La sintassi {{MySQL}} {{regex}} non rispetta l'indicatore {{`m`}}",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} deve essere una funzione"
+}
+
diff --git a/intl/ja/messages.json b/intl/ja/messages.json
new file mode 100644
index 00000000..d45c69be
--- /dev/null
+++ b/intl/ja/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} は {{object}} でなければなりません: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} は必須のストリング引数です: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "引数がありません - {{Enum}} を作成できませんでした。",
+ "80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}} 構文では {{`g`}} フラグは考慮されません",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}} 構文では {{`i`}} フラグは考慮されません",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}} 構文では {{`m`}} フラグは考慮されません",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} は関数でなければなりません"
+}
+
diff --git a/intl/ko/messages.json b/intl/ko/messages.json
new file mode 100644
index 00000000..fdc58235
--- /dev/null
+++ b/intl/ko/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}}이(가) {{object}}이어야 함: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}}은 필수 문자열 인수임: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "인수 없음 - {{Enum}}을(를) 작성할 수 없습니다. ",
+ "80a32e80cbed65eba2103201a7c94710": "모델을 찾을 수 없음: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}} 구문에서 {{`g`}} 플래그를 준수하지 않음",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}} 구문에서 {{`i`}} 플래그를 준수하지 않음",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}} 구문에서 {{`m`}} 플래그를 준수하지 않음",
+ "57512a471969647e8eaa2509cc292018": "{{callback}}이(가) 함수여야 함"
+}
+
diff --git a/intl/nl/messages.json b/intl/nl/messages.json
new file mode 100644
index 00000000..39c02107
--- /dev/null
+++ b/intl/nl/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} moet een {{object}} zijn: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} is een verplicht tekenreeksargument: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "Geen argumenten - {{Enum}} kan niet worden gemaakt.",
+ "80a32e80cbed65eba2103201a7c94710": "Model is niet gevonden: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "Syntaxis van {{MySQL}} {{regex}} voldoet niet aan vlag {{`g`}}",
+ "0ac9f848b934332210bb27747d12a033": "Syntaxis van {{MySQL}} {{regex}} voldoet niet aan vlag {{`i`}}",
+ "4e9e35876bfb1511205456b52c6659d0": "Syntaxis van {{MySQL}} {{regex}} voldoet niet aan vlag {{`m`}}",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} moet een functie zijn"
+}
+
diff --git a/intl/pt/messages.json b/intl/pt/messages.json
new file mode 100644
index 00000000..33010f1d
--- /dev/null
+++ b/intl/pt/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} deve ser um {{object}}: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} é um argumento de sequência necessário: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "Sem argumentos - não foi possível criar {{Enum}}.",
+ "80a32e80cbed65eba2103201a7c94710": "Modelo não localizado: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "Sintaxe {{regex}} de {{MySQL}} não respeita a sinalização {{`g`}}",
+ "0ac9f848b934332210bb27747d12a033": "Sintaxe {{regex}} de {{MySQL}} não respeita a sinalização {{`i`}}",
+ "4e9e35876bfb1511205456b52c6659d0": "Sintaxe {{regex}} de {{MySQL}} não respeita a sinalização {{`m`}}",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} deve ser uma função"
+}
+
diff --git a/intl/tr/messages.json b/intl/tr/messages.json
new file mode 100644
index 00000000..8af6c04b
--- /dev/null
+++ b/intl/tr/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} bir {{object}} olmalıdır: {0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} zorunlu bir dizgi bağımsız değişkeni: {0}",
+ "b7c60421de706ca1e050f2a86953745e": "Bağımsız değişken yok - {{Enum}} yaratılamadı.",
+ "80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}} düzenli ifade sözdizimi {{`g`}} işareti kuralına uymuyor",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}} düzenli ifade sözdizimi {{`i`}} işareti kuralına uymuyor",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}} düzenli ifade sözdizimi {{`m`}} işareti kuralına uymuyor",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} bir işlev olmalıdır"
+}
+
diff --git a/intl/zh-Hans/messages.json b/intl/zh-Hans/messages.json
new file mode 100644
index 00000000..97116597
--- /dev/null
+++ b/intl/zh-Hans/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} 必须为 {{object}}:{0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} 是必需的字符串自变量:{0}",
+ "b7c60421de706ca1e050f2a86953745e": "无自变量 - 无法创建 {{Enum}}。",
+ "80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}} 语法不考虑 {{`g`}} 标志",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}} 语法不考虑 {{`i`}} 标志",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}} 语法不考虑 {{`m`}} 标志",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} 应该是函数"
+}
+
diff --git a/intl/zh-Hant/messages.json b/intl/zh-Hant/messages.json
new file mode 100644
index 00000000..40c0db36
--- /dev/null
+++ b/intl/zh-Hant/messages.json
@@ -0,0 +1,11 @@
+{
+ "6ce5c3a3d305e965ff06e2b3e16e1252": "{{options}} 必須是 {{object}}:{0}",
+ "a0078d732b2dbabf98ed2efcdb55b402": "{{table}} 是必要的字串引數:{0}",
+ "b7c60421de706ca1e050f2a86953745e": "沒有引數 - 無法建立 {{Enum}}。",
+ "80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
+ "026ed55518f3812a9ef4b86e8a195e76": "{{MySQL}} {{regex}} 語法未遵循 {{`g`}} 旗標",
+ "0ac9f848b934332210bb27747d12a033": "{{MySQL}} {{regex}} 語法未遵循 {{`i`}} 旗標",
+ "4e9e35876bfb1511205456b52c6659d0": "{{MySQL}} {{regex}} 語法未遵循 {{`m`}} 旗標",
+ "57512a471969647e8eaa2509cc292018": "{{callback}} 應該是函數"
+}
+
diff --git a/lib/discovery.js b/lib/discovery.js
index 3a2a786f..336a3f32 100644
--- a/lib/discovery.js
+++ b/lib/discovery.js
@@ -1,3 +1,11 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+var g = require('strong-globalize')();
+
module.exports = mixinDiscovery;
/*!
@@ -81,7 +89,6 @@ function mixinDiscovery(MySQL, mysql) {
function queryViews(options) {
var sqlViews = null;
if (options.views) {
-
var schema = options.owner || options.schema;
if (options.all && !schema) {
@@ -162,7 +169,7 @@ function mixinDiscovery(MySQL, mysql) {
*/
function getArgs(table, options, cb) {
if ('string' !== typeof table || !table) {
- throw new Error('table is a required string argument: ' + table);
+ throw new Error(g.f('{{table}} is a required string argument: %s', table));
}
options = options || {};
if (!cb && 'function' === typeof options) {
@@ -170,13 +177,13 @@ function mixinDiscovery(MySQL, mysql) {
options = {};
}
if (typeof options !== 'object') {
- throw new Error('options must be an object: ' + options);
+ throw new Error(g.f('{{options}} must be an {{object}}: %s', options));
}
return {
schema: options.owner || options.schema,
table: table,
options: options,
- cb: cb
+ cb: cb,
};
}
@@ -196,6 +203,7 @@ function mixinDiscovery(MySQL, mysql) {
' character_maximum_length AS "dataLength",' +
' numeric_precision AS "dataPrecision",' +
' numeric_scale AS "dataScale",' +
+ ' column_type AS "columnType",' +
' is_nullable = \'YES\' AS "nullable"' +
' FROM information_schema.columns' +
' WHERE table_schema=' + mysql.escape(schema) +
@@ -209,6 +217,7 @@ function mixinDiscovery(MySQL, mysql) {
' character_maximum_length AS "dataLength",' +
' numeric_precision AS "dataPrecision",' +
' numeric_scale AS "dataScale",' +
+ ' column_type AS "columnType",' +
' is_nullable = \'YES\' AS "nullable"' +
' FROM information_schema.columns' +
(table ? ' WHERE table_name=' + mysql.escape(table) : ''),
@@ -233,6 +242,22 @@ function mixinDiscovery(MySQL, mysql) {
}
table = args.table;
options = args.options;
+
+ // Recommended MySQL 5.7 Boolean scheme. See
+ // http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html
+ // Currently default is the inverse of the recommendation for backward compatibility.
+ var defaultOptions = {
+ treatCHAR1AsString: false,
+ treatBIT1AsBit: true,
+ treatTINYINT1AsTinyInt: true,
+ };
+
+ for (var opt in defaultOptions) {
+ if (defaultOptions.hasOwnProperty(opt) && !options.hasOwnProperty(opt)) {
+ options[opt] = defaultOptions[opt];
+ }
+ }
+
cb = args.cb;
var sql = queryColumns(schema, table);
@@ -241,7 +266,7 @@ function mixinDiscovery(MySQL, mysql) {
cb(err, results);
} else {
results.map(function(r) {
- r.type = self.buildPropertyType(r);
+ r.type = self.buildPropertyType(r, options);
r.nullable = r.nullable ? 'Y' : 'N';
});
cb(err, results);
@@ -397,20 +422,18 @@ function mixinDiscovery(MySQL, mysql) {
this.execute(sql, cb);
};
- MySQL.prototype.buildPropertyType = function (columnDefinition) {
+ MySQL.prototype.buildPropertyType = function(columnDefinition, options) {
var mysqlType = columnDefinition.dataType;
+ var columnType = columnDefinition.columnType;
var dataLength = columnDefinition.dataLength;
var type = mysqlType.toUpperCase();
switch (type) {
case 'CHAR':
- if (dataLength === 1) {
- // Treat char(1) as boolean
+ if (!options.treatCHAR1AsString && columnType === 'char(1)') {
+ // Treat char(1) as boolean ('Y', 'N', 'T', 'F', '0', '1')
return 'Boolean';
- } else {
- return 'String';
}
- break;
case 'VARCHAR':
case 'TINYTEXT':
case 'MEDIUMTEXT':
@@ -426,8 +449,16 @@ function mixinDiscovery(MySQL, mysql) {
case 'BINARY':
case 'VARBINARY':
case 'BIT':
+ // treat BIT(1) as boolean as it's 1 or 0
+ if (!options.treatBIT1AsBit && columnType === 'bit(1)') {
+ return 'Boolean';
+ }
return 'Binary';
case 'TINYINT':
+ // treat TINYINT(1) as boolean as it is aliased as BOOL and BOOLEAN in mysql
+ if (!options.treatTINYINT1AsTinyInt && columnType === 'tinyint(1)') {
+ return 'Boolean';
+ }
case 'SMALLINT':
case 'INT':
case 'MEDIUMINT':
@@ -442,10 +473,13 @@ function mixinDiscovery(MySQL, mysql) {
return 'Date';
case 'POINT':
return 'GeoPoint';
+ case 'BOOL':
+ case 'BOOLEAN':
+ return 'Boolean';
default:
return 'String';
}
- }
+ };
MySQL.prototype.getDefaultSchema = function() {
if (this.dataSource && this.dataSource.settings &&
diff --git a/lib/enumFactory.js b/lib/enumFactory.js
index f57f8e42..4e681748 100644
--- a/lib/enumFactory.js
+++ b/lib/enumFactory.js
@@ -1,10 +1,18 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+var g = require('strong-globalize')();
+
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]
+ return Enum[arg];
} else if (Enum._values.indexOf(arg) !== -1) {
return arg;
} else if (arg === null) {
@@ -23,7 +31,7 @@ var EnumFactory = function() {
configurable: false,
enumerable: true,
value: arg,
- writable: false
+ writable: false,
});
dxList.push(arg);
}
@@ -31,18 +39,18 @@ var EnumFactory = function() {
configurable: false,
enumerable: false,
value: dxList,
- writable: false
+ writable: false,
});
Object.defineProperty(Enum, '_string', {
configurable: false,
enumerable: false,
value: stringified(Enum),
- writable: false
+ writable: false,
});
Object.freeze(Enum);
return Enum;
} else {
- throw "No arguments - could not create Enum.";
+ throw g.f('No arguments - could not create {{Enum}}.');
}
};
@@ -57,10 +65,3 @@ function stringified(anEnum) {
}
exports.EnumFactory = EnumFactory;
-
-
-
-
-
-
-
diff --git a/lib/migration.js b/lib/migration.js
index f1c18a5f..b8e9cdc6 100644
--- a/lib/migration.js
+++ b/lib/migration.js
@@ -1,3 +1,10 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+var g = require('strong-globalize')();
var async = require('async');
module.exports = mixinMigration;
@@ -29,7 +36,7 @@ function mixinMigration(MySQL, mysql) {
async.each(models, function(model, done) {
if (!(model in self._models)) {
return process.nextTick(function() {
- done(new Error('Model not found: ' + model));
+ done(new Error(g.f('Model not found: %s', model)));
});
}
var table = self.tableEscaped(model);
@@ -43,7 +50,6 @@ function mixinMigration(MySQL, mysql) {
});
});
}, cb);
-
};
/*!
@@ -124,7 +130,7 @@ function mixinMigration(MySQL, mysql) {
if (!ai[name]) {
ai[name] = {
info: i,
- columns: []
+ columns: [],
};
}
ai[name].columns[i.Seq_in_index - 1] = i.Column_name;
@@ -136,18 +142,19 @@ function mixinMigration(MySQL, mysql) {
propNames.forEach(function(propName) {
if (m.properties[propName] && self.id(model, propName)) return;
var found;
+ var colName = expectedColName(propName);
if (actualFields) {
actualFields.forEach(function(f) {
- if (f.Field === propName) {
+ if (f.Field === colName) {
found = f;
}
});
}
if (found) {
- actualize(propName, found);
+ actualize(colName, found);
} else {
- sql.push('ADD COLUMN ' + self.client.escapeId(propName) + ' ' +
+ sql.push('ADD COLUMN ' + self.client.escapeId(colName) + ' ' +
self.buildColumnDefinition(model, propName));
}
});
@@ -155,9 +162,12 @@ function mixinMigration(MySQL, mysql) {
// 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]) {
+ var colNames = propNames.map(expectedColName);
+ var index = colNames.indexOf(f.Field);
+ var propName = index >= 0 ? propNames[index] : f.Field;
+ var notFound = !~index;
+ if (m.properties[propName] && self.id(model, propName)) return;
+ if (notFound || !m.properties[propName]) {
sql.push('DROP COLUMN ' + self.client.escapeId(f.Field));
}
});
@@ -179,10 +189,28 @@ function mixinMigration(MySQL, mysql) {
// 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;
- });
+ //check if indexes are configured as "columns"
+ if (m.settings.indexes[indexName].columns) {
+ m.settings.indexes[indexName].columns.split(/,\s*/).forEach(
+ function(columnName, i) {
+ if (ai[indexName].columns[i] !== columnName) orderMatched = false;
+ });
+ } else if (m.settings.indexes[indexName].keys) {
+ //if indexes are configured as "keys"
+ var index = 0;
+ for (var key in m.settings.indexes[indexName].keys) {
+ var sortOrder = m.settings.indexes[indexName].keys[key];
+ if (ai[indexName].columns[index] !== key) {
+ orderMatched = false;
+ break;
+ }
+ index++;
+ }
+ //if number of columns differ between new and old index
+ if (index !== ai[indexName].columns.length) {
+ orderMatched = false;
+ }
+ }
}
if (!orderMatched) {
sql.push('DROP INDEX ' + self.client.escapeId(indexName));
@@ -199,7 +227,8 @@ function mixinMigration(MySQL, mysql) {
}
var found = ai[propName] && ai[propName].info;
if (!found) {
- var pName = self.client.escapeId(propName);
+ var colName = expectedColName(propName);
+ var pName = self.client.escapeId(colName);
var type = '';
var kind = '';
if (i.type) {
@@ -210,7 +239,7 @@ function mixinMigration(MySQL, mysql) {
' (' + pName + ') ' + type);
} else {
if (typeof i === 'object' && i.unique && i.unique === true) {
- kind = "UNIQUE";
+ kind = 'UNIQUE';
}
sql.push('ADD ' + kind + ' INDEX ' + pName + ' ' + type +
' (' + pName + ') ');
@@ -231,13 +260,35 @@ function mixinMigration(MySQL, mysql) {
}
if (i.kind) {
kind = i.kind;
+ } else if (i.options && i.options.unique && i.options.unique == true) {
+ //if index unique indicator is configured
+ kind = 'UNIQUE';
+ }
+
+ var indexedColumns = [];
+ var columns = '';
+ //if indexes are configured as "keys"
+ if (i.keys) {
+ for (var key in i.keys) {
+ if (i.keys[key] !== -1) {
+ indexedColumns.push(key);
+ } else {
+ indexedColumns.push(key + ' DESC ');
+ }
+ }
+ }
+ if (indexedColumns.length > 0) {
+ columns = indexedColumns.join(',');
+ } else if (i.columns) {
+ //if indexes are configured as "columns"
+ columns = i.columns;
}
if (kind && type) {
sql.push('ADD ' + kind + ' INDEX ' + iName +
- ' (' + i.columns + ') ' + type);
+ ' (' + columns + ') ' + type);
} else {
sql.push('ADD ' + kind + ' INDEX ' + type + ' ' + iName +
- ' (' + i.columns + ')');
+ ' (' + columns + ')');
}
}
});
@@ -283,6 +334,18 @@ function mixinMigration(MySQL, mysql) {
}
return false;
}
+
+ function expectedColName(propName) {
+ var mysql = m.properties[propName].mysql;
+ if (typeof mysql === 'undefined') {
+ return propName;
+ }
+ var colName = mysql.columnName;
+ if (typeof colName === 'undefined') {
+ return propName;
+ }
+ return colName;
+ }
};
MySQL.prototype.buildColumnDefinitions =
@@ -344,7 +407,7 @@ function mixinMigration(MySQL, mysql) {
return (kind + ' INDEX ' + columnName + ' (' + columnName + ') ' + type);
} else {
if (typeof i === 'object' && i.unique && i.unique === true) {
- kind = "UNIQUE";
+ kind = 'UNIQUE';
}
return (kind + ' INDEX ' + columnName + ' ' + type + ' (' + columnName + ') ');
}
@@ -364,23 +427,43 @@ function mixinMigration(MySQL, mysql) {
type = 'USING ' + i.type;
}
if (i.kind) {
+ //if index uniqueness is configured as "kind"
kind = i.kind;
+ } else if (i.options && i.options.unique && i.options.unique == true) {
+ //if index unique indicator is configured
+ kind = 'UNIQUE';
}
var indexedColumns = [];
var indexName = this.escapeName(index);
- if (Array.isArray(i.keys)) {
- indexedColumns = i.keys.map(function(key) {
- return self.columnEscaped(model, key);
- });
+ var columns = '';
+ //if indexes are configured as "keys"
+ if (i.keys) {
+ //for each field in "keys" object
+ for (var key in i.keys) {
+ if (i.keys[key] !== -1) {
+ indexedColumns.push(key);
+ } else {
+ //mysql does not support index sorting Currently
+ //but mysql has added DESC keyword for future support
+ indexedColumns.push(key + ' DESC ');
+ }
+ }
}
- var columns = indexedColumns.join(',') || i.columns;
- if (kind && type) {
- indexClauses.push(kind + ' INDEX ' + indexName + ' (' + columns + ') ' + type);
- } else {
- indexClauses.push(kind + ' INDEX ' + type + ' ' + indexName + ' (' + columns + ')');
+ if (indexedColumns.length) {
+ columns = indexedColumns.join(',');
+ } else if (i.columns) {
+ columns = i.columns;
+ }
+ if (columns.length) {
+ if (kind && type) {
+ indexClauses.push(kind + ' INDEX ' +
+ indexName + ' (' + columns + ') ' + type);
+ } else {
+ indexClauses.push(kind + ' INDEX ' + type +
+ ' ' + indexName + ' (' + columns + ')');
+ }
}
}
-
// Define index for each of the properties
for (var p in definition.properties) {
var propIndex = self.buildIndex(model, p);
@@ -469,7 +552,7 @@ function mixinMigration(MySQL, mysql) {
// The maximum length for an ID column is 1000 bytes
// The maximum row size is 64K
var len = p.length || p.limit ||
- ((p.type !== String) ? 4096 : p.id ? 255 : 512);
+ ((p.type !== String) ? 4096 : p.id || p.index ? 255 : 512);
columnType += '(' + len + ')';
break;
case 'char':
@@ -490,10 +573,10 @@ function mixinMigration(MySQL, mysql) {
function stringOptions(p, columnType) {
if (p.charset) {
- columnType += " CHARACTER SET " + p.charset;
+ columnType += ' CHARACTER SET ' + p.charset;
}
if (p.collation) {
- columnType += " COLLATE " + p.collation;
+ columnType += ' COLLATE ' + p.collation;
}
return columnType;
}
diff --git a/lib/mysql.js b/lib/mysql.js
index fc545eeb..52292dc5 100644
--- a/lib/mysql.js
+++ b/lib/mysql.js
@@ -1,3 +1,11 @@
+// Copyright IBM Corp. 2012,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+var g = require('strong-globalize')();
+
/*!
* Module dependencies
*/
@@ -26,9 +34,15 @@ exports.initialize = function initializeDataSource(dataSource, callback) {
dataSource.EnumFactory = EnumFactory; // factory for Enums. Note that currently Enums can not be registered.
- process.nextTick(function() {
- callback && callback();
- });
+ if (callback) {
+ if (dataSource.settings.lazyConnect) {
+ process.nextTick(function() {
+ callback();
+ });
+ } else {
+ dataSource.connector.connect(callback);
+ }
+ }
};
exports.MySQL = MySQL;
@@ -55,7 +69,41 @@ function defineMySQLTypes(dataSource) {
*/
function MySQL(settings) {
SqlConnector.call(this, 'mysql', settings);
+}
+
+require('util').inherits(MySQL, SqlConnector);
+MySQL.prototype.connect = function(callback) {
+ var self = this;
+ var options = generateOptions(this.settings);
+ var s = self.settings || {};
+
+ if (this.client) {
+ if (callback) {
+ process.nextTick(function() {
+ callback(null, self.client);
+ });
+ }
+ } else {
+ this.client = mysql.createPool(options);
+ this.client.getConnection(function(err, connection) {
+ var conn = connection;
+ if (!err) {
+ if (self.debug) {
+ debug('MySQL connection is established: ' + self.settings || {});
+ }
+ connection.release();
+ } else {
+ if (self.debug || !callback) {
+ console.error('MySQL connection is failed: ' + self.settings || {}, err);
+ }
+ }
+ callback && callback(err, conn);
+ });
+ }
+};
+
+function generateOptions(settings) {
var s = settings || {};
if (s.collation) {
// Charset should be first 'chunk' of collation.
@@ -72,49 +120,41 @@ function MySQL(settings) {
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,
- 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;
- }
-
- // Take other options for mysql driver
- // See https://github.com/strongloop/loopback-connector-mysql/issues/46
- for (var p in s) {
- if (p === 'database' && s.createDatabase) {
- continue;
- }
- if (options[p] === undefined) {
- options[p] = s[p];
+ var options;
+ if (s.url) {
+ // use url to override other settings if url provided
+ options = s.url;
+ } else {
+ options = {
+ host: s.host || s.hostname || 'localhost',
+ port: s.port || 3306,
+ user: s.username || s.user,
+ password: s.password,
+ timezone: s.timezone,
+ 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;
}
- }
-
- this.client = mysql.createPool(options);
-
- this.client.on('error', function(err) {
- dataSource.emit('error', err);
- dataSource.connected = false;
- dataSource.connecting = false;
- });
- if (debug.enabled) {
- debug('Settings: %j', s);
+ // Take other options for mysql driver
+ // See https://github.com/strongloop/loopback-connector-mysql/issues/46
+ for (var p in s) {
+ if (p === 'database' && s.createDatabase) {
+ continue;
+ }
+ if (options[p] === undefined) {
+ options[p] = s[p];
+ }
+ }
}
+ return options;
}
-
-require('util').inherits(MySQL, SqlConnector);
-
/**
* Execute the sql statement
*
@@ -127,7 +167,7 @@ MySQL.prototype.executeSQL = function(sql, params, options, callback) {
var debugEnabled = debug.enabled;
var db = this.settings.database;
if (typeof callback !== 'function') {
- throw new Error('callback should be a function');
+ throw new Error(g.f('{{callback}} should be a function'));
}
if (debugEnabled) {
debug('SQL: %s, params: %j', sql, params);
@@ -200,55 +240,72 @@ MySQL.prototype.executeSQL = function(sql, params, options, 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 = MySQL.prototype.save =
- function(model, data, options, callback) {
- var fields = this.buildFields(model, data);
-
- var sql = new ParameterizedSQL('INSERT INTO ' + this.tableEscaped(model));
- var columnValues = fields.columnValues;
- var fieldNames = fields.names;
- if (fieldNames.length) {
- sql.merge('(' + fieldNames.join(',') + ')', '');
- var values = ParameterizedSQL.join(columnValues, ',');
- values.sql = 'VALUES(' + values.sql + ')';
- sql.merge(values);
- } else {
- sql.merge(this.buildInsertDefaultValues(model, data, options));
- }
+MySQL.prototype._modifyOrCreate = function(model, data, options, fields, cb) {
+ var sql = new ParameterizedSQL('INSERT INTO ' + this.tableEscaped(model));
+ var columnValues = fields.columnValues;
+ var fieldNames = fields.names;
+ if (fieldNames.length) {
+ sql.merge('(' + fieldNames.join(',') + ')', '');
+ var values = ParameterizedSQL.join(columnValues, ',');
+ values.sql = 'VALUES(' + values.sql + ')';
+ sql.merge(values);
+ } else {
+ sql.merge(this.buildInsertDefaultValues(model, data, options));
+ }
- sql.merge('ON DUPLICATE KEY UPDATE');
- var setValues = [];
- for (var i = 0, n = fields.names.length; i < n; i++) {
- if (!fields.properties[i].id) {
- setValues.push(new ParameterizedSQL(fields.names[i] + '=' +
+ sql.merge('ON DUPLICATE KEY UPDATE');
+ var setValues = [];
+ for (var i = 0, n = fields.names.length; i < n; i++) {
+ if (!fields.properties[i].id) {
+ setValues.push(new ParameterizedSQL(fields.names[i] + '=' +
columnValues[i].sql, columnValues[i].params));
- }
}
+ }
- sql.merge(ParameterizedSQL.join(setValues, ','));
+ sql.merge(ParameterizedSQL.join(setValues, ','));
- this.execute(sql.sql, sql.params, options, function(err, info) {
- if (!err && info && info.insertId) {
- data.id = info.insertId;
- }
- var meta = {};
- if (info) {
+ this.execute(sql.sql, sql.params, options, function(err, info) {
+ if (!err && info && info.insertId) {
+ data.id = info.insertId;
+ }
+ var meta = {};
+ if (info) {
// When using the INSERT ... ON DUPLICATE KEY UPDATE statement,
// the returned value is as follows:
// 1 for each successful INSERT.
// 2 for each successful UPDATE.
- meta.isNewInstance = (info.affectedRows === 1);
- }
- callback(err, data, meta);
- });
- };
+ meta.isNewInstance = (info.affectedRows === 1);
+ }
+ cb(err, data, meta);
+ });
+};
+
+/**
+ * Replace 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 {Object} options The options
+ * @param {Function} [cb] The callback function
+ */
+MySQL.prototype.replaceOrCreate = function(model, data, options, cb) {
+ var fields = this.buildReplaceFields(model, data);
+ this._modifyOrCreate(model, data, options, fields, cb);
+};
+
+/**
+ * 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 {Object} options The options
+ * @param {Function} [cb] The callback function
+ */
+MySQL.prototype.save =
+MySQL.prototype.updateOrCreate = function(model, data, options, cb) {
+ var fields = this.buildFields(model, data);
+ this._modifyOrCreate(model, data, options, fields, cb);
+};
function dateToMysql(val) {
return val.getUTCFullYear() + '-' +
@@ -307,7 +364,7 @@ MySQL.prototype.toColumnValue = function(prop, val) {
if (prop.type.name === 'GeoPoint') {
return new ParameterizedSQL({
sql: 'Point(?,?)',
- params: [val.lat, val.lng]
+ params: [val.lat, val.lng],
});
}
if (prop.type === Object) {
@@ -351,7 +408,15 @@ MySQL.prototype.fromColumnValue = function(prop, val) {
val = String(val);
break;
case 'Date':
- val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
+
+ // MySQL allows, unless NO_ZERO_DATE is set, dummy date/time entries
+ // new Date() will return Invalid Date for those, so we need to handle
+ // those separate.
+ if (val == '0000-00-00 00:00:00') {
+ val = null;
+ } else {
+ val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
+ }
break;
case 'Boolean':
val = Boolean(val);
@@ -360,7 +425,7 @@ MySQL.prototype.fromColumnValue = function(prop, val) {
case 'Point':
val = {
lat: val.x,
- lng: val.y
+ lng: val.y,
};
break;
case 'List':
@@ -409,7 +474,7 @@ MySQL.prototype._buildLimit = function(model, limit, offset) {
return '';
}
return 'LIMIT ' + (offset ? (offset + ',' + limit) : limit);
-}
+};
MySQL.prototype.applyPagination = function(model, stmt, filter) {
var limitClause = this._buildLimit(model, filter.limit,
@@ -463,13 +528,13 @@ MySQL.prototype.buildExpression = function(columnName, operator, operatorValue,
propertyDefinition) {
if (operator === 'regexp') {
if (operatorValue.ignoreCase)
- console.warn('MySQL regex syntax does not respect the `i` flag');
+ g.warn('{{MySQL}} {{regex}} syntax does not respect the {{`i`}} flag');
if (operatorValue.global)
- console.warn('MySQL regex syntax does not respect the `g` flag');
+ g.warn('{{MySQL}} {{regex}} syntax does not respect the {{`g`}} flag');
if (operatorValue.multiline)
- console.warn('MySQL regex syntax does not respect the `m` flag');
+ g.warn('{{MySQL}} {{regex}} syntax does not respect the {{`m`}} flag');
return new ParameterizedSQL(columnName + ' REGEXP ?',
[operatorValue.source]);
diff --git a/lib/transaction.js b/lib/transaction.js
index f1fbe5ef..926aa07f 100644
--- a/lib/transaction.js
+++ b/lib/transaction.js
@@ -1,3 +1,9 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var debug = require('debug')('loopback:connector:mysql:transaction');
module.exports = mixinTransaction;
@@ -6,7 +12,6 @@ module.exports = mixinTransaction;
* @param {Object} mysql mysql driver
*/
function mixinTransaction(MySQL, mysql) {
-
/**
* Begin a new transaction
* @param isolationLevel
@@ -15,8 +20,8 @@ function mixinTransaction(MySQL, mysql) {
MySQL.prototype.beginTransaction = function(isolationLevel, cb) {
debug('Begin a transaction with isolation level: %s', isolationLevel);
this.client.getConnection(function(err, connection) {
- if(err) return cb(err);
- if(isolationLevel) {
+ if (err) return cb(err);
+ if (isolationLevel) {
connection.query(
'SET SESSION TRANSACTION ISOLATION LEVEL ' + isolationLevel,
function(err) {
@@ -60,4 +65,4 @@ function mixinTransaction(MySQL, mysql) {
cb(err);
});
};
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index edfa2027..bb823fab 100644
--- a/package.json
+++ b/package.json
@@ -1,31 +1,37 @@
{
"name": "loopback-connector-mysql",
- "version": "2.2.0",
+ "version": "3.0.0",
"description": "MySQL connector for loopback-datasource-juggler",
+ "engines": {
+ "node": ">=4"
+ },
"main": "index.js",
"scripts": {
- "test": "mocha"
+ "pretest": "node pretest.js",
+ "lint": "eslint .",
+ "test": "mocha --timeout 10000 test/*.js",
+ "posttest": "npm run lint"
},
"dependencies": {
"async": "^0.9.0",
"debug": "^2.1.1",
- "loopback-connector": "^2.1.0",
- "mysql": "^2.5.4"
+ "loopback-connector": "^3.0.0",
+ "mysql": "^2.11.1",
+ "strong-globalize": "^2.5.8"
},
"devDependencies": {
"bluebird": "~2.9.10",
- "loopback-datasource-juggler": "^2.28.0",
+ "eslint": "^2.13.1",
+ "eslint-config-loopback": "^4.0.0",
+ "loopback-datasource-juggler": "^3.0.0",
"mocha": "^2.1.0",
"rc": "^1.0.0",
- "should": "^5.0.0",
+ "should": "^8.0.2",
"sinon": "^1.15.4"
},
"repository": {
"type": "git",
"url": "https://github.com/strongloop/loopback-connector-mysql.git"
},
- "license": "MIT",
- "optionalDependencies": {
- "sl-blip": "http://blip.strongloop.com/loopback-connector-mysql@2.2.0"
- }
+ "license": "MIT"
}
diff --git a/pretest.js b/pretest.js
new file mode 100644
index 00000000..cbf69478
--- /dev/null
+++ b/pretest.js
@@ -0,0 +1,43 @@
+'use strict';
+
+if (!process.env.TEST_MYSQL_USER &&
+ !process.env.MYSQL_USER &&
+ !process.env.CI) {
+ return console.log('Not seeding DB with test db');
+}
+
+process.env.TEST_MYSQL_HOST =
+ process.env.TEST_MYSQL_HOST || process.env.MYSQL_HOST || 'localhost';
+process.env.TEST_MYSQL_PORT =
+ process.env.TEST_MYSQL_PORT || process.env.MYSQL_PORT || 3306;
+process.env.TEST_MYSQL_USER =
+ process.env.TEST_MYSQL_USER || process.env.MYSQL_USER || 'test';
+process.env.TEST_MYSQL_PASSWORD =
+ process.env.TEST_MYSQL_PASSWORD || process.env.MYSQL_PASSWORD || 'test';
+
+var fs = require('fs');
+var cp = require('child_process');
+
+var sql = fs.createReadStream(require.resolve('./test/schema.sql'));
+var stdio = ['pipe', process.stdout, process.stderr];
+var args = ['--user=' + process.env.TEST_MYSQL_USER];
+
+if (process.env.TEST_MYSQL_HOST) {
+ args.push('--host=' + process.env.TEST_MYSQL_HOST);
+}
+if (process.env.TEST_MYSQL_PORT) {
+ args.push('--port=' + process.env.TEST_MYSQL_PORT);
+}
+if (process.env.TEST_MYSQL_PASSWORD) {
+ args.push('--password=' + process.env.TEST_MYSQL_PASSWORD);
+}
+
+console.log('seeding DB with example db...');
+var mysql = cp.spawn('mysql', args, {stdio: stdio});
+sql.pipe(mysql.stdin);
+mysql.on('exit', function(code) {
+ console.log('done seeding DB');
+ setTimeout(function() {
+ process.exit(code);
+ }, 200);
+});
diff --git a/test/connection.test.js b/test/connection.test.js
index d31360cf..33d58ecd 100644
--- a/test/connection.test.js
+++ b/test/connection.test.js
@@ -1,47 +1,107 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
require('./init.js');
var assert = require('assert');
+var should = require('should');
+var DataSource = require('loopback-datasource-juggler').DataSource;
+var mysqlConnector = require('../');
+var url = require('url');
-var db, DummyModel, odb;
-
-describe('connections', function () {
+var db, DummyModel, odb, config;
- before(function () {
+describe('connections', function() {
+ before(function() {
require('./init.js');
+ config = global.getConfig();
+
odb = getDataSource({collation: 'utf8_general_ci', createDatabase: true});
db = odb;
});
- it('should use utf8 charset', function (done) {
+ it('should pass with valid settings', function(done) {
+ var db = new DataSource(mysqlConnector, config);
+ db.ping(done);
+ });
+ it('ignores all other settings when url is present', function(done) {
+ var formatedUrl = generateURL(config);
+ var dbConfig = {
+ url: formatedUrl,
+ host: 'invalid-hostname',
+ port: 80,
+ database: 'invalid-database',
+ username: 'invalid-username',
+ password: 'invalid-password',
+ };
+
+ var db = new DataSource(mysqlConnector, dbConfig);
+ db.ping(done);
+ });
+
+ 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.disconnect(function () {
+ it('should disconnect first db', function(done) {
+ db.disconnect(function() {
odb = getDataSource();
done();
});
});
- it('should use latin1 charset', 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.execute('DROP DATABASE IF EXISTS ' + db.settings.database, function(err) {
+ db.disconnect(function() {
+ done();
+ });
+ });
});
- it('should drop db and disconnect all', function (done) {
- db.connector.execute('DROP DATABASE IF EXISTS ' + db.settings.database, function (err) {
- db.disconnect(function () {
+ describe('lazyConnect', function() {
+ it('should skip connect phase (lazyConnect = true)', function(done) {
+ var dbConfig = {
+ host: '127.0.0.1',
+ port: 4,
+ lazyConnect: true,
+ };
+ var ds = new DataSource(mysqlConnector, dbConfig);
+
+ var errTimeout = setTimeout(function() {
+ done();
+ }, 2000);
+ ds.on('error', function(err) {
+ clearTimeout(errTimeout);
+ done(err);
+ });
+ });
+
+ it('should report connection error (lazyConnect = false)', function(done) {
+ var dbConfig = {
+ host: '127.0.0.1',
+ port: 4,
+ lazyConnect: false,
+ };
+ var ds = new DataSource(mysqlConnector, dbConfig);
+
+ ds.on('error', function(err) {
+ err.message.should.containEql('ECONNREFUSED');
done();
});
});
@@ -49,21 +109,19 @@ describe('connections', function () {
});
function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done) {
-
- query('DROP DATABASE IF EXISTS ' + odb.settings.database, function (err) {
+ query('DROP DATABASE IF EXISTS ' + odb.settings.database, function(err) {
assert.ok(!err);
- odb.disconnect(function () {
-
+ odb.disconnect(function() {
db = getDataSource({collation: test_set_collo, createDatabase: true});
DummyModel = db.define('DummyModel', {string: String});
- db.automigrate(function () {
+ db.automigrate(function() {
var q = 'SELECT DEFAULT_COLLATION_NAME' +
' FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ' +
db.driver.escape(db.settings.database) + ' LIMIT 1';
- db.connector.execute(q, function (err, r) {
+ db.connector.execute(q, function(err, r) {
assert.ok(!err);
- assert.ok(r[0].DEFAULT_COLLATION_NAME.match(test_collo));
- db.connector.execute('SHOW VARIABLES LIKE "character_set%"', function (err, r) {
+ should(r[0].DEFAULT_COLLATION_NAME).match(test_collo);
+ db.connector.execute('SHOW VARIABLES LIKE "character_set%"', function(err, r) {
assert.ok(!err);
var hit_all = 0;
for (var result in r) {
@@ -74,7 +132,7 @@ function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done) {
}
assert.equal(hit_all, 4);
});
- db.connector.execute('SHOW VARIABLES LIKE "collation%"', function (err, r) {
+ db.connector.execute('SHOW VARIABLES LIKE "collation%"', function(err, r) {
assert.ok(!err);
var hit_all = 0;
for (var result in r) {
@@ -88,7 +146,6 @@ function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done) {
});
});
});
-
}
function matchResult(result, variable_name, match) {
@@ -99,12 +156,21 @@ function matchResult(result, variable_name, match) {
return 0;
}
-var query = function (sql, cb) {
+var query = function(sql, cb) {
odb.connector.execute(sql, cb);
};
-
-
-
-
-
+function generateURL(config) {
+ var urlObj = {
+ protocol: 'mysql',
+ auth: config.username || '',
+ hostname: config.host,
+ pathname: config.database,
+ slashes: true,
+ };
+ if (config.password) {
+ urlObj.auth += ':' + config.password;
+ }
+ var formatedUrl = url.format(urlObj);
+ return formatedUrl;
+}
diff --git a/test/datatypes.test.js b/test/datatypes.test.js
index 998d8765..3fff9add 100644
--- a/test/datatypes.test.js
+++ b/test/datatypes.test.js
@@ -1,19 +1,25 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
require('./init.js');
var assert = require('assert');
var db, EnumModel, ANIMAL_ENUM;
+var mysqlVersion;
-describe('MySQL specific datatypes', function () {
-
+describe('MySQL specific datatypes', function() {
before(setup);
- it('should run migration', function (done) {
- db.automigrate(function () {
+ it('should run migration', function(done) {
+ db.automigrate(function() {
done();
});
});
- it('An enum should parse itself', 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));
@@ -24,11 +30,11 @@ describe('MySQL specific datatypes', function () {
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) {
+ 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) {
+ 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);
@@ -37,26 +43,26 @@ describe('MySQL specific datatypes', function () {
});
});
- 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 fail spectacularly with invalid enum values', function(done) {
+ // In MySQL 5.6/5.7, An ENUM value must be one of those listed in the column definition,
+ // or the internal numeric equivalent thereof. Invalid values are rejected.
+ // Reference: http://dev.mysql.com/doc/refman/5.7/en/constraint-enum.html
+ EnumModel.create({animal: 'horse', condition: 'sleepy', mood: 'happy'}, function(err, obj) {
+ assert.ok(err);
+ assert.equal(err.code, 'WARN_DATA_TRUNCATED');
+ assert.equal(err.errno, 1265);
+ done();
});
});
- it('should create a model instance with object/json types', function (done) {
+ it('should create a model instance with object/json types', function(done) {
var note = {a: 1, b: '2'};
var extras = {c: 3, d: '4'};
var em = EnumModel.create({animal: ANIMAL_ENUM.DOG, condition: 'sleepy',
- mood: 'happy', note: note, extras: extras}, function (err, obj) {
+ mood: 'happy', note: note, extras: extras}, function(err, obj) {
assert.ok(!err);
assert.equal(obj.condition, 'sleepy');
- EnumModel.findOne({where: {animal: ANIMAL_ENUM.DOG}}, function (err, found) {
+ EnumModel.findOne({where: {animal: ANIMAL_ENUM.DOG}}, function(err, found) {
assert.ok(!err);
assert.equal(found.mood, 'happy');
assert.equal(found.animal, ANIMAL_ENUM.DOG);
@@ -67,15 +73,13 @@ describe('MySQL specific datatypes', function () {
});
});
- it('should disconnect when done', function (done) {
+ it('should disconnect when done', function(done) {
db.disconnect();
- done()
+ done();
});
-
});
function setup(done) {
-
require('./init.js');
db = getSchema();
@@ -83,26 +87,28 @@ function setup(done) {
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') },
+ animal: {type: ANIMAL_ENUM, null: false},
+ condition: {type: db.EnumFactory('hungry', 'sleepy', 'thirsty')},
+ mood: {type: db.EnumFactory('angry', 'happy', 'sad')},
note: Object,
- extras: 'JSON'
+ extras: 'JSON',
});
- blankDatabase(db, done);
-
+ query('SELECT VERSION()', function(err, res) {
+ mysqlVersion = res && res[0] && res[0]['VERSION()'];
+ blankDatabase(db, done);
+ });
}
-var query = function (sql, cb) {
+var query = function(sql, cb) {
db.adapter.execute(sql, cb);
};
-var blankDatabase = function (db, 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) {
+ query('DROP DATABASE IF EXISTS ' + dbn, function(err) {
var q = 'CREATE DATABASE ' + dbn;
if (cs) {
q += ' CHARACTER SET ' + cs;
@@ -110,46 +116,40 @@ var blankDatabase = function (db, cb) {
if (co) {
q += ' COLLATE ' + co;
}
- query(q, function (err) {
+ query(q, function(err) {
query('USE ' + dbn, cb);
});
});
};
-getFields = function (model, cb) {
- query('SHOW FIELDS FROM ' + model, function (err, res) {
+var getFields = function(model, cb) {
+ query('SHOW FIELDS FROM ' + model, function(err, res) {
if (err) {
cb(err);
} else {
var fields = {};
- res.forEach(function (field) {
+ res.forEach(function(field) {
fields[field.Field] = field;
});
cb(err, fields);
}
});
-}
+};
-getIndexes = function (model, cb) {
- query('SHOW INDEXES FROM ' + model, function (err, res) {
+var 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) {
+ res.forEach(function(index) {
if (parseInt(index.Seq_in_index, 10) == 1) {
- indexes[index.Key_name] = index
+ indexes[index.Key_name] = index;
}
});
cb(err, indexes);
}
});
};
-
-
-
-
-
-
diff --git a/test/helpers/platform.js b/test/helpers/platform.js
new file mode 100644
index 00000000..394599c4
--- /dev/null
+++ b/test/helpers/platform.js
@@ -0,0 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+
+exports.isWindows = /^win/.test(process.platform);
diff --git a/test/imported.test.js b/test/imported.test.js
index 3ed5f883..3437d88a 100644
--- a/test/imported.test.js
+++ b/test/imported.test.js
@@ -1,10 +1,14 @@
-describe('mysql imported features', function () {
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
- before(function () {
+'use strict';
+describe('mysql imported features', function() {
+ before(function() {
require('./init.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 e337a656..af9efbe2 100644
--- a/test/init.js
+++ b/test/init.js
@@ -1,18 +1,24 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+
module.exports = require('should');
var DataSource = require('loopback-datasource-juggler').DataSource;
var config = require('rc')('loopback', {test: {mysql: {}}}).test.mysql;
-console.log(config)
-global.getConfig = function (options) {
-
+console.log(config);
+global.getConfig = function(options) {
var dbConf = {
- host: process.env.TEST_MYSQL_HOST || config.host || 'localhost',
- port: process.env.TEST_MYSQL_PORT || config.port || 3306,
+ host: process.env.MYSQL_HOST || config.host || 'localhost',
+ port: process.env.MYSQL_PORT || config.port || 3306,
database: 'myapp_test',
- username: process.env.TEST_MYSQL_USER || config.username,
- password: process.env.TEST_MYSQL_PASSWORD || config.password,
- createDatabase: true
+ username: process.env.MYSQL_USER || config.username,
+ password: process.env.MYSQL_PASSWORD || config.password,
+ createDatabase: true,
};
if (options) {
@@ -23,9 +29,14 @@ global.getConfig = function (options) {
return dbConf;
};
-global.getDataSource = global.getSchema = function (options) {
+global.getDataSource = global.getSchema = function(options) {
var db = new DataSource(require('../'), getConfig(options));
return db;
};
+global.connectorCapabilities = {
+ ilike: false,
+ nilike: false,
+};
+
global.sinon = require('sinon');
diff --git a/test/migration.test.js b/test/migration.test.js
index ce41fb22..bd32308c 100644
--- a/test/migration.test.js
+++ b/test/migration.test.js
@@ -1,21 +1,30 @@
-var should = require('./init.js');
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var assert = require('assert');
+var async = require('async');
+var platform = require('./helpers/platform');
+var should = require('./init');
var Schema = require('loopback-datasource-juggler').Schema;
var db, UserData, StringData, NumberData, DateData;
+var mysqlVersion;
-describe('migrations', function () {
-
+describe('migrations', function() {
before(setup);
- it('should run migration', function (done) {
- db.automigrate(function () {
+ it('should run migration', function(done) {
+ db.automigrate(function() {
done();
});
});
- it('UserData should have correct columns', function (done) {
- getFields('UserData', function (err, fields) {
+ it('UserData should have correct columns', function(done) {
+ getFields('UserData', function(err, fields) {
+ if (!fields) return done();
fields.should.be.eql({
id: {
Field: 'id',
@@ -23,228 +32,247 @@ describe('migrations', function () {
Null: 'NO',
Key: 'PRI',
Default: null,
- Extra: 'auto_increment' },
+ Extra: 'auto_increment'},
email: {
Field: 'email',
- Type: 'varchar(512)',
+ Type: 'varchar(255)',
Null: 'NO',
Key: 'MUL',
Default: null,
- Extra: '' },
+ Extra: ''},
name: {
Field: 'name',
Type: 'varchar(512)',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
+ Extra: ''},
bio: {
Field: 'bio',
Type: 'text',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
+ Extra: ''},
birthDate: {
Field: 'birthDate',
Type: 'datetime',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
+ Extra: ''},
pendingPeriod: {
Field: 'pendingPeriod',
Type: 'int(11)',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
+ Extra: ''},
createdByAdmin: {
Field: 'createdByAdmin',
Type: 'tinyint(1)',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' }
+ Extra: ''},
});
done();
});
});
- it('UserData should have correct indexes', function (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) {
- fields.should.be.eql({ 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',
+ getIndexes('UserData', function(err, fields) {
+ if (!fields) return done();
+ fields.should.match({
+ PRIMARY: {
+ Table: /UserData/i,
+ Non_unique: 0,
+ Key_name: 'PRIMARY',
+ Seq_in_index: 1,
+ Column_name: 'id',
+ Collation: 'A',
+ // XXX: this actually has more to do with whether the table existed or not and
+ // what kind of data is in it that MySQL has analyzed:
+ // https://dev.mysql.com/doc/refman/5.5/en/show-index.html
+ // Cardinality: /^5\.[567]/.test(mysqlVersion) ? 0 : null,
+ Sub_part: null,
+ Packed: null,
+ Null: '',
+ Index_type: 'BTREE',
+ Comment: ''},
+ email: {
+ Table: /UserData/i,
Non_unique: 1,
Key_name: 'email',
Seq_in_index: 1,
Column_name: 'email',
Collation: 'A',
- Cardinality: null,
- Sub_part: 333,
+ // XXX: this actually has more to do with whether the table existed or not and
+ // what kind of data is in it that MySQL has analyzed:
+ // https://dev.mysql.com/doc/refman/5.5/en/show-index.html
+ // Cardinality: /^5\.[567]/.test(mysqlVersion) ? 0 : null,
+ Sub_part: null,
Packed: null,
Null: '',
Index_type: 'BTREE',
- Comment: '' },
- index0: { Table: 'UserData',
+ Comment: ''},
+ index0: {
+ Table: /UserData/i,
Non_unique: 1,
Key_name: 'index0',
Seq_in_index: 1,
Column_name: 'email',
Collation: 'A',
- Cardinality: null,
- Sub_part: 333,
+ // XXX: this actually has more to do with whether the table existed or not and
+ // what kind of data is in it that MySQL has analyzed:
+ // https://dev.mysql.com/doc/refman/5.5/en/show-index.html
+ // Cardinality: /^5\.[567]/.test(mysqlVersion) ? 0 : null,
+ Sub_part: null,
Packed: null,
Null: '',
Index_type: 'BTREE',
- Comment: '' }
+ Comment: ''},
});
done();
});
});
- it('StringData should have correct columns', function (done) {
- getFields('StringData', function (err, fields) {
+ it('StringData should have correct columns', function(done) {
+ getFields('StringData', function(err, fields) {
fields.should.be.eql({
- idString: { Field: "idString",
+ idString: {Field: 'idString',
Type: 'varchar(255)',
Null: 'NO',
Key: 'PRI',
Default: null,
Extra: ''},
- smallString: { Field: 'smallString',
+ smallString: {Field: 'smallString',
Type: 'char(127)',
Null: 'NO',
Key: 'MUL',
Default: null,
- Extra: '' },
- mediumString: { Field: 'mediumString',
+ Extra: ''},
+ mediumString: {Field: 'mediumString',
Type: 'varchar(255)',
Null: 'NO',
Key: '',
Default: null,
- Extra: '' },
- tinyText: { Field: 'tinyText',
+ Extra: ''},
+ tinyText: {Field: 'tinyText',
Type: 'tinytext',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
- giantJSON: { Field: 'giantJSON',
+ Extra: ''},
+ giantJSON: {Field: 'giantJSON',
Type: 'longtext',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
- text: { Field: 'text',
+ Extra: ''},
+ text: {Field: 'text',
Type: 'varchar(1024)',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' }
+ Extra: ''},
});
done();
});
});
- it('NumberData should have correct columns', function (done) {
- getFields('NumberData', function (err, fields) {
+ it('NumberData should have correct columns', function(done) {
+ getFields('NumberData', function(err, fields) {
fields.should.be.eql({
- id: { Field: 'id',
+ id: {Field: 'id',
Type: 'int(11)',
Null: 'NO',
Key: 'PRI',
Default: null,
- Extra: 'auto_increment' },
- number: { Field: 'number',
+ Extra: 'auto_increment'},
+ number: {Field: 'number',
Type: 'decimal(10,3) unsigned',
Null: 'NO',
Key: 'MUL',
Default: null,
- Extra: '' },
- tinyInt: { Field: 'tinyInt',
+ Extra: ''},
+ tinyInt: {Field: 'tinyInt',
Type: 'tinyint(2)',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
- mediumInt: { Field: 'mediumInt',
+ Extra: ''},
+ mediumInt: {Field: 'mediumInt',
Type: 'mediumint(8) unsigned',
Null: 'NO',
Key: '',
Default: null,
- Extra: '' },
- floater: { Field: 'floater',
+ Extra: ''},
+ floater: {Field: 'floater',
Type: 'double(14,6)',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' }
+ Extra: ''},
});
done();
});
});
- it('DateData should have correct columns', function (done) {
- getFields('DateData', function (err, fields) {
+ it('DateData should have correct columns', function(done) {
+ getFields('DateData', function(err, fields) {
fields.should.be.eql({
- id: { Field: 'id',
+ id: {Field: 'id',
Type: 'int(11)',
Null: 'NO',
Key: 'PRI',
Default: null,
- Extra: 'auto_increment' },
- dateTime: { Field: 'dateTime',
+ Extra: 'auto_increment'},
+ dateTime: {Field: 'dateTime',
Type: 'datetime',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' },
- timestamp: { Field: 'timestamp',
+ Extra: ''},
+ timestamp: {Field: 'timestamp',
Type: 'timestamp',
Null: 'YES',
Key: '',
Default: null,
- Extra: '' }
+ Extra: ''},
});
done();
});
});
- it('should autoupdate', function (done) {
- var userExists = function (cb) {
- query('SELECT * FROM UserData', function (err, res) {
+ it('should autoupdate', function(done) {
+ // With an install of MYSQL5.7 on windows, these queries `randomly` fail and raise errors
+ // especially with decimals, number and Date format.
+ if (platform.isWindows) {
+ return 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) {
+ UserData.create({email: 'test@example.com'}, function(err, user) {
assert.ok(!err, 'Could not create user: ' + err);
- userExists(function (yep) {
+ userExists(function(yep) {
assert.ok(yep, 'User does not exist');
});
- UserData.defineProperty('email', { type: String });
+ 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) {
+ 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
@@ -259,7 +287,7 @@ describe('migrations', function () {
// assert.ok(!fields.pendingPeriod,
// 'Did not drop column pendingPeriod');
// user still exists
- userExists(function (yep) {
+ userExists(function(yep) {
assert.ok(yep, 'User does not exist');
done();
});
@@ -268,42 +296,81 @@ describe('migrations', function () {
});
});
- it('should check actuality of dataSource', function (done) {
+ it('should check actuality of dataSource', function(done) {
+ // With an install of MYSQL5.7 on windows, these queries `randomly` fail and raise errors
+ // with date, number and decimal format
+ if (platform.isWindows) {
+ return done();
+ }
// 'drop column'
- UserData.dataSource.isActual(function (err, ok) {
+ 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) {
+ UserData.dataSource.isActual(function(err, ok) {
assert.ok(!ok, 'dataSource is actual (shouldn\t be)');
- done()
+ 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();
+ // In MySQL 5.6/5.7 Out of range values are rejected.
+ // Reference: http://dev.mysql.com/doc/refman/5.7/en/integer-types.html
+ it('allows numbers with decimals', function(done) {
+ NumberData.create(
+ {number: 1.1234567, tinyInt: 127, mediumInt: 16777215, floater: 12345678.123456},
+ function(err, obj) {
+ if (err) return (err);
+ NumberData.findById(obj.id, function(err, found) {
+ assert.equal(found.number, 1.123);
+ assert.equal(found.tinyInt, 127);
+ assert.equal(found.mediumInt, 16777215);
+ assert.equal(found.floater, 12345678.123456);
+ done();
+ });
});
- });
});
- it('should allow both kinds of date columns', function (done) {
+ // Reference: http://dev.mysql.com/doc/refman/5.7/en/out-of-range-and-overflow.html
+ it('rejects out-of-range and overflow values', function(done) {
+ async.series([
+ function(next) {
+ NumberData.create({number: 1.1234567, tinyInt: 128, mediumInt: 16777215}, function(err, obj) {
+ assert(err);
+ assert.equal(err.code, 'ER_WARN_DATA_OUT_OF_RANGE');
+ next();
+ });
+ }, function(next) {
+ NumberData.create({number: 1.1234567, mediumInt: 16777215 + 1}, function(err, obj) {
+ assert(err);
+ assert.equal(err.code, 'ER_WARN_DATA_OUT_OF_RANGE');
+ next();
+ });
+ }, function(next) {
+ //Minimum value for unsigned mediumInt is 0
+ NumberData.create({number: 1.1234567, mediumInt: -8388608}, function(err, obj) {
+ assert(err);
+ assert.equal(err.code, 'ER_WARN_DATA_OUT_OF_RANGE');
+ next();
+ });
+ }, function(next) {
+ NumberData.create({number: 1.1234567, tinyInt: -129, mediumInt: 0}, function(err, obj) {
+ assert(err);
+ assert.equal(err.code, 'ER_WARN_DATA_OUT_OF_RANGE');
+ next();
+ });
+ },
+ ], 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) {
+ 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) {
+ 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(),
@@ -313,6 +380,63 @@ describe('migrations', function () {
});
});
+ // InMySQL5.7, DATETIME supported range is '1000-01-01 00:00:00' to '9999-12-31 23:59:59'.
+ // TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC
+ // Reference: http://dev.mysql.com/doc/refman/5.7/en/datetime.html
+ // Out of range values are set to null in windows but rejected elsewhere
+ // the next example is designed for windows while the following 2 are for other platforms
+ it('should map zero dateTime into null', function(done) {
+ if (!platform.isWindows) {
+ return done();
+ }
+
+ query('INSERT INTO `DateData` ' +
+ '(`dateTime`, `timestamp`) ' +
+ 'VALUES("0000-00-00 00:00:00", "0000-00-00 00:00:00") ',
+ function(err, ret) {
+ should.not.exists(err);
+ DateData.findById(ret.insertId, function(err, dateData) {
+ should(dateData.dateTime)
+ .be.null();
+ should(dateData.timestamp)
+ .be.null();
+ done();
+ });
+ });
+ });
+
+ it('rejects out of range datetime', function(done) {
+ if (platform.isWindows) {
+ return done();
+ }
+
+ query('INSERT INTO `DateData` ' +
+ '(`dateTime`, `timestamp`) ' +
+ 'VALUES("0000-00-00 00:00:00", "0000-00-00 00:00:00") ', function(err) {
+ var errMsg = 'ER_TRUNCATED_WRONG_VALUE: Incorrect datetime value: ' +
+ '\'0000-00-00 00:00:00\' for column \'dateTime\' at row 1';
+ assert(err);
+ assert.equal(err.message, errMsg);
+ done();
+ });
+ });
+
+ it('rejects out of range timestamp', function(done) {
+ if (platform.isWindows) {
+ return done();
+ }
+
+ query('INSERT INTO `DateData` ' +
+ '(`dateTime`, `timestamp`) ' +
+ 'VALUES("1000-01-01 00:00:00", "0000-00-00 00:00:00") ', function(err) {
+ var errMsg = 'ER_TRUNCATED_WRONG_VALUE: Incorrect datetime value: ' +
+ '\'0000-00-00 00:00:00\' for column \'timestamp\' at row 1';
+ assert(err);
+ assert.equal(err.message, errMsg);
+ done();
+ });
+ });
+
it('should report errors for automigrate', function() {
db.automigrate('XYZ', function(err) {
assert(err);
@@ -325,31 +449,29 @@ describe('migrations', function () {
});
});
- it('should disconnect when done', function (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 },
+ email: {type: String, null: false, index: true},
name: String,
bio: Schema.Text,
birthDate: Date,
pendingPeriod: Number,
createdByAdmin: Boolean,
- }, { indexes: {
+ }, {indexes: {
index0: {
- columns: 'email, createdByAdmin'
- }
- }
+ columns: 'email, createdByAdmin',
+ },
+ },
});
StringData = db.define('StringData', {
@@ -359,7 +481,7 @@ function setup(done) {
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}
+ text: {type: Schema.Text, dataType: 'varchar', limit: 1024},
});
NumberData = db.define('NumberData', {
@@ -368,27 +490,29 @@ function setup(done) {
tinyInt: {type: Number, dataType: 'tinyInt', display: 2},
mediumInt: {type: Number, dataType: 'mediumInt', unsigned: true,
required: true},
- floater: {type: Number, dataType: 'double', precision: 14, scale: 6}
+ floater: {type: Number, dataType: 'double', precision: 14, scale: 6},
});
DateData = db.define('DateData', {
dateTime: {type: Date, dataType: 'datetime'},
- timestamp: {type: Date, dataType: 'timestamp'}
+ timestamp: {type: Date, dataType: 'timestamp'},
});
- blankDatabase(db, done);
-
+ query('SELECT VERSION()', function(err, res) {
+ mysqlVersion = res && res[0] && res[0]['VERSION()'];
+ blankDatabase(db, done);
+ });
}
-var query = function (sql, cb) {
+var query = function(sql, cb) {
db.adapter.execute(sql, cb);
};
-var blankDatabase = function (db, 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) {
+ query('DROP DATABASE IF EXISTS ' + dbn, function(err) {
var q = 'CREATE DATABASE ' + dbn;
if (cs) {
q += ' CHARACTER SET ' + cs;
@@ -396,46 +520,40 @@ var blankDatabase = function (db, cb) {
if (co) {
q += ' COLLATE ' + co;
}
- query(q, function (err) {
+ query(q, function(err) {
query('USE ' + dbn, cb);
});
});
};
-getFields = function (model, cb) {
- query('SHOW FIELDS FROM ' + model, function (err, res) {
+var getFields = function(model, cb) {
+ query('SHOW FIELDS FROM ' + model, function(err, res) {
if (err) {
cb(err);
} else {
var fields = {};
- res.forEach(function (field) {
+ res.forEach(function(field) {
fields[field.Field] = field;
});
cb(err, fields);
}
});
-}
+};
-getIndexes = function (model, cb) {
- query('SHOW INDEXES FROM ' + model, function (err, res) {
+var 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) {
+ res.forEach(function(index) {
if (parseInt(index.Seq_in_index, 10) == 1) {
- indexes[index.Key_name] = index
+ indexes[index.Key_name] = index;
}
});
cb(err, indexes);
}
});
};
-
-
-
-
-
-
diff --git a/test/mysql.autoupdate.test.js b/test/mysql.autoupdate.test.js
index eb1f89df..3975feec 100644
--- a/test/mysql.autoupdate.test.js
+++ b/test/mysql.autoupdate.test.js
@@ -1,94 +1,176 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var assert = require('assert');
require('./init');
var ds;
-before(function () {
+before(function() {
ds = getDataSource();
});
-describe('MySQL connector', function () {
- it('should auto migrate/update tables', function (done) {
+describe('MySQL connector', function() {
+ before(function() {
+ setupAltColNameData();
+ });
+ it('should auto migrate/update tables', function(done) {
var schema_v1 =
- {
- "name": "CustomerTest",
- "options": {
- "idInjection": false,
- "mysql": {
- "schema": "myapp_test",
- "table": "customer_test"
- }
- },
- "properties": {
- "id": {
- "type": "String",
- "length": 20,
- "id": 1
+ {
+ 'name': 'CustomerTest',
+ 'options': {
+ 'idInjection': false,
+ 'mysql': {
+ 'schema': 'myapp_test',
+ 'table': 'customer_test',
+ },
+ 'indexes': {
+ 'name_index': {
+ 'keys': {
+ 'name': 1,
+ },
+ 'options': {
+ 'unique': true,
+ },
+ },
+ },
},
- "name": {
- "type": "String",
- "required": false,
- "length": 40
+ 'properties': {
+ 'id': {
+ 'type': 'String',
+ 'length': 20,
+ 'id': 1,
+ },
+ 'name': {
+ 'type': 'String',
+ 'required': false,
+ 'length': 40,
+ },
+ 'email': {
+ 'type': 'String',
+ 'required': true,
+ 'length': 40,
+ },
+ 'age': {
+ 'type': 'Number',
+ 'required': false,
+ },
+ 'discount': {
+ 'type': 'Number',
+ 'required': false,
+ 'dataType': 'decimal',
+ 'precision': 10,
+ 'scale': 2,
+ 'mysql': {
+ 'columnName': 'customer_discount',
+ 'dataType': 'decimal',
+ 'dataPrecision': 10,
+ 'dataScale': 2,
+ },
+ },
},
- "email": {
- "type": "String",
- "required": true,
- "length": 40
- },
- "age": {
- "type": "Number",
- "required": false
- }
- }
- }
+ };
var schema_v2 =
- {
- "name": "CustomerTest",
- "options": {
- "idInjection": false,
- "mysql": {
- "schema": "myapp_test",
- "table": "customer_test"
- }
- },
- "properties": {
- "id": {
- "type": "String",
- "length": 20,
- "id": 1
- },
- "email": {
- "type": "String",
- "required": false,
- "length": 60,
- "mysql": {
- "columnName": "email",
- "dataType": "varchar",
- "dataLength": 60,
- "nullable": "YES"
- }
+ {
+ 'name': 'CustomerTest',
+ 'options': {
+ 'idInjection': false,
+ 'mysql': {
+ 'schema': 'myapp_test',
+ 'table': 'customer_test',
+ },
+ 'indexes': {
+ 'updated_name_index': {
+ 'keys': {
+ 'firstName': 1,
+ 'lastName': -1,
+ },
+ 'options': {
+ 'unique': true,
+ },
+ },
+ },
},
- "firstName": {
- "type": "String",
- "required": false,
- "length": 40
+ 'properties': {
+ 'id': {
+ 'type': 'String',
+ 'length': 20,
+ 'id': 1,
+ },
+ 'email': {
+ 'type': 'String',
+ 'required': false,
+ 'length': 60,
+ 'mysql': {
+ 'columnName': 'email',
+ 'dataType': 'varchar',
+ 'dataLength': 60,
+ 'nullable': 'YES',
+ },
+ },
+ 'firstName': {
+ 'type': 'String',
+ 'required': false,
+ 'length': 40,
+ },
+ 'lastName': {
+ 'type': 'String',
+ 'required': false,
+ 'length': 40,
+ },
+ // remove age
+ // change data type details with column name
+ 'discount': {
+ 'type': 'Number',
+ 'required': false,
+ 'dataType': 'decimal',
+ 'precision': 12,
+ 'scale': 5,
+ 'mysql': {
+ 'columnName': 'customer_discount',
+ 'dataType': 'decimal',
+ 'dataPrecision': 12,
+ 'dataScale': 5,
+ },
+ },
+ // add new column with column name
+ 'address': {
+ 'type': 'String',
+ 'required': false,
+ 'length': 10,
+ 'mysql': {
+ 'columnName': 'customer_address',
+ 'dataType': 'varchar',
+ 'length': 10,
+ },
+ },
+ // add new column with index & column name
+ 'code': {
+ 'type': 'String',
+ 'required': true,
+ 'length': 12,
+ 'index': {
+ unique: true,
+ },
+ 'mysql': {
+ 'columnName': 'customer_code',
+ 'dataType': 'varchar',
+ 'length': 12,
+ },
+ },
},
- "lastName": {
- "type": "String",
- "required": false,
- "length": 40
- }
- }
- }
+ };
ds.createModel(schema_v1.name, schema_v1.properties, schema_v1.options);
- ds.automigrate(function () {
-
- ds.discoverModelProperties('customer_test', function (err, props) {
- assert.equal(props.length, 4);
- var names = props.map(function (p) {
+ ds.automigrate(function() {
+ ds.discoverModelProperties('customer_test', function(err, props) {
+ assert.equal(props.length, 5);
+ var names = props.map(function(p) {
return p.columnName;
});
assert.equal(props[0].nullable, 'N');
@@ -99,26 +181,84 @@ describe('MySQL connector', function () {
assert.equal(names[1], 'name');
assert.equal(names[2], 'email');
assert.equal(names[3], 'age');
+ assert.equal(names[4], 'customer_discount');
- ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options);
-
- ds.autoupdate(function (err, result) {
- ds.discoverModelProperties('customer_test', function (err, props) {
- assert.equal(props.length, 4);
- var names = props.map(function (p) {
- return p.columnName;
+ ds.connector.execute('SHOW INDEXES FROM customer_test', function(err, indexes) {
+ if (err) return done (err);
+ assert(indexes);
+ assert(indexes.length.should.be.above(1));
+ assert.equal(indexes[1].Key_name, 'name_index');
+ assert.equal(indexes[1].Non_unique, 0);
+ ds.createModel(schema_v2.name, schema_v2.properties, schema_v2.options);
+ ds.autoupdate(function(err, result) {
+ if (err) return done (err);
+ ds.discoverModelProperties('customer_test', function(err, props) {
+ if (err) return done (err);
+ assert.equal(props.length, 7);
+ var names = props.map(function(p) {
+ return p.columnName;
+ });
+ assert.equal(names[0], 'id');
+ assert.equal(names[1], 'email');
+ assert.equal(names[2], 'customer_discount');
+ assert.equal(names[3], 'firstName');
+ assert.equal(names[4], 'lastName');
+ assert.equal(names[5], 'customer_address');
+ assert.equal(names[6], 'customer_code');
+ ds.connector.execute('SHOW INDEXES FROM customer_test', function(err, updatedindexes) {
+ if (err) return done (err);
+ assert(updatedindexes);
+ assert(updatedindexes.length.should.be.above(3));
+ assert.equal(updatedindexes[1].Key_name, 'customer_code');
+ assert.equal(updatedindexes[2].Key_name, 'updated_name_index');
+ assert.equal(updatedindexes[3].Key_name, 'updated_name_index');
+ //Mysql supports only index sorting in ascending; DESC is ignored
+ assert.equal(updatedindexes[1].Collation, 'A');
+ assert.equal(updatedindexes[2].Collation, 'A');
+ assert.equal(updatedindexes[3].Collation, 'A');
+ assert.equal(updatedindexes[1].Non_unique, 0);
+ assert.equal(updatedindexes[2].Non_unique, 0);
+ assert.equal(updatedindexes[3].Non_unique, 0);
+ done(err, result);
+ });
});
- assert.equal(names[0], 'id');
- assert.equal(names[1], 'email');
- assert.equal(names[2], 'firstName');
- assert.equal(names[3], 'lastName');
- done(err, result);
});
});
});
});
});
+ function setupAltColNameData() {
+ var schema = {
+ name: 'ColRenameTest',
+ options: {
+ idInjection: false,
+ mysql: {
+ schema: 'myapp_test',
+ table: 'col_rename_test',
+ },
+ },
+ properties: {
+ firstName: {
+ type: 'String',
+ required: false,
+ length: 40,
+ mysql: {
+ columnName: 'first_name',
+ dataType: 'varchar',
+ dataLength: 40,
+ },
+ },
+ lastName: {
+ type: 'String',
+ required: false,
+ length: 40,
+ },
+ },
+ };
+ ds.createModel(schema.name, schema.properties, schema.options);
+ }
+
it('should report errors for automigrate', function(done) {
ds.automigrate('XYZ', function(err) {
assert(err);
@@ -133,5 +273,24 @@ describe('MySQL connector', function () {
});
});
-});
+ it('"mysql.columnName" is updated with correct name on create table', function(done) {
+ // first autoupdate call uses create table
+ verifyMysqlColumnNameAutoupdate(done);
+ });
+ it('"mysql.columnName" is updated without changing column name on alter table', function(done) {
+ // second autoupdate call uses alter table
+ verifyMysqlColumnNameAutoupdate(done);
+ });
+
+ function verifyMysqlColumnNameAutoupdate(done) {
+ ds.autoupdate('ColRenameTest', function(err) {
+ ds.discoverModelProperties('col_rename_test', function(err, props) {
+ assert.equal(props[0].columnName, 'first_name');
+ assert.equal(props[1].columnName, 'lastName');
+ assert.equal(props.length, 2);
+ done();
+ });
+ });
+ }
+});
diff --git a/test/mysql.discover.test.js b/test/mysql.discover.test.js
index 123e73db..5c176fd7 100644
--- a/test/mysql.discover.test.js
+++ b/test/mysql.discover.test.js
@@ -1,3 +1,9 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
process.env.NODE_ENV = 'test';
require('should');
@@ -5,13 +11,14 @@ var assert = require('assert');
var DataSource = require('loopback-datasource-juggler').DataSource;
var db, config;
-before(function () {
- config = require('rc')('loopback', {dev: {mysql: {}}}).dev.mysql;
+before(function() {
+ require('./init');
+ config = getConfig();
config.database = 'STRONGLOOP';
db = new DataSource(require('../'), config);
});
-describe('discoverModels', function () {
+describe('discoverModels', function() {
describe('Discover database schemas', function() {
it('should return an array of db schemas', function(done) {
db.connector.discoverDatabaseSchemas(function(err, schemas) {
@@ -23,19 +30,18 @@ describe('discoverModels', function () {
});
});
- describe('Discover models including views', function () {
- it('should return an array of tables and views', function (done) {
-
+ 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) {
+ 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;
@@ -48,18 +54,17 @@ describe('discoverModels', function () {
});
});
- describe('Discover current user\'s tables', function () {
- it('should return an array of tables for the current user', function (done) {
-
+ describe('Discover current user\'s tables', function() {
+ it('should return an array of tables for the current user', function(done) {
db.discoverModelDefinitions({
- limit: 3
- }, function (err, models) {
+ limit: 3,
+ }, function(err, models) {
if (err) {
console.error(err);
done(err);
} else {
var views = false;
- models.forEach(function (m) {
+ models.forEach(function(m) {
assert.equal(m.owner, config.username);
});
done(null, models);
@@ -68,19 +73,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() {
+ // TODO: this test assumes the current user owns the tables
+ it.skip('should return an array of only tables', function(done) {
db.discoverModelDefinitions({
views: false,
- limit: 3
- }, function (err, models) {
+ 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;
@@ -95,19 +100,18 @@ 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) {
+ 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;
@@ -120,51 +124,50 @@ 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');
+ assert(m.tableName === 'product');
});
done(null, models);
}
});
});
});
-
});
-describe('Discover model primary keys', function () {
- it('should return an array of primary keys for PRODUCT', function (done) {
- db.discoverPrimaryKeys('PRODUCT', function (err, models) {
+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) {
+ models.forEach(function(m) {
// console.dir(m);
- assert(m.tableName === 'PRODUCT');
+ 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) {
+ 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) {
+ models.forEach(function(m) {
// console.dir(m);
- assert(m.tableName === 'PRODUCT');
+ assert(m.tableName === 'product');
});
done(null, models);
}
@@ -172,14 +175,14 @@ describe('Discover model primary keys', function () {
});
});
-describe('Discover model foreign keys', function () {
- it('should return an array of foreign keys for INVENTORY', function (done) {
- db.discoverForeignKeys('INVENTORY', function (err, 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) {
+ models.forEach(function(m) {
// console.dir(m);
assert(m.fkTableName === 'INVENTORY');
});
@@ -187,13 +190,13 @@ describe('Discover model foreign keys', function () {
}
});
});
- it('should return an array of foreign keys for STRONGLOOP.INVENTORY', function (done) {
- db.discoverForeignKeys('INVENTORY', {owner: 'STRONGLOOP'}, function (err, 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) {
+ models.forEach(function(m) {
// console.dir(m);
assert(m.fkTableName === 'INVENTORY');
});
@@ -203,51 +206,203 @@ describe('Discover model foreign keys', function () {
});
});
-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.required);
- 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.required === false);
- assert(schema.properties.available.type === 'Number');
- assert(schema.properties.total);
- assert(schema.properties.total.type === 'Number');
- done(null, schema);
+describe('Discover LDL schema from a table', function() {
+ var schema;
+ before(function(done) {
+ db.discoverSchema('INVENTORY', {owner: 'STRONGLOOP'}, function(err, schema_) {
+ schema = schema_;
+ done(err);
});
});
+ it('should return an LDL schema for INVENTORY', function() {
+ var productId = 'productId' in schema.properties ? 'productId' : 'productid';
+ var locationId = 'locationId' in schema.properties ? 'locationId' : 'locationid';
+ console.error('schema:', schema);
+ assert.strictEqual(schema.name, 'Inventory');
+ assert.ok(/STRONGLOOP/i.test(schema.options.mysql.schema));
+ assert.strictEqual(schema.options.mysql.table, 'INVENTORY');
+ assert(schema.properties[productId]);
+ // TODO: schema shows this field is default NULL, which means it isn't required
+ // assert(schema.properties[productId].required);
+ assert.strictEqual(schema.properties[productId].type, 'String');
+ assert.strictEqual(schema.properties[productId].mysql.columnName, 'PRODUCT_ID');
+ assert(schema.properties[locationId]);
+ assert.strictEqual(schema.properties[locationId].type, 'String');
+ assert.strictEqual(schema.properties[locationId].mysql.columnName, 'LOCATION_ID');
+ assert(schema.properties.available);
+ assert.strictEqual(schema.properties.available.required, false);
+ assert.strictEqual(schema.properties.available.type, 'Number');
+ assert(schema.properties.total);
+ assert.strictEqual(schema.properties.total.type, 'Number');
+ });
});
-describe('Discover and build models', function () {
- it('should discover and build models', function (done) {
- db.discoverAndBuildModels('INVENTORY', {owner: 'STRONGLOOP', visited: {}, associations: true}, function (err, models) {
- assert(models.Inventory, 'Inventory model should be discovered and built');
- var schema = models.Inventory.definition;
- assert(schema.settings.mysql.schema === 'STRONGLOOP');
- assert(schema.settings.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);
- models.Inventory.findOne(function (err, inv) {
- assert(!err, 'error should not be reported');
- done();
+describe('Discover and build models', function() {
+ var models;
+ before(function(done) {
+ db.discoverAndBuildModels('INVENTORY', {owner: 'STRONGLOOP', visited: {}, associations: true},
+ function(err, models_) {
+ models = models_;
+ done(err);
+ });
+ });
+ it('should discover and build models', function() {
+ assert(models.Inventory, 'Inventory model should be discovered and built');
+ var schema = models.Inventory.definition;
+ var productId = 'productId' in schema.properties ? 'productId' : 'productid';
+ var locationId = 'locationId' in schema.properties ? 'locationId' : 'locationid';
+ assert(/STRONGLOOP/i.test(schema.settings.mysql.schema));
+ assert.strictEqual(schema.settings.mysql.table, 'INVENTORY');
+ assert(schema.properties[productId]);
+ assert.strictEqual(schema.properties[productId].type, String);
+ assert.strictEqual(schema.properties[productId].mysql.columnName, 'PRODUCT_ID');
+ assert(schema.properties[locationId]);
+ assert.strictEqual(schema.properties[locationId].type, String);
+ assert.strictEqual(schema.properties[locationId].mysql.columnName, 'LOCATION_ID');
+ assert(schema.properties.available);
+ assert.strictEqual(schema.properties.available.type, Number);
+ assert(schema.properties.total);
+ assert.strictEqual(schema.properties.total.type, Number);
+ });
+ it('should be able to find an instance', function(done) {
+ assert(models.Inventory, 'Inventory model must exist');
+ models.Inventory.findOne(function(err, inv) {
+ assert(!err, 'error should not be reported');
+ done();
+ });
+ });
+
+ describe('discoverModelProperties() flags', function() {
+ context('with default flags', function() {
+ var models, schema;
+ before(discoverAndBuildModels);
+
+ it('handles CHAR(1) as Boolean', function() {
+ assert(schema.properties.enabled);
+ assert.strictEqual(schema.properties.enabled.type, Boolean);
});
+
+ it('handles BIT(1) as Bit', function() {
+ assert(schema.properties.disabled);
+ assert.strictEqual(schema.properties.disabled.type, Buffer);
+ });
+
+ it('handles TINYINT(1) as Number', function() {
+ assert(schema.properties.active);
+ assert.strictEqual(schema.properties.active.type, Number);
+ });
+
+ function discoverAndBuildModels(done) {
+ db.discoverAndBuildModels('INVENTORY', {
+ owner: 'STRONGLOOP',
+ visited: {},
+ associations: true,
+ }, function(err, models_) {
+ models = models_;
+ schema = models.Inventory.definition;
+ done(err);
+ });
+ }
+ });
+
+ context('with flag treatCHAR1AsString = true', function() {
+ var models, schema;
+ before(discoverAndBuildModels);
+
+ it('handles CHAR(1) as String', function() {
+ assert(schema.properties.enabled);
+ assert.strictEqual(schema.properties.enabled.type, String);
+ });
+
+ it('handles BIT(1) as Binary', function() {
+ assert(schema.properties.disabled);
+ assert.strictEqual(schema.properties.disabled.type, Buffer);
+ });
+
+ it('handles TINYINT(1) as Number', function() {
+ assert(schema.properties.active);
+ assert.strictEqual(schema.properties.active.type, Number);
+ });
+
+ function discoverAndBuildModels(done) {
+ db.discoverAndBuildModels('INVENTORY', {
+ owner: 'STRONGLOOP',
+ visited: {},
+ associations: true,
+ treatCHAR1AsString: true,
+ }, function(err, models_) {
+ models = models_;
+ schema = models.Inventory.definition;
+ done(err);
+ });
+ }
+ });
+
+ context('with flag treatBIT1AsBit = false', function() {
+ var models, schema;
+ before(discoverAndBuildModels);
+
+ it('handles CHAR(1) as Boolean', function() {
+ assert(schema.properties.enabled);
+ assert.strictEqual(schema.properties.enabled.type, Boolean);
+ });
+
+ it('handles BIT(1) as Boolean', function() {
+ assert(schema.properties.disabled);
+ assert.strictEqual(schema.properties.disabled.type, Boolean);
+ });
+
+ it('handles TINYINT(1) as Number', function() {
+ assert(schema.properties.active);
+ assert.strictEqual(schema.properties.active.type, Number);
+ });
+
+ function discoverAndBuildModels(done) {
+ db.discoverAndBuildModels('INVENTORY', {
+ owner: 'STRONGLOOP',
+ visited: {},
+ associations: true,
+ treatBIT1AsBit: false,
+ }, function(err, models_) {
+ models = models_;
+ schema = models.Inventory.definition;
+ done(err);
+ });
+ }
+ });
+
+ context('with flag treatTINYINT1AsTinyInt = false', function() {
+ var models, schema;
+ before(discoverAndBuildModels);
+
+ it('handles CHAR(1) as Boolean', function() {
+ assert(schema.properties.enabled);
+ assert.strictEqual(schema.properties.enabled.type, Boolean);
+ });
+
+ it('handles BIT(1) as Binary', function() {
+ assert(schema.properties.disabled);
+ assert.strictEqual(schema.properties.disabled.type, Buffer);
+ });
+
+ it('handles TINYINT(1) as Boolean', function() {
+ assert(schema.properties.active);
+ assert.strictEqual(schema.properties.active.type, Boolean);
+ });
+
+ function discoverAndBuildModels(done) {
+ db.discoverAndBuildModels('INVENTORY', {
+ owner: 'STRONGLOOP',
+ visited: {},
+ associations: true,
+ treatTINYINT1AsTinyInt: false,
+ }, function(err, models_) {
+ if (err) return done(err);
+ models = models_;
+ schema = models.Inventory.definition;
+ done();
+ });
+ }
});
});
});
diff --git a/test/mysql.test.js b/test/mysql.test.js
index bd521167..1034a60a 100644
--- a/test/mysql.test.js
+++ b/test/mysql.test.js
@@ -1,3 +1,9 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var should = require('./init.js');
var Post, PostWithStringId, PostWithUniqueTitle, db;
@@ -15,51 +21,51 @@ ObjectID.prototype.toJSON = function() {
return this.id1 + this.id2;
};
-describe('mysql', function () {
-
- before(function (done) {
+describe('mysql', function() {
+ before(function(done) {
db = getDataSource();
Post = db.define('PostWithDefaultId', {
- title: { type: String, length: 255, index: true },
- content: { type: String },
+ title: {type: String, length: 255, index: true},
+ content: {type: String},
comments: [String],
history: Object,
stars: Number,
- userId: ObjectID
+ userId: ObjectID,
+ }, {
+ forceId: false,
});
PostWithStringId = db.define('PostWithStringId', {
id: {type: String, id: true},
- title: { type: String, length: 255, index: true },
- content: { type: String }
+ title: {type: String, length: 255, index: true},
+ content: {type: String},
});
PostWithUniqueTitle = db.define('PostWithUniqueTitle', {
- title: { type: String, length: 255, index: {unique: true} },
- content: { type: String }
+ title: {type: String, length: 255, index: {unique: true}},
+ content: {type: String},
});
- db.automigrate(['PostWithDefaultId', 'PostWithStringId', 'PostWithUniqueTitle'], function (err) {
+ db.automigrate(['PostWithDefaultId', 'PostWithStringId', 'PostWithUniqueTitle'], function(err) {
should.not.exist(err);
done(err);
});
});
- beforeEach(function (done) {
- Post.destroyAll(function () {
- PostWithStringId.destroyAll(function () {
- PostWithUniqueTitle.destroyAll(function () {
+ beforeEach(function(done) {
+ Post.destroyAll(function() {
+ PostWithStringId.destroyAll(function() {
+ PostWithUniqueTitle.destroyAll(function() {
done();
});
});
});
});
- it('should allow array or object', function (done) {
+ it('should allow array or object', function(done) {
Post.create({title: 'a', content: 'AAA', comments: ['1', '2'],
history: {a: 1, b: 'b'}}, function(err, post) {
-
should.not.exist(err);
Post.findById(post.id, function(err, p) {
@@ -79,7 +85,6 @@ describe('mysql', function () {
var uid = new ObjectID('123');
Post.create({title: 'a', content: 'AAA', userId: uid},
function(err, post) {
-
should.not.exist(err);
Post.findById(post.id, function(err, p) {
@@ -93,15 +98,15 @@ describe('mysql', function () {
});
});
- it('updateOrCreate should update the instance', function (done) {
- Post.create({title: 'a', content: 'AAA'}, function (err, post) {
+ it('updateOrCreate should update the instance', function(done) {
+ Post.create({title: 'a', content: 'AAA'}, function(err, post) {
post.title = 'b';
- Post.updateOrCreate(post, function (err, p) {
+ Post.updateOrCreate(post, function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
- Post.findById(post.id, function (err, p) {
+ Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
@@ -110,19 +115,18 @@ describe('mysql', function () {
done();
});
});
-
});
});
- it('updateOrCreate should update the instance without removing existing properties', function (done) {
- Post.create({title: 'a', content: 'AAA'}, function (err, post) {
+ it('updateOrCreate should update the instance without removing existing properties', function(done) {
+ Post.create({title: 'a', content: 'AAA'}, function(err, post) {
post = post.toObject();
delete post.title;
- Post.updateOrCreate(post, function (err, p) {
+ Post.updateOrCreate(post, function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
- Post.findById(post.id, function (err, p) {
+ Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
@@ -131,19 +135,18 @@ describe('mysql', function () {
done();
});
});
-
});
});
- it('updateOrCreate should create a new instance if it does not exist', function (done) {
+ it('updateOrCreate should create a new instance if it does not exist', function(done) {
var post = {id: 123, title: 'a', content: 'AAA'};
- Post.updateOrCreate(post, function (err, p) {
+ Post.updateOrCreate(post, function(err, p) {
should.not.exist(err);
p.title.should.be.equal(post.title);
p.content.should.be.equal(post.content);
p.id.should.be.equal(post.id);
- Post.findById(p.id, function (err, p) {
+ Post.findById(p.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
@@ -153,18 +156,89 @@ describe('mysql', function () {
done();
});
});
+ });
+
+ context('replaceOrCreate', function() {
+ it('should replace the instance', function(done) {
+ Post.create({title: 'a', content: 'AAA'}, function(err, post) {
+ if (err) return done(err);
+ post = post.toObject();
+ delete post.content;
+ Post.replaceOrCreate(post, function(err, p) {
+ if (err) return done(err);
+ p.id.should.equal(post.id);
+ p.title.should.equal('a');
+ should.not.exist(p.content);
+ should.not.exist(p._id);
+ Post.findById(post.id, function(err, p) {
+ if (err) return done(err);
+ p.id.should.equal(post.id);
+ p.title.should.equal('a');
+ should.not.exist(post.content);
+ should.not.exist(p._id);
+ done();
+ });
+ });
+ });
+ });
+ it('should replace with new data', function(done) {
+ Post.create({title: 'a', content: 'AAA', comments: ['Comment1']},
+ function(err, post) {
+ if (err) return done(err);
+ post = post.toObject();
+ delete post.comments;
+ delete post.content;
+ post.title = 'b';
+ Post.replaceOrCreate(post, function(err, p) {
+ if (err) return done(err);
+ p.id.should.equal(post.id);
+ should.not.exist(p._id);
+ p.title.should.equal('b');
+ should.not.exist(p.content);
+ should.not.exist(p.comments);
+ Post.findById(post.id, function(err, p) {
+ if (err) return done(err);
+ p.id.should.equal(post.id);
+ should.not.exist(p._id);
+ p.title.should.equal('b');
+ should.not.exist(p.content);
+ should.not.exist(p.comments);
+ done();
+ });
+ });
+ });
+ });
+
+ it('should create a new instance if it does not exist', function(done) {
+ var post = {id: 123, title: 'a', content: 'AAA'};
+ Post.replaceOrCreate(post, function(err, p) {
+ if (err) return done(err);
+ p.id.should.equal(post.id);
+ should.not.exist(p._id);
+ p.title.should.equal(post.title);
+ p.content.should.equal(post.content);
+ Post.findById(p.id, function(err, p) {
+ if (err) return done(err);
+ p.id.should.equal(post.id);
+ should.not.exist(p._id);
+ p.title.should.equal(post.title);
+ p.content.should.equal(post.content);
+ done();
+ });
+ });
+ });
});
- it('save should update the instance with the same id', function (done) {
- Post.create({title: 'a', content: 'AAA'}, function (err, post) {
+ it('save should update the instance with the same id', function(done) {
+ Post.create({title: 'a', content: 'AAA'}, function(err, post) {
post.title = 'b';
- post.save(function (err, p) {
+ post.save(function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
- Post.findById(post.id, function (err, p) {
+ Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
@@ -173,19 +247,18 @@ describe('mysql', function () {
done();
});
});
-
});
});
- it('save should update the instance without removing existing properties', function (done) {
- Post.create({title: 'a', content: 'AAA'}, function (err, post) {
+ it('save should update the instance without removing existing properties', function(done) {
+ Post.create({title: 'a', content: 'AAA'}, function(err, post) {
delete post.title;
- post.save(function (err, p) {
+ post.save(function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
- Post.findById(post.id, function (err, p) {
+ Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
@@ -194,19 +267,18 @@ describe('mysql', function () {
done();
});
});
-
});
});
- it('save should create a new instance if it does not exist', function (done) {
+ it('save should create a new instance if it does not exist', function(done) {
var post = new Post({id: 123, title: 'a', content: 'AAA'});
- post.save(post, function (err, p) {
+ post.save(post, function(err, p) {
should.not.exist(err);
p.title.should.be.equal(post.title);
p.content.should.be.equal(post.content);
p.id.should.be.equal(post.id);
- Post.findById(p.id, function (err, p) {
+ Post.findById(p.id, function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
@@ -217,13 +289,12 @@ describe('mysql', function () {
done();
});
});
-
});
- it('all return should honor filter.fields', function (done) {
- var post = new Post({title: 'b', content: 'BBB'})
- post.save(function (err, post) {
- Post.all({fields: ['title'], where: {title: 'b'}}, function (err, posts) {
+ it('all return should honor filter.fields', function(done) {
+ var post = new Post({title: 'b', content: 'BBB'});
+ post.save(function(err, post) {
+ Post.all({fields: ['title'], where: {title: 'b'}}, function(err, posts) {
should.not.exist(err);
posts.should.have.lengthOf(1);
post = posts[0];
@@ -233,25 +304,24 @@ describe('mysql', function () {
done();
});
-
});
});
it('find should order by id if the order is not set for the query filter',
- function (done) {
- PostWithStringId.create({id: '2', title: 'c', content: 'CCC'}, function (err, post) {
- PostWithStringId.create({id: '1', title: 'd', content: 'DDD'}, function (err, post) {
- PostWithStringId.find(function (err, posts) {
+ function(done) {
+ PostWithStringId.create({id: '2', title: 'c', content: 'CCC'}, function(err, post) {
+ PostWithStringId.create({id: '1', title: 'd', content: 'DDD'}, function(err, post) {
+ PostWithStringId.find(function(err, posts) {
should.not.exist(err);
posts.length.should.be.equal(2);
posts[0].id.should.be.equal('1');
- PostWithStringId.find({limit: 1, offset: 0}, function (err, posts) {
+ PostWithStringId.find({limit: 1, offset: 0}, function(err, posts) {
should.not.exist(err);
posts.length.should.be.equal(1);
posts[0].id.should.be.equal('1');
- PostWithStringId.find({limit: 1, offset: 1}, function (err, posts) {
+ PostWithStringId.find({limit: 1, offset: 1}, function(err, posts) {
should.not.exist(err);
posts.length.should.be.equal(1);
posts[0].id.should.be.equal('2');
@@ -263,9 +333,9 @@ describe('mysql', function () {
});
});
- it('should allow to find using like', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
- Post.find({where: {title: {like: 'M%st'}}}, function (err, posts) {
+ it('should allow to find using like', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
+ Post.find({where: {title: {like: 'M%st'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
@@ -273,9 +343,9 @@ describe('mysql', function () {
});
});
- it('should support like for no match', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
- Post.find({where: {title: {like: 'M%XY'}}}, function (err, posts) {
+ it('should support like for no match', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
+ Post.find({where: {title: {like: 'M%XY'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -283,9 +353,9 @@ describe('mysql', function () {
});
});
- it('should allow to find using nlike', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
- Post.find({where: {title: {nlike: 'M%st'}}}, function (err, posts) {
+ it('should allow to find using nlike', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
+ Post.find({where: {title: {nlike: 'M%st'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -293,9 +363,9 @@ describe('mysql', function () {
});
});
- it('should support nlike for no match', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
- Post.find({where: {title: {nlike: 'M%XY'}}}, function (err, posts) {
+ it('should support nlike for no match', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
+ Post.find({where: {title: {nlike: 'M%XY'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
@@ -303,12 +373,12 @@ describe('mysql', function () {
});
});
- it('should support "and" operator that is satisfied', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
+ it('should support "and" operator that is satisfied', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {and: [
{title: 'My Post'},
- {content: 'Hello'}
- ]}}, function (err, posts) {
+ {content: 'Hello'},
+ ]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
@@ -316,12 +386,12 @@ describe('mysql', function () {
});
});
- it('should support "and" operator that is not satisfied', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
+ it('should support "and" operator that is not satisfied', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {and: [
{title: 'My Post'},
- {content: 'Hello1'}
- ]}}, function (err, posts) {
+ {content: 'Hello1'},
+ ]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -329,12 +399,12 @@ describe('mysql', function () {
});
});
- it('should support "or" that is satisfied', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
+ it('should support "or" that is satisfied', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {or: [
{title: 'My Post'},
- {content: 'Hello1'}
- ]}}, function (err, posts) {
+ {content: 'Hello1'},
+ ]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
@@ -342,12 +412,12 @@ describe('mysql', function () {
});
});
- it('should support "or" operator that is not satisfied', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
+ it('should support "or" operator that is not satisfied', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {or: [
{title: 'My Post1'},
- {content: 'Hello1'}
- ]}}, function (err, posts) {
+ {content: 'Hello1'},
+ ]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -356,18 +426,18 @@ describe('mysql', function () {
});
// The where object should be parsed by the connector
- it('should support where for count', function (done) {
- Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
+ it('should support where for count', function(done) {
+ Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.count({and: [
{title: 'My Post'},
- {content: 'Hello'}
- ]}, function (err, count) {
+ {content: 'Hello'},
+ ]}, function(err, count) {
should.not.exist(err);
count.should.be.equal(1);
Post.count({and: [
{title: 'My Post1'},
- {content: 'Hello'}
- ]}, function (err, count) {
+ {content: 'Hello'},
+ ]}, function(err, count) {
should.not.exist(err);
count.should.be.equal(0);
done();
@@ -377,15 +447,15 @@ describe('mysql', function () {
});
// The where object should be parsed by the connector
- it('should support where for destroyAll', function (done) {
- Post.create({title: 'My Post1', content: 'Hello'}, function (err, post) {
- Post.create({title: 'My Post2', content: 'Hello'}, function (err, post) {
+ it('should support where for destroyAll', function(done) {
+ Post.create({title: 'My Post1', content: 'Hello'}, function(err, post) {
+ Post.create({title: 'My Post2', content: 'Hello'}, function(err, post) {
Post.destroyAll({and: [
{title: 'My Post1'},
- {content: 'Hello'}
- ]}, function (err) {
+ {content: 'Hello'},
+ ]}, function(err) {
should.not.exist(err);
- Post.count(function (err, count) {
+ Post.count(function(err, count) {
should.not.exist(err);
count.should.be.equal(1);
done();
@@ -395,13 +465,13 @@ describe('mysql', function () {
});
});
- it('should not allow SQL injection for inq operator', function (done) {
+ it('should not allow SQL injection for inq operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
- function (err, post) {
+ function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
- function (err, post) {
+ function(err, post) {
Post.find({where: {title: {inq: ['SELECT title from PostWithDefaultId']}}},
- function (err, posts) {
+ function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -410,13 +480,13 @@ describe('mysql', function () {
});
});
- it('should not allow SQL injection for lt operator', function (done) {
+ it('should not allow SQL injection for lt operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
- function (err, post) {
+ function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
- function (err, post) {
+ function(err, post) {
Post.find({where: {stars: {lt: 'SELECT title from PostWithDefaultId'}}},
- function (err, posts) {
+ function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -425,13 +495,13 @@ describe('mysql', function () {
});
});
- it('should not allow SQL injection for nin operator', function (done) {
+ it('should not allow SQL injection for nin operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
- function (err, post) {
+ function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
- function (err, post) {
+ function(err, post) {
Post.find({where: {title: {nin: ['SELECT title from PostWithDefaultId']}}},
- function (err, posts) {
+ function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 2);
done();
@@ -440,14 +510,13 @@ describe('mysql', function () {
});
});
-
- it('should not allow SQL injection for inq operator with number column', function (done) {
+ it('should not allow SQL injection for inq operator with number column', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
- function (err, post) {
+ function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
- function (err, post) {
+ function(err, post) {
Post.find({where: {stars: {inq: ['SELECT title from PostWithDefaultId']}}},
- function (err, posts) {
+ function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -456,13 +525,13 @@ describe('mysql', function () {
});
});
- it('should not allow SQL injection for inq operator with array value', function (done) {
+ it('should not allow SQL injection for inq operator with array value', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
- function (err, post) {
+ function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
- function (err, post) {
+ function(err, post) {
Post.find({where: {stars: {inq: [5, 'SELECT title from PostWithDefaultId']}}},
- function (err, posts) {
+ function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
@@ -471,13 +540,13 @@ describe('mysql', function () {
});
});
- it('should not allow SQL injection for between operator', function (done) {
+ it('should not allow SQL injection for between operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
- function (err, post) {
+ function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
- function (err, post) {
+ function(err, post) {
Post.find({where: {stars: {between: [5, 'SELECT title from PostWithDefaultId']}}},
- function (err, posts) {
+ function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
@@ -486,11 +555,11 @@ describe('mysql', function () {
});
});
- it('should not allow duplicate titles', function (done) {
+ it('should not allow duplicate titles', function(done) {
var data = {title: 'a', content: 'AAA'};
- PostWithUniqueTitle.create(data, function (err, post) {
+ PostWithUniqueTitle.create(data, function(err, post) {
should.not.exist(err);
- PostWithUniqueTitle.create(data, function (err, post) {
+ PostWithUniqueTitle.create(data, function(err, post) {
should.exist(err);
done();
});
@@ -504,7 +573,7 @@ describe('mysql', function () {
beforeEach(function createTestFixtures(done) {
Post.create([
{title: 'a', content: 'AAA'},
- {title: 'b', content: 'BBB'}
+ {title: 'b', content: 'BBB'},
], done);
});
after(function deleteTestFixtures(done) {
@@ -542,27 +611,27 @@ describe('mysql', function () {
it('should print a warning when the ignore flag is set',
function(done) {
- Post.find({where: {content: {regexp: '^a/i'}}}, function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ Post.find({where: {content: {regexp: '^a/i'}}}, function(err, posts) {
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
it('should print a warning when the global flag is set',
function(done) {
- Post.find({where: {content: {regexp: '^a/g'}}}, function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ Post.find({where: {content: {regexp: '^a/g'}}}, function(err, posts) {
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
it('should print a warning when the multiline flag is set',
function(done) {
- Post.find({where: {content: {regexp: '^a/m'}}}, function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ Post.find({where: {content: {regexp: '^a/m'}}}, function(err, posts) {
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
});
});
@@ -597,27 +666,27 @@ describe('mysql', function () {
it('should print a warning when the ignore flag is set',
function(done) {
- Post.find({where: {content: {regexp: /^a/i}}}, function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ Post.find({where: {content: {regexp: /^a/i}}}, function(err, posts) {
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
it('should print a warning when the global flag is set',
function(done) {
- Post.find({where: {content: {regexp: /^a/g}}}, function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ Post.find({where: {content: {regexp: /^a/g}}}, function(err, posts) {
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
it('should print a warning when the multiline flag is set',
function(done) {
- Post.find({where: {content: {regexp: /^a/m}}}, function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ Post.find({where: {content: {regexp: /^a/m}}}, function(err, posts) {
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
});
});
@@ -633,11 +702,11 @@ describe('mysql', function () {
it('should work', function(done) {
Post.find({where: {content: {regexp: new RegExp(/^A/)}}},
function(err, posts) {
- should.not.exist(err);
- posts.length.should.equal(1);
- posts[0].content.should.equal('AAA');
- done();
- });
+ should.not.exist(err);
+ posts.length.should.equal(1);
+ posts[0].content.should.equal('AAA');
+ done();
+ });
});
});
@@ -645,45 +714,45 @@ describe('mysql', function () {
it('should work', function(done) {
Post.find({where: {content: {regexp: new RegExp(/^a/i)}}},
function(err, posts) {
- should.not.exist(err);
- posts.length.should.equal(1);
- posts[0].content.should.equal('AAA');
- done();
- });
+ should.not.exist(err);
+ posts.length.should.equal(1);
+ posts[0].content.should.equal('AAA');
+ done();
+ });
});
it('should print a warning when the ignore flag is set',
function(done) {
- Post.find({where: {content: {regexp: new RegExp(/^a/i)}}},
+ Post.find({where: {content: {regexp: new RegExp(/^a/i)}}},
function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
it('should print a warning when the global flag is set',
function(done) {
- Post.find({where: {content: {regexp: new RegExp(/^a/g)}}},
+ Post.find({where: {content: {regexp: new RegExp(/^a/g)}}},
function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
it('should print a warning when the multiline flag is set',
function(done) {
- Post.find({where: {content: {regexp: new RegExp(/^a/m)}}},
+ Post.find({where: {content: {regexp: new RegExp(/^a/m)}}},
function(err, posts) {
- console.warn.calledOnce.should.be.ok;
- done();
- });
- });
+ console.warn.calledOnce.should.be.ok;
+ done();
+ });
+ });
});
});
});
- after(function (done) {
- Post.destroyAll(function () {
- PostWithStringId.destroyAll(function () {
+ after(function(done) {
+ Post.destroyAll(function() {
+ PostWithStringId.destroyAll(function() {
PostWithUniqueTitle.destroyAll(done);
});
});
diff --git a/test/persistence-hooks.test.js b/test/persistence-hooks.test.js
index 26fe81ef..ddcb28be 100644
--- a/test/persistence-hooks.test.js
+++ b/test/persistence-hooks.test.js
@@ -1,4 +1,12 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var should = require('./init');
var suite = require('loopback-datasource-juggler/test/persistence-hooks.suite.js');
-suite(global.getDataSource(), should);
+suite(global.getDataSource(), should, {
+ replaceOrCreateReportsNewInstance: true,
+});
diff --git a/test/schema.sql b/test/schema.sql
new file mode 100644
index 00000000..3f10afbc
--- /dev/null
+++ b/test/schema.sql
@@ -0,0 +1,225 @@
+-- MySQL dump 10.13 Distrib 5.7.14, for osx10.10 (x86_64)
+--
+-- Host: 166.78.158.45 Database: STRONGLOOP
+-- ------------------------------------------------------
+-- Server version 5.1.69
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Current Database: `STRONGLOOP`
+--
+
+/*!40000 DROP DATABASE IF EXISTS `STRONGLOOP`*/;
+
+CREATE DATABASE /*!32312 IF NOT EXISTS*/ `STRONGLOOP` /*!40100 DEFAULT CHARACTER SET utf8 */;
+
+USE `STRONGLOOP`;
+
+--
+-- Table structure for table `CUSTOMER`
+--
+
+DROP TABLE IF EXISTS `CUSTOMER`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `CUSTOMER` (
+ `ID` varchar(20) NOT NULL,
+ `NAME` varchar(40) DEFAULT NULL,
+ `MILITARY_AGENCY` varchar(20) DEFAULT NULL,
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `CUSTOMER`
+--
+
+LOCK TABLES `CUSTOMER` WRITE;
+/*!40000 ALTER TABLE `CUSTOMER` DISABLE KEYS */;
+/*!40000 ALTER TABLE `CUSTOMER` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `INVENTORY`
+--
+
+DROP TABLE IF EXISTS `INVENTORY`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `INVENTORY` (
+ `PRODUCT_ID` varchar(20) NOT NULL,
+ `LOCATION_ID` varchar(20) NOT NULL,
+ `AVAILABLE` int(11) DEFAULT NULL,
+ `TOTAL` int(11) DEFAULT NULL,
+ `ACTIVE` BOOLEAN DEFAULT TRUE,
+ `DISABLED` BIT(1) DEFAULT 0,
+ `ENABLED` CHAR(1) DEFAULT 'Y',
+ PRIMARY KEY (`PRODUCT_ID`,`LOCATION_ID`),
+ KEY `LOCATION_FK` (`LOCATION_ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `INVENTORY`
+--
+
+LOCK TABLES `INVENTORY` WRITE;
+/*!40000 ALTER TABLE `INVENTORY` DISABLE KEYS */;
+/*!40000 ALTER TABLE `INVENTORY` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Temporary view structure for view `INVENTORY_VIEW`
+--
+
+DROP TABLE IF EXISTS `INVENTORY_VIEW`;
+/*!50001 DROP VIEW IF EXISTS `INVENTORY_VIEW`*/;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `INVENTORY_VIEW` AS SELECT
+ 1 AS `ID`,
+ 1 AS `PRODUCT_ID`,
+ 1 AS `PRODUCT_NAME`,
+ 1 AS `AUDIBLE_RANGE`,
+ 1 AS `EFFECTIVE_RANGE`,
+ 1 AS `ROUNDS`,
+ 1 AS `EXTRAS`,
+ 1 AS `FIRE_MODES`,
+ 1 AS `LOCATION_ID`,
+ 1 AS `LOCATION`,
+ 1 AS `CITY`,
+ 1 AS `ZIPCODE`,
+ 1 AS `AVAILABLE`*/;
+SET character_set_client = @saved_cs_client;
+
+--
+-- Table structure for table `LOCATION`
+--
+
+DROP TABLE IF EXISTS `LOCATION`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `LOCATION` (
+ `ID` varchar(20) NOT NULL,
+ `STREET` varchar(20) DEFAULT NULL,
+ `CITY` varchar(20) DEFAULT NULL,
+ `ZIPCODE` varchar(20) DEFAULT NULL,
+ `NAME` varchar(20) DEFAULT NULL,
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `LOCATION`
+--
+
+LOCK TABLES `LOCATION` WRITE;
+/*!40000 ALTER TABLE `LOCATION` DISABLE KEYS */;
+/*!40000 ALTER TABLE `LOCATION` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `PRODUCT`
+--
+
+DROP TABLE IF EXISTS `PRODUCT`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `PRODUCT` (
+ `ID` varchar(20) NOT NULL,
+ `NAME` varchar(64) DEFAULT NULL,
+ `AUDIBLE_RANGE` decimal(12,2) DEFAULT NULL,
+ `EFFECTIVE_RANGE` decimal(12,2) DEFAULT NULL,
+ `ROUNDS` decimal(10,0) DEFAULT NULL,
+ `EXTRAS` varchar(64) DEFAULT NULL,
+ `FIRE_MODES` varchar(64) DEFAULT NULL,
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `PRODUCT`
+--
+
+LOCK TABLES `PRODUCT` WRITE;
+/*!40000 ALTER TABLE `PRODUCT` DISABLE KEYS */;
+/*!40000 ALTER TABLE `PRODUCT` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `RESERVATION`
+--
+
+DROP TABLE IF EXISTS `RESERVATION`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `RESERVATION` (
+ `ID` varchar(20) NOT NULL,
+ `PRODUCT_ID` varchar(20) NOT NULL,
+ `LOCATION_ID` varchar(20) NOT NULL,
+ `CUSTOMER_ID` varchar(20) NOT NULL,
+ `QTY` int(11) DEFAULT NULL,
+ `STATUS` varchar(20) DEFAULT NULL,
+ `RESERVE_DATE` date DEFAULT NULL,
+ `PICKUP_DATE` date DEFAULT NULL,
+ `RETURN_DATE` date DEFAULT NULL,
+ PRIMARY KEY (`ID`),
+ KEY `RESERVATION_PRODUCT_FK` (`PRODUCT_ID`),
+ KEY `RESERVATION_LOCATION_FK` (`LOCATION_ID`),
+ KEY `RESERVATION_CUSTOMER_FK` (`CUSTOMER_ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `RESERVATION`
+--
+
+LOCK TABLES `RESERVATION` WRITE;
+/*!40000 ALTER TABLE `RESERVATION` DISABLE KEYS */;
+/*!40000 ALTER TABLE `RESERVATION` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Current Database: `STRONGLOOP`
+--
+
+USE `STRONGLOOP`;
+
+--
+-- Final view structure for view `INVENTORY_VIEW`
+--
+
+/*!50001 DROP VIEW IF EXISTS `INVENTORY_VIEW`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`strongloop`@`%` SQL SECURITY DEFINER */
+/*!50001 VIEW `INVENTORY_VIEW` AS select concat(concat(`P`.`ID`,':'),`L`.`ID`) AS `ID`,`P`.`ID` AS `PRODUCT_ID`,`P`.`NAME` AS `PRODUCT_NAME`,`P`.`AUDIBLE_RANGE` AS `AUDIBLE_RANGE`,`P`.`EFFECTIVE_RANGE` AS `EFFECTIVE_RANGE`,`P`.`ROUNDS` AS `ROUNDS`,`P`.`EXTRAS` AS `EXTRAS`,`P`.`FIRE_MODES` AS `FIRE_MODES`,`L`.`ID` AS `LOCATION_ID`,`L`.`NAME` AS `LOCATION`,`L`.`CITY` AS `CITY`,`L`.`ZIPCODE` AS `ZIPCODE`,`I`.`AVAILABLE` AS `AVAILABLE` from ((`INVENTORY` `I` join `PRODUCT` `P`) join `LOCATION` `L`) where ((`P`.`ID` = `I`.`PRODUCT_ID`) and (`L`.`ID` = `I`.`LOCATION_ID`)) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2016-08-09 19:14:01
diff --git a/test/transaction.promise.test.js b/test/transaction.promise.test.js
index 0f169277..127cbe4d 100644
--- a/test/transaction.promise.test.js
+++ b/test/transaction.promise.test.js
@@ -1,3 +1,9 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
if (typeof Promise === 'undefined') {
global.Promise = require('bluebird');
}
@@ -8,17 +14,16 @@ require('should');
var db, Post, Review;
describe('transactions with promise', function() {
-
before(function(done) {
db = getDataSource({collation: 'utf8_general_ci', createDatabase: true});
db.once('connected', function() {
Post = db.define('PostTX', {
title: {type: String, length: 255, index: true},
- content: {type: String}
+ content: {type: String},
}, {mysql: {engine: 'INNODB'}});
Review = db.define('ReviewTX', {
author: String,
- content: {type: String}
+ content: {type: String},
}, {mysql: {engine: 'INNODB'}});
Post.hasMany(Review, {as: 'reviews', foreignKey: 'postId'});
db.automigrate(['PostTX', 'ReviewTX'], done);
@@ -33,7 +38,7 @@ describe('transactions with promise', function() {
// Transaction.begin(db.connector, Transaction.READ_COMMITTED,
var promise = Post.beginTransaction({
isolationLevel: Transaction.READ_COMMITTED,
- timeout: timeout
+ timeout: timeout,
});
promise.then(function(tx) {
(typeof tx.id).should.be.eql('string');
@@ -60,7 +65,7 @@ describe('transactions with promise', function() {
function(p) {
p.reviews.create({
author: 'John',
- content: 'Review for ' + p.title
+ content: 'Review for ' + p.title,
}, {transaction: currentTx}).then(
function(c) {
done(null, c);
@@ -97,7 +102,6 @@ describe('transactions with promise', function() {
}
describe('commit', function() {
-
var post = {title: 't1', content: 'c1'};
before(createPostInTx(post));
@@ -124,7 +128,6 @@ describe('transactions with promise', function() {
});
describe('rollback', function() {
-
var post = {title: 't2', content: 'c2'};
before(createPostInTx(post));
@@ -151,7 +154,6 @@ describe('transactions with promise', function() {
});
describe('timeout', function() {
-
var post = {title: 't3', content: 'c3'};
before(createPostInTx(post, 500));
@@ -171,7 +173,5 @@ describe('transactions with promise', function() {
done();
});
});
-
});
});
-
diff --git a/test/transaction.test.js b/test/transaction.test.js
index a1667a40..b4f9d9b7 100644
--- a/test/transaction.test.js
+++ b/test/transaction.test.js
@@ -1,3 +1,9 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: loopback-connector-mysql
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
var Transaction = require('loopback-datasource-juggler').Transaction;
require('./init.js');
require('should');
@@ -5,17 +11,16 @@ require('should');
var db, Post, Review;
describe('transactions', function() {
-
before(function(done) {
db = getDataSource({collation: 'utf8_general_ci', createDatabase: true});
db.once('connected', function() {
Post = db.define('PostTX', {
title: {type: String, length: 255, index: true},
- content: {type: String}
+ content: {type: String},
}, {mysql: {engine: 'INNODB'}});
Review = db.define('ReviewTX', {
author: String,
- content: {type: String}
+ content: {type: String},
}, {mysql: {engine: 'INNODB'}});
Post.hasMany(Review, {as: 'reviews', foreignKey: 'postId'});
db.automigrate(['PostTX', 'ReviewTX'], done);
@@ -29,9 +34,9 @@ describe('transactions', function() {
return function(done) {
// Transaction.begin(db.connector, Transaction.READ_COMMITTED,
Post.beginTransaction({
- isolationLevel: Transaction.READ_COMMITTED,
- timeout: timeout
- },
+ isolationLevel: Transaction.READ_COMMITTED,
+ timeout: timeout,
+ },
function(err, tx) {
if (err) return done(err);
(typeof tx.id).should.be.eql('string');
@@ -59,9 +64,9 @@ describe('transactions', function() {
done(err);
} else {
p.reviews.create({
- author: 'John',
- content: 'Review for ' + p.title
- }, {transaction: tx},
+ author: 'John',
+ content: 'Review for ' + p.title,
+ }, {transaction: tx},
function(err, c) {
done(err);
});
@@ -100,7 +105,6 @@ describe('transactions', function() {
}
describe('commit', function() {
-
var post = {title: 't1', content: 'c1'};
before(createPostInTx(post));
@@ -127,7 +131,6 @@ describe('transactions', function() {
});
describe('rollback', function() {
-
var post = {title: 't2', content: 'c2'};
before(createPostInTx(post));
@@ -154,7 +157,6 @@ describe('transactions', function() {
});
describe('timeout', function() {
-
var post = {title: 't3', content: 'c3'};
before(createPostInTx(post, 500));
@@ -174,7 +176,5 @@ describe('transactions', function() {
done();
});
});
-
});
});
-