From 5f8c4ad9b0e18f2ccb381e385167a77e1bc13288 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Wed, 5 Nov 2014 17:08:56 -0500 Subject: [PATCH] Fix issue with using where method with an object with multiple keys --- lib/helpers.js | 51 ++++++++++++++++++ lib/node-query.js | 78 +++++++++++++++------------- lib/query-builder.js | 3 +- package.json | 2 +- tests/adapters/dblite_test.js | 6 +++ tests/adapters/mysql2_test.js | 6 +++ tests/adapters/mysql_test.js | 6 +++ tests/adapters/node-firebird_test.js | 6 +++ tests/adapters/pg_test.js | 5 ++ tests/adapters/sqlite3_test.js | 6 +++ tests/base_test.js | 9 +++- tests/helpers_test.js | 35 +++++++++++++ tests/query-builder-base.js | 10 ++++ 13 files changed, 183 insertions(+), 40 deletions(-) diff --git a/lib/helpers.js b/lib/helpers.js index aa42bba..f355470 100755 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -46,6 +46,57 @@ var h = { isScalar: function(obj) { var scalar = ['string', 'number', 'boolean']; return scalar.indexOf(h.type(obj)) !== -1; + }, + /** + * Get a list of values with a common key from an array of objects + * + * @param {Array} arr - The array of objects to search + * @param {String} key - The key of the object to get + * @return {Array} + */ + arrayPluck: function(arr, key) { + var output = []; + + // Empty case + if (arr.length === 0) return output; + + arr.forEach(function(obj) { + if ( ! h.isUndefined(obj[key])) + { + output.push(obj[key]); + } + }); + + return output; + }, + /** + * Determine if a value matching the passed regular expression is + * in the passed array + * + * @param {Array} arr - The array to search + * @param {RegExp} pattern - The pattern to match + * @return {Boolean} - If an array item matches the pattern + */ + regexInArray: function(arr, pattern) { + // Empty case(s) + if ( ! h.isArray(arr)) return false; + if (arr.length === 0) return false; + + var i, l = arr.length; + + for(i=0; i< l; i++) + { + // Recurse for nested arrays + if (Array.isArray(arr[i])) + { + return h.regexInArray(arr[i], pattern); + } + + // Short circuit if any items match + if (pattern.test(arr[i])) return true; + } + + return false; } }; diff --git a/lib/node-query.js b/lib/node-query.js index 798a8e1..cd0e343 100755 --- a/lib/node-query.js +++ b/lib/node-query.js @@ -1,52 +1,56 @@ "use strict"; /** @module node-query */ -var nodeQuery = {}; +var NodeQuery = function() { -var instance = null; + var instance = null; -/** - * Create a query builder object - * - * @alias module:node-query - * @param {String} drivername - The name of the database type, eg. mysql or pg - * @param {Object} connObject - A connection object from the database library you are connecting with - * @param {String} connLib - The name of the db connection library you are using, eg. mysql or mysql2 - * @return {queryBuilder} - */ -nodeQuery.init = function (driverType, connObject, connLib) { - var fs = require('fs'), - qb = require('./query-builder'); + /** + * Create a query builder object + * + * @alias module:node-query + * @param {String} drivername - The name of the database type, eg. mysql or pg + * @param {Object} connObject - A connection object from the database library you are connecting with + * @param {String} connLib - The name of the db connection library you are using, eg. mysql or mysql2 + * @return {queryBuilder} + */ + this.init = function (driverType, connObject, connLib) { + var fs = require('fs'), + qb = require('./query-builder'); - var paths = { - driver: __dirname + '/drivers/' + driverType + '.js', - adapter: __dirname + '/adapters/' + connLib + '.js' + var paths = { + driver: __dirname + '/drivers/' + driverType + '.js', + adapter: __dirname + '/adapters/' + connLib + '.js' + }; + + Object.keys(paths).forEach(function(type) { + if ( ! fs.existsSync(paths[type])) + { + console.log(paths[type]); + throw new Error('Selected ' + type + ' does not exist!'); + } + }); + + instance = qb(require(paths.driver), require(paths.adapter)(connObject)); + + return instance; }; - Object.keys(paths).forEach(function(type) { - if ( ! fs.existsSync(paths[type])) - { - console.log(paths[type]); - throw new Error('Selected ' + type + ' does not exist!'); + /** + * Return an existing query builder instance + * + * @return {queryBuilder} + */ + this.getQuery = function () { + if ( ! instance) { + throw new Error("No Query Builder instance to return"); } - }); - instance = qb(require(paths.driver), require(paths.adapter)(connObject)); + return instance; + }; - return instance; }; -/** - * Return an existing query builder instance - * - * @return {queryBuilder} - */ -nodeQuery.getQuery = function () { - if ( ! instance) { - throw new Error("No Query Builder instance to return"); - } - return instance; -}; -module.exports = nodeQuery; \ No newline at end of file +module.exports = new NodeQuery(); \ No newline at end of file diff --git a/lib/query-builder.js b/lib/query-builder.js index 5c9afba..1e99a79 100755 --- a/lib/query-builder.js +++ b/lib/query-builder.js @@ -205,8 +205,9 @@ var QueryBuilder = function(driver, adapter) { lastItem = state.queryMap[state.queryMap.length - 1]; // Determine the correct conjunction + var conjunctionList = helpers.arrayPluck(state.queryMap, 'conjunction'); var conj = defaultConj; - if (state.queryMap.length === 0 || firstItem.conjunction.contains('JOIN')) + if (state.queryMap.length === 0 || ( ! helpers.regexInArray(conjunctionList, /^ ?WHERE/i))) { conj = " WHERE "; } diff --git a/package.json b/package.json index bd21888..0b4366d 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ci-node-query", - "version": "1.1.0", + "version": "1.1.1", "description": "A query builder for node based on the one in CodeIgniter", "author": "Timothy J Warren ", "engines": { diff --git a/tests/adapters/dblite_test.js b/tests/adapters/dblite_test.js index adebb11..a648e9b 100644 --- a/tests/adapters/dblite_test.js +++ b/tests/adapters/dblite_test.js @@ -48,6 +48,12 @@ if (connection) test.done(); }); + tests['nodeQuery.getQuery = nodeQuery.init'] = function(test) { + test.expect(1); + test.deepEqual(qb, nodeQuery.getQuery(), "getQuery returns same object"); + test.done(); + }; + tests["dblite adapter with query builder"] = function(test) { test.expect(1); test.ok(testBase.qb); diff --git a/tests/adapters/mysql2_test.js b/tests/adapters/mysql2_test.js index 6fdb970..60043c3 100644 --- a/tests/adapters/mysql2_test.js +++ b/tests/adapters/mysql2_test.js @@ -30,6 +30,12 @@ testBase._setUp(qb, function(test, err, rows) { test.done(); }); +tests['nodeQuery.getQuery = nodeQuery.init'] = function(test) { + test.expect(1); + test.deepEqual(qb, nodeQuery.getQuery(), "getQuery returns same object"); + test.done(); +}; + // Export the final test object tests["mysql2 adapter with query builder"] = function(test) { test.expect(1); diff --git a/tests/adapters/mysql_test.js b/tests/adapters/mysql_test.js index bd912ce..a7b95f9 100644 --- a/tests/adapters/mysql_test.js +++ b/tests/adapters/mysql_test.js @@ -30,6 +30,12 @@ testBase._setUp(qb, function(test, err, rows) { test.done(); }); +tests['nodeQuery.getQuery = nodeQuery.init'] = function(test) { + test.expect(1); + test.deepEqual(qb, nodeQuery.getQuery(), "getQuery returns same object"); + test.done(); +}; + tests["mysql adapter with query builder"] = function(test) { test.expect(1); test.ok(testBase.qb); diff --git a/tests/adapters/node-firebird_test.js b/tests/adapters/node-firebird_test.js index 78f2793..b099ae5 100644 --- a/tests/adapters/node-firebird_test.js +++ b/tests/adapters/node-firebird_test.js @@ -70,6 +70,12 @@ try { test.done(); }; + testBase.tests['nodeQuery.getQuery = nodeQuery.init'] = function(test) { + test.expect(1); + test.deepEqual(qb, nodeQuery.getQuery(), "getQuery returns same object"); + test.done(); + }; + testBase.tests["firebird adapter with query builder"] = function(test) { test.expect(1); test.ok(testBase.qb); diff --git a/tests/adapters/pg_test.js b/tests/adapters/pg_test.js index 3a4639a..2ca267f 100644 --- a/tests/adapters/pg_test.js +++ b/tests/adapters/pg_test.js @@ -35,6 +35,11 @@ testBase._setUp(qb, function(test, err, result) { test.done(); }); +tests['nodeQuery.getQuery = nodeQuery.init'] = function(test) { + test.expect(1); + test.deepEqual(qb, nodeQuery.getQuery(), "getQuery returns same object"); + test.done(); +}; tests["pg adapter with query builder"] = function(test) { test.expect(1); diff --git a/tests/adapters/sqlite3_test.js b/tests/adapters/sqlite3_test.js index 8b6493d..9100f3b 100644 --- a/tests/adapters/sqlite3_test.js +++ b/tests/adapters/sqlite3_test.js @@ -51,6 +51,12 @@ if (connection) test.done(); }); + tests['nodeQuery.getQuery = nodeQuery.init'] = function(test) { + test.expect(1); + test.deepEqual(qb, nodeQuery.getQuery(), "getQuery returns same object"); + test.done(); + }; + tests["sqlite3 adapter with query builder"] = function(test) { test.expect(1); test.ok(testBase.qb); diff --git a/tests/base_test.js b/tests/base_test.js index 2f945b2..e83172b 100755 --- a/tests/base_test.js +++ b/tests/base_test.js @@ -24,10 +24,17 @@ module.exports = { test.done(); }, + 'NodeQuery.getQuery with no instance': function(test) { + test.expect(1); + test.throws(function() { + nodeQuery.getQuery(); + }, Error, "No query builder instance if none created"); + test.done(); + }, 'Invalid driver type': function(test) { test.expect(1); test.throws(function() { - modules['node-query'].init('foo', {}, 'bar'); + nodeQuery.init('foo', {}, 'bar'); }, Error, "Bad driver throws exception"); test.done(); } diff --git a/tests/helpers_test.js b/tests/helpers_test.js index 0b5d4f0..0d1c963 100644 --- a/tests/helpers_test.js +++ b/tests/helpers_test.js @@ -37,11 +37,46 @@ var helperTests = { test.done(); }, 'stringTrim': function(test) { + test.expect(1); + var orig = [' x y ', 'z ', ' q']; var ret = ['x y', 'z', 'q']; test.deepEqual(ret, orig.map(helpers.stringTrim)); + test.done(); + }, + 'arrayPluck': function(test) { + test.expect(3); + + var orig = [{ + foo: 1 + },{ + foo: 2, + bar: 10 + },{ + foo: 3, + bar: 15 + }]; + + test.deepEqual([1,2,3], helpers.arrayPluck(orig, 'foo'), 'Finding members in all objects'); + test.deepEqual([10,15], helpers.arrayPluck(orig, 'bar'), 'Some members are missing in some objects'); + + // Empty case + test.deepEqual([], helpers.arrayPluck([], 'apple')); + + test.done(); + }, + 'regexInArray': function(test) { + var orig = ['apple', ' string ', 6, 4, 7]; + + test.expect(4); + + test.equal(false, helpers.regexInArray(orig, /\$/), 'Dollar sign is not in any of the array items'); + test.equal(true, helpers.regexInArray(orig, /^ ?string/), "' string ' matches /^ ?string/"); + test.equal(true, helpers.regexInArray(orig, /APPLE/i), "'apple' matches /APPLE/i"); + test.equal(false, helpers.regexInArray(orig, /5/), 'None of the numbers in the array match /5/'); + test.done(); } }; diff --git a/tests/query-builder-base.js b/tests/query-builder-base.js index 11855d5..a977100 100644 --- a/tests/query-builder-base.js +++ b/tests/query-builder-base.js @@ -290,6 +290,16 @@ module.exports = (function QueryBuilderTestBase() { base.qb.from('create_test ct') .join('create_join cj', 'cj.id=ct.id', 'inner') .get(base.testCallback.bind(this, test)); + }, + 'Join with multiple where values': function(test) { + test.expect(1); + base.qb.from('create_test ct') + .join('create_join cj', 'cj.id=ct.id', 'inner') + .where({ + 'ct.id <': 3, + 'ct.key': 'foo' + }) + .get(base.testCallback.bind(this, test)); } }, // ! DB Update test