UNPKG

postgrejs

Version:

Professional PostgreSQL client NodeJS

296 lines (295 loc) 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Connection = void 0; const constants_js_1 = require("../constants.js"); const data_type_map_js_1 = require("../data-type-map.js"); const safe_event_emitter_js_1 = require("../safe-event-emitter.js"); const bind_param_js_1 = require("./bind-param.js"); const intl_connection_js_1 = require("./intl-connection.js"); const prepared_statement_js_1 = require("./prepared-statement.js"); class Connection extends safe_event_emitter_js_1.SafeEventEmitter { constructor(arg0, arg1) { super(); this._notificationListeners = new safe_event_emitter_js_1.SafeEventEmitter(); this._closing = false; if (arg0 && typeof arg0 === 'object' && typeof arg0.acquire === 'function') { if (!(arg1 instanceof intl_connection_js_1.IntlConnection)) throw new TypeError('Invalid argument'); this._pool = arg0; this._intlCon = arg1; } else { this._intlCon = new intl_connection_js_1.IntlConnection(arg0); } this._intlCon.on('ready', (...args) => this.emit('ready', ...args)); this._intlCon.on('error', (...args) => this.emit('error', ...args)); this._intlCon.on('close', (...args) => this.emit('close', ...args)); this._intlCon.on('connecting', (...args) => this.emit('connecting', ...args)); this._intlCon.on('ready', (...args) => this.emit('ready', ...args)); this._intlCon.on('terminate', (...args) => this.emit('terminate', ...args)); this._intlCon.on('notification', (msg) => this._handleNotification(msg)); } /** * Returns configuration object */ get config() { return this._intlCon.config; } /** * Returns true if connection is in a transaction */ get inTransaction() { return this._intlCon.inTransaction; } /** * Returns current state of the connection */ get state() { return this._intlCon.state; } /** * Returns processId of current session */ get processID() { return this._intlCon.processID; } /** * Returns information parameters for current session */ get sessionParameters() { return this._intlCon.sessionParameters; } /** * Returns secret key of current session */ get secretKey() { return this._intlCon.secretKey; } /** * Connects to the server */ async connect() { await this._captureErrorStack(this._intlCon.connect()); if (this.state === constants_js_1.ConnectionState.READY) this._closing = false; } /** * Closes connection. You can define how long time the connection will * wait for active queries before terminating the connection. * On the end of the given time, it forces to close the socket and than emits `terminate` event. * * @param terminateWait {number} - Determines how long the connection will wait for active queries before terminating. */ async close(terminateWait) { this._notificationListeners.removeAllListeners(); this._intlCon.statementQueue.clearQueue(); if (this.state === constants_js_1.ConnectionState.CLOSED || this._closing) return; /* istanbul ignore next */ if (this.listenerCount('debug')) { this.emit('debug', { location: 'Connection.close', connection: this, message: `[${this.processID}] closing`, }); } this._closing = true; if (this._intlCon.refCount > 0 && typeof terminateWait === 'number' && terminateWait > 0) { const startTime = Date.now(); return this._captureErrorStack(new Promise((resolve, reject) => { /* istanbul ignore next */ if (this.listenerCount('debug')) { this.emit('debug', { location: 'Connection.close', connection: this, message: `[${this.processID}] waiting active queries`, }); } const timer = setInterval(() => { if (this._intlCon.refCount <= 0 || Date.now() > startTime + terminateWait) { clearInterval(timer); if (this._intlCon.refCount > 0) { /* istanbul ignore next */ if (this.listenerCount('debug')) { this.emit('debug', { location: 'Connection.close', connection: this, message: `[${this.processID}] terminate`, }); } this.emit('terminate'); } this._close().then(resolve).catch(reject); } }, 50); })); } await this._close(); } /** * Executes single or multiple SQL scripts using Simple Query protocol. * * @param sql {string} - SQL script that will be executed * @param options {ScriptExecuteOptions} - Execute options */ async execute(sql, options) { this.emit('execute', sql, options); return this._captureErrorStack(this._intlCon.execute(sql, options)).catch((e) => { throw this._handleError(e, sql); }); } async query(sql, options) { this._intlCon.assertConnected(); /* istanbul ignore next */ if (this.listenerCount('debug')) { this.emit('debug', { location: 'Connection.query', connection: this, message: `[${this.processID}] query | ${sql}`, sql, }); } this.emit('query', sql, options); const typeMap = options?.typeMap || data_type_map_js_1.GlobalTypeMap; const paramTypes = options?.params?.map(prm => prm instanceof bind_param_js_1.BindParam ? prm.oid : typeMap.determine(prm)); const statement = await this.prepare(sql, { paramTypes, typeMap }).catch((e) => { throw this._handleError(e, sql); }); try { const params = options?.params?.map(prm => prm instanceof bind_param_js_1.BindParam ? prm.value : prm); return await this._captureErrorStack(statement.execute({ ...options, params })); } finally { await statement.close(); } } /** * Creates a PreparedStatement instance * @param sql {string} - SQL script that will be executed * @param options {StatementPrepareOptions} - Options */ async prepare(sql, options) { /* istanbul ignore next */ if (this.listenerCount('debug')) { this.emit('debug', { location: 'Connection.prepare', connection: this, message: `[${this.processID}] prepare | ${sql}`, sql, }); } return await this._captureErrorStack(prepared_statement_js_1.PreparedStatement.prepare(this, sql, options)); } /** * Starts a transaction */ startTransaction() { return this._captureErrorStack(this._intlCon.startTransaction()); } /** * Commits current transaction */ commit() { return this._captureErrorStack(this._intlCon.commit()); } /** * Rolls back current transaction */ rollback() { return this._captureErrorStack(this._intlCon.rollback()); } /** * Starts transaction and creates a savepoint * @param name {string} - Name of the savepoint */ async savepoint(name) { if (!this._intlCon.inTransaction) await this._intlCon.startTransaction(); return this._captureErrorStack(this._intlCon.savepoint(name)); } /** * Rolls back current transaction to given savepoint * @param name {string} - Name of the savepoint */ rollbackToSavepoint(name) { return this._captureErrorStack(this._intlCon.rollbackToSavepoint(name)); } /** * Releases savepoint * @param name {string} - Name of the savepoint */ releaseSavepoint(name) { return this._captureErrorStack(this._intlCon.releaseSavepoint(name)); } async listen(channel, callback) { if (!/^[A-Z]\w+$/i.test(channel)) throw new TypeError(`Invalid channel name`); const registered = !!this._notificationListeners.eventNames().length; this._notificationListeners.on(channel, callback); if (!registered) await this._captureErrorStack(this.query('LISTEN ' + channel)); } async unListen(channel) { if (!/^[A-Z]\w+$/i.test(channel)) throw new TypeError(`Invalid channel name`); this._notificationListeners.removeAllListeners(channel); await this._captureErrorStack(this.query('UNLISTEN ' + channel)); } async unListenAll() { this._notificationListeners.removeAllListeners(); await this._captureErrorStack(this.query('UNLISTEN *')); } _handleNotification(msg) { this.emit('notification', msg); this._notificationListeners.emit(msg.channel, msg); } async _close() { if (this._pool) { await this._captureErrorStack(this._pool.release(this)); this.emit('release'); } else await this._captureErrorStack(this._intlCon.close()); this._closing = false; } _handleError(err, script) { if (err.position != null) { const i1 = script.lastIndexOf('\n', err.position - 1) + 1; err.lineNr = [...script.substring(0, i1).matchAll(/\n/g)].length + 1; err.colNr = err.position - i1; const lines = script.split('\n'); err.line = lines[err.lineNr - 1]; err.message += `\n at line ${err.lineNr} column ${err.colNr}`; if (err.lineNr > 1) err.message += `\n${String(err.lineNr - 1).padStart(3)}| ${lines[err.lineNr - 2]}`; err.message += `\n${String(err.lineNr).padStart(3)}| ${err.line}\n .${'-'.repeat(Math.max(err.colNr - 1, 0))}^`; } return err; } async _captureErrorStack(promise) { const stack = new Error().stack; return promise.catch(e => { if (e instanceof Error && stack) { if (e.stack && stack) { e.stack = e.stack.substring(0, e.stack.indexOf('\n')) + '\n' + stack .split('\n') .filter((x, i) => i && !x.includes('._captureErrorStack')) .join('\n'); } } throw e; }); } [Symbol.asyncDispose]() { return this.close(); } } exports.Connection = Connection;