diff --git a/lib/migration.js b/lib/migration.js index 5b0ff4e..96a0967 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -535,7 +535,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': diff --git a/test/datatypes.test.js b/test/datatypes.test.js index faa96cf..3fff9ad 100644 --- a/test/datatypes.test.js +++ b/test/datatypes.test.js @@ -44,19 +44,14 @@ describe('MySQL specific datatypes', function() { }); it('should fail spectacularly with invalid enum values', function(done) { - // TODO: with a default install of MySQL 5.7, these queries actually do fail and raise errors... - if (/^5\.7/.test(mysqlVersion)) { - assert.ok(mysqlVersion, 'skipping decimal/number test on mysql 5.7'); - return 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(); - }); + // 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(); }); }); diff --git a/test/helpers/platform.js b/test/helpers/platform.js new file mode 100644 index 0000000..394599c --- /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/migration.test.js b/test/migration.test.js index 43a3b81..d8a7eb6 100644 --- a/test/migration.test.js +++ b/test/migration.test.js @@ -6,7 +6,9 @@ 'use strict'; var should = require('./init.js'); var assert = require('assert'); +var async = require('async'); var Schema = require('loopback-datasource-juggler').Schema; +var platform = require('./helpers/platform'); var db, UserData, StringData, NumberData, DateData; var mysqlVersion; @@ -32,7 +34,7 @@ describe('migrations', function() { Extra: 'auto_increment'}, email: { Field: 'email', - Type: 'varchar(512)', + Type: 'varchar(255)', Null: 'NO', Key: 'MUL', Default: null, @@ -109,7 +111,7 @@ describe('migrations', function() { // 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: /^5\.7/.test(mysqlVersion) ? null : /^5\.5/.test(mysqlVersion) ? 255 : 333, + Sub_part: null, Packed: null, Null: '', Index_type: 'BTREE', @@ -125,7 +127,7 @@ describe('migrations', function() { // 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: /^5\.7/.test(mysqlVersion) ? null : /^5\.5/.test(mysqlVersion) ? 255 : 333, + Sub_part: null, Packed: null, Null: '', Index_type: 'BTREE', @@ -244,6 +246,11 @@ describe('migrations', function() { }); 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'); @@ -288,6 +295,11 @@ describe('migrations', function() { }); 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) { assert.ok(ok, 'dataSource is not actual (should be)'); @@ -300,27 +312,54 @@ describe('migrations', function() { }); }); + // 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('should allow numbers with decimals', function(done) { - // TODO: Default install of MySQL 5.7 returns an error here, which we assert should not happen. - if (/^5\.7/.test(mysqlVersion)) { - assert.ok(mysqlVersion, 'skipping decimal/number test on mysql 5.7'); - return done(); - } - - NumberData.create({number: 1.1234567, tinyInt: 123456, mediumInt: -1234567, - floater: 123456789.1234567}, function(err, obj) { - assert.ok(!err); - assert.ok(obj); + 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, 0); - assert.equal(found.floater, 99999999.999999); + assert.equal(found.mediumInt, 16777215); + assert.equal(found.floater, 12345678.123456); 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'), @@ -338,14 +377,22 @@ describe('migrations', function() { }); }); - it('should map zero dateTime into null', function (done) { + // 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) { + function(err, ret) { should.not.exists(err); - DateData.findById(ret.insertId, function (err, dateData) { + DateData.findById(ret.insertId, function(err, dateData) { should(dateData.dateTime) .be.null(); should(dateData.timestamp) @@ -354,6 +401,37 @@ describe('migrations', function() { }); }); }); + 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) { diff --git a/test/mysql.test.js b/test/mysql.test.js index 8fd66ca..1034a60 100644 --- a/test/mysql.test.js +++ b/test/mysql.test.js @@ -33,7 +33,7 @@ describe('mysql', function() { stars: Number, userId: ObjectID, }, { - forceId: false + forceId: false, }); PostWithStringId = db.define('PostWithStringId', { diff --git a/test/transaction.promise.test.js b/test/transaction.promise.test.js index 127cbe4..7041d48 100644 --- a/test/transaction.promise.test.js +++ b/test/transaction.promise.test.js @@ -30,6 +30,13 @@ describe('transactions with promise', function() { }); }); + after(function(done) { + // disconnect from this db to avoid too many connection error + // due to multiple instance of connection pool + db.disconnect(); + done(); + }); + var currentTx; var hooks = []; // Return an async function to start a transaction and create a post