Files
FTC25009/tools/mcp/sqlserver-mcp-server/dist/sql.js
2026-06-24 09:13:18 +08:00

86 lines
3.5 KiB
JavaScript

export function quoteIdentifier(identifier) {
const trimmed = identifier.trim();
if (!trimmed) {
throw new Error("SQL identifier must not be empty.");
}
return `[${trimmed.replaceAll("]", "]]")}]`;
}
export function qualifyName(schemaName, objectName) {
return `${quoteIdentifier(schemaName)}.${quoteIdentifier(objectName)}`;
}
export function assertSafeSelect(query) {
const trimmed = query.trim();
if (!trimmed) {
throw new Error("SELECT query must not be empty.");
}
const withoutTrailingSemicolon = trimmed.replace(/;+\s*$/u, "");
if (withoutTrailingSemicolon.includes(";")) {
throw new Error("Only a single SELECT statement is allowed.");
}
if (!/^(select|with)\b/iu.test(withoutTrailingSemicolon)) {
throw new Error("Only SELECT queries or CTE queries starting with WITH are allowed.");
}
if (/\b(insert|update|delete|merge|drop|alter|create|truncate|exec|execute|grant|revoke)\b/iu.test(withoutTrailingSemicolon)) {
throw new Error("SELECT query contains a disallowed mutating keyword.");
}
}
export function buildWhereClause(criteria, prefix) {
const entries = Object.entries(criteria).filter(([, value]) => value !== undefined);
if (entries.length === 0) {
throw new Error("WHERE criteria must include at least one field.");
}
const params = [];
const parts = entries.map(([key, value], index) => {
const column = quoteIdentifier(key);
if (value === null) {
return `${column} IS NULL`;
}
const name = `${prefix}${index}`;
params.push({ name, value });
return `${column} = @${name}`;
});
return {
clause: parts.join(" AND "),
params
};
}
export function resolveDatabaseName(requested, defaultDatabase, allowedDatabases) {
const databaseName = (requested?.trim() || defaultDatabase.trim());
if (!databaseName) {
throw new Error("Database name is required.");
}
if (allowedDatabases.length === 0) {
return databaseName;
}
const match = allowedDatabases.find((allowed) => allowed.toLowerCase() === databaseName.toLowerCase());
if (!match) {
throw new Error(`Database '${databaseName}' is not in the configured allowed database list.`);
}
return match;
}
export function validateProcedureDefinition(definition, schemaName, procedureName) {
const trimmed = definition.trim();
if (!/^(create\s+(or\s+alter\s+)?|alter\s+)proc(edure)?\b/iu.test(trimmed)) {
throw new Error("Stored procedure definition must start with CREATE PROCEDURE, ALTER PROCEDURE, or CREATE OR ALTER PROCEDURE.");
}
const nameMatch = trimmed.match(/^(?:create\s+(?:or\s+alter\s+)?|alter\s+)proc(?:edure)?\s+([^\s(@;]+)/iu);
if (!nameMatch) {
throw new Error("Stored procedure definition must include a procedure name.");
}
const normalizedExpected = normalizeObjectName(`${schemaName}.${procedureName}`);
const normalizedActual = normalizeObjectName(nameMatch[1]);
if (normalizedActual !== normalizedExpected) {
throw new Error(`Stored procedure definition name '${nameMatch[1]}' does not match requested name '${schemaName}.${procedureName}'.`);
}
}
function normalizeObjectName(name) {
return name
.replaceAll("[", "")
.replaceAll("]", "")
.replaceAll("\"", "")
.split(".")
.map((part) => part.trim().toLowerCase())
.filter(Boolean)
.join(".");
}
//# sourceMappingURL=sql.js.map