Repository: basho/riak-nodejs-client
Pull request #58 (2015-05-13 17:24:08)

CLIENTS-331
Add command to get preflist.


Patch:
diff --git a/lib/commands/kv/deletevalue.js b/lib/commands/kv/deletevalue.js
index c7e1b76..42a9054 100644
--- a/lib/commands/kv/deletevalue.js
+++ b/lib/commands/kv/deletevalue.js
@@ -273,8 +273,7 @@ Builder.prototype = {
     withVClock : function(vclock) {
         this.vclock = vclock;
         return this;
-    },
-    /**
+    },/**
     * Set a timeout for this operation.
     * @method withTimeout
     * @param {Number} timeout a timeout in milliseconds.
@@ -284,6 +283,7 @@ Builder.prototype = {
         this.timeout = timeout;
         return this;
     },
+
     /**
      * Set the callback to be executed when the operation completes.
      * @method withCallback
diff --git a/lib/commands/kv/fetchpreflist.js b/lib/commands/kv/fetchpreflist.js
new file mode 100644
index 0000000..de4b7ef
--- /dev/null
+++ b/lib/commands/kv/fetchpreflist.js
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2015 Basho Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var CommandBase = require('../commandbase');
+var inherits = require('util').inherits;
+var logger = require('winston');
+var Joi = require('joi');
+
+/**
+ * Provides the FetchPreflist class and its builder.
+ * @module KV
+ */
+
+/**
+ * Command used to fetch an object's preflist from Riak.
+ *
+ * As a convenience, a builder class is provided:
+ *
+ *     var fetch = new FetchPreflist.Builder()
+ *         .withBucketType('myBucketType')
+ *         .withBucket('myBucket')
+ *         .withKey('myKey')
+ *         .withCallback(myCallback)
+ *         .build();
+ *
+ * See {{#crossLink "FetchPreflist.Builder"}}FetchPreflist.Builder{{/crossLink}}
+ *
+ * @class FetchPreflist
+ * @constructor
+ * @param {Object} options The options for this command.
+ * @param {String} [options.bucketType=default] The bucket type in riak. If not supplied 'default' is used.
+ * @param {String} options.bucket The bucket in riak.
+ * @param {String} options.key The key for the object you want to fetch.
+ * @param {Function} callback The callback to be executed when the operation completes.
+ * @param {String} callback.err An error message. Will ne null if no error.
+ * @param {Object} callback.response the response from Riak.
+ * @param {Object[]} callback.response.preflist An array of one or more preflist entries.
+ * @extends CommandBase
+ */
+
+function FetchPreflist(options, callback) {
+
+    CommandBase.call(this, 'RpbGetBucketKeyPreflistReq', 'RpbGetBucketKeyPreflistResp', callback);
+
+    var self = this;
+    Joi.validate(options, schema, function(err, options) {
+
+        if (err) {
+            throw err;
+        }
+
+        self.options = options;
+    });
+
+    this.streaming = false;
+    this.remainingTries = 1;
+}
+
+inherits(FetchPreflist, CommandBase);
+
+FetchPreflist.prototype.constructPbRequest = function() {
+
+    var protobuf = this.getPbReqBuilder();
+
+    protobuf.setType(new Buffer(this.options.bucketType));
+    protobuf.setBucket(new Buffer(this.options.bucket));
+    protobuf.setKey(new Buffer(this.options.key));
+
+    return protobuf;
+
+};
+
+FetchPreflist.prototype.onSuccess = function(rpbGetResp) {
+
+    var response = { preflist: [] };
+
+    if (rpbGetResp) {
+        var pbPreflistArray = rpbGetResp.getPreflist();
+        if (pbPreflistArray.length !== 0) {
+            var preflist = new Array(pbPreflistArray.length);
+            for (var i = 0; i < pbPreflistArray.length; ++i) {
+                var pi = pbPreflistArray[i];
+                preflist[i] = {
+                    partition: pi.partition.toNumber(),
+                    node: pi.node.toString('utf8'),
+                    primary: pi.primary
+                };
+            }
+            response = { preflist: preflist };
+        }
+    }
+
+    this._callback(null, response);
+
+    return true;
+};
+
+var schema = Joi.object().keys({
+   bucketType: Joi.string().default('default'),
+   bucket: Joi.string().required(),
+   key: Joi.string().required()
+});
+
+/**
+ * A builder for constructing FetchPreflist instances.
+ *
+ * Rather than having to manually construct the __options__ and instantiating
+ * a FetchPreflist directly, this builder may be used.
+ *
+ *     var fetchValue = new FetchPreflist.Builder()
+ *          .withBucketType('myBucketType')
+ *          .withBucket('myBucket')
+ *          .withKey('myKey')
+ *          .build();
+ *
+ * @class FetchPreflist.Builder
+ * @constructor
+ */
+function Builder() {}
+
+Builder.prototype = {
+
+    /**
+     * Set the bucket type.
+     * If not supplied, 'default' is used.
+     * @method withBucketType
+     * @param {String} bucketType the bucket type in riak
+     * @chainable
+     */
+    withBucketType : function(bucketType) {
+        this.bucketType = bucketType;
+        return this;
+    },
+    /**
+     * Set the bucket.
+     * @method withBucket
+     * @param {String} bucket the bucket in Riak
+     * @chainable
+     */
+    withBucket : function(bucket) {
+        this.bucket = bucket;
+        return this;
+    },
+    /**
+     * Set the key.
+     * @method withKey
+     * @param {String} key the key in riak.
+     * @chainable
+     */
+    withKey : function(key) {
+        this.key = key;
+        return this;
+    },
+    /**
+     * Set the callback to be executed when the operation completes.
+     * @method withCallback
+     * @param {Function} callback The callback to be executed when the operation completes.
+     * @param {String} callback.err An error message. Will ne null if no error.
+     * @param {Object} callback.response the response from Riak.
+     * @param {Object[]} callback.response.preflist An array of one or more preflist items.
+     * @chainable
+     */
+    withCallback : function(callback) {
+        this.callback = callback;
+        return this;
+    },
+    /**
+     * Construct a FetchPreflist command.
+     * @method build
+     * @return {FetchPreflist}
+     */
+    build : function() {
+        var cb = this.callback;
+        delete this.callback;
+        return new FetchPreflist(this, cb);
+    }
+
+};
+
+module.exports = FetchPreflist;
+module.exports.Builder = Builder;
diff --git a/lib/commands/yokozuna/storeindex.js b/lib/commands/yokozuna/storeindex.js
index 19b56d1..9bbc20b 100644
--- a/lib/commands/yokozuna/storeindex.js
+++ b/lib/commands/yokozuna/storeindex.js
@@ -43,23 +43,27 @@ var RpbYokozunaIndex = require('../../protobuf/riakprotobuf').getProtoFor('RpbYo
  * @param {Object} options The options for this command
  * @param {String} options.indexName The name of the index.
  * @param {String} [options.schemaName=_yz_default] The name of the schema for this index.
- * @param {Number} [options.timeout] Set a timeout for this operation.
  * @param {Function} callback The callback to be executed when the operation completes.
  * @param {String} callback.err An error message. Will be null if no error.
  * @param {Boolean} callback.response The operation either succeeds or errors. This will be true.
  * @extends CommandBase
  */
 function StoreIndex(options, callback) {
+
     CommandBase.call(this, 'RpbYokozunaIndexPutReq' , 'RpbPutResp', callback);
     var self = this;
     Joi.validate(options, schema, function(err, options) {
+
         if (err) {
             throw err;
         }
+
         self.options = options;
     });
 
     this.remainingTries = 1;
+
+
 }
 
 inherits(StoreIndex, CommandBase);
@@ -69,33 +73,27 @@ StoreIndex.prototype.constructPbRequest = function() {
     var protobuf = this.getPbReqBuilder();
 
     var pbIndex = new RpbYokozunaIndex();
-
     pbIndex.setName(new Buffer(this.options.indexName));
-
     if (this.options.schemaName) {
         pbIndex.setSchema(new Buffer(this.options.schemaName));
     }
-
     protobuf.index = pbIndex;
-
-    if (this.options.timeout) {
-        protobuf.setTimeout(this.options.timeout);
-    }
-
     return protobuf;
 
 };
 
 StoreIndex.prototype.onSuccess = function(RpbPutResp) {
+
     // RpbPutResp is simply null (no body)
+
     this._callback(null, true);
     return true;
 };
 
+
 var schema = Joi.object().keys({
     indexName: Joi.string().required(),
-    schemaName: Joi.string().default(null).optional(),
-    timeout: Joi.number().default(null).optional()
+    schemaName: Joi.string().default(null).optional()
 });
 
 /**
@@ -138,16 +136,6 @@ Builder.prototype = {
         this.schemaName = schemaName;
         return this;
     },
-    /**
-    * Set a timeout for this operation.
-    * @method withTimeout
-    * @param {Number} timeout a timeout in milliseconds.
-    * @chainable
-    */
-    withTimeout : function(timeout) {
-        this.timeout = timeout;
-        return this;
-    },
     /**
      * Set the callback to be executed when the operation completes.
      * @method withCallback


Test patch:
diff --git a/test/integration/kv/fetchpreflist.js b/test/integration/kv/fetchpreflist.js
new file mode 100644
index 0000000..0817739
--- /dev/null
+++ b/test/integration/kv/fetchpreflist.js
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 Basho Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var Test = require('../testparams');
+var FetchPreflist = require('../../../lib/commands/kv/fetchpreflist');
+var RiakNode = require('../../../lib/core/riaknode');
+var RiakCluster = require('../../../lib/core/riakcluster');
+var assert = require('assert');
+var logger = require('winston');
+
+var cb = function (err, resp) {
+    assert(!err, err);
+    assert.equal(resp.preflist.length, 3); // NB: since nval is 3
+};
+
+describe('FetchPreflist - Integration', function() {
+    var cluster;
+    this.timeout(10000);
+
+    before(function(done) {
+        var nodes = RiakNode.buildNodes(Test.nodeAddresses);
+        cluster = new RiakCluster({ nodes: nodes});
+        cluster.start();
+        done();
+    });
+
+    it('Should fetch a preflist from Riak (default type)', function(done) {
+        var fetch = new FetchPreflist.Builder()
+                .withBucket(Test.bucketName)
+                .withKey('my_key1')
+                .withCallback(function (err, resp) { cb(err, resp); done(); })
+                .build();
+        
+        cluster.execute(fetch);
+    });
+    
+    it('Should fetch a preflist for a non-default bucket-type from Riak', function(done) {
+        var callback = function(err, resp) {
+            assert(!err, err);
+            assert.equal(resp.preflist.length, 3); // NB: since nval is 3
+            done();
+        };
+        
+        var fetch = new FetchPreflist.Builder()
+                .withBucket(Test.bucketName)
+                .withBucketType(Test.bucketType)
+                .withKey('my_key1')
+                .withCallback(function (err, resp) { cb(err, resp); done(); })
+                .build();
+        
+        cluster.execute(fetch);
+    });
+});
diff --git a/test/integration/kv/fetchvalue.js b/test/integration/kv/fetchvalue.js
index 96115e8..6c7f568 100644
--- a/test/integration/kv/fetchvalue.js
+++ b/test/integration/kv/fetchvalue.js
@@ -26,7 +26,6 @@ describe('FetchValue - Integration', function() {
     var cluster;
     this.timeout(10000);
     
-    
     before(function(done) {
         var nodes = RiakNode.buildNodes(Test.nodeAddresses);
         cluster = new RiakCluster({ nodes: nodes});
@@ -39,7 +38,6 @@ describe('FetchValue - Integration', function() {
             if (count === 5) {
                 done();
             }
-            
         };
         
         var store = new StoreValue.Builder()
@@ -73,7 +71,6 @@ describe('FetchValue - Integration', function() {
                 .build();
             
             cluster.execute(store);
-               
        }
 
         store = new StoreValue.Builder()
@@ -85,9 +82,6 @@ describe('FetchValue - Integration', function() {
                 .build();
 
         cluster.execute(store);
-
-        
-        
     });
     
     after(function(done) {
@@ -97,11 +91,9 @@ describe('FetchValue - Integration', function() {
                 cluster.stop();
             });
         });
-        
    });
 
     it('Should fetch a value from Riak (default type)', function(done) {
-       
         var callback = function(err, resp) {
             assert(!err, err);
             assert.equal(resp.values.length, 1);
@@ -117,7 +109,6 @@ describe('FetchValue - Integration', function() {
                 .build();
         
         cluster.execute(fetch);
-        
     });
     
     it('Should fetch a value in a non-default bucket-type from Riak', function(done) {
diff --git a/test/integration/testparams.js b/test/integration/testparams.js
index 484e979..788ced3 100644
--- a/test/integration/testparams.js
+++ b/test/integration/testparams.js
@@ -24,10 +24,10 @@ module.exports.bucketName = 'riak_index_tests';
 /**
 * Bucket type
 * 
-* you must create the type 'plain' to use this:
+* you must create the type 'leveldb_type' to use this:
 *
-* riak-admin bucket-type create plain '{"props":{}}'
-* riak-admin bucket-type activate plain
+* riak-admin bucket-type create leveldb_type '{"props":{"backend":"leveldb_backend"}}'
+* riak-admin bucket-type activate leveldb_type
 */
 module.exports.bucketType = 'leveldb_type';
 
@@ -37,9 +37,11 @@ if (process.env.RIAK_HOST && process.env.RIAK_PORT) {
     riakHost = process.env.RIAK_HOST;
     riakPort = process.env.RIAK_PORT;
 }
+
 module.exports.nodeAddresses = [ riakHost + ':' + riakPort ];
 module.exports.riakHost = riakHost;
 module.exports.riakPort = riakPort;
+
 /**
  * 
  * CRDTs - need to create these types 
@@ -101,4 +103,3 @@ module.exports.cleanBucket = function(cluster, type, bucket, callback) {
     cluster.execute(list);
     
 };
-
diff --git a/test/unit/fetchpreflist.js b/test/unit/fetchpreflist.js
new file mode 100644
index 0000000..2bf5d69
--- /dev/null
+++ b/test/unit/fetchpreflist.js
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015 Basho Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var FetchPreflist = require('../../lib/commands/kv/fetchpreflist');
+var rpbuf = require('../../lib/protobuf/riakprotobuf');
+var RpbGetBucketKeyPreflistReq = rpbuf.getProtoFor('RpbGetBucketKeyPreflistReq');
+var RpbGetBucketKeyPreflistResp = rpbuf.getProtoFor('RpbGetBucketKeyPreflistResp');
+var RpbBucketKeyPreflistItem = rpbuf.getProtoFor('RpbBucketKeyPreflistItem');
+var RpbErrorResp = rpbuf.getProtoFor('RpbErrorResp');
+
+var assert = require('assert');
+
+var bucketType = 'bucket_type_name';
+var bucket = 'bucket_name';
+var key = 'key_name';
+
+function getRandom() {
+  return Math.floor(Math.random() * Math.pow(2, 32));
+}
+
+describe('FetchPreflist', function() {
+
+    describe('Build', function() {
+
+        it('should build a RpbGetBucketKeyPreflistReq correctly', function(done) {
+            var fetchCommand = new FetchPreflist.Builder()
+               .withBucketType(bucketType)
+               .withBucket(bucket)
+               .withKey(key)
+               .withCallback(function(){})
+               .build();
+       
+            var protobuf = fetchCommand.constructPbRequest();
+            
+            assert.equal(protobuf.getType().toString('utf8'), bucketType);
+            assert.equal(protobuf.getBucket().toString('utf8'), bucket);
+            assert.equal(protobuf.getKey().toString('utf8'), key);
+
+            done();
+        });
+        
+        it('should take a RpbGetBucketKeyPreflistResp and call the users callback with the response', function(done) {
+            var partitionId = getRandom();
+            var node_name = 'node-foo';
+
+            var rpbItem = new RpbBucketKeyPreflistItem();
+            rpbItem.setPartition(partitionId);
+            rpbItem.setNode(new Buffer(node_name));
+            rpbItem.setPrimary(true);
+            
+            var rpbGetResp = new RpbGetBucketKeyPreflistResp();
+            rpbGetResp.preflist.push(rpbItem);
+            
+            var callback = function(err, response) {
+                if (response) {
+                    assert.equal(response.preflist.length, 1);
+                    var preflistItem = response.preflist.shift();
+                    assert.equal(preflistItem.partition, partitionId);
+                    assert.equal(preflistItem.node, node_name);
+                    assert.equal(preflistItem.primary, true);
+                    done();
+                }
+            };
+            
+            var fetchCommand = new FetchPreflist.Builder()
+                .withBucketType(bucketType)
+                .withBucket(bucket)
+                .withKey(key)
+                .withCallback(callback)
+                .build();
+
+            fetchCommand.onSuccess(rpbGetResp);
+        });
+       
+       it ('should take a RpbErrorResp and call the users callback with the error message', function(done) {
+           var rpbErrorResp = new RpbErrorResp();
+           rpbErrorResp.setErrmsg(new Buffer('this is an error'));
+           
+           var callback = function(err, response) {
+               if (err) {
+                   assert.equal(err,'this is an error');
+                   done();
+               }
+           };
+           
+           var fetchCommand = new FetchPreflist.Builder()
+               .withBucketType('bucket_type')
+               .withBucket('bucket_name')
+               .withKey('key')
+               .withCallback(callback)
+               .build();
+       
+            fetchCommand.onRiakError(rpbErrorResp);
+       });
+
+    });
+});
diff --git a/test/unit/yokozuna/storeindex.js b/test/unit/yokozuna/storeindex.js
index ce05a4f..4572de6 100644
--- a/test/unit/yokozuna/storeindex.js
+++ b/test/unit/yokozuna/storeindex.js
@@ -20,6 +20,7 @@ var RpbErrorResp = require('../../../lib/protobuf/riakprotobuf').getProtoFor('Rp
 
 var assert = require('assert');
 
+
 describe('StoreIndex', function() {
     describe('Build', function() {
         it('should build a RpbYokozunaIndexPutReq correctly', function(done) {
@@ -27,83 +28,55 @@ describe('StoreIndex', function() {
             var store = new StoreIndex.Builder()
                     .withIndexName('indexName')
                     .withSchemaName('schemaName')
-                    .withTimeout(60000)
                     .withCallback(function(){})
                     .build();
             
             var protobuf = store.constructPbRequest();
-            assert.equal(protobuf.getTimeout(), 60000);
-
             var index = protobuf.index;
             assert.equal(index.getName().toString('utf8'), 'indexName');
             assert.equal(index.getSchema().toString('utf8'), 'schemaName');
             done();
         });
-
-        it('should only require name and callback', function(done) {
-
-            assert.throws(function () {
-                new StoreIndex.Builder().build();
-            });
-
-            assert.throws(function () {
-                new StoreIndex.Builder()
-                    .withIndexName('indexName')
-                    .build();
-            });
-
-            assert.throws(function () {
-                new StoreIndex.Builder()
-                    .withCallback(function () {})
-                    build();
-            });
-
-            assert.doesNotThrow(function () {
-                new StoreIndex.Builder()
-                    .withIndexName('indexName')
-                    .withCallback(function () {})
-                    .build();
-            });
-
-            done();
-        });
     });
     
     it('should take a RpbPutResp and call the users callback with the response', function(done) {
+           
             
-        var callback = function(err, response) {
-            assert.equal(response, true);
-            done();
-        };
-        
-        var store = new StoreIndex.Builder()
-                .withIndexName('indexName')
-                .withCallback(callback)
-                .build();
-        
-        // the RpbPutResp is a null message (no body) from riak
-        store.onSuccess(null);
-        
-    });
-        
-    it ('should take a RpbErrorResp and call the users callback with the error message', function(done) {
-        var rpbErrorResp = new RpbErrorResp();
-        rpbErrorResp.setErrmsg(new Buffer('this is an error'));
-        
-        var callback = function(err, response) {
-            if (err) {
-                assert.equal(err,'this is an error');
+            var callback = function(err, response) {
+              
+                assert.equal(response, true);
                 done();
-            }
-        };
-        
-        var store = new StoreIndex.Builder()
-                .withIndexName('indexName')
-                .withCallback(callback)
-                .build();
-        
-        store.onRiakError(rpbErrorResp);
+            };
+            
+            var store = new StoreIndex.Builder()
+                    .withIndexName('indexName')
+                    .withCallback(callback)
+                    .build();
+            
+            // the RpbPutResp is a null message (no body) from riak
+            store.onSuccess(null);
+            
+        });
         
-    });
+        it ('should take a RpbErrorResp and call the users callback with the error message', function(done) {
+           var rpbErrorResp = new RpbErrorResp();
+           rpbErrorResp.setErrmsg(new Buffer('this is an error'));
+           
+           var callback = function(err, response) {
+               if (err) {
+                   assert.equal(err,'this is an error');
+                   done();
+               }
+           };
+           
+           var store = new StoreIndex.Builder()
+                    .withIndexName('indexName')
+                    .withCallback(callback)
+                    .build();
+            
+            store.onRiakError(rpbErrorResp);
+           
+       });
+
     
 });