UNPKG

postgrejs

Version:

Professional PostgreSQL client NodeJS

223 lines (222 loc) 8.6 kB
import { coerceToBoolean } from 'putil-varhelpers'; import { DEFAULT_COLUMN_FORMAT } from '../constants.js'; import { GlobalTypeMap } from '../data-type-map.js'; import { Protocol } from '../protocol/protocol.js'; import { SafeEventEmitter } from '../safe-event-emitter.js'; import { convertRowToObject } from '../util/convert-row-to-object.js'; import { getParsers } from '../util/get-parsers.js'; import { parseRow } from '../util/parse-row.js'; import { wrapRowDescription } from '../util/wrap-row-description.js'; import { Cursor } from './cursor.js'; import { getIntlConnection } from './intl-connection.js'; import { Portal } from './portal.js'; let statementCounter = 0; let portalCounter = 0; export class PreparedStatement extends SafeEventEmitter { constructor(connection, sql, paramTypes) { super(); this._sql = ''; this._name = ''; this._refCount = 0; this._connection = connection; this._name = 'S_' + ++statementCounter; this._sql = sql; this._paramTypes = paramTypes; this._onErrorSavePoint = 'SP_' + Math.round(Math.random() * 100000000); } static async prepare(connection, sql, options) { const intoCon = getIntlConnection(connection); intoCon.assertConnected(); const socket = intoCon.socket; const statement = new PreparedStatement(connection, sql, options?.paramTypes); await intoCon.statementQueue .enqueue(async () => { intoCon.ref(); try { socket.sendParseMessage({ statement: statement.name, sql: statement.sql, paramTypes: statement.paramTypes, }); socket.sendFlushMessage(); try { await socket.capture(async (code, msg, done) => { if (code === Protocol.BackendMessageCode.ParseComplete) done(); // May be Protocol.BackendMessageCode.NoticeResponse }); } finally { socket.sendSyncMessage(); await socket.capture(async (code, msg, done) => { if (code === Protocol.BackendMessageCode.ReadyForQuery) done(); // May be Protocol.BackendMessageCode.NoticeResponse }); } } finally { intoCon.unref(); } }) .toPromise(); statement._refCount = 1; return statement; } get connection() { return this._connection; } get name() { return this._name; } get sql() { return this._sql; } get paramTypes() { return this._paramTypes; } async execute(options = {}) { const intlCon = getIntlConnection(this.connection); const transactionCommand = this.sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i); let beginFirst = false; let commitLast = false; if (!transactionCommand) { if (!intlCon.inTransaction && (options?.autoCommit != null ? options?.autoCommit : intlCon.config.autoCommit) === false) { beginFirst = true; } if (intlCon.inTransaction && options?.autoCommit) commitLast = true; } if (beginFirst) await intlCon.execute('BEGIN'); const rollbackOnError = !transactionCommand && (options?.rollbackOnError != null ? options.rollbackOnError : coerceToBoolean(intlCon.config.rollbackOnError, true)); if (intlCon.inTransaction && rollbackOnError) await intlCon.execute('SAVEPOINT ' + this._onErrorSavePoint); try { const result = await intlCon.statementQueue .enqueue(() => this._execute(options)) .toPromise(); if (commitLast) await intlCon.execute('COMMIT'); else if (intlCon.inTransaction && rollbackOnError) { await intlCon.execute('RELEASE ' + this._onErrorSavePoint + ';'); } return result; } catch (e) { if (intlCon.inTransaction && rollbackOnError) { await intlCon.execute('ROLLBACK TO ' + this._onErrorSavePoint + ';'); } throw e; } } async close() { --this._refCount; if (this._refCount > 0) return; const intoCon = getIntlConnection(this.connection); await intoCon.statementQueue.enqueue(() => this._close()).toPromise(); } async cancel() { throw new Error('Not implemented yet'); } async _execute(options = {}) { let portal; const intlCon = getIntlConnection(this.connection); intlCon.ref(); try { const result = { command: undefined }; const startTime = Date.now(); const t = Date.now(); // Create portal const portalName = 'P_' + ++portalCounter; portal = new Portal(this, portalName); await portal.bind(options.params, options); const fields = await portal.retrieveFields(); const typeMap = options.typeMap || GlobalTypeMap; let parsers; let resultFields; if (fields) { parsers = getParsers(typeMap, fields); resultFields = wrapRowDescription(typeMap, fields, options.columnFormat || DEFAULT_COLUMN_FORMAT); result.fields = resultFields; result.rowType = options.objectRows ? 'object' : 'array'; if (options.cursor) { result.cursor = new Cursor(this, portal, resultFields, parsers, options); this._refCount++; portal = undefined; return result; } } const executeResult = await portal.execute(options.fetchCount); result.executeTime = Date.now() - t; if (executeResult.command) result.command = executeResult.command; if (resultFields && parsers && executeResult.rows) { if (!result.command) result.command = 'SELECT'; const rows = (result.rows = executeResult.rows); const l = rows.length; let row; for (let i = 0; i < l; i++) { row = rows[i]; parseRow(parsers, row, options); if (options.objectRows) { rows[i] = convertRowToObject(resultFields, row); } } } if (result.command === 'DELETE' || result.command === 'INSERT' || result.command === 'UPDATE') { result.rowsAffected = executeResult.rowCount; } result.executeTime = Date.now() - startTime; return result; } finally { intlCon.unref(); if (portal) await portal.close(); } } async _close() { if (--this._refCount > 0) return; const intoCon = getIntlConnection(this.connection); intoCon.ref(); try { const socket = intoCon.socket; socket.sendCloseMessage({ type: 'S', name: this.name }); socket.sendSyncMessage(); await socket.capture(async (code, msg, done) => { switch (code) { case Protocol.BackendMessageCode.NoticeResponse: this.emit('notice', msg); break; case Protocol.BackendMessageCode.CloseComplete: break; case Protocol.BackendMessageCode.ReadyForQuery: intoCon.transactionStatus = msg.status; done(); break; default: done(new Error(`Server returned unexpected response message (0x${code.toString(16)})`)); } }); } finally { intoCon.unref(); } this.emit('close'); } [Symbol.asyncDispose]() { return this.close(); } }