UNPKG

neo4j-driver-core

Version:
529 lines (528 loc) 26.9 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 __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 util_1 = require("./internal/util"); var connection_holder_1 = require("./internal/connection-holder"); var bookmarks_1 = require("./internal/bookmarks"); var tx_config_1 = require("./internal/tx-config"); var observers_1 = require("./internal/observers"); var error_1 = require("./error"); var result_1 = __importDefault(require("./result")); /** * Represents a transaction in the Neo4j database. * * @access public */ var Transaction = /** @class */ (function () { /** * @constructor * @param {object} args * @param {ConnectionHolder} args.connectionHolder - the connection holder to get connection from. * @param {function()} args.onClose - Function to be called when transaction is committed or rolled back. * @param {function(bookmarks: Bookmarks)} args.onBookmarks callback invoked when new bookmark is produced. * @param {function()} args.onConnection - Function to be called when a connection is obtained to ensure the conneciton * is not yet released. * @param {boolean} args.reactive whether this transaction generates reactive streams * @param {number} args.fetchSize - the record fetch size in each pulling batch. * @param {string} args.impersonatedUser - The name of the user which should be impersonated for the duration of the session. * @param {number} args.highRecordWatermark - The high watermark for the record buffer. * @param {number} args.lowRecordWatermark - The low watermark for the record buffer. * @param {NotificationFilter} args.notificationFilter - The notification filter used for this transaction. * @param {NonAutoCommitApiTelemetryConfig} args.apiTelemetryConfig - The api telemetry configuration. Empty/Null for disabling telemetry */ function Transaction(_a) { var connectionHolder = _a.connectionHolder, onClose = _a.onClose, onBookmarks = _a.onBookmarks, onConnection = _a.onConnection, reactive = _a.reactive, fetchSize = _a.fetchSize, impersonatedUser = _a.impersonatedUser, highRecordWatermark = _a.highRecordWatermark, lowRecordWatermark = _a.lowRecordWatermark, notificationFilter = _a.notificationFilter, apiTelemetryConfig = _a.apiTelemetryConfig; var _this = this; this._connectionHolder = connectionHolder; this._reactive = reactive; this._state = _states.ACTIVE; this._onClose = onClose; this._onBookmarks = onBookmarks; this._onConnection = onConnection; this._onError = this._onErrorCallback.bind(this); this._fetchSize = fetchSize; this._onComplete = this._onCompleteCallback.bind(this); this._results = []; this._impersonatedUser = impersonatedUser; this._lowRecordWatermak = lowRecordWatermark; this._highRecordWatermark = highRecordWatermark; this._bookmarks = bookmarks_1.Bookmarks.empty(); this._notificationFilter = notificationFilter; this._apiTelemetryConfig = apiTelemetryConfig; this._acceptActive = function () { }; // satisfy DenoJS this._activePromise = new Promise(function (resolve, reject) { _this._acceptActive = resolve; }); } /** * @private * @param {Bookmarks | string | string []} bookmarks * @param {TxConfig} txConfig * @param {Object} events List of observers to events * @returns {void} */ Transaction.prototype._begin = function (getBookmarks, txConfig, events) { var _this = this; this._connectionHolder .getConnection() .then(function (connection) { return __awaiter(_this, void 0, void 0, function () { var _a; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: this._onConnection(); if (!(connection != null)) return [3 /*break*/, 2]; _a = this; return [4 /*yield*/, getBookmarks()]; case 1: _a._bookmarks = _b.sent(); return [2 /*return*/, connection.beginTransaction({ bookmarks: this._bookmarks, txConfig: txConfig, mode: this._connectionHolder.mode(), database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, notificationFilter: this._notificationFilter, apiTelemetryConfig: this._apiTelemetryConfig, beforeError: function (error) { if (events != null) { events.onError(error); } _this._onError(error).catch(function () { }); }, afterComplete: function (metadata) { if (events != null) { events.onComplete(metadata); } _this._onComplete(metadata); } })]; case 2: throw (0, error_1.newError)('No connection available'); } }); }); }) .catch(function (error) { if (events != null) { events.onError(error); } _this._onError(error).catch(function () { }); }) // It should make the transaction active anyway // further errors will be treated by the existing // observers .finally(function () { return _this._acceptActive(); }); }; /** * 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. * @param {mixed} query - Cypher query to execute * @param {Object} parameters - Map with parameters to use in query * @return {Result} New Result */ Transaction.prototype.run = function (query, parameters) { var _a = (0, util_1.validateQueryAndParameters)(query, parameters), validatedQuery = _a.validatedQuery, params = _a.params; var result = this._state.run(validatedQuery, params, { connectionHolder: this._connectionHolder, onError: this._onError, onComplete: this._onComplete, onConnection: this._onConnection, reactive: this._reactive, fetchSize: this._fetchSize, highRecordWatermark: this._highRecordWatermark, lowRecordWatermark: this._lowRecordWatermak, preparationJob: this._activePromise }); this._results.push(result); return result; }; /** * Commits the transaction and returns the result. * * After committing the transaction can no longer be used. * * @returns {Promise<void>} An empty promise if committed successfully or error if any error happened during commit. */ Transaction.prototype.commit = function () { var _this = this; var committed = this._state.commit({ connectionHolder: this._connectionHolder, onError: this._onError, onComplete: function (meta) { return _this._onCompleteCallback(meta, _this._bookmarks); }, onConnection: this._onConnection, pendingResults: this._results, preparationJob: this._activePromise }); this._state = committed.state; // clean up this._onClose(); return new Promise(function (resolve, reject) { committed.result.subscribe({ onCompleted: function () { return resolve(); }, onError: function (error) { return reject(error); } }); }); }; /** * Rollbacks the transaction. * * After rolling back, the transaction can no longer be used. * * @returns {Promise<void>} An empty promise if rolled back successfully or error if any error happened during * rollback. */ Transaction.prototype.rollback = function () { var rolledback = this._state.rollback({ connectionHolder: this._connectionHolder, onError: this._onError, onComplete: this._onComplete, onConnection: this._onConnection, pendingResults: this._results, preparationJob: this._activePromise }); this._state = rolledback.state; // clean up this._onClose(); return new Promise(function (resolve, reject) { rolledback.result.subscribe({ onCompleted: function () { return resolve(); }, onError: function (error) { return reject(error); } }); }); }; /** * Check if this transaction is active, which means commit and rollback did not happen. * @return {boolean} `true` when not committed and not rolled back, `false` otherwise. */ Transaction.prototype.isOpen = function () { return this._state === _states.ACTIVE; }; /** * Closes the transaction * * This method will roll back the transaction if it is not already committed or rolled back. * * @returns {Promise<void>} An empty promise if closed successfully or error if any error happened during */ Transaction.prototype.close = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.isOpen()) return [3 /*break*/, 2]; return [4 /*yield*/, this.rollback()]; case 1: _a.sent(); _a.label = 2; case 2: return [2 /*return*/]; } }); }); }; // eslint-disable-next-line // @ts-ignore Transaction.prototype[Symbol.asyncDispose] = function () { return this.close(); }; Transaction.prototype._onErrorCallback = function (error) { // error will be "acknowledged" by sending a RESET message // database will then forget about this transaction and cleanup all corresponding resources // it is thus safe to move this transaction to a FAILED state and disallow any further interactions with it this._state = _states.FAILED; this._onClose(); this._results.forEach(function (result) { if (result.isOpen()) { // @ts-expect-error result._streamObserverPromise .then(function (resultStreamObserver) { return resultStreamObserver.onError(error); }) // Nothing to do since we don't have a observer to notify the error // the result will be already broke in other ways. .catch(function (_) { }); } }); // release connection back to the pool return this._connectionHolder.releaseConnection(); }; /** * @private * @param {object} meta The meta with bookmarks * @returns {void} */ Transaction.prototype._onCompleteCallback = function (meta, previousBookmarks) { this._onBookmarks(new bookmarks_1.Bookmarks(meta === null || meta === void 0 ? void 0 : meta.bookmark), previousBookmarks !== null && previousBookmarks !== void 0 ? previousBookmarks : bookmarks_1.Bookmarks.empty(), meta === null || meta === void 0 ? void 0 : meta.db); }; return Transaction; }()); var _states = { // The transaction is running with no explicit success or failure marked ACTIVE: { commit: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete, onConnection = _a.onConnection, pendingResults = _a.pendingResults, preparationJob = _a.preparationJob; return { result: finishTransaction(true, connectionHolder, onError, onComplete, onConnection, pendingResults, preparationJob), state: _states.SUCCEEDED }; }, rollback: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete, onConnection = _a.onConnection, pendingResults = _a.pendingResults, preparationJob = _a.preparationJob; return { result: finishTransaction(false, connectionHolder, onError, onComplete, onConnection, pendingResults, preparationJob), state: _states.ROLLED_BACK }; }, run: function (query, parameters, _a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete, onConnection = _a.onConnection, reactive = _a.reactive, fetchSize = _a.fetchSize, highRecordWatermark = _a.highRecordWatermark, lowRecordWatermark = _a.lowRecordWatermark, preparationJob = _a.preparationJob; // RUN in explicit transaction can't contain bookmarks and transaction configuration // No need to include mode and database name as it shall be included in begin var requirements = preparationJob !== null && preparationJob !== void 0 ? preparationJob : Promise.resolve(); var observerPromise = connectionHolder.getConnection() .then(function (conn) { return requirements.then(function () { return conn; }); }) .then(function (conn) { onConnection(); if (conn != null) { return conn.run(query, parameters, { bookmarks: bookmarks_1.Bookmarks.empty(), txConfig: tx_config_1.TxConfig.empty(), beforeError: onError, afterComplete: onComplete, reactive: reactive, fetchSize: fetchSize, highRecordWatermark: highRecordWatermark, lowRecordWatermark: lowRecordWatermark }); } else { throw (0, error_1.newError)('No connection available'); } }) .catch(function (error) { return new observers_1.FailedObserver({ error: error, onError: onError }); }); return newCompletedResult(observerPromise, query, parameters, connectionHolder, highRecordWatermark, lowRecordWatermark); } }, // An error has occurred, transaction can no longer be used and no more messages will // be sent for this transaction. FAILED: { commit: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return { result: newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot commit this transaction, because it has been rolled back either because of an error or explicit termination.'), onError: onError }), 'COMMIT', {}, connectionHolder, 0, // high watermark 0 // low watermark ), state: _states.FAILED }; }, rollback: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return { result: newCompletedResult(new observers_1.CompletedObserver(), 'ROLLBACK', {}, connectionHolder, 0, // high watermark 0 // low watermark ), state: _states.FAILED }; }, run: function (query, parameters, _a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot run query in this transaction, because it has been rolled back either because of an error or explicit termination.'), onError: onError }), query, parameters, connectionHolder, 0, // high watermark 0 // low watermark ); } }, // This transaction has successfully committed SUCCEEDED: { commit: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return { result: newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot commit this transaction, because it has already been committed.'), onError: onError }), 'COMMIT', {}, connection_holder_1.EMPTY_CONNECTION_HOLDER, 0, // high watermark 0 // low watermark ), state: _states.SUCCEEDED, connectionHolder: connectionHolder }; }, rollback: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return { result: newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot rollback this transaction, because it has already been committed.'), onError: onError }), 'ROLLBACK', {}, connection_holder_1.EMPTY_CONNECTION_HOLDER, 0, // high watermark 0 // low watermark ), state: _states.SUCCEEDED, connectionHolder: connectionHolder }; }, run: function (query, parameters, _a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot run query in this transaction, because it has already been committed.'), onError: onError }), query, parameters, connectionHolder, 0, // high watermark 0 // low watermark ); } }, // This transaction has been rolled back ROLLED_BACK: { commit: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return { result: newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot commit this transaction, because it has already been rolled back.'), onError: onError }), 'COMMIT', {}, connectionHolder, 0, // high watermark 0 // low watermark ), state: _states.ROLLED_BACK }; }, rollback: function (_a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return { result: newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot rollback this transaction, because it has already been rolled back.') }), 'ROLLBACK', {}, connectionHolder, 0, // high watermark 0 // low watermark ), state: _states.ROLLED_BACK }; }, run: function (query, parameters, _a) { var connectionHolder = _a.connectionHolder, onError = _a.onError, onComplete = _a.onComplete; return newCompletedResult(new observers_1.FailedObserver({ error: (0, error_1.newError)('Cannot run query in this transaction, because it has already been rolled back.'), onError: onError }), query, parameters, connectionHolder, 0, // high watermark 0 // low watermark ); } } }; /** * * @param {boolean} commit * @param {ConnectionHolder} connectionHolder * @param {function(err:Error): any} onError * @param {function(metadata:object): any} onComplete * @param {function() : any} onConnection * @param {list<Result>>}pendingResults all run results in this transaction */ function finishTransaction(commit, connectionHolder, onError, onComplete, onConnection, pendingResults, preparationJob) { var requirements = preparationJob !== null && preparationJob !== void 0 ? preparationJob : Promise.resolve(); var observerPromise = connectionHolder.getConnection() .then(function (conn) { return requirements.then(function () { return conn; }); }) .then(function (connection) { onConnection(); pendingResults.forEach(function (r) { return r._cancel(); }); return Promise.all(pendingResults.map(function (result) { return result.summary(); })).then(function (results) { if (connection != null) { if (commit) { return connection.commitTransaction({ beforeError: onError, afterComplete: onComplete }); } else { return connection.rollbackTransaction({ beforeError: onError, afterComplete: onComplete }); } } else { throw (0, error_1.newError)('No connection available'); } }); }) .catch(function (error) { return new observers_1.FailedObserver({ error: error, onError: onError }); }); // for commit & rollback we need result that uses real connection holder and notifies it when // connection is not needed and can be safely released to the pool return new result_1.default(observerPromise, commit ? 'COMMIT' : 'ROLLBACK', {}, connectionHolder, { high: Number.MAX_VALUE, low: Number.MAX_VALUE }); } /** * Creates a {@link Result} with empty connection holder. * For cases when result represents an intermediate or failed action, does not require any metadata and does not * need to influence real connection holder to release connections. * @param {ResultStreamObserver} observer - an observer for the created result. * @param {string} query - the cypher query that produced the result. * @param {Object} parameters - the parameters for cypher query that produced the result. * @param {ConnectionHolder} connectionHolder - the connection holder used to get the result * @return {Result} new result. * @private */ function newCompletedResult(observerPromise, query, parameters, connectionHolder, highRecordWatermark, lowRecordWatermark) { if (connectionHolder === void 0) { connectionHolder = connection_holder_1.EMPTY_CONNECTION_HOLDER; } return new result_1.default(Promise.resolve(observerPromise), query, parameters, new connection_holder_1.ReadOnlyConnectionHolder(connectionHolder !== null && connectionHolder !== void 0 ? connectionHolder : connection_holder_1.EMPTY_CONNECTION_HOLDER), { low: lowRecordWatermark, high: highRecordWatermark }); } exports.default = Transaction;