Source: store.js

/*-
 *
 *  This file is part of Oracle NoSQL Database
 *  Copyright (C) 2014, 2018 Oracle and/or its affiliates.  All rights reserved.
 *
 * If you have received this file as part of Oracle NoSQL Database the
 * following applies to the work as a whole:
 *
 *   Oracle NoSQL Database server software is free software: you can
 *   redistribute it and/or modify it under the terms of the GNU Affero
 *   General Public License as published by the Free Software Foundation,
 *   version 3.
 *
 *   Oracle NoSQL Database is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Affero General Public License for more details.
 *
 * If you have received this file as part of Oracle NoSQL Database Client or
 * distributed separately the following applies:
 *
 *   Oracle NoSQL Database client software is free software: you can
 *   redistribute it and/or modify it under the terms of the Apache License
 *   as published by the Apache Software Foundation, version 2.0.
 *
 * You should have received a copy of the GNU Affero General Public License
 * and/or the Apache License in the LICENSE file along with Oracle NoSQL
 * Database client or server distribution.  If not, see
 * <http://www.gnu.org/licenses/>
 * or
 * <http://www.apache.org/licenses/LICENSE-2.0>.
 *
 * An active Oracle commercial licensing agreement for this product supersedes
 * these licenses and in such case the license notices, but not the copyright
 * notice, may be removed by you in connection with your distribution that is
 * in accordance with the commercial licensing terms.
 *
 * For more information please contact:
 *
 * berkeleydb-info_us@oracle.com
 *
 */
'use strict';

/*global thrift*/
/*global Logger*/
/*global LOG_LEVELS*/
/*global Errors*/
/*global Types*/
/*global fs*/

var EventEmitter = require('events').EventEmitter;
var Proxy = require('./proxy');
var Iterator = require('./iterator');
var ONDBClient = require('./thrift/ONDB');
var ttypes = require('./thrift/ondb_types');
var Types = require('./types')
var Parse = require('./parse');
var Stringify = require('./stringify');
var Stream = require('./readable');
var pack = JSON.parse(fs.readFileSync(__dirname + '/../package.json'));

util.inherits(Store, EventEmitter);
module.exports = Store;

/**
 * Store constructor
 * @param {Configuration} configuration Configuration object.
 * @constructor
 */
