postgrejs
Version:
Professional PostgreSQL client NodeJS
259 lines (258 loc) • 11.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IntlConnection = void 0;
exports.getIntlConnection = getIntlConnection;
const power_tasks_1 = require("power-tasks");
const putil_varhelpers_1 = require("putil-varhelpers");
const constants_js_1 = require("../constants.js");
const data_type_map_js_1 = require("../data-type-map.js");
const pg_socket_js_1 = require("../protocol/pg-socket.js");
const protocol_js_1 = require("../protocol/protocol.js");
const safe_event_emitter_js_1 = require("../safe-event-emitter.js");
const connection_config_js_1 = require("../util/connection-config.js");
const convert_row_to_object_js_1 = require("../util/convert-row-to-object.js");
const escape_literal_js_1 = require("../util/escape-literal.js");
const get_parsers_js_1 = require("../util/get-parsers.js");
const parse_row_js_1 = require("../util/parse-row.js");
const wrap_row_description_js_1 = require("../util/wrap-row-description.js");
const DataFormat = protocol_js_1.Protocol.DataFormat;
class IntlConnection extends safe_event_emitter_js_1.SafeEventEmitter {
constructor(config) {
super();
this._refCount = 0;
this.transactionStatus = 'I';
this.statementQueue = new power_tasks_1.TaskQueue({ concurrency: 1 });
this._config = Object.freeze((0, connection_config_js_1.getConnectionConfig)(config));
this.socket = new pg_socket_js_1.PgSocket(this._config);
this.socket.on('error', err => this._onError(err));
this.socket.on('close', () => this.emit('close'));
this.socket.on('notification', payload => this.emit('notification', payload));
this.socket.on('connecting', () => this.emit('connecting'));
this._onErrorSavePoint = 'SP_' + Math.round(Math.random() * 100000000);
}
get config() {
return this._config;
}
get inTransaction() {
return this.transactionStatus === 'T' || this.transactionStatus === 'E';
}
get state() {
return this.socket.state;
}
get refCount() {
return this._refCount;
}
get processID() {
return this.socket.processID;
}
get secretKey() {
return this.socket.secretKey;
}
get sessionParameters() {
return this.socket.sessionParameters;
}
async connect() {
if (this.socket.state === constants_js_1.ConnectionState.READY)
return;
await new Promise((resolve, reject) => {
const handleConnectError = (err) => reject(err);
this.socket.once('ready', () => {
this.socket.removeListener('error', handleConnectError);
resolve();
});
this.socket.once('error', handleConnectError);
this.socket.connect();
});
let startupCommand = '';
if (this.config.schema)
startupCommand +=
'SET search_path = ' + (0, escape_literal_js_1.escapeLiteral)(this.config.schema) + ';';
if (this.config.timezone)
startupCommand +=
'SET timezone TO ' + (0, escape_literal_js_1.escapeLiteral)(this.config.timezone) + ';';
if (startupCommand)
await this.execute(startupCommand, { autoCommit: true });
this.emit('ready');
}
async close() {
if (this.state === constants_js_1.ConnectionState.CLOSED)
return;
this.statementQueue.clearQueue();
return new Promise(resolve => {
if (this.socket.state === constants_js_1.ConnectionState.CLOSED)
return;
this.socket.once('close', resolve);
this.socket.sendTerminateMessage(() => {
this.socket.close();
this.emit('close');
});
});
}
async execute(sql, options, cb) {
this.assertConnected();
return this.statementQueue
.enqueue(async () => {
const transactionCommand = sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
let beginFirst = false;
let commitLast = false;
if (!transactionCommand) {
if (!this.inTransaction &&
(options?.autoCommit != null
? options?.autoCommit
: this.config.autoCommit) === false) {
beginFirst = true;
}
if (this.inTransaction && options?.autoCommit)
commitLast = true;
}
if (beginFirst)
await this._execute('BEGIN');
const rollbackOnError = !transactionCommand &&
(options?.rollbackOnError != null
? options.rollbackOnError
: (0, putil_varhelpers_1.coerceToBoolean)(this.config.rollbackOnError, true));
if (this.inTransaction && rollbackOnError)
await this._execute('SAVEPOINT ' + this._onErrorSavePoint);
try {
const result = await this._execute(sql, options, cb);
if (commitLast)
await this._execute('COMMIT');
else if (this.inTransaction && rollbackOnError) {
await this._execute('RELEASE ' + this._onErrorSavePoint + ';');
}
return result;
}
catch (e) {
if (this.inTransaction && rollbackOnError)
await this._execute('ROLLBACK TO ' + this._onErrorSavePoint + ';');
throw e;
}
})
.toPromise();
}
async startTransaction() {
if (!this.inTransaction)
await this.execute('BEGIN');
}
async savepoint(name) {
if (!(name && name.match(/^[a-zA-Z]\w+$/)))
throw new Error(`Invalid savepoint "${name}"`);
await this.execute('BEGIN; SAVEPOINT ' + name);
}
async commit() {
if (this.inTransaction)
await this.execute('COMMIT');
}
async rollback() {
if (this.inTransaction)
await this.execute('ROLLBACK');
}
async rollbackToSavepoint(name) {
if (!(name && name.match(/^[a-zA-Z]\w+$/)))
throw new Error(`Invalid savepoint "${name}"`);
await this.execute('ROLLBACK TO SAVEPOINT ' + name, { autoCommit: false });
}
async releaseSavepoint(name) {
if (!(name && name.match(/^[a-zA-Z]\w+$/)))
throw new Error(`Invalid savepoint "${name}"`);
await this.execute('RELEASE SAVEPOINT ' + name, { autoCommit: false });
}
ref() {
this._refCount++;
}
unref() {
this._refCount--;
return !this._refCount;
}
assertConnected() {
if (this.state === constants_js_1.ConnectionState.CLOSING)
throw new Error('Connection is closing');
if (this.state === constants_js_1.ConnectionState.CLOSED)
throw new Error('Connection closed');
}
async _execute(sql, options, cb) {
this.ref();
try {
const startTime = Date.now();
const result = {
totalCommands: 0,
totalTime: 0,
results: [],
};
const opts = options || {};
this.socket.sendQueryMessage(sql);
let currentStart = Date.now();
let parsers;
let current = { command: undefined };
let fields;
const typeMap = opts.typeMap || data_type_map_js_1.GlobalTypeMap;
return await this.socket.capture(async (code, msg, done) => {
switch (code) {
case protocol_js_1.Protocol.BackendMessageCode.NoticeResponse:
case protocol_js_1.Protocol.BackendMessageCode.CopyInResponse:
case protocol_js_1.Protocol.BackendMessageCode.CopyOutResponse:
case protocol_js_1.Protocol.BackendMessageCode.EmptyQueryResponse:
break;
case protocol_js_1.Protocol.BackendMessageCode.RowDescription:
fields = msg.fields;
parsers = (0, get_parsers_js_1.getParsers)(typeMap, fields);
current.fields = (0, wrap_row_description_js_1.wrapRowDescription)(typeMap, fields, DataFormat.text);
current.rows = [];
break;
case protocol_js_1.Protocol.BackendMessageCode.DataRow:
{
let row = msg.columns.map((x) => x.toString('utf8'));
// The null override assumes we can trust PG to always send the RowDescription first
(0, parse_row_js_1.parseRow)(parsers, row, opts);
if (opts.objectRows && current.fields)
row = (0, convert_row_to_object_js_1.convertRowToObject)(current.fields, row);
if (cb)
cb('row', row);
current.rows = current.rows || [];
current.rows.push(row);
}
break;
case protocol_js_1.Protocol.BackendMessageCode.CommandComplete:
// Ignore BEGIN command that we added to sql
current.command = msg.command;
if (current.command === 'DELETE' ||
current.command === 'INSERT' ||
current.command === 'UPDATE') {
current.rowsAffected = msg.rowCount;
}
current.executeTime = Date.now() - currentStart;
if (current.rows)
current.rowType =
opts.objectRows && current.fields ? 'object' : 'array';
result.results.push(current);
if (cb)
cb('command-complete', current);
current = { command: undefined };
currentStart = Date.now();
break;
case protocol_js_1.Protocol.BackendMessageCode.ReadyForQuery:
this.transactionStatus = msg.status;
result.totalTime = Date.now() - startTime;
// Ignore COMMIT command that we added to sql
result.totalCommands = result.results.length;
done(undefined, result);
break;
default:
break;
}
});
}
finally {
this.unref();
}
}
_onError(err) {
if (this.socket.state !== constants_js_1.ConnectionState.READY)
return;
this.emit('error', err);
}
}
exports.IntlConnection = IntlConnection;
function getIntlConnection(connection) {
return connection._intlCon;
}