Source: sql/Row.js

/*
 * Copyright 2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var Utils = require('../utils.js');

var gKernelP;

/*
 * NOTE: the following have not been implemented as they do not make sense for JavaScript
 *
 * <K,V> java.util.Map<K,V>    getJavaMap(int i)
 * Returns the value at position i of array type as a Map.
 * <T> java.util.List<T>   getList(int i)
 * Returns the value at position i of array type as List.
 * <K,V> scala.collection.Map<K,V> getMap(int i)
 * Returns the value at position i of map type as a Scala Map.
 * <T> scala.collection.Seq<T> getSeq(int i)
 * Returns the value at position i of array type as a Scala Seq.
 * <T> scala.collection.immutable.Map<java.lang.String,T>  getValuesMap(scala.collection.Seq<java.lang.String> fieldNames)
 * Returns a Map(name -> value) for the requested fieldNames
 * scala.collection.Seq<java.lang.Object>  toSeq()
 * Return a Scala Seq representing the row.
 *
 * NOTE: the following are being ignored as they also don't make sense for JavaScript (see ./types/DataTypes.js)
 *
 * byte getByte(int i)
 * decimal getDecimal(int i)
 * long getLong(int i)
 * short getShort(int i)
 */

// Local resolve functions to parse results of various types
function _resolveBool(result, resolve, reject) {
  // parse stringified result here
  resolve(JSON.parse(result));
}

function _resolveFloat(result, resolve, reject) {
  resolve(parseFloat(result));
}

function _resolveInt(result, resolve, reject) {
  resolve(parseInt(result));
}

function _resolveObj(result, resolve, reject) {
  // have to parse if number or bool
  resolve(isFinite(result) ? new Number(result).valueOf() : isBool(result) ? JSON.parse(result) : result);
}

function isBool (val) {
  return val === 'true' || val === 'false';
}

function deepEquals(obj1, obj2) {
  var isEqual = false;

  if (typeof(obj1) === typeof(obj2)) {
    if (Array.isArray(obj1)) {
      if (obj1.length == obj2.length) {
        isEqual = obj1.some(function(val, index) {
          return val === obj2[index];
        });
      }
    } else if (obj1 !== null && typeof(obj1) === 'object') {
      var keys1 = [], keys2 = [], values1 = [], values2 = [];

      for (var key in obj1) {
        keys1.push(key);
        values1.push(obj1[key]);
      }

      for (var key in obj2) {
        keys2.push(key);
        values2.push(obj2[key]);
      }

      if (deepEquals(keys1, keys2) && deepEquals(values1, values2)) {
        isEqual = true;
      }
    } else {
      isEqual = (obj1 === obj2);
    }
  }

  return isEqual;
}

function areTwoRowsEqual(row1, row2) {
  var isEqual = row1 instanceof Row && row2 instanceof Row && deepEquals(row1._schema, row2._schema) && deepEquals(row1._values, row2._values);

  return isEqual;
}

function getByIndex(row, index) {
  var obj = {value: null, schema: null};

  if (row._values) {
    if (index < row._values.length) {
      obj.value = row._values[index];
      obj.schema = row._schema.fields[index];
    } else {
      throw new Error('Index ' + index + ' is out of bounds');
    }
  }

  return obj;
}

/**
 * @constructor
 * @memberof module:eclairjs/sql
 * @classdesc Represents one row of output from a relational operator. Allows both generic access by ordinal, which will incur boxing overhead for primitives, as well as native primitive access.
 * It is invalid to use the native primitive interface to retrieve a value that is null, instead a user must check isNullAt before attempting to retrieve a value that might be null.
 * To create a new Row, use RowFactory.create()
 */
function Row() {
  if (arguments && arguments.length == 2 && arguments[0] instanceof Promise && arguments[1] instanceof Promise) {
    this.kernelP = arguments[0];
    this.refIdP = arguments[1];
  } else {
    // local copy of data
    this._values = arguments[0];
    this._schema = arguments[1];
    this._eclairLocal = true;
  }
}

/**
 * Returns true if there are any NULL values in this row.
 * @returns {boolean}
 */