function Store(/*Configuration*/ configuration) {

  Logger.debug('New Store instance');

  var self = this;
  this._version = null;
  var proxyConf = configuration.proxy;
  Errors.missingParameter(configuration, 'configuration');
  Errors.missingParameter(proxyConf, 'configuration.proxy');
  Errors.missingParameter(proxyConf.startProxy,
    'configuration.proxy.startProxy');
  Errors.missingParameter(proxyConf.host, 'configuration.proxy.host');
  Errors.missingParameter(configuration.storeName, 'configuration.storeName');
  Errors.missingParameter(configuration.storeHelperHosts,
    'configuration.storeHelperHosts');

  configuration.requestTimeout = parseInt(configuration.requestTimeout);

  // verify jars for proxy
  if (proxyConf.startProxy === true) {
    if (!fs.existsSync(proxyConf.KVCLIENT_JAR)) {
      var error = new Errors.ParameterError('configuration.proxy.KVCLIENT_JAR',
        'kvclient.jar file not found, KVCLIENT_JAR value: ' +
        configuration.proxy.KVCLIENT_JAR
      );
      Logger.error(error);
      throw error;
    }
    var proxyFile = path.normalize(proxyConf.KVPROXY_JAR);
    if (!fs.existsSync(proxyFile)) {
      var error = new Errors.ParameterError('proxy.KVPROXY_JAR',
        'kvproxy.jar file not found, full path: ' +
        proxyFile + '  using KVPROXY_JAR value: ' + proxyConf.KVPROXY_JAR
      );
      Logger.error(error);
      throw error;
    }
  }

  EventEmitter.call(this);
  this.isConnected = false;
  this.thriftClient = null;
  this.configuration = configuration;
  var thriftConnection;
  this._verifyDefaultConsistency();

  function setClient(callback) {
    Logger.debug('Set NoSQL DB Proxy: ' + proxyConf.host);

    callback = callback || function () {
    };
    var host;
    var port;
    var colon = proxyConf.host.indexOf(':');
    if (colon) {
      host = proxyConf.host.substr(0, colon);
      port = proxyConf.host.substr(colon + 1);
      if (proxyConf.startProxy === true) {
        if (!(host == 'localhost') || (host == '127.0.0.1')) {
          callback(new Errors.ParameterError(
            'configuration.proxy.host',
            'To start the proxy, the host should be localhost.'));
          return;
        }
      }
    } else {
      callback(new Errors.ParameterError(
        'configuration.proxy.host',
        'The parameter must follow the format host:port'));
      return;
    }
    var options = {
      transport: thrift.TFramedTransport,
      protocol: thrift.TBinaryProtocol
    };
    if (configuration.requestTimeout > 0)
      options.timeout = configuration.requestTimeout;
    thriftConnection = thrift.createConnection(host, port, options)
      .on('error', function onErrorThrift(err) {
        Logger.debug('Error on thrift Connection' + err);
        callback(err);
        thriftConnection.removeAllListeners('error');
        thriftConnection.removeAllListeners('connect');
      }).on('connect', function onConnectThrift(err) {
        Logger.debug('Thrift Connection successful');
        self.thriftClient = thrift.createClient(ONDBClient, thriftConnection);
        callback();
        thriftConnection.removeAllListeners('error');
        thriftConnection.removeAllListeners('connect');
      });
  }

  function verify(callback) {

    Logger.debug('Starting verify process');
    Logger.debug('kvStoreName: ' + configuration.storeName);
    Logger.debug('kvStoreHelperHosts: ' + configuration.storeHelperHosts);
    Logger.debug('username: ' + configuration.username);
    Logger.debug('readZones: ' + configuration.readZones);

    callback = callback || function () {
    };
    var verify = new Types.VerifyProperties({
      kvStoreName: configuration.storeName,
      kvStoreHelperHosts: configuration.storeHelperHosts,
      username: configuration.username,
      readZones: configuration.readZones,
      driverProtocolVersion: pack.protocolVersion
    });

    var timeout = setTimeout(function () {
      callback(new Errors.ConnectionError('Connection timeout'))
    }, 2000);

    self.thriftClient.verify(verify, function (err, result) {
      if (err) {
        if (callback)
          callback(new Errors.ConnectionError('Verification error', err));
      } else {
        if (result) {
          if (result.isConnected) {
            if (result.proxyProtocolVersion) {
              var found = false;
              for (var index in pack.protocolSupported) {
                if (pack.protocolSupported[index] ==
                    result.proxyProtocolVersion)
                  found = true;
              }
              if (found)
                callback();
              else
                callback(new Errors.ConnectionError(
                  'Verification process failed.',
                  'This driver can\'t support ' +
                  ' proxy protocol version: ' + result.proxyProtocolVersion));
            } else
              callback(new Errors.ConnectionError(
                'Verification process failed.',
                'Protocol version not set'));
          } else
            callback(new Errors.ConnectionError(
              'Verification process failed.',
              types.VerifyErrorDescription(result.errorType)));
        } else
          callback(new Errors.ConnectionError(
            'Verification result not received', err));
      }
      callback = null;
      clearTimeout(timeout);
    });
  }

  /**
   * This method opens a connection to a kvstore server. It tries to start a
   * Thrift proxy if startProxy is set to true on configuration options. This
   * process also calls the 'open' event in the listener protocol or 'error' if
   * an error occurs during opening.
   * @param {openCallback} [callback] Calls this function when the process
   *   finishes, will return an error object if the opening process fails.
   * @method
   */
  this.open = function open(/*function*/ callback, attempt, latestError) {
    Logger.debug('Store open');
    callback = callback || function () {
    };
    if (typeof attempt === 'undefined')
      attempt = 1; else attempt++;

    if (self.isConnected) {
      var error =
        new Errors.ConnectionError('This store is already connected');
      self.reportError(error, callback);
      callback = null;
      return;
    }
    if (attempt > configuration.connectionAttempts) {
      var errorMsg = 'Maximum connection attempts ' +
                     configuration.connectionAttempts +
                     ' reached';
      if (proxyConf.startProxy === true) {
        errorMsg += ', latest proxy error: ' + latestError;
      }
      var error = new Errors.ConnectionError(errorMsg);
      self.reportError(error, callback);
      callback = null;
      return;
    }
    Logger.debug('Setting client');
    setClient(function (err) {
      if (err !== undefined) {
        if (err instanceof Errors.ParameterError) {
          self.reportError(err, callback);
          return;
        }
        if (proxyConf.startProxy === true)
          Proxy.startProxy(configuration, function (err, output) {
            self.open(callback, attempt, output);
          });
        else {
          var error = new Errors.ConnectionError(
            'Error connecting the proxy', err);
          self.reportError(error, callback);
          callback = null;
        }
      } else {
        verify(function (err) { // Verify failed
          if (err) {
            var error = new Errors.ConnectionError(
              'Error verifying the proxy connection', err);
            self.reportError(error, callback);
            callback = null;
            thriftConnection.end();
          } else {
            Logger.debug('Store connected to proxy');
            self.isConnected = true;
            if (callback)
              callback();
            callback = null;
            self.emit('open');
          }
        });
      }
    });
  };

  /**
   * Closes the connection.
   */
  this.close = function (callback) {
    Logger.info('Store close');
    callback = callback || function () {
    };

    if (self.isConnected) {
      thriftConnection.end();
      self.isConnected = false;
      callback();
      self.emit('close');
    }
  };
}
/**
 * @callback openCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 */

