105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
import sql, { type config as SqlConfig, type ConnectionPool, type IResult, type Request } from "mssql";
|
|
import type { ServerConfig } from "./config.js";
|
|
import { quoteIdentifier, resolveDatabaseName, type SqlParam } from "./sql.js";
|
|
|
|
export type QueryResult = IResult<Record<string, unknown>>;
|
|
|
|
export class SqlServerClient {
|
|
private readonly pools = new Map<string, Promise<ConnectionPool>>();
|
|
|
|
constructor(private readonly config: ServerConfig) {}
|
|
|
|
get allowedDatabases(): string[] {
|
|
return this.config.allowedDatabases;
|
|
}
|
|
|
|
get defaultDatabase(): string {
|
|
return this.config.defaultDatabase;
|
|
}
|
|
|
|
get readOnly(): boolean {
|
|
return this.config.readOnly;
|
|
}
|
|
|
|
get enableDdl(): boolean {
|
|
return this.config.enableDdl;
|
|
}
|
|
|
|
get maxRows(): number {
|
|
return this.config.maxRows;
|
|
}
|
|
|
|
get maxWriteRows(): number {
|
|
return this.config.maxWriteRows;
|
|
}
|
|
|
|
async query(
|
|
databaseName: string | undefined,
|
|
statement: string,
|
|
params: SqlParam[] = []
|
|
): Promise<QueryResult> {
|
|
const pool = await this.getPool(databaseName);
|
|
const request = pool.request();
|
|
bindParams(request, params);
|
|
return request.query(statement) as Promise<QueryResult>;
|
|
}
|
|
|
|
async batch(databaseName: string | undefined, statement: string): Promise<QueryResult> {
|
|
const pool = await this.getPool(databaseName);
|
|
return pool.request().batch(statement) as Promise<QueryResult>;
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
const pools = await Promise.all([...this.pools.values()]);
|
|
await Promise.all(pools.map((pool) => pool.close()));
|
|
this.pools.clear();
|
|
}
|
|
|
|
private async getPool(requestedDatabase: string | undefined): Promise<ConnectionPool> {
|
|
const database = resolveDatabaseName(
|
|
requestedDatabase,
|
|
this.config.defaultDatabase,
|
|
this.config.allowedDatabases
|
|
);
|
|
const key = database.toLowerCase();
|
|
const existing = this.pools.get(key);
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
|
|
const poolPromise = new sql.ConnectionPool(buildSqlConfig(this.config, database)).connect();
|
|
this.pools.set(key, poolPromise);
|
|
return poolPromise;
|
|
}
|
|
}
|
|
|
|
export function useDatabaseStatement(databaseName: string): string {
|
|
return `USE ${quoteIdentifier(databaseName)};`;
|
|
}
|
|
|
|
function buildSqlConfig(config: ServerConfig, database: string): SqlConfig {
|
|
return {
|
|
server: config.serverName,
|
|
database,
|
|
user: config.user,
|
|
password: config.password,
|
|
connectionTimeout: config.connectionTimeoutMs,
|
|
requestTimeout: config.requestTimeoutMs,
|
|
options: {
|
|
encrypt: config.encrypt,
|
|
trustServerCertificate: config.trustServerCertificate
|
|
},
|
|
pool: {
|
|
max: 5,
|
|
min: 0,
|
|
idleTimeoutMillis: 30000
|
|
}
|
|
};
|
|
}
|
|
|
|
function bindParams(request: Request, params: SqlParam[]): void {
|
|
for (const param of params) {
|
|
request.input(param.name, param.value);
|
|
}
|
|
}
|