import sql from "mssql";
|
import { loadDataSrcConfig, loadSqlServerConfig } from "../config/loadConfig.js";
|
|
/** 提取 SQL 中的 @参数名(忽略 @@ 系统符号) */
|
export function extractNamedSqlParams(sqlText: string): string[] {
|
const re = /(?<![@])@([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
const seen = new Set<string>();
|
let m: RegExpExecArray | null;
|
while ((m = re.exec(sqlText)) !== null) {
|
seen.add(m[1]!);
|
}
|
return [...seen];
|
}
|
|
let pool: sql.ConnectionPool | null = null;
|
|
export async function getSqlPool(): Promise<sql.ConnectionPool> {
|
if (!pool) {
|
const cfg = loadSqlServerConfig();
|
pool = await new sql.ConnectionPool(cfg as sql.config).connect();
|
}
|
return pool;
|
}
|
|
function bindInput(request: sql.Request, name: string, value: unknown): void {
|
if (value === undefined) {
|
throw new Error(`缺少 SQL 参数: @${name}`);
|
}
|
if (value instanceof Date) {
|
/** 与 SQL 中 CONVERT(DATE, …) 比较,避免与 varchar/datetime2 隐式混用报「从字符串转换」错误 */
|
if (name === "startDate" || name === "endDate") {
|
request.input(name, sql.Date, value);
|
} else {
|
request.input(name, sql.DateTime2, value);
|
}
|
return;
|
}
|
if (typeof value === "number") {
|
if (Number.isInteger(value)) {
|
request.input(name, sql.Int, value);
|
} else {
|
request.input(name, sql.Float, value);
|
}
|
return;
|
}
|
if (typeof value === "boolean") {
|
request.input(name, sql.Bit, value);
|
return;
|
}
|
request.input(name, sql.NVarChar(sql.MAX), String(value));
|
}
|
|
/**
|
* 按 datasrc.json 中的 id 执行 SQL,参数与语句中的 @名称 一一对应,返回行数组(可 JSON 序列化)。
|
*/
|
export async function executeDataSourceById(
|
id: string,
|
params: Record<string, unknown>
|
): Promise<Record<string, unknown>[]> {
|
const { sources } = loadDataSrcConfig();
|
const src = sources.find((s) => s.id === id);
|
if (!src) {
|
throw new Error(`datasrc 未定义 id: ${id}`);
|
}
|
const paramNames = extractNamedSqlParams(src.sql);
|
const p = await getSqlPool();
|
const request = p.request();
|
for (const name of paramNames) {
|
bindInput(request, name, params[name]);
|
}
|
const result = await request.query(src.sql);
|
return (result.recordset ?? []) as Record<string, unknown>[];
|
}
|
|
/**
|
* 将 GET/POST 混合格式转为 SQL 参数(字符串中的日期字段尝试解析为 Date)。
|
*/
|
export function coerceParamsFromRequest(
|
raw: Record<string, unknown>
|
): Record<string, unknown> {
|
const o: Record<string, unknown> = {};
|
for (const [k, val] of Object.entries(raw)) {
|
if (val === undefined || val === "") {
|
continue;
|
}
|
if (typeof val === "number" || typeof val === "boolean") {
|
o[k] = val;
|
continue;
|
}
|
if (val instanceof Date) {
|
o[k] = val;
|
continue;
|
}
|
const s = Array.isArray(val) ? String(val[0]) : String(val);
|
if (/date$/i.test(k) || /^(start|end)Date$/i.test(k)) {
|
const d = new Date(s);
|
if (!Number.isNaN(d.getTime())) {
|
o[k] = d;
|
continue;
|
}
|
}
|
if (/^-?\d+$/.test(s)) {
|
o[k] = Number(s);
|
continue;
|
}
|
if (s === "true" || s === "false") {
|
o[k] = s === "true";
|
continue;
|
}
|
o[k] = s;
|
}
|
return o;
|
}
|