Store.prototype._verifyDefaultConsistency =
function _verifyDefaultConsistency() {
  var self = this;
  var consistency = self.configuration.defaultConsistency;

  if (consistency !== undefined)
    if (consistency)
      if (!(
        (consistency instanceof Types.Consistency) ||
        (consistency instanceof Types.TimeConsistency) ||
        (consistency instanceof Types.VersionConsistency)
        ))
        throw new Error('Error in configuration.defaultConsistency, ' +
                        'is not a valid Consistency object.');

  //Check for Time/Version default consistency
  if (!(consistency instanceof Types.Consistency))
    consistency = new Types.Consistency(consistency);

  self.configuration.defaultConsistency = consistency;
};

Store.prototype._checkReadOptions =
function _checkReadOptions(/*ReadOptions*/ readOptions) {
  var self = this;

  // Default readoptions
  readOptions = readOptions ||
                new Types.ReadOptions(
                  self.configuration.defaultConsistency,
                  self.configuration.requestTimeout);

  // Default consistency
  var consistency =
    readOptions.consistency || self.configuration.defaultConsistency;

  // Check for true objects
  if (!(
    (consistency instanceof Types.Consistency) ||
    (consistency instanceof Types.SimpleConsistency.NONE_REQUIRED_NO_MASTER)
    ||
    (consistency instanceof Types.SimpleConsistency.NONE_REQUIRED) ||
    (consistency instanceof Types.SimpleConsistency.ABSOLUTE) ||
    (consistency instanceof Types.TimeConsistency) ||
    (consistency instanceof Types.VersionConsistency) ))
    throw new Error('Error in readOptions.consistency, ' +
                    'is not a valid Consistency object.');

  // Check for Simple/Time/Version consistency
  if (!(consistency instanceof Types.Consistency))
    consistency = new Types.Consistency(readOptions.consistency);

  readOptions.consistency = consistency;

  return readOptions;
};

function checkWriteOptions(/*WriteOptions*/ writeOptions,
                           /*Configuration*/ configuration) {
  writeOptions = writeOptions ||
                 new Types.WriteOptions(
                   configuration.defaultDurability,
                   configuration.requestTimeout);
  writeOptions.durability = writeOptions.durability ||
                            configuration.defaultDurability;
  return writeOptions;
}

Store.prototype.reportError = function reportError(error, callback) {
  callback = callback || function () {
  };
  Logger.error(error);
  callback(error);
  try {
    this.emit('error', error);
  } catch (error) {
    Logger.debug("No error event available to catch error: " + error);
  }
};

Store.prototype.checkIsConnected = function checkIsConnected() {
  var self = this;
  if (!self.isConnected)
    throw new Errors.ConnectionError(
      '\nThe store is not connected.' +
      '\nStore name: ' + self.configuration.storeName +
      '\nStore helper hosts: ' + self.configuration.storeHelperHosts +
      '\nUsername: ' + self.configuration.username +
      '\nRead zones: ' + self.configuration.readZones +
      '\nCheck your configuration and try again.');
};

/**
 * Attempts to shutdown the proxy related to the configuration of this store.
 * @param {shutdownProxyCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.shutdownProxy = function shutdownProxy(callback) {
  var self = this;

  callback = callback || function () {
  };

  if (self.isConnected) {
    self.close();
    self.thriftClient.shutdown();
    callback();
  } else {
    Proxy.stopProxy(self.configuration.proxy, callback);
  }
};
/**
 * @callback shutdownProxyCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 */

/**
 * Gets the Row associated with the primary key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {ReadOptions} [readOptions] Non-default options for the operation or
 *   null to get default behavior.
 * @param {getCallback} [callback] A function that is called when the process
 *   ends.
 *
 */
