kysely
Version:
Type safe SQL query builder
269 lines (268 loc) • 9.51 kB
JavaScript
"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_RELEASE_METHOD = Symbol();
const PRIVATE_DESTROY_METHOD = Symbol();
class MssqlDriver {
#config;
#pool;
constructor(config) {
this.#config = (0, object_utils_js_1.freeze)({ ...config });
this.#pool = new this.#config.tarn.Pool({
...this.#config.tarn.options,
create: async () => {
const connection = await this.#config.tedious.connectionFactory();
await new Promise((resolve, reject) => connection.connect((error) => {
if (error)
reject(error);
else
resolve(undefined);
}));
return new MssqlConnection(connection, this.#config.tedious);
},
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: (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 releaseConnection(connection) {
await connection[PRIVATE_RELEASE_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;
}
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 executeQuery(compiledQuery) {
try {
const deferred = new deferred_js_1.Deferred();
const request = new MssqlRequest(this.#tedious, compiledQuery, 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() {
await new Promise((resolve, reject) => this.#connection.rollbackTransaction((error) => {
if (error)
reject(error);
else
resolve(undefined);
}));
}
async *streamQuery(compiledQuery, chunkSize) {
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
throw new Error('chunkSize must be a positive integer');
}
const request = new MssqlRequest(this.#tedious, compiledQuery);
this.#connection.execSql(request.request);
try {
while (true) {
const rows = await request.readChunk(chunkSize);
if (rows.length === 0) {
break;
}
yield { rows };
if (rows.length < chunkSize) {
break;
}
}
}
finally {
this.#connection.cancel();
}
}
async validate() {
try {
const deferred = new deferred_js_1.Deferred();
const request = new MssqlRequest(this.#tedious, compiled_query_js_1.CompiledQuery.raw('select 1'), deferred);
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;
}
[PRIVATE_RELEASE_METHOD]() {
return 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;
#tedious;
#completed;
#error;
#rowCount;
constructor(tedious, compiledQuery, onDone) {
this.#completed = false;
this.#rows = [];
this.#tedious = tedious;
const { parameters, sql } = compiledQuery;
this.#request = new this.#tedious.Request(sql, (err, rowCount) => {
if (err) {
this.#error = err instanceof AggregateError ? err.errors : err;
onDone?.reject(this.#error);
}
else {
this.#rowCount = rowCount;
}
});
this.#addParametersToRequest(parameters);
this.#attachListeners(onDone);
}
get request() {
return this.#request;
}
readChunk(chunkSize) {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (this.#error) {
clearInterval(interval);
reject(this.#error);
}
else if (this.#completed || this.#rows.length >= chunkSize) {
clearInterval(interval);
resolve(this.#rows.splice(0, chunkSize));
}
}, 0);
});
}
#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(onDone) {
const rowListener = (columns) => {
const row = {};
for (const column of columns) {
row[column.metadata.colName] = column.value;
}
this.#rows.push(row);
};
this.#request.on('row', rowListener);
this.#request.once('requestCompleted', () => {
this.#completed = true;
this.#request.off('row', rowListener);
onDone?.resolve({
rowCount: this.#rowCount,
rows: this.#rows,
});
});
}
#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;
}
}