diff --git a/README.md b/README.md
index c7a9014..bb13891 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,8 @@
# loopback-connector-mysql
-[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.
-
-
+[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.
## Installation
@@ -15,15 +12,48 @@ In your application root directory, enter this command to install the connector:
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.
+**Note**: The MySQL connector requires MySQL 5.0+.
-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.
+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.
+For LoopBack 4 users, use the LoopBack 4
+[Command-line interface](https://loopback.io/doc/en/lb4/Command-line-interface.html)
+to generate a DataSource with MySQL connector to your LB4 application. Run
+[`lb4 datasource`](https://loopback.io/doc/en/lb4/DataSource-generator.html), it
+will prompt for configurations such as host, post, etc. that are required to
+connect to a MySQL database.
+
+After setting it up, the configuration can be found under
+`src/datasources/.datasource.ts`, which would look like this:
+
+```ts
+const config = {
+ name: 'db',
+ connector: 'mysql',
+ url: '',
+ host: 'localhost',
+ port: 3306,
+ user: 'user',
+ password: 'pass',
+ database: 'testdb',
+};
+```
+
+For LoopBack 3 users
+
+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:
@@ -39,7 +69,10 @@ The entry in the application's `/server/datasources.json` will look like this:
}
```
-Edit `datasources.json` to add any other additional properties that you require.
+
+
+Edit `.datasources.ts` to add any other additional properties
+that you require.
### Properties
@@ -120,11 +153,13 @@ Edit `datasources.json` to add any other additional properties that you require.
-**NOTE**: In addition to these properties, you can use additional parameters supported by [`node-mysql`](https://github.com/felixge/node-mysql).
+**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.
+See [LoopBack types](http://loopback.io/doc/en/lb3/LoopBack-types.html) for
+details on LoopBack's data types.
### LoopBack to MySQL types
@@ -209,127 +244,149 @@ See [LoopBack types](http://loopback.io/doc/en/lb3/LoopBack-types.html) for de
_NOTE_ as of v3.0.0 of MySQL Connector, the following flags were introduced:
-- `treatCHAR1AsString`
- default `false` - treats CHAR(1) as a String instead of a Boolean
-- `treatBIT1AsBit`
- default `true` - treats BIT(1) as a Boolean instead of a Binary
-- `treatTINYINT1AsTinyInt`
- default `true` - treats TINYINT(1) as a Boolean instead of a Number
+- `treatCHAR1AsString` default `false` - treats CHAR(1) as a String instead of a
+ Boolean
+- `treatBIT1AsBit` default `true` - treats BIT(1) as a Boolean instead of a
+ Binary
+- `treatTINYINT1AsTinyInt` default `true` - treats TINYINT(1) as a Boolean
+ instead of a Number
-## Using the datatype field/column option with MySQL
+## Data mapping properties
-Use the `mysql` model property to specify additional MySQL-specific properties for a LoopBack model.
+### Table/Column Names
-For example:
+Besides the basic LoopBack types, as we introduced above, you can also specify
+additional MySQL-specific properties for a LoopBack model. It would be mapped to
+the database.
-{% include code-caption.html content="/common/models/model.json" %}
+Use the `mysql.` in the model definition or the property definition to
+configure the table/column definition.
+
+For example, the following settings would allow you to have custom table name
+(`Custom_User`) and column name (`custom_id` and `custom_name`). Such mapping is
+useful when you'd like to have different table/column names from the model:
+
+{% include code-caption.html content="user.model.ts" %}
+
+```ts
+@model({
+ settings: { mysql: { schema: 'testdb', table: 'Custom_User'} },
+})
+export class User extends Entity {
+ @property({
+ type: 'number',
+ required: true,
+ id: true,
+ mysql: {
+ columnName: 'custom_id',
+ },
+ })
+ id: number;
+
+ @property({
+ type: 'string',
+ mysql: {
+ columnName: 'custom_name',
+ },
+ })
+ name?: string;
+```
+
+For LoopBack 3 users
```javascript
-"locationId":{
- "type":"String",
- "required":true,
- "length":20,
- "mysql":
- {
- "columnName":"LOCATION_ID",
- "dataType":"VARCHAR",
- "dataLength":20,
- "nullable":"N"
+{
+ "name": "User",
+ "options": {
+ "mysql": {
+ "schema": "testdb",
+ "table": "Custom_User"
}
+ },
+ "properties": {
+ "id": {
+ "type": "Number",
+ "required": true,
+ "mysql": {
+ "columnName": "custom_id",
+ }
+ },
+ "name": {
+ "type": "String",
+ "mysql": {
+ "columnName": "custom_name",
+ }
+ },
+ }
}
```
-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
+### Numeric Types
+
+Except the names, you can also use the dataType column/property attribute to
+specify what MySQL column type to use. The following MySQL type-dataType
+combinations are supported:
+
+- number
- integer
- tinyint
- smallint
- mediumint
- int
- bigint
+- float
+- double
+- decimal
-Use the `limit` option to alter the display width. Example:
+The following examples will be in LoopBack 4 style, but it's the same if you
+provide `mysql.` to the LB3 property definition.
-```javascript
-{ userName : {
- type: String,
- dataType: 'char',
- limit: 24
- }
-}
-```
+#### Floating-point types
-### Default Clause/Constant
+For Float and Double data types, use the `precision` and `scale` options to
+specify custom precision. Default is (16,8).
-Use the `default` property to have MySQL handle setting column `DEFAULT` value.
+Example
-```javascript
-"status": {
- "type": "string",
- "mysql": {
- "default": "pending"
- }
-},
-"number": {
- "type": "number",
- "mysql": {
- "default": 256
- }
-}
-```
-
-For the date or timestamp types use `CURRENT_TIMESTAMP` or `now`:
-
-```javascript
-"last_modified": {
- "type": "date",
- "mysql": {
- "default":"CURRENT_TIMESTAMP"
- }
-}
-```
-
-**NOTE**: The following column types do **NOT** supported [MySQL Default Values](https://dev.mysql.com/doc/refman/5.7/en/data-type-defaults.html):
-
-- BLOB
-- TEXT
-- GEOMETRY
-- JSON
-
-### 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,
+```ts
+@property({
+ type: 'Number',
+ mysql: {
dataType: 'float',
precision: 20,
scale: 4
}
-}
+})
+price: Number;
```
-### 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.
+#### Fixed-point exact value types
-Example:
+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.
-```javascript
-{ stdDev :
- { type: Number,
+Example
+
+```ts
+@property({
+ type: 'Number',
+ mysql: {
dataType: 'decimal',
precision: 12,
scale: 8
}
-}
+})
+price: Number;
```
-### Other types
+
+
+### Text types
Convert String / DataSource.Text / DataSource.JSON to the following MySQL types:
@@ -340,190 +397,447 @@ Convert String / DataSource.Text / DataSource.JSON to the following MySQL types:
- tinytext
- longtext
-Example:
+Example
-```javascript
-{ userName :
- { type: String,
+```ts
+@property({
+ type: 'String',
+ mysql: {
dataType: 'char',
- limit: 24
- }
-}
+ dataLength: 24 // limits the property length
+ },
+})
+userName: String;
```
-Example:
+
-```javascript
-{ biography :
- { type: String,
- dataType: 'longtext'
- }
-}
+### Dat types
+
+Convert JSON Date types to datetime or timestamp.
+
+Example
+
+```ts
+@property({
+ type: 'Date',
+ mysql: {
+ dataType: 'timestamp',
+ },
+})
+startTime: Date;
```
-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'
+```ts
+const 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 }}
+
+export class User extends Entity {
+ //..
+ @property({
+ type: MOOD,
+ })
+ mood: MOOD;
+}
```
+### Default Clause/Constant
+
+Use the `default` property to have MySQL handle setting column `DEFAULT` value.
+
+Example
+
+```ts
+@property({
+ type: 'String',
+ mysql: {
+ default: 'pending'
+ }
+})
+status: String;
+
+@property({
+ type: 'Number',
+ mysql: {
+ default: 42
+ }
+})
+maxDays: Number;
+```
+
+
+
+For the date or timestamp types use `CURRENT_TIMESTAMP` or `now`.
+
+Example
+
+```ts
+@property({
+ type: 'Date',
+ mysql: {
+ default: 'CURRENT_TIMESTAMP'
+ }
+})
+last_modified: Date;
+```
+
+
+
+**NOTE**: The following column types do **NOT** supported
+[MySQL Default Values](https://dev.mysql.com/doc/refman/5.7/en/data-type-defaults.html):
+
+- BLOB
+- TEXT
+- GEOMETRY
+- JSON
+
## 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).
+The MySQL connector supports _model discovery_ that enables you to create
+LoopBack models based on an existing database schema. Once you defined your
+datasource:
+
+- LoopBack 4 users could use the commend
+ [`lb4 discover`](https://loopback.io/doc/en/lb4/Discovering-models.html) to
+ discover models.
+- For LB3 users, please check
+ [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html).
+ (See
+ [database discovery API](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-discoverandbuildmodels)
+ for related APIs information)
### Auto-migration
-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).
+The MySQL connector also supports _auto-migration_ that enables you to create a
+database schema from LoopBack models. For example, based on the following model,
+the auto-migration method would create/alter existing `Customer` table in the
+database. Table `Customer` would have two columns: `name` and `id`, where `id`
+is also the primary key that has `auto_increment` set as it has definition of
+`type: 'Number'` and `generated: true`:
-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.
+```ts
+@model()
+export class Customer extends Entity {
+ @property({
+ id: true,
+ type: 'Number',
+ generated: true,
+ })
+ id: number;
+
+ @property({
+ type: 'string',
+ })
+ name: string;
+}
+```
+
+Moreover, additional MySQL-specific properties mentioned in the
+[Data mapping properties](#data-mapping-properties) section work with
+auto-migration as well.
+
+#### Auto-generated ids
+
+For now LoopBack MySQL connector only supports auto-generated id
+(`generated: true`) for integer type as for MySQL, the default id type is
+_integer_. If you'd like to use other types such as string (uuid) as the id
+type, you can:
+
+- use uuid that is **generated by your LB application** by setting
+ [`defaultFn: uuid`](https://loopback.io/doc/en/lb4/Model.html#property-decorator).
+
+```ts
+ @property({
+ id: true,
+ type: 'string'
+ defaultFn: 'uuidv4',
+ // generated: true, -> not needed
+ })
+ id: string;
+```
+
+- Alter the table in your database to use a certain function if you prefer
+ having **the database to generate the value**.
+
+```ts
+ @property({
+ id: true,
+ type: 'string'
+ generated: true, // to indicate the value generates by the db
+ useDefaultIdType: false, // needed
+ })
+ id: string;
+```
#### Auto-migrate/Auto-update models with foreign keys
-MySQL handles the foreign key integrity of the related models upon auto-migrate or auto-update operation. It first deletes any related models before calling delete on the models with the relationship.
+Foreign key constraints can be defined in the model definition.
-Example:
+**Note**: The order of table creation is important. A referenced table must
+exist before creating a foreign key constraint.
+
+Define your models and the foreign key constraints as follows:
+
+{% include code-caption.html content="customer.model.ts" %}
+
+```ts
+@model()
+export class Customer extends Entity {
+ @property({
+ id: true,
+ type: 'Number',
+ generated: true,
+ })
+ id: number;
+
+ @property({
+ type: 'string',
+ })
+ name: string;
+}
+```
+
+`order.model.ts`:
+
+```ts
+@model({
+ settings: {
+ foreignKeys: {
+ fk_order_customerId: {
+ name: 'fk_order_customerId',
+ entity: 'Customer',
+ entityKey: 'id',
+ foreignKey: 'customerId',
+ },
+ },
+ })
+export class Order extends Entity {
+ @property({
+ id: true,
+ type: 'Number',
+ generated: true
+ })
+ id: number;
+
+ @property({
+ type: 'string'
+ })
+ name: string;
+
+ @property({
+ type: 'Number'
+ })
+ customerId: number;
+}
+```
+
+For LoopBack 3 users
+
+```json
+({
+ "name": "Customer",
+ "options": {
+ "idInjection": false
+ },
+ "properties": {
+ "id": {
+ "type": "Number",
+ "id": 1
+ },
+ "name": {
+ "type": "String",
+ "required": false
+ }
+ }
+},
+{
+ "name": "Order",
+ "options": {
+ "idInjection": false,
+ "foreignKeys": {
+ "fk_order_customerId": {
+ "name": "fk_order_customerId",
+ "entity": "Customer",
+ "entityKey": "id",
+ "foreignKey": "customerId"
+ }
+ }
+ },
+ "properties": {
+ "id": {
+ "type": "Number"
+ "id": 1
+ },
+ "customerId": {
+ "type": "Number"
+ },
+ "description": {
+ "type": "String",
+ "required": false
+ }
+ }
+})
+```
+
+
+
+MySQL handles the foreign key integrity by the referential action specified by
+`ON UPDATE` and `ON DELETE`. You can specify which referential actions the
+foreign key follows in the model definition upon auto-migrate or auto-update
+operation. Both `onDelete` and `onUpdate` default to `restrict`.
+
+Take the example we showed above, let's add the referential action to the
+foreign key `customerId`:
+
+```ts
+@model({
+ settings: {
+ foreignKeys: {
+ fk_order_customerId: {
+ name: 'fk_order_customerId',
+ entity: 'Customer',
+ entityKey: 'id',
+ foreignKey: 'customerId',
+ onUpdate: 'restrict', // restrict|cascade|set null|no action|set default
+ onDelete: 'cascade' // restrict|cascade|set null|no action|set default
+ },
+ },
+ })
+export class Order extends Entity {
+...
+```
+
+For LoopBack 3 users
**model-definiton.json**
```json
{
- "name": "Book",
- "base": "PersistedModel",
- "idInjection": false,
+ "name": "Customer",
+ "options": {
+ "idInjection": false
+ },
"properties": {
- "bId": {
- "type": "number",
- "id": true,
- "required": true
+ "id": {
+ "type": "Number",
+ "id": 1
},
"name": {
- "type": "string"
- },
- "isbn": {
- "type": "string"
- }
- },
- "validations": [],
- "relations": {
- "author": {
- "type": "belongsTo",
- "model": "Author",
- "foreignKey": "authorId"
- }
- },
- "acls": [],
- "methods": {},
- "foreignKeys": {
- "authorId": {
- "name": "authorId",
- "foreignKey": "authorId",
- "entityKey": "aId",
- "entity": "Author",
- "onUpdate": "restrict",
- "onDelete": "restrict"
+ "type": "String",
+ "required": false
}
}
-}
-```
-
-```json
+},
{
- "name": "Author",
- "base": "PersistedModel",
- "idInjection": false,
- "properties": {
- "aId": {
- "type": "number",
- "id": true,
- "required": true
- },
- "name": {
- "type": "string"
- },
- "dob": {
- "type": "date"
+ "name": "Order",
+ "options": {
+ "idInjection": false,
+ "foreignKeys": {
+ "fk_order_customerId": {
+ "name": "fk_order_customerId",
+ "entity": "Customer",
+ "entityKey": "id",
+ "foreignKey": "customerId",
+ "onUpdate": "restrict",
+ "onDelete": "cascade"
+ }
}
},
- "validations": [],
- "relations": {},
- "acls": [],
- "methods": {}
+ "properties": {
+ "id": {
+ "type": "Number"
+ "id": 1
+ },
+ "customerId": {
+ "type": "Number"
+ },
+ "description": {
+ "type": "String",
+ "required": false
+ }
+ }
}
```
**boot-script.js**
```js
-module.exports = function(app) {
+module.exports = function (app) {
var mysqlDs = app.dataSources.mysqlDS;
- var Book = app.models.Book;
- var Author = app.models.Author;
+ var Book = app.models.Order;
+ var Author = app.models.Customer;
- // first autoupdate the `Author` model to avoid foreign key constraint failure
- mysqlDs.autoupdate('Author', function(err) {
+ // first autoupdate the `Customer` model to avoid foreign key constraint failure
+ mysqlDs.autoupdate('Customer', function (err) {
if (err) throw err;
- console.log('\nAutoupdated table `Author`.');
+ console.log('\nAutoupdated table `Customer`.');
- mysqlDs.autoupdate('Book', function(err) {
+ mysqlDs.autoupdate('Order', function (err) {
if (err) throw err;
- console.log('\nAutoupdated table `Book`.');
- // at this point the database table `Book` should have one foreign key `authorId` integrated
+ console.log('\nAutoupdated table `Order`.');
+ // at this point the database table `Order` should have one foreign key `customerId` integrated
});
});
};
```
+
+
#### Breaking Changes with GeoPoint since 5.x
-Prior to `loopback-connector-mysql@5.x`, MySQL connector was saving and loading GeoPoint properties from the MySQL database in reverse.
-MySQL expects values to be POINT(X, Y) or POINT(lng, lat), but the connector was saving them in the opposite order(i.e. POINT(lat,lng)).
-If you have an application with a model that has a GeoPoint property using previous versions of this connector, you can migrate your models
-using the following programmatic approach:
-**NOTE** Please back up the database tables that have your application data before performing any of the steps.
+Prior to `loopback-connector-mysql@5.x`, MySQL connector was saving and loading
+GeoPoint properties from the MySQL database in reverse. MySQL expects values to
+be `POINT(X, Y)` or `POINT(lng, lat)`, but the connector was saving them in the
+opposite order(i.e. `POINT(lat,lng)`).
+
+Use the `geopoint` type to achieve so:
+
+```ts
+ @property({
+ type: 'geopoint'
+ })
+ name: GeoPoint;
+```
+
+If you have an application with a model that has a GeoPoint property using
+previous versions of this connector, you can migrate your models using the
+following programmatic approach:
+
+Click here to expend
+
+**NOTE** Please back up the database tables that have your application data
+before performing any of the steps.
1. Create a boot script under `server/boot/` directory with the following:
```js
'use strict';
-module.exports = function(app) {
+module.exports = function (app) {
function findAndUpdate() {
var teashop = app.models.teashop;
//find all instances of the model we'd like to migrate
- teashop.find({}, function(err, teashops) {
- teashops.forEach(function(teashopInstance) {
+ teashop.find({}, function (err, teashops) {
+ teashops.forEach(function (teashopInstance) {
//what we fetch back from the db is wrong, so need to revert it here
var newLocation = {
lng: teashopInstance.location.lat,
- lat: teashopInstance.location.lng
+ lat: teashopInstance.location.lng,
};
//only update the GeoPoint property for the model
- teashopInstance.updateAttribute('location', newLocation, function(
+ teashopInstance.updateAttribute('location', newLocation, function (
err,
- inst
+ inst,
) {
if (err) console.log('update attribute failed', err);
else console.log('updateAttribute successful');
@@ -564,11 +878,14 @@ For the above example, the model definition is as follows:
}
```
+
+
## Running tests
### Own instance
-If you have a local or remote MySQL instance and would like to use that to run the test suite, use the following command:
+If you have a local or remote MySQL instance and would like to use that to run
+the test suite, use the following command:
- Linux
@@ -584,15 +901,20 @@ SET MYSQL_HOST= SET MYSQL_PORT= SET MYSQL_USER= SET MYSQL_PASS
### Docker
-If you do not have a local MySQL instance, you can also run the test suite with very minimal requirements.
+If you do not have a local MySQL instance, you can also run the test suite with
+very minimal requirements.
-- Assuming you have [Docker](https://docs.docker.com/engine/installation/) installed, run the following script which would spawn a MySQL instance on your local:
+- Assuming you have [Docker](https://docs.docker.com/engine/installation/)
+ installed, run the following script which would spawn a MySQL instance on your
+ local:
```bash
source setup.sh
```
-where ``, ``, ``, `` and `` are optional parameters. The default values are `localhost`, `3306`, `root`, `pass` and `testdb` respectively.
+where ``, ``, ``, `` and `` are optional
+parameters. The default values are `localhost`, `3306`, `root`, `pass` and
+`testdb` respectively.
- Run the test: