/*
* 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 Int64 = require('node-int64');
var Hypertable = require('./Client_types.js');
/**
* Used for decoding cells from a serialized cells buffer.
* @example
* var reader = new hypertable.SerializedCellsReader(buffer);
* while (reader.next()) {
* var cell = reader.getCell();
* // do something with cell ...
* }
* @param {Buffer} buffer Buffer holding serialized cells
* @class
* @classdesc Decodes cells from a serialized cells buffer.
* @memberof module:hypertable
*/
var SerializedCellsReader = module.exports = function(buffer) {
/**
* Serialized cells buffer.
* @name module:hypertable.SerializedCellsReader#buffer
* @type Buffer
*/
/**
* Current decode offset within {@link module:hypertable.SerializedCellsReader#buffer buffer}.
* @name module:hypertable.SerializedCellsReader#offset
* @type Number
*/
if (buffer === undefined)
throw new TypeError('Missing \'buffer\' argument in SerializedCellsReader constructor');
if (!(buffer instanceof Buffer))
throw new TypeError('\'buffer\' argument for SerializedCellsReader constructor must be a Buffer');
this.buffer = buffer;
this.offset = 0;
this.previousRow = null;
this.flag = 0;
this.timestamp = null;
this.revision = null;
this.row = null;
this.columnFamily = null;
this.columnQualifier = null;
this.value = null;
this.cellFlag = 0;
var version = this.buffer.readUInt32LE(this.offset);
this.offset += 4;
if (version !== SerializedCellsVersion.SCVERSION)
throw new Error('Invalid buffer passed to SerializedCellsReader (expected version ' +
SerializedCellsVersion.SCVERSION + ', but got ' + version);
};
SerializedCellsReader.prototype = {};
SerializedCellsReader.prototype.constructor = SerializedCellsReader;
/**
* Extracts string from buffer. Advances offset to byte position after
* extracted string.
* @return {String} Extracted string
* @private
*/
SerializedCellsReader.prototype.extractStringBuffer = function() {
var startOffset = this.offset;
while (this.offset < this.buffer.length && this.buffer[this.offset] !== 0)
this.offset++;
if (this.offset == this.buffer.length)
throw new Error('Unterminated string encounterd in serialized cells buffer');
var strBuf;
if (this.offset > startOffset)
strBuf = this.buffer.slice(startOffset, this.offset);
this.offset++;
return strBuf;
};
/**
* Flips <code>length</code> bytes of {@link module:hypertable.SerializedCellsReader#buffer buffer}
* starting at {@link module:hypertable.SerializedCellsReader#offset offset}. For example:
* <pre>
* 12345678 -> 87654321
* </pre>
* @param {Number} length Length of bytes to flip
*/
SerializedCellsReader.prototype.flipBytes = function(length) {
for (var i=0; i<length/2; i++) {
var tmp = this.buffer[(this.offset+length)-i-1];
this.buffer[(this.offset+length)-i-1] = this.buffer[this.offset+i];
this.buffer[this.offset+i] = tmp;
}
};
/**
* Extracts next cell from {@link module:hypertable.SerializedCellsReader#buffer buffer}. Advances
* {@link module:hypertable.SerializedCellsReader#offset offset} to first byte past extracted cell.
* The extracted cell, or portion thereof, can be subsequently obtain with one
* of the following methods:
* <p/>
* <table cellpadding="3">
* <tr><td>{@link module:hypertable.SerializedCellsReader#getCell getCell()}</td><td>Cell</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getKey getKey()}</td><td>Key</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getRow getRow()}</td><td>Row</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getColumnFamily getColumnFamily()}</td><td>Column family</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getColumnQualifier getColumnQualifier()}</td><td>Column qualifier</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getTimestamp getColumnTimestamp()}</td><td>Timestamp</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getRevision getColumnRevision()}</td><td>Revision</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getValue getValue()}</td><td>Value</td></tr>
* <tr><td>{@link module:hypertable.SerializedCellsReader#getCellFlag getCellFlag()}</td><td>Cell flag</td></tr>
* </table>
* @return {Boolean} <i>true</i> if cell successfully extracted, <i>false</i> if
* end of buffer.
*/
SerializedCellsReader.prototype.next = function() {
if (this.offset >= this.buffer.length)
return false;
// flag byte
this.flag = this.buffer[this.offset];
this.offset++;
// Check for EOB
if (this.flag & SerializedCellsFlag.EOB) {
this.offset = this.buffer.length;
return false;
}
// Timestamp
if (this.flag & SerializedCellsFlag.HAVE_TIMESTAMP) {
this.flipBytes(8);
this.timestamp = new Int64(this.buffer, this.offset);
this.offset += 8;
}
// Revision
if (this.flag & SerializedCellsFlag.REV_IS_TS)
this.revision = this.timestamp;
else if (this.flag & SerializedCellsFlag.HAVE_REVISION) {
this.flipBytes(8);
this.revision = new Int64(this.buffer, this.offset);
this.offset += 8;
}
else
this.revision = null;
// Row
if (this.buffer[this.offset] === 0) {
assert(this.previousRow, 'Mal-formed serialized cells buffer');
this.row = this.previousRow;
this.offset++;
}
else {
this.row = this.previousRow = this.extractStringBuffer();
assert(this.row, 'Empty row key encountered in serialized cells buffer');
}
// Column family
this.columnFamily = this.extractStringBuffer();
assert(this.columnFamily, 'Empty row key encountered in serialized cells buffer');
// Column Qualifier
this.columnQualifier = this.extractStringBuffer();
// Value
var valueLength = this.buffer.readUInt32LE(this.offset);
this.offset += 4;
assert(this.offset + valueLength < this.buffer.length,
'Mal-formed value encountered in serialized cells buffer');
if (valueLength === 0)
this.value = null;
else {
this.value = this.buffer.slice(this.offset, this.offset+valueLength);
this.offset += valueLength;
}
// Cell flag
this.cellFlag = this.buffer[this.offset];
this.offset++;
if (this.cellFlag === Hypertable.KeyFlag.DELETE_ROW)
this.columnFamily = this.columnQualifier = null;
else if (this.cellFlag === Hypertable.KeyFlag.DELETE_CF)
this.columnQualifier = null;
return true;
};
/**
* Returns row of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {String} Row of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getRow = function() {
assert(this.row,
'SerializedCellsReader.next() must first be called before calling this method');
return this.row.toString();
};
/**
* Returns column family of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {String} Column family of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getColumnFamily = function() {
if (!this.columnFamily)
return '';
return this.columnFamily.toString();
};
/**
* Returns column qualifier of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {String} Column qualifier of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getColumnQualifier = function() {
if (!this.columnQualifier)
return '';
return this.columnQualifier.toString();
};
/**
* Returns timestamp of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {Int64} Timestamp of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getTimestamp = function() {
return this.timestamp;
};
/**
* Returns revision of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {Int64} Revision of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getRevision = function() {
return this.revision;
};
/**
* Returns value of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {Buffer} Value of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getValue = function() {
return this.value;
};
/**
* Returns cell flag of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {Number} Cell flag of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getCellFlag = function() {
return this.cellFlag;
};
/**
* Returns key of cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {module:hypertable.Key} Key of cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getKey = function() {
return new Hypertable.Key({row: this.getRow(),
column_family: this.getColumnFamily(),
column_qualifier: this.getColumnQualifier(),
timestamp: this.timestamp,
revision: this.revision,
flag: this.cellFlag});
};
/**
* Returns cell extracted from last call to {@link SerializedCellsReader#next next}.
* @return {module:hypertable.Cell} Cell extracted from last call to {@link SerializedCellsReader#next next}.
*/
SerializedCellsReader.prototype.getCell = function() {
return new Hypertable.Cell({key: this.getKey(), value: this.value});
};