From 59f61df46541aa10370c939e4a6ce99df7e01585 Mon Sep 17 00:00:00 2001 From: Zach Ashman Date: Fri, 20 Apr 2012 11:31:44 -0400 Subject: [PATCH 1/6] Add parameters into specification object for query and index calls Allow text querying by setting specifications.query Allow for complex AND/OR queries --- lib/session.js | 106 +++++++++++++++++++++++++++------------ test/session.test.coffee | 26 ++++++++++ 2 files changed, 101 insertions(+), 31 deletions(-) diff --git a/lib/session.js b/lib/session.js index 6721e77..934d196 100644 --- a/lib/session.js +++ b/lib/session.js @@ -249,36 +249,86 @@ var Session = module.exports = (function () { } }; - Session.prototype._buildSpecifications = function (specifications) { - query = ''; + Session.prototype._parseQueryParameters = function (parameters, parentKey) { + if (Array.isArray(parameters)) { + var statements = []; + for (var i=0; i 1) { + return '(' + statements.join(' OR ') + ')'; + } + } else if (typeof parameters === 'object') { + var statements = []; + for (var name in parameters) { + if (parameters.hasOwnProperty(name)) { + statements.push(this._parseQueryParameters(parameters[name], name)); + } + } + if (statements.length === 1) { + return statements[0]; + } else if (statements.length > 1) { + return '(' + statements.join(' AND ') + ')'; + } + } else { + var statement = '(' + parameters + ')' + if (typeof parentKey === 'string') { + statement = parentKey + ':' + statement + } + return statement; + } + return '' + } + + Session.prototype._buildQuery = function (urlFormat, urlTypeName, specifications) { + if (!urlFormat || !urlTypeName) { + return; + } + var query = util.format(urlFormat, urlTypeName); if (typeof specifications === 'object') { - if (Array.isArray(specifications.projections)) { - query += '&' + qs.stringify({ fetch: specifications.projections }, '&', '='); - } else if (typeof specifications.projections === 'string') { - query += '&fetch=' + specifications.projections; + var queryParameters = {}; + + if (typeof specifications.query === 'string' && specifications.query.length > 0) { + queryParameters.query = specifications.query; + } else { + if (typeof specifications.parameters === 'object') { + queryParameters.query = this._parseQueryParameters(specifications.parameters); + } } - if (Array.isArray(specifications.sort)) { - query += '&' + qs.stringify({ sort: specifications.sort }, '&', '='); - } else if (typeof specifications.sort === 'string') { - query += '&sort=' + specifications.sort; + if (Array.isArray(specifications.projections) || typeof specifications.projections === 'string') { + queryParameters.fetch = specifications.projections; } - if (typeof specifications.start === 'number') { - query += '&start=' + specifications.start; + if (Array.isArray(specifications.sort) || typeof specifications.sort === 'string') { + queryParameters.sort = specifications.sort } - if (typeof specifications.pageSize === 'number') { - query += '&pageSize=' + specifications.pageSize; + if (typeof specifications.skip === 'number') { + if (specifications.skip > 0) { + queryParameters.start = specifications.skip; + } + } + + if (typeof specifications.take === 'number') { + if (specifications.take >= 0) { + queryParameters.pageSize = specifications.take; + } } - } + var sQueryParameters = qs.stringify(queryParameters); + if (sQueryParameters.length > 0) { + query += '?' + sQueryParameters; + } + } return query; } - Session.prototype.query = function (type, parameters, specifications, callback) { - if (!type || !parameters) { + Session.prototype.query = function (type, specifications, callback) { + if (!type) { return; } @@ -292,18 +342,15 @@ var Session = module.exports = (function () { } var plural = inflector.pluralize(type); - var query = util.format(masks.dynamic, (plural.charAt(0).toUpperCase() + plural.slice(1)), qs.stringify(parameters, '%20AND%20', ':')); - - query += this._buildSpecifications(specifications); + var query = this._buildQuery(masks.dynamic, (plural.charAt(0).toUpperCase() + plural.slice(1)), specifications); - this._request('GET', query, callback, { json: function (json) { - return [ new Queryable(json.Results), new Statistics(json) ]; - } + this._request('GET', query, callback, function (json) { + return [ new Queryable(json.Results), new Statistics(json) ]; }); }; - Session.prototype.index = function (index, parameters, specifications, callback) { - if (!index || !parameters) { + Session.prototype.index = function (index, specifications, callback) { + if (!index) { return; } @@ -312,13 +359,10 @@ var Session = module.exports = (function () { specifications = undefined; } - var query = util.format(masks.index, index, qs.stringify(parameters, '%20AND%20', ':')); - - query += this._buildSpecifications(specifications); + var query = this._buildQuery(masks.index, index, specifications); - this._request('GET', query, callback, { json: function (json) { - return [ new Queryable(json.Results), new Statistics(json) ]; - } + this._request('GET', query, callback, function (json) { + return [ new Queryable(json.Results), new Statistics(json) ]; }); }; diff --git a/test/session.test.coffee b/test/session.test.coffee index 4bd4c6c..1d6851e 100644 --- a/test/session.test.coffee +++ b/test/session.test.coffee @@ -80,3 +80,29 @@ describe 'session ->', -> it 'when passed an array containing both valid and invalid objects, store should only add valid documents to the tracker', -> session.store([ rudolph, 2, 3 ]) Object.keys(session._store).length.should.equal(1) + + describe 'building a query ->', -> + it 'should build simple AND query', -> + queryParams = + name: 'max' + type: 'dog' + expect = '(name:(max) AND type:(dog))' + query = session._parseQueryParameters queryParams + query.should.equal expect + it 'should build simple OR query', -> + queryParams = + _or: [ + name: 'max' + , + type: 'dog' + ] + expect = '(name:(max) OR type:(dog))' + query = session._parseQueryParameters queryParams + query.should.equal expect + it 'should build complex AND and OR query parameters', -> + queryParams = + name: ['max', { name: 'rex' }, { _special_type: [{ sex: 'female', breed: 'boxer' }] } ] + type: ['dog', 'cat'] + expect = '((name:(max) OR name:(rex) OR (sex:(female) AND breed:(boxer))) AND (type:(dog) OR type:(cat)))' + query = session._parseQueryParameters queryParams + query.should.equal expect \ No newline at end of file From 39322f10b0cc3e080b5564b5e161e00a800f8325 Mon Sep 17 00:00:00 2001 From: Zach Ashman Date: Fri, 20 Apr 2012 11:55:37 -0400 Subject: [PATCH 2/6] Bug Fixes and handlers update Fix statistics: 0 skipped results adhere to new internal _request handlers format simplify "masks" --- lib/objects/statistics.js | 5 +---- lib/session.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/objects/statistics.js b/lib/objects/statistics.js index 97c32f6..8a954a3 100644 --- a/lib/objects/statistics.js +++ b/lib/objects/statistics.js @@ -2,15 +2,12 @@ var Statistics = module.exports = (function () { function Statistics (stats) { if (stats) { this.total = stats.TotalResults ? stats.TotalResults : stats.Results.length; + this.skipped = stats.SkippedResults ? stats.SkippedResults : 0; if (stats.Stale) { this.stale = stats.Stale; } - if (stats.SkippedResults) { - this.skipped = stats.SkippedResults; - } - if (stats.IndexName) { this.index = stats.IndexName; } diff --git a/lib/session.js b/lib/session.js index 934d196..d5c5c76 100644 --- a/lib/session.js +++ b/lib/session.js @@ -36,8 +36,8 @@ var Session = module.exports = (function () { document: '/docs/%s', queries: '/queries', bulk: '/bulk_docs', - dynamic: '/indexes/dynamic/%s?query=%s', - index: '/indexes/%s?query=%s' + dynamic: '/indexes/dynamic/%s', + index: '/indexes/%s' }; Session.prototype._request = function (method, query, callback, handlers, writeable, headers) { @@ -344,8 +344,9 @@ var Session = module.exports = (function () { var plural = inflector.pluralize(type); var query = this._buildQuery(masks.dynamic, (plural.charAt(0).toUpperCase() + plural.slice(1)), specifications); - this._request('GET', query, callback, function (json) { - return [ new Queryable(json.Results), new Statistics(json) ]; + this._request('GET', query, callback, { json: function (json) { + return [ new Queryable(json.Results), new Statistics(json) ]; + } }); }; @@ -361,8 +362,9 @@ var Session = module.exports = (function () { var query = this._buildQuery(masks.index, index, specifications); - this._request('GET', query, callback, function (json) { - return [ new Queryable(json.Results), new Statistics(json) ]; + this._request('GET', query, callback, { json: function (json) { + return [ new Queryable(json.Results), new Statistics(json) ]; + } }); }; From 33726b5ce75722190e335aa41c081e241446e0b6 Mon Sep 17 00:00:00 2001 From: Zach Ashman Date: Thu, 3 May 2012 08:22:44 -0400 Subject: [PATCH 3/6] Improve concurrency error handling and remove regex that throws errors --- lib/session.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/session.js b/lib/session.js index d5c5c76..5506893 100644 --- a/lib/session.js +++ b/lib/session.js @@ -83,8 +83,13 @@ var Session = module.exports = (function () { var json = JSON.parse(data); if (response.statusCode > 299 || json.Error) { - error = new Error(json.Error.match(/: (.*)\r\n/)[1] || 'An error occured.'); - error.statusCode = response.statusCode; + var errorDetail = new Error(json.Error || 'An error occured.'); + errorDetail.statusCode = response.statusCode; + if (error) { + error.inner = errorDetail; + } else { + error = errorDetail; + } } else if (typeof handlers.json === 'function') { args = args.concat(handlers.json(json, response.headers)); } From 810b52ba0df90640be85fe073b6686e384292e11 Mon Sep 17 00:00:00 2001 From: Zach Ashman Date: Tue, 12 Jun 2012 11:16:50 -0400 Subject: [PATCH 4/6] Fix inflector matching Prevent cached Rexex from starting the search at the previous location in the string --- lib/util/inflector.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/util/inflector.js b/lib/util/inflector.js index 0d971ad..a0d1df4 100644 --- a/lib/util/inflector.js +++ b/lib/util/inflector.js @@ -65,6 +65,7 @@ var inflector = { while (i--) { var rule = rules[i][0]; + rule.lastIndex = 0 //Prevent cached Rexex from starting the search at the previous location in the string if(rule.test(word)) { return word.replace(rule, rules[i][1]); From e6d99e429f5d9b6fde6f69c954a758979695cb93 Mon Sep 17 00:00:00 2001 From: Zach Ashman Date: Wed, 1 Aug 2012 12:20:26 -0400 Subject: [PATCH 5/6] correct spec parser provide return value for save to determine if there was anything batched --- lib/session.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/session.js b/lib/session.js index 5506893..2f162ae 100644 --- a/lib/session.js +++ b/lib/session.js @@ -293,7 +293,7 @@ var Session = module.exports = (function () { } var query = util.format(urlFormat, urlTypeName); - if (typeof specifications === 'object') { + if (specifications !== null && typeof specifications === 'object') { var queryParameters = {}; if (typeof specifications.query === 'string' && specifications.query.length > 0) { @@ -407,7 +407,7 @@ var Session = module.exports = (function () { Session.prototype.save = function (callback) { if (Object.keys(this._store).length === 0 && Object.keys(this._changes).length === 0) { - return; + return false; } var session = this, @@ -450,7 +450,7 @@ var Session = module.exports = (function () { } if (batch.length === 0) { - return; + return false; } handlers.json = function (json) { @@ -468,6 +468,7 @@ var Session = module.exports = (function () { }; session._request('POST', masks.bulk, callback, handlers, batch); + return true; }; Session.prototype.delete = function (document, etag, callback) { From 192eec42612e1bc7157720ed336e8cf5b4f52a65 Mon Sep 17 00:00:00 2001 From: Zach Ashman Date: Wed, 1 Aug 2012 13:46:20 -0400 Subject: [PATCH 6/6] Add option to bypass pluralizing type name --- lib/session.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/session.js b/lib/session.js index 2f162ae..fdb080d 100644 --- a/lib/session.js +++ b/lib/session.js @@ -332,7 +332,7 @@ var Session = module.exports = (function () { return query; } - Session.prototype.query = function (type, specifications, callback) { + Session.prototype.query = function (type, specifications, callback, singular) { if (!type) { return; } @@ -346,8 +346,8 @@ var Session = module.exports = (function () { specifications = undefined; } - var plural = inflector.pluralize(type); - var query = this._buildQuery(masks.dynamic, (plural.charAt(0).toUpperCase() + plural.slice(1)), specifications); + var typeName = singular ? type : inflector.pluralize(type); + var query = this._buildQuery(masks.dynamic, (typeName.charAt(0).toUpperCase() + typeName.slice(1)), specifications); this._request('GET', query, callback, { json: function (json) { return [ new Queryable(json.Results), new Statistics(json) ];