Store.prototype.get = function get(/*String*/ tableName,
                                   /*Object*/ primaryKey,
                                   /*ReadOptions*/ readOptions,
                                   /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof readOptions === 'function') {
    callback = readOptions;
    readOptions = null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.get(tableName, primaryKey, readOptions,
    function (err, response) {
      Logger.debug('Return from get with err:' + err);
      if (err) {
        var error = Errors.getProxyError(err, 'get()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.GetResult(response);
        callback(null, result);
      }
    });
};
/**
 * @callback getCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {GetResult} result The result associated with this operation.
 */

/**
 * Returns the rows associated with a partial primary key in an atomic
 * manner. Rows are returned in primary key order. The key used must contain
 * all of the fields defined for the table's shard key.
 * @param {String} tableName The table name.
 * @param {Object} partialPrimaryKey The primary key for a table.
 * It may be partial or complete. The key used must contain all of the fields
 * defined for the table's shard key.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     readOptions: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {multiGetCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.multiGet = function multiGet(/*String*/ tableName,
                                             /*Object*/ partialPrimaryKey,
                                             /*Object*/ options,
                                             /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(partialPrimaryKey, 'primaryKey');

  partialPrimaryKey = new ttypes.TRow({jsonRow: Stringify(partialPrimaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, readOptions;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.multiGet(tableName, partialPrimaryKey, fieldRange,
    includedTables,
    readOptions, function (err, response) {

      if (err) {
        var error = Errors.getProxyError(err, 'multiGet()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.MultiGetResult(response);
        callback(null, result);
      }

    });

};
/**
 * @callback multiGetCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {MultiGetResult} result The result associated with this
 * operation.
 */

/**
 * Returns the rows associated with a partial primary key in an atomic manner.
 * Keys are returned in primary key order. The key used must contain all of
 * the fields defined for the table's shard key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     readOptions: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys. the primaryKey parameter
 *   is always included as a target.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {multiGetKeysCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.multiGetKeys =
function multiGetKeys(/*String*/ tableName,
                      /*Object*/ primaryKey,
                      /*Object*/ options,
                      /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, readOptions;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.multiGetKeys(tableName, primaryKey, fieldRange,
    includedTables, readOptions, function (err,
                                           response) {
      if (err) {
        var error = Errors.getProxyError(err, 'multiGetKeys()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response && response.rowsWithMetadata)
          result = new Types.MultiGetKeyResult(response);
        callback(null, result);
      }
    });
};
/**
 * @callback multiGetKeysCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {MultiGetKeyResult} result The result associated with this
 * operation.
 */

/**
 * Puts a row into a table. The row must contain a complete primary key and
 * all required fields.
 * @param {String} tableName The table name.
 * @param {Object} row the primary key for a table. It must be a complete
 *   primary key, with all fields set.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behaviour.
 * @param {putCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.put = function put(/*String*/ tableName,
                                   /*Object*/ row,
                                   /*WriteOptions*/ writeOptions,
                                   /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(row, 'row');

  var ttl = row.ttl;  
  row = new ttypes.TRow({jsonRow: Stringify(row)});
  row.ttl = ttl;
  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();

  self.thriftClient.put(tableName, row, writeOptions,
    function (err, response) {
      Logger.debug('Return from put with err:' + err);
      if (err) {
        var error = Errors.getProxyError(err, 'put()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.PutResult(response);
        callback(null, result);
      }
    });
};

/**
 * Puts a row into a table, but only if the row does not exist. The row must
 * contain a complete primary key and all required fields.
 * @param {String} tableName The table name.
 * @param {Object} row The primary key for a table. It must be a complete
 *   primary key, with all fields set.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behavior.
 * @param {putCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.putIfAbsent =
function putIfAbsent(/*String*/ tableName,
                     /*Object*/ row,
                     /*WriteOptions*/ writeOptions,
                     /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(row, 'row');

  row = new ttypes.TRow({jsonRow: Stringify(row)});
  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.putIfAbsent(tableName, row, writeOptions,
    function (err, response) {
      Logger.debug('Return from putIfAbsent with err:' + err);
      if (err) {
        var error = Errors.getProxyError(err, 'putIfAbsent()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.PutResult(response);
        callback(null, result);
      }
    });
};

/**
 * Puts a row into a table, but only if the row already exists. The row must
 * contain a complete primary key and all required fields.
 * @param {String} tableName The table name.
 * @param {Object} row the primary key for a table. It must be a complete
 *   primary key, with all fields set.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behavior.
 * @param {putCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.putIfPresent =
function putIfPresent(/*String*/ tableName,
                      /*Object*/ row,
                      /*WriteOptions*/ writeOptions,
                      /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(row, 'row');

  row = new ttypes.TRow({jsonRow: Stringify(row)});
  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.putIfPresent(tableName, row, writeOptions,
    function (err, response) {
      if (err) {
        var error = Errors.getProxyError(err, 'putIfPresent()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.PutResult(response);
        callback(null, result);
      }
    });
};

/**
 * Puts a row, but only if the version of the existing row matches the
 * matchVersion argument. Used when updating a value to ensure that it has
 * not changed since it was last read. The row must contain a complete
 * primary key and all required fields.
 * @param {String} tableName The table name.
 * @param {Object} row The primary key for a table. It must be a complete
 *   primary key, with all fields set.
 * @param {Buffer} matchVersion The version to match.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behavior.
 * @param {putCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.putIfVersion =
function putIfVersion(/*String*/ tableName,
                      /*Object*/ row,
                      /*Version*/ matchVersion,
                      /*WriteOptions*/ writeOptions,
                      /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(row, 'row');
  Errors.missingParameter(matchVersion, 'matchVersion');

  row = new ttypes.TRow({jsonRow: Stringify(row)});
  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.putIfVersion(tableName, row, matchVersion.id,
    writeOptions, function (err, response) {
      if (err) {
        var error = Errors.getProxyError(err, 'putIfVersion()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.PutResult(response);
        callback(null, result);
      }
    });
};

/**
 * @callback putCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {PutResult} result The result associated with this
 * operation.
 */

/**
 * Deletes a row from a table.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behaviour.
 * @param {deleteCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.delete = function deleteRow(/*String*/ tableName,
                                            /*Object*/ primaryKey,
                                            /*WriteOptions*/ writeOptions,
                                            /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }

  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.deleteRow(tableName, primaryKey, writeOptions,
    function (err, response) {
      if (err) {
        var error = Errors.getProxyError(err, 'deleteRow()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.DeleteResult(response);
        callback(null, result);
      }
    });
};

/**
 * Deletes a row from a table but only if its version matches the one
 * specified in matchVersion.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Version} matchVersion The version to match.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behavior.
 * @param {deleteCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.deleteIfVersion =
function deleteIfVersion(/*String*/ tableName,
                         /*Object*/ primaryKey,
                         /*Version*/ matchVersion,
                         /*WriteOptions*/ writeOptions,
                         /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');
  Errors.missingParameter(matchVersion, 'matchVersion');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.deleteRowIfVersion(tableName, primaryKey, matchVersion.id,
    writeOptions, function (err, response) {
      if (err) {
        var error = Errors.getProxyError(err, 'deleteRowIfVersion()');
        self.reportError(error, callback);
      } else {
        var result = null;
        if (response)
          result = new Types.DeleteResult(response);
        callback(null, result);
      }
    });
};

/**
 * @callback deleteCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {DeleteResult} result The result associated with this
 * operation.
 */

/**
 * Deletes multiple rows from a table in an atomic operation. The key used
 * may be partial but must contain all of the fields that are in the shard
 * key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     writeOptions: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {WriteOptions} options.writeOptions Non-default arguments controlling
 *   the Durability of the operation, or null to get default behavior.
 * @param {multiDeleteCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.multiDelete =
function multiDelete(/*String*/ tableName,
                     /*Object*/ primaryKey,
                     /*Object*/ options,
                     /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName', 'string');
  Errors.missingParameter(primaryKey, 'primaryKey', 'object');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, writeOptions;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    writeOptions = ('readOptions' in options) ? options.readOptions : null;
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {
  };

  self.checkIsConnected();
  return self.thriftClient.multiDelete(
    tableName,
    primaryKey,
    fieldRange,
    includedTables,
    writeOptions,
    function (err, response) {
      Logger.debug('Return from multiDelete with err:' + err);
      if (err) {
        var error = Errors.getProxyError(err, 'multiDelete()');
        self.reportError(error, callback);
      } else {
        callback(null, response);
      }
    });

};
/**
 * @callback multiDeleteCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {Number} result The number of rows deleted by this
 * operation.
 */

/**
 * Returns an iterator over the rows associated with a partial primary key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {iteratorCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.tableIterator =
function tableIterator(/*String*/ tableName,
                       /*Object*/ primaryKey,
                       /*Object*/ options,
                       /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, readOptions, direction;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.tableIterator(
    tableName,
    primaryKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'tableIterator()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Iterator(self.thriftClient, result, Types.MultiGetResult));
    });

};

