Files
floci-ui/packages/api/src/adapter-azure/MssqlDataClient.ts
T
Zakaria 91ce11274d
CI / checks (pull_request) Successful in 25m47s
chore: show TheCloud instead of Floci in console text and messages
Labels, empty states, error messages and defaults the console shows now name
TheCloud or its runtimes. Identifiers that must match the emulator (FLOCI_*
settings, the X-Floci-CSRF header, emulator-returned host names) are unchanged.
2026-10-24 14:30:44 -04:00

85 lines
2.9 KiB
TypeScript

import sql from 'mssql'
import type {config as MssqlConfig, IRecordSet} from 'mssql'
import type {SqlColumn, SqlQueryResult, SqlResultSet} from '../cloud-spi/types'
import {mapSqlDataError, type SqlDataPhase} from './sqlDataErrors'
import type {SqlTlsMode} from './sqlTransport'
import {normalizeSqlRow} from './sqlValues'
const MAX_ROWS_PER_RESULT_SET = 500
export interface SqlDataConnection {
server: string
port: number
database: string
username: string
password: string
tlsMode: SqlTlsMode
}
export interface SqlDataClient {
query(connection: SqlDataConnection, query: string): Promise<SqlQueryResult>
}
export class MssqlDataClient implements SqlDataClient {
async query(connection: SqlDataConnection, query: string): Promise<SqlQueryResult> {
const pool = new sql.ConnectionPool(connectionConfig(connection))
const startedAt = performance.now()
let phase: SqlDataPhase = 'connect'
try {
await pool.connect()
phase = 'query'
const result = await pool.request().query<Record<string, unknown>>(query)
phase = 'result'
return {
resultSets: result.recordsets.map(toResultSet),
rowsAffected: result.rowsAffected,
durationMs: Math.round(performance.now() - startedAt),
}
} catch (error) {
throw mapSqlDataError('Azure SQL', phase, error)
} finally {
await pool.close().catch(() => undefined)
}
}
}
function connectionConfig(connection: SqlDataConnection): MssqlConfig {
return {
user: connection.username,
password: connection.password,
server: connection.server,
port: connection.port,
database: connection.database,
connectionTimeout: 10_000,
requestTimeout: 30_000,
pool: {min: 0, max: 1, idleTimeoutMillis: 5_000},
options: {
encrypt: connection.tlsMode !== 'disable',
trustServerCertificate: connection.tlsMode !== 'verify',
appName: 'TheCloud',
},
}
}
function toResultSet(recordset: IRecordSet<Record<string, unknown>>): SqlResultSet {
return {
columns: Object.values(recordset.columns)
.sort((left, right) => left.index - right.index)
.map((column): SqlColumn => ({
name: column.name,
type: sqlTypeName(column.type),
})),
rows: recordset.slice(0, MAX_ROWS_PER_RESULT_SET).map(normalizeSqlRow),
truncated: recordset.length > MAX_ROWS_PER_RESULT_SET,
}
}
function sqlTypeName(type: unknown): string {
if (type && (typeof type === 'object' || typeof type === 'function') && 'declaration' in type) {
const declaration = (type as {declaration?: unknown}).declaration
if (typeof declaration === 'string') return declaration
}
return 'unknown'
}