UNPKG

cassandra-driver

Version:

DataStax Node.js Driver for Apache Cassandra

275 lines (245 loc) 9.7 kB
/* * 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;