Repository: basho/riak-nodejs-client
Pull request #123 (2016-01-05 21:31:03)

READY: RTS-666 / RTS-798
* Add `DESCRIBE` tests
* Add method to encapsulate retrieving table description
* Add tests to ensure `CREATE TABLE` works correctly via query method


Patch:
diff --git a/lib/client.js b/lib/client.js
index b955e29..b47c175 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -458,6 +458,18 @@ Client.prototype.tsStore = function(options, callback) {
     this.cluster.execute(cmd);
 };
 
+/**
+ * See {{#crossLink "Describe"}}{{/crossLink}}
+ * @method tsDescribe
+ * @param {Object} options The options for this operation.
+ * @param {Function} callback The callback to be executed when the operation completes.
+ * 
+ */
+Client.prototype.tsDescribe = function(options, callback) {
+    var cmd = new TS.Describe(options, callback);
+    this.cluster.execute(cmd);
+};
+
 /**
  * See {{#crossLink "Query"}}{{/crossLink}}
  * @method tsQuery
diff --git a/lib/commands/ts.js b/lib/commands/ts.js
index 976b2b2..6c87053 100644
--- a/lib/commands/ts.js
+++ b/lib/commands/ts.js
@@ -11,6 +11,7 @@ function TS() { }
 
 // TS exports
 module.exports = TS;
+module.exports.Describe = require('./ts/describe');
 module.exports.Store = require('./ts/store');
 module.exports.Query = require('./ts/query');
 module.exports.Get = require('./ts/get');
diff --git a/lib/commands/ts/describe.js b/lib/commands/ts/describe.js
new file mode 100644
index 0000000..a042d07
--- /dev/null
+++ b/lib/commands/ts/describe.js
@@ -0,0 +1,111 @@
+'use strict';
+
+var inherits = require('util').inherits;
+var Joi = require('joi');
+var logger = require('winston');
+
+var Query = require('./query');
+
+/**
+ * Provides the Describe class, its builder, and its response.
+ * @module TS
+ */
+
+/**
+ * Command used to get a timeseries table's description.
+ *
+ * As a convenience, a builder class is provided:
+ *
+ *      var storeValue = new Describe.Builder()
+ *          .withTable('GeoCheckin')
+ *          .withCallback(callback)
+ *          .build();
+ *
+ * See {{#crossLink "Describe.Builder"}}Describe.Builder{{/crossLink}}
+ *
+ * @class Describe
+ * @constructor
+ * @param {Object} options The options for this command.
+ * @param {String} options.table The timeseries table in Riak.
+ * @param {Function} callback The allback to be executed when the operation completes.
+ * @param {String} callback.err An error message. Will be null if no error.
+ * @param {Object} callback.response Object containing timeseries table metadata.
+ * @param {Object} callback.response.columns Timeseries table metadata columns.
+ * @param {Object} callback.response.rows Timeseries table metadata rows.
+ * @param {Object} callback.data additional error data. Will be null if no error.
+ * @extends Query
+ */
+function Describe(options, callback) {
+    var self = this;
+    Joi.validate(options, schema, function(err, options) {
+        if (err) {
+            throw err;
+        }
+        self.options = options;
+    });
+
+    var queryOptions = {
+        query : 'DESCRIBE ' + self.options.table
+    };
+
+    Query.call(this, queryOptions, callback);
+}
+
+inherits(Describe, Query);
+
+var schema = Joi.object().keys({
+    table: Joi.string().required()
+});
+
+/**
+ * A builder for constructing Describe instances.
+ *
+ * Rather than having to manually construct the __options__ and instantiating
+ * a Describe directly, this builder may be used.
+ *
+ *     var storeValue = new Describe.Builder()
+ *          .withTable('table')
+ *          .build();
+ *
+ * @class Describe.Builder
+ * @constructor
+ */
+function Builder() {}
+
+Builder.prototype = {
+    /**
+     * Set the timeseries table.
+     * @method withDescribe
+     * @param {String} table the timeseries table
+     * @chainable
+     */
+    withTable : function(table) {
+        this.table = table;
+        return this;
+    },
+    /**
+     * Set the callback to be executed when the operation completes.
+     * @method withCallback
+     * @param {Function} callback The allback to be executed when the operation completes.
+     * @param {String} callback.err An error message. Will be null if no error.
+     * @param {Object} callback.response The response from Riak
+     * @chainable
+     */
+    withCallback : function(callback) {
+        this.callback = callback;
+        return this;
+    },
+    /**
+     * Construct a Describe instance.
+     * @method build
+     * @return {Describe} a Describe instance
+     */
+    build : function() {
+        var cb = this.callback;
+        delete this.callback;
+        return new Describe(this, cb);
+    }
+};
+
+module.exports = Describe;
+module.exports.Builder = Builder;
diff --git a/lib/commands/ts/query.js b/lib/commands/ts/query.js
index e920355..8ffa21c 100644
--- a/lib/commands/ts/query.js
+++ b/lib/commands/ts/query.js
@@ -65,12 +65,6 @@ Query.prototype.constructPbRequest = function() {
     return protobuf;
 };
 
