Home Reference Source

lib6/result-summary.js

/**
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * 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.
 */
import { util } from './internal';
import { buildGqlStatusObjectFromMetadata, buildNotificationsFromMetadata } from './notification';
/**
 * A ResultSummary instance contains structured metadata for a {@link Result}.
 * @access public
 */
class ResultSummary {
    /**
     * @constructor
     * @param {string} query - The query this summary is for
     * @param {Object} parameters - Parameters for the query
     * @param {Object} metadata - Query metadata
     * @param {number|undefined} protocolVersion - Bolt Protocol Version
     */
    constructor(query, parameters, metadata, protocolVersion) {
        var _a, _b, _c;
        /**
         * The query and parameters this summary is for.
         * @type {{text: string, parameters: Object}}
         * @public
         */
        this.query = { text: query, parameters };
        /**
         * The type of query executed. Can be "r" for read-only query, "rw" for read-write query,
         * "w" for write-only query and "s" for schema-write query.
         * String constants are available in {@link queryType} object.
         * @type {string}
         * @public
         */
        this.queryType = metadata.type;
        /**
         * Counters for operations the query triggered.
         * @type {QueryStatistics}
         * @public
         */
        this.counters = new QueryStatistics((_a = metadata.stats) !== null && _a !== void 0 ? _a : {});
        // for backwards compatibility, remove in future version
        /**
         * Use {@link ResultSummary.counters} instead.
         * @type {QueryStatistics}
         * @deprecated
         */
        this.updateStatistics = this.counters;
        /**
         * This describes how the database will execute the query.
         * Query plan for the executed query if available, otherwise undefined.
         * Will only be populated for queries that start with "EXPLAIN".
         * @type {Plan|false}
         * @public
         */
        this.plan =
            metadata.plan != null || metadata.profile != null
                ? new Plan((_b = metadata.plan) !== null && _b !== void 0 ? _b : metadata.profile)
                : false;
        /**
         * This describes how the database did execute your query. This will contain detailed information about what
         * each step of the plan did. Profiled query plan for the executed query if available, otherwise undefined.
         * Will only be populated for queries that start with "PROFILE".
         * @type {ProfiledPlan}
         * @public
         */
        this.profile = metadata.profile != null ? new ProfiledPlan(metadata.profile) : false;
        /**
         * An array of notifications that might arise when executing the query. Notifications can be warnings about
         * problematic queries or other valuable information that can be presented in a client. Unlike failures
         * or errors, notifications do not affect the execution of a query.
         * @type {Array<Notification>}
         * @public
         */
        this.notifications = buildNotificationsFromMetadata(metadata);
        /**
         * A list of GqlStatusObjects that arise when executing the query.
         *
         * The list always contains at least 1 status representing the Success, No Data or Omitted Result.
         *
         * When discarding records while connected to a non-gql aware server and using a RxSession,
         * the driver might not be able to tell apart Success and No Data.
         *
         * All other status are notifications like warnings about problematic queries or other valuable
         * information that can be presented in a client.
         *
         * The GqlStatusObjects will be presented in the following order:
         *
         * - A “no data” (02xxx) has precedence over a warning;
         * - A warning (01xxx) has precedence over a success.
         * - A success (00xxx) has precedence over anything informational (03xxx)
         *
         * @type {Array<GqlStatusObject>}
         * @public
         * @experimental
         */
        this.gqlStatusObjects = buildGqlStatusObjectFromMetadata(metadata);
        /**
         * The basic information of the server where the result is obtained from.
         * @type {ServerInfo}
         * @public
         */
        this.server = new ServerInfo(metadata.server, protocolVersion);
        /**
         * The time it took the server to consume the result.
         * @type {number}
         * @public
         */
        this.resultConsumedAfter = metadata.result_consumed_after;
        /**
         * The time it took the server to make the result available for consumption in milliseconds.
         * @type {number}
         * @public
         */
        this.resultAvailableAfter = metadata.result_available_after;
        /**
         * The database name where this summary is obtained from.
         * @type {{name: string}}
         * @public
         */
        this.database = { name: (_c = metadata.db) !== null && _c !== void 0 ? _c : null };
    }
    /**
     * Check if the result summary has a plan
     * @return {boolean}
     */
    hasPlan() {
        return this.plan instanceof Plan;
    }
    /**
     * Check if the result summary has a profile
     * @return {boolean}
     */
    hasProfile() {
        return this.profile instanceof ProfiledPlan;
    }
}
/**
 * Class for execution plan received by prepending Cypher with EXPLAIN.
 * @access public
 */
class Plan {
    /**
     * Create a Plan instance
     * @constructor
     * @param {Object} plan - Object with plan data
     */
    constructor(plan) {
        this.operatorType = plan.operatorType;
        this.identifiers = plan.identifiers;
        this.arguments = plan.args;
        this.children = plan.children != null
            ? plan.children.map((child) => new Plan(child))
            : [];
    }
}
/**
 * Class for execution plan received by prepending Cypher with PROFILE.
 * @access public
 */
