UNPKG

kysely

Version:
349 lines (348 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MssqlDriver = void 0; const object_utils_js_1 = require("../../util/object-utils.js"); const compiled_query_js_1 = require("../../query-compiler/compiled-query.js"); const stack_trace_utils_js_1 = require("../../util/stack-trace-utils.js"); const random_string_js_1 = require("../../util/random-string.js"); const deferred_js_1 = require("../../util/deferred.js"); const PRIVATE_RESET_METHOD = Symbol(); const PRIVATE_DESTROY_METHOD = Symbol(); class MssqlDriver { #config; #pool; constructor(config) { this.#config = (0, object_utils_js_1.freeze)({ ...config }); const { tarn, tedious, validateConnections } = this.#config; const { validateConnections: deprecatedValidateConnections, ...poolOptions } = tarn.options; this.#pool = new tarn.Pool({ ...poolOptions, create: async () => { const connection = await tedious.connectionFactory(); return await new MssqlConnection(connection, tedious).connect(); }, destroy: async (connection) => { await connection[PRIVATE_DESTROY_METHOD](); }, // @ts-ignore `tarn` accepts a function that returns a promise here, but // the types are not aligned and it type errors. validate: validateConnections === false || deprecatedValidateConnections === false ? undefined : (connection) => connection.validate(), }); } async init() { // noop } async acquireConnection() { return await this.#pool.acquire().promise; } async beginTransaction(connection, settings) { await connection.beginTransaction(settings); } async commitTransaction(connection) { await connection.commitTransaction(); } async rollbackTransaction(connection) { await connection.rollbackTransaction(); } async savepoint(connection, savepointName) { await connection.savepoint(savepointName); } async rollbackToSavepoint(connection, savepointName) { await connection.rollbackTransaction(savepointName); } async releaseConnection(connection) { if (this.#config.resetConnectionsOnRelease || this.#config.tedious.resetConnectionOnRelease) { await connection[PRIVATE_RESET_METHOD](); } this.#pool.release(connection); } async destroy() { await this.#pool.destroy(); } } exports.MssqlDriver = MssqlDriver; class MssqlConnection { #connection; #tedious; constructor(connection, tedious) { this.#connection = connection; this.#tedious = tedious; this.#connection.on('error', console.error); this.#connection.once('end', () => { this.#connection.off('error', console.error); }); } async beginTransaction(settings) { const { isolationLevel } = settings; await new Promise((resolve, reject) => this.#connection.beginTransaction((error) => { if (error) reject(error); else resolve(undefined); }, isolationLevel ? (0, random_string_js_1.randomString)(8) : undefined, isolationLevel ? this.#getTediousIsolationLevel(isolationLevel) : undefined)); } async commitTransaction() { await new Promise((resolve, reject) => this.#connection.commitTransaction((error) => { if (error) reject(error); else resolve(undefined); })); } async connect() { await new Promise((resolve, reject) => { this.#connection.connect((error) => { if (error) { console.error(error); reject(error); } else { resolve(undefined); } }); }); return this; } async executeQuery(compiledQuery) { try { const deferred = new deferred_js_1.Deferred(); const request = new MssqlRequest({ compiledQuery, tedious: this.#tedious, onDone: deferred, }); this.#connection.execSql(request.request); const { rowCount, rows } = await deferred.promise; return { numAffectedRows: rowCount !== undefined ? BigInt(rowCount) : undefined, rows, }; } catch (err) { throw (0, stack_trace_utils_js_1.extendStackTrace)(err, new Error()); } } async rollbackTransaction(savepointName) { await new Promise((resolve, reject) => this.#connection.rollbackTransaction((error) => { if (error) reject(error); else resolve(undefined); }, savepointName)); } async savepoint(savepointName) { await new Promise((resolve, reject) => this.#connection.saveTransaction((error) => { if (error) reject(error); else resolve(undefined); }, savepointName)); } async *streamQuery(compiledQuery, chunkSize) { if (!Number.isInteger(chunkSize) || chunkSize <= 0) { throw new Error('chunkSize must be a positive integer'); } const request = new MssqlRequest({ compiledQuery, streamChunkSize: chunkSize, tedious: this.#tedious, }); this.#connection.execSql(request.request); try { while (true) { const rows = await request.readChunk(); if (rows.length === 0) { break; } yield { rows }; if (rows.length < chunkSize) { break; } } } finally { await this.#cancelRequest(request); } } async validate() { try { const deferred = new deferred_js_1.Deferred(); const request = new MssqlRequest({ compiledQuery: compiled_query_js_1.CompiledQuery.raw('select 1'), onDone: deferred, tedious: this.#tedious, }); this.#connection.execSql(request.request); await deferred.promise; return true; } catch { return false; } } #getTediousIsolationLevel(isolationLevel) { const { ISOLATION_LEVEL } = this.#tedious; const mapper = { 'read committed': ISOLATION_LEVEL.READ_COMMITTED, 'read uncommitted': ISOLATION_LEVEL.READ_UNCOMMITTED, 'repeatable read': ISOLATION_LEVEL.REPEATABLE_READ, serializable: ISOLATION_LEVEL.SERIALIZABLE, snapshot: ISOLATION_LEVEL.SNAPSHOT, }; const tediousIsolationLevel = mapper[isolationLevel]; if (tediousIsolationLevel === undefined) { throw new Error(`Unknown isolation level: ${isolationLevel}`); } return tediousIsolationLevel; } #cancelRequest(request) { return new Promise((resolve) => { request.request.once('requestCompleted', resolve); const wasCanceled = this.#connection.cancel(); if (!wasCanceled) { request.request.off('requestCompleted', resolve); resolve(undefined); } }); } async [PRIVATE_RESET_METHOD]() { await new Promise((resolve, reject) => { this.#connection.reset((error) => { if (error) reject(error); else resolve(undefined); }); }); } [PRIVATE_DESTROY_METHOD]() { return new Promise((resolve) => { this.#connection.once('end', () => { resolve(undefined); }); this.#connection.close(); }); } } class MssqlRequest { #request; #rows; #streamChunkSize; #subscribers; #tedious; #rowCount; constructor(props) { const { compiledQuery, onDone, streamChunkSize, tedious } = props; this.#rows = []; this.#streamChunkSize = streamChunkSize; this.#subscribers = {}; this.#tedious = tedious; if (onDone) { const subscriptionKey = 'onDone'; this.#subscribers[subscriptionKey] = (event, error) => { if (event === 'chunkReady') { return; } delete this.#subscribers[subscriptionKey]; if (event === 'error') { onDone.reject(error); } else { onDone.resolve({ rowCount: this.#rowCount, rows: this.#rows, }); } }; } this.#request = new this.#tedious.Request(compiledQuery.sql, (err, rowCount) => { if (err) { Object.values(this.#subscribers).forEach((subscriber) => subscriber('error', err instanceof AggregateError ? err.errors : err)); } else { this.#rowCount = rowCount; } }); this.#addParametersToRequest(compiledQuery.parameters); this.#attachListeners(); } get request() { return this.#request; } readChunk() { const subscriptionKey = this.readChunk.name; return new Promise((resolve, reject) => { this.#subscribers[subscriptionKey] = (event, error) => { delete this.#subscribers[subscriptionKey]; if (event === 'error') { reject(error); } else { resolve(this.#rows.splice(0, this.#streamChunkSize)); } }; this.#request.resume(); }); } #addParametersToRequest(parameters) { for (let i = 0; i < parameters.length; i++) { const parameter = parameters[i]; this.#request.addParameter(String(i + 1), this.#getTediousDataType(parameter), parameter); } } #attachListeners() { const pauseAndEmitChunkReady = this.#streamChunkSize ? () => { if (this.#streamChunkSize <= this.#rows.length) { this.#request.pause(); Object.values(this.#subscribers).forEach((subscriber) => subscriber('chunkReady')); } } : () => { }; const rowListener = (columns) => { const row = {}; for (const column of columns) { row[column.metadata.colName] = column.value; } this.#rows.push(row); pauseAndEmitChunkReady(); }; this.#request.on('row', rowListener); this.#request.once('requestCompleted', () => { Object.values(this.#subscribers).forEach((subscriber) => subscriber('completed')); this.#request.off('row', rowListener); }); } #getTediousDataType(value) { if ((0, object_utils_js_1.isNull)(value) || (0, object_utils_js_1.isUndefined)(value) || (0, object_utils_js_1.isString)(value)) { return this.#tedious.TYPES.NVarChar; } if ((0, object_utils_js_1.isBigInt)(value) || ((0, object_utils_js_1.isNumber)(value) && value % 1 === 0)) { if (value < -2147483648 || value > 2147483647) { return this.#tedious.TYPES.BigInt; } else { return this.#tedious.TYPES.Int; } } if ((0, object_utils_js_1.isNumber)(value)) { return this.#tedious.TYPES.Float; } if ((0, object_utils_js_1.isBoolean)(value)) { return this.#tedious.TYPES.Bit; } if ((0, object_utils_js_1.isDate)(value)) { return this.#tedious.TYPES.DateTime; } if ((0, object_utils_js_1.isBuffer)(value)) { return this.#tedious.TYPES.VarBinary; } return this.#tedious.TYPES.NVarChar; } }