-function makeSetValueDeserializer(set) {
-    return function (rpbval) {
-        set.push(JSON.parse(rpbval.toString('utf8')));
-    };
-}
-
 Query.prototype.onSuccess = function(rpbQueryResp) {
     var response = tsutils.convertToResponse(rpbQueryResp);
     this._callback(null, response);


Test patch:
diff --git a/test/integration/ts/timeseries.js b/test/integration/ts/timeseries.js
index 505bc36..23da0f8 100644
--- a/test/integration/ts/timeseries.js
+++ b/test/integration/ts/timeseries.js
@@ -8,6 +8,7 @@ var RiakCluster = require('../../../lib/core/riakcluster');
 
 var assert = require('assert');
 var logger = require('winston');
+var rs = require('randomstring');
 
 var tableName = 'GeoCheckin';
 var now = 1443796900000; // Let's just pretend this is the value of Date.now();
@@ -66,6 +67,37 @@ describe('Timeseries - Integration', function () {
     });
 
     describe('Query', function () {
+        it('can describe a timeseries table', function(done) {
+            var queryText = 'DESCRIBE GeoCheckin';
+            var callback = function(err, resp) {
+                assert(!err, err);
+                assert.equal(resp.columns.length, 5);
+                assert.equal(resp.rows.length, 5);
+                done();
+            };
+            var q = new TS.Query.Builder()
+                .withQuery(queryText)
+                .withCallback(callback)
+                .build();
+            cluster.execute(q);
+        });
+        it('can create a new timeseries table', function(done) {
+            var tmp = rs.generate(32);
+            var queryText = 'CREATE TABLE ' + tmp +
+                '(geohash varchar not null, user varchar not null, time timestamp not null, weather varchar not null, temperature double, ' +
+                'PRIMARY KEY((geohash, user, quantum(time, 15, m)), geohash, user, time))';
+            var callback = function(err, resp) {
+                assert(!err, err);
+                assert.equal(resp.columns.length, 0);
+                assert.equal(resp.rows.length, 0);
+                done();
+            };
+            var q = new TS.Query.Builder()
+                .withQuery(queryText)
+                .withCallback(callback)
+                .build();
+            cluster.execute(q);
+        });
         it('no matches returns no data', function(done) {
             var queryText = "select * from GeoCheckin where time > 0 and time < 10 and geohash = 'hash1' and user = 'user1'";
             var callback = function(err, resp) {
@@ -140,6 +172,22 @@ describe('Timeseries - Integration', function () {
         });
     });
 
+    describe('Describe', function () {
+        it('returns timeseries table description', function(done) {
+            var callback = function(err, resp) {
+                assert(!err, err);
+                assert.equal(resp.columns.length, 5);
+                assert.equal(resp.rows.length, 5);
+                done();
+            };
+            var cmd = new TS.Describe.Builder()
+                .withTable(tableName)
+                .withCallback(callback)
+                .build();
+            cluster.execute(cmd);
+        });
+    });
+
     describe('Get', function () {
         it('returns one row of TS data', function(done) {
             var callback = function(err, resp) {
diff --git a/test/unit/client.js b/test/unit/client.js
index a9d8fa9..eb57383 100644
--- a/test/unit/client.js
+++ b/test/unit/client.js
@@ -79,6 +79,11 @@ describe('Client', function() {
             assert(typeofTsStore === 'function', "typeof(Riak.Commands.TS.Store): " + typeofTsStore);
             done();
         });
+        it('Riak.Commands.TS.Describe', function(done) {
+            var typeofTsDescribe = typeof(Riak.Commands.TS.Describe);
+            assert(typeofTsDescribe === 'function', "typeof(Riak.Commands.TS.Describe): " + typeofTsDescribe);
+            done();
+        });
         it('Riak.Commands.TS.Query', function(done) {
             var typeofTsQuery = typeof(Riak.Commands.TS.Query);
             assert(typeofTsQuery === 'function', "typeof(Riak.Commands.TS.Query): " + typeofTsQuery);
diff --git a/test/unit/commandbase.js b/test/unit/commandbase.js
index e01f972..4d1576e 100644
--- a/test/unit/commandbase.js
+++ b/test/unit/commandbase.js
@@ -332,6 +332,15 @@ describe('CommandBase', function() {
                         b.withRows([ ['foo', 'bar', 'baz'], ['bat', 'bing', 'zing'] ]);
                     }
                 },
+            'TS.Describe' : {
+                    options : {
+                        table: ts_options.table,
+                        callback: cb
+                    },
+                    builder_func : function (b) {
+                        b.withTable(ts_options.table);
+                    }
+                },
             'TS.Query' : {
                     options : {
                         query: 'select * from baz',