From d26b78f3e5cc5db4cf523d3e29354073c7a6c6dd Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 27 Jan 2015 12:45:54 -0600 Subject: [PATCH 01/43] Updated README with new N1QL syntax --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a71f37b..5319291 100644 --- a/README.md +++ b/README.md @@ -38,17 +38,17 @@ Step 4: Open http://localhost:8093/tutorial in your browser to use the tutorial To connect N1QL with your Couchbase Server: - ./cbq-engine -couchbase http://[server_name]:8091/ + ./cbq-engine -datastore=http://[server_name]:8091/ To use the command-line interactive query tool: - ./cbq-engine=http://[couchbase-query-engine-server-name]:8093/ + ./cbq -engine=http://[couchbase-query-engine-server-name]:8093/ Step 5: Before issuing queries against a Couchbase bucket, run the following command from the query command line: - CREATE PRIMARY INDEX ON [bucket-name] + CREATE PRIMARY INDEX ON [bucket-name]; From 69ba1cc03c4c2397a57b7fec16b19a74265009b2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 27 Jan 2015 19:16:35 -0600 Subject: [PATCH 02/43] -Added save -Changed "updateOneDoc" to use the id of inboundData if no idkey is given -Fixed a type in error message of "updateOneDoc" --- lib/cb.js | 62 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 3b3ae56..bffe7e1 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -4,8 +4,8 @@ var N1qlQuery = couchbase.N1qlQuery; var Connector = require('loopback-connector').Connector; var async = require('async'); var debug = require('debug')('loopback:connector:couchbase'); -var clauseBuilderClass = require("./clauseBuilder"); -var dataBuilderClass = require("./dataBuilder"); +var clauseBuilderClass = require("./clauseBuilder"); +var dataBuilderClass = require("./dataBuilder"); var constants = require('./base/constants.js'); var uuid = require('node-uuid'); var debug = require('debug')('connector:couchbase'); @@ -38,7 +38,7 @@ exports.initialize = function initializeDataSource(dataSource, callback) { operationTimeout: s.operationTimeout || 15000 }; options.connectUrl = "couchbase://" + options.host; //do not attach any port number such as 8901, will cause error code 24 which means "Error while establishing TCP connection" - options.n1qlUrl = options.host + ":" + options.n1qlport; + options.n1qlUrl = options.host + ":" + options.n1qlport; debug("couchbase connector initializeDataSource(): options:" + JSON.stringify([options])); //initializes the Couchbase connector: @@ -104,7 +104,7 @@ var cbquery = function (sql,selfThis, callback) { var query = N1qlQuery.fromString(sql); debug("cbquery() : query is: " + JSON.stringify([query]) ); - myBucket.query(query, function(err, res) { + myBucket.query(query, function(err, res) { if (err) { debug("cbquery() : query failed" + JSON.stringify([err, res])); callback(err,null); @@ -131,8 +131,8 @@ CouchbaseDB.prototype.count = function (model, callback, where) { var self = this; - var clauseBuilderIndex = new clauseBuilderClass(); - + var clauseBuilderIndex = new clauseBuilderClass(); + var clausebuilderObj = clauseBuilderIndex.getClauseBuilder(where,self.settings.bucket,model); var clause = clausebuilderObj.buildClause('SELECT count(*) AS cnt FROM '); @@ -150,13 +150,13 @@ CouchbaseDB.prototype.count = function (model, callback, where) { return; }); }; - + /** * Find a model instance by id */ CouchbaseDB.prototype.find = function find(model, id, callback) { var self = this; - debug("CouchbaseDB.prototype.find() : " + JSON.stringify([model, id])); + debug("CouchbaseDB.prototype.find() : " + JSON.stringify([model, id])); var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ if(err){ @@ -183,11 +183,11 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { * Will be called when POST to /model_url_name */ CouchbaseDB.prototype.create = function (model, data, callback) { - + var self = this; debug("CouchbaseDB.prototype.create() : " + JSON.stringify([model, data])); - + var createDataBuilderObj = prepareCreateData(data,self,model,callback); @@ -204,7 +204,7 @@ CouchbaseDB.prototype.create = function (model, data, callback) { } result = createDataBuilderObj.setImplicitFieldsInResult(data,result); debug("CouchbaseDB.prototype.create() : added id/doctype,now result is: " + JSON.stringify([result]) ); - + callback(err,result); }); @@ -215,8 +215,16 @@ CouchbaseDB.prototype.create = function (model, data, callback) { * Save a model instance */ CouchbaseDB.prototype.save = function (model, data, callback) { - debug("CouchbaseDB.prototype.save() : " + JSON.stringify([model, data])); - throw "stop here !"; + var self = this; + debug("CouchbaseDB.prototype.save() : " + JSON.stringify([model, data])); + + var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + if(err){ + callback(err,null); + return; + } + updateOneDoc(self,myBucket,undefined,model,data,callback); + }); }; /** @@ -224,7 +232,7 @@ CouchbaseDB.prototype.save = function (model, data, callback) { */ CouchbaseDB.prototype.exists = function (model, id, callback) { var self = this; - debug("CouchbaseDB.prototype.exists() : " + JSON.stringify([model, id])); + debug("CouchbaseDB.prototype.exists() : " + JSON.stringify([model, id])); var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ if(err){ @@ -255,7 +263,7 @@ CouchbaseDB.prototype.exists = function (model, id, callback) { * Update a model instance or create a new model instance if it doesn't exist */ CouchbaseDB.prototype.updateOrCreate = function updateOrCreate(model, data, callback) { - debug("CouchbaseDB.prototype.updateOrCreate() : " + JSON.stringify([model, data])); + debug("CouchbaseDB.prototype.updateOrCreate() : " + JSON.stringify([model, data])); var self = this; var createDataBuilderObj = prepareCreateData(data,self,model,callback); @@ -288,7 +296,7 @@ var prepareCreateData=function(inboundData,thisRef,model,callback){ */ CouchbaseDB.prototype.destroy = function destroy(model, id, callback) { var self = this; - debug("CouchbaseDB.prototype.destroy() : " + JSON.stringify([model, id])); + debug("CouchbaseDB.prototype.destroy() : " + JSON.stringify([model, id])); var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ if(err){ @@ -314,15 +322,15 @@ CouchbaseDB.prototype.destroy = function destroy(model, id, callback) { * Query model instances by the filter */ CouchbaseDB.prototype.all = function all(model, filter, callback) { - debug("CouchbaseDB.prototype.all() : " + JSON.stringify([model, filter])); + debug("CouchbaseDB.prototype.all() : " + JSON.stringify([model, filter])); var self = this; - var clauseBuilderIndex = new clauseBuilderClass(); + var clauseBuilderIndex = new clauseBuilderClass(); var clausebuilderObj = clauseBuilderIndex.getClauseBuilder(filter,self.settings.bucket,model); var clause = clausebuilderObj.buildClause(); qryString = clause; - debug("CouchbaseDB.prototype.all() final query is : " + qryString); + debug("CouchbaseDB.prototype.all() final query is : " + qryString); cbquery(qryString,self,function(err,res){ if(err){ callback(err,null); @@ -337,10 +345,10 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { * Delete all model instances */ CouchbaseDB.prototype.destroyAll = function destroyAll(model, callback) { - debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model])); + debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model])); throw "stop here !"; }; - + /** * Update the attributes for a model instance by id * Will be called when PUT to /model_url_name/{id} @@ -348,7 +356,7 @@ CouchbaseDB.prototype.destroyAll = function destroyAll(model, callback) { CouchbaseDB.prototype.updateAttributes = function updateAttrs(model, id, data, callback) { var self = this; - debug("CouchbaseDB.prototype.updateAttributes() : " + JSON.stringify([model, id, data])); + debug("CouchbaseDB.prototype.updateAttributes() : " + JSON.stringify([model, id, data])); var idkey = id; var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ if(err){ @@ -368,9 +376,11 @@ var updateOneDoc = function(thisRef,myBucket,idkey,model,inboundData,callback){ var props = self._models[model].properties; var dataBuilderIndex = new dataBuilderClass(); + idkey = idkey || inboundData.id; + myBucket.get(idkey,function(err, result) { if(err){ - debug(" CouchbaseDB updateOneDoc() : Can not find the document with the id '" + id + "'" ); + debug(" CouchbaseDB updateOneDoc() : Can not find the document with the id '" + idkey + "'" ); callback(err,null); return; } @@ -399,13 +409,13 @@ var updateOneDoc = function(thisRef,myBucket,idkey,model,inboundData,callback){ */ CouchbaseDB.prototype.escapeName = function (name) { var self = this; - debug("CouchbaseDB.prototype.escapeName() : " + JSON.stringify([name])); + debug("CouchbaseDB.prototype.escapeName() : " + JSON.stringify([name])); return name; }; CouchbaseDB.prototype.toDatabase = function (prop, val, forCreate) { var self = this; - debug("CouchbaseDB.prototype.toDatabase() : " + JSON.stringify([prop, val, forCreate])); + debug("CouchbaseDB.prototype.toDatabase() : " + JSON.stringify([prop, val, forCreate])); }; CouchbaseDB.prototype.toFields = function (model, data) { @@ -422,4 +432,4 @@ CouchbaseDB.prototype.query = function (sql, callback) { var self = this; callback(null,null); -}; \ No newline at end of file +}; From 4e65c862cb7ba4211af8e0b2ca1ed9c6a2768ec7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 3 Feb 2015 11:30:33 -0600 Subject: [PATCH 03/43] Now does not use N1QL anymore when the document id is already given --- lib/cb.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index bffe7e1..6092c08 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -170,7 +170,7 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { return; } debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); - callback(err,result); + callback(err,result.value); return; }); }); @@ -325,20 +325,23 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { debug("CouchbaseDB.prototype.all() : " + JSON.stringify([model, filter])); var self = this; + + // If the "where" has an id set, the rest is not really interesting anymore because it can be just + // one document that matches and that is the one we return + if (filter.where && filter.where.id) { + self.find(model, filter.where.id, function(err, res) { + callback(err, [res]) + }); + return; + } + var clauseBuilderIndex = new clauseBuilderClass(); var clausebuilderObj = clauseBuilderIndex.getClauseBuilder(filter,self.settings.bucket,model); var clause = clausebuilderObj.buildClause(); qryString = clause; debug("CouchbaseDB.prototype.all() final query is : " + qryString); - cbquery(qryString,self,function(err,res){ - if(err){ - callback(err,null); - return; - } - callback(err,res); - return; - }); + cbquery(qryString,self,callback); }; /** From 6d3e980aa32f8373e116d2350cdf2b8ea8a1a69c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 3 Feb 2015 13:23:59 -0600 Subject: [PATCH 04/43] Removed not needed parameters from prepareCreateData --- lib/cb.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 6092c08..4cc8733 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -188,7 +188,7 @@ CouchbaseDB.prototype.create = function (model, data, callback) { var self = this; debug("CouchbaseDB.prototype.create() : " + JSON.stringify([model, data])); - var createDataBuilderObj = prepareCreateData(data,self,model,callback); + var createDataBuilderObj = prepareCreateData(data, model); var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ @@ -266,7 +266,7 @@ CouchbaseDB.prototype.updateOrCreate = function updateOrCreate(model, data, call debug("CouchbaseDB.prototype.updateOrCreate() : " + JSON.stringify([model, data])); var self = this; - var createDataBuilderObj = prepareCreateData(data,self,model,callback); + var createDataBuilderObj = prepareCreateData(data, model); var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ if(err){ @@ -281,8 +281,7 @@ CouchbaseDB.prototype.updateOrCreate = function updateOrCreate(model, data, call /* Calls CreateDataBuilder to prepared the inbound data that is to be inserted */ -var prepareCreateData=function(inboundData,thisRef,model,callback){ - var self = thisRef; +var prepareCreateData = function(inboundData, model){ var dataBuilderIndex = new dataBuilderClass(); var createDataBuilderObj = dataBuilderIndex.getCreateDataBuilder(inboundData,model); From 7093a2aa49fefb53ddf577ec726d84c5959b9e28 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 3 Feb 2015 13:34:55 -0600 Subject: [PATCH 05/43] Create returns now just the id like the connector expect it to. So that created and queried documents both have the id as string. --- lib/cb.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 4cc8733..bb02020 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -202,10 +202,15 @@ CouchbaseDB.prototype.create = function (model, data, callback) { callback(err,null); return; } - result = createDataBuilderObj.setImplicitFieldsInResult(data,result); - debug("CouchbaseDB.prototype.create() : added id/doctype,now result is: " + JSON.stringify([result]) ); - callback(err,result); + // Deactivated for now because the value which gets returned gets set as the id. Impossible to set other ones. + // So we also just return the id like the connector expects. + //result = createDataBuilderObj.setImplicitFieldsInResult(data,result); + //debug("CouchbaseDB.prototype.create() : added id/doctype,now result is: " + JSON.stringify([result]) ); + + debug("CouchbaseDB.prototype.create() : created document: " + JSON.stringify([result]) ); + + callback(err,result && result.id); }); }); From 11dcf6e503fe9ff71b0fc6a29ed92543bd420f11 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 3 Feb 2015 20:34:38 -0600 Subject: [PATCH 06/43] Do not error anymore when an id does not exist --- lib/cb.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index bb02020..d16f359 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -166,8 +166,15 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { myBucket.get(id,function(err, result) { if(err){ - callback(err,null); - return; + // Error code 13 is "The key does not exist on the server". Because Loopback does not treat that as an error + // we will neither to be consistent. + if (err.code == 13) { + callback(null, null); + return; + } else { + callback(err, null); + return; + } } debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); callback(err,result.value); From c9961c8a90d42ec6638723a69190641e4731ae2f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 3 Feb 2015 21:13:19 -0600 Subject: [PATCH 07/43] Fixed issue that it did return an empty array instead of null when id did not exist --- lib/cb.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index d16f359..fc21813 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -341,7 +341,12 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { // one document that matches and that is the one we return if (filter.where && filter.where.id) { self.find(model, filter.where.id, function(err, res) { - callback(err, [res]) + // Result has to be an array but only if it really contains any data. + if (res) { + callback(err, [res]) + } else { + callback(err, null) + } }); return; } From 7f8078bddf45e9798a205a249acd95b5bf7eb055 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 5 Feb 2015 12:19:31 -0600 Subject: [PATCH 08/43] Does now not anymore query the document before it gets saved --- lib/cb.js | 48 ++++++++++-------------- package.json | 101 +++++++++++++++++++++++++++------------------------ 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index fc21813..5ab5c7c 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -9,6 +9,7 @@ var dataBuilderClass = require("./dataBuilder"); var constants = require('./base/constants.js'); var uuid = require('node-uuid'); var debug = require('debug')('connector:couchbase'); +var extend = require('node.extend'); /** * @module loopback-connector-couchbase @@ -235,7 +236,7 @@ CouchbaseDB.prototype.save = function (model, data, callback) { callback(err,null); return; } - updateOneDoc(self,myBucket,undefined,model,data,callback); + updateOneDoc(myBucket,undefined,model,data,callback); }); }; @@ -285,7 +286,7 @@ CouchbaseDB.prototype.updateOrCreate = function updateOrCreate(model, data, call callback(err,null); return; } - updateOneDoc(self,myBucket,createDataBuilderObj.getIdKey(),model,createDataBuilderObj.getPreparedData(),callback); + updateOneDoc(myBucket,createDataBuilderObj.getIdKey(),model,createDataBuilderObj.getPreparedData(),callback); }); }; @@ -341,7 +342,7 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { // one document that matches and that is the one we return if (filter.where && filter.where.id) { self.find(model, filter.where.id, function(err, res) { - // Result has to be an array but only if it really contains any data. + // Result has to be an if (res) { callback(err, [res]) } else { @@ -382,44 +383,35 @@ CouchbaseDB.prototype.updateAttributes = function updateAttrs(model, id, data, c callback(err,null); return; } - updateOneDoc(self,myBucket,idkey,model,data,callback); + updateOneDoc(myBucket,idkey,model,data,callback); }); }; -/* +/** Updates one document, do not replace the fields that are preserved */ +var updateOneDoc = function(myBucket,idkey,model,inboundData,callback){ + idkey = idkey || inboundData.id; -var updateOneDoc = function(thisRef,myBucket,idkey,model,inboundData,callback){ - var self = thisRef; - var props = self._models[model].properties; - var dataBuilderIndex = new dataBuilderClass(); + // Copy the data so that we do not change any outside values + var preparedData = extend({}, inboundData); - idkey = idkey || inboundData.id; + // Make sure that id and document-type get preserved so simply set them + preparedData.id = idkey; + preparedData.docType = model; + + // This gets later used to set the cas + var options = {}; - myBucket.get(idkey,function(err, result) { + // Now actually update it + myBucket.upsert(idkey, preparedData, options, function(err, result) { if(err){ - debug(" CouchbaseDB updateOneDoc() : Can not find the document with the id '" + idkey + "'" ); callback(err,null); return; } - //found the document - debug(" CouchbaseDB updateOneDoc() : found the docuemnt: " + JSON.stringify([err, result]) ); - var updateDataBuilderObj = dataBuilderIndex.getUpdateDataBuilder(props,inboundData,result["value"]); - var preparedData = updateDataBuilderObj.buildData(); - myBucket.upsert(idkey,preparedData,function(err, result) { - if(err){ - callback(err,null); - return; - } - debug(" CouchbaseDB updateOneDoc() : updateAttributes result is: " + JSON.stringify([err, result]) ); - callback(err,result); - }); - - + debug(" CouchbaseDB updateOneDoc() : updateAttributes result is: " + JSON.stringify([err, result]) ); + callback(err,result); }); - - }; /** diff --git a/package.json b/package.json index ba53542..fd5677d 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,56 @@ { - "name": "loopback-connector-couchbase", - "version": "1.0.4", - "description": "LoopBack Couchbase Connector", - "keywords": [ - "StrongLoop", - "LoopBack", - "Couchbase", - "DataSource", - "Connector" - ], - "main": "index.js", - "scripts": { - "test": "make test" + "name": "loopback-connector-couchbase", + "version": "1.0.4", + "description": "LoopBack Couchbase Connector", + "keywords": [ + "StrongLoop", + "LoopBack", + "Couchbase", + "DataSource", + "Connector" + ], + "main": "index.js", + "scripts": { + "test": "make test" + }, + "repository": { + "type": "git", + "url": "https://github.com/guardly/loopback-connector-couchbase.git" + }, + "dependencies": { + "async": "~0.9.0", + "couchbase": "^2.0.1", + "debug": "^1.0.4", + "loopback": "^2.8.6", + "loopback-connector": "^1.2.0", + "loopback-datasource-juggler": "^2.12.0", + "node-dir": "^0.1.6", + "node-uuid": "^1.4.1", + "node.extend": "^1.1.3" + }, + "devDependencies": { + "rc": "~0.4.0" + }, + "license": { + "name": "MIT", + "url": "https://github.com/guardly/loopback-connector-couchbase/blob/master/LICENSE.md" + }, + "bugs": { + "url": "https://github.com/guardly/loopback-connector-couchbase/issues" + }, + "homepage": "https://github.com/guardly/loopback-connector-couchbase", + "maintainers": [ + { + "name": "ecguardly", + "email": "eric@guardly.com" }, - "repository": { - "type": "git", - "url": "https://github.com/guardly/loopback-connector-couchbase.git" + { + "name": "nolandubeau", + "email": "nolan@guardly.com" }, - "dependencies": { - "async": "~0.9.0", - "couchbase": "^2.0.1", - "debug": "^1.0.4", - "loopback": "^2.8.6", - "loopback-connector": "^1.2.0", - "loopback-datasource-juggler": "^2.12.0", - "node-dir": "^0.1.6", - "node-uuid": "^1.4.1" - }, - "devDependencies": { - "rc": "~0.4.0" - }, - "license": { - "name": "MIT", - "url": "https://github.com/guardly/loopback-connector-couchbase/blob/master/LICENSE.md" - }, - "bugs": { - "url": "https://github.com/guardly/loopback-connector-couchbase/issues" - }, - "homepage": "https://github.com/guardly/loopback-connector-couchbase", - "maintainers": [{ - "name": "ecguardly", - "email": "eric@guardly.com" - }, { - "name": "nolandubeau", - "email": "nolan@guardly.com" - }, { - "name": "jbroughton72", - "email": "jason@guardly.com" - }] -} \ No newline at end of file + { + "name": "jbroughton72", + "email": "jason@guardly.com" + } + ] +} From 979fa87f500742a6dcc0c5e8b5653e9166e7f0bf Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 9 Feb 2015 18:55:13 -0600 Subject: [PATCH 09/43] Removed all the N1QL code and made it work with views instead --- README.md | 98 ++++++----- lib/cb.js | 180 ++++++++++++-------- lib/clauseBuilder/ClauseBuilder.js | 130 -------------- lib/clauseBuilder/FieldsBuilder.js | 34 ---- lib/clauseBuilder/IncludeBuilder.js | 14 -- lib/clauseBuilder/LimitBuilder.js | 28 --- lib/clauseBuilder/OrderBuilder.js | 56 ------ lib/clauseBuilder/SkipBuilder.js | 27 --- lib/clauseBuilder/WhereBuilder.js | 136 --------------- lib/clauseBuilder/WhereSimpleElemBuilder.js | 92 ---------- lib/clauseBuilder/index.js | 26 --- lib/test/test.js | 133 --------------- package.json | 26 +-- 13 files changed, 167 insertions(+), 813 deletions(-) delete mode 100644 lib/clauseBuilder/ClauseBuilder.js delete mode 100644 lib/clauseBuilder/FieldsBuilder.js delete mode 100644 lib/clauseBuilder/IncludeBuilder.js delete mode 100644 lib/clauseBuilder/LimitBuilder.js delete mode 100644 lib/clauseBuilder/OrderBuilder.js delete mode 100644 lib/clauseBuilder/SkipBuilder.js delete mode 100644 lib/clauseBuilder/WhereBuilder.js delete mode 100644 lib/clauseBuilder/WhereSimpleElemBuilder.js delete mode 100644 lib/clauseBuilder/index.js diff --git a/README.md b/README.md index 5319291..6ef8b55 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,21 @@ -## loopback-connector-couchbase +## loopback-connector-couchbase without N1QL The Couchbase Connector module for for [loopback-datasource-juggler](http://docs.strongloop.com/loopback-datasource-juggler/). +This connector does not use N1QL. That means you can not simply lookup data you do not have view setup and defined in the configuration. This means you have to be very careful which data you query. If you for example have a relationship setup and you want to use it to query related data it will fail till you create * setup a view which allows it. Actually querying data will probably almost always fail if you do not use "findById". So you have to write your application a very specific way for it to work but the price for that effort is high performance. So for the most people using the N1QL implementation (from which I did fork) is probably better: https://github.com/guardly/loopback-connector-couchbase + +Btw. is also not really well tested. Did just fork to work best for my own needs of high performance (and no random query need). ### What is Couchbase Server? Couchbase Server is a NoSQL document database for interactive web applications. It has a flexible data model, is easily scalable, provides consistent high performance and is "always-on", meaning it can serve application data 24 hours, 7 days a week. -### What is N1QL? -N1QL (pronounced "Nickel") is a next generation query language for Couchbase Server. N1QL presents easy and familiar abstractions to quickly develop scalable applications that work with next generation database systems. It allows for joins, filter expressions, aggregate expressions and many other features to build a rich application. - - ### Getting Started -Before installing the connector module, make sure you've taken the appropriate steps to install and configure Couchbase Server and the N1QL Engine. +Before installing the connector module, make sure you've taken the appropriate steps to install and configure Couchbase Server. * Download and install [Couchbase Server](http://www.couchbase.com/nosql-databases/downloads). * After the install completes confirm the Couchbase Administrator is running at http://localhost:8091 -* Download the [N1QL Binaries](http://www.couchbase.com/nosql-databases/downloads#PreRelease) - - -### N1QL Tutorial -N1QL has an interactive online tutorial and a N1QL cheatsheet. The online tutorial is a good way to get started and play with N1QL in an easy, fun environment. The cheatsheet is a quick glance and easy reference of the N1QL syntax. NOTE: Not all capabilities of the N1QL Engine are supported in this connector. For more information on N1QL, see: - -* [N1QL Cheatsheet](http://docs.couchbase.com/files/Couchbase-N1QL-CheatSheet.pdf) -* [Couchbase Language Tutorial](http://query.pub.couchbase.com/tutorial/#1) - - -### Running N1QL -To run N1QL on your local system: - -Step 1: Expand the package archive. -Step 2: On the command line, navigate to your local N1QL directory. -Step 3: Run ./start_tutorial.sh (Unix) - ./start_tutorial.bat (Windows) -Step 4: Open http://localhost:8093/tutorial in your browser to use the tutorial on your local server. - -To connect N1QL with your Couchbase Server: - - ./cbq-engine -datastore=http://[server_name]:8091/ - - -To use the command-line interactive query tool: - - ./cbq -engine=http://[couchbase-query-engine-server-name]:8093/ - - -Step 5: Before issuing queries against a Couchbase bucket, run the following command from the query command line: - - CREATE PRIMARY INDEX ON [bucket-name]; - ### Installing the connector @@ -63,12 +29,55 @@ Step 5: Before issuing queries against a Couchbase bucket, run the following com The connector can be configured using the following settings from the data source. * host (default to 'localhost'): The host name or ip address of the Couchbase server * port (default to 8091): The port number of the Couchbase server -* n1qlport (default to 8093): The port number of the N1QL Engine * database: The Couchbase bucket * connectionTimeout (default to 20000): The connection timeout value * operationTimeout (default to 15000): The operation timeout value +* mappings: Prefixes for values which get used as key (example below) +* views: Views which can be used to loopup data + +**NOTE**: Unlike other datasources, Couchbase does not require user credentials to access a bucket/database. Buckets can be protected with a password. -**NOTE**: Unlike other datasources, Couchbase does not require user credentials to access a bucket/database. Buckets can be protected with a password, however, the N1QL Developer Pre-release 3 currently does not support querying password protected buckets. As this capability is released for N1QL, we will update the connector settings. + +## Example Configuration + +```json + { + "couchbase": { + "host": "localhost", + "port": "8091", + "database": "myBucket", + "name": "couchbase", + "connector": "couchbase", + "mappings": { + "user": { + "email": "u::" + } + }, + "views": { + "Orders": { + "userId": { + "designDocument": "loopback", + "viewName": "orders_userid" + } + } + } + } + } + +``` + +Everything should be clear the only interesting parts are mappings & views. + +* "mappings" defines id-prefixes. So if somebody queries for a user by email then the query will be rewritten to a lookup by id. So if I look for {"email": "test@example.com"} it will do instead a look for the id: "u::test@example.com" +* "views" defines views which can be used to loopup specific values. The example bellow allows to lookup "order" by "userId" by using the view "orders_userid" under a design-document called "loopback". For it to work a view like this has to be setup: + +```json + function (doc, meta) { + if (doc.docType && doc.docType == "Order" && doc.userId) { + emit(doc.userId, null); + } + } +``` ## Model definition for Couchbase Documents @@ -112,17 +121,12 @@ The model definition consists of the following properties: ``` -### Example Application -We have also put together a small example application which uses the **beer-sample** bucket which is an optional add-on when installing Couchbase Server. There is currently no client front-end for the example app, however you can use the LoopBack Explorer to interact with the API and connector. - -* [loopback-example-couchbase](https://github.com/guardly/loopback-example-couchbase): Example application - ### Working with data Please refer to the official [LoopBack Documentation](http://docs.strongloop.com/display/public/LB/Working+with+data) for how to work with models and data. ### More to come!!! -* Write additional tests +* Write tests * Improve debugging and logging * etc!!! diff --git a/lib/cb.js b/lib/cb.js index 5ab5c7c..8714b52 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -1,10 +1,8 @@ var couchbase = require('couchbase'); -var N1qlQuery = couchbase.N1qlQuery; //var SqlConnector = require('loopback-connector').SqlConnector; var Connector = require('loopback-connector').Connector; var async = require('async'); var debug = require('debug')('loopback:connector:couchbase'); -var clauseBuilderClass = require("./clauseBuilder"); var dataBuilderClass = require("./dataBuilder"); var constants = require('./base/constants.js'); var uuid = require('node-uuid'); @@ -32,14 +30,14 @@ exports.initialize = function initializeDataSource(dataSource, callback) { host: s.host || 'localhost', port: s.port || 8091, password : s.password || '', - n1qlport: s.n1qlport || 8093, bucket: s.database || 'default', env: s.env || 'debugging', connectionTimeout: s.connectionTimeout || 20000, - operationTimeout: s.operationTimeout || 15000 + operationTimeout: s.operationTimeout || 15000, + mappings: s.mappings, + views: s.views }; options.connectUrl = "couchbase://" + options.host; //do not attach any port number such as 8901, will cause error code 24 which means "Error while establishing TCP connection" - options.n1qlUrl = options.host + ":" + options.n1qlport; debug("couchbase connector initializeDataSource(): options:" + JSON.stringify([options])); //initializes the Couchbase connector: @@ -81,43 +79,67 @@ CouchbaseDB.prototype.connect = function (callback) { }); } else { self.myCluster = new couchbase.Cluster(self.settings.connectUrl); + self.viewQuery = couchbase.ViewQuery; callback && callback(null, self.db); } }; + + /** - * Does the common query to the couchbase server and return the result + * Does the view query to the couchbase server and return the result * - * @param {String} sql The SQL string which follows the N1QL syntax + * @param {String} query The ViewQuery object * @param {Object} selfThis The reference to the "this" of this module * @param {Function} [callback] The callback function */ -var cbquery = function (sql,selfThis, callback) { - var self = selfThis; - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ - if(err){ - callback(err,null); - return; - } - - myBucket.enableN1ql([self.settings.n1qlUrl]); - - var query = N1qlQuery.fromString(sql); - debug("cbquery() : query is: " + JSON.stringify([query]) ); - - myBucket.query(query, function(err, res) { - if (err) { - debug("cbquery() : query failed" + JSON.stringify([err, res])); - callback(err,null); - return; - } - debug("cbquery() : success!" + JSON.stringify([err, res])); - callback(err,res); - return; - }); - }); +var cbqueryView = function (query, selfThis, callback) { + var self = selfThis; + + var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + if(err){ + callback(err,null); + return; + } + + debug("cbqueryView() : query is: " + JSON.stringify([query]) ); + + myBucket.query(query, function(err, res) { + if (err) { + debug("cbqueryView() : query failed" + JSON.stringify([err, res])); + callback(err,null); + return; + } + debug("cbqueryView() : view success!" + JSON.stringify([err, res])); + + // Get all the ids of the documents we found + var ids = res.map(function(document) {return document.id}); + + // Query all the documents at once + myBucket.getMulti(ids,function(err, result) { + if(err){ + callback(err, null); + return; + } + + var returnData = []; + + // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value + for (var id in result) { + var documentData = result[id]; + documentData.value._cas = documentData.cas; + returnData.push(documentData.value); + } + + debug("cbqueryView() : success!" + JSON.stringify([err, result])); + callback(err, returnData); + return; + }); + }); + }); }; + /** * Find matching model instances by the filter * @@ -129,27 +151,8 @@ var cbquery = function (sql,selfThis, callback) { * Count the model instances by the where criteria */ CouchbaseDB.prototype.count = function (model, callback, where) { - - var self = this; - - var clauseBuilderIndex = new clauseBuilderClass(); - - var clausebuilderObj = clauseBuilderIndex.getClauseBuilder(where,self.settings.bucket,model); - var clause = clausebuilderObj.buildClause('SELECT count(*) AS cnt FROM '); - - - - qryString = clause; - - cbquery(qryString,self,function(err,res){ - if(err){ - callback(err,null); - return; - } - var c = (res && res[0] && res[0].cnt) || 0; - callback(err, c); - return; - }); + debug("CouchbaseDB.prototype.count() : " + JSON.stringify([model, where])); + throw "Count is not implemented!"; }; /** @@ -338,27 +341,64 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { var self = this; - // If the "where" has an id set, the rest is not really interesting anymore because it can be just - // one document that matches and that is the one we return + var mappings = self.settings.mappings; + var views = self.settings.views; + var keys = Object.keys(filter.where); + + + var documentId = undefined; if (filter.where && filter.where.id) { - self.find(model, filter.where.id, function(err, res) { - // Result has to be an - if (res) { - callback(err, [res]) - } else { - callback(err, null) - } - }); - return; - } + // If the "where" has an id set, the rest is not really interesting anymore because it can be just + // one document that matches and that is the one we return + + documentId = filter.where.id; + } else if (keys.length == 1 && mappings.hasOwnProperty(model) && mappings[model].hasOwnProperty(keys[0])) { + var prefix = mappings[model][keys[0]]; + var documentId = prefix + filter.where[keys[0]]; + } + + if (documentId) { + // If we were able to get the documentId we can simply do a loopup now + + // Get the document and return it + self.find(model, documentId, function (err, res) { + // Result has to be an + if (res) { + callback(err, [res]) + } else { + callback(err, null) + } + }); + + return; + } + + // Check if we can do the query + if (keys.length == 1 && views.hasOwnProperty(model) && views[model].hasOwnProperty(keys[0])) { + // Only if we are looking for exactly one key and there is a view defined for it we can look it up + + // Get the name of the view and design-document which we can use to query the data + var viewName = views[model][keys[0]].viewName; + var designDocument = views[model][keys[0]].designDocument; - var clauseBuilderIndex = new clauseBuilderClass(); - var clausebuilderObj = clauseBuilderIndex.getClauseBuilder(filter,self.settings.bucket,model); - var clause = clausebuilderObj.buildClause(); + // Get the we are looking for + var keyValue = filter.where[keys[0]]; - qryString = clause; - debug("CouchbaseDB.prototype.all() final query is : " + qryString); - cbquery(qryString,self,callback); + // Build now the query with the given data + var query = self.viewQuery.from(designDocument, viewName).key(keyValue); + if (filter.limit) { + query.limit(filter.limit); + } + if (filter.skip) { + query.skip(filter.skip); + } + + // Now run the query and return the data + cbqueryView(query, self, callback); + } else { + // If there are more keys or if we do not have a view we stop (so that it is quite apparent in the development phase) + throw "This query is currently not supported: " + JSON.stringify(filter); + } }; /** diff --git a/lib/clauseBuilder/ClauseBuilder.js b/lib/clauseBuilder/ClauseBuilder.js deleted file mode 100644 index a9618cb..0000000 --- a/lib/clauseBuilder/ClauseBuilder.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - The public interface of the builder which interprets and create all the clause - This is the main entrance of building EVERY parts of the SELECT statement - - All the work has to go through here -*/ - -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var fieldsBuilderClass = require('./FieldsBuilder.js'); -var includeBuilderClass = require('./IncludeBuilder.js'); -var limitBuilderClass = require('./LimitBuilder.js'); -var orderBuilderClass = require('./OrderBuilder.js'); -var skipBuilderClass = require('./SkipBuilder.js'); -var whereBuilderClass = require('./WhereBuilder.js'); -var debug = require('debug')('connector:builder'); - -var ClauseBuilder = function(conds,defaultBucket,currentModel){ - var originalModel = currentModel; - var originalConds = conds; - var originalDefaultBucket = defaultBucket; - - this.getCurrentModel = function(){ - return originalModel; - }; - this.getConditions = function(){ - return originalConds; - }; - this.getDefaultBucket = function(){ - return originalDefaultBucket; - }; -}; -util.inherits(ClauseBuilder, BaseModel); - -/** - When no condition was specified (no WHERE, LIMIT, ORDER,FIELD etc... specified), we should at least limits the searching scope due to docType -*/ -var GetNoConditionWhereClause = function(constantDocType, selectStatement,defaultBucket,model){ - var returnval = ''; - if(selectStatement){ - returnval = selectStatement + defaultBucket; - } - if(model){ returnval = returnval + " WHERE (" + constantDocType + "='" + model + "')";} - return returnval; -}; - -/** - The main entrance method interpreting and building the clauses -*/ -ClauseBuilder.prototype.buildClause = function(selectStatement){ - var self = this; - var conds = this.getConditions(); - var model = this.getCurrentModel(); - var defaultBucket = this.getDefaultBucket(); - var constants = this.getConstants(); - - if(!conds){ - return GetNoConditionWhereClause(constants.KEYWORD_CONVENTION_DOCTYPE,selectStatement,defaultBucket,model); - } - var fieldsBuilderObj = null; - var includeBuilderObj = null; - var limitBuilderObj = null; - var orderBuilderObj = null; - var skipBuilderObj = null; - var whereBuilderObj = null; - - var finalClause = ''; - debug("ClauseBuilder.prototype.buildClause() conds and model: " + JSON.stringify([conds,model]) ); - Object.keys(conds).forEach(function (key) { - debug(" Doing the key: " + key); - if (self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_WHERE)) - { - whereBuilderObj = new whereBuilderClass(conds[key],model); - }else if(self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_FIELDS)){ - fieldsBuilderObj = new fieldsBuilderClass(conds[key]); - }else if(self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_INCLUDE)){ - - }else if(self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_LIMIT)){ - limitBuilderObj = new limitBuilderClass(conds[key]); - }else if(self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_ORDER)){ - orderBuilderObj = new orderBuilderClass(conds[key]); - }else if(self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_SKIP) || self.stringEqualNoCase(key,constants.KEYWORD_LEVEL1_OFFSET)){ - skipBuilderObj = new skipBuilderClass(conds[key]); - - }else{ - debug(" treating else key: " + key); - whereBuilderObj = new whereBuilderClass(conds,model); - } - - }); - finalClause = "SELECT * FROM "; - if(selectStatement){ finalClause = selectStatement ;} - if(fieldsBuilderObj){ - finalClause = fieldsBuilderObj.buildClause(); - } - if(includeBuilderObj){ - finalClause = finalClause + self.escapeFieldWithBackTick(defaultBucket) +" "; - - }else{ - finalClause = finalClause + self.escapeFieldWithBackTick(defaultBucket) +" "; - - } - debug("ClauseBuilder.prototype.buildClause() now whereBuilderObj and model: " + JSON.stringify([whereBuilderObj,model]) ); - - if(!whereBuilderObj && model){ - whereBuilderObj = new whereBuilderClass(null,model); - } - if(whereBuilderObj){ - finalClause = finalClause + whereBuilderObj.buildClause(); - } - - if(orderBuilderObj){ - finalClause = finalClause + " " + orderBuilderObj.buildClause(); - } - - if(limitBuilderObj){ - finalClause = finalClause + " " + limitBuilderObj.buildClause(); - } - - if(skipBuilderObj){ - finalClause = finalClause + " " + skipBuilderObj.buildClause(); - } - - debug("ClauseBuilder.prototype.buildClause() finalClause: " + finalClause); - - return finalClause; -}; - -module.exports = ClauseBuilder; - diff --git a/lib/clauseBuilder/FieldsBuilder.js b/lib/clauseBuilder/FieldsBuilder.js deleted file mode 100644 index 4042e25..0000000 --- a/lib/clauseBuilder/FieldsBuilder.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - The public interface of the builder which interprets and converts the FIELDS keyword - This is the main entrance of building FIELDS part -*/ -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var spaces = " ";//4 spaces -var debug = require('debug')('connector:builder'); -var FieldsBuilder = function(fields){ - - var currentFields = fields; - this.getFields = function(){ - return currentFields; - }; -}; -util.inherits(FieldsBuilder, BaseModel); - -FieldsBuilder.prototype.buildClause = function(){ - var self = this; - var fields = this.getFields(); - var finalClause = "SELECT "; - var i = 0; - - for(i=0;i0){ - finalClause = finalClause + ","; - } - finalClause = finalClause + self.escapeFieldWithBackTick(fields[i]); - } - finalClause = finalClause + " FROM "; - return finalClause; -}; - -module.exports = FieldsBuilder; \ No newline at end of file diff --git a/lib/clauseBuilder/IncludeBuilder.js b/lib/clauseBuilder/IncludeBuilder.js deleted file mode 100644 index ca2f70b..0000000 --- a/lib/clauseBuilder/IncludeBuilder.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - The public interface of the builder which interprets and converts the INCLUDE keyword - This is the main entrance of building INCLUDE part -*/ -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var debug = require('debug')('connector:builder'); -var spaces = " ";//4 spaces -var IncludeBuilder = function(){ - -}; -util.inherits(IncludeBuilder, BaseModel); - -module.exports = IncludeBuilder; \ No newline at end of file diff --git a/lib/clauseBuilder/LimitBuilder.js b/lib/clauseBuilder/LimitBuilder.js deleted file mode 100644 index e77c547..0000000 --- a/lib/clauseBuilder/LimitBuilder.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - The public interface of the builder which interprets and converts the LIMIT keyword - This is the main entrance of building LIMIT part -*/ -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var spaces = " ";//4 spaces -var debug = require('debug')('connector:builder'); -var LimitBuilder = function(limitnumber){ - var currentLimitNumber = limitnumber; - this.getLimitNumber = function(){ - return currentLimitNumber; - }; - -}; -util.inherits(LimitBuilder, BaseModel); - -/** - The main entrance method interpreting and building the clauses -*/ -LimitBuilder.prototype.buildClause = function(){ - var self = this; - var limitNum = this.getLimitNumber(); - var constants = this.getConstants(); - return constants.KEYWORD_LEVEL1_LIMIT.toUpperCase() + " " + limitNum; -}; - -module.exports = LimitBuilder; \ No newline at end of file diff --git a/lib/clauseBuilder/OrderBuilder.js b/lib/clauseBuilder/OrderBuilder.js deleted file mode 100644 index d509a08..0000000 --- a/lib/clauseBuilder/OrderBuilder.js +++ /dev/null @@ -1,56 +0,0 @@ - -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var debug = require('debug')('connector:builder'); -var spaces = " ";//4 spaces -var OrderBuilder = function(orderList){ - var currentOrderList = orderList; - this.getOrderList = function(){ - return currentOrderList; - }; -}; -util.inherits(OrderBuilder, BaseModel); - -/** - The main entrance method interpreting and building the clauses -*/ -OrderBuilder.prototype.buildClause = function(){ - var self = this; - var orderList = this.getOrderList(); - var finalClause = "ORDER BY"; - - if(typeof orderList == "string"){ - finalClause = finalClause + " " + escapeOneOrderBy(orderList,self); - }else{ - var i = 0; - for(i=0;i0){ - finalClause = finalClause + ' ' + keyName.toUpperCase() + ' '; - } - Object.keys(currentConds).forEach(function (key) { - if (self.stringEqualNoCase(key,constants.KEYWORD_LEVEL2_AND) || self.stringEqualNoCase(key,constants.KEYWORD_LEVEL2_OR)){ - finalClause = readAndOr(currentConds[key],self,constants,finalClause,key); - }else{ - finalClause = readSimpleElement(currentConds,self,constants,finalClause); - } - }); - } - finalClause = finalClause + ')'; - return finalClause; -}; - - -var readAndOrPart = function(conds,theThis,constants,keyName,resultClause){ - var self = theThis; - if(resultClause){ - resultClause = resultClause + '('; - }else{ - var resultClause = '('; - } - - var i = 0; - for (i=0;i0){ - resultClause = resultClause + ' ' + keyName.toUpperCase() + ' '; - } - Object.keys(currentConds).forEach(function (key) { - if (self.stringEqualNoCase(key,constants.KEYWORD_LEVEL2_AND) || self.stringEqualNoCase(key,constants.KEYWORD_LEVEL2_OR)){ - resultClause = readAndOrPart(currentConds[key],self,constants,key,resultClause); - }else{ - resultClause = readSimpleElement(currentConds,self,constants,resultClause); - } - }); - } - resultClause = resultClause + ')'; - return resultClause; -}; - -/* - Interprets and converts the simple element (no nested elements) of a where clause -*/ -var readSimpleElement = function(conds,theThis,constants,resultClause){ - var self = theThis; - resultClause = resultClause + readSimpleElementPart(conds,theThis,constants); - return resultClause; -}; -var readSimpleElementPart = function(conds,theThis,constants){ - var self = theThis; - var resultClause = '('; - var whereSimpleElementBuilderObj = new whereSimpleElementBuilderClass(conds); - var simpleElemClause = whereSimpleElementBuilderObj.buildClause(); - - resultClause = resultClause + simpleElemClause + ')'; - return resultClause; -}; - -module.exports = WhereBuilder; - diff --git a/lib/clauseBuilder/WhereSimpleElemBuilder.js b/lib/clauseBuilder/WhereSimpleElemBuilder.js deleted file mode 100644 index 8919184..0000000 --- a/lib/clauseBuilder/WhereSimpleElemBuilder.js +++ /dev/null @@ -1,92 +0,0 @@ - -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var debug = require('debug')('connector:builder'); -var spaces = " ";//4 spaces -var WhereSimpleElementBuilder = function(conds){ - var currentConds = conds; - this.getConditions = function(){ - return currentConds; - }; -}; -util.inherits(WhereSimpleElementBuilder, BaseModel); - -/* - Builds the final N1QL compatible clause -*/ -WhereSimpleElementBuilder.prototype.buildClause = function(){ - var self = this; - var conds = this.getConditions(); - - var allkeys = Object.keys(conds); - if(allkeys.length === 0){ - throw JSON.stringify([conds]) + " does not have valid keys."; - } - - var firstKey = allkeys[0]; - - if(typeof conds[firstKey] === 'object'){ - return readObjectElement(firstKey,conds[firstKey],self); - }else{ - return readkeyValuePair(firstKey,conds[firstKey],self); - } - return ""; -}; - -/* - for the clause like "keyname":"value" only -*/ -var readkeyValuePair = function(keyname,val,self){ - var finalClause = self.escapeFieldWithBackTick(keyname) + "="; //would contain the whole clause that should be returned to the caller side - finalClause = concatValuePart(finalClause,val); - return finalClause; -}; - -/* - decides how to concat the value part of the key-value pair -*/ -var concatValuePart = function(finalClause,val){ - if(typeof val === 'string'){ - finalClause = finalClause + "'" + val +"'"; - }else if(typeof val === 'boolean' || typeof val === 'number'){ - finalClause = finalClause + val; - }else{ - throw JSON.stringify([val]) + " has invalid type : " + typeof val; - } - return finalClause; -}; - -var readObjectElement = function(keyname,conds,self){ - var finalClause = self.escapeFieldWithBackTick(keyname) + " "; //would contain the whole clause that should be returned to the caller side - var allkeys = Object.keys(conds); - if(allkeys.length === 0){ - throw JSON.stringify([conds]) + " does not have valid keys."; - } - var firstKey = allkeys[0]; - var constants = self.getConstants(); - if (self.stringEqualNoCase(firstKey,constants.KEYWORD_LEVEL3_GREAT_THAN)){ - finalClause = finalClause + ">"; - finalClause = concatValuePart(finalClause,conds[firstKey]); - }else if (self.stringEqualNoCase(firstKey,constants.KEYWORD_LEVEL3_LESS_THAN)){ - finalClause = finalClause + "<"; - finalClause = concatValuePart(finalClause,conds[firstKey]); - }else if (self.stringEqualNoCase(firstKey,constants.KEYWORD_LEVEL3_LIKE)){ - finalClause = finalClause + " " + firstKey.toUpperCase() + " '" + conds[firstKey] + "'"; - }else if (self.stringEqualNoCase(firstKey,constants.KEYWORD_LEVEL3_BETWEEN)){ - var arrVals = conds[firstKey]; - if(arrVals.length < 2){ - throw "the values of the "+ firstKey + " statement are wrong: " + JSON.stringify([conds]); - } - finalClause = finalClause + " " + firstKey.toUpperCase() + " "; - finalClause = concatValuePart(finalClause,arrVals[0]); - finalClause = finalClause + " AND "; - finalClause = concatValuePart(finalClause,arrVals[1]); - - }else{ - throw firstKey + " is an unknown key"; - - } - return finalClause; -}; - -module.exports = WhereSimpleElementBuilder; \ No newline at end of file diff --git a/lib/clauseBuilder/index.js b/lib/clauseBuilder/index.js deleted file mode 100644 index 533e0e2..0000000 --- a/lib/clauseBuilder/index.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - The public interface which initializes and returns the instance of the builders -*/ -var BaseModel = require('../base/BaseModel.js'); -var debug = require('debug')('connector:builder'); -var util = require('util'); -var builderFactory = function(){ - -}; - -util.inherits(builderFactory, BaseModel); - - -/** - Initializes and returns the instance of a ClauseBuilder class. - This would be the main entrance of each interpreting work - - This parameter "conds" is a JSON object containing all the select statement parts. -*/ - -builderFactory.prototype.getClauseBuilder = function(conds,defaultBucket,currentModel){ - var clauseBuilderClass = require('./ClauseBuilder.js'); - return new clauseBuilderClass(conds,defaultBucket,currentModel); -}; - -module.exports = builderFactory; \ No newline at end of file diff --git a/lib/test/test.js b/lib/test/test.js index 64b4cf3..e69de29 100644 --- a/lib/test/test.js +++ b/lib/test/test.js @@ -1,133 +0,0 @@ -var assert = require("assert"); // node.js core module -var clauseBuilderClass = require("../clauseBuilder"); // node.js core module -var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); -var clauseBuilder = new clauseBuilderClass(); - -var test10 = '{"where":{"title":{"like":"M. st"}}}'; -var test11 = '{"where":{"carClass":"fullsize"}}'; - -var test12 = '{"where":{"date":{"gt":"2014-04-01T18:30:00.000Z"}}}'; -var test13 = '{"where":{"price":{"lt":10}}}'; -var test14 = '{"where":{"price":{"between":["0","7"]}}}'; - -var test20 = '{"where":{"price":{"between":["0","7"]}},"limit":10,"offset":15,"order":["id DESC"],"skip":15}'; -var test30 = '{"where":{"and":[{"title":"My Post"},{"content":"Hello"},{"price":{"between":["0","7"]}}]},"limit":10,"offset":15,"order":["id DESC","name ASC"],"skip":15}'; -var test31 = '{"where":{"and":[{"or":[{"fname":"eric"},{"lname":"chou"},{"location":"toronto"}]},{"content":"Hello"},{"price":{"between":["0","7"]}}]}}'; -var test33 = '{"where":{"and":[{"or":[{"fname":"eric"},{"lname":"chou"},{"location":"toronto"}]},{"content":"Hello"},{"price":{"between":["0","7"]}}]}}'; - -var test32 = '{"where":{"and":[{"or":[{"fname":"eric"},{"lname":"chou"},{"location":"toronto"}]},{"content":"Hello"},{"price":25}]}}'; -var test34 = '{"fields":["id","name","address"],"where":{"and":[{"title":"My Post"},{"content":"Hello"},{"price":{"between":["0","7"]}}]},"limit":10,"offset":15,"order":["id DESC","name ASC"],"skip":15}'; - -var test35 = '{"where":{"customerId":4},"fields":["id","code","label","icon_android","icon_ios","icon_desktop","duress"],"order":["order ASC"]}'; -//var test36 = '{"where":{"id":"67b539c7-6928-4502-a509-427953250860","customerId":4},"limit":1,"offset":0,"skip":0}'; -var test36 = '{"where":{"carClass":"fullsize","carModel":"toyota","and":[{"title":"My Post"},{"content":"Hello"},{"price":{"between":["0","7"]}}]}}';; - -describe('Basic Tests', function(){ - //--- dummy test--- - describe('--Dummy test', function(){ - it('should return -1 when the value is not present', function(){ - assert.equal(-1, [1,2,3].indexOf(4)); // 4 is not present in this array so indexOf returns -1 - }); - }); - - //--- make sure the included module is an object--- - describe('--Check the referenced object', function(){ - it('clauseBuilder should be a valid object', function(){ - assert.equal(typeof clauseBuilder, 'object'); - assert.equal(typeof clauseBuilder.foo, 'function'); - assert.equal(typeof clauseBuilder.getConstants, 'function'); - clauseBuilder.foo(); - }); - }); - - //--- test constant--- - describe('--Test whether the contants could be properly imported', function(){ - it('constant KEYWORD_LEVEL1_WHERE should have the value "where"', function(){ - assert.equal(typeof clauseBuilder.getConstants, 'function'); - var constants = clauseBuilder.getConstants(); - assert.equal(constants.KEYWORD_LEVEL1_WHERE, 'where'); - assert.equal(constants.KEYWORD_LEVEL1_WHERE.toUpperCase()==='WHERE', true); - }); - }); -}); - -describe('Where Clause tests', function(){ - //--- test where clause : test10--- - describe('--Test the initialization of the ClaseBuilder', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test10); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - assert.equal(typeof clauseBuilderObj, 'object'); - var constants = clauseBuilderObj.getConstants(); - assert.equal(constants.KEYWORD_LEVEL1_WHERE, 'where'); - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test11', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test11); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test32', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test32); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test13', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test13); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - }); - }); - - describe('--Test the clauseBuilderObj.buildClause() with test30', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test30); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test34', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test34); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test35', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test35); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test36', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test36); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - - }); - }); - describe('--Test the clauseBuilderObj.buildClause() with test31', function(){ - it('ClaseBuilder instance should be a valid object', function(){ - var jsonConds = JSON.parse(test31); - var clauseBuilderObj = clauseBuilder.getClauseBuilder(jsonConds,"beer-sample","brewery"); - var buildingResult = clauseBuilderObj.buildClause(); - assert.equal(typeof clauseBuilderObj, 'object'); - }); - }); -}); \ No newline at end of file diff --git a/package.json b/package.json index fd5677d..7a4781e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "loopback-connector-couchbase", - "version": "1.0.4", - "description": "LoopBack Couchbase Connector", + "version": "0.1.0", + "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", "LoopBack", @@ -15,7 +15,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/guardly/loopback-connector-couchbase.git" + "url": "https://github.com/janober/loopback-connector-couchbase.git" }, "dependencies": { "async": "~0.9.0", @@ -33,24 +33,10 @@ }, "license": { "name": "MIT", - "url": "https://github.com/guardly/loopback-connector-couchbase/blob/master/LICENSE.md" + "url": "https://github.com/janober/loopback-connector-couchbase/blob/master/LICENSE.md" }, "bugs": { - "url": "https://github.com/guardly/loopback-connector-couchbase/issues" + "url": "https://github.com/janober/loopback-connector-couchbase/issues" }, - "homepage": "https://github.com/guardly/loopback-connector-couchbase", - "maintainers": [ - { - "name": "ecguardly", - "email": "eric@guardly.com" - }, - { - "name": "nolandubeau", - "email": "nolan@guardly.com" - }, - { - "name": "jbroughton72", - "email": "jason@guardly.com" - } - ] + "homepage": "https://github.com/janober/loopback-connector-couchbase" } From 08707a7508a6e5f88a63fb512b0a6d74f6e3c5f7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 9 Feb 2015 18:56:39 -0600 Subject: [PATCH 10/43] Removed the cas-data till the rest is also there --- lib/cb.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 8714b52..3d57478 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -124,10 +124,10 @@ var cbqueryView = function (query, selfThis, callback) { var returnData = []; - // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value + // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value (deactivated for now) for (var id in result) { var documentData = result[id]; - documentData.value._cas = documentData.cas; + //documentData.value._cas = documentData.cas; returnData.push(documentData.value); } From 7856a67dc863e807da873da58f43534f5622d171 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 9 Feb 2015 19:27:32 -0600 Subject: [PATCH 11/43] Now returns and uses the CAS. Will only update if it matches. To make it ignore the CAS it can simply be set to and empty object liek that "whatever._cas = {}". It will then be ignored. --- README.md | 2 +- lib/cb.js | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6ef8b55..11a9143 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Everything should be clear the only interesting parts are mappings & views. * "mappings" defines id-prefixes. So if somebody queries for a user by email then the query will be rewritten to a lookup by id. So if I look for {"email": "test@example.com"} it will do instead a look for the id: "u::test@example.com" * "views" defines views which can be used to loopup specific values. The example bellow allows to lookup "order" by "userId" by using the view "orders_userid" under a design-document called "loopback". For it to work a view like this has to be setup: -```json +``` function (doc, meta) { if (doc.docType && doc.docType == "Order" && doc.userId) { emit(doc.userId, null); diff --git a/lib/cb.js b/lib/cb.js index 3d57478..894c04a 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -127,7 +127,7 @@ var cbqueryView = function (query, selfThis, callback) { // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value (deactivated for now) for (var id in result) { var documentData = result[id]; - //documentData.value._cas = documentData.cas; + documentData.value._cas = documentData.cas; returnData.push(documentData.value); } @@ -181,8 +181,11 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { } } debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); - callback(err,result.value); - return; + + var returnData = result.value; + returnData._cas = result.cas; + callback(err,result.value); + return; }); }); }; @@ -443,7 +446,14 @@ var updateOneDoc = function(myBucket,idkey,model,inboundData,callback){ // This gets later used to set the cas var options = {}; - // Now actually update it + // Check if we got a cas value. If add it to the options and remove from the data we save + // To make it ignore the cas value it can simply be set to {}. It will then update no matter what the cas in the database is. + if (preparedData.hasOwnProperty("_cas") ) { + options.cas = preparedData._cas; + delete preparedData._cas; + } + + // Now actually update it myBucket.upsert(idkey, preparedData, options, function(err, result) { if(err){ callback(err,null); From 9b96b0d6c58fc243504abf3bc50988a9a24aeadb Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 9 Feb 2015 19:32:52 -0600 Subject: [PATCH 12/43] Removed not needed constants (was needed in part for N1QL) --- lib/base/constants.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/base/constants.js b/lib/base/constants.js index 0734ba1..dd9f683 100644 --- a/lib/base/constants.js +++ b/lib/base/constants.js @@ -5,22 +5,6 @@ function define(name, value) { }); } -define("KEYWORD_LEVEL1_WHERE", "where"); -define("KEYWORD_LEVEL1_FIELDS", "fields"); -define("KEYWORD_LEVEL1_INCLUDE", "include"); -define("KEYWORD_LEVEL1_LIMIT", "limit"); -define("KEYWORD_LEVEL1_ORDER", "order"); -define("KEYWORD_LEVEL1_SKIP", "skip"); -define("KEYWORD_LEVEL1_OFFSET", "offset"); - -define("KEYWORD_LEVEL2_AND", "and"); -define("KEYWORD_LEVEL2_OR", "or"); - -define("KEYWORD_LEVEL3_GREAT_THAN", "gt"); -define("KEYWORD_LEVEL3_LESS_THAN", "lt"); -define("KEYWORD_LEVEL3_BETWEEN", "between"); -define("KEYWORD_LEVEL3_LIKE", "like"); - define("KEYWORD_CONVENTION_DOCID", "id"); define("KEYWORD_CONVENTION_DOCTYPE", "docType"); From 305e8edf2add0a6076dea2b42f94778eb2c80dca Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 10 Feb 2015 12:48:58 -0600 Subject: [PATCH 13/43] Fixed issue which makes loopback crash when view returns and empty result --- lib/cb.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index 894c04a..c6ef98f 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -112,6 +112,15 @@ var cbqueryView = function (query, selfThis, callback) { } debug("cbqueryView() : view success!" + JSON.stringify([err, res])); + var returnData = []; + + // If the result is empty we have nothing to query and can simply return + if (res.length == 0) { + debug("cbqueryView() : success!" + JSON.stringify([err, returnData])); + callback(err, returnData); + return; + } + // Get all the ids of the documents we found var ids = res.map(function(document) {return document.id}); @@ -122,7 +131,6 @@ var cbqueryView = function (query, selfThis, callback) { return; } - var returnData = []; // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value (deactivated for now) for (var id in result) { From 4f6bc21e087fe17ab220ad9cc0fb6a127f5cab22 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 10 Feb 2015 13:40:34 -0600 Subject: [PATCH 14/43] Added password support --- lib/cb.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index c6ef98f..f30fe84 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -96,7 +96,7 @@ CouchbaseDB.prototype.connect = function (callback) { var cbqueryView = function (query, selfThis, callback) { var self = selfThis; - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ if(err){ callback(err,null); return; @@ -132,7 +132,7 @@ var cbqueryView = function (query, selfThis, callback) { } - // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value (deactivated for now) + // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value for (var id in result) { var documentData = result[id]; documentData.value._cas = documentData.cas; @@ -170,7 +170,7 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { var self = this; debug("CouchbaseDB.prototype.find() : " + JSON.stringify([model, id])); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ if(err){ callback(err,null); return; @@ -213,7 +213,7 @@ CouchbaseDB.prototype.create = function (model, data, callback) { var createDataBuilderObj = prepareCreateData(data, model); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ if(err){ callback(err,null); return; @@ -245,7 +245,7 @@ CouchbaseDB.prototype.save = function (model, data, callback) { var self = this; debug("CouchbaseDB.prototype.save() : " + JSON.stringify([model, data])); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ if(err){ callback(err,null); return; @@ -261,7 +261,7 @@ CouchbaseDB.prototype.exists = function (model, id, callback) { var self = this; debug("CouchbaseDB.prototype.exists() : " + JSON.stringify([model, id])); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ if(err){ callback(err,null); return; @@ -295,7 +295,7 @@ CouchbaseDB.prototype.updateOrCreate = function updateOrCreate(model, data, call var self = this; var createDataBuilderObj = prepareCreateData(data, model); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ if(err){ callback(err,null); return; @@ -324,7 +324,7 @@ CouchbaseDB.prototype.destroy = function destroy(model, id, callback) { var self = this; debug("CouchbaseDB.prototype.destroy() : " + JSON.stringify([model, id])); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ if(err){ callback(err,null); return; @@ -429,7 +429,7 @@ CouchbaseDB.prototype.updateAttributes = function updateAttrs(model, id, data, c var self = this; debug("CouchbaseDB.prototype.updateAttributes() : " + JSON.stringify([model, id, data])); var idkey = id; - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ if(err){ callback(err,null); return; From 53bc5bcf6e861074be8d9009ae4201dd2fe6d8da Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 10 Feb 2015 16:11:05 -0600 Subject: [PATCH 15/43] Updated readme to example with password --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 11a9143..c58ea0d 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ The connector can be configured using the following settings from the data sourc "port": "8091", "database": "myBucket", "name": "couchbase", + "password": "secret", "connector": "couchbase", "mappings": { "user": { From 535c4581ac808ce9736d449c0dd963d2340cdc30 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 10 Feb 2015 16:33:12 -0600 Subject: [PATCH 16/43] Now converts data on load into the correct format (Date & Boolean) which fixes issue #1 --- lib/cb.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index f30fe84..01a5f8d 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -93,7 +93,7 @@ CouchbaseDB.prototype.connect = function (callback) { * @param {Object} selfThis The reference to the "this" of this module * @param {Function} [callback] The callback function */ -var cbqueryView = function (query, selfThis, callback) { +var cbqueryView = function (model, query, selfThis, callback) { var self = selfThis; var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ @@ -135,8 +135,11 @@ var cbqueryView = function (query, selfThis, callback) { // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value for (var id in result) { var documentData = result[id]; - documentData.value._cas = documentData.cas; - returnData.push(documentData.value); + + // Bring all the data in the correct format an return only the fields that are specified in the configuration + var newDocumentData = self.fromDatabase(model, documentData); + + returnData.push(newDocumentData); } debug("cbqueryView() : success!" + JSON.stringify([err, result])); @@ -190,9 +193,10 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { } debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); - var returnData = result.value; - returnData._cas = result.cas; - callback(err,result.value); + // Bring all the data in the correct format an return only the fields that are specified in the configuration + var returnData = self.fromDatabase(model, result); + + callback(err,returnData); return; }); }); @@ -405,7 +409,7 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { } // Now run the query and return the data - cbqueryView(query, self, callback); + cbqueryView(model, query, self, callback); } else { // If there are more keys or if we do not have a view we stop (so that it is quite apparent in the development phase) throw "This query is currently not supported: " + JSON.stringify(filter); @@ -502,3 +506,47 @@ CouchbaseDB.prototype.query = function (sql, callback) { callback(null,null); }; + + +CouchbaseDB.prototype.fromDatabase = function (model, data) { + debug("CouchbaseDB.prototype.fromDatabase() model, data: " + JSON.stringify([model, data])); + + var props = this._models[model].properties; + + if (!data.value) { + return undefined; + } + + var returnData = {} + for (var p in props) { + var value = data.value[p]; + debug(value); + + if (value === undefined) { + returnData[p] = undefined; + continue; + } + + if (props[p] && props[p].type.name) { + // It seems that not always everything starts with a capital letter so + // lets make it simply work with whatever is given. + var type = props[p].type.name.toLowerCase(); + + switch (type) { + case 'date': + value = new Date(value.toString()); + break; + case 'boolean': + value = Boolean(value); + break; + } + } + + returnData[p] = value; + + // Add the cas to be able to check on save if the data changed in the meantime + returnData._cas = data.cas; + } + + return returnData; +}; From e39faf0d4a97ab8245967c62c8a8d4a834fc21ae Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 10 Feb 2015 17:10:00 -0600 Subject: [PATCH 17/43] Removed debug code which I left behind by accident --- lib/cb.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index 01a5f8d..9a4c461 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -520,7 +520,6 @@ CouchbaseDB.prototype.fromDatabase = function (model, data) { var returnData = {} for (var p in props) { var value = data.value[p]; - debug(value); if (value === undefined) { returnData[p] = undefined; From 6cb3512cb19fdd6939859555aa17837967ddf1b9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 13 Feb 2015 14:29:37 -0600 Subject: [PATCH 18/43] Now just creates the bucket-connection once and then reuses it #4 --- lib/cb.js | 364 ++++++++++++++++++++++++++++++--------------------- package.json | 3 +- 2 files changed, 218 insertions(+), 149 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 9a4c461..fb584d2 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -8,6 +8,7 @@ var constants = require('./base/constants.js'); var uuid = require('node-uuid'); var debug = require('debug')('connector:couchbase'); var extend = require('node.extend'); +var $q = require('q'); /** * @module loopback-connector-couchbase @@ -60,6 +61,8 @@ function CouchbaseDB(settings, dataSource) { this._models = {}; this.settings = settings; this.dataSource = dataSource; + this.__bucket = undefined; + Connector.call(this, 'couchbase', settings); } @@ -74,13 +77,14 @@ exports.CouchbaseDB = CouchbaseDB; CouchbaseDB.prototype.connect = function (callback) { var self = this; if (self.db) { - process.nextTick(function () { - callback && callback(null, self.db); - }); + process.nextTick(function () { + callback && callback(null, self.db); + }); } else { - self.myCluster = new couchbase.Cluster(self.settings.connectUrl); - self.viewQuery = couchbase.ViewQuery; - callback && callback(null, self.db); + self.myCluster = new couchbase.Cluster(self.settings.connectUrl); + self.viewQuery = couchbase.ViewQuery; + self.__openBucketConnection(); + callback && callback(null, self.db); } }; @@ -96,58 +100,59 @@ CouchbaseDB.prototype.connect = function (callback) { var cbqueryView = function (model, query, selfThis, callback) { var self = selfThis; - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ - if(err){ - callback(err,null); - return; - } + self.__getBucket().then( + function(myBucket) { - debug("cbqueryView() : query is: " + JSON.stringify([query]) ); + debug("cbqueryView() : query is: " + JSON.stringify([query]) ); - myBucket.query(query, function(err, res) { - if (err) { - debug("cbqueryView() : query failed" + JSON.stringify([err, res])); - callback(err,null); - return; - } - debug("cbqueryView() : view success!" + JSON.stringify([err, res])); - - var returnData = []; - - // If the result is empty we have nothing to query and can simply return - if (res.length == 0) { - debug("cbqueryView() : success!" + JSON.stringify([err, returnData])); - callback(err, returnData); - return; - } + myBucket.query(query, function(err, res) { + if (err) { + debug("cbqueryView() : query failed" + JSON.stringify([err, res])); + callback(err,null); + return; + } + debug("cbqueryView() : view success!" + JSON.stringify([err, res])); - // Get all the ids of the documents we found - var ids = res.map(function(document) {return document.id}); + var returnData = []; - // Query all the documents at once - myBucket.getMulti(ids,function(err, result) { - if(err){ - callback(err, null); + // If the result is empty we have nothing to query and can simply return + if (res.length == 0) { + debug("cbqueryView() : success!" + JSON.stringify([err, returnData])); + callback(err, returnData); return; } + // Get all the ids of the documents we found + var ids = res.map(function(document) {return document.id}); - // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value - for (var id in result) { - var documentData = result[id]; + // Query all the documents at once + myBucket.getMulti(ids,function(err, result) { + if(err){ + callback(err, null); + return; + } - // Bring all the data in the correct format an return only the fields that are specified in the configuration - var newDocumentData = self.fromDatabase(model, documentData); + // It returns an object but loopback wants an array. So convert it and also use the chance to add the cas-value + for (var id in result) { + var documentData = result[id]; - returnData.push(newDocumentData); - } + // Bring all the data in the correct format an return only the fields that are specified in the configuration + var newDocumentData = self.fromDatabase(model, documentData); - debug("cbqueryView() : success!" + JSON.stringify([err, result])); - callback(err, returnData); - return; + returnData.push(newDocumentData); + } + + debug("cbqueryView() : success!" + JSON.stringify([err, result])); + callback(err, returnData); + return; + }); }); - }); - }); + + }, function(err) { + callback(err, null); + return; + } + ); }; @@ -173,35 +178,89 @@ CouchbaseDB.prototype.find = function find(model, id, callback) { var self = this; debug("CouchbaseDB.prototype.find() : " + JSON.stringify([model, id])); - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ - if(err){ - callback(err,null); - return; - } - myBucket.get(id,function(err, result) { - if(err){ - // Error code 13 is "The key does not exist on the server". Because Loopback does not treat that as an error - // we will neither to be consistent. - if (err.code == 13) { - callback(null, null); - return; - } else { - callback(err, null); - return; - } - } - debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); - - // Bring all the data in the correct format an return only the fields that are specified in the configuration - var returnData = self.fromDatabase(model, result); - - callback(err,returnData); - return; - }); - }); + self.__getBucket().then( + function(myBucket) { + + myBucket.get(id,function(err, result) { + if(err){ + // Error code 13 is "The key does not exist on the server". Because Loopback does not treat that as an error + // we will neither to be consistent. + if (err.code == 13) { + callback(null, null); + return; + } else { + callback(err, null); + return; + } + } + debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); + + // Bring all the data in the correct format an return only the fields that are specified in the configuration + var returnData = self.fromDatabase(model, result); + + callback(err,returnData); + return; + }); + + + }, function(err) { + callback(err,null); + return; + } + ); }; +/** + * Returns an existing bucket-connection or creates a new one if none exists + */ +CouchbaseDB.prototype.__getBucket = function() { + debug("CouchbaseDB.prototype.__getBucket()"); + + var self = this; + + var deferred = $q.defer(); + + if (self.__bucket) { + // Simply return an existing bucket-connection + deferred.resolve( self.__bucket ); + } else { + // Open a new bucket-connection + CouchbaseDB.prototype.__openBucketConnection().then( + function(myBucket) { + deferred.resolve( myBucket ) + }, function(err) { + deferred.reject( err ); + } + ); + } + return deferred.promise; +} + +/** + * Opens and returns a new bucket connection + */ +CouchbaseDB.prototype.__openBucketConnection = function() { + debug("CouchbaseDB.prototype.__openBucketConnection()"); + + var self = this; + + var deferred = $q.defer(); + + var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err) { + if(err){ + debug(err) + deferred.reject( err ); + return deferred.promise; + } + + self.__bucket = myBucket; + deferred.resolve( self.__bucket ); + + }); + + return deferred.promise; +} /** @@ -216,30 +275,32 @@ CouchbaseDB.prototype.create = function (model, data, callback) { var createDataBuilderObj = prepareCreateData(data, model); + self.__getBucket().then( + function(myBucket) { - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ - if(err){ - callback(err,null); - return; - } + myBucket.insert(createDataBuilderObj.getIdKey(),createDataBuilderObj.getPreparedData(),function(err, result) { + if(err){ + callback(err,null); + return; + } - myBucket.insert(createDataBuilderObj.getIdKey(),createDataBuilderObj.getPreparedData(),function(err, result) { - if(err){ - callback(err,null); - return; - } + // Deactivated for now because the value which gets returned gets set as the id. Impossible to set other ones. + // So we also just return the id like the connector expects. + //result = createDataBuilderObj.setImplicitFieldsInResult(data,result); + //debug("CouchbaseDB.prototype.create() : added id/doctype,now result is: " + JSON.stringify([result]) ); - // Deactivated for now because the value which gets returned gets set as the id. Impossible to set other ones. - // So we also just return the id like the connector expects. - //result = createDataBuilderObj.setImplicitFieldsInResult(data,result); - //debug("CouchbaseDB.prototype.create() : added id/doctype,now result is: " + JSON.stringify([result]) ); + debug("CouchbaseDB.prototype.create() : created document: " + JSON.stringify([result]) ); - debug("CouchbaseDB.prototype.create() : created document: " + JSON.stringify([result]) ); + callback(err,result && result.id); - callback(err,result && result.id); + }); + + }, function(err) { + callback(err,null); + return; + } + ); - }); - }); }; /** @@ -249,13 +310,15 @@ CouchbaseDB.prototype.save = function (model, data, callback) { var self = this; debug("CouchbaseDB.prototype.save() : " + JSON.stringify([model, data])); - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ - if(err){ - callback(err,null); - return; - } - updateOneDoc(myBucket,undefined,model,data,callback); - }); + self.__getBucket().then( + function(myBucket) { + updateOneDoc(myBucket,undefined,model,data,callback); + }, function(error) { + callback(err,null); + return; + } + ); + }; /** @@ -265,28 +328,28 @@ CouchbaseDB.prototype.exists = function (model, id, callback) { var self = this; debug("CouchbaseDB.prototype.exists() : " + JSON.stringify([model, id])); - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password ,function(err){ - if(err){ - callback(err,null); - return; - } - myBucket.get(id,function(err, result) { - if(err){ - debug("CouchbaseDB.prototype.find() : find exists is: " + JSON.stringify([err, result]) ); - if(err.code && err.code ==13){ - callback(null,false); - return; - } - callback(err,null); - return; - } - debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); - callback(err,true); - return; - }); - - }); + self.__getBucket().then( + function(myBucket) { + myBucket.get(id,function(err, result) { + if(err){ + debug("CouchbaseDB.prototype.find() : find exists is: " + JSON.stringify([err, result]) ); + if(err.code && err.code ==13){ + callback(null,false); + return; + } + callback(err,null); + return; + } + debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); + callback(err,true); + return; + }); + }, function(err) { + callback(err,null); + return; + } + ); }; @@ -299,13 +362,15 @@ CouchbaseDB.prototype.updateOrCreate = function updateOrCreate(model, data, call var self = this; var createDataBuilderObj = prepareCreateData(data, model); - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ - if(err){ - callback(err,null); - return; - } - updateOneDoc(myBucket,createDataBuilderObj.getIdKey(),model,createDataBuilderObj.getPreparedData(),callback); - }); + self.__getBucket().then( + function(myBucket) { + updateOneDoc(myBucket,createDataBuilderObj.getIdKey(),model,createDataBuilderObj.getPreparedData(),callback); + }, function(err) { + callback(err,null); + return; + } + ); + }; @@ -328,24 +393,24 @@ CouchbaseDB.prototype.destroy = function destroy(model, id, callback) { var self = this; debug("CouchbaseDB.prototype.destroy() : " + JSON.stringify([model, id])); - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ - if(err){ - callback(err,null); - return; - } - - myBucket.remove(id,function(err, result) { - if(err){ - callback(err,null); - return; - } - debug("CouchbaseDB.prototype.destroy() : removal result is: " + JSON.stringify([err, result]) ); - callback(err,result); - return; + self.__getBucket().then( + function(myBucket) { + myBucket.remove(id,function(err, result) { + if(err){ + callback(err,null); + return; + } + debug("CouchbaseDB.prototype.destroy() : removal result is: " + JSON.stringify([err, result]) ); + callback(err,result); + return; - }); + }); + }, function(err) { + callback(err,null); + return; + } + ); - }); }; /** @@ -433,13 +498,16 @@ CouchbaseDB.prototype.updateAttributes = function updateAttrs(model, id, data, c var self = this; debug("CouchbaseDB.prototype.updateAttributes() : " + JSON.stringify([model, id, data])); var idkey = id; - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err){ - if(err){ - callback(err,null); - return; - } - updateOneDoc(myBucket,idkey,model,data,callback); - }); + + self.__getBucket().then( + function(myBucket) { + updateOneDoc(myBucket,idkey,model,data,callback); + }, function(err) { + callback(err,null); + return; + } + ); + }; /** diff --git a/package.json b/package.json index 7a4781e..8e39391 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "loopback-datasource-juggler": "^2.12.0", "node-dir": "^0.1.6", "node-uuid": "^1.4.1", - "node.extend": "^1.1.3" + "node.extend": "^1.1.3", + "q": "^1.1.2" }, "devDependencies": { "rc": "~0.4.0" From 07fbeb9f9193fc4ee12ce2f9e2d3db460d53533d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 24 Feb 2015 20:10:14 -0600 Subject: [PATCH 19/43] Now supports deleteById --- lib/cb.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index fb584d2..1d5ac89 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -484,8 +484,17 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { /** * Delete all model instances */ -CouchbaseDB.prototype.destroyAll = function destroyAll(model, callback) { - debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model])); +CouchbaseDB.prototype.destroyAll = function destroyAll(model, where, callback) { + + var self = this; + debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model, where])); + + // For now we just support deleting one document (deleteById) + if (where && where.id) { + self.destroy(model, where.id, callback); + return; + } + throw "stop here !"; }; From 8b5f1a0d830cf35821bb9f39e2cc65853060f949 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 23 May 2015 12:51:05 -0500 Subject: [PATCH 20/43] Renamed package --- README.md | 2 +- package.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c58ea0d..c4557bf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ The Couchbase Connector module for for [loopback-datasource-juggler](http://docs.strongloop.com/loopback-datasource-juggler/). -This connector does not use N1QL. That means you can not simply lookup data you do not have view setup and defined in the configuration. This means you have to be very careful which data you query. If you for example have a relationship setup and you want to use it to query related data it will fail till you create * setup a view which allows it. Actually querying data will probably almost always fail if you do not use "findById". So you have to write your application a very specific way for it to work but the price for that effort is high performance. So for the most people using the N1QL implementation (from which I did fork) is probably better: https://github.com/guardly/loopback-connector-couchbase +This connector does not use N1QL. That means you can not simply lookup data you do not have view setup and defined in the configuration. So you have to be very careful which data you query (that is why I put the expert in the name). If you for example have a relationship setup and you want to use it to query related data it will fail till you create * setup a view which allows it. Actually querying data will probably almost always fail if you do not use "findById". So you have to write your application a very specific way for it to work but the price for that effort is high performance. So for the most people using the N1QL implementation (from which I did fork) is probably better: https://github.com/guardly/loopback-connector-couchbase Btw. is also not really well tested. Did just fork to work best for my own needs of high performance (and no random query need). diff --git a/package.json b/package.json index 8e39391..5f67558 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "loopback-connector-couchbase", + "name": "loopback-connector-couchbase-expert", "version": "0.1.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ @@ -15,7 +15,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/janober/loopback-connector-couchbase.git" + "url": "https://github.com/janober/loopback-connector-couchbase-expert.git" }, "dependencies": { "async": "~0.9.0", @@ -34,10 +34,10 @@ }, "license": { "name": "MIT", - "url": "https://github.com/janober/loopback-connector-couchbase/blob/master/LICENSE.md" + "url": "https://github.com/janober/loopback-connector-couchbase-expert/blob/master/LICENSE.md" }, "bugs": { - "url": "https://github.com/janober/loopback-connector-couchbase/issues" + "url": "https://github.com/janober/loopback-connector-couchbase-expert/issues" }, - "homepage": "https://github.com/janober/loopback-connector-couchbase" + "homepage": "https://github.com/janober/loopback-connector-couchbase-expert" } From 854e0125454fc60c3390a980d61b3a5419d06a4d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 23 May 2015 14:46:16 -0500 Subject: [PATCH 21/43] Updated to use new name --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c4557bf..aae94d7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Before installing the connector module, make sure you've taken the appropriate s ### Installing the connector ```npm install couchbase``` -```npm install loopback-connector-couchbase``` +```npm install loopback-connector-couchbase-expert``` ## Connector settings @@ -46,9 +46,9 @@ The connector can be configured using the following settings from the data sourc "host": "localhost", "port": "8091", "database": "myBucket", - "name": "couchbase", + "name": "couchbase-expert", "password": "secret", - "connector": "couchbase", + "connector": "couchbase-expert", "mappings": { "user": { "email": "u::" From ee5814e5447a660ba5e55eb75c01640494f08a8c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 25 May 2015 21:52:33 -0500 Subject: [PATCH 22/43] Fixed a bug which caused connector to crash when a database operation got started after launch --- lib/cb.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 1d5ac89..b74ce11 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -226,7 +226,7 @@ CouchbaseDB.prototype.__getBucket = function() { deferred.resolve( self.__bucket ); } else { // Open a new bucket-connection - CouchbaseDB.prototype.__openBucketConnection().then( + self.__openBucketConnection().then( function(myBucket) { deferred.resolve( myBucket ) }, function(err) { diff --git a/package.json b/package.json index 5f67558..948bd6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.1.0", + "version": "0.2.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From 338cbbbb6428846f89d77bd24a9ea6b8a3658d58 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 27 Oct 2015 12:28:57 +0100 Subject: [PATCH 23/43] Removed docType Needs a lot of space and does not get used anyway. If we have to distinguish later for what reason ever between the docTypes, simply the first letter of the id can be used. --- lib/base/constants.js | 1 - lib/cb.js | 5 ++--- lib/dataBuilder/CreateDataBuilder.js | 18 ++++++++---------- lib/dataBuilder/UpdateDataBuilder.js | 9 ++++----- package.json | 2 +- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/base/constants.js b/lib/base/constants.js index dd9f683..2d7bd22 100644 --- a/lib/base/constants.js +++ b/lib/base/constants.js @@ -6,7 +6,6 @@ function define(name, value) { } define("KEYWORD_CONVENTION_DOCID", "id"); -define("KEYWORD_CONVENTION_DOCTYPE", "docType"); define("KEYWORD_PRESERVED_COUCHBASE", "couchbase"); define("KEYWORD_PRESERVED_KEYNAME", "preserve"); diff --git a/lib/cb.js b/lib/cb.js index b74ce11..7f5d364 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -287,7 +287,7 @@ CouchbaseDB.prototype.create = function (model, data, callback) { // Deactivated for now because the value which gets returned gets set as the id. Impossible to set other ones. // So we also just return the id like the connector expects. //result = createDataBuilderObj.setImplicitFieldsInResult(data,result); - //debug("CouchbaseDB.prototype.create() : added id/doctype,now result is: " + JSON.stringify([result]) ); + //debug("CouchbaseDB.prototype.create() : added id,now result is: " + JSON.stringify([result]) ); debug("CouchbaseDB.prototype.create() : created document: " + JSON.stringify([result]) ); @@ -528,9 +528,8 @@ var updateOneDoc = function(myBucket,idkey,model,inboundData,callback){ // Copy the data so that we do not change any outside values var preparedData = extend({}, inboundData); - // Make sure that id and document-type get preserved so simply set them + // Make sure that id gets preserved so simply set them preparedData.id = idkey; - preparedData.docType = model; // This gets later used to set the cas var options = {}; diff --git a/lib/dataBuilder/CreateDataBuilder.js b/lib/dataBuilder/CreateDataBuilder.js index 37762ce..5cf9978 100644 --- a/lib/dataBuilder/CreateDataBuilder.js +++ b/lib/dataBuilder/CreateDataBuilder.js @@ -1,15 +1,15 @@ /** The public interface of the builder which prepares the data that are to be inserted into the database Will implicitly read the "id" field for the document id, if not existing, create a UUID for it - Will implicitly set the field "id" and "docType" if not specified + Will implicitly set the field "id" if not specified - For creating new document (CouchbaseDB.prototype.create) or updating the existing document (CouchbaseDB.prototype.updateOrCreate) only. - In those two cases, document id is not specified in a separate parameter, we need to find the possible id by name convention + For creating new document (CouchbaseDB.prototype.create) or updating the existing document (CouchbaseDB.prototype.updateOrCreate) only. + In those two cases, document id is not specified in a separate parameter, we need to find the possible id by name convention */ var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); +var util = require('util'); var debug = require('debug')('connector:builder'); var constants = require('../base/constants.js'); var uuid = require('node-uuid'); @@ -30,13 +30,13 @@ var CreateDataBuilder = function(inboundData,model){ }; this.setIdKey = function(newKeyId){ currentIdkey = newKeyId; - }; + }; this.getPreparedData = function(){ return preparedData; }; this.setPreparedData = function(newData){ preparedData = newData; - }; + }; }; util.inherits(CreateDataBuilder, BaseModel); @@ -65,7 +65,6 @@ CreateDataBuilder.prototype.buildData = function(callback){ */ var setImplicitFields = function(data,idkey,model){ data[constants.KEYWORD_CONVENTION_DOCID] = idkey; //field 'id' has to be there. if id was not defined, a uuid would be created and we need to make sure it is written into the id field - data[constants.KEYWORD_CONVENTION_DOCTYPE] = model; //field "docType' has to be there, this is an implicit field return data; }; @@ -85,7 +84,7 @@ var readDocumentID = function(data, callback){ idkey = uuid.v4(); debug("No document id defined, create one for it : " + idkey ); } - + if(!(typeof idkey === 'string' || idkey instanceof Buffer)){ debug("The document key '" + idkey + "', which is from the '" + constants.KEYWORD_CONVENTION_DOCID + "' field, should be in string format or buffer format"); callback("The document key '" + idkey + "', which is from the '" + constants.KEYWORD_CONVENTION_DOCID + "' field, should be in string format or buffer format",null); @@ -95,12 +94,11 @@ var readDocumentID = function(data, callback){ /* - Set docType and document id into the result object + Set document id into the result object */ CreateDataBuilder.prototype.setImplicitFieldsInResult = function(data,result){ debug("CreateDataBuilder.prototype.setImplicitFieldsInResult() : created result is: " + JSON.stringify(result) ); result[constants.KEYWORD_CONVENTION_DOCID] = data[constants.KEYWORD_CONVENTION_DOCID]; - result[constants.KEYWORD_CONVENTION_DOCTYPE] = data[constants.KEYWORD_CONVENTION_DOCTYPE]; return result; }; diff --git a/lib/dataBuilder/UpdateDataBuilder.js b/lib/dataBuilder/UpdateDataBuilder.js index a31b660..b864d5d 100644 --- a/lib/dataBuilder/UpdateDataBuilder.js +++ b/lib/dataBuilder/UpdateDataBuilder.js @@ -1,12 +1,12 @@ /** - The public interface of the builder which merges the coming-in data and the existing data, + The public interface of the builder which merges the coming-in data and the existing data, decides which fields are to be changed or preserved - + */ var BaseModel = require('../base/BaseModel.js'); -var util = require('util'); +var util = require('util'); var debug = require('debug')('connector:builder'); var constants = require('../base/constants.js'); var UpdateDataBuilder = function(modelProperties,inboundData,docData){ @@ -52,9 +52,8 @@ UpdateDataBuilder.prototype.buildData = function(){ */ var getPreservedFields = function(modelProperties){ var preservedFields = new Array(); - //the field 'id' and 'docType' should be preserved implicitly + //the field 'id' should be preserved implicitly preservedFields.push(constants.KEYWORD_CONVENTION_DOCID); - preservedFields.push(constants.KEYWORD_CONVENTION_DOCTYPE); //debug(" UpdateDataBuilder getPreservedFields() modelProperties: " + JSON.stringify([modelProperties])); diff --git a/package.json b/package.json index 948bd6e..e20736a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.2.0", + "version": "0.3.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From 2355f9931b323ee492878c9ed3644932d82ce202 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Feb 2016 12:22:29 +0100 Subject: [PATCH 24/43] Update to latest couchbase library version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e20736a..7bf6509 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "async": "~0.9.0", - "couchbase": "^2.0.1", + "couchbase": "^2.1.3", "debug": "^1.0.4", "loopback": "^2.8.6", "loopback-connector": "^1.2.0", From 7c810ded453b14d6f1154aabfe36cd9381f8791d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Feb 2016 12:22:56 +0100 Subject: [PATCH 25/43] Upversion to 0.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7bf6509..d209e6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.3.0", + "version": "0.4.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From 04ec91997392ffcb91a4a38c112326ae39c7cacc Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Feb 2016 10:52:34 +0100 Subject: [PATCH 26/43] Fix issue when ids are now given as arrays --- lib/cb.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index 7f5d364..069452a 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -427,14 +427,26 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { var documentId = undefined; - if (filter.where && filter.where.id) { + if (filter.hasOwnProperty('where') && filter.where.hasOwnProperty('id') && filter.hasOwnProperty('limit') && filter.limit === 1) { // If the "where" has an id set, the rest is not really interesting anymore because it can be just // one document that matches and that is the one we return documentId = filter.where.id; + + // Now the id is sometimes an array, so we account for that + if (Array.isArray(documentId)) { + if (documentId.length === 1) { + documentId = documentId[0]; + } else { + // If it has more or less entries we can not do anything for now + callback(err, null); + return; + } + } + } else if (keys.length == 1 && mappings.hasOwnProperty(model) && mappings[model].hasOwnProperty(keys[0])) { var prefix = mappings[model][keys[0]]; - var documentId = prefix + filter.where[keys[0]]; + documentId = prefix + filter.where[keys[0]]; } if (documentId) { From 01e594a89053c8df478f3662671f93e2d91559e1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Feb 2016 10:53:38 +0100 Subject: [PATCH 27/43] Upversion to 0.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d209e6f..2dbb58d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.4.0", + "version": "0.4.1", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From 8888ad486427489acd132140eee22ba19cce6f34 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 19 Feb 2016 08:19:14 +0100 Subject: [PATCH 28/43] Fix bug --- lib/cb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index 069452a..6120f4d 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -439,7 +439,7 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { documentId = documentId[0]; } else { // If it has more or less entries we can not do anything for now - callback(err, null); + callback('Can currently only query one document at a time', null); return; } } From 599b54499ca0fa90d6f6e4cf0c51c04fd7e8f33a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 19 Feb 2016 08:19:38 +0100 Subject: [PATCH 29/43] Upversion to 0.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dbb58d..9373c60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.4.1", + "version": "0.4.2", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From a6234916a0d9f28c3ff29015618522a30298e6e9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 29 May 2016 12:58:34 +0200 Subject: [PATCH 30/43] Fix typo --- lib/cb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index 6120f4d..252ebaf 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -313,7 +313,7 @@ CouchbaseDB.prototype.save = function (model, data, callback) { self.__getBucket().then( function(myBucket) { updateOneDoc(myBucket,undefined,model,data,callback); - }, function(error) { + }, function(err) { callback(err,null); return; } From e4097879a3014ec4a67d697096f01fcbebaed3ab Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 9 Feb 2017 07:29:29 +0100 Subject: [PATCH 31/43] Allow where queries for id no matter if limit is set (becaus can just be one anyway) --- lib/cb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index 252ebaf..56c1efb 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -427,7 +427,7 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { var documentId = undefined; - if (filter.hasOwnProperty('where') && filter.where.hasOwnProperty('id') && filter.hasOwnProperty('limit') && filter.limit === 1) { + if (filter.hasOwnProperty('where') && filter.where.hasOwnProperty('id')) { // If the "where" has an id set, the rest is not really interesting anymore because it can be just // one document that matches and that is the one we return From 215c392ba49b66d78503d9d10f6cadcccd961e82 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 9 Feb 2017 07:30:01 +0100 Subject: [PATCH 32/43] Upversion to 0.4.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9373c60..12361dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.4.2", + "version": "0.4.3", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From b85cd8ae27741196903a5c58896f8ab68312e990 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 14 May 2017 14:40:09 -0500 Subject: [PATCH 33/43] Allow to set document expiry date by setting "_expiry" on the document --- lib/cb.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/cb.js b/lib/cb.js index 56c1efb..6e7b891 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -278,7 +278,16 @@ CouchbaseDB.prototype.create = function (model, data, callback) { self.__getBucket().then( function(myBucket) { - myBucket.insert(createDataBuilderObj.getIdKey(),createDataBuilderObj.getPreparedData(),function(err, result) { + // This gets later used to set the expiry time + var options = {}; + + // Check if we got a expiry value. If add it to the options and remove from the data we save + if (data.hasOwnProperty("_expiry") ) { + options.expiry = data._expiry; + delete data._expiry; + } + + myBucket.insert(createDataBuilderObj.getIdKey(), createDataBuilderObj.getPreparedData(), options, function(err, result) { if(err){ callback(err,null); return; From 41cbe1e6fb0b2a38854b1e1d79dd624eccb43c37 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 14 May 2017 14:41:37 -0500 Subject: [PATCH 34/43] Upversion to 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12361dd..2f6ba1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.4.3", + "version": "0.5.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From e11308bc1665f511e3df1afaf50c39bfc2a1a051 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 14 May 2017 22:40:27 -0500 Subject: [PATCH 35/43] Allow to set document expiry date by setting "_expiry" on the document also for updates --- lib/cb.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/cb.js b/lib/cb.js index 6e7b891..ff1fa2b 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -562,6 +562,12 @@ var updateOneDoc = function(myBucket,idkey,model,inboundData,callback){ delete preparedData._cas; } + // Check if we got a expiry value. If add it to the options and remove from the data we save + if (preparedData.hasOwnProperty("_expiry") ) { + options.expiry = preparedData._expiry; + delete preparedData._expiry; + } + // Now actually update it myBucket.upsert(idkey, preparedData, options, function(err, result) { if(err){ From f86c49caa0d25e7f912879105e2c6b85c3852000 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 14 May 2017 22:40:46 -0500 Subject: [PATCH 36/43] Upversion to 0.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f6ba1b..9955ac3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.5.0", + "version": "0.6.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From 8bd2357fee49f683a59cc8f86ec4e64190554fc0 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 24 Nov 2017 08:26:20 +0100 Subject: [PATCH 37/43] Changes to make it work with new authentication in Couchbase Server 5.0 --- README.md | 7 ++++--- lib/cb.js | 4 +++- package.json | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aae94d7..8a41f49 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ The connector can be configured using the following settings from the data sourc * host (default to 'localhost'): The host name or ip address of the Couchbase server * port (default to 8091): The port number of the Couchbase server * database: The Couchbase bucket +* user: The user to access Couchbase cluster +* password: The password to access Couchbase cluster * connectionTimeout (default to 20000): The connection timeout value * operationTimeout (default to 15000): The operation timeout value * mappings: Prefixes for values which get used as key (example below) * views: Views which can be used to loopup data -**NOTE**: Unlike other datasources, Couchbase does not require user credentials to access a bucket/database. Buckets can be protected with a password. - ## Example Configuration @@ -47,7 +47,8 @@ The connector can be configured using the following settings from the data sourc "port": "8091", "database": "myBucket", "name": "couchbase-expert", - "password": "secret", + "user": "my-user", + "password": "my-password", "connector": "couchbase-expert", "mappings": { "user": { diff --git a/lib/cb.js b/lib/cb.js index ff1fa2b..affa2d0 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -30,6 +30,7 @@ exports.initialize = function initializeDataSource(dataSource, callback) { dsconnector: s.connector, host: s.host || 'localhost', port: s.port || 8091, + user : s.user || '', password : s.password || '', bucket: s.database || 'default', env: s.env || 'debugging', @@ -82,6 +83,7 @@ CouchbaseDB.prototype.connect = function (callback) { }); } else { self.myCluster = new couchbase.Cluster(self.settings.connectUrl); + self.myCluster.authenticate(self.settings.user, self.settings.password); self.viewQuery = couchbase.ViewQuery; self.__openBucketConnection(); callback && callback(null, self.db); @@ -247,7 +249,7 @@ CouchbaseDB.prototype.__openBucketConnection = function() { var deferred = $q.defer(); - var myBucket = self.myCluster.openBucket(self.settings.bucket, self.settings.password, function(err) { + var myBucket = self.myCluster.openBucket(self.settings.bucket, function(err) { if(err){ debug(err) deferred.reject( err ); diff --git a/package.json b/package.json index 9955ac3..a0b7603 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "async": "~0.9.0", - "couchbase": "^2.1.3", + "couchbase": "^2.4.2", "debug": "^1.0.4", "loopback": "^2.8.6", "loopback-connector": "^1.2.0", From 55f2569adaa15355bfc163703cd2b332dc0128ec Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 24 Nov 2017 08:27:25 +0100 Subject: [PATCH 38/43] Upversion to 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a0b7603..375c474 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.6.0", + "version": "0.7.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From 3b6bf9cb08cc2175c0241663b00e4c5dfafec7ed Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 3 Jun 2018 19:32:19 +0200 Subject: [PATCH 39/43] Make destroyAll work as long as only one id is given --- lib/cb.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index affa2d0..b4a48c9 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -507,18 +507,28 @@ CouchbaseDB.prototype.all = function all(model, filter, callback) { /** * Delete all model instances */ -CouchbaseDB.prototype.destroyAll = function destroyAll(model, where, callback) { +CouchbaseDB.prototype.destroyAll = function destroyAll(model, where, options, callback) { var self = this; - debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model, where])); + debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model, where])); // For now we just support deleting one document (deleteById) + var documentId = undefined; if (where && where.id) { - self.destroy(model, where.id, callback); - return; + if (typeof documentId === "object") { + if (documentId.hasOwnProperty("inq") && Object.keys(documentId).length === 1 && documentId.inq.length === 1) { + documentId = documentId.inq[0]; + } + } else { + documentId = where.id; + } + } + + if (documentId === undefined) { + throw "stop here !"; } - throw "stop here !"; + self.destroy(model, documentId, callback); }; /** From 980a845087911facae1d5c0680845de4641d4c2b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 3 Jun 2018 20:34:10 +0200 Subject: [PATCH 40/43] Fix error in previous commit --- lib/cb.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index b4a48c9..c3fe2fb 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -515,9 +515,9 @@ CouchbaseDB.prototype.destroyAll = function destroyAll(model, where, options, ca // For now we just support deleting one document (deleteById) var documentId = undefined; if (where && where.id) { - if (typeof documentId === "object") { - if (documentId.hasOwnProperty("inq") && Object.keys(documentId).length === 1 && documentId.inq.length === 1) { - documentId = documentId.inq[0]; + if (typeof where.id === "object") { + if (where.id.hasOwnProperty("inq") && Object.keys(where.id).length === 1 && where.id.inq.length === 1) { + documentId = where.id.inq[0]; } } else { documentId = where.id; From 000ded48ac249512a4c0dcb6ffd29ac28951a2af Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 3 Jun 2018 20:35:27 +0200 Subject: [PATCH 41/43] Upversion to 0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 375c474..5a52d16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.7.0", + "version": "0.8.0", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop", From d4bc356eb9fa41dc9fa5ec4467a44ddc2d8985b7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 3 Jun 2018 20:51:31 +0200 Subject: [PATCH 42/43] Do not fail destroyAll when no id is given --- lib/cb.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/cb.js b/lib/cb.js index c3fe2fb..f8f51ed 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -516,8 +516,14 @@ CouchbaseDB.prototype.destroyAll = function destroyAll(model, where, options, ca var documentId = undefined; if (where && where.id) { if (typeof where.id === "object") { - if (where.id.hasOwnProperty("inq") && Object.keys(where.id).length === 1 && where.id.inq.length === 1) { - documentId = where.id.inq[0]; + if (where.id.hasOwnProperty("inq") && Object.keys(where.id).length === 1) { + if (where.id.inq.length === 0) { + // Actually nothing given to delete so simply return successfully + callback(null, {}); + return; + } else if (where.id.inq.length === 1) { + documentId = where.id.inq[0]; + } } } else { documentId = where.id; From f5452b9359fd6ada03f34daab547fcb486e45c0b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 3 Jun 2018 20:52:00 +0200 Subject: [PATCH 43/43] Upversion to 0.8.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a52d16..9eb3599 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-couchbase-expert", - "version": "0.8.0", + "version": "0.8.1", "description": "LoopBack Couchbase Connector without N1QL", "keywords": [ "StrongLoop",