Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
cassandra-driver / test / unit / metadata-tests.js
Size: Mime:
"use strict";
var assert = require('assert');
var util = require('util');
var events = require('events');
var rewire = require('rewire');

var helper = require('../test-helper.js');
var clientOptions = require('../../lib/client-options.js');
var Host = require('../../lib/host.js').Host;
var Metadata = require('../../lib/metadata');
var TableMetadata = require('../../lib/metadata/table-metadata');
var tokenizer = require('../../lib/tokenizer');
var types = require('../../lib/types');
var dataTypes = types.dataTypes;
var utils = require('../../lib/utils');
var errors = require('../../lib/errors');
var Encoder = require('../../lib/encoder');

describe('Metadata', function () {
  describe('#refreshKeyspaces()', function () {
    it('should parse C*2 keyspace metadata for simple strategy', function (done) {
      var cc = {
        query: function (q, w, cb) {
          cb(null, { rows: [{
            'keyspace_name': 'ks1',
            'strategy_class': 'org.apache.cassandra.locator.SimpleStrategy',
            'strategy_options': '{"replication_factor": 3}',
            'durable_writes': false
          }]});
        }
      };
      //noinspection JSCheckFunctionSignatures
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.tokenizer = new tokenizer.Murmur3Tokenizer();
      metadata.ring = [0, 1, 2, 3, 4, 5];
      metadata.primaryReplicas = {'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5'};
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function (err) {
        assert.ifError(err);
        assert.ok(metadata.keyspaces);
        var ks = metadata.keyspaces['ks1'];
        assert.ok(ks);
        assert.strictEqual(ks.strategy, 'org.apache.cassandra.locator.SimpleStrategy');
        assert.ok(ks.strategyOptions);
        assert.strictEqual(ks.strategyOptions['replication_factor'], 3);
        assert.strictEqual(ks.durableWrites, false);
        done();
      });
    });
    it('should parse C*2 keyspace metadata for network strategy', function (done) {
      var cc = {
        query: function (q, w, cb) {
          cb(null, { rows: [{
            'keyspace_name': 'ks2',
            'strategy_class': 'org.apache.cassandra.locator.NetworkTopologyStrategy',
            'strategy_options': '{"dc1": 3, "dc2": 1}'
          }]});
        }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.tokenizer = new tokenizer.Murmur3Tokenizer();
      metadata.ring = [0, 1, 2, 3, 4, 5];
      metadata.primaryReplicas = {'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5'};
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function (err) {
        assert.ifError(err);
        assert.ok(metadata.keyspaces);
        var ks = metadata.keyspaces['ks2'];
        assert.ok(ks);
        assert.strictEqual(ks.strategy, 'org.apache.cassandra.locator.NetworkTopologyStrategy');
        assert.ok(ks.strategyOptions);
        assert.strictEqual(ks.strategyOptions['dc1'], 3);
        assert.strictEqual(ks.strategyOptions['dc2'], 1);
        done();
      });
    });
    it('should parse C*3 keyspace metadata for simple strategy', function (done) {
      var cc = {
        query: function (q, w, cb) {
          cb(null, { rows: [{
            'keyspace_name': 'ks1',
            'replication': {'class': 'org.apache.cassandra.locator.SimpleStrategy', 'replication_factor': '3'},
            'durable_writes': true
          }]});
        }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.tokenizer = new tokenizer.Murmur3Tokenizer();
      metadata.setCassandraVersion([3, 0]);
      metadata.ring = [0, 1, 2, 3, 4, 5];
      metadata.primaryReplicas = {'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5'};
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function (err) {
        assert.ifError(err);
        assert.ok(metadata.keyspaces);
        var ks = metadata.keyspaces['ks1'];
        assert.ok(ks);
        assert.strictEqual(ks.strategy, 'org.apache.cassandra.locator.SimpleStrategy');
        assert.ok(ks.strategyOptions);
        assert.strictEqual(ks.strategyOptions['replication_factor'], '3');
        assert.strictEqual(ks.durableWrites, true);
        done();
      });
    });
    it('should parse C*3 keyspace metadata for network strategy', function (done) {
      var cc = {
        query: function (q, w, cb) {
          cb(null, { rows: [{
            'keyspace_name': 'ks2',
            'replication': {'class': 'org.apache.cassandra.locator.NetworkTopologyStrategy', 'datacenter1': '2'},
            'durable_writes': true
          }]});
        }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.tokenizer = new tokenizer.Murmur3Tokenizer();
      metadata.setCassandraVersion([3, 0]);
      metadata.ring = [0, 1, 2, 3, 4, 5];
      metadata.primaryReplicas = {'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5'};
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function (err) {
        assert.ifError(err);
        assert.ok(metadata.keyspaces);
        var ks = metadata.keyspaces['ks2'];
        assert.ok(ks);
        assert.strictEqual(ks.strategy, 'org.apache.cassandra.locator.NetworkTopologyStrategy');
        assert.ok(ks.strategyOptions);
        assert.strictEqual(ks.strategyOptions['datacenter1'], '2');
        done();
      });
    });
  });
  describe('#getReplicas()', function () {
    it('should return depending on the rf and ring size with simple strategy', function () {
      var cc = {
        query: function (q, w, cb) {
          cb(null, { rows: [{
            'keyspace_name': 'dummy',
            'strategy_class': 'SimpleStrategy',
            'strategy_options': '{"replication_factor": 3}'
          }]});
        }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.tokenizer = new tokenizer.Murmur3Tokenizer();
      //Use the value as token
      metadata.tokenizer.hash = function (b) { return b[0];};
      metadata.tokenizer.compare = function (a, b) {if (a > b) {return 1;} if (a < b) {return -1;} return 0;};
      metadata.ring = [0, 1, 2, 3, 4, 5];
      metadata.primaryReplicas = {'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5'};
      metadata.log = helper.noop;
      metadata.refreshKeyspaces();
      var replicas = metadata.getReplicas('dummy', new Buffer([0]));
      assert.ok(replicas);
      //Primary replica plus the 2 next tokens
      assert.strictEqual(replicas.length, 3);
      assert.strictEqual(replicas[0], '0');
      assert.strictEqual(replicas[1], '1');
      assert.strictEqual(replicas[2], '2');

      replicas = metadata.getReplicas('dummy', new Buffer([5]));
      assert.ok(replicas);
      assert.strictEqual(replicas.length, 3);
      assert.strictEqual(replicas[0], '5');
      assert.strictEqual(replicas[1], '0');
      assert.strictEqual(replicas[2], '1');
    });
    it('should return depending on the dc rf with network topology', function (done) {
      var cc = getControlConnectionForRows([{
        'keyspace_name': 'dummy',
        'strategy_class': 'NetworkTopologyStrategy',
        'strategy_options': '{"dc1": "3", "dc2": "1"}'
      }]);
      var options = clientOptions.extend({}, helper.baseOptions);
      var metadata = new Metadata(options, cc);
      metadata.tokenizer = getTokenizer();
      var racks = new utils.HashSet();
      racks.add('rack1');
      metadata.datacenters = {
        'dc1': { hostLength: 4, racks: racks },
        'dc2': { hostLength: 4, racks: racks }};
      metadata.ring = [0, 1, 2, 3, 4, 5, 6, 7];
      //load primary replicas
      metadata.primaryReplicas = {};
      for (var i = 0; i < metadata.ring.length; i ++) {
        var h = new Host(i.toString(), 2, options);
        h.datacenter = 'dc' + ((i % 2) + 1);
        h.rack = 'rack1';
        metadata.primaryReplicas[i.toString()] = h;
      }
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function () {
        var replicas = metadata.getReplicas('dummy', new Buffer([0]));
        assert.ok(replicas);
        //3 replicas from dc1 and 1 replica from dc2
        assert.strictEqual(replicas.length, 4);
        assert.strictEqual(replicas[0].address, '0');
        assert.strictEqual(replicas[1].address, '1');
        assert.strictEqual(replicas[2].address, '2');
        assert.strictEqual(replicas[3].address, '4');
        done();
      });
    });
    it('should return depending on the dc rf and rack with network topology', function (done) {
      var cc = getControlConnectionForRows([{
        'keyspace_name': 'dummy',
        'strategy_class': 'NetworkTopologyStrategy',
        'strategy_options': '{"dc1": "3", "dc2": "3", "non_existent_dc": "1"}'
      }]);
      var options = clientOptions.extend({}, helper.baseOptions);
      var metadata = new Metadata(options, cc);
      metadata.tokenizer = getTokenizer();
      var racksDc1 = new utils.HashSet();
      racksDc1.add('dc1_r1');
      racksDc1.add('dc1_r2');
      var racksDc2 = new utils.HashSet();
      racksDc2.add('dc2_r1');
      racksDc2.add('dc2_r2');
      metadata.datacenters = {
        'dc1': { hostLength: 4, racks: racksDc1 },
        'dc2': { hostLength: 4, racks: racksDc2 }};
      metadata.ring = [0, 1, 2, 3, 4, 5, 6, 7];
      //load primary replicas
      metadata.primaryReplicas = {};
      for (var i = 0; i < metadata.ring.length; i ++) {
        // Hosts with in alternate dc and alternate rack
        var h = new Host(i.toString(), 2, options);
        h.datacenter = 'dc' + ((i % 2) + 1);
        h.rack = h.datacenter + '_r' + ((i % 4) <= 1 ? 1 : 2);
        metadata.primaryReplicas[i.toString()] = h;
      }
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function () {
        var replicas = metadata.getReplicas('dummy', new Buffer([0]));
        assert.ok(replicas);
        assert.deepEqual(replicas.map(getAddress), [ '0', '1', '2', '3', '4', '5' ]);
        replicas = metadata.getReplicas('dummy', new Buffer([1]));
        assert.ok(replicas);
        assert.deepEqual(replicas.map(getAddress), [ '1', '2', '3', '4', '5', '6' ]);
        replicas = metadata.getReplicas('dummy', new Buffer([3]));
        assert.ok(replicas);
        assert.deepEqual(replicas.map(getAddress), [ '3', '4', '5', '6', '7', '0' ]);
        done();
      });
    });
    it('should return depending on the dc rf and rack with network topology and skipping hosts', function (done) {
      var cc = getControlConnectionForRows([{
        'keyspace_name': 'dummy',
        'strategy_class': 'NetworkTopologyStrategy',
        'strategy_options': '{"dc1": "3", "dc2": "2"}'
      }]);
      var options = clientOptions.extend({}, helper.baseOptions);
      var metadata = new Metadata(options, cc);
      metadata.tokenizer = getTokenizer();
      var racksDc1 = new utils.HashSet();
      racksDc1.add('dc1_r1');
      racksDc1.add('dc1_r2');
      var racksDc2 = new utils.HashSet();
      racksDc2.add('dc2_r1');
      racksDc2.add('dc2_r2');
      metadata.datacenters = {
        'dc1': { hostLength: 4, racks: racksDc1 },
        'dc2': { hostLength: 4, racks: racksDc2 }};
      metadata.ring = [0, 1, 2, 3, 4, 5, 6, 7];
      //load primary replicas
      metadata.primaryReplicas = {};
      for (var i = 0; i < metadata.ring.length; i ++) {
        // Hosts with in alternate dc and alternate rack
        var h = new Host(i.toString(), 2, options);
        h.datacenter = 'dc' + ((i % 2) + 1);
        h.rack = h.datacenter + '_r' + ((i % 4) <= 1 ? 1 : 2);
        metadata.primaryReplicas[i.toString()] = h;
      }
      //reorganize racks in dc1 to set contiguous tokens in the same rack
      metadata.primaryReplicas['0'].rack = 'dc1_rack1';
      metadata.primaryReplicas['2'].rack = 'dc1_rack1';
      metadata.primaryReplicas['4'].rack = 'dc1_rack2';
      metadata.primaryReplicas['6'].rack = 'dc1_rack2';
      metadata.log = helper.noop;
      metadata.refreshKeyspaces(function () {
        var replicas = metadata.getReplicas('dummy', new Buffer([0]));
        assert.ok(replicas);
        // For DC1, it should skip the replica with the same rack (node2) and add it at the end: 0, 4, 2
        assert.deepEqual(replicas.map(getAddress), [ '0', '1', '3', '4', '2' ]);
        done();
      });
    });
    it('should return quickly with many replicas and 0 nodes in one DC.', function (done) {
      this.timeout(2000);
      var cc = getControlConnectionForRows([{
        'keyspace_name': 'dummy',
        'strategy_class': 'NetworkTopologyStrategy',
        'strategy_options': '{"dc1": "3", "dc2": "2"}'
      }]);
      var options = clientOptions.extend({}, helper.baseOptions);
      var metadata = new Metadata(options, cc);
      metadata.tokenizer = getTokenizer();
      var racksDc1 = new utils.HashSet();
      racksDc1.add('dc1_r1');
      var racksDc2 = new utils.HashSet();
      racksDc2.add('dc2_r1');
      metadata.datacenters = {
        'dc1': {hostLength: 100, racks: racksDc1},
        'dc2': {hostLength: 0, racks: racksDc2}
      };

      metadata.ring = [];
      // create ring with 100 replicas and 256 vnodes each.  place every replica in DC1.
      metadata.primaryReplicas = {};
      for (var r = 0; r < 100; r++) {
        var h = new Host(r.toString(), 2, options);
        h.datacenter = 'dc1';
        h.rack = 'dc1_r1';
        // 256 vnodes per replica.
        for (var v = 0; v < 256; v++) {
          var token = (v * 256) + r;
          metadata.ring.push(token);
          metadata.primaryReplicas[token.toString()] = h;
        }
      }
      metadata.ring.sort(function (a, b) {
        return a - b;
      });

      metadata.log = helper.noop;
      // Get the replicas of 5.  Since DC2 has 0 replicas, we only expect 3 replicas (the number of DC1).
      metadata.refreshKeyspaces(function () {
        var replicas = metadata.getReplicas('dummy', new Buffer([5]));
        assert.ok(replicas);
        assert.deepEqual(replicas.map(getAddress), ['5', '6', '7']);
        done();
      });
    });
    it('should return quickly with many replicas and not enough nodes in a DC to satisfy RF.', function (done) {
      this.timeout(2000);
      var cc = getControlConnectionForRows([{
        'keyspace_name': 'dummy',
        'strategy_class': 'NetworkTopologyStrategy',
        'strategy_options': '{"dc1": "3", "dc2": "2"}'
      }]);
      var options = clientOptions.extend({}, helper.baseOptions);
      var metadata = new Metadata(options, cc);
      metadata.tokenizer = getTokenizer();
      var racksDc1 = new utils.HashSet();
      racksDc1.add('dc1_r1');
      var racksDc2 = new utils.HashSet();
      racksDc2.add('dc2_r1');
      metadata.datacenters = {
        'dc1': {hostLength: 100, racks: racksDc1},
        'dc2': {hostLength: 1, racks: racksDc2}
      };

      metadata.ring = [];
      // create ring with 100 replicas and 256 vnodes each.  place every replica in DC1 except replica 0.
      metadata.primaryReplicas = {};
      for (var r = 0; r < 100; r++) {
        var h = new Host(r.toString(), 2, options);
        h.datacenter = 'dc1';
        h.rack = 'dc1_r1';
        // Place replica 0 in DC2.
        if (r === 0) {
          h.datacenter = 'dc2';
          h.rack = 'dc2_r1';
        }
        // 256 vnodes per replica.
        for (var v = 0; v < 256; v++) {
          var token = (v * 256) + r;
          metadata.ring.push(token);
          metadata.primaryReplicas[token.toString()] = h;
        }
      }
      // sort the ring so the tokens are in order (this is done in metadata buildTokens, but it accounts for
      // partitioner which we don't need to use here).
      metadata.ring.sort(function (a, b) {
        return a - b;
      });

      metadata.log = helper.noop;
      // Get the replicas of 0.  Since token 0 is a replica in DC2 and DC2 only has 1, it should return 1 replica
      // in addition to the next 3 replicas from DC1.
      metadata.refreshKeyspaces(function () {
        var replicas = metadata.getReplicas('dummy', new Buffer([0]));
        assert.ok(replicas);
        assert.deepEqual(replicas.map(getAddress), ['0', '1', '2', '3']);
        // Get the replicas for token 51752 which should resolve to primary replica 40 (202nd vnode, 212 * 256 + 40),
        // its next two subsequent replicas for DC1, and then 0 for DC2 since it only has that one replica.
        replicas = metadata.getReplicas('dummy', new Buffer([51752]));
        assert.ok(replicas);
        assert.deepEqual(replicas.map(getAddress), ['40', '41', '42', '0']);
        done();
      });
    });
  });
  describe('#clearPrepared()', function () {
    it('should clear the internal state', function () {
      var metadata = new Metadata(clientOptions.defaultOptions(), null);
      metadata.getPreparedInfo(null, 'QUERY1');
      metadata.getPreparedInfo(null, 'QUERY2');
      assert.strictEqual(metadata.preparedQueries['__length'], 2);
      metadata.clearPrepared();
      assert.strictEqual(metadata.preparedQueries['__length'], 0);
    });
  });
  describe('#getPreparedInfo()', function () {
    it('should create a new EventEmitter when the query has not been prepared', function () {
      var metadata = new Metadata(clientOptions.defaultOptions(), null);
      var info = metadata.getPreparedInfo(null, 'query1');
      helper.assertInstanceOf(info, events.EventEmitter);
      info = metadata.getPreparedInfo(null, 'query2');
      helper.assertInstanceOf(info, events.EventEmitter);
    });
    it('should get the same EventEmitter when the query is the same', function () {
      var metadata = new Metadata(clientOptions.defaultOptions(), null);
      var info1 = metadata.getPreparedInfo(null, 'query1');
      helper.assertInstanceOf(info1, events.EventEmitter);
      var info2 = metadata.getPreparedInfo(null, 'query1');
      helper.assertInstanceOf(info2, events.EventEmitter);
      assert.strictEqual(info1, info2);
    });
    it('should create a new EventEmitter when the query is the same but the keyspace is different', function () {
      var metadata = new Metadata(clientOptions.defaultOptions(), null);
      var info0 = metadata.getPreparedInfo(null, 'query1');
      var info1 = metadata.getPreparedInfo('ks1', 'query1');
      var info2 = metadata.getPreparedInfo('ks2', 'query1');
      assert.notStrictEqual(info0, info1);
      assert.notStrictEqual(info0, info2);
      assert.notStrictEqual(info1, info2);
    });
  });
  describe('#getUdt()', function () {
    it('should retrieve the udt information', function (done) {
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            cb(null, new types.ResultSet({
              rows: [ {
                field_names: ['field1', 'field2', 'field3'],
                field_types: ['org.apache.cassandra.db.marshal.UUIDType', 'org.apache.cassandra.db.marshal.UTF8Type',
                  'org.apache.cassandra.db.marshal.DynamicCompositeType('
                  + 's=>org.apache.cassandra.db.marshal.UTF8Type,'
                  + 'i=>org.apache.cassandra.db.marshal.Int32Type)']}],
              flags: utils.emptyObject
            }));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks1: { udts: {}}};
      metadata.getUdt('ks1', 'udt1', function (err, udtInfo) {
        assert.ifError(err);
        assert.ok(udtInfo);
        assert.strictEqual(udtInfo.name, 'udt1');
        assert.ok(udtInfo.fields);
        assert.strictEqual(udtInfo.fields.length, 3);
        done();
      });
    });
    it('should callback in err when there is an error', function (done) {
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            cb(new Error('Test error'));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks1: { udts: {}}};
      metadata.getUdt('ks1', 'udt1', function (err) {
        helper.assertInstanceOf(err, Error);
        done();
      });
    });
    it('should be null when it is not found', function (done) {
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            cb(null, new types.ResultSet({ rows: [], flags: utils.emptyObject}));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks1: { udts: {}}};
      metadata.getUdt('ks1', 'udt1', function (err, udtInfo) {
        assert.ifError(err);
        assert.strictEqual(udtInfo, null);
        done();
      });
    });
    it('should be null when keyspace does not exists', function (done) {
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            cb(null, new types.ResultSet({
              rows: [ {
                field_names: ['field1', 'field2'],
                field_types: ['org.apache.cassandra.db.marshal.UUIDType', 'org.apache.cassandra.db.marshal.UTF8Type']}],
              flags: utils.emptyObject
            }));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      //no keyspace named ks1 in metadata
      metadata.keyspaces = {};
      metadata.getUdt('ks1', 'udt1', function (err, udtInfo) {
        assert.ifError(err);
        assert.strictEqual(udtInfo, null);
        done();
      });
    });
    it('should query once when called in parallel', function (done) {
      var queried = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            queried++;
            cb(null, new types.ResultSet({
              rows: [ {
                field_names: ['field1', 'field2'],
                field_types: ['org.apache.cassandra.db.marshal.UUIDType', 'org.apache.cassandra.db.marshal.UTF8Type']}],
              flags: utils.emptyObject
            }));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      //no keyspace named ks1 in metadata
      metadata.keyspaces = { ks1: { udts: {}}};
      //Invoke multiple times in parallel
      utils.times(50, function (n, next) {
        metadata.getUdt('ks1', 'udt5', function (err, udtInfo) {
          if (err) {
            return next(err);
          }
          assert.ok(udtInfo);
          assert.ok(util.isArray(udtInfo.fields));
          next();
        });
      }, function (err) {
        assert.ifError(err);
        assert.strictEqual(queried, 1);
        done();
      });
    });
    it('should query once and cache when called serially', function (done) {
      var queried = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            queried++;
            cb(null, new types.ResultSet({
              rows: [ {
                field_names: ['field1', 'field2'],
                field_types: ['org.apache.cassandra.db.marshal.UUIDType', 'org.apache.cassandra.db.marshal.BooleanType']}],
              flags: utils.emptyObject
            }));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      //no keyspace named ks1 in metadata
      metadata.keyspaces = { ks1: { udts: {}}};
      //Invoke multiple times in parallel
      utils.timesSeries(50, function (n, next) {
        metadata.getUdt('ks1', 'udt10', function (err, udtInfo) {
          if (err) {return next(err);}
          assert.ok(udtInfo);
          assert.ok(util.isArray(udtInfo.fields));
          next();
        });
      }, function (err) {
        assert.ifError(err);
        assert.strictEqual(queried, 1);
        done();
      });
    });
    it('should query the following times if it was null', function (done) {
      var queried = 0;
      var cc = {
        query: function (q, cb) {
          queried++;
          setImmediate(function () {
            cb(null, new types.ResultSet({ rows: [], flags: utils.emptyObject}));
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks1: { udts: {}}};
      utils.timesSeries(20, function (n, next) {
        metadata.getUdt('ks1', 'udt20', function (err, udtInfo) {
          if (err) {return next(err);}
          assert.strictEqual(udtInfo, null);
          next();
        });
      }, function (err) {
        assert.ifError(err);
        assert.strictEqual(queried, 20);
        done();
      });
    });
  });
  describe('#getTrace()', function () {
    it('should return the trace if its already stored', function (done) {
      var sessionRow = {
        request: 'request value',
        coordinator: types.InetAddress.fromString('10.10.10.1'),
        parameters: ['a', 'b'],
        started_at: new Date(),
        duration: 2002
      };
      var eventRows = [ {
        event_id: types.TimeUuid.now(),
        activity: 'act 1',
        source: types.InetAddress.fromString('10.10.10.2'),
        source_elapsed: 101
      }];
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system_traces.sessions') >= 0) {
              return cb(null, { rows: [ sessionRow], flags: utils.emptyObject});
            }
            cb(null, { rows: eventRows});
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.getTrace(types.Uuid.random(), function (err, trace) {
        assert.ifError(err);
        assert.ok(trace);
        assert.strictEqual(trace.requestType, sessionRow.request);
        assert.strictEqual(trace.parameters, sessionRow.parameters);
        assert.strictEqual(trace.coordinator, sessionRow.coordinator);
        assert.strictEqual(trace.startedAt, sessionRow.started_at);
        assert.strictEqual(trace.events.length, 1);
        assert.strictEqual(trace.events[0].id, eventRows[0].event_id);
        assert.strictEqual(trace.events[0].activity, eventRows[0].activity);
        done();
      });
    });
    it('should parse the new client address column', function (done) {
      var sessionRow = {
        session_id: types.Uuid.fromString('69e37ff0-0475-11e5-9798-f3efee551757'),
        client: types.InetAddress.fromString('127.0.0.2'),
        command: 'QUERY',
        coordinator: types.InetAddress.fromString('127.0.0.2'),
        duration: 11922,
        parameters: {
          page_size: '5000',
          query: 'SELECT * FROM system.schema_keyspaces'
        },
        request: 'Execute CQL3 query',
        started_at: new Date()
      };
      var eventRows = [ {
        event_id: types.TimeUuid.now(),
        activity: 'act 1',
        source: types.InetAddress.fromString('10.10.10.2'),
        source_elapsed: 101
      }];
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system_traces.sessions') >= 0) {
              return cb(null, { rows: [ sessionRow], flags: utils.emptyObject});
            }
            cb(null, { rows: eventRows});
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.getTrace(types.Uuid.random(), function (err, trace) {
        assert.ifError(err);
        assert.ok(trace);
        assert.strictEqual(trace.requestType, sessionRow.request);
        assert.strictEqual(trace.parameters, sessionRow.parameters);
        assert.strictEqual(trace.coordinator, sessionRow.coordinator);
        assert.strictEqual(trace.startedAt, sessionRow.started_at);
        assert.strictEqual(trace.clientAddress, sessionRow.client);
        assert.strictEqual(trace.events.length, 1);
        assert.strictEqual(trace.events[0].id, eventRows[0].event_id);
        assert.strictEqual(trace.events[0].activity, eventRows[0].activity);
        done();
      });
    });
    it('should retry if its not already stored', function (done) {
      var sessionRow = {
        request: 'request value',
        coordinator: types.InetAddress.fromString('10.10.10.1'),
        parameters: ['a', 'b'],
        started_at: new Date(),
        duration: null
      };
      var eventRows = [ {
        event_id: types.TimeUuid.now(),
        activity: 'act 2',
        source: types.InetAddress.fromString('10.10.10.1'),
        source_elapsed: 102
      }];
      var calls = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system_traces.sessions') >= 0) {
              if (++calls > 1) {
                sessionRow.duration = 2002;
              }
              return cb(null, { rows: [ sessionRow]});
            }
            cb(null, { rows: eventRows});
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.getTrace(types.Uuid.random(), function (err, trace) {
        assert.ifError(err);
        assert.ok(trace);
        assert.strictEqual(calls, 2);
        assert.strictEqual(trace.requestType, sessionRow.request);
        assert.strictEqual(trace.parameters, sessionRow.parameters);
        assert.strictEqual(trace.coordinator, sessionRow.coordinator);
        assert.strictEqual(trace.startedAt, sessionRow.started_at);
        assert.strictEqual(trace.events.length, 1);
        assert.strictEqual(trace.events[0].id, eventRows[0].event_id);
        assert.strictEqual(trace.events[0].activity, eventRows[0].activity);
        done();
      });
    });
    it('should stop retrying after a few attempts', function (done) {
      var sessionRow = {
        request: 'request value',
        coordinator: types.InetAddress.fromString('10.10.10.1'),
        parameters: ['a', 'b'],
        started_at: new Date(),
        duration: null //duration null means that trace is not fully flushed
      };
      var calls = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            //try with empty result and null duration
            var rows = [];
            if (++calls > 1) {
              rows = [ sessionRow ];
            }
            cb(null, { rows: rows});
          });
        },
        getEncoder: function () { return new Encoder(1, {});}
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.getTrace(types.Uuid.random(), function (err, trace) {
        assert.ok(err);
        assert.ok(!trace);
        assert.ok(err.message);
        assert.ok(err.message.indexOf('attempt') > 0);
        done();
      });
    });
    it('should callback in error if there was an error retrieving the trace', function (done) {
      var err = new Error('dummy err');
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            cb(err);
          });
        }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.getTrace(types.Uuid.random(), function (receivedErr, trace) {
        assert.ok(err);
        assert.strictEqual(receivedErr, err);
        assert.ok(!trace);
        done();
      });
    });
  });
  describe('#getTable()', function () {
    it('should be null when it is not found', function (done) {
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system.schema_columnfamilies') >= 0) {
              return cb(null, {rows: []});
            }
            cb(null, {rows: []});
          });
        },
        getEncoder: function () { return new Encoder(1, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
      metadata.getTable('ks_tbl_meta', 'tbl_does_not_exists', function (err, table) {
        assert.ifError(err);
        assert.strictEqual(table, null);
        done();
      });
    });
    it('should be null when keyspace does not exists', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), {});
      metadata.keyspaces = { };
      metadata.getTable('ks_does_not_exists', 'tbl1', function (err, table) {
        assert.ifError(err);
        assert.strictEqual(table, null);
        done();
      });
    });
    it('should query once when called in parallel', function (done) {
      var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.02, caching: 'KEYS_ONLY',
        column_aliases: '["ck"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
        comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.TimeUUIDType,org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.LZ4Compressor"}', default_time_to_live: 0, default_validator: 'org.apache.cassandra.db.marshal.BytesType', dropped_columns: null, gc_grace_seconds: 864000, index_interval: 128, is_dense: false,
        key_aliases: '["pk1","apk2"]', key_validator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)', local_read_repair_chance: 0.1, max_compaction_threshold: 32, memtable_flush_period_in_ms: 0, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0, replicate_on_write: true, speculative_retry: '99.0PERCENTILE', subcomparator: null, type: 'Standard', value_alias: null };
      var columnRows = [
        { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'apk2', component_index: 1, index_name: null, index_options: null, index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UTF8Type' },
        { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'ck', component_index: 0, index_name: null, index_options: null, index_type: null, type: 'clustering_key', validator: 'org.apache.cassandra.db.marshal.TimeUUIDType' },
        { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'pk1', component_index: 0, index_name: null, index_options: null, index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UUIDType' }
      ];
      var calledTable = 0;
      var calledRows = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system.schema_columnfamilies') >= 0) {
              calledTable++;
              return cb(null, {rows: [tableRow]});
            }
            calledRows++;
            cb(null, {rows: columnRows});
          });
        },
        getEncoder: function () { return new Encoder(1, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
      utils.map(new Array(100), function (n, next) {
        metadata.getTable('ks_tbl_meta', 'tbl1', next);
      }, function (err, results) {
        assert.ifError(err);
        assert.strictEqual(calledTable, 1);
        assert.strictEqual(calledRows, 1);
        assert.strictEqual(results.length, 100);
        assert.strictEqual(results[0].name, 'tbl1');
        assert.strictEqual(results[0], results[1]);
        assert.strictEqual(results[0], results[99]);
        done();
      });
    });
    it('should query once if query the same table serially', function (done) {
      var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.02, caching: 'KEYS_ONLY',
        column_aliases: '["ck"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
        comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.TimeUUIDType,org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.LZ4Compressor"}', default_time_to_live: 0, default_validator: 'org.apache.cassandra.db.marshal.BytesType', dropped_columns: null, gc_grace_seconds: 864000, index_interval: 128, is_dense: false,
        key_aliases: '["pk1","apk2"]', key_validator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)', local_read_repair_chance: 0.1, max_compaction_threshold: 32, memtable_flush_period_in_ms: 0, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0, replicate_on_write: true, speculative_retry: '99.0PERCENTILE', subcomparator: null, type: 'Standard', value_alias: null };
      var columnRows = [
        { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'apk2', component_index: 1, index_name: null, index_options: null, index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UTF8Type' },
        { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'ck', component_index: 0, index_name: null, index_options: null, index_type: null, type: 'clustering_key', validator: 'org.apache.cassandra.db.marshal.TimeUUIDType' },
        { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'pk1', component_index: 0, index_name: null, index_options: null, index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UUIDType' }
      ];
      var calledTable = 0;
      var calledRows = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system.schema_columnfamilies') >= 0) {
              calledTable++;
              return cb(null, {rows: [tableRow]});
            }
            calledRows++;
            cb(null, {rows: columnRows});
          });
        },
        getEncoder: function () { return new Encoder(1, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
      utils.mapSeries(new Array(100), function (n, next) {
        metadata.getTable('ks_tbl_meta', 'tbl1', next);
      }, function (err, results) {
        assert.ifError(err);
        assert.strictEqual(calledTable, 1);
        assert.strictEqual(calledRows, 1);
        assert.strictEqual(results.length, 100);
        assert.strictEqual(results[0].name, 'tbl1');
        assert.strictEqual(results[0], results[1]);
        assert.strictEqual(results[0], results[99]);
        done();
      });
    });
    it('should query the following times if it was not found', function (done) {
      var calledTable = 0;
      var calledRows = 0;
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            if (q.indexOf('system.schema_columnfamilies') >= 0) {
              calledTable++;
              return cb(null, {rows: []});
            }
            calledRows++;
            cb(null, {rows: []});
          });
        },
        getEncoder: function () { return new Encoder(1, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
      utils.mapSeries(new Array(100), function (n, next) {
        metadata.getTable('ks_tbl_meta', 'tbl1', next);
      }, function (err, results) {
        assert.ifError(err);
        assert.strictEqual(calledTable, 100);
        assert.strictEqual(calledRows, 0);
        assert.strictEqual(results.length, 100);
        assert.strictEqual(results[0], null);
        assert.strictEqual(results[1], null);
        assert.strictEqual(results[99], null);
        done();
      });
    });
    it('should query each time if metadata retrieval flag is false', function (done) {
      var tableRow = {"keyspace_name":"ks_tbl_meta","table_name":"tbl1","bloom_filter_fp_chance":0.01,"caching":{"keys":"ALL","rows_per_partition":"NONE"},"comment":"","compaction":{"min_threshold":"4","max_threshold":"32","class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"},"compression":{"chunk_length_in_kb":"64","class":"org.apache.cassandra.io.compress.LZ4Compressor"},"dclocal_read_repair_chance":0.1,"default_time_to_live":0,"extensions":{},"flags":["compound"],"gc_grace_seconds":864000,"id":"7e0e8bf0-5862-11e5-84f8-c7d0c38d1d8d","max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99PERCENTILE"};
      var columnRows = [
        {"keyspace_name": "ks_tbl_meta", "table_name": "tbl1", "column_name": "id", "clustering_order": "none", "column_name_bytes": "0x6964", "kind": "partition_key", "position": -1, "type": "uuid"},
        {"keyspace_name": "ks_tbl_meta", "table_name": "tbl1", "column_name": "text_sample", "clustering_order": "none", "column_name_bytes": "0x746578745f73616d706c65", "kind": "regular", "position": -1, "type": "text"}
      ];
      var options = utils.extend({}, clientOptions.defaultOptions());
      options.isMetadataSyncEnabled = false;
      var cc = getControlConnectionForTable(tableRow, columnRows);
      var metadata = new Metadata(options, cc);
      metadata.keyspaces = { };
      metadata.setCassandraVersion([3, 0]);
      utils.mapSeries(new Array(100), function (n, next) {
        metadata.getTable('ks_tbl_meta', 'tbl1', next);
      }, function (err, results) {
        assert.ifError(err);
        assert.strictEqual(cc.queriedTable, 100);
        assert.strictEqual(cc.queriedRows, 100);
        assert.strictEqual(results.length, 100);
        helper.assertInstanceOf(results[0], TableMetadata);
        helper.assertInstanceOf(results[1], TableMetadata);
        helper.assertInstanceOf(results[99], TableMetadata);
        done();
      });
    });
    describe('with C*2.0 metadata rows', function () {
      it('should parse partition and clustering keys', function (done) {
        var customType ="org.apache.cassandra.db.marshal.DynamicCompositeType(s=>org.apache.cassandra.db.marshal.UTF8Type, i=>org.apache.cassandra.db.marshal.Int32Type)";
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.02, caching: 'KEYS_ONLY',
          column_aliases: '["ck"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
          comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.TimeUUIDType,org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.LZ4Compressor"}', default_time_to_live: 0, default_validator: 'org.apache.cassandra.db.marshal.BytesType', dropped_columns: null, gc_grace_seconds: 864000, index_interval: 128, is_dense: false,
          key_aliases: '["pk1","apk2"]', key_validator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)', local_read_repair_chance: 0.1, max_compaction_threshold: 32, memtable_flush_period_in_ms: 3000, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0, replicate_on_write: true, speculative_retry: '99.0PERCENTILE', subcomparator: null, type: 'Standard', value_alias: null };
        var columnRows = [
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'apk2', component_index: 1, index_name: null, index_options: null, index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UTF8Type' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'ck', component_index: 0, index_name: null, index_options: null, index_type: null, type: 'clustering_key', validator: 'org.apache.cassandra.db.marshal.TimeUUIDType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'pk1', component_index: 0, index_name: null, index_options: null, index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UUIDType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'val1', component_index: 1, index_name: null, index_options: null, index_type: null, type: 'regular', validator: 'org.apache.cassandra.db.marshal.Int32Type' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'val2', component_index: 1, index_name: null, index_options: null, index_type: null, type: 'regular', validator: 'org.apache.cassandra.db.marshal.BytesType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'valcus3', component_index: 1, index_name: null, index_options: null, index_type: null, type: 'regular', validator: customType }
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.bloomFilterFalsePositiveChance, 0.02);
          assert.strictEqual(table.isCompact, false);
          assert.ok(table.caching);
          assert.strictEqual(table.caching, 'KEYS_ONLY');
          assert.strictEqual(table.populateCacheOnFlush, false);
          assert.strictEqual(table.speculativeRetry, '99.0PERCENTILE');
          assert.strictEqual(table.indexInterval, 128);
          assert.strictEqual(table.memtableFlushPeriod, 3000);
          assert.strictEqual(table.minIndexInterval, null);
          assert.strictEqual(table.maxIndexInterval, null);
          assert.strictEqual(table.crcCheckChance, null);
          assert.strictEqual(table.extensions, null);
          assert.strictEqual(table.defaultTtl, 0);
          assert.strictEqual(table.columns.length, 6);
          assert.strictEqual(table.columns[0].name, 'apk2');
          assert.strictEqual(table.columns[0].type.code, types.dataTypes.varchar);
          assert.strictEqual(table.columns[1].name, 'ck');
          assert.strictEqual(table.columns[1].type.code, types.dataTypes.timeuuid);
          assert.strictEqual(table.columns[2].name, 'pk1');
          assert.strictEqual(table.columns[2].type.code, types.dataTypes.uuid);
          assert.strictEqual(table.columns[3].name, 'val1');
          assert.strictEqual(table.columns[3].type.code, types.dataTypes.int);
          assert.strictEqual(table.columns[4].name, 'val2');
          assert.strictEqual(table.columns[4].type.code, types.dataTypes.blob);
          assert.strictEqual(table.columns[5].name, 'valcus3');
          assert.strictEqual(table.columns[5].type.code, types.dataTypes.custom);
          assert.strictEqual(table.columns[5].type.info, customType);
          assert.strictEqual(table.partitionKeys.length, 2);
          assert.strictEqual(table.partitionKeys[0].name, 'pk1');
          assert.strictEqual(table.partitionKeys[1].name, 'apk2');
          assert.strictEqual(table.clusteringKeys.length, 1);
          assert.strictEqual(table.clusteringKeys[0].name, 'ck');
          assert.strictEqual(table.clusteringOrder.length, 1);
          assert.strictEqual(table.clusteringOrder[0], 'ASC');
          done();
        });
      });
      it('should parse table with compact storage with clustering key', function (done) {
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.01, caching: '{"keys":"ALL", "rows_per_partition":"NONE"}', cf_id: types.Uuid.fromString('96c86920-ed84-11e4-8991-199e66562428'),
          column_aliases: '["id2"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
          comparator: 'org.apache.cassandra.db.marshal.TimeUUIDType', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.LZ4Compressor"}', default_time_to_live: 0, default_validator: 'org.apache.cassandra.db.marshal.UTF8Type', dropped_columns: null, gc_grace_seconds: 864000, index_interval: null, is_dense: true, key_aliases: '["id1"]', key_validator: 'org.apache.cassandra.db.marshal.UUIDType', local_read_repair_chance: 0.1, max_compaction_threshold: 32, max_index_interval: 2048, memtable_flush_period_in_ms: 0, min_compaction_threshold: 4, min_index_interval: 128, read_repair_chance: 0, speculative_retry: '99.0PERCENTILE', subcomparator: null, type: 'Standard', value_alias: 'text1' };
        var columnRows = [
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'id1', component_index: null, index_name: null, index_options: 'null', index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UUIDType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'id2', component_index: null, index_name: null, index_options: 'null', index_type: null, type: 'clustering_key', validator: 'org.apache.cassandra.db.marshal.TimeUUIDType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'text1', component_index: null, index_name: null, index_options: 'null', index_type: null, type: 'compact_value', validator: 'org.apache.cassandra.db.marshal.UTF8Type' }
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.ok(table.caching);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.columns[0].name, 'id1');
          assert.strictEqual(table.columns[1].name, 'id2');
          assert.strictEqual(table.columns[2].name, 'text1');
          assert.strictEqual(table.clusteringKeys.length, 1);
          assert.strictEqual(table.clusteringKeys[0].name, 'id2');
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id1');
          done();
        });
      });
      it('should parse table with compact storage', function (done) {
        var tableRow = {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","bloom_filter_fp_chance":0.01,"caching":"{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}","cf_id":"609f53a0-038b-11e5-be48-0d419bfb85c8","column_aliases":"[]","comment":"","compaction_strategy_class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy","compaction_strategy_options":"{}","comparator":"org.apache.cassandra.db.marshal.UTF8Type","compression_parameters":"{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}","default_time_to_live":0,"default_validator":"org.apache.cassandra.db.marshal.BytesType","dropped_columns":null,"gc_grace_seconds":864000,"index_interval":null,"is_dense":false,"key_aliases":"[\"id\"]","key_validator":"org.apache.cassandra.db.marshal.UUIDType","local_read_repair_chance":0.1,"max_compaction_threshold":32,"max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_compaction_threshold":4,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99.0PERCENTILE","subcomparator":null,"type":"Standard","value_alias":null};
        var columnRows = [
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"id","component_index":null,"index_name":null,"index_options":"null","index_type":null,"type":"partition_key","validator":"org.apache.cassandra.db.marshal.UUIDType"},
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"text1","component_index":null,"index_name":null,"index_options":"null","index_type":null,"type":"regular","validator":"org.apache.cassandra.db.marshal.UTF8Type"},
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"text2","component_index":null,"index_name":null,"index_options":"null","index_type":null,"type":"regular","validator":"org.apache.cassandra.db.marshal.UTF8Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.ok(table.caching);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.columns[0].name, 'id');
          assert.strictEqual(table.columns[1].name, 'text1');
          assert.strictEqual(table.columns[2].name, 'text2');
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id');
          assert.strictEqual(table.clusteringKeys.length, 0);
          done();
        });
      });
      it('should parse custom index (legacy)', function (done) {
        var tableRow = {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","bloom_filter_fp_chance":0.01,"caching":"{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}","cf_id":"609f53a0-038b-11e5-be48-0d419bfb85c8","column_aliases":"[]","comment":"","compaction_strategy_class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy","compaction_strategy_options":"{}","comparator":"org.apache.cassandra.db.marshal.UTF8Type","compression_parameters":"{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}","default_time_to_live":0,"default_validator":"org.apache.cassandra.db.marshal.BytesType","dropped_columns":null,"gc_grace_seconds":864000,"index_interval":null,"is_dense":false,"key_aliases":"[\"id\"]","key_validator":"org.apache.cassandra.db.marshal.UUIDType","local_read_repair_chance":0.1,"max_compaction_threshold":32,"max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_compaction_threshold":4,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99.0PERCENTILE","subcomparator":null,"type":"Standard","value_alias":null};
        var columnRows = [
            {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"id","component_index":null,"index_name":null,"index_options":"null","index_type":null,"type":"partition_key","validator":"org.apache.cassandra.db.marshal.UUIDType"},
            {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"text1","component_index":null,"index_name":"custom_index","index_options":'{"foo":"bar", "class_name":"dummy.DummyIndex"}',"index_type":"CUSTOM","type":"regular","validator":"org.apache.cassandra.db.marshal.UTF8Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.ok(table.indexes);
          assert.ok(table.indexes.length === 1);
          var index = table.indexes[0];
          assert.ok(index, 'Index not found');
          assert.strictEqual(index.name, 'custom_index');
          assert.strictEqual(index.target, 'text1');
          assert.strictEqual(index.isCompositesKind(), false);
          assert.strictEqual(index.isCustomKind(), true);
          assert.strictEqual(index.isKeysKind(), false);
          assert.ok(index.options);
          assert.strictEqual(index.options['foo'], 'bar');
          assert.strictEqual(index.options['class_name'], 'dummy.DummyIndex');
          done();
        });
      });
      it('should parse keys index (legacy)', function (done) {
        var tableRow = {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","bloom_filter_fp_chance":0.01,"caching":"{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}","cf_id":"609f53a0-038b-11e5-be48-0d419bfb85c8","column_aliases":"[]","comment":"","compaction_strategy_class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy","compaction_strategy_options":"{}","comparator":"org.apache.cassandra.db.marshal.UTF8Type","compression_parameters":"{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}","default_time_to_live":0,"default_validator":"org.apache.cassandra.db.marshal.BytesType","dropped_columns":null,"gc_grace_seconds":864000,"index_interval":null,"is_dense":false,"key_aliases":"[\"id\"]","key_validator":"org.apache.cassandra.db.marshal.UUIDType","local_read_repair_chance":0.1,"max_compaction_threshold":32,"max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_compaction_threshold":4,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99.0PERCENTILE","subcomparator":null,"type":"Standard","value_alias":null};
        var columnRows = [
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"id","component_index":null,"index_name":null,"index_options":"null","index_type":null,"type":"partition_key","validator":"org.apache.cassandra.db.marshal.UUIDType"},
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"text1","component_index":null,"index_name":"custom_index","index_options":'{"index_keys": ""}',"index_type":"KEYS","type":"regular","validator":"org.apache.cassandra.db.marshal.UTF8Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'b@706172656e745f70617468', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.ok(table.indexes);
          assert.ok(table.indexes.length === 1);
          var index = table.indexes[0];
          assert.ok(index, 'Index not found');
          assert.strictEqual(index.name, 'custom_index');
          // target should be column name since we weren't able to parse index_keys from index_options.
          assert.strictEqual(index.target, 'keys(text1)');
          assert.strictEqual(index.isCompositesKind(), false);
          assert.strictEqual(index.isCustomKind(), false);
          assert.strictEqual(index.isKeysKind(), true);
          assert.ok(index.options);
          done();
        });
      });
      it('should parse keys index with null string index options', function (done) {
        // Validates a special case where a 'KEYS' index was created using thrift.  In this particular case the index
        // lacks index_options, however the index_options value is a 'null' string rather than a null value.
        // DSE Analytics at some point did this with its cfs tables.

        var tableRow = {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","bloom_filter_fp_chance":0.01,"caching":"{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}","cf_id":"609f53a0-038b-11e5-be48-0d419bfb85c8","column_aliases":"[]","comment":"","compaction_strategy_class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy","compaction_strategy_options":"{}","comparator":"org.apache.cassandra.db.marshal.UTF8Type","compression_parameters":"{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}","default_time_to_live":0,"default_validator":"org.apache.cassandra.db.marshal.BytesType","dropped_columns":null,"gc_grace_seconds":864000,"index_interval":null,"is_dense":false,"key_aliases":"[\"id\"]","key_validator":"org.apache.cassandra.db.marshal.UUIDType","local_read_repair_chance":0.1,"max_compaction_threshold":32,"max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_compaction_threshold":4,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99.0PERCENTILE","subcomparator":null,"type":"Standard","value_alias":null};
        var columnRows = [
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"id","component_index":null,"index_name":null,"index_options":"null","index_type":null,"type":"partition_key","validator":"org.apache.cassandra.db.marshal.UUIDType"},
          {"keyspace_name":"ks_tbl_meta","columnfamily_name":"tbl1","column_name":"b@706172656e745f70617468","component_index":null,"index_name":"cfs_archive_parent_path","index_options":"\"null\"","index_type":"KEYS","type":"regular","validator":"org.apache.cassandra.db.marshal.UTF8Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'b@706172656e745f70617468', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.ok(table.indexes);
          assert.ok(table.indexes.length === 1);
          var index = table.indexes[0];
          assert.ok(index, 'Index not found');
          assert.strictEqual(index.name, 'cfs_archive_parent_path');
          // target should be column name since we weren't able to parse index_keys from index_options.
          assert.strictEqual(index.target, 'b@706172656e745f70617468');
          assert.strictEqual(index.isCompositesKind(), false);
          assert.strictEqual(index.isCustomKind(), false);
          assert.strictEqual(index.isKeysKind(), true);
          assert.ok(index.options);
          done();
        });
      });
    });
    describe('with C*1.2 metadata rows', function () {
      it('should parse partition and clustering keys', function (done) {
        var customType ="org.apache.cassandra.db.marshal.DynamicCompositeType(s=>org.apache.cassandra.db.marshal.UTF8Type, i=>org.apache.cassandra.db.marshal.Int32Type)";
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.01, caching: 'KEYS_ONLY',
          column_aliases: '["zck"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
          comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.TimeUUIDType,org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.SnappyCompressor"}', default_validator: 'org.apache.cassandra.db.marshal.BytesType', gc_grace_seconds: 864000, id: null, key_alias: null,
          key_aliases: '["pk1","apk2"]', key_validator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)', local_read_repair_chance: 0, max_compaction_threshold: 32, min_compaction_threshold: 4, populate_io_cache_on_flush: true, read_repair_chance: 0.1, replicate_on_write: true, subcomparator: null, type: 'Standard', value_alias: null };
        var columnRows = [
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'val2', component_index: 1, index_name: null, index_options: null, index_type: null, validator: 'org.apache.cassandra.db.marshal.BytesType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'valz1', component_index: 1, index_name: null, index_options: null, index_type: null, validator: 'org.apache.cassandra.db.marshal.Int32Type' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'valcus3', component_index: 1, index_name: null, index_options: null, index_type: null, validator: customType }
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, false);
          assert.strictEqual(table.columns.length, 6);
          assert.strictEqual(table.partitionKeys.length, 2);
          assert.strictEqual(table.partitionKeys[0].name, 'pk1');
          assert.strictEqual(table.partitionKeys[1].name, 'apk2');
          assert.strictEqual(table.clusteringKeys.length, 1);
          assert.strictEqual(table.clusteringKeys[0].name, 'zck');
          assert.strictEqual(table.columnsByName['valcus3'].type.code, dataTypes.custom);
          assert.strictEqual(table.columnsByName['valcus3'].type.info, customType);
          assert.strictEqual(table.populateCacheOnFlush, true);
          //default as it does not exist for C* 1.2
          assert.strictEqual(table.speculativeRetry, 'NONE');
          assert.strictEqual(table.indexInterval, null);
          assert.strictEqual(table.memtableFlushPeriod, 0);
          assert.strictEqual(table.minIndexInterval, null);
          assert.strictEqual(table.maxIndexInterval, null);
          assert.strictEqual(table.defaultTtl, 0);
          assert.strictEqual(table.crcCheckChance, null);
          assert.strictEqual(table.extensions, null);
          done();
        });
      });
      it('should parse with no clustering keys', function (done) {
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.01, caching: 'KEYS_ONLY',
          column_aliases: '[]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}', comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.SnappyCompressor"}', default_validator: 'org.apache.cassandra.db.marshal.BytesType', gc_grace_seconds: 864000, id: null, key_alias: null,
          key_aliases: '["id"]', key_validator: 'org.apache.cassandra.db.marshal.UUIDType', local_read_repair_chance: 0, max_compaction_threshold: 32, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0.1, replicate_on_write: true, subcomparator: null, type: 'Standard', value_alias: null };
        var columnRows = [ { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'text_sample', component_index: 0, index_name: null, index_options: null, index_type: null, validator: 'org.apache.cassandra.db.marshal.UTF8Type' } ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.columns.length, 2);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id');
          assert.strictEqual(table.partitionKeys[0].type.code, types.dataTypes.uuid);
          assert.strictEqual(table.clusteringKeys.length, 0);
          done();
        });
      });
      it('should parse with compact storage', function (done) {
        //1 pk, 1 ck and 1 val
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl5', bloom_filter_fp_chance: 0.01, caching: 'KEYS_ONLY',
          column_aliases: '["id2"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
          comparator: 'org.apache.cassandra.db.marshal.TimeUUIDType', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.SnappyCompressor"}',
          default_validator: 'org.apache.cassandra.db.marshal.UTF8Type', gc_grace_seconds: 864000, id: null, key_alias: null, key_aliases: '["id1"]', key_validator: 'org.apache.cassandra.db.marshal.UUIDType', local_read_repair_chance: 0, max_compaction_threshold: 32, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0.1, replicate_on_write: true, subcomparator: null, type: 'Standard',
          value_alias: 'text1' };
        var columnRows = [];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id1');
          assert.strictEqual(table.clusteringKeys.length, 1);
          assert.strictEqual(table.clusteringKeys[0].name, 'id2');
          done();
        });
      });
      it('should parse with compact storage with pk', function (done) {
        //1 pk, 2 val
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.01, caching: 'KEYS_ONLY',
          column_aliases: '[]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
          comparator: 'org.apache.cassandra.db.marshal.UTF8Type', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.SnappyCompressor"}', default_validator: 'org.apache.cassandra.db.marshal.BytesType', gc_grace_seconds: 864000, id: null, key_alias: null, key_aliases: '["id"]', key_validator: 'org.apache.cassandra.db.marshal.UUIDType', local_read_repair_chance: 0, max_compaction_threshold: 32, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0.1, replicate_on_write: true, subcomparator: null, type: 'Standard', value_alias: null };
        var columnRows = [
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'text1', component_index: null, index_name: null, index_options: null, index_type: null, validator: 'org.apache.cassandra.db.marshal.UTF8Type' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', column_name: 'text2', component_index: null, index_name: null, index_options: null, index_type: null, validator: 'org.apache.cassandra.db.marshal.UTF8Type' }
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.clusteringKeys.length, 0);
          done();
        });
      });
      it('should parse with compact storage and all columns as clustering keys', function (done) {
        //1 pk, 2 ck
        var tableRow = { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl1', bloom_filter_fp_chance: 0.01, caching: 'KEYS_ONLY',
          column_aliases: '["id2","text1"]', comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}',
          comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.TimeUUIDType,org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.SnappyCompressor"}', default_validator: 'org.apache.cassandra.db.marshal.BytesType', gc_grace_seconds: 864000, id: null, key_alias: null, key_aliases: '["id1"]', key_validator: 'org.apache.cassandra.db.marshal.UUIDType', local_read_repair_chance: 0, max_compaction_threshold: 32, min_compaction_threshold: 4, populate_io_cache_on_flush: false, read_repair_chance: 0.1, replicate_on_write: true, subcomparator: null, type: 'Standard', value_alias: '' };
        var columnRows = [];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.clusteringKeys.length, 2);
          assert.strictEqual(table.clusteringKeys[0].name, 'id2');
          assert.strictEqual(table.clusteringKeys[1].name, 'text1');
          done();
        });
      });
    });
    describe('with C*2.2 metadata rows', function () {
      it('should parse new 2.2 types', function (done) {
        var customType ="org.apache.cassandra.db.marshal.DynamicCompositeType(s=>org.apache.cassandra.db.marshal.UTF8Type, i=>org.apache.cassandra.db.marshal.Int32Type)";
        var tableRow = {
          keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', bloom_filter_fp_chance: 0.03, caching: '{"keys":"ALL", "rows_per_partition":"NONE"}',
          cf_id: types.Uuid.fromString('c05f4c40-fe05-11e4-8481-277ff03b5030'), comment: '', compaction_strategy_class: 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', compaction_strategy_options: '{}', comparator: 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type)', compression_parameters: '{"sstable_compression":"org.apache.cassandra.io.compress.LZ4Compressor"}', default_time_to_live: 0, default_validator: 'org.apache.cassandra.db.marshal.BytesType', dropped_columns: null, gc_grace_seconds: 864000, is_dense: false, key_validator: 'org.apache.cassandra.db.marshal.UUIDType', local_read_repair_chance: 0.1, max_compaction_threshold: 32, max_index_interval: 2048, memtable_flush_period_in_ms: 0, min_compaction_threshold: 4, min_index_interval: 128, read_repair_chance: 0, speculative_retry: '99.0PERCENTILE', subcomparator: null, type: 'Standard' };
        var columnRows = [
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', column_name: 'date_sample', component_index: 0, index_name: null, index_options: 'null', index_type: null, type: 'regular', validator: 'org.apache.cassandra.db.marshal.SimpleDateType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', column_name: 'id', component_index: null, index_name: null, index_options: 'null', index_type: null, type: 'partition_key', validator: 'org.apache.cassandra.db.marshal.UUIDType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', column_name: 'smallint_sample', component_index: 0, index_name: null, index_options: 'null', index_type: null, type: 'regular', validator: 'org.apache.cassandra.db.marshal.ShortType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', column_name: 'time_sample', component_index: 0, index_name: null, index_options: 'null', index_type: null, type: 'regular', validator: 'org.apache.cassandra.db.marshal.TimeType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', column_name: 'tinyint_sample', component_index: 0, index_name: null, index_options: 'null', index_type: null, type: 'regular', validator: 'org.apache.cassandra.db.marshal.ByteType' },
          { keyspace_name: 'ks_tbl_meta', columnfamily_name: 'tbl_c22', column_name: 'custom_sample', component_index: 0, index_name: null, index_options: 'null', index_type: null, type: 'regular', validator: customType }
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl_c22', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.bloomFilterFalsePositiveChance, 0.03);
          assert.strictEqual(table.isCompact, false);
          assert.ok(table.caching);
          assert.strictEqual(table.columns.length, 6);
          assert.strictEqual(table.columns[0].name, 'date_sample');
          assert.strictEqual(table.columns[0].type.code, types.dataTypes.date);
          assert.strictEqual(table.columns[1].name, 'id');
          assert.strictEqual(table.columns[1].type.code, types.dataTypes.uuid);
          assert.strictEqual(table.columns[2].name, 'smallint_sample');
          assert.strictEqual(table.columns[2].type.code, types.dataTypes.smallint);
          assert.strictEqual(table.columns[3].name, 'time_sample');
          assert.strictEqual(table.columns[3].type.code, types.dataTypes.time);
          assert.strictEqual(table.columns[4].name, 'tinyint_sample');
          assert.strictEqual(table.columns[4].type.code, types.dataTypes.tinyint);
          assert.strictEqual(table.columns[5].name, 'custom_sample');
          assert.strictEqual(table.columns[5].type.code, types.dataTypes.custom);
          assert.strictEqual(table.columns[5].type.info, customType);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id');
          assert.strictEqual(table.clusteringKeys.length, 0);
          done();
        });
      });
    });
    describe('with C*3.0+ metadata rows', function () {
      it('should parse partition and clustering keys', function (done) {
        var tableRow = {
          "keyspace_name":"ks_tbl_meta",
          "table_name":"tbl4",
          "bloom_filter_fp_chance":0.01,
          "caching":{"keys":"ALL","rows_per_partition":"NONE"},
          "comment":"",
          "compaction":{"min_threshold":"4","max_threshold":"32","class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"},
          "compression":{"chunk_length_in_kb":"64","class":"org.apache.cassandra.io.compress.LZ4Compressor"},
          "dclocal_read_repair_chance":0.1,
          "default_time_to_live":0,
          "extensions":{'hello': new Buffer('world')},
          "flags":["compound"],
          "gc_grace_seconds":864000,
          "id":"8008ae40-5862-11e5-b0ce-c7d0c38d1d8d",
          "max_index_interval":1024,
          "crc_check_chance": 0.8,
          "memtable_flush_period_in_ms":0,"min_index_interval":64,"read_repair_chance":0,"speculative_retry":"99PERCENTILE"};
        var customType ="org.apache.cassandra.db.marshal.DynamicCompositeType(s=>org.apache.cassandra.db.marshal.UTF8Type, i=>org.apache.cassandra.db.marshal.Int32Type)";
        var columnRows = [
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl4", "column_name": "apk2", "clustering_order": "none", "column_name_bytes": "0x61706b32", "kind": "partition_key", "position": 1, "type": "text"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl4", "column_name": "pk1", "clustering_order": "none", "column_name_bytes": "0x706b31", "kind": "partition_key", "position": 0, "type": "uuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl4", "column_name": "val2", "clustering_order": "none", "column_name_bytes": "0x76616c32", "kind": "regular", "position": -1, "type": "blob"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl4", "column_name": "valz1", "clustering_order": "none", "column_name_bytes": "0x76616c7a31", "kind": "regular", "position": -1, "type": "int"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl4", "column_name": "valcus3", "clustering_order": "none", "column_name_bytes": "0x76611663757333", "kind": "regular", "position": -1, "type": "'" + customType + "'"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl4", "column_name": "zck", "clustering_order": "asc", "column_name_bytes": "0x7a636b", "kind": "clustering", "position": 0, "type": "timeuuid"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl4', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, false);
          assert.strictEqual(table.speculativeRetry, '99PERCENTILE');
          assert.strictEqual(table.indexInterval, null);
          assert.strictEqual(table.memtableFlushPeriod, 0);
          assert.strictEqual(table.minIndexInterval, 64);
          assert.strictEqual(table.maxIndexInterval, 1024);
          assert.strictEqual(table.crcCheckChance, 0.8);
          assert.ok(table.extensions);
          assert.strictEqual(table.extensions['hello'].toString('utf8'), 'world');
          //not present, default
          assert.strictEqual(table.populateCacheOnFlush, false);
          assert.strictEqual(table.columns.length, 6);
          assert.deepEqual(table.columns.map(function (x) { return x.type.code; }),
            [dataTypes.text, dataTypes.uuid, dataTypes.blob, dataTypes.int, dataTypes.custom, dataTypes.timeuuid]);
          assert.strictEqual(table.partitionKeys.length, 2);
          assert.strictEqual(table.partitionKeys[0].name, 'pk1');
          assert.strictEqual(table.partitionKeys[1].name, 'apk2');
          assert.strictEqual(table.clusteringKeys.length, 1);
          assert.strictEqual(table.clusteringKeys[0].name, 'zck');
          assert.strictEqual(table.columnsByName['valcus3'].type.info, customType);
          done();
        });
      });
      it('should parse with no clustering keys', function (done) {
        var tableRow = {"keyspace_name":"ks_tbl_meta","table_name":"tbl1","bloom_filter_fp_chance":0.01,"caching":{"keys":"ALL","rows_per_partition":"NONE"},"comment":"","compaction":{"min_threshold":"4","max_threshold":"32","class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"},"compression":{"chunk_length_in_kb":"64","class":"org.apache.cassandra.io.compress.LZ4Compressor"},"dclocal_read_repair_chance":0.1,"default_time_to_live":0,"extensions":{},"flags":["compound"],"gc_grace_seconds":864000,"id":"7e0e8bf0-5862-11e5-84f8-c7d0c38d1d8d","max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99PERCENTILE"};
        var columnRows = [
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl1", "column_name": "id", "clustering_order": "none", "column_name_bytes": "0x6964", "kind": "partition_key", "position": -1, "type": "uuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl1", "column_name": "text_sample", "clustering_order": "none", "column_name_bytes": "0x746578745f73616d706c65", "kind": "regular", "position": -1, "type": "text"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.columns.length, 2);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id');
          assert.strictEqual(table.partitionKeys[0].type.code, types.dataTypes.uuid);
          assert.strictEqual(table.clusteringKeys.length, 0);
          done();
        });
      });
      it('should parse with compact storage', function (done) {
        //1 pk, 1 ck and 1 val
        var tableRow = {
          "keyspace_name":"ks_tbl_meta","table_name":"tbl5","bloom_filter_fp_chance":0.01,"caching":{"keys":"ALL","rows_per_partition":"NONE"},"comment":"","compaction":{"min_threshold":"4","max_threshold":"32","class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"},"compression":{"chunk_length_in_kb":"64","class":"org.apache.cassandra.io.compress.LZ4Compressor"},"dclocal_read_repair_chance":0.1,"default_time_to_live":0,"extensions":{},
          "flags":["dense"],"gc_grace_seconds":864000,"id":"80fd9590-5862-11e5-84f8-c7d0c38d1d8d","max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99PERCENTILE"};
        var columnRows = [
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl5", "column_name": "id1", "clustering_order": "none", "column_name_bytes": "0x696431", "kind": "partition_key", "position": -1, "type": "uuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl5", "column_name": "id2", "clustering_order": "asc", "column_name_bytes": "0x696432", "kind": "clustering", "position": 0, "type": "timeuuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl5", "column_name": "text1", "clustering_order": "none", "column_name_bytes": "0x7465787431", "kind": "regular", "position": -1, "type": "text"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl5', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.partitionKeys[0].name, 'id1');
          assert.strictEqual(table.clusteringKeys.length, 1);
          assert.strictEqual(table.clusteringKeys[0].name, 'id2');
          done();
        });
      });
      it('should parse with custom index', function (done) {
        //1 pk, 1 ck and 1 val
        var tableRow = {
          "keyspace_name":"ks_tbl_meta","table_name":"tbl5","bloom_filter_fp_chance":0.01,"caching":{"keys":"ALL","rows_per_partition":"NONE"},"comment":"","compaction":{"min_threshold":"4","max_threshold":"32","class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"},"compression":{"chunk_length_in_kb":"64","class":"org.apache.cassandra.io.compress.LZ4Compressor"},"dclocal_read_repair_chance":0.1,"default_time_to_live":0,"extensions":{},
          "flags":["dense"],"gc_grace_seconds":864000,"id":"80fd9590-5862-11e5-84f8-c7d0c38d1d8d","max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99PERCENTILE"};
        var columnRows = [
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl5", "column_name": "id1", "clustering_order": "none", "column_name_bytes": "0x696431", "kind": "partition_key", "position": -1, "type": "uuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl5", "column_name": "id2", "clustering_order": "asc", "column_name_bytes": "0x696432", "kind": "clustering", "position": 0, "type": "timeuuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl5", "column_name": "text1", "clustering_order": "none", "column_name_bytes": "0x7465787431", "kind": "regular", "position": -1, "type": "text"}
        ];
        var indexRows = [
          {"index_name": "custom_index", "kind": "CUSTOM", "options": {"foo":"bar","class_name":"dummy.DummyIndex","target":"a, b, keys(c)"}}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows,indexRows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl5', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.ok(table.indexes);
          assert.ok(table.indexes.length === 1);
          var index = table.indexes[0];
          assert.ok(index, 'Index not found');
          assert.strictEqual(index.name, 'custom_index');
          assert.strictEqual(index.target, 'a, b, keys(c)');
          assert.strictEqual(index.isCompositesKind(), false);
          assert.strictEqual(index.isCustomKind(), true);
          assert.strictEqual(index.isKeysKind(), false);
          assert.ok(index.options);
          assert.strictEqual(index.options['foo'], 'bar');
          assert.strictEqual(index.options['class_name'], 'dummy.DummyIndex');
          done();
        });
      });
      it('should parse with compact storage with pk', function (done) {
        //1 pk, 2 val
        var tableRow = {
          "keyspace_name":"ks_tbl_meta","table_name":"tbl6","bloom_filter_fp_chance":0.01,"caching":{"keys":"ALL","rows_per_partition":"NONE"},"comment":"","compaction":{"min_threshold":"4","max_threshold":"32","class":"org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"},"compression":{"chunk_length_in_kb":"64","class":"org.apache.cassandra.io.compress.LZ4Compressor"},"dclocal_read_repair_chance":0.1,"default_time_to_live":0,"extensions":{},
          "flags":[],"gc_grace_seconds":864000,"id":"81a32460-5862-11e5-b0ce-c7d0c38d1d8d","max_index_interval":2048,"memtable_flush_period_in_ms":0,"min_index_interval":128,"read_repair_chance":0,"speculative_retry":"99PERCENTILE"};
        var columnRows = [
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl6", "column_name": "column1", "clustering_order": "asc", "column_name_bytes": "0x636f6c756d6e31", "kind": "clustering", "position": 0, "type": "text"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl6", "column_name": "id", "clustering_order": "none", "column_name_bytes": "0x6964", "kind": "partition_key", "position": -1, "type": "uuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl6", "column_name": "text1", "clustering_order": "none", "column_name_bytes": "0x7465787431", "kind": "static", "position": -1, "type": "text"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl6", "column_name": "text2", "clustering_order": "none", "column_name_bytes": "0x7465787432", "kind": "static", "position": -1, "type": "text"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl6", "column_name": "value", "clustering_order": "none", "column_name_bytes": "0x76616c7565", "kind": "regular", "position": -1, "type": "blob"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl6', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(Object.keys(table.columnsByName).length, 3);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.clusteringKeys.length, 0);
          done();
        });
      });
      it('should parse with compact storage and all columns as clustering keys', function (done) {
        //1 pk, 2 ck. similar to tbl5 but with id2 and text1 as ck
        var tableRow = {
          "keyspace_name": "ks1", "table_name": "tbl10", "bloom_filter_fp_chance": 0.01, "caching": {"keys": "ALL", "rows_per_partition": "NONE"}, "comment": "", "compaction": {"min_threshold": "4", "max_threshold": "32", "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy"}, "compression": {"chunk_length_in_kb": "64", "class": "org.apache.cassandra.io.compress.LZ4Compressor"}, "dclocal_read_repair_chance": 0.1, "default_time_to_live": 0, "extensions": {},
          "flags": ["compound", "dense"], "gc_grace_seconds": 864000, "id": "b4d56ea0-5881-11e5-8326-c7d0c38d1d8d", "max_index_interval": 2048, "memtable_flush_period_in_ms": 0, "min_index_interval": 128, "read_repair_chance": 0.0, "speculative_retry": "99PERCENTILE"};
        var columnRows = [
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl10", "column_name": "id1", "clustering_order": "none", "column_name_bytes": "0x696431", "kind": "partition_key", "position": -1, "type": "uuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl10", "column_name": "id2", "clustering_order": "asc", "column_name_bytes": "0x696432", "kind": "clustering", "position": 0, "type": "timeuuid"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl10", "column_name": "text1", "clustering_order": "asc", "column_name_bytes": "0x7465787431", "kind": "clustering", "position": 1, "type": "text"},
          {"keyspace_name": "ks_tbl_meta", "table_name": "tbl10", "column_name": "value", "clustering_order": "none", "column_name_bytes": "0x76616c7565", "kind": "regular", "position": -1, "type": "empty"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForTable(tableRow, columnRows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces = { ks_tbl_meta: { tables: {}}};
        metadata.getTable('ks_tbl_meta', 'tbl1', function (err, table) {
          assert.ifError(err);
          assert.ok(table);
          assert.strictEqual(table.isCompact, true);
          assert.strictEqual(table.columns.length, 3);
          assert.strictEqual(table.partitionKeys.length, 1);
          assert.strictEqual(table.clusteringKeys.length, 2);
          assert.strictEqual(table.clusteringKeys[0].name, 'id2');
          assert.strictEqual(table.clusteringKeys[1].name, 'text1');
          done();
        });
      });
    });
  });
  describe('#getFunctions()', function () {
    it('should return an empty array when not found', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
        assert.ifError(err);
        assert.ok(funcArray);
        assert.strictEqual(funcArray.length, 0);
        done();
      });
    });
    describe('with C* 2.2 metadata rows', function () {
      it('should query once when called in parallel', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            called++;
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { functions: {}};
        utils.times(10, function (n, next) {
          metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
            assert.ifError(err);
            assert.ok(funcArray);
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 1);
          done();
        });
      });
      it('should query once when called serially', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            called++;
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { functions: {}};
        utils.timesSeries(10, function (n, next) {
          metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
            assert.ifError(err);
            assert.ok(funcArray);
            assert.strictEqual(funcArray.length, 1);
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 1);
          done();
        });
      });
      it('should query the following times if was previously not found', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            if (called++ < 5) {
              return setImmediate(function () {
                cb(null, {rows: []});
              });
            }
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { functions: {}};
        utils.timesSeries(10, function (n, next) {
          metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
            assert.ifError(err);
            assert.ok(funcArray);
            if (n < 5) {
              assert.strictEqual(funcArray.length, 0);
            }
            else {
              //there should be a row
              assert.strictEqual(funcArray.length, 1);
            }
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 6);
          done();
        });
      });
      it('should query the following times if there was an error previously', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            if (called++ < 5) {
              return setImmediate(function () {
                cb(new Error('Dummy'));
              });
            }
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { functions: {}};
        utils.timesSeries(10, function (n, next) {
          metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
            if (n < 5) {
              assert.ok(err);
              assert.strictEqual(err.message, 'Dummy');
            }
            else {
              assert.ifError(err);
              assert.ok(funcArray);
              assert.strictEqual(funcArray.length, 1);
            }
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 6);
          done();
        });
      });
      it('should parse function metadata with 2 parameters', function (done) {
        var rows = [
          {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"},
          {"keyspace_name":"ks_udf","function_name":"plus","signature":["int","int"],"argument_names":["arg1","arg2"],"argument_types":["org.apache.cassandra.db.marshal.Int32Type","org.apache.cassandra.db.marshal.Int32Type"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.Int32Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
        metadata.keyspaces['ks_udf'] = { functions: {}};
        metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
          assert.ifError(err);
          assert.ok(funcArray);
          assert.strictEqual(funcArray.length, 2);
          assert.strictEqual(funcArray[0].name, 'plus');
          assert.strictEqual(funcArray[0].keyspaceName, 'ks_udf');
          assert.strictEqual(funcArray[0].signature.join(', '), ['bigint', 'bigint'].join(', '));
          assert.strictEqual(funcArray[0].argumentNames.join(', '), ['s', 'v'].join(', '));
          assert.ok(funcArray[0].argumentTypes[0]);
          assert.strictEqual(funcArray[0].argumentTypes[0].code, types.dataTypes.bigint);
          assert.ok(funcArray[0].argumentTypes[1]);
          assert.strictEqual(funcArray[0].argumentTypes[1].code, types.dataTypes.bigint);
          assert.strictEqual(funcArray[0].language, 'java');
          assert.ok(funcArray[0].returnType);
          assert.strictEqual(funcArray[0].returnType.code, types.dataTypes.bigint);

          assert.strictEqual(funcArray[1].name, 'plus');
          assert.strictEqual(funcArray[0].keyspaceName, 'ks_udf');
          assert.strictEqual(funcArray[1].signature.join(', '), ['int', 'int'].join(', '));
          assert.strictEqual(funcArray[1].argumentNames.join(', '), ['arg1', 'arg2'].join(', '));
          assert.ok(funcArray[1].argumentTypes[0]);
          assert.strictEqual(funcArray[1].argumentTypes[0].code, types.dataTypes.int);
          assert.ok(funcArray[1].argumentTypes[1]);
          assert.strictEqual(funcArray[1].argumentTypes[1].code, types.dataTypes.int);
          assert.strictEqual(funcArray[1].language, 'java');
          assert.ok(funcArray[1].returnType);
          assert.strictEqual(funcArray[1].returnType.code, types.dataTypes.int);
          done();
        });
      });
      it('should parse a function metadata with no parameters', function (done) {
        var rows = [
          {"keyspace_name":"ks_udf","function_name":"return_one","signature":[],"argument_names":null,"argument_types":null,"body":"return 1;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.Int32Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
        metadata.keyspaces['ks_udf'] = { functions: {}};
        metadata.getFunctions('ks_udf', 'return_one', function (err, funcArray) {
          assert.ifError(err);
          assert.ok(funcArray);
          assert.strictEqual(funcArray.length, 1);
          assert.strictEqual(funcArray[0].name, 'return_one');
          assert.strictEqual(funcArray[0].signature.length, 0);
          assert.strictEqual(funcArray[0].argumentNames, utils.emptyArray);
          assert.strictEqual(funcArray[0].argumentTypes.length, 0);
          assert.strictEqual(funcArray[0].language, 'java');
          assert.ok(funcArray[0].returnType);
          assert.strictEqual(funcArray[0].returnType.code, types.dataTypes.int);
          done();
        });
      });
    });
    describe('with C* 3.0+ metadata rows', function () {
      it('should parse function metadata with 2 parameters', function (done) {
        var rows = [
          {"keyspace_name": "ks_udf", "function_name": "plus", "argument_types": ["int", "int"], "argument_names": ["s", "v"], "body": "return s+v;", "called_on_null_input": false, "language": "java", "return_type": "int"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces['ks_udf'] = { functions: {}};
        metadata.getFunctions('ks_udf', 'plus', function (err, funcArray) {
          assert.ifError(err);
          assert.ok(funcArray);
          assert.strictEqual(funcArray.length, 1);
          assert.strictEqual(funcArray[0].name, 'plus');
          assert.strictEqual(funcArray[0].keyspaceName, 'ks_udf');
          assert.strictEqual(funcArray[0].signature.join(', '), ['int', 'int'].join(', '));
          assert.strictEqual(funcArray[0].argumentNames.join(', '), ['s', 'v'].join(', '));
          assert.ok(funcArray[0].argumentTypes[0]);
          assert.strictEqual(funcArray[0].argumentTypes[0].code, types.dataTypes.int);
          assert.ok(funcArray[0].argumentTypes[1]);
          assert.strictEqual(funcArray[0].argumentTypes[1].code, types.dataTypes.int);
          assert.strictEqual(funcArray[0].language, 'java');
          assert.ok(funcArray[0].returnType);
          assert.strictEqual(funcArray[0].returnType.code, types.dataTypes.int);
          done();
        });
      });
    });
  });
  describe('#getFunction()', function () {
    context('with no callback specified', function () {
      if (!helper.promiseSupport) {
        it('should throw an ArgumentError', function () {
          var metadata = newInstance();
          assert.throws(function () {
            metadata.getFunction('ks1', 'fn1', []);
          }, errors.ArgumentError);
        });
        return;
      }
      it('should return a Promise', function () {
        var metadata = newInstance();
        var p = metadata.getFunction('ks1', 'fn1', []);
        helper.assertInstanceOf(p, Promise);
      });
    });
    it('should callback in error if keyspace or name are not provided', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      metadata.getFunction('ks_udf', null, [], function (err) {
        helper.assertInstanceOf(err, errors.ArgumentError);
        done();
      });
    });
    it('should callback in error if signature is not an array', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      //noinspection JSCheckFunctionSignatures
      metadata.getFunction('ks_udf', 'func1', {}, function (err) {
        helper.assertInstanceOf(err, errors.ArgumentError);
        done();
      });
    });
    it('should callback in error if signature types are not found', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      //noinspection JSCheckFunctionSignatures
      metadata.getFunction('ks_udf', 'func1', [{code: 0x1000}], function (err) {
        helper.assertInstanceOf(err, errors.ArgumentError);
        done();
      });
    });
    it('should query once when called in parallel', function (done) {
      var called = 0;
      var rows = [
        {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"}
      ];
      var cc = {
        query: function (q, cb) {
          called++;
          setImmediate(function () {
            cb(null, {rows: rows});
          });
        },
        getEncoder: function () { return new Encoder(4, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces['ks_udf'] = { functions: {}};
      utils.times(10, function (n, next) {
        metadata.getFunction('ks_udf', 'plus', ['bigint', 'bigint'], function (err, func) {
          assert.ifError(err);
          assert.ok(func);
          next();
        });
      }, function (err) {
        assert.ifError(err);
        assert.strictEqual(called, 1);
        done();
      });
    });
    it('should query once when called serially', function (done) {
      var called = 0;
      var rows = [
        {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"}
      ];
      var cc = {
        query: function (q, cb) {
          called++;
          helper.assertContains(q, 'system.schema_functions');
          setImmediate(function () {
            cb(null, {rows: rows});
          });
        },
        getEncoder: function () { return new Encoder(4, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.keyspaces['ks_udf'] = { functions: {}};
      utils.timesSeries(10, function (n, next) {
        metadata.getFunction('ks_udf', 'plus', ['bigint', 'bigint'], function (err, func) {
          assert.ifError(err);
          assert.ok(func);
          assert.strictEqual(func.name, 'plus');
          next();
        });
      }, function (err) {
        assert.ifError(err);
        assert.strictEqual(called, 1);
        done();
      });
    });
    it('should return null when not found', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      metadata.getFunction('ks_udf', 'plus', [], function (err, func) {
        assert.ifError(err);
        assert.strictEqual(func, null);
        done();
      });
    });
    it('should parse function metadata with 2 parameters', function (done) {
      var rows = [
        {"keyspace_name":"ks_udf","function_name":"plus","signature":["bigint","bigint"],"argument_names":["s","v"],"argument_types":["org.apache.cassandra.db.marshal.LongType","org.apache.cassandra.db.marshal.LongType"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.LongType"},
        {"keyspace_name":"ks_udf","function_name":"plus","signature":["int","int"],"argument_names":["arg1","arg2"],"argument_types":["org.apache.cassandra.db.marshal.Int32Type","org.apache.cassandra.db.marshal.Int32Type"],"body":"return s+v;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.Int32Type"}
      ];
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      metadata.getFunction('ks_udf', 'plus', ['int', 'int'], function (err, func) {
        assert.ifError(err);
        assert.ok(func);
        assert.strictEqual(func.name, 'plus');
        assert.strictEqual(func.signature.join(', '), ['int', 'int'].join(', '));
        assert.strictEqual(func.argumentNames.join(', '), ['arg1', 'arg2'].join(', '));
        assert.ok(func.argumentTypes[0]);
        assert.strictEqual(func.argumentTypes[0].code, types.dataTypes.int);
        assert.ok(func.argumentTypes[1]);
        assert.strictEqual(func.argumentTypes[1].code, types.dataTypes.int);
        assert.strictEqual(func.language, 'java');
        assert.ok(func.returnType);
        assert.strictEqual(func.returnType.code, types.dataTypes.int);
        done();
      });
    });
    it('should parse a function metadata with no parameters', function (done) {
      var rows = [
        {"keyspace_name":"ks_udf","function_name":"return_one","signature":[],"argument_names":null,"argument_types":null,"body":"return 1;","called_on_null_input":false,"language":"java","return_type":"org.apache.cassandra.db.marshal.Int32Type"}
      ];
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
      metadata.keyspaces['ks_udf'] = { functions: {}};
      metadata.getFunction('ks_udf', 'return_one', [], function (err, func) {
        assert.ifError(err);
        assert.ok(func);
        assert.strictEqual(func.name, 'return_one');
        assert.strictEqual(func.signature.length, 0);
        assert.strictEqual(func.argumentNames, utils.emptyArray);
        assert.strictEqual(func.argumentTypes.length, 0);
        assert.strictEqual(func.language, 'java');
        assert.ok(func.returnType);
        assert.strictEqual(func.returnType.code, types.dataTypes.int);
        done();
      });
    });
  });
  describe('#getAggregates()', function () {
    it('should return an empty array when not found', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
      metadata.keyspaces['ks_udf'] = { aggregates: {}};
      metadata.getAggregates('ks_udf', 'plus', function (err, funcArray) {
        assert.ifError(err);
        assert.ok(funcArray);
        assert.strictEqual(funcArray.length, 0);
        done();
      });
    });
    describe('with C* 2.2 metadata rows', function () {
      it('should query once when called in parallel', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","aggregate_name":"sum","signature":["bigint"],"argument_types":["org.apache.cassandra.db.marshal.LongType"],"final_func":null,"initcond":new Buffer([0,0,0,0,0,0,0,0]),"return_type":"org.apache.cassandra.db.marshal.LongType","state_func":"plus","state_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            called++;
            helper.assertContains(q, 'system.schema_aggregates');
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { aggregates: {}};
        utils.times(10, function (n, next) {
          metadata.getAggregates('ks_udf', 'sum', function (err, funcArray) {
            assert.ifError(err);
            assert.ok(funcArray);
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 1);
          done();
        });
      });
      it('should query once when called serially', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","aggregate_name":"sum","signature":["bigint"],"argument_types":["org.apache.cassandra.db.marshal.LongType"],"final_func":null,"initcond":new Buffer([0,0,0,0,0,0,0,0]),"return_type":"org.apache.cassandra.db.marshal.LongType","state_func":"plus","state_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            called++;
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { aggregates: {}};
        utils.timesSeries(10, function (n, next) {
          metadata.getAggregates('ks_udf', 'sum', function (err, funcArray) {
            assert.ifError(err);
            assert.ok(funcArray);
            assert.strictEqual(funcArray.length, 1);
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 1);
          done();
        });
      });
      it('should query the following times if was previously not found', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","aggregate_name":"sum","signature":["bigint"],"argument_types":["org.apache.cassandra.db.marshal.LongType"],"final_func":null,"initcond":new Buffer([0,0,0,0,0,0,0,0]),"return_type":"org.apache.cassandra.db.marshal.LongType","state_func":"plus","state_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            if (called++ < 5) {
              return setImmediate(function () {
                cb(null, {rows: []});
              });
            }
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { aggregates: {}};
        utils.timesSeries(10, function (n, next) {
          metadata.getAggregates('ks_udf', 'sum', function (err, funcArray) {
            assert.ifError(err);
            assert.ok(funcArray);
            if (n < 5) {
              assert.strictEqual(funcArray.length, 0);
            }
            else {
              //there should be a row
              assert.strictEqual(funcArray.length, 1);
            }
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 6);
          done();
        });
      });
      it('should query the following times if there was an error previously', function (done) {
        var called = 0;
        var rows = [
          {"keyspace_name":"ks_udf","aggregate_name":"sum","signature":["bigint"],"argument_types":["org.apache.cassandra.db.marshal.LongType"],"final_func":null,"initcond":new Buffer([0,0,0,0,0,0,0,0]),"return_type":"org.apache.cassandra.db.marshal.LongType","state_func":"plus","state_type":"org.apache.cassandra.db.marshal.LongType"}
        ];
        var cc = {
          query: function (q, cb) {
            helper.assertContains(q, 'system.schema_aggregates');
            if (called++ < 5) {
              return setImmediate(function () {
                cb(new Error('Dummy'));
              });
            }
            setImmediate(function () {
              cb(null, {rows: rows});
            });
          },
          getEncoder: function () { return new Encoder(4, {}); }
        };
        var metadata = new Metadata(clientOptions.defaultOptions(), cc);
        metadata.keyspaces['ks_udf'] = { aggregates: {}};
        utils.timesSeries(10, function (n, next) {
          metadata.getAggregates('ks_udf', 'sum', function (err, funcArray) {
            if (n < 5) {
              assert.ok(err);
              assert.strictEqual(err.message, 'Dummy');
            }
            else {
              assert.ifError(err);
              assert.ok(funcArray);
              assert.strictEqual(funcArray.length, 1);
            }
            next();
          });
        }, function (err) {
          assert.ifError(err);
          assert.strictEqual(called, 6);
          done();
        });
      });
      it('should parse aggregate metadata with 1 parameter', function (done) {
        var rows = [
          {"keyspace_name":"ks_udf1","aggregate_name":"sum","signature":["bigint"],"argument_types":["org.apache.cassandra.db.marshal.LongType"],"final_func":null,"initcond":new Buffer([0,0,0,0,0,0,0,0]),"return_type":"org.apache.cassandra.db.marshal.LongType","state_func":"plus","state_type":"org.apache.cassandra.db.marshal.LongType"},
          {"keyspace_name":"ks_udf1","aggregate_name":"sum","signature":["int"],"argument_types":["org.apache.cassandra.db.marshal.Int32Type"],"final_func":null,"initcond":new Buffer([0,0,0,0]),"return_type":"org.apache.cassandra.db.marshal.Int32Type","state_func":"plus","state_type":"org.apache.cassandra.db.marshal.Int32Type"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
        metadata.keyspaces['ks_udf1'] = { aggregates: {}};
        metadata.getAggregates('ks_udf1', 'sum', function (err, aggregatesArray) {
          assert.ifError(err);
          assert.ok(aggregatesArray);
          assert.strictEqual(aggregatesArray.length, 2);
          assert.strictEqual(aggregatesArray[0].name, 'sum');
          assert.strictEqual(aggregatesArray[0].keyspaceName, 'ks_udf1');
          assert.strictEqual(aggregatesArray[0].signature.join(', '), ['bigint'].join(', '));
          assert.strictEqual(aggregatesArray[0].argumentTypes.length, 1);
          assert.ok(aggregatesArray[0].argumentTypes[0]);
          assert.strictEqual(aggregatesArray[0].argumentTypes[0].code, types.dataTypes.bigint);
          assert.ok(aggregatesArray[0].returnType);
          assert.strictEqual(aggregatesArray[0].returnType.code, types.dataTypes.bigint);
          assert.strictEqual(aggregatesArray[0].finalFunction, null);
          assert.ok(aggregatesArray[0].stateType);
          assert.strictEqual(aggregatesArray[0].stateType.code, types.dataTypes.bigint);
          assert.strictEqual(aggregatesArray[0].stateFunction, 'plus');
          assert.strictEqual(aggregatesArray[0].initCondition, '0');

          assert.strictEqual(aggregatesArray[1].name, 'sum');
          assert.strictEqual(aggregatesArray[0].keyspaceName, 'ks_udf1');
          assert.strictEqual(aggregatesArray[1].signature.join(', '), ['int'].join(', '));
          assert.strictEqual(aggregatesArray[1].argumentTypes.length, 1);
          assert.ok(aggregatesArray[1].argumentTypes[0]);
          assert.strictEqual(aggregatesArray[1].argumentTypes[0].code, types.dataTypes.int);
          assert.ok(aggregatesArray[1].returnType);
          assert.strictEqual(aggregatesArray[1].returnType.code, types.dataTypes.int);
          assert.strictEqual(aggregatesArray[1].finalFunction, null);
          assert.ok(aggregatesArray[1].stateType);
          assert.strictEqual(aggregatesArray[1].stateType.code, types.dataTypes.int);
          assert.strictEqual(aggregatesArray[1].stateFunction, 'plus');
          assert.strictEqual(aggregatesArray[1].initCondition, '0');
          done();
        });
      });
    });
    describe('with C* 3.0+ metadata rows', function () {
      it('should parse aggregate metadata with 1 parameter', function (done) {
        var rows = [
          {"keyspace_name": "ks_udf1", "aggregate_name": "sum", "argument_types": ["bigint"], "final_func": null, "initcond": '2', "return_type": "bigint", "state_func": "plus", "state_type": "bigint"},
          {"keyspace_name": "ks_udf1", "aggregate_name": "sum", "argument_types": ["int"], "final_func": null, "initcond": '1', "return_type": "int", "state_func": "plus", "state_type": "int"}
        ];
        var metadata = new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows(rows));
        metadata.setCassandraVersion([3, 0]);
        metadata.keyspaces['ks_udf1'] = { aggregates: {}};
        metadata.getAggregates('ks_udf1', 'sum', function (err, aggregatesArray) {
          assert.ifError(err);
          assert.ok(aggregatesArray);
          assert.strictEqual(aggregatesArray.length, 2);
          assert.strictEqual(aggregatesArray[0].name, 'sum');
          assert.strictEqual(aggregatesArray[0].keyspaceName, 'ks_udf1');
          assert.strictEqual(aggregatesArray[0].signature.join(', '), ['bigint'].join(', '));
          assert.strictEqual(aggregatesArray[0].argumentTypes.length, 1);
          assert.ok(aggregatesArray[0].argumentTypes[0]);
          assert.strictEqual(aggregatesArray[0].argumentTypes[0].code, types.dataTypes.bigint);
          assert.ok(aggregatesArray[0].returnType);
          assert.strictEqual(aggregatesArray[0].returnType.code, types.dataTypes.bigint);
          assert.strictEqual(aggregatesArray[0].finalFunction, null);
          assert.ok(aggregatesArray[0].stateType);
          assert.strictEqual(aggregatesArray[0].stateType.code, types.dataTypes.bigint);
          assert.strictEqual(aggregatesArray[0].stateFunction, 'plus');
          assert.strictEqual(aggregatesArray[0].initCondition, '2');

          assert.strictEqual(aggregatesArray[1].name, 'sum');
          assert.strictEqual(aggregatesArray[0].keyspaceName, 'ks_udf1');
          assert.strictEqual(aggregatesArray[1].signature.join(', '), ['int'].join(', '));
          assert.strictEqual(aggregatesArray[1].argumentTypes.length, 1);
          assert.ok(aggregatesArray[1].argumentTypes[0]);
          assert.strictEqual(aggregatesArray[1].argumentTypes[0].code, types.dataTypes.int);
          assert.ok(aggregatesArray[1].returnType);
          assert.strictEqual(aggregatesArray[1].returnType.code, types.dataTypes.int);
          assert.strictEqual(aggregatesArray[1].finalFunction, null);
          assert.ok(aggregatesArray[1].stateType);
          assert.strictEqual(aggregatesArray[1].stateType.code, types.dataTypes.int);
          assert.strictEqual(aggregatesArray[1].stateFunction, 'plus');
          assert.strictEqual(typeof aggregatesArray[1].initCondition, 'string');
          assert.strictEqual(aggregatesArray[1].initCondition, '1');
          done();
        });
      });
    });
  });
  describe('#getMaterializedView()', function () {
    var scoresTableMetadata = new TableMetadata('scores');
    scoresTableMetadata.columnsByName = {
      'score': { type: { code: types.dataTypes.int}, name: 'score' },
      'user': { type: { code: types.dataTypes.text}, name: 'user' },
      'game': { type: { code: types.dataTypes.text}, name: 'game' },
      'year': { type: { code: types.dataTypes.int}, name: 'year'},
      'month': { type: { code: types.dataTypes.int}, name: 'month'},
      'day': { type: { code: types.dataTypes.int}, name: 'day'}
    };
    it('should return null when the view is not found', function (done) {
      var cc = {
        query: function (q, cb) {
          setImmediate(function () {
            //return an empty array
            cb(null, {rows: []});
          });
        },
        getEncoder: function () { return new Encoder(4, {}); }
      };
      var metadata = new Metadata(clientOptions.defaultOptions(), cc);
      metadata.setCassandraVersion([3, 0]);
      metadata.keyspaces['ks_mv'] = { views: {}};
      metadata.getMaterializedView('ks_mv', 'not_found', function (err, view) {
        assert.ifError(err);
        assert.strictEqual(view, null);
        done();
      });
    });
    it('should callback in error when cassandra version is lower than 3.0', function (done) {
      var metadata = new Metadata(clientOptions.defaultOptions(), {});
      metadata.setCassandraVersion([2, 1]);
      metadata.keyspaces['ks_mv'] = { views: {}};
      metadata.getTable = function (ksName, name, cb) {
        cb(null, scoresTableMetadata);
      };
      metadata.getMaterializedView('ks_mv', 'view1', function (err) {
        helper.assertInstanceOf(err, errors.NotSupportedError);
        done();
      });
    });
  });
});
describe('SchemaParser', function () {
  var isDoneForToken = rewire('../../lib/metadata/schema-parser')['__get__']('isDoneForToken');
  describe('isDoneForToken()', function () {
    it('should skip if dc not included in topology', function () {
      var replicationFactors = { 'dc1': 3, 'dc2': 1 };
      //dc2 does not exist
      var datacenters = {
        'dc1': { hostLength: 6 }
      };
      assert.strictEqual(false, isDoneForToken(replicationFactors, datacenters, {}));
    });
    it('should skip if rf equals to 0', function () {
      //rf 0 for dc2
      var replicationFactors = { 'dc1': 4, 'dc2': 0 };
      var datacenters = {
        'dc1': { hostLength: 6 },
        'dc2': { hostLength: 6 }
      };
      assert.strictEqual(true, isDoneForToken(replicationFactors, datacenters, { 'dc1': 4 }));
    });
    it('should return false for undefined replicasByDc[dcName]', function () {
      var replicationFactors = { 'dc1': 3, 'dc2': 1 };
      //dc2 does not exist
      var datacenters = {
        'dc1': { hostLength: 6 }
      };
      assert.strictEqual(false, isDoneForToken(replicationFactors, datacenters, {}));
    });
  });
});

function getControlConnectionForTable(tableRow, columnRows, indexRows) {
  return {
    queriedTable: 0,
    queriedRows: 0,
    queriedIndexes: 0,
    query: function (q, cb) {
      var self = this;
      setImmediate(function () {
        if (q.indexOf('system.schema_columnfamilies') >= 0 || q.indexOf('system_schema.tables') >= 0) {
          self.queriedTable++;
          return cb(null, { rows: [tableRow]});
        }
        if (q.indexOf('system_schema.indexes') >= 0) {
          self.queriedIndexes++;
          return cb(null, { rows: (indexRows || [])});
        }
        self.queriedRows++;
        cb(null, {rows: columnRows});
      });
    },
    getEncoder: function () { return new Encoder(1, {}); }
  };
}

function getControlConnectionForRows(rows, protocolVersion) {
  return {
    query: function (q, w, cb) {
      if (typeof w === 'function') {
        cb = w;
      }
      setImmediate(function () {
        cb(null, {rows: rows});
      });
    },
    getEncoder: function () { return new Encoder(protocolVersion || 4, {}); }
  };
}

function getAddress(h) {
  return h.address;
}

/**
 * Creates a dummy tokenizer based on the first byte of the buffer.
 * @returns {Murmur3Tokenizer}
 */
function getTokenizer() {
  var t = new tokenizer.Murmur3Tokenizer();
  //Use the first byte as token
  t.hash = function (b) { return b[0];};
  t.compare = function (a, b) { if (a > b) {return 1;} if (a < b) {return -1;} return 0; };
  return t;
}

/** @returns {Metadata} */
function newInstance() {
  return new Metadata(clientOptions.defaultOptions(), getControlConnectionForRows([]));
}