UNPKG

neo4j-driver-core

Version:
606 lines (605 loc) 30.7 kB
"use strict"; /** * 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. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable @typescript-eslint/promise-function-async */ var observers_1 = require("./internal/observers"); var util_1 = require("./internal/util"); var constants_1 = require("./internal/constants"); var error_1 = require("./error"); var result_1 = __importDefault(require("./result")); var connection_holder_1 = require("./internal/connection-holder"); var transaction_executor_1 = require("./internal/transaction-executor"); var bookmarks_1 = require("./internal/bookmarks"); var tx_config_1 = require("./internal/tx-config"); var transaction_promise_1 = __importDefault(require("./transaction-promise")); var transaction_managed_1 = __importDefault(require("./transaction-managed")); /** * A Session instance is used for handling the connection and * sending queries through the connection. * In a single session, multiple queries will be executed serially. * In order to execute parallel queries, multiple sessions are required. * @access public */ var Session = /** @class */ (function () { /** * @constructor * @protected * @param {Object} args * @param {string} args.mode the default access mode for this session. * @param {ConnectionProvider} args.connectionProvider - The connection provider to acquire connections from. * @param {Bookmarks} args.bookmarks - The initial bookmarks for this session. * @param {string} args.database the database name * @param {Object} args.config={} - This driver configuration. * @param {boolean} args.reactive - Whether this session should create reactive streams * @param {number} args.fetchSize - Defines how many records is pulled in each pulling batch * @param {string} args.impersonatedUser - The username which the user wants to impersonate for the duration of the session. * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. */ function Session(_a) { var mode = _a.mode, connectionProvider = _a.connectionProvider, bookmarks = _a.bookmarks, database = _a.database, config = _a.config, reactive = _a.reactive, fetchSize = _a.fetchSize, impersonatedUser = _a.impersonatedUser, bookmarkManager = _a.bookmarkManager, notificationFilter = _a.notificationFilter, auth = _a.auth, log = _a.log; this._mode = mode; this._database = database; this._reactive = reactive; this._fetchSize = fetchSize; this._onDatabaseNameResolved = this._onDatabaseNameResolved.bind(this); this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this); this._readConnectionHolder = new connection_holder_1.ConnectionHolder({ mode: constants_1.ACCESS_MODE_READ, auth: auth, database: database, bookmarks: bookmarks, connectionProvider: connectionProvider, impersonatedUser: impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log: log }); this._writeConnectionHolder = new connection_holder_1.ConnectionHolder({ mode: constants_1.ACCESS_MODE_WRITE, auth: auth, database: database, bookmarks: bookmarks, connectionProvider: connectionProvider, impersonatedUser: impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log: log }); this._open = true; this._hasTx = false; this._impersonatedUser = impersonatedUser; this._lastBookmarks = bookmarks !== null && bookmarks !== void 0 ? bookmarks : bookmarks_1.Bookmarks.empty(); this._configuredBookmarks = this._lastBookmarks; this._transactionExecutor = _createTransactionExecutor(config); this._databaseNameResolved = this._database !== ''; var calculatedWatermaks = this._calculateWatermaks(); this._lowRecordWatermark = calculatedWatermaks.low; this._highRecordWatermark = calculatedWatermaks.high; this._results = []; this._bookmarkManager = bookmarkManager; this._notificationFilter = notificationFilter; this._log = log; } /** * Run Cypher query * Could be called with a query object i.e.: `{text: "MATCH ...", parameters: {param: 1}}` * or with the query and parameters as separate arguments. * * @public * @param {mixed} query - Cypher query to execute * @param {Object} parameters - Map with parameters to use in query * @param {TransactionConfig} [transactionConfig] - Configuration for the new auto-commit transaction. * @return {Result} New Result. */ Session.prototype.run = function (query, parameters, transactionConfig) { var _this = this; var _a = (0, util_1.validateQueryAndParameters)(query, parameters), validatedQuery = _a.validatedQuery, params = _a.params; var autoCommitTxConfig = (transactionConfig != null) ? new tx_config_1.TxConfig(transactionConfig, this._log) : tx_config_1.TxConfig.empty(); var result = this._run(validatedQuery, params, function (connection) { return __awaiter(_this, void 0, void 0, function () { var bookmarks; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._bookmarks()]; case 1: bookmarks = _a.sent(); this._assertSessionIsOpen(); return [2 /*return*/, connection.run(validatedQuery, params, { bookmarks: bookmarks, txConfig: autoCommitTxConfig, mode: this._mode, database: this._database, apiTelemetryConfig: { api: constants_1.TELEMETRY_APIS.AUTO_COMMIT_TRANSACTION }, impersonatedUser: this._impersonatedUser, afterComplete: function (meta) { return _this._onCompleteCallback(meta, bookmarks); }, reactive: this._reactive, fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter })]; } }); }); }); this._results.push(result); return result; }; Session.prototype._run = function (query, parameters, customRunner) { var _a = this._acquireAndConsumeConnection(customRunner), connectionHolder = _a.connectionHolder, resultPromise = _a.resultPromise; var observerPromise = resultPromise.catch(function (error) { return Promise.resolve(new observers_1.FailedObserver({ error: error })); }); var watermarks = { high: this._highRecordWatermark, low: this._lowRecordWatermark }; return new result_1.default(observerPromise, query, parameters, connectionHolder, watermarks); }; /** * This method is used by Rediscovery on the neo4j-driver-bolt-protocol package. * * @private * @param {function()} connectionConsumer The method which will use the connection * @returns {Promise<T>} A connection promise */ Session.prototype._acquireConnection = function (connectionConsumer) { var _this = this; var _a = this._acquireAndConsumeConnection(connectionConsumer), connectionHolder = _a.connectionHolder, resultPromise = _a.resultPromise; return resultPromise.then(function (result) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, connectionHolder.releaseConnection()]; case 1: _a.sent(); return [2 /*return*/, result]; } }); }); }); }; /** * Acquires a {@link Connection}, consume it and return a promise of the result along with * the {@link ConnectionHolder} used in the process. * * @private * @param connectionConsumer * @returns {object} The connection holder and connection promise. */ Session.prototype._acquireAndConsumeConnection = function (connectionConsumer) { var resultPromise; var connectionHolder = this._connectionHolderWithMode(this._mode); if (!this._open) { resultPromise = Promise.reject((0, error_1.newError)('Cannot run query in a closed session.')); } else if (!this._hasTx && connectionHolder.initializeConnection()) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method // return // eslint-disable-next-line @typescript-eslint/no-non-null-assertion .then(function (connection) { return connectionConsumer(connection); }); } else { resultPromise = Promise.reject((0, error_1.newError)('Queries cannot be run directly on a ' + 'session with an open transaction; either run from within the ' + 'transaction or use a different session.')); } return { connectionHolder: connectionHolder, resultPromise: resultPromise }; }; /** * Begin a new transaction in this session. A session can have at most one transaction running at a time, if you * want to run multiple concurrent transactions, you should use multiple concurrent sessions. * * While a transaction is open the session cannot be used to run queries outside the transaction. * * @param {TransactionConfig} [transactionConfig] - Configuration for the new auto-commit transaction. * @returns {TransactionPromise} New Transaction. */ Session.prototype.beginTransaction = function (transactionConfig) { // this function needs to support bookmarks parameter for backwards compatibility // parameter was of type {string|string[]} and represented either a single or multiple bookmarks // that's why we need to check parameter type and decide how to interpret the value var arg = transactionConfig; var txConfig = tx_config_1.TxConfig.empty(); if (arg != null) { txConfig = new tx_config_1.TxConfig(arg, this._log); } return this._beginTransaction(this._mode, txConfig, { api: constants_1.TELEMETRY_APIS.UNMANAGED_TRANSACTION }); }; Session.prototype._beginTransaction = function (accessMode, txConfig, apiTelemetryConfig) { var _this = this; if (!this._open) { throw (0, error_1.newError)('Cannot begin a transaction on a closed session.'); } if (this._hasTx) { throw (0, error_1.newError)('You cannot begin a transaction on a session with an open transaction; ' + 'either run from within the transaction or use a different session.'); } var mode = Session._validateSessionMode(accessMode); var connectionHolder = this._connectionHolderWithMode(mode); connectionHolder.initializeConnection(); this._hasTx = true; var tx = new transaction_promise_1.default({ connectionHolder: connectionHolder, impersonatedUser: this._impersonatedUser, onClose: this._transactionClosed.bind(this), onBookmarks: function (newBm, oldBm, db) { return _this._updateBookmarks(newBm, oldBm, db); }, onConnection: this._assertSessionIsOpen.bind(this), reactive: this._reactive, fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, apiTelemetryConfig: apiTelemetryConfig }); tx._begin(function () { return _this._bookmarks(); }, txConfig); return tx; }; /** * @private * @returns {void} */ Session.prototype._assertSessionIsOpen = function () { if (!this._open) { throw (0, error_1.newError)('You cannot run more transactions on a closed session.'); } }; /** * @private * @returns {void} */ Session.prototype._transactionClosed = function () { this._hasTx = false; }; /** * Return the bookmarks received following the last completed {@link Transaction}. * * @deprecated This method will be removed in version 6.0. Please, use Session#lastBookmarks instead. * * @return {string[]} A reference to a previous transaction. * @see {@link Session#lastBookmarks} */ Session.prototype.lastBookmark = function () { return this.lastBookmarks(); }; /** * Return the bookmarks received following the last completed {@link Transaction}. * * @return {string[]} A reference to a previous transaction. */ Session.prototype.lastBookmarks = function () { return this._lastBookmarks.values(); }; Session.prototype._bookmarks = function () { var _a; return __awaiter(this, void 0, void 0, function () { var bookmarks; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, ((_a = this._bookmarkManager) === null || _a === void 0 ? void 0 : _a.getBookmarks())]; case 1: bookmarks = _b.sent(); if (bookmarks === undefined) { return [2 /*return*/, this._lastBookmarks]; } return [2 /*return*/, new bookmarks_1.Bookmarks(__spreadArray(__spreadArray([], __read(bookmarks), false), __read(this._configuredBookmarks), false))]; } }); }); }; /** * Execute given unit of work in a {@link READ} transaction. * * Transaction will automatically be committed unless the given function throws or returns a rejected promise. * Some failures of the given function or the commit itself will be retried with exponential backoff with initial * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's * `maxTransactionRetryTime` property in milliseconds. * * @deprecated This method will be removed in version 6.0. Please, use Session#executeRead instead. * * @param {function(tx: Transaction): Promise} transactionWork - Callback that executes operations against * a given {@link Transaction}. * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work. * @return {Promise} Resolved promise as returned by the given function or rejected promise when given * function or commit fails. * @see {@link Session#executeRead} */ Session.prototype.readTransaction = function (transactionWork, transactionConfig) { var config = new tx_config_1.TxConfig(transactionConfig, this._log); return this._runTransaction(constants_1.ACCESS_MODE_READ, config, transactionWork); }; /** * Execute given unit of work in a {@link WRITE} transaction. * * Transaction will automatically be committed unless the given function throws or returns a rejected promise. * Some failures of the given function or the commit itself will be retried with exponential backoff with initial * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's * `maxTransactionRetryTime` property in milliseconds. * * @deprecated This method will be removed in version 6.0. Please, use Session#executeWrite instead. * * @param {function(tx: Transaction): Promise} transactionWork - Callback that executes operations against * a given {@link Transaction}. * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work. * @return {Promise} Resolved promise as returned by the given function or rejected promise when given * function or commit fails. * @see {@link Session#executeWrite} */ Session.prototype.writeTransaction = function (transactionWork, transactionConfig) { var config = new tx_config_1.TxConfig(transactionConfig, this._log); return this._runTransaction(constants_1.ACCESS_MODE_WRITE, config, transactionWork); }; Session.prototype._runTransaction = function (accessMode, transactionConfig, transactionWork) { var _this = this; return this._transactionExecutor.execute(function (apiTelemetryConfig) { return _this._beginTransaction(accessMode, transactionConfig, apiTelemetryConfig); }, transactionWork); }; /** * Execute given unit of work in a {@link READ} transaction. * * Transaction will automatically be committed unless the given function throws or returns a rejected promise. * Some failures of the given function or the commit itself will be retried with exponential backoff with initial * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's * `maxTransactionRetryTime` property in milliseconds. * * @param {function(tx: ManagedTransaction): Promise} transactionWork - Callback that executes operations against * a given {@link Transaction}. * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work. * @return {Promise} Resolved promise as returned by the given function or rejected promise when given * function or commit fails. */ Session.prototype.executeRead = function (transactionWork, transactionConfig) { var config = new tx_config_1.TxConfig(transactionConfig, this._log); return this._executeInTransaction(constants_1.ACCESS_MODE_READ, config, transactionWork); }; /** * Execute given unit of work in a {@link WRITE} transaction. * * Transaction will automatically be committed unless the given function throws or returns a rejected promise. * Some failures of the given function or the commit itself will be retried with exponential backoff with initial * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's * `maxTransactionRetryTime` property in milliseconds. * * @param {function(tx: ManagedTransaction): Promise} transactionWork - Callback that executes operations against * a given {@link Transaction}. * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work. * @return {Promise} Resolved promise as returned by the given function or rejected promise when given * function or commit fails. */ Session.prototype.executeWrite = function (transactionWork, transactionConfig) { var config = new tx_config_1.TxConfig(transactionConfig, this._log); return this._executeInTransaction(constants_1.ACCESS_MODE_WRITE, config, transactionWork); }; /** * @private * @param {SessionMode} accessMode * @param {TxConfig} transactionConfig * @param {ManagedTransactionWork} transactionWork * @returns {Promise} */ Session.prototype._executeInTransaction = function (accessMode, transactionConfig, transactionWork) { var _this = this; return this._transactionExecutor.execute(function (apiTelemetryConfig) { return _this._beginTransaction(accessMode, transactionConfig, apiTelemetryConfig); }, transactionWork, transaction_managed_1.default.fromTransaction); }; /** * Sets the resolved database name in the session context. * @private * @param {string|undefined} database The resolved database name * @returns {void} */ Session.prototype._onDatabaseNameResolved = function (database) { if (!this._databaseNameResolved) { var normalizedDatabase = database !== null && database !== void 0 ? database : ''; this._database = normalizedDatabase; this._readConnectionHolder.setDatabase(normalizedDatabase); this._writeConnectionHolder.setDatabase(normalizedDatabase); this._databaseNameResolved = true; } }; Session.prototype._getConnectionAcquistionBookmarks = function () { var _a; return __awaiter(this, void 0, void 0, function () { var bookmarks; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, ((_a = this._bookmarkManager) === null || _a === void 0 ? void 0 : _a.getBookmarks())]; case 1: bookmarks = _b.sent(); if (bookmarks === undefined) { return [2 /*return*/, this._lastBookmarks]; } return [2 /*return*/, new bookmarks_1.Bookmarks(__spreadArray(__spreadArray([], __read(this._configuredBookmarks), false), __read(bookmarks), false))]; } }); }); }; /** * Update value of the last bookmarks. * @private * @param {Bookmarks} newBookmarks - The new bookmarks. * @returns {void} */ Session.prototype._updateBookmarks = function (newBookmarks, previousBookmarks, database) { var _a, _b, _c; if ((newBookmarks != null) && !newBookmarks.isEmpty()) { (_a = this._bookmarkManager) === null || _a === void 0 ? void 0 : _a.updateBookmarks((_b = previousBookmarks === null || previousBookmarks === void 0 ? void 0 : previousBookmarks.values()) !== null && _b !== void 0 ? _b : [], (_c = newBookmarks === null || newBookmarks === void 0 ? void 0 : newBookmarks.values()) !== null && _c !== void 0 ? _c : []).catch(function () { }); this._lastBookmarks = newBookmarks; this._configuredBookmarks = bookmarks_1.Bookmarks.empty(); } }; /** * Close this session. * @return {Promise} */ Session.prototype.close = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this._open) return [3 /*break*/, 3]; this._open = false; this._results.forEach(function (result) { return result._cancel(); }); this._transactionExecutor.close(); return [4 /*yield*/, this._readConnectionHolder.close(this._hasTx)]; case 1: _a.sent(); return [4 /*yield*/, this._writeConnectionHolder.close(this._hasTx)]; case 2: _a.sent(); _a.label = 3; case 3: return [2 /*return*/]; } }); }); }; // eslint-disable-next-line // @ts-ignore Session.prototype[Symbol.asyncDispose] = function () { return this.close(); }; Session.prototype._connectionHolderWithMode = function (mode) { if (mode === constants_1.ACCESS_MODE_READ) { return this._readConnectionHolder; } else if (mode === constants_1.ACCESS_MODE_WRITE) { return this._writeConnectionHolder; } else { throw (0, error_1.newError)('Unknown access mode: ' + mode); } }; /** * @private * @param {Object} meta Connection metadatada * @returns {void} */ Session.prototype._onCompleteCallback = function (meta, previousBookmarks) { this._updateBookmarks(new bookmarks_1.Bookmarks(meta.bookmark), previousBookmarks, meta.db); }; /** * @private * @returns {void} */ Session.prototype._calculateWatermaks = function () { if (this._fetchSize === constants_1.FETCH_ALL) { return { low: Number.MAX_VALUE, high: Number.MAX_VALUE // we shall never reach this number to disable auto pull }; } return { low: 0.3 * this._fetchSize, high: 0.7 * this._fetchSize }; }; /** * Configure the transaction executor * * This used by {@link Driver#executeQuery} * @private * @returns {void} */ Session.prototype._configureTransactionExecutor = function (pipelined, telemetryApi) { this._transactionExecutor.pipelineBegin = pipelined; this._transactionExecutor.telemetryApi = telemetryApi; }; /** * @protected */ Session._validateSessionMode = function (rawMode) { var mode = rawMode !== null && rawMode !== void 0 ? rawMode : constants_1.ACCESS_MODE_WRITE; if (mode !== constants_1.ACCESS_MODE_READ && mode !== constants_1.ACCESS_MODE_WRITE) { throw (0, error_1.newError)('Illegal session mode ' + mode); } return mode; }; return Session; }()); /** * @private * @param {object} config * @returns {TransactionExecutor} The transaction executor */ function _createTransactionExecutor(config) { var _a; var maxRetryTimeMs = (_a = config === null || config === void 0 ? void 0 : config.maxTransactionRetryTime) !== null && _a !== void 0 ? _a : null; return new transaction_executor_1.TransactionExecutor(maxRetryTimeMs); } exports.default = Session;