cassandra-driver
Version:
DataStax Node.js Driver for Apache Cassandra
275 lines (245 loc) • 9.7 kB
JavaScript
/*
* 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 utils = require('../utils');
const errors = require('../errors');
const asyncIteratorSymbol = Symbol.asyncIterator || '@@asyncIterator';
/** @module types */
/**
* Creates a new instance of ResultSet.
* @class
* @classdesc Represents the result of a query.
* @param {Object} response
* @param {String} host
* @param {Object} triedHosts
* @param {Number} speculativeExecutions
* @param {Number} consistency
* @param {Boolean} isSchemaInAgreement
* @constructor
*/
function ResultSet(response, host, triedHosts, speculativeExecutions, consistency, isSchemaInAgreement) {
// if no execution was made at all, set to 0.
if (speculativeExecutions === -1) {
speculativeExecutions = 0;
}
/**
* Information on the execution of a successful query:
* @member {Object}
* @property {Number} achievedConsistency The consistency level that has been actually achieved by the query.
* @property {String} queriedHost The Cassandra host that coordinated this query.
* @property {Object} triedHosts Gets the associative array of host that were queried before getting a valid response,
* being the last host the one that replied correctly.
* @property {Object} speculativeExecutions The number of speculative executions (not including the first) executed before
* getting a valid response.
* @property {Uuid} traceId Identifier of the trace session.
* @property {Array.<string>} warnings Warning messages generated by the server when executing the query.
* @property {Boolean} isSchemaInAgreement Whether the cluster had reached schema agreement after the execution of
* this query.
* <p>
* After a successful schema-altering query (ex: creating a table), the driver will check if
* the cluster's nodes agree on the new schema version. If not, it will keep retrying for a given
* delay (see <code>protocolOptions.maxSchemaAgreementWaitSeconds</code>).
* </p>
* <p>
* Note that the schema agreement check is only performed for schema-altering queries For other
* query types, this method will always return <code>true</code>. If this method returns <code>false</code>,
* clients can call [Metadata.checkSchemaAgreement()]{@link module:metadata~Metadata#checkSchemaAgreement} later to
* perform the check manually.
* </p>
*/
this.info = {
queriedHost: host,
triedHosts: triedHosts,
speculativeExecutions: speculativeExecutions,
achievedConsistency: consistency,
traceId: null,
warnings: null,
customPayload: null,
isSchemaInAgreement
};
if (response.flags) {
this.info.traceId = response.flags.traceId;
this.info.warnings = response.flags.warnings;
this.info.customPayload = response.flags.customPayload;
}
/**
* Gets an array rows returned by the query.
* When the result set represents a response from a write query, this property will be <code>undefined</code>.
* When the read query result contains more rows than the fetch size (5000), this property will only contain the
* first rows up to fetch size. To obtain all the rows, you can use the built-in async iterator that will retrieve the
* following pages of results.
* @type {Array<Row>|undefined}
*/
this.rows = response.rows;
/**
* Gets the row length of the result, regardless if the result has been buffered or not
* @type {Number|undefined}
*/
this.rowLength = this.rows ? this.rows.length : response.rowLength;
/**
* Gets the columns returned in this ResultSet.
* @type {Array.<{name, type}>}
* @default null
*/
this.columns = null;
/**
* A string token representing the current page state of query. It can be used in the following executions to
* continue paging and retrieve the remained of the result for the query.
* @type {String|null}
* @default null
*/
this.pageState = null;
/**
* Method used to manually fetch the next page of results.
* This method is only exposed when using the {@link Client#eachRow} method and there are more rows available in
* following pages.
* @type Function
*/
this.nextPage = undefined;
/**
* Method used internally to fetch the next page of results using promises.
* @internal
* @ignore
* @type {Function}
*/
this.nextPageAsync = undefined;
const meta = response.meta;
if (meta) {
this.columns = meta.columns;
if (meta.pageState) {
this.pageState = meta.pageState.toString('hex');
// Expose rawPageState internally
Object.defineProperty(this, 'rawPageState', { value: meta.pageState, enumerable: false });
}
}
}
/**
* Returns the first row or null if the result rows are empty.
*/
ResultSet.prototype.first = function () {
if (this.rows && this.rows.length) {
return this.rows[0];
}
return null;
};
ResultSet.prototype.getPageState = function () {
// backward-compatibility
return this.pageState;
};
ResultSet.prototype.getColumns = function () {
// backward-compatibility
return this.columns;
};
/**
* When this instance is the result of a conditional update query, it returns whether it was successful.
* Otherwise, it returns <code>true</code>.
* <p>
* For consistency, this method always returns <code>true</code> for non-conditional queries (although there is
* no reason to call the method in that case). This is also the case for conditional DDL statements
* (CREATE KEYSPACE... IF NOT EXISTS, CREATE TABLE... IF NOT EXISTS), for which the server doesn't return
* information whether it was applied or not.
* </p>
*/
ResultSet.prototype.wasApplied = function () {
if (!this.rows || this.rows.length === 0) {
return true;
}
const firstRow = this.rows[0];
const applied = firstRow['[applied]'];
return typeof applied === 'boolean' ? applied : true;
};
/**
* Gets the iterator function.
* <p>
* Retrieves the iterator of the underlying fetched rows, without causing the driver to fetch the following
* result pages. For more information on result paging,
* [visit the documentation]{@link http://docs.datastax.com/en/developer/nodejs-driver/latest/features/paging/}.
* </p>
* @alias module:types~ResultSet#@@iterator
* @see {@link module:types~ResultSet#@@asyncIterator}
* @example <caption>Using for...of statement</caption>
* const query = 'SELECT user_id, post_id, content FROM timeline WHERE user_id = ?';
* const result = await client.execute(query, [ id ], { prepare: true });
* for (const row of result) {
* console.log(row['email']);
* }
* @returns {Iterator.<Row>}
*/
ResultSet.prototype[Symbol.iterator] = function getIterator() {
if (!this.rows) {
return utils.emptyArray[Symbol.iterator]();
}
return this.rows[Symbol.iterator]();
};
/**
* Gets the async iterator function.
* <p>
* Retrieves the async iterator representing the entire query result, the driver will fetch the following result
* pages.
* </p>
* <p>Use the async iterator when the query result might contain more rows than the <code>fetchSize</code>.</p>
* <p>
* Note that using the async iterator will not affect the internal state of the <code>ResultSet</code> instance.
* You should avoid using both <code>rows</code> property that contains the row instances of the first page of
* results, and the async iterator, that will yield all the rows in the result regardless on the number of pages.
* </p>
* <p>Multiple concurrent async iterations are not supported.</p>
* @alias module:types~ResultSet#@@asyncIterator
* @example <caption>Using for await...of statement</caption>
* const query = 'SELECT user_id, post_id, content FROM timeline WHERE user_id = ?';
* const result = await client.execute(query, [ id ], { prepare: true });
* for await (const row of result) {
* console.log(row['email']);
* }
* @returns {AsyncIterator<Row>}
*/
ResultSet.prototype[asyncIteratorSymbol] = function getAsyncGenerator() {
let index = 0;
let pageState = this.rawPageState;
let rows = this.rows;
if (!rows || rows.length === 0) {
return { next: () => Promise.resolve({ done: true }) };
}
const self = this;
// Async generators are not present in Node.js 8, implement it manually
return {
async next() {
if (index >= rows.length && pageState) {
if (!self.nextPageAsync) {
throw new errors.DriverInternalError('Property nextPageAsync should be set when pageState is defined');
}
const rs = await self.nextPageAsync(pageState);
rows = rs.rows;
index = 0;
pageState = rs.rawPageState;
}
if (index < rows.length) {
return { done: false, value: rows[index++] };
}
return { done: true };
}
};
};
/**
* Determines whether there are more pages of results.
* If so, the driver will initially retrieve and contain only the first page of results.
* To obtain all the rows, use the [AsyncIterator]{@linkcode module:types~ResultSet#@@asyncIterator}.
* @returns {boolean}
*/
ResultSet.prototype.isPaged = function() {
return !!this.rawPageState;
};
module.exports = ResultSet;