Row.prototype.anyNull = function() {
  var result = false;

  if (this._values) {
    result = this._values.some(function(item) {
      return item === null;
    });
  }

  return result;
};

/**
 * Returns the value at position index.
 * @param index
 * @returns {object}
 */
Row.prototype.apply = function(index) {
  return getByIndex(this, index).value;
};

/**
 * Make a copy of the current Row object
 * @returns {module:eclairjs/sql.Row}
 */
Row.prototype.copy = function() {
  return new Row(this._values, this._schema);
};

/**
 * compares object obj to this Row object
 * @param {object} obj
 * @returns {boolean}
 */
Row.prototype.equals = function(obj) {
  return areTwoRowsEqual(this, obj);
};

Row.prototype._fieldIndex = function(name) {
  var index = -1;

  if (this._schema) {
    this._schema.fields.some(function(field, i) {
      if (field.name === name) {
        index = i;
        return true;
      } else {
        return false;
      }
    });
  }

  if (index >= 0) {
    return index;
  } else {
    throw new Error('field "'+name+'" does not exist');
  }
};

/**
 * Returns the index of a given field name.
 * @param {string} name
 * @returns {integer}
 */
Row.prototype.fieldIndex = function(name) {
  if (this._schema) {
    return this._fieldIndex(name);
  } else {
    throw new Error('fieldIndex on a Row without a schema is undefined');
  }
};

/**
 * Returns the value at position index.
 * @param {integer} index
 * @returns {object}
 */
Row.prototype.get = function(index) {
  return getByIndex(this, index).value;
};

/**
 * Returns the value at position index as a primitive boolean.
 * @param {integer} index
 * @returns {boolean}
 */
Row.prototype.getBoolean = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'boolean') {
    return item.value;
  } else {
    throw new Error('the type for index '+index+' is not a Boolean');
  }
};

/**
 * Returns the value at position idex as a primitive byte.
 * @param {integer} index
 * @returns {byte}
 * @ignore
 */
/* Not applicable to JavaScript
Row.prototype.getByte = function(index) {

  throw {name:'NotImplementedException', message:'The method is not implemented for JavaScript'};
};
*/

/**
 * Returns the value at position index of type as Date.
 * @param {integer} index
 * @returns {Date}
 */
Row.prototype.getDate = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'date') {
    return new Date(item.value);
  } else {
    throw new Error('the type for index '+index+' is not a Date');
  }
};

/**
 * Returns the value at position index of type as decimal.
 * @param {integer} index
 * @returns {Promise.<decimal>}
 * @ignore
 */
/* Not applicable to JavaScript
Row.prototype.getDecimal = function(index) {

  throw {name:'NotImplementedException', message:'The method is not implemented for JavaScript'};
};
*/

/**
 * Returns the value at position index of type as double.
 * @param {integer} index
 * @returns {double}
 */
Row.prototype.getDouble = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'double') {
    return item.value;
  } else {
    throw new Error('the type for index '+index+' is not a Double');
  }
};

/**
 * Returns the value at position index of type as float.
 * @param {integer} index
 * @returns {float}
 */
Row.prototype.getFloat = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'double') {
    return item.value;
  } else {
    throw new Error('the type for index '+index+' is not a Float');
  }
};

/**
 * Returns the value at position index of type as integer.
 * @param {integer} index
 * @returns {integer}
 */
Row.prototype.getInt = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'integer') {
    return item.value;
  } else {
    throw new Error('the type for index '+index+' is not a Integer');
  }
};

/**
 * Returns the value at position index of type as long.
 * @param {integer} index
 * @returns {long}
 * @ignore
 */
/* Not applicable to JavaScript
Row.prototype.getLong = function(index) {

  throw {name:'NotImplementedException', message:'The method is not implemented for JavaScript'};
};
*/

/**
 * Returns the value at position index of type as short.
 * @param {integer} index
 * @returns {short}
 * @ignore
 */
/* Not applicable to JavaScript
Row.prototype.getShort = function(index) {

  throw {name:'NotImplementedException', message:'The method is not implemented for JavaScript'};
};
*/

