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 6721e77..fdb080d 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) { @@ -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)); } @@ -249,36 +254,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 (specifications !== null && typeof specifications === 'object') { + var queryParameters = {}; - 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; + 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, singular) { + if (!type) { return; } @@ -291,10 +346,8 @@ var Session = module.exports = (function () { specifications = undefined; } - 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 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) ]; @@ -302,8 +355,8 @@ var Session = module.exports = (function () { }); }; - Session.prototype.index = function (index, parameters, specifications, callback) { - if (!index || !parameters) { + Session.prototype.index = function (index, specifications, callback) { + if (!index) { return; } @@ -312,9 +365,7 @@ 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) ]; @@ -356,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, @@ -399,7 +450,7 @@ var Session = module.exports = (function () { } if (batch.length === 0) { - return; + return false; } handlers.json = function (json) { @@ -417,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) { 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]); 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