/**
 * Returns an iterator over the keys associated with a partial primary key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {iteratorCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.tableKeyIterator =
function tableKeyIterator(/*String*/ tableName,
                          /*Object*/ primaryKey,
                          /*Object*/ options,
                          /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, readOptions, direction;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.tableKeyIterator(
    tableName,
    primaryKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'tableKeyIterator()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Iterator(self.thriftClient, result, Types.MultiGetKeyResult));
    });
};

/**
 * Returns an iterator over the rows associated with an index key. This
 * method requires an additional database read on the server side to get row
 * information for matching rows. Ancestor table rows for matching index rows
 * may be returned as well if specified in the getOptions parameter. Index
 * operations may not specify the return of child table rows.
 * @param {String} tableName The table name.
 * @param {String} indexName The index name.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { indexKey: null,
 *     fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {Object} options.indexKey The index key for the operation. It may be
 *   partial or complete. If the key has no fields set the entire index is
 *   matched.
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {iteratorCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.indexIterator =
function indexIterator(/*String*/ tableName,
                       /*String*/ indexName,
                       /*Object*/ options,
                       /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(indexName, 'indexName');

  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var indexKey, fieldRange, includedTables, readOptions, direction;
  if (options) {
    indexKey = ('indexKey' in options) ?
      new ttypes.TRow({jsonRow: Stringify(options.indexKey)}) : null;
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.indexIterator(
    tableName,
    indexName,
    indexKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'indexIterator()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Iterator(self.thriftClient, result, Types.MultiGetResult));
    });
};

