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 Integer, { int } from './integer';
/**
 * 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 = this._buildNotifications(metadata.notifications);
        /**
         * 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 };
    }
    _buildNotifications(notifications) {
        if (notifications == null) {
            return [];
        }
        return notifications.map(function (n) {
            return new Notification(n);
        });
    }
    /**
     * 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] = intValue(statistics[index]);
            }
            else if (camelCaseIndex === 'systemUpdates') {
                this._systemUpdates = intValue(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;
    }
}
/**
 * @typedef {'WARNING' | 'INFORMATION' | 'UNKNOWN'} NotificationSeverityLevel
 */
/**
 * Constants that represents the Severity level in the {@link Notification}
 */
const notificationSeverityLevel = {
    WARNING: 'WARNING',
    INFORMATION: 'INFORMATION',
    UNKNOWN: 'UNKNOWN'
};
Object.freeze(notificationSeverityLevel);
const severityLevels = Object.values(notificationSeverityLevel);
/**
 * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'TOPOLOGY' | 'SECURITY' | 'DEPRECATION' | 'GENERIC' | 'UNKNOWN' } NotificationCategory
 */
/**
 * Constants that represents the Category in the {@link Notification}
 */
const notificationCategory = {
    HINT: 'HINT',
    UNRECOGNIZED: 'UNRECOGNIZED',
    UNSUPPORTED: 'UNSUPPORTED',
    PERFORMANCE: 'PERFORMANCE',
    DEPRECATION: 'DEPRECATION',
    TOPOLOGY: 'TOPOLOGY',
    SECURITY: 'SECURITY',
    GENERIC: 'GENERIC',
    UNKNOWN: 'UNKNOWN'
};
Object.freeze(notificationCategory);
const categories = Object.values(notificationCategory);
/**
 * Class for Cypher notifications
 * @access public
 */
class Notification {
    /**
     * Create a Notification instance
     * @constructor
     * @param {Object} notification - Object with notification data
     */
    constructor(notification) {
        /**
         * The code
         * @type {string}
         * @public
         */
        this.code = notification.code;
        /**
         * The title
         * @type {string}
         * @public
         */
        this.title = notification.title;
        /**
         * The description
         * @type {string}
         * @public
         */
        this.description = notification.description;
        /**
         * The raw severity
         *
         * Use {@link Notification#rawSeverityLevel} for the raw value or {@link Notification#severityLevel} for an enumerated value.
         *
         * @type {string}
         * @public
         * @deprecated This property will be removed in 6.0.
         */
        this.severity = notification.severity;
        /**
         * The position which the notification had occur.
         *
         * @type {NotificationPosition}
         * @public
         */
        this.position = Notification._constructPosition(notification.position);
        /**
         * The severity level
         *
         * @type {NotificationSeverityLevel}
         * @public
         * @example
         * const { summary } = await session.run("RETURN 1")
         *
         * for (const notification of summary.notifications) {
         *     switch(notification.severityLevel) {
         *         case neo4j.notificationSeverityLevel.INFORMATION: // or simply 'INFORMATION'
         *             console.info(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationSeverityLevel.WARNING: // or simply 'WARNING'
         *             console.warn(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationSeverityLevel.UNKNOWN: // or simply 'UNKNOWN'
         *         default:
         *             // the raw info came from the server could be found at notification.rawSeverityLevel
         *             console.log(`${notification.title} - ${notification.description}`)
         *             break
         *     }
         * }
         */
        this.severityLevel = severityLevels.includes(notification.severity)
            ? notification.severity
            : notificationSeverityLevel.UNKNOWN;
        /**
         * The severity level returned by the server without any validation.
         *
         * @type {string}
         * @public
         */
        this.rawSeverityLevel = notification.severity;
        /**
         * The category
         *
         * @type {NotificationCategory}
         * @public
         * @example
         * const { summary } = await session.run("RETURN 1")
         *
         * for (const notification of summary.notifications) {
         *     switch(notification.category) {
         *         case neo4j.notificationCategory.QUERY: // or simply 'QUERY'
         *             console.info(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationCategory.PERFORMANCE: // or simply 'PERFORMANCE'
         *             console.warn(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationCategory.UNKNOWN: // or simply 'UNKNOWN'
         *         default:
         *             // the raw info came from the server could be found at notification.rawCategory
         *             console.log(`${notification.title} - ${notification.description}`)
         *             break
         *     }
         * }
         */
        this.category = categories.includes(notification.category)
            ? notification.category
            : notificationCategory.UNKNOWN;
        /**
         * The category returned by the server without any validation.
         *
         * @type {string|undefined}
         * @public
         */
        this.rawCategory = notification.category;
    }
    static _constructPosition(pos) {
        if (pos == null) {
            return {};
        }
        /* eslint-disable @typescript-eslint/no-non-null-assertion */
        return {
            offset: intValue(pos.offset),
            line: intValue(pos.line),
            column: intValue(pos.column)
        };
        /* eslint-enable @typescript-eslint/no-non-null-assertion */
    }
}
/**
 * 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 intValue(value) {
    if (value instanceof Integer) {
        return value.toNumber();
    }
    else if (typeof value === 'bigint') {
        return int(value).toNumber();
    }
    else {
        return value;
    }
}
function valueOrDefault(key, values, defaultValue = 0) {
    if (values !== false && key in values) {
        const value = values[key];
        return intValue(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, Notification, Plan, ProfiledPlan, QueryStatistics, Stats, notificationSeverityLevel, notificationCategory };
export default ResultSummary;