/*
* Copyright (C) 2007-2015 Hypertable, Inc.
*
* This file is part of Hypertable.
*
* Hypertable is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*
* Hypertable 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
'use strict';
var assert = require('assert');
var SerializedCellsFlag = require('./SerializedCellsFlag.js');
var SerializedCellsVersion = require('./SerializedCellsVersion.js');
var Timestamp = require('./Timestamp.js');
var Hypertable = require('./Client_types.js');
/**
* Used for encoding cells into a serialized cells buffer.
* @example
* var writer = new hypertable.SerializedCellsWriter(1024);
* var ts = new Timestamp( new Date(2015, 3, 15, 16, 15, 30) );
* writer.add('row1', 'column1', 'qaulifier1', ts, 'value1');
* writer.add('row2', 'column2', 'qaulifier2', ts, 'value2');
* writer.add('row3', 'column3', 'qaulifier3', ts, 'value3');
* ...
* @param {Number} size Size of serialized cells buffer to generate
* @param {Boolean} [grow=false] Grow buffer to accomodate added cells
* @class
* @classdesc Encodes cells into a serialized cells buffer.
* @memberof module:hypertable
*/
var SerializedCellsWriter = module.exports = function(size, grow) {
/**
* Serialized cells buffer.
* @name module:hypertable.SerializedCellsWriter#buffer
* @type Buffer
*/
/**
* Current encode offset within {@link module:hypertable.SerializedCellsWriter#buffer buffer}.
* @name module:hypertable.SerializedCellsWriter#offset
* @type Number
*/
if (size === undefined)
throw new TypeError('Missing \'size\' argument in SerializedCellsWriter constructor');
if (typeof size !== 'number')
throw new TypeError('\'size\' argument for SerializedCellsWriter constructor must be of type \'number\'');
if (grow !== undefined && typeof grow !== 'boolean')
throw new TypeError('\'grow\' argument for SerializedCellsWriter constructor must be of type \'boolean\'');
this.buffer = new Buffer(size);
this.offset = 0;
this.finalized = false;
this.grow = Boolean(grow);
this.previousRow = null;
};
SerializedCellsWriter.prototype = {};
SerializedCellsWriter.prototype.constructor = SerializedCellsWriter;
SerializedCellsWriter.prototype.growBy = function(length) {
if (!this.buffer)
this.buffer = new Buffer(length);
else {
var tmpBuffer = new Buffer(this.buffer.length+length);
this.buffer.copy(tmpBuffer, 0, 0, this.offset);
this.buffer = tmpBuffer;
}
};
/**
* Adds a cell to the serialized cells buffer.
* This method can be called with either a single {@link Cell} argument, or all of the cell
* components, for example:
* <pre>
* writer.add(cell);
* writer.add(row, column_family, column_qualifier, timestamp, value, cell_flag);
* </pre>
* @param cell|row {module:hypertable.Cell|String} Cell or row key
* @param [column_family] {String} Column family
* @param [column_qualifier] {String} Column qualifier
* @param [timestamp] {Timestamp|Int64} Timetamp
* @param [value] {Buffer} Value
* @param [cell_flag=255] {Number} Cell flag
*/
SerializedCellsWriter.prototype.add = function(row, column_family, column_qualifier, timestamp, value, cell_flag) {
var rowBuffer;
var columnFamilyBuffer;
var columnQualifierBuffer;
var valueBuffer;
if (row instanceof Cell) {
rowBuffer = new Buffer(row.key.row);
columnFamilyBuffer = row.key.column_family ? new Buffer(row.key.column_family) : undefined;
columnQualifierBuffer = row.key.column_qualifier ? new Buffer(row.key.column_qualifier) : undefined;
valueBuffer = row.value;
timestamp = row.key.timestamp;
cell_flag = row.key.flag;
}
else {
assert(typeof row === 'string',
'Parameter \'row\' must be of \'string\' type');
if (column_family)
assert(typeof column_family === 'string',
'Parameter \'column_family\' must be of \'string\' type');
if (column_qualifier)
assert(typeof column_qualifier === 'string',
'Parameter \'column_family\' must be of \'string\' type');
rowBuffer = new Buffer(row);
columnFamilyBuffer = column_family ? new Buffer(column_family) : undefined;
columnQualifierBuffer = column_qualifier ? new Buffer(column_qualifier) : undefined;
if (value) {
if (typeof value === 'string')
valueBuffer = new Buffer(value);
else
valueBuffer = value;
}
if (cell_flag === undefined)
cell_flag = Hypertable.KeyFlag.INSERT;
}
var needRow = true;
if (this.previousRow && rowBuffer.equals(this.previousRow))
needRow = false;
var length = 13;
if (columnFamilyBuffer)
length += columnFamilyBuffer.length;
if (columnQualifierBuffer)
length += columnQualifierBuffer.length;
if (valueBuffer)
length += valueBuffer.length;
if (this.offset === 0)
length += 4;
if (needRow)
length += rowBuffer.length;
var flag;
if (timestamp === undefined || timestamp == null || Timestamp.AUTO_ASSIGN.equals(timestamp))
flag = SerializedCellsFlag.AUTO_TIMESTAMP;
else if (!Timestamp.TIMESTAMP_NULL.equals(timestamp)) {
flag = SerializedCellsFlag.HAVE_TIMESTAMP;
length += 8;
}
// need to leave room for the termination byte
if (!this.buffer || length > this.buffer.length - this.offset) {
if (this.buffer && this.grow) {
this.growBy(length);
}
else {
if (this.offset !== 0)
return false;
this.buffer = new Buffer(length);
}
}
// version
if (this.offset === 0) {
this.buffer.writeUInt32LE(SerializedCellsVersion.SCVERSION, this.offset);
this.offset += 4;
}
// flag byte
this.buffer[this.offset] = flag;
this.offset++;
// timestamp
if (flag === SerializedCellsFlag.HAVE_TIMESTAMP) {
if (timestamp instanceof Timestamp)
timestamp.copyLE(this.buffer, this.offset);
else {
var tmpTimestamp = new Timestamp(timestamp);
tmpTimestamp.copyLE(this.buffer, this.offset);
}
this.offset += 8;
}
// revision
if (flag === SerializedCellsFlag.HAVE_REVISION &&
(flag & SerializedCellsFlag.REV_IS_TS) === 0) {
this.buffer.writeUInt32LE(0, this.offset);
this.offset += 4;
this.buffer.writeUInt32LE(0, this.offset);
this.offset += 4;
}
// row; only write it if it's not identical to the previous row
if (needRow) {
rowBuffer.copy(this.buffer, this.offset);
this.offset += rowBuffer.length;
this.previousRow = rowBuffer;
}
this.buffer[this.offset] = 0;
this.offset++;
if (columnFamilyBuffer) {
columnFamilyBuffer.copy(this.buffer, this.offset);
this.offset += columnFamilyBuffer.length;
}
this.buffer[this.offset] = 0;
this.offset++;
if (columnQualifierBuffer) {
columnQualifierBuffer.copy(this.buffer, this.offset);
this.offset += columnQualifierBuffer.length;
}
this.buffer[this.offset] = 0;
this.offset++;
if (valueBuffer) {
this.buffer.writeUInt32LE(valueBuffer.length, this.offset);
this.offset += 4;
valueBuffer.copy(this.buffer, this.offset);
this.offset += valueBuffer.length;
}
else {
this.buffer.writeUInt32LE(0, this.offset);
this.offset += 4;
}
this.buffer[this.offset] = cell_flag;
this.offset++;
return true;
}
SerializedCellsWriter.prototype.finalize = function(flag) {
if (!this.buffer)
this.buffer = new Buffer(5);
if (this.offset === 0) {
if (this.buffer.length < 5) {
assert(this.grow, 'No room for finalize flag in SerializedCellsWriter');
this.growBy(5);
}
this.buffer.writeUInt32LE(SerializedCellsVersion.SCVERSION, this.offset);
this.offset += 4;
}
flag = flag || 0;
this.buffer[this.offset] = SerializedCellsFlag.EOB | flag;
this.offset++;
this.finalized = true;
}
/**
* Gets serialized cells buffer. Finalizes the buffer and returns it sliced to
* the exact length.
* @return Serialized cells buffer
*/
SerializedCellsWriter.prototype.getBuffer = function() {
if (!this.finalized)
this.finalize();
return this.buffer.slice(0, this.offset);
}
/**
* Checks if serialized cells buffer is empty
* ({@link module:hypertable.SerializedCellsWriter#offset offset} is 0)
* @return <i>true</i> if buffer is empty, <i>false</i> otherwise.
*/
SerializedCellsWriter.prototype.empty = function() {
return this.offset === 0;
}
/**
* Clears serialized cells buffer. This method sets the object to
* uninitialized state, setting
* {@link module:hypertable.SerializedCellsWriter#offset offset} to 0, but keeping
* the underlying {@link module:hypertable.SerializedCellsWriter#buffer buffer}.
*/
SerializedCellsWriter.prototype.clear = function() {
this.offset = 0;
this.previousRow = undefined;
this.finalized = false;
}