class ProfiledPlan {
    /**
     * Create a ProfiledPlan instance
     * @constructor
     * @param {Object} profile - Object with profile data
     */
    constructor(profile) {
        this.operatorType = profile.operatorType;
        this.identifiers = profile.identifiers;
        this.arguments = profile.args;
        this.dbHits = valueOrDefault('dbHits', profile);
        this.rows = valueOrDefault('rows', profile);
        this.pageCacheMisses = valueOrDefault('pageCacheMisses', profile);
        this.pageCacheHits = valueOrDefault('pageCacheHits', profile);
        this.pageCacheHitRatio = valueOrDefault('pageCacheHitRatio', profile);
        this.time = valueOrDefault('time', profile);
        this.children = profile.children != null
            ? profile.children.map((child) => new ProfiledPlan(child))
            : [];
    }
    hasPageCacheStats() {
        return (this.pageCacheMisses > 0 ||
            this.pageCacheHits > 0 ||
            this.pageCacheHitRatio > 0);
    }
}
/**
 * Stats Query statistics dictionary for a {@link QueryStatistics}
 * @public
 */
class Stats {
    /**
     * @constructor
     * @private
     */
    constructor() {
        /**
         * nodes created
         * @type {number}
         * @public
         */
        this.nodesCreated = 0;
        /**
         * nodes deleted
         * @type {number}
         * @public
         */
        this.nodesDeleted = 0;
        /**
         * relationships created
         * @type {number}
         * @public
         */
        this.relationshipsCreated = 0;
        /**
         * relationships deleted
         * @type {number}
         * @public
         */
        this.relationshipsDeleted = 0;
        /**
         * properties set
         * @type {number}
         * @public
         */
        this.propertiesSet = 0;
        /**
         * labels added
         * @type {number}
         * @public
         */
        this.labelsAdded = 0;
        /**
         * labels removed
         * @type {number}
         * @public
         */
        this.labelsRemoved = 0;
        /**
         * indexes added
         * @type {number}
         * @public
         */
        this.indexesAdded = 0;
        /**
         * indexes removed
         * @type {number}
         * @public
         */
        this.indexesRemoved = 0;
        /**
         * constraints added
         * @type {number}
         * @public
         */
        this.constraintsAdded = 0;
        /**
         * constraints removed
         * @type {number}
         * @public
         */
        this.constraintsRemoved = 0;
    }
}
/**
 * Get statistical information for a {@link Result}.
 * @access public
 */
class QueryStatistics {
    /**
     * Structurize the statistics
     * @constructor
     * @param {Object} statistics - Result statistics
     */
    constructor(statistics) {
        this._stats = {
            nodesCreated: 0,
            nodesDeleted: 0,
            relationshipsCreated: 0,
            relationshipsDeleted: 0,
            propertiesSet: 0,
            labelsAdded: 0,
            labelsRemoved: 0,
            indexesAdded: 0,
            indexesRemoved: 0,
            constraintsAdded: 0,
            constraintsRemoved: 0
        };
        this._systemUpdates = 0;
        Object.keys(statistics).forEach(index => {
            // To camelCase
            const camelCaseIndex = index.replace(/(-\w)/g, m => m[1].toUpperCase());
            if (camelCaseIndex in this._stats) {
                this._stats[camelCaseIndex] = util.toNumber(statistics[index]);
            }
            else if (camelCaseIndex === 'systemUpdates') {
                this._systemUpdates = util.toNumber(statistics[index]);
            }
            else if (camelCaseIndex === 'containsSystemUpdates') {
                this._containsSystemUpdates = statistics[index];
            }
            else if (camelCaseIndex === 'containsUpdates') {
                this._containsUpdates = statistics[index];
            }
        });
        this._stats = Object.freeze(this._stats);
    }
    /**
     * Did the database get updated?
     * @return {boolean}
     */
    containsUpdates() {
        return this._containsUpdates !== undefined
            ? this._containsUpdates
            : (Object.keys(this._stats).reduce((last, current) => {
                return last + this._stats[current];
            }, 0) > 0);
    }
    /**
     * Returns the query statistics updates in a dictionary.
     * @returns {Stats}
     */
    updates() {
        return this._stats;
    }
    /**
     * Return true if the system database get updated, otherwise false
     * @returns {boolean} - If the system database get updated or not.
     */
    containsSystemUpdates() {
        return this._containsSystemUpdates !== undefined
            ? this._containsSystemUpdates
            : this._systemUpdates > 0;
    }
    /**
     * @returns {number} - Number of system updates
     */
    systemUpdates() {
        return this._systemUpdates;
    }
}
/**
 * Class for exposing server info from a result.
 * @access public
 */
class ServerInfo {
    /**
     * Create a ServerInfo instance
     * @constructor
     * @param {Object} serverMeta - Object with serverMeta data
     * @param {Object} connectionInfo - Bolt connection info
     * @param {number} protocolVersion - Bolt Protocol Version
     */
    constructor(serverMeta, protocolVersion) {
        if (serverMeta != null) {
            /**
             * The server adress
             * @type {string}
             * @public
             */
            this.address = serverMeta.address;
            /**
             * The server user agent string
             * @type {string}
             * @public
             */
            this.agent = serverMeta.version;
        }
        /**
         * The protocol version used by the connection
         * @type {number}
         * @public
         */
        this.protocolVersion = protocolVersion;
    }
}
function valueOrDefault(key, values, defaultValue = 0) {
    if (values !== false && key in values) {
        const value = values[key];
        return util.toNumber(value);
    }
    else {
        return defaultValue;
    }
}
/**
 * The constants for query types
 * @type {{SCHEMA_WRITE: string, WRITE_ONLY: string, READ_ONLY: string, READ_WRITE: string}}
 */
const queryType = {
    READ_ONLY: 'r',
    READ_WRITE: 'rw',
    WRITE_ONLY: 'w',
    SCHEMA_WRITE: 's'
};
export { queryType, ServerInfo, Plan, ProfiledPlan, QueryStatistics, Stats };
export default ResultSummary;