diff --git a/README.md b/README.md index a71f37b..8a41f49 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,27 @@ -## 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. 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). ### 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 -couchbase 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 ```npm install couchbase``` -```npm install loopback-connector-couchbase``` +```npm install loopback-connector-couchbase-expert``` ## Connector settings @@ -63,12 +29,57 @@ 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 +* 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, 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-expert", + "user": "my-user", + "password": "my-password", + "connector": "couchbase-expert", + "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: + +``` + function (doc, meta) { + if (doc.docType && doc.docType == "Order" && doc.userId) { + emit(doc.userId, null); + } + } +``` ## Model definition for Couchbase Documents @@ -112,17 +123,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/base/constants.js b/lib/base/constants.js index 0734ba1..2d7bd22 100644 --- a/lib/base/constants.js +++ b/lib/base/constants.js @@ -5,24 +5,7 @@ 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"); define("KEYWORD_PRESERVED_COUCHBASE", "couchbase"); define("KEYWORD_PRESERVED_KEYNAME", "preserve"); diff --git a/lib/cb.js b/lib/cb.js index 3b3ae56..f8f51ed 100644 --- a/lib/cb.js +++ b/lib/cb.js @@ -1,14 +1,14 @@ 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 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'); +var $q = require('q'); /** * @module loopback-connector-couchbase @@ -30,15 +30,16 @@ exports.initialize = function initializeDataSource(dataSource, callback) { dsconnector: s.connector, host: s.host || 'localhost', port: s.port || 8091, + user : s.user || '', 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: @@ -61,6 +62,8 @@ function CouchbaseDB(settings, dataSource) { this._models = {}; this.settings = settings; this.dataSource = dataSource; + this.__bucket = undefined; + Connector.call(this, 'couchbase', settings); } @@ -75,48 +78,86 @@ 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); - callback && callback(null, self.db); + 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); } }; + + /** - * 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 (model, query, selfThis, callback) { + var self = selfThis; + + self.__getBucket().then( + function(myBucket) { + + 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; + } + + // 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; + } + + // 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]; + + // 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])); + callback(err, returnData); + return; + }); + }); + + }, function(err) { + callback(err, null); + return; + } + ); }; + /** * Find matching model instances by the filter * @@ -128,54 +169,100 @@ var cbquery = function (sql,selfThis, callback) { * Count the model instances by the where criteria */ CouchbaseDB.prototype.count = function (model, callback, where) { + debug("CouchbaseDB.prototype.count() : " + JSON.stringify([model, where])); + throw "Count is not implemented!"; +}; +/** + * 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])); + + + 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; + } + ); +}; - var clauseBuilderIndex = new clauseBuilderClass(); - - var clausebuilderObj = clauseBuilderIndex.getClauseBuilder(where,self.settings.bucket,model); - var clause = clausebuilderObj.buildClause('SELECT count(*) AS cnt FROM '); +/** + * 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(); - qryString = clause; + if (self.__bucket) { + // Simply return an existing bucket-connection + deferred.resolve( self.__bucket ); + } else { + // Open a new bucket-connection + self.__openBucketConnection().then( + function(myBucket) { + deferred.resolve( myBucket ) + }, function(err) { + deferred.reject( err ); + } + ); + } + return deferred.promise; +} - 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; - }); -}; - /** - * Find a model instance by id + * Opens and returns a new bucket connection */ -CouchbaseDB.prototype.find = function find(model, id, callback) { - var self = this; - debug("CouchbaseDB.prototype.find() : " + JSON.stringify([model, id])); +CouchbaseDB.prototype.__openBucketConnection = function() { + debug("CouchbaseDB.prototype.__openBucketConnection()"); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ - if(err){ - callback(err,null); - return; - } + var self = this; - myBucket.get(id,function(err, result) { - if(err){ - callback(err,null); - return; - } - debug("CouchbaseDB.prototype.find() : find result is: " + JSON.stringify([err, result]) ); - callback(err,result); - return; - }); - }); -}; + var deferred = $q.defer(); + var myBucket = self.myCluster.openBucket(self.settings.bucket, function(err) { + if(err){ + debug(err) + deferred.reject( err ); + return deferred.promise; + } + + self.__bucket = myBucket; + deferred.resolve( self.__bucket ); + + }); + + return deferred.promise; +} /** @@ -183,40 +270,66 @@ 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); + var createDataBuilderObj = prepareCreateData(data, model); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ - if(err){ - callback(err,null); - return; - } + self.__getBucket().then( + function(myBucket) { + + // 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; + } + + // 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,now result is: " + JSON.stringify([result]) ); + + debug("CouchbaseDB.prototype.create() : created document: " + JSON.stringify([result]) ); + + callback(err,result && result.id); + + }); + + }, function(err) { + callback(err,null); + return; + } + ); - myBucket.insert(createDataBuilderObj.getIdKey(),createDataBuilderObj.getPreparedData(),function(err, result) { - if(err){ - 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); - - }); - }); }; /** * 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])); + + self.__getBucket().then( + function(myBucket) { + updateOneDoc(myBucket,undefined,model,data,callback); + }, function(err) { + callback(err,null); + return; + } + ); + }; /** @@ -224,30 +337,30 @@ 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])); - - var myBucket = self.myCluster.openBucket(self.settings.bucket,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; - }); - - }); + debug("CouchbaseDB.prototype.exists() : " + JSON.stringify([model, id])); + + + 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; + } + ); }; @@ -255,26 +368,27 @@ 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); + var createDataBuilderObj = prepareCreateData(data, model); + + self.__getBucket().then( + function(myBucket) { + updateOneDoc(myBucket,createDataBuilderObj.getIdKey(),model,createDataBuilderObj.getPreparedData(),callback); + }, function(err) { + callback(err,null); + return; + } + ); - var myBucket = self.myCluster.openBucket(self.settings.bucket,function(err){ - if(err){ - callback(err,null); - return; - } - updateOneDoc(self,myBucket,createDataBuilderObj.getIdKey(),model,createDataBuilderObj.getPreparedData(),callback); - }); }; /* 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); @@ -288,59 +402,141 @@ 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])); + + 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; + } + ); - var myBucket = self.myCluster.openBucket(self.settings.bucket,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; - - }); - - }); }; /** * 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 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; - }); + var mappings = self.settings.mappings; + var views = self.settings.views; + var keys = Object.keys(filter.where); + + + var documentId = undefined; + 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 + + 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('Can currently only query one document at a time', null); + return; + } + } + + } else if (keys.length == 1 && mappings.hasOwnProperty(model) && mappings[model].hasOwnProperty(keys[0])) { + var prefix = mappings[model][keys[0]]; + 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; + + // Get the we are looking for + var keyValue = filter.where[keys[0]]; + + // 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(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); + } }; /** * Delete all model instances */ -CouchbaseDB.prototype.destroyAll = function destroyAll(model, callback) { - debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model])); - throw "stop here !"; +CouchbaseDB.prototype.destroyAll = function destroyAll(model, where, options, callback) { + + var self = this; + debug("CouchbaseDB.prototype.destroyAll() : " + JSON.stringify([model, where])); + + // For now we just support deleting one document (deleteById) + var documentId = undefined; + if (where && where.id) { + if (typeof where.id === "object") { + 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; + } + } + + if (documentId === undefined) { + throw "stop here !"; + } + + self.destroy(model, documentId, callback); }; - + /** * Update the attributes for a model instance by id * Will be called when PUT to /model_url_name/{id} @@ -348,49 +544,57 @@ 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){ - callback(err,null); - return; - } - updateOneDoc(self,myBucket,idkey,model,data,callback); - }); + + self.__getBucket().then( + function(myBucket) { + updateOneDoc(myBucket,idkey,model,data,callback); + }, function(err) { + callback(err,null); + return; + } + ); + }; -/* +/** 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); + + // Make sure that id gets preserved so simply set them + preparedData.id = idkey; + + // This gets later used to set the cas + var options = {}; - myBucket.get(idkey,function(err, result) { + // 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; + } + + // 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){ - debug(" CouchbaseDB updateOneDoc() : Can not find the document with the id '" + id + "'" ); 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); }); - - }; /** @@ -399,13 +603,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 +626,47 @@ CouchbaseDB.prototype.query = function (sql, callback) { var self = this; callback(null,null); -}; \ No newline at end of file +}; + + +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]; + + 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; +}; 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/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/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 ba53542..9eb3599 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,43 @@ { - "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" - }, - "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": "loopback-connector-couchbase-expert", + "version": "0.8.1", + "description": "LoopBack Couchbase Connector without N1QL", + "keywords": [ + "StrongLoop", + "LoopBack", + "Couchbase", + "DataSource", + "Connector" + ], + "main": "index.js", + "scripts": { + "test": "make test" + }, + "repository": { + "type": "git", + "url": "https://github.com/janober/loopback-connector-couchbase-expert.git" + }, + "dependencies": { + "async": "~0.9.0", + "couchbase": "^2.4.2", + "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", + "q": "^1.1.2" + }, + "devDependencies": { + "rc": "~0.4.0" + }, + "license": { + "name": "MIT", + "url": "https://github.com/janober/loopback-connector-couchbase-expert/blob/master/LICENSE.md" + }, + "bugs": { + "url": "https://github.com/janober/loopback-connector-couchbase-expert/issues" + }, + "homepage": "https://github.com/janober/loopback-connector-couchbase-expert" +}