You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
583 lines
18 KiB
JavaScript
583 lines
18 KiB
JavaScript
2 years ago
|
/*
|
||
|
* Copyright DataStax, Inc.
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
const util = require('util');
|
||
|
const { Transform, Writable } = require('stream');
|
||
|
|
||
|
const types = require('./types');
|
||
|
const utils = require('./utils');
|
||
|
const errors = require('./errors');
|
||
|
const { FrameHeader } = types;
|
||
|
const { FrameReader } = require('./readers');
|
||
|
|
||
|
/**
|
||
|
* Transforms chunks, emits data objects {header, chunk}
|
||
|
* @param options Stream options
|
||
|
* @extends Transform
|
||
|
*/
|
||
|
function Protocol (options) {
|
||
|
Transform.call(this, options);
|
||
|
this.header = null;
|
||
|
this.bodyLength = 0;
|
||
|
this.clearHeaderChunks();
|
||
|
this.version = 0;
|
||
|
this.headerSize = 0;
|
||
|
}
|
||
|
|
||
|
util.inherits(Protocol, Transform);
|
||
|
|
||
|
Protocol.prototype._transform = function (chunk, encoding, callback) {
|
||
|
let error = null;
|
||
|
try {
|
||
|
this.readItems(chunk);
|
||
|
}
|
||
|
catch (err) {
|
||
|
error = err;
|
||
|
}
|
||
|
callback(error);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Parses the chunk into frames (header and body).
|
||
|
* Emits (push) complete frames or frames with incomplete bodies. Following chunks containing the rest of the body will
|
||
|
* be emitted using the same frame.
|
||
|
* It buffers incomplete headers.
|
||
|
* @param {Buffer} chunk
|
||
|
*/
|
||
|
Protocol.prototype.readItems = function (chunk) {
|
||
|
if (!chunk || chunk.length === 0) {
|
||
|
return;
|
||
|
}
|
||
|
if (this.version === 0) {
|
||
|
//The server replies the first message with the max protocol version supported
|
||
|
this.version = FrameHeader.getProtocolVersion(chunk);
|
||
|
this.headerSize = FrameHeader.size(this.version);
|
||
|
}
|
||
|
let offset = 0;
|
||
|
let currentHeader = this.header;
|
||
|
this.header = null;
|
||
|
if (this.headerChunks.byteLength !== 0) {
|
||
|
//incomplete header was buffered try to read the header from the buffered chunks
|
||
|
this.headerChunks.parts.push(chunk);
|
||
|
if (this.headerChunks.byteLength + chunk.length < this.headerSize) {
|
||
|
this.headerChunks.byteLength += chunk.length;
|
||
|
return;
|
||
|
}
|
||
|
currentHeader = FrameHeader.fromBuffer(Buffer.concat(this.headerChunks.parts, this.headerSize));
|
||
|
offset = this.headerSize - this.headerChunks.byteLength;
|
||
|
this.clearHeaderChunks();
|
||
|
}
|
||
|
const items = [];
|
||
|
while (true) {
|
||
|
if (!currentHeader) {
|
||
|
if (this.headerSize > chunk.length - offset) {
|
||
|
if (chunk.length - offset <= 0) {
|
||
|
break;
|
||
|
}
|
||
|
//the header is incomplete, buffer it until the next chunk
|
||
|
const headerPart = chunk.slice(offset, chunk.length);
|
||
|
this.headerChunks.parts.push(headerPart);
|
||
|
this.headerChunks.byteLength = headerPart.length;
|
||
|
break;
|
||
|
}
|
||
|
//read header
|
||
|
currentHeader = FrameHeader.fromBuffer(chunk, offset);
|
||
|
offset += this.headerSize;
|
||
|
}
|
||
|
//parse body
|
||
|
const remaining = chunk.length - offset;
|
||
|
if (currentHeader.bodyLength <= remaining + this.bodyLength) {
|
||
|
items.push({ header: currentHeader, chunk: chunk, offset: offset, frameEnded: true });
|
||
|
offset += currentHeader.bodyLength - this.bodyLength;
|
||
|
//reset the body length
|
||
|
this.bodyLength = 0;
|
||
|
}
|
||
|
else if (remaining >= 0) {
|
||
|
//the body is not fully contained in this chunk
|
||
|
//will continue later
|
||
|
this.header = currentHeader;
|
||
|
this.bodyLength += remaining;
|
||
|
if (remaining > 0) {
|
||
|
//emit if there is at least a byte to emit
|
||
|
items.push({ header: currentHeader, chunk: chunk, offset: offset, frameEnded: false });
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
currentHeader = null;
|
||
|
}
|
||
|
for (let i = 0; i < items.length; i++) {
|
||
|
this.push(items[i]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Protocol.prototype.clearHeaderChunks = function () {
|
||
|
this.headerChunks = { byteLength: 0, parts: [] };
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* A stream that gets reads header + body chunks and transforms them into header + (row | error)
|
||
|
* @param {Object} streamOptions Node.js Stream options
|
||
|
* @param {Encoder} encoder Encoder instance for the parser to use
|
||
|
* @extends Transform
|
||
|
*/
|
||
|
function Parser (streamOptions, encoder) {
|
||
|
Transform.call(this, streamOptions);
|
||
|
//frames that are streaming, indexed by id
|
||
|
this.frames = {};
|
||
|
this.encoder = encoder;
|
||
|
}
|
||
|
|
||
|
util.inherits(Parser, Transform);
|
||
|
|
||
|
Parser.prototype._transform = function (item, encoding, callback) {
|
||
|
const frameInfo = this.frameState(item);
|
||
|
|
||
|
let error = null;
|
||
|
try {
|
||
|
this.parseBody(frameInfo, item);
|
||
|
}
|
||
|
catch (err) {
|
||
|
error = err;
|
||
|
}
|
||
|
callback(error);
|
||
|
|
||
|
if (item.frameEnded) {
|
||
|
if (frameInfo.cellBuffer) {
|
||
|
//Frame was being streamed but an error force it to buffer the result
|
||
|
this.push({
|
||
|
header: frameInfo.header,
|
||
|
error: new errors.DriverInternalError('There was an problem while parsing streaming frame, opcode ' +
|
||
|
frameInfo.header.opcode)
|
||
|
});
|
||
|
}
|
||
|
//all the parsing finished and it was streamed down
|
||
|
//emit an item that signals it
|
||
|
this.push({ header: frameInfo.header, frameEnded: true});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @param frameInfo
|
||
|
* @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
|
||
|
*/
|
||
|
Parser.prototype.parseBody = function (frameInfo, item) {
|
||
|
frameInfo.isStreaming = frameInfo.byRow && item.header.opcode === types.opcodes.result;
|
||
|
if (!this.handleFrameBuffers(frameInfo, item)) {
|
||
|
// Frame isn't complete and we are not streaming the frame
|
||
|
return;
|
||
|
}
|
||
|
const reader = new FrameReader(item.header, item.chunk, item.offset);
|
||
|
// Check that flags have not been parsed yet for this frame
|
||
|
if (frameInfo.flagsInfo === undefined) {
|
||
|
const originalOffset = reader.offset;
|
||
|
try {
|
||
|
frameInfo.flagsInfo = reader.readFlagsInfo();
|
||
|
}
|
||
|
catch (e) {
|
||
|
return this.handleParsingError(e, frameInfo, reader, originalOffset);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//All the body for most operations is already buffered at this stage
|
||
|
//Except for RESULT
|
||
|
switch (item.header.opcode) {
|
||
|
case types.opcodes.result:
|
||
|
return this.parseResult(frameInfo, reader);
|
||
|
case types.opcodes.ready:
|
||
|
case types.opcodes.authSuccess:
|
||
|
return this.push({ header: frameInfo.header, ready: true });
|
||
|
case types.opcodes.authChallenge:
|
||
|
return this.push({ header: frameInfo.header, authChallenge: true, token: reader.readBytes()});
|
||
|
case types.opcodes.authenticate:
|
||
|
return this.push({ header: frameInfo.header, mustAuthenticate: true, authenticatorName: reader.readString()});
|
||
|
case types.opcodes.error:
|
||
|
return this.push({ header: frameInfo.header, error: reader.readError()});
|
||
|
case types.opcodes.supported:
|
||
|
return this.push({ header: frameInfo.header, supported: reader.readStringMultiMap()});
|
||
|
case types.opcodes.event:
|
||
|
return this.push({ header: frameInfo.header, event: reader.readEvent()});
|
||
|
default:
|
||
|
return this.push({ header: frameInfo.header, error: new Error('Received invalid opcode: ' + item.header.opcode) });
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Buffers if needed and returns true if it has all the necessary data to continue parsing the frame.
|
||
|
* @param frameInfo
|
||
|
* @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
|
||
|
* @returns {Boolean}
|
||
|
*/
|
||
|
Parser.prototype.handleFrameBuffers = function (frameInfo, item) {
|
||
|
if (!frameInfo.isStreaming) {
|
||
|
// Handle buffering for complete frame bodies
|
||
|
const currentLength = (frameInfo.bufferLength || 0) + item.chunk.length - item.offset;
|
||
|
if (currentLength < item.header.bodyLength) {
|
||
|
//buffer until the frame is completed
|
||
|
this.addFrameBuffer(frameInfo, item);
|
||
|
return false;
|
||
|
}
|
||
|
//We have received the full frame body
|
||
|
if (frameInfo.buffers) {
|
||
|
item.chunk = this.getFrameBuffer(frameInfo, item);
|
||
|
item.offset = 0;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
if (frameInfo.cellBuffer) {
|
||
|
// Handle buffering for frame cells (row cells or metadata cells)
|
||
|
if (item.offset !== 0) {
|
||
|
throw new errors.DriverInternalError('Following chunks can not have an offset greater than zero');
|
||
|
}
|
||
|
frameInfo.cellBuffer.parts.push(item.chunk);
|
||
|
if (!frameInfo.cellBuffer.expectedLength) {
|
||
|
//Its a buffer outside a row cell (metadata or other)
|
||
|
if (frameInfo.cellBuffer.parts.length !== 2) {
|
||
|
throw new errors.DriverInternalError('Buffer for streaming frame can not contain more than 1 item');
|
||
|
}
|
||
|
item.chunk = Buffer.concat(frameInfo.cellBuffer.parts, frameInfo.cellBuffer.byteLength + item.chunk.length);
|
||
|
frameInfo.cellBuffer = null;
|
||
|
return true;
|
||
|
}
|
||
|
if (frameInfo.cellBuffer.expectedLength > frameInfo.cellBuffer.byteLength + item.chunk.length) {
|
||
|
//We still haven't got the cell data
|
||
|
frameInfo.cellBuffer.byteLength += item.chunk.length;
|
||
|
return false;
|
||
|
}
|
||
|
item.chunk = Buffer.concat(frameInfo.cellBuffer.parts, frameInfo.cellBuffer.byteLength + item.chunk.length);
|
||
|
frameInfo.cellBuffer = null;
|
||
|
}
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Adds this chunk to the frame buffers.
|
||
|
* @param frameInfo
|
||
|
* @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
|
||
|
*/
|
||
|
Parser.prototype.addFrameBuffer = function (frameInfo, item) {
|
||
|
if (!frameInfo.buffers) {
|
||
|
frameInfo.buffers = [ item.chunk.slice(item.offset) ];
|
||
|
frameInfo.bufferLength = item.chunk.length - item.offset;
|
||
|
return;
|
||
|
}
|
||
|
if (item.offset > 0) {
|
||
|
throw new errors.DriverInternalError('Following chunks can not have an offset greater than zero');
|
||
|
}
|
||
|
frameInfo.buffers.push(item.chunk);
|
||
|
frameInfo.bufferLength += item.chunk.length;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Adds the last chunk and concatenates the frame buffers
|
||
|
* @param frameInfo
|
||
|
* @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
|
||
|
*/
|
||
|
Parser.prototype.getFrameBuffer = function (frameInfo, item) {
|
||
|
frameInfo.buffers.push(item.chunk);
|
||
|
const result = Buffer.concat(frameInfo.buffers, frameInfo.bodyLength);
|
||
|
frameInfo.buffers = null;
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Tries to read the result in the body of a message
|
||
|
* @param frameInfo Frame information, header / metadata
|
||
|
* @param {FrameReader} reader
|
||
|
*/
|
||
|
Parser.prototype.parseResult = function (frameInfo, reader) {
|
||
|
let result;
|
||
|
// As we might be streaming and the frame buffer might not be complete,
|
||
|
// read the metadata and different types of result values in a try-catch.
|
||
|
// Store the reader position
|
||
|
const originalOffset = reader.offset;
|
||
|
try {
|
||
|
if (!frameInfo.meta) {
|
||
|
frameInfo.kind = reader.readInt();
|
||
|
// Spec 4.2.5
|
||
|
switch (frameInfo.kind) {
|
||
|
case types.resultKind.voidResult:
|
||
|
result = { header: frameInfo.header, flags: frameInfo.flagsInfo };
|
||
|
break;
|
||
|
case types.resultKind.rows:
|
||
|
// Parse the rows metadata, the rest of the response is going to be parsed afterwards
|
||
|
frameInfo.meta = reader.readMetadata(frameInfo.kind);
|
||
|
break;
|
||
|
case types.resultKind.setKeyspace:
|
||
|
result = { header: frameInfo.header, keyspaceSet: reader.readString(), flags: frameInfo.flagsInfo };
|
||
|
break;
|
||
|
case types.resultKind.prepared:
|
||
|
{
|
||
|
const preparedId = utils.copyBuffer(reader.readShortBytes());
|
||
|
frameInfo.meta = reader.readMetadata(frameInfo.kind);
|
||
|
result = { header: frameInfo.header, id: preparedId, meta: frameInfo.meta, flags: frameInfo.flagsInfo };
|
||
|
break;
|
||
|
}
|
||
|
case types.resultKind.schemaChange:
|
||
|
result = { header: frameInfo.header, schemaChange: reader.parseSchemaChange(), flags: frameInfo.flagsInfo };
|
||
|
break;
|
||
|
default:
|
||
|
throw errors.DriverInternalError('Unexpected result kind: ' + frameInfo.kind);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (e) {
|
||
|
return this.handleParsingError(e, frameInfo, reader, originalOffset);
|
||
|
}
|
||
|
if (result) {
|
||
|
if (frameInfo.emitted) {
|
||
|
// It may contain additional metadata and info that it's not being parsed
|
||
|
return;
|
||
|
}
|
||
|
frameInfo.emitted = true;
|
||
|
return this.push(result);
|
||
|
}
|
||
|
if (reader.remainingLength() > 0) {
|
||
|
this.parseRows(frameInfo, reader);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @param frameInfo
|
||
|
* @param {FrameReader} reader
|
||
|
*/
|
||
|
Parser.prototype.parseRows = function (frameInfo, reader) {
|
||
|
if (frameInfo.parsingError) {
|
||
|
//No more processing on this frame
|
||
|
return;
|
||
|
}
|
||
|
if (frameInfo.rowLength === undefined) {
|
||
|
try {
|
||
|
frameInfo.rowLength = reader.readInt();
|
||
|
}
|
||
|
catch (e) {
|
||
|
return this.handleParsingError(e, frameInfo, reader);
|
||
|
}
|
||
|
}
|
||
|
if (frameInfo.rowLength === 0) {
|
||
|
return this.push({
|
||
|
header: frameInfo.header,
|
||
|
result: { rows: utils.emptyArray, meta: frameInfo.meta, flags: frameInfo.flagsInfo }
|
||
|
});
|
||
|
}
|
||
|
const meta = frameInfo.meta;
|
||
|
frameInfo.rowIndex = frameInfo.rowIndex || 0;
|
||
|
for (let i = frameInfo.rowIndex; i < frameInfo.rowLength; i++) {
|
||
|
const rowOffset = reader.offset;
|
||
|
const row = new types.Row(meta.columns);
|
||
|
let cellBuffer;
|
||
|
for (let j = 0; j < meta.columns.length; j++ ) {
|
||
|
const c = meta.columns[j];
|
||
|
try {
|
||
|
cellBuffer = reader.readBytes();
|
||
|
}
|
||
|
catch (e) {
|
||
|
return this.handleParsingError(e, frameInfo, reader, rowOffset, i);
|
||
|
}
|
||
|
try {
|
||
|
row[c.name] = this.encoder.decode(cellBuffer, c.type);
|
||
|
}
|
||
|
catch (e) {
|
||
|
//Something went wrong while decoding, we are not going to be able to recover
|
||
|
return this.handleParsingError(e, frameInfo, null);
|
||
|
}
|
||
|
}
|
||
|
this.push({
|
||
|
header: frameInfo.header,
|
||
|
row: row,
|
||
|
meta: frameInfo.meta,
|
||
|
byRow: frameInfo.byRow,
|
||
|
length: frameInfo.rowLength,
|
||
|
flags: frameInfo.flagsInfo
|
||
|
});
|
||
|
}
|
||
|
if (frameInfo.byRow) {
|
||
|
// Use an event item to identify that all the streaming rows have finished processing
|
||
|
this.push({
|
||
|
header: frameInfo.header,
|
||
|
byRowCompleted: true,
|
||
|
meta: frameInfo.meta,
|
||
|
length: frameInfo.rowLength,
|
||
|
flags: frameInfo.flagsInfo
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Sets parser options (ie: how to yield the results as they are parsed)
|
||
|
* @param {Number} id Id of the stream
|
||
|
* @param options
|
||
|
*/
|
||
|
Parser.prototype.setOptions = function (id, options) {
|
||
|
if (this.frames[id.toString()]) {
|
||
|
throw new types.DriverError('There was already state for this frame');
|
||
|
}
|
||
|
this.frames[id.toString()] = options;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Manually clears the frame options.
|
||
|
* This class already clears the provided options when the frame ends, so it's usually not required to invoke this
|
||
|
* method.
|
||
|
* When manually setting the options for continuous paging, it's possible that the frame options are set while
|
||
|
* it's being cancelled.
|
||
|
* @param {Number} id The streamId
|
||
|
*/
|
||
|
Parser.prototype.clearOptions = function (id) {
|
||
|
delete this.frames[id.toString()];
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Gets the frame info from the internal state.
|
||
|
* In case it is not there, it creates it.
|
||
|
* In case the frame ended
|
||
|
*/
|
||
|
Parser.prototype.frameState = function (item) {
|
||
|
let frameInfo = this.frames[item.header.streamId];
|
||
|
if (!frameInfo) {
|
||
|
frameInfo = {};
|
||
|
if (!item.frameEnded) {
|
||
|
//store it in the frames
|
||
|
this.frames[item.header.streamId] = frameInfo;
|
||
|
}
|
||
|
}
|
||
|
else if (item.frameEnded) {
|
||
|
//if it was already stored, remove it
|
||
|
delete this.frames[item.header.streamId];
|
||
|
}
|
||
|
frameInfo.header = item.header;
|
||
|
return frameInfo;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Handles parsing error: pushing an error if its unexpected or buffer the cell if its streaming
|
||
|
* @param {Error} e
|
||
|
* @param frameInfo
|
||
|
* @param {FrameReader} reader
|
||
|
* @param {Number} [originalOffset]
|
||
|
* @param {Number} [rowIndex]
|
||
|
*/
|
||
|
Parser.prototype.handleParsingError = function (e, frameInfo, reader, originalOffset, rowIndex) {
|
||
|
if (reader && frameInfo.isStreaming && (e instanceof RangeError)) {
|
||
|
//A controlled error, buffer from offset and move on
|
||
|
return this.bufferResultCell(frameInfo, reader, originalOffset, rowIndex, e.expectedLength);
|
||
|
}
|
||
|
frameInfo.parsingError = true;
|
||
|
frameInfo.cellBuffer = null;
|
||
|
this.push({ header: frameInfo.header, error: e });
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* When streaming, it buffers data since originalOffset.
|
||
|
* @param frameInfo
|
||
|
* @param {FrameReader} reader
|
||
|
* @param {Number} [originalOffset]
|
||
|
* @param {Number} [rowIndex]
|
||
|
* @param {Number} [expectedLength]
|
||
|
*/
|
||
|
Parser.prototype.bufferResultCell = function (frameInfo, reader, originalOffset, rowIndex, expectedLength) {
|
||
|
if (!originalOffset && originalOffset !== 0) {
|
||
|
originalOffset = reader.offset;
|
||
|
}
|
||
|
frameInfo.rowIndex = rowIndex;
|
||
|
const buffer = reader.slice(originalOffset);
|
||
|
frameInfo.cellBuffer = {
|
||
|
parts: [ buffer ],
|
||
|
byteLength: buffer.length,
|
||
|
expectedLength: expectedLength
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Represents a writable streams that emits results
|
||
|
*/
|
||
|
function ResultEmitter(options) {
|
||
|
Writable.call(this, options);
|
||
|
/**
|
||
|
* Stores the rows for frames that needs to be yielded as one result with many rows
|
||
|
*/
|
||
|
this.rowBuffer = {};
|
||
|
}
|
||
|
|
||
|
util.inherits(ResultEmitter, Writable);
|
||
|
|
||
|
ResultEmitter.prototype._write = function (item, encoding, callback) {
|
||
|
let error = null;
|
||
|
try {
|
||
|
this.each(item);
|
||
|
}
|
||
|
catch (err) {
|
||
|
error = err;
|
||
|
}
|
||
|
callback(error);
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Analyzes the item and emit the corresponding event
|
||
|
*/
|
||
|
ResultEmitter.prototype.each = function (item) {
|
||
|
if (item.error || item.result) {
|
||
|
//Its either an error or an empty array rows
|
||
|
//no transformation needs to be made
|
||
|
return this.emit('result', item.header, item.error, item.result);
|
||
|
}
|
||
|
if (item.frameEnded) {
|
||
|
return this.emit('frameEnded', item.header);
|
||
|
}
|
||
|
if (item.lastContinuousPage) {
|
||
|
return this.emit('lastContinuousPage', item.header);
|
||
|
}
|
||
|
if (item.byRowCompleted) {
|
||
|
return this.emit('byRowCompleted', item.header, item.row, item.meta, item.flags);
|
||
|
}
|
||
|
if (item.byRow) {
|
||
|
//it should be yielded by row
|
||
|
return this.emit('row', item.header, item.row, item.meta, item.length, item.flags);
|
||
|
}
|
||
|
if (item.row) {
|
||
|
//it should be yielded as a result
|
||
|
//it needs to be buffered to an array of rows
|
||
|
return this.bufferAndEmit(item);
|
||
|
}
|
||
|
if (item.event) {
|
||
|
//its an event from Cassandra
|
||
|
return this.emit('nodeEvent', item.header, item.event);
|
||
|
}
|
||
|
//its a raw response (object with flags)
|
||
|
return this.emit('result', item.header, null, item);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Buffers the rows until the result set is completed and emits the result event.
|
||
|
*/
|
||
|
ResultEmitter.prototype.bufferAndEmit = function (item) {
|
||
|
let rows = this.rowBuffer[item.header.streamId];
|
||
|
if (!rows) {
|
||
|
rows = this.rowBuffer[item.header.streamId] = [];
|
||
|
}
|
||
|
rows.push(item.row);
|
||
|
if (rows.length === item.length) {
|
||
|
this.emit('result', item.header, null, { rows: rows, meta: item.meta, flags: item.flags});
|
||
|
delete this.rowBuffer[item.header.streamId];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
exports.Protocol = Protocol;
|
||
|
exports.Parser = Parser;
|
||
|
exports.ResultEmitter = ResultEmitter;
|