/**
 * Returns the KeyPair for matching rows associated with an index key. The
 * iterator returned only references information directly available from the
 * index -- the row's primary key and the matching index key for the entry.
 * No extra fetch operations are performed. Ancestor table keys for
 * matching index keys may be returned as well if specified in the getOptions
 * parameter. Index operations may not specify the return of child table keys.
 * @param {String} tableName The table name.
 * @param {String} indexName The index name.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { indexKey: null,
 *     fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {Object} options.indexKey The index key for the operation. It may be
 *   partial or complete. If the key has no fields set the entire index is
 *   matched.
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {iteratorCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.indexKeyIterator =
function indexKeyIterator(/*String*/ tableName,
                          /*String*/ indexName,
                          /*Object*/ options,
                          /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(indexName, 'indexName');

  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var indexKey, fieldRange, includedTables, readOptions, direction;
  if (options) {
    indexKey = ('indexKey' in options) ?
      new ttypes.TRow({jsonRow: Stringify(options.indexKey)}) : null;
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);

  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.indexKeyIterator(
    tableName,
    indexName,
    indexKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'indexKeyIterator()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Iterator(self.thriftClient, result, Types.KeyPairResult));
    });
};

/**
 * @callback iteratorCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {Iterator} result An Iterator object with the item results for this
 *  call.
 *
 */

/**
 * Refreshes cached information about the tables. This method is
 * required before using any tables that had been modified.
 * @param {function} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.refreshTables = function refreshTables(callback) {
  var self = this;

  callback = callback || function () {
  };
  self.checkIsConnected();
  self.thriftClient.refreshTables(function (err) {
    if (err) {
      var error = Errors.getProxyError(err, 'refreshTables()');
      self.reportError(error, callback);
    } //else
      //callback();
  });
  // TODO: error in proxy, no return, use manual callback
  callback();
};

/**
 * Executes a table statement. Currently, table statements
 * can be used to create or modify tables and indices.
 * @param {String} statement The statement to be performed.
 * @param {executeCallback} [callback] A function that is called when the
 * process ends.
 */
Store.prototype.execute = function execute(/*String*/ statement,
                                           /*function*/ callback) {
  var self = this;

  Errors.missingParameter(statement, 'statement');

  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.executeSyncV2(statement, function (err, result) {
    if (err) {
      var error = Errors.getProxyError(err, 'execute()');
      self.reportError(error, callback);
    } else
      callback(null, new Types.StatementResult(result));
  })
};

/**
 * Executes a table DDL statement in the server without waiting for a response.
 * Currently, table statements can be used to create or modify tables and
 * indices.
 * @param {String} statement The statement to be performed.
 * @param {executeFutureCallback} [callback] A function that is called when the
 * process ends.
 */
Store.prototype.executeFuture =
function executeFuture(/*String*/ statement,
                       /*executeCallback*/ callback) {
  var self = this;

  Errors.missingParameter(statement, 'statement');

  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.executeV2(statement, function (err, result) {
    if (err) {
      var error = Errors.getProxyError(err, 'executeBackground()');
      self.reportError(error, callback);
    } else
      callback(null, new Types.ExecutionFuture(result, self));
  })
};

/**
 * @callback executeFutureCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {ExecutionFuture} result A statementResult object with information
 *  about the process.
 */

/**
 * Creates an ExecutionFuture object from a ExecutionId object.
 * @param {ExecutionId} executionId The ExecutionId from which the execution
 * future is going to be constructed.
 * @return {ExecutionFuture} executionFuture The ExecutionFuture object.
 */
Store.prototype.getExecutionFuture =
function getExecutionFuture(/*ExecutionId*/ executionId) {
  var self = this;

  Errors.missingParameter(executionId, 'executionId');

  self.checkIsConnected();

  return new Types.ExecutionFuture(executionId, self);
};

/**
 * This method provides an efficient and transactional mechanism for
 * executing a sequence of operations associated with tables that share
 * the same shard key portion of their primary keys.
 * @param {Array} operations The operations to be performed.
 * @param {WriteOptions} [writeOptions] Non-default arguments controlling the
 *   Durability of the operation, or null to get default behaviour.
 *   For this method, returnChoice is not allowed within writeOptions object
 *   and it must be null, instead, every operation should handle their own
 *   returnChoice option.
 * @param {executeUpdatesCallback} [callback] A function that is called when
 * the process ends.
 */
