diff --git a/helpers/avg.js b/helpers/avg.js index 1fad2c84..e3674230 100644 --- a/helpers/avg.js +++ b/helpers/avg.js @@ -123,7 +123,7 @@ module.exports = require('machine').build({ // Compile the original Waterline Query var compiledQuery; try { - compiledQuery = Helpers.query.compileStatement(statement); + compiledQuery = Helpers.query.compileStatement(statement, query.meta); } catch (e) { return exits.error(e); } diff --git a/helpers/count.js b/helpers/count.js index 8629d4c1..786ecaef 100644 --- a/helpers/count.js +++ b/helpers/count.js @@ -122,7 +122,7 @@ module.exports = require('machine').build({ // Compile the original Waterline Query var compiledQuery; try { - compiledQuery = Helpers.query.compileStatement(statement); + compiledQuery = Helpers.query.compileStatement(statement, query.meta); } catch (e) { return exits.error(e); } diff --git a/helpers/destroy.js b/helpers/destroy.js index 348411a2..eee80013 100644 --- a/helpers/destroy.js +++ b/helpers/destroy.js @@ -142,7 +142,7 @@ module.exports = require('machine').build({ // Compile the original Waterline Query var compiledQuery; try { - compiledQuery = Helpers.query.compileStatement(statement); + compiledQuery = Helpers.query.compileStatement(statement, query.meta); } catch (e) { return exits.error(e); } diff --git a/helpers/private/query/compile-statement.js b/helpers/private/query/compile-statement.js index 05f40a14..1d37d40d 100644 --- a/helpers/private/query/compile-statement.js +++ b/helpers/private/query/compile-statement.js @@ -15,8 +15,13 @@ // Transform a Waterline Query Statement into a SQL query. var PG = require('machinepack-postgresql'); +var modifyWhereClause = require('./modify-where-clause'); + +module.exports = function compileStatement(statement, meta) { + if (statement.where) { + statement.where = modifyWhereClause(statement.where, meta); + } -module.exports = function compileStatement(statement) { var report = PG.compileStatement({ statement: statement }).execSync(); diff --git a/helpers/private/query/modify-where-clause.js b/helpers/private/query/modify-where-clause.js new file mode 100644 index 00000000..a808e5e5 --- /dev/null +++ b/helpers/private/query/modify-where-clause.js @@ -0,0 +1,61 @@ +// ███╗ ███╗ ██████╗ ██████╗ ██╗███████╗██╗ ██╗ ██╗ ██╗██╗ ██╗███████╗██████╗ ███████╗ +// ████╗ ████║██╔═══██╗██╔══██╗██║██╔════╝╚██╗ ██╔╝ ██║ ██║██║ ██║██╔════╝██╔══██╗██╔════╝ +// ██╔████╔██║██║ ██║██║ ██║██║█████╗ ╚████╔╝ ██║ █╗ ██║███████║█████╗ ██████╔╝█████╗ +// ██║╚██╔╝██║██║ ██║██║ ██║██║██╔══╝ ╚██╔╝ ██║███╗██║██╔══██║██╔══╝ ██╔══██╗██╔══╝ +// ██║ ╚═╝ ██║╚██████╔╝██████╔╝██║██║ ██║ ╚███╔███╔╝██║ ██║███████╗██║ ██║███████╗ +// ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ + +// ██████╗██╗ █████╗ ██╗ ██╗███████╗███████╗ +// ██╔════╝██║ ██╔══██╗██║ ██║██╔════╝██╔════╝ +// ██║ ██║ ███████║██║ ██║███████╗█████╗ +// ██║ ██║ ██╔══██║██║ ██║╚════██║██╔══╝ +// ╚██████╗███████╗██║ ██║╚██████╔╝███████║███████╗ +// ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ + +// Modify the where clause of a query object + +var _ = require("@sailshq/lodash"); + +module.exports = function modifyWhereClause(whereClause, meta) { + // Handle empty `where` clause and missing meta. + if (_.keys(whereClause).length === 0 || !meta) { + return whereClause; + } + + var makeLikeModifierCaseInsensitive = meta.makeLikeModifierCaseInsensitive; + + // Recursively modify the `where` clause. + var queryFilter = (function recurse(branch) { + var loneKey = _.first(_.keys(branch)); + + // Handle AND/OR conditions + if (loneKey === "and" || loneKey === "or") { + var conjunctsOrDisjuncts = branch[loneKey]; + branch[loneKey] = _.map(conjunctsOrDisjuncts, function (conjunctOrDisjunct) { + return recurse(conjunctOrDisjunct); + }); + + return branch; + } + + // We're dealing with a constraint of some kind. + var constraintColumnName = loneKey; + var constraint = branch[constraintColumnName]; + + // If it's a primitive, return as is. + if (_.isString(constraint) || _.isNumber(constraint) || _.isBoolean(constraint) || _.isNull(constraint)) { + return branch; + } + + // Modify `LIKE` to `ILIKE` if case insensitivity is enabled. + if (constraint.like && makeLikeModifierCaseInsensitive) { + constraint.ilike = constraint.like; + delete constraint.like; + } + + return branch; + })(whereClause); + + // Return the modified query filter. + return queryFilter; +}; diff --git a/helpers/select.js b/helpers/select.js index ce8c3b12..b3c1efa6 100644 --- a/helpers/select.js +++ b/helpers/select.js @@ -124,7 +124,7 @@ module.exports = require('machine').build({ // Compile the original Waterline Query var compiledQuery; try { - compiledQuery = Helpers.query.compileStatement(statement); + compiledQuery = Helpers.query.compileStatement(statement, query.meta); } catch (e) { return exits.error(e); } diff --git a/helpers/sum.js b/helpers/sum.js index 0c4c997c..5e04fa8b 100644 --- a/helpers/sum.js +++ b/helpers/sum.js @@ -123,7 +123,7 @@ module.exports = require('machine').build({ // Compile the original Waterline Query var compiledQuery; try { - compiledQuery = Helpers.query.compileStatement(statement); + compiledQuery = Helpers.query.compileStatement(statement, query.meta); } catch (e) { return exits.error(e); } diff --git a/helpers/update.js b/helpers/update.js index 33d4ea84..fe0725fc 100644 --- a/helpers/update.js +++ b/helpers/update.js @@ -171,7 +171,7 @@ module.exports = require('machine').build({ // Compile statement into a native query. var compiledQuery; try { - compiledQuery = Helpers.query.compileStatement(statement); + compiledQuery = Helpers.query.compileStatement(statement, query.meta); } catch (e) { return exits.error(e); } diff --git a/lib/private/redact-passwords.js b/lib/private/redact-passwords.js index cffa70d2..bea7c77e 100644 --- a/lib/private/redact-passwords.js +++ b/lib/private/redact-passwords.js @@ -28,4 +28,4 @@ module.exports = function redactPasswords(err) { } } return err; -} +}; diff --git a/test/adapter/unit/destroy.js b/test/adapter/unit/destroy.js index 83002775..d3febdc7 100644 --- a/test/adapter/unit/destroy.js +++ b/test/adapter/unit/destroy.js @@ -47,6 +47,38 @@ describe('Unit Tests ::', function() { }); }); + it('should ensure the record is deleted with case insensitive like', function(done) { + var query = { + using: 'test_destroy', + criteria: { + where: { + fieldB: { + like: 'BAR' + } + } + }, + meta: { + makeLikeModifierCaseInsensitive: true + } + }; + + Adapter.destroy('test', query, function(err) { + if (err) { + return done(err); + } + + Adapter.find('test', query, function(err, results) { + if (err) { + return done(err); + } + + assert.equal(results.length, 0); + + return done(); + }); + }); + }); + // Look into the bowels of the PG Driver and ensure the Create function handles // it's connections properly. it('should release its connection when completed', function(done) { diff --git a/test/adapter/unit/find.js b/test/adapter/unit/find.js index f02f90b6..f8553a75 100644 --- a/test/adapter/unit/find.js +++ b/test/adapter/unit/find.js @@ -88,6 +88,34 @@ describe('Unit Tests ::', function() { }); }); + it('should be case insensitive when `meta.makeLikeModifierCaseInsensitive` is true', function(done) { + var query = { + using: 'test_find', + criteria: { + where: { + fieldB: { + like: 'bar_2' + } + } + }, + meta: { + makeLikeModifierCaseInsensitive: true + } + }; + + Adapter.find('test', query, function(err, results) { + if (err) { + return done(err); + } + + assert(_.isArray(results)); + assert.equal(results.length, 1); + assert.equal(_.first(results).fieldB, 'bAr_2'); + + return done(); + }); + }); + it('should return `ref` type attributes unchanged', function(done) { var query = { using: 'test_find', diff --git a/test/adapter/unit/update.js b/test/adapter/unit/update.js index 1f3bff6d..330a2f56 100644 --- a/test/adapter/unit/update.js +++ b/test/adapter/unit/update.js @@ -81,6 +81,39 @@ describe('Unit Tests ::', function() { }); }); + it('should be case insensitive when `meta.makeLikeModifierCaseInsensitive` is true', function(done) { + var query = { + using: 'test_update', + criteria: { + where: { + fieldB: { + like: 'bar_2' + } + } + }, + valuesToSet: { + fieldA: 'FooBar2' + }, + meta: { + fetch: true, + makeLikeModifierCaseInsensitive: true + } + }; + + Adapter.update('test', query, function(err, results) { + if (err) { + return done(err); + } + + assert(_.isArray(results)); + assert.equal(results.length, 1); + assert.equal(_.first(results).fieldA, 'FooBar2'); + assert.equal(_.first(results).fieldB, 'bAr_2'); + + return done(); + }); + }); + // Look into the bowels of the PG Driver and ensure the Create function handles // it's connections properly. it('should release its connection when completed', function(done) {