UNPKG

@sqb/connect

Version:

Multi-dialect database connection framework written with TypeScript

281 lines (280 loc) 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SqbConnection = void 0; const tslib_1 = require("tslib"); const builder_1 = require("@sqb/builder"); const assert_1 = tslib_1.__importDefault(require("assert")); const debug_1 = tslib_1.__importDefault(require("debug")); const power_tasks_1 = require("power-tasks"); const putil_varhelpers_1 = require("putil-varhelpers"); const strict_typed_events_1 = require("strict-typed-events"); const entity_metadata_js_1 = require("../orm/model/entity-metadata.js"); const repository_class_js_1 = require("../orm/repository.class.js"); const cursor_js_1 = require("./cursor.js"); const helpers_js_1 = require("./helpers.js"); const debug = (0, debug_1.default)('sqb:connection'); class SqbConnection extends (0, strict_typed_events_1.TypedEventEmitterClass)(strict_typed_events_1.AsyncEventEmitter) { constructor(client, adapterConnection, options) { super(); this.client = client; this._tasks = new power_tasks_1.TaskQueue(); this._inTransaction = false; this._refCount = 1; this._intlcon = adapterConnection; this._options = options || {}; } /** * Returns session id */ get sessionId() { return this._intlcon && this._intlcon.sessionId; } /** * Returns reference counter value */ get refCount() { return this._refCount; } /** * Returns true if transaction started */ get inTransaction() { if (this._intlcon && typeof this._intlcon.getInTransaction === 'function') { this._inTransaction = this._intlcon.getInTransaction(); } return this._inTransaction; } /** * Increases internal reference counter to keep session alive */ retain() { this._refCount++; debug('[%s] retain | refCount: %s', this.sessionId, this._refCount); this.emit('retain', this._refCount); } /** * Decreases the internal reference counter. * When reference count is 0, connection returns to the pool. * Returns true if connection released. */ release() { if (!this._intlcon) return true; const ref = --this._refCount; this.emit('release', this._refCount); debug('[%s] release | refCount: %s', this.sessionId, ref); if (!ref) { this.close().catch(() => 0); return true; } return false; } /** * Immediately releases the connection. */ async close() { if (!this._intlcon) return; await this.emitAsyncSerial('close'); const intlcon = this._intlcon; this._intlcon = undefined; this.client.pool.release(intlcon, (e) => { if (e) this.client.emit('error', e); }); debug('[%s] closed', intlcon.sessionId); } async execute(query, options) { if (!this._intlcon) throw new Error(`Can't execute query, because connection is released`); return this._tasks.enqueue(() => this._execute(query, options)).toPromise(); } getRepository(entity, opts) { let ctor; if (typeof entity === 'string') { ctor = this.client.getEntity(entity); if (!ctor) throw new Error(`Repository "${entity}" is not registered`); } else ctor = entity; const entityDef = entity_metadata_js_1.EntityMetadata.get(ctor); if (!entityDef) throw new Error(`You must provide an @Entity annotated constructor`); return new repository_class_js_1.Repository(entityDef, this, opts?.schema); } async getSchema() { assert_1.default.ok(this._intlcon, `Can't set schema, because connection is released`); assert_1.default.ok(this._intlcon.getSchema, `${this.client.dialect} adapter does have Schema support`); return await this._intlcon.getSchema(); } async setSchema(schema) { assert_1.default.ok(this._intlcon, `Can't set schema, because connection is released`); assert_1.default.ok(this._intlcon.setSchema, `${this.client.dialect} adapter does have Schema support`); await this._intlcon.setSchema(schema); } /** * Executes a query */ async _execute(query, options) { assert_1.default.ok(this._intlcon, `Can't execute query, because connection is released`); const intlcon = this._intlcon; this.retain(); try { const startTime = Date.now(); const request = this._prepareQueryRequest(query, options); debug('[%s] execute | %o', this.sessionId, request); this.emit('execute', request); // Call execute hooks if (request.executeHooks) { for (const fn of request.executeHooks) { await fn(this, request); } } const response = await intlcon.execute(request); if (!response) throw new Error('Database adapter returned an empty response'); const result = { executeTime: Date.now() - startTime, }; if (request.showSql) result.query = request; if (response.rows || response.cursor) { if (!response.fields) throw new Error('Adapter did not returned fields info'); if (!response.rowType) throw new Error('Adapter did not returned rowType'); result.fields = (0, helpers_js_1.wrapAdapterFields)(response.fields, request.fieldNaming); result.rowType = response.rowType; if (response.rows) { result.rows = request.objectRows ? (0, helpers_js_1.normalizeRowsToObjectRows)(result.fields, response.rowType, response.rows, request) : (0, helpers_js_1.normalizeRowsToArrayRows)(result.fields, response.rowType, response.rows, request); (0, helpers_js_1.callFetchHooks)(result.rows, request); } else if (response.cursor) { const cursor = (result.cursor = new cursor_js_1.Cursor(this, result.fields, response.cursor, request)); const hook = () => cursor.close(); cursor.once('close', () => this.off('close', hook)); this.on('close', hook); } } if (response.rowsAffected) result.rowsAffected = response.rowsAffected; return result; } finally { this.release(); } } async startTransaction() { if (!this._intlcon) throw new Error('Can not call startTransaction() on a released connection'); await this._intlcon.startTransaction(); this._inTransaction = true; this.emit('start-transaction'); } async commit() { if (!this._intlcon) throw new Error('Can not call commit() on a released connection'); await this._intlcon.commit(); this._inTransaction = false; this.emit('commit'); } async rollback() { if (!this._intlcon) throw new Error('Can not call rollback() on a released connection'); await this._intlcon.rollback(); this._inTransaction = false; this.emit('rollback'); } async setSavepoint(savepoint) { if (!this._intlcon) throw new Error('Can not call setSavepoint() on a released connection'); if (typeof this._intlcon.setSavepoint !== 'function') { throw new Error(this.client.driver + ' does not support setSavepoint method'); } if (!this.inTransaction) await this.startTransaction(); await this._intlcon.setSavepoint(savepoint); this.emit('set-savepoint'); } async releaseSavepoint(savepoint) { if (!this._intlcon) throw new Error('Can not call releaseSavepoint() on a released connection'); if (typeof this._intlcon.releaseSavepoint !== 'function') { throw new Error(this.client.driver + ' does not support releaseSavepoint method'); } await this._intlcon.releaseSavepoint(savepoint); this.emit('release-savepoint'); } async rollbackSavepoint(savepoint) { if (!this._intlcon) throw new Error('Can not call rollbackSavepoint() on a released connection'); if (typeof this._intlcon.rollbackSavepoint !== 'function') { throw new Error(this.client.driver + ' does not support rollbackSavepoint method'); } await this._intlcon.rollbackSavepoint(savepoint); this.emit('rollback-savepoint'); } async test() { if (!this._intlcon) throw new Error('Can not call test() on a released connection'); await this._intlcon.test(); } _prepareQueryRequest(query, options = {}) { if (!this._intlcon) throw new Error('Session released'); const defaults = this.client.defaults; const request = { dialect: this.client.dialect, sql: '', autoCommit: this.inTransaction ? false : (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.autoCommit, this._options?.autoCommit, defaults.autoCommit), true), cursor: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.cursor, defaults.cursor), false), objectRows: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.objectRows, defaults.objectRows), true), ignoreNulls: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.ignoreNulls, defaults.ignoreNulls), false), fetchRows: (0, putil_varhelpers_1.coerceToInt)((0, putil_varhelpers_1.coalesce)(options.fetchRows, defaults.fetchRows), 100), fieldNaming: (0, putil_varhelpers_1.coalesce)(options.namingStrategy, defaults.fieldNaming), transform: (0, putil_varhelpers_1.coalesce)(options.transform, defaults.transform), showSql: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.showSql, defaults.showSql), false), prettyPrint: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.prettyPrint, defaults.prettyPrint), false), action: (0, putil_varhelpers_1.coerceToString)(options.action), fetchAsString: options.fetchAsString, }; request.ignoreNulls = request.ignoreNulls && request.objectRows; if (query instanceof builder_1.classes.Query) { if (this._intlcon.onGenerateQuery) this._intlcon.onGenerateQuery(request, query); const q = query.generate({ dialect: request.dialect, dialectVersion: request.dialectVersion, params: options.params, strictParams: true, prettyPrint: request.prettyPrint, }); request.sql = q.sql; request.params = q.params; request.paramOptions = q.paramOptions; if (q.returningFields) request.returningFields = q.returningFields; if (query.listenerCount('execute')) request.executeHooks = query.listeners('execute'); if (query.listenerCount('fetch')) request.fetchHooks = query.listeners('fetch'); } else { // noinspection SuspiciousTypeOfGuard if (typeof query === 'string') { request.sql = query; request.params = options.params; } } // @ts-ignore if (!request.sql) throw new Error('No sql given'); return request; } } exports.SqbConnection = SqbConnection;