Store.prototype.executeUpdates =
function executeUpdates(/*Array*/ operations,
                        /*WriteOptions*/ writeOptions,
                        /*function*/ callback) {
  var self = this;

  Errors.missingParameter(operations, 'operations');
  if (!(operations instanceof Array))  // Only one operation sent
    operations = [operations];
  for (var key in operations)
    if (!(operations[key] instanceof Types.Operation)) {
      throw new Errors.ParameterError(
        'operation[' + key + ']',
        'The operation is invalid. ' +
        'Create operations with Operation() constructor.');
    }
  if (operations.length <= 0)
    throw new Errors.ParameterError(
      'operations', 'The operations array is empty.');
  // check for null versions
  for (var key in operations) {
    if (operations[key].tableName === null)
      throw new Errors.ParameterError(
        'operation[' + key + ']',
        'The operation contains a null tableName.');
    if (operations[key].type === null)
      throw new Errors.ParameterError(
        'operation[' + key + ']',
        'The operation contains a null type.');
    if (operations[key].row === null)
      throw new Errors.ParameterError(
        'operation[' + key + ']',
        'The operation contains a null row.');

    if ((operations[key].type === Types.OperationType.PUT_IF_VERSION) ||
        (operations[key].type === Types.OperationType.DELETE_IF_VERSION))
      if (operations[key].matchVersion instanceof Types.Version) {
        if (operations[key].matchVersion.id !== undefined)
          operations[key].matchVersion = operations[key].matchVersion.id;
      } else
        throw new Errors.ParameterError(
          'operation[' + key + ']',
          'The operation contains an invalid Version.');

    //fix content
    operations[key].row =
    new ttypes.TRow({jsonRow: Stringify(operations[key].row)});
    if (operations[key].ttl !== undefined)
      if (operations[key].ttl)
         operations[key].row.ttl = operations[key].ttl;

  }

  if (typeof writeOptions === 'function') {
    callback = writeOptions;
    writeOptions = null;
  }

  // Verify that returnChoice doesn't exist or is null
  if (writeOptions) {
    if (writeOptions.returnChoice) {
      var error = new Errors.ParameterError('writeOptions.returnChoice',
        'returnChoice is not allowed on writeOptions for executeUpdates(). ' +
        'It must be used on each operation. ');
      throw error;
    }
  }
  writeOptions = checkWriteOptions(writeOptions, self.configuration);
  callback = callback || function () {};

  self.checkIsConnected();
  self.thriftClient.executeUpdates(operations, writeOptions,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'executeUpdates()');
        self.reportError(error, callback);
      } else {
        var finalResult = [];
        if (result) {
          for (var row in result)
            finalResult.push(new Types.UpdateResult(result[row]))
        }
        callback(null, finalResult);
      }
    });
};
/**
 * @callback executeUpdatesCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {UpdateResult} result A statementResult object with information
 *  about the process.
 */

/**
 * Returns a Readable Stream over the rows associated with a partial primary
 * key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {streamCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.tableStream = function tableStream(/*String*/ tableName,
                                                   /*Object*/ primaryKey,
                                                   /*Object*/ options,
                                                   /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, readOptions, direction;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.tableIterator(
    tableName,
    primaryKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'tableStream()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Stream(self, result));
    });

};

/**
 * Returns a Readable Stream over the keys associated with a partial primary
 * key.
 * @param {String} tableName The table name.
 * @param {Object} primaryKey The primary key for a table. It must be a
 *   complete primary key, with all fields set.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {streamCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.tableKeyStream =
function tableKeyStream(/*String*/ tableName,
                        /*Object*/ primaryKey,
                        /*Object*/ options,
                        /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(primaryKey, 'primaryKey');

  primaryKey = new ttypes.TRow({jsonRow: Stringify(primaryKey)});
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var fieldRange, includedTables, readOptions, direction;
  if (options) {
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.tableKeyIterator(
    tableName,
    primaryKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'tableKeyStream()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Stream(self, result));
    });
};

/**
 * Returns a Readable Stream over the rows associated with an index key. This
 * method requires an additional database read on the server side to get row
 * information for matching rows. Ancestor table rows for matching index rows
 * may be returned as well if specified in the getOptions parameter. Index
 * operations may not specify the return of child table rows.
 * @param {String} tableName The table name.
 * @param {String} indexName The index name.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { indexKey: null,
 *     fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {Object} options.indexKey The index key for the operation. It may be
 *   partial or complete. If the key has no fields set the entire index is
 *   matched.
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {streamCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.indexStream = function indexStream(/*String*/ tableName,
                                                   /*String*/ indexName,
                                                   /*Object*/ options,
                                                   /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(indexName, 'indexName');

  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var indexKey, fieldRange, includedTables, readOptions, direction;
  if (options) {
    indexKey = ('indexKey' in options) ?
      new ttypes.TRow({jsonRow: Stringify(options.indexKey)}) : null;
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.indexIterator(
    tableName,
    indexName,
    indexKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'indexStream()');
        self.reportError(error, callback);
      } else
        callback(null,
          new Stream(self, result));
    });
};

