postgrejs
Version:
Professional PostgreSQL client NodeJS
296 lines (295 loc) • 11.3 kB
JavaScript
"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;