UNPKG

postgrejs

Version:

Professional PostgreSQL client NodeJS

259 lines (258 loc) 11.1 kB
"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; }