/**
 * Returns the value at position index of type as String.
 * @param {integer} index
 * @returns {String}
 */
Row.prototype.getString = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'string') {
    return item.value;
  } else {
    throw new Error('the type for index '+index+' is not a String');
  }
};

/**
 * Returns the value at position index of struct type as a Row object.
 * @param {integer} index
 * @returns {module:eclairjs/sql.Row}
 */
Row.prototype.getStruct = function(index) {
  throw "not implemented by ElairJS";
/*
  var args = {
    target: this,
    method: 'getStruct',
    args: Utils.wrapArguments(arguments),
    returnType: Row
  };

  return Utils.generate(args);
*/
};

/**
 * Returns the value at position index of date type as Date.
 * @param {integer} index
 * @returns {Date}
 */
Row.prototype.getTimestamp = function(index) {
  var item = getByIndex(this, index);

  if (item.schema.dataType === 'timestamp') {
    return new Date(item.value);
  } else {
    throw new Error('the type for index '+index+' is not a Timestamp');
  }
};

/**
 * Checks whether the value at position index is null.
 * @param {integer} index
 * @returns {boolean}
 */
Row.prototype.isNullAt = function(index) {
  return (getByIndex(this, index).value === null);
};

/**
 * Number of elements in the Row.
 * @returns {Promise.<integer>}
 */
Row.prototype.length = function() {
  return this._values.length;
};

/**
 * Displays all elements of this traversable or iterator in a string using start, end, and separator strings.
 * @param {string} [separator]
 * @param {string} [start] start will be ignored if end parameter is not specified
 * @param {string} [end] Required if start specified
 * @returns {Promise.<string>}
 */
Row.prototype.mkString = function() {
  var str = '';
  var args = arguments;

  if (args.length == 3) {
    str += args[0];
  }

  this._values.forEach(function (value, i, values) {
    str += value;

    if (i < values.length - 1) {
      if (args.length == 1) {
        str += args[0];
      } else if (args.length == 3) {
        str += args[1];
      }
    }
  });

  if (args.length == 3) {
    str += arguments[2];
  }

  return str;
};

/**
 * Schema for the row.
 * @returns {module:eclairjs/sql/types.StructType}
 */
Row.prototype.schema = function() {
  var StructType = require('./types/StructType.js')(this.kernelP);

  var StructField = require('./types/StructField.js')(this.kernelP);
  var Metadata = require('./types/Metadata.js')(this.kernelP);
  var DataTypes = require('./types/DataTypes.js')(this.kernelP);

  var fields = [];

  this._schema.fields.forEach(function (field) {
    var dt;

    // TODO: make this easier, perhaps a method in datatypes?
    switch (field.dataType) {
      case 'boolean':
        dt = DataTypes.BooleanType;
        break;

      case 'date':
        dt = DataTypes.DateType;
        break;

      case 'double':
        dt = DataTypes.DoubleType;
        break;

      case 'float':
        dt = DataTypes.FloatType;
        break;

      case 'integer':
        dt = DataTypes.IntegerType;
        break;

      case 'timestamp':
        dt = DataTypes.TimestampType;
        break;

      case 'string':
        dt = DataTypes.StringType;
        break;
    }

    fields.push(new StructField(field.name, dt, field.nullable, Metadata.empty()));
  });

  return new StructType(fields);
};

/**
 * Number of elements in the Row.
 * @returns {integer}
 */
Row.prototype.size = function() {
  return this._values.length;
};

Row.prototype.toJSON = function() {
  if (this._schema) {
    var result = {};
    var myThis = this;
    this._schema.fields.forEach(function(field, i) {
      result[field.name] = myThis._values[i];
    }.bind(this));

    return result;
  } else {
    return this._values;
  }
};

Row.prototype._generateRemote = function() {
  var RowFactory = require('./RowFactory')(gKernelP);

  return RowFactory.createRemote(this._values);
};

Row.moduleLocation = '/sql/Row';

module.exports = function(kernelP) {
  gKernelP = kernelP;

  return Row;
};