/**
 * Return a Readable Stream with the KeyPairs for matching rows associated with
 * an index key. The Stream returned only references information directly
 * available from the index. No extra fetch operations are performed.
 * Ancestor table keys for matching index keys may be returned as well if
 * specified in the getOptions parameter. Index operations may not specify the
 * return of child table keys.
 * @param {String} tableName The table name.
 * @param {String} indexName The index name.
 * @param {Object} [options] An object that includes the following
 *   parameters:
 *   { indexKey: null,
 *     fieldRange: null,
 *     includedTables: null,
 *     readOptions: null,
 *     direction: null }
 * @param {Object} options.indexKey The index key for the operation. It may be
 *   partial or complete. If the key has no fields set the entire index is
 *   matched.
 * @param {FieldRange} options.fieldRange The FieldRange to be used to restrict
 *   the range of the operation.
 * @param {Array} options.includedTables The list of tables to be included in
 *   an operation that returns multiple rows or keys.
 * @param {ReadOptions} options.readOptions Non-default options for the
 *   operation or null to get default behavior.
 * @param {Direction} options.direction The Direction for this operation. If
 *   the primary key contains a complete shard key both Direction.FORWARD and
 *   Direction.REVERSE are allowed.
 * @param {streamCallback} [callback] A function that is called when the process
 *   ends.
 */
Store.prototype.indexKeyStream =
function indexKeyStream(/*String*/ tableName,
                        /*String*/ indexName,
                        /*Object*/ options,
                        /*function*/ callback) {
  var self = this;

  Errors.missingParameter(tableName, 'tableName');
  Errors.missingParameter(indexName, 'indexName');

  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  var indexKey, fieldRange, includedTables, readOptions, direction;
  if (options) {
    indexKey = ('indexKey' in options) ?
      new ttypes.TRow({jsonRow: Stringify(options.indexKey)}) : null;
    fieldRange = ('fieldRange' in options) ? options.fieldRange : null;
    includedTables =
    ('includedTables' in options) ? options.includedTables : null;
    readOptions = ('readOptions' in options) ? options.readOptions : null;
    direction = ('direction' in options) ? options.direction : null;
  }
  readOptions = self._checkReadOptions(readOptions);
  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.indexKeyIterator(
    tableName,
    indexName,
    indexKey,
    fieldRange,
    includedTables,
    readOptions,
    direction,
    self.configuration.iteratorBufferSize,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'indexKeyStream()');
        self.reportError(error, callback);
      } else
        callback(null,
                 new Stream(self, result));
    });
};

/**
 * @callback streamCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {Readable} result A Stream object with the result of this operation.
 */

/**
 * Return a String with the version of the Proxy/Java
 * @param {ModuleInfo} moduleInfo This parameter indicates which module the
 * version is asked.
 * @param {versionCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.version =
function version(/*ModuleInfo*/ moduleInfo, /*funcion*/ callback) {
  var self = this;

  Errors.missingParameter(moduleInfo, 'moduleInfo');

  callback = callback || function () {
  };

  self.checkIsConnected();
  if (moduleInfo == Types.ModuleInfo.JS_DRIVER) {
    callback(null, pack.version);
  } else
    self.thriftClient.version(moduleInfo,
      function (err, result) {
        if (err) {
          var error = Errors.getProxyError(err, 'version()');
          self.reportError(error, callback);
        } else {
          callback(null, result);
        }
      });
};

/**
 * Return a String with the status of the Proxy/Java.
 * @param {ModuleInfo} moduleInfo This parameter indicates which module the
 * version is asked.
 * @param {versionCallback} [callback] A function that is called when the
 *   process ends.
 */
Store.prototype.status =
function status(/*ModuleInfo*/ moduleInfo, /*funcion*/ callback) {
  var self = this;

  Errors.missingParameter(moduleInfo, 'moduleInfo');

  callback = callback || function () {
  };

  self.checkIsConnected();
  self.thriftClient.status(moduleInfo,
    function (err, result) {
      if (err) {
        var error = Errors.getProxyError(err, 'status()');
        self.reportError(error, callback);
      } else
        callback(null, result);
    });
};

/**
 * Helper function to parse the results of a key-only iteration as a
 * row with metadata or a plain key pair.
 */
Store.prototype.parseKeyPair =
    function parseKeyPair(/*TRowWithMetadata*/ args) {
        return new Types.KeyPair(args);
    }

/**
 * @callback versionCallback
 * @param {Error} error The error returned by the operation, if any, null
 *  otherwise.
 * @param {String} result A String with the result of this operation.
 */

/**
 * Helper function to parse strings and convert them into Javascript objects,
 * use it instead of common JSON.parse when you use long integers (64 bits).
 * This function converts these long integers into Int64 objects.
 * @type {parse|exports}
 */
Store.prototype.parse = Parse;

/**
 * Helper function to convert Javascript objects into strings,
 * use it instead of common JSON.strings when you use Int64 objects.
 * This function converts these Int64 objects into correct long integer strings.
 * @type {parse|exports}
 */
Store.prototype.stringify = Stringify;

/**
 * Called when an error occurred
 * @event Store#error
 */

/**
 * Called when the Store is opened
 * @event Store#open
 */

/**
 * Called when the Store is closed
 * @event Store#close
 */