118 lines
5.2 KiB
TypeScript
118 lines
5.2 KiB
TypeScript
import type { DatabaseType } from "@/types/database";
|
|
import { isSchemaAware } from "./databaseCapabilities";
|
|
import { quoteTableIdentifier } from "./tableSelectSql";
|
|
|
|
export interface BuildRoutineExecutionSqlOptions {
|
|
databaseType?: DatabaseType;
|
|
schema?: string;
|
|
routineName: string;
|
|
}
|
|
|
|
export type RoutineParameterMode = "IN" | "OUT" | "INOUT" | "RETURN" | "UNKNOWN";
|
|
|
|
export interface RoutineParameter {
|
|
name: string;
|
|
dataType: string;
|
|
mode: RoutineParameterMode;
|
|
ordinal: number;
|
|
hasDefault?: boolean;
|
|
defaultValue?: string | null;
|
|
}
|
|
|
|
export interface RoutineParameterValue extends RoutineParameter {
|
|
value: string;
|
|
useNull?: boolean;
|
|
useDefault?: boolean;
|
|
}
|
|
|
|
export function qualifiedRoutineName(options: BuildRoutineExecutionSqlOptions): string {
|
|
const { databaseType, schema, routineName } = options;
|
|
if (isSchemaAware(databaseType) && schema) {
|
|
return `${quoteTableIdentifier(databaseType, schema)}.${quoteTableIdentifier(databaseType, routineName)}`;
|
|
}
|
|
return quoteTableIdentifier(databaseType, routineName);
|
|
}
|
|
|
|
export function buildProcedureExecutionSql(options: BuildRoutineExecutionSqlOptions): string {
|
|
return buildProcedureExecutionSqlFromValues({ ...options, parameters: [] });
|
|
}
|
|
|
|
export function buildProcedureExecutionSqlFromValues(options: BuildRoutineExecutionSqlOptions & { parameters: RoutineParameterValue[] }): string {
|
|
const routine = qualifiedRoutineName(options);
|
|
const sortedParameters = [...options.parameters].sort((a, b) => a.ordinal - b.ordinal);
|
|
const values = sortedParameters.filter((parameter) => shouldIncludeParameter(parameter));
|
|
const useNamedArguments = shouldUseNamedArguments(options.databaseType, sortedParameters);
|
|
if (options.databaseType === "sqlserver") {
|
|
const args = values.map((parameter) => `${sqlServerParameterName(parameter.name)} = ${routineParameterSqlValue(options.databaseType, parameter)}`).join(", ");
|
|
return args ? `EXEC ${routine} ${args};` : `EXEC ${routine};`;
|
|
}
|
|
if (options.databaseType === "oracle" || options.databaseType === "dameng" || options.databaseType === "oceanbase-oracle") {
|
|
return `BEGIN\n ${routine}(${values.map((parameter) => routineArgumentSql(options.databaseType, parameter, useNamedArguments)).join(", ")});\nEND;`;
|
|
}
|
|
return `CALL ${routine}(${values.map((parameter) => routineArgumentSql(options.databaseType, parameter, useNamedArguments)).join(", ")});`;
|
|
}
|
|
|
|
export function shouldIncludeParameter(parameter: RoutineParameterValue): boolean {
|
|
if (parameter.useDefault && parameter.hasDefault) return false;
|
|
return acceptsRoutineInput(parameter);
|
|
}
|
|
|
|
export function acceptsRoutineInput(parameter: Pick<RoutineParameterValue, "mode">): boolean {
|
|
return parameter.mode === "IN" || parameter.mode === "INOUT" || parameter.mode === "UNKNOWN";
|
|
}
|
|
|
|
export function routineParameterSqlValue(databaseType: DatabaseType | undefined, parameter: RoutineParameterValue): string {
|
|
if (parameter.useNull) return "NULL";
|
|
const raw = parameter.value;
|
|
if (raw.trim() === "") return "NULL";
|
|
if (looksLikeNumericType(parameter.dataType)) return raw.trim();
|
|
if (looksLikeBooleanType(parameter.dataType)) return normalizeBooleanLiteral(raw, databaseType);
|
|
return quoteSqlString(raw);
|
|
}
|
|
|
|
function sqlServerParameterName(name: string): string {
|
|
return name.startsWith("@") ? name : `@${name}`;
|
|
}
|
|
|
|
function routineArgumentSql(databaseType: DatabaseType | undefined, parameter: RoutineParameterValue, useNamedArguments: boolean): string {
|
|
const value = routineParameterSqlValue(databaseType, parameter);
|
|
if (!useNamedArguments) return value;
|
|
return `${quoteTableIdentifier(databaseType, parameter.name)} => ${value}`;
|
|
}
|
|
|
|
function shouldUseNamedArguments(databaseType: DatabaseType | undefined, sortedParameters: RoutineParameterValue[]): boolean {
|
|
if (databaseType !== "postgres" && databaseType !== "oracle" && databaseType !== "dameng" && databaseType !== "oceanbase-oracle") {
|
|
return false;
|
|
}
|
|
let omittedDefault = false;
|
|
for (const parameter of sortedParameters) {
|
|
if (parameter.useDefault && parameter.hasDefault && acceptsRoutineInput(parameter)) {
|
|
omittedDefault = true;
|
|
continue;
|
|
}
|
|
if (omittedDefault && shouldIncludeParameter(parameter)) return sortedParameters.every((item) => !!item.name);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function quoteSqlString(value: string): string {
|
|
return `'${value.replace(/'/g, "''")}'`;
|
|
}
|
|
|
|
function looksLikeNumericType(dataType: string): boolean {
|
|
return /\b(bigint|int|integer|smallint|tinyint|serial|number|numeric|decimal|dec|float|double|real|money)\b/i.test(dataType);
|
|
}
|
|
|
|
function looksLikeBooleanType(dataType: string): boolean {
|
|
return /\b(bool|boolean|bit)\b/i.test(dataType);
|
|
}
|
|
|
|
function normalizeBooleanLiteral(value: string, databaseType: DatabaseType | undefined): string {
|
|
const normalized = value.trim().toLowerCase();
|
|
const truthy = normalized === "true" || normalized === "t" || normalized === "yes" || normalized === "y" || normalized === "1";
|
|
const falsy = normalized === "false" || normalized === "f" || normalized === "no" || normalized === "n" || normalized === "0";
|
|
if (!truthy && !falsy) return quoteSqlString(value);
|
|
if (databaseType === "sqlserver") return truthy ? "1" : "0";
|
|
return truthy ? "TRUE" : "FALSE";
|
|
}
|