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. - -
See also LoopBack MySQL Connector in LoopBack documentation. -

-NOTE: The MySQL connector requires MySQL 5.0+. -
+[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: