diff --git a/lib/mysql.js b/lib/mysql.js index 75b1e16..c67d93d 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -459,6 +459,27 @@ MySQL.prototype.ping = function(cb) { this.execute('SELECT 1 AS result', cb); }; +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'); + + if (operatorValue.global) + console.warn('MySQL regex syntax does not respect the `g` flag'); + + if (operatorValue.multiline) + console.warn('MySQL regex syntax does not respect the `m` flag'); + + return new ParameterizedSQL(columnName + ' REGEXP ?', + [operatorValue.source]); + } + + // invoke the base implementation of `buildExpression` + return this.invokeSuper('buildExpression', columnName, operator, + operatorValue, propertyDefinition); +}; + require('./migration')(MySQL, mysql); require('./discovery')(MySQL, mysql); require('./transaction')(MySQL, mysql); diff --git a/package.json b/package.json index 74d0cbf..032ea65 100644 --- a/package.json +++ b/package.json @@ -7,17 +7,18 @@ "test": "mocha" }, "dependencies": { - "loopback-connector": "^2.1.0", - "mysql": "^2.5.4", "async": "^0.9.0", - "debug": "^2.1.1" + "debug": "^2.1.1", + "loopback-connector": "^2.1.0", + "mysql": "^2.5.4" }, "devDependencies": { "bluebird": "~2.9.10", "loopback-datasource-juggler": "^2.28.0", "mocha": "^2.1.0", "rc": "^1.0.0", - "should": "^5.0.0" + "should": "^5.0.0", + "sinon": "^1.15.4" }, "repository": { "type": "git", diff --git a/test/init.js b/test/init.js index 9959213..83bd213 100644 --- a/test/init.js +++ b/test/init.js @@ -29,4 +29,4 @@ global.getDataSource = global.getSchema = function (options) { return db; }; - +global.sinon = require('sinon'); diff --git a/test/mysql.test.js b/test/mysql.test.js index 2f533e1..3702127 100644 --- a/test/mysql.test.js +++ b/test/mysql.test.js @@ -497,6 +497,158 @@ describe('mysql', function () { }); }); + context('regexp operator', function() { + beforeEach(function createTestFixtures(done) { + Post.create([ + {title: 'a', content: 'AAA'}, + {title: 'b', content: 'BBB'} + ], done); + }); + afterEach(function deleteTestFixtures(done) { + Post.destroyAll(done); + }); + + context('with regex strings', function() { + context('using no flags', function() { + it('should work', function(done) { + Post.find({where: {content: {regexp: '^A'}}}, function(err, posts) { + should.not.exist(err); + posts.length.should.equal(1); + posts[0].content.should.equal('AAA'); + done(); + }); + }); + }); + + context('using flags', function() { + beforeEach(function addSpy() { + sinon.stub(console, 'warn'); + }); + afterEach(function removeSpy() { + console.warn.restore(); + }); + + 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(); + }); + }); + + 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(); + }); + }); + + 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(); + }); + }); + }); + }); + + context('with regex literals', function() { + context('using no flags', function() { + it('should work', function(done) { + Post.find({where: {content: {regexp: /^A/}}}, function(err, posts) { + should.not.exist(err); + posts.length.should.equal(1); + posts[0].content.should.equal('AAA'); + done(); + }); + }); + + }); + + context('using flags', function() { + beforeEach(function addSpy() { + sinon.stub(console, 'warn'); + }); + afterEach(function removeSpy() { + console.warn.restore(); + }); + + 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(); + }); + }); + + 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(); + }); + }); + + 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(); + }); + }); + }); + }); + + context('with regex objects', function() { + beforeEach(function addSpy() { + sinon.stub(console, 'warn'); + }); + afterEach(function removeSpy() { + console.warn.restore(); + }); + + context('using no flags', 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(); + }); + }); + }); + + context('using flags', 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(); + }); + }); + + 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(); + }); + }); + + 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(); + }); + }); + }); + }); + }); + after(function (done) { Post.destroyAll(function () { PostWithStringId.destroyAll(function () {