533 lines
23 KiB
JavaScript
533 lines
23 KiB
JavaScript
import { z } from "zod";
|
|
import { assertSafeSelect, buildWhereClause, qualifyName, quoteIdentifier, validateProcedureDefinition } from "./sql.js";
|
|
const JsonValueSchema = z.union([
|
|
z.string(),
|
|
z.number(),
|
|
z.boolean(),
|
|
z.null(),
|
|
z.array(z.unknown()),
|
|
z.record(z.string(), z.unknown())
|
|
]);
|
|
const DatabaseNameSchema = z.string().min(1).optional().describe("Optional database name from the configured DATABASES allowlist.");
|
|
const SchemaNameSchema = z.string().min(1).default("dbo").describe("SQL Server schema name. Defaults to dbo.");
|
|
const ObjectNameSchema = z.string().min(1).describe("SQL Server object name without schema.");
|
|
const ResponseFormatSchema = z.enum(["markdown", "json"]).default("markdown");
|
|
export function registerSqlServerTools(server, client) {
|
|
server.registerTool("sqlserver_list_databases", {
|
|
title: "List Configured SQL Server Databases",
|
|
description: "List database names this SQL Server MCP is allowed to access. This reads local MCP configuration only.",
|
|
inputSchema: z.object({ response_format: ResponseFormatSchema }).strict(),
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ response_format }) => toolResult({
|
|
defaultDatabase: client.defaultDatabase,
|
|
databases: client.allowedDatabases
|
|
}, response_format));
|
|
server.registerTool("sqlserver_list_tables", {
|
|
title: "List SQL Server Tables",
|
|
description: "List user tables in a configured SQL Server database, optionally filtered by schema.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: z.string().min(1).optional(),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, response_format }) => handleTool(async () => {
|
|
const params = [];
|
|
let where = "WHERE t.is_ms_shipped = 0";
|
|
if (schemaName) {
|
|
where += " AND s.name = @schemaName";
|
|
params.push({ name: "schemaName", value: schemaName });
|
|
}
|
|
const result = await client.query(databaseName, `
|
|
SELECT
|
|
s.name AS schema_name,
|
|
t.name AS table_name,
|
|
SUM(p.rows) AS row_count,
|
|
t.create_date,
|
|
t.modify_date
|
|
FROM sys.tables t
|
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
LEFT JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0, 1)
|
|
${where}
|
|
GROUP BY s.name, t.name, t.create_date, t.modify_date
|
|
ORDER BY s.name, t.name`, params);
|
|
return toolResult({ count: result.recordset.length, tables: result.recordset }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_describe_table", {
|
|
title: "Describe SQL Server Table",
|
|
description: "Return columns, indexes, and row count metadata for a SQL Server table.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
tableName: ObjectNameSchema,
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, tableName, response_format }) => handleTool(async () => {
|
|
const columns = await client.query(databaseName, `
|
|
SELECT
|
|
c.column_id,
|
|
c.name AS column_name,
|
|
ty.name AS data_type,
|
|
c.max_length,
|
|
c.precision,
|
|
c.scale,
|
|
c.is_nullable,
|
|
dc.definition AS default_definition,
|
|
ep.value AS description
|
|
FROM sys.columns c
|
|
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
|
|
LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id
|
|
LEFT JOIN sys.extended_properties ep
|
|
ON ep.major_id = c.object_id
|
|
AND ep.minor_id = c.column_id
|
|
AND ep.name = 'MS_Description'
|
|
WHERE c.object_id = OBJECT_ID(@objectName)
|
|
ORDER BY c.column_id`, [{ name: "objectName", value: `${schemaName}.${tableName}` }]);
|
|
const indexes = await client.query(databaseName, `
|
|
SELECT
|
|
i.name AS index_name,
|
|
i.type_desc,
|
|
i.is_unique,
|
|
i.is_primary_key,
|
|
STRING_AGG(c.name, ', ') WITHIN GROUP (ORDER BY ic.key_ordinal) AS columns
|
|
FROM sys.indexes i
|
|
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
WHERE i.object_id = OBJECT_ID(@objectName)
|
|
AND i.name IS NOT NULL
|
|
GROUP BY i.name, i.type_desc, i.is_unique, i.is_primary_key
|
|
ORDER BY i.is_primary_key DESC, i.name`, [{ name: "objectName", value: `${schemaName}.${tableName}` }]);
|
|
return toolResult({
|
|
table: `${schemaName}.${tableName}`,
|
|
columns: columns.recordset,
|
|
indexes: indexes.recordset
|
|
}, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_read_data", {
|
|
title: "Read SQL Server Data",
|
|
description: "Execute a single SELECT or CTE query against SQL Server. Mutating statements and multiple statements are blocked.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
query: z.string().min(1).describe("A single SELECT statement or CTE query."),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, query, response_format }) => handleTool(async () => {
|
|
assertSafeSelect(query);
|
|
const result = await client.query(databaseName, query);
|
|
const rows = result.recordset.slice(0, client.maxRows);
|
|
return toolResult({
|
|
count: rows.length,
|
|
truncated: result.recordset.length > rows.length,
|
|
rows
|
|
}, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_insert_rows", {
|
|
title: "Insert SQL Server Rows",
|
|
description: "Insert one or more rows into a table. Disabled when READONLY=true.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
tableName: ObjectNameSchema,
|
|
rows: z.array(z.record(z.string(), JsonValueSchema)).min(1).max(100),
|
|
confirm: z.literal(true).describe("Must be true to execute the insert."),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
idempotentHint: false,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, tableName, rows, response_format }) => handleTool(async () => {
|
|
assertWritable(client);
|
|
assertWriteLimit(client, rows.length);
|
|
const result = await insertRows(client, databaseName, schemaName, tableName, rows);
|
|
return toolResult({ rowsAffected: result.rowsAffected }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_update_rows", {
|
|
title: "Update SQL Server Rows",
|
|
description: "Update table rows using equality-based SET values and WHERE criteria. WHERE is required. Disabled when READONLY=true.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
tableName: ObjectNameSchema,
|
|
values: z.record(z.string(), JsonValueSchema).refine((value) => Object.keys(value).length > 0, "values must not be empty"),
|
|
where: z.record(z.string(), JsonValueSchema).refine((value) => Object.keys(value).length > 0, "where must not be empty"),
|
|
confirm: z.literal(true).describe("Must be true to execute the update."),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: false,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, tableName, values, where, response_format }) => handleTool(async () => {
|
|
assertWritable(client);
|
|
const setClause = buildAssignments(values, "s");
|
|
const whereClause = buildWhereClause(where, "w");
|
|
const result = await client.query(databaseName, `UPDATE ${qualifyName(schemaName, tableName)} SET ${setClause.clause} WHERE ${whereClause.clause}`, [...setClause.params, ...whereClause.params]);
|
|
assertAffectedRowsWithinLimit(client, result.rowsAffected);
|
|
return toolResult({ rowsAffected: result.rowsAffected }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_delete_rows", {
|
|
title: "Delete SQL Server Rows",
|
|
description: "Delete table rows using equality-based WHERE criteria. WHERE is required. Disabled when READONLY=true.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
tableName: ObjectNameSchema,
|
|
where: z.record(z.string(), JsonValueSchema).refine((value) => Object.keys(value).length > 0, "where must not be empty"),
|
|
confirm: z.literal(true).describe("Must be true to execute the delete."),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: false,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, tableName, where, response_format }) => handleTool(async () => {
|
|
assertWritable(client);
|
|
const whereClause = buildWhereClause(where, "w");
|
|
const result = await client.query(databaseName, `DELETE FROM ${qualifyName(schemaName, tableName)} WHERE ${whereClause.clause}`, whereClause.params);
|
|
assertAffectedRowsWithinLimit(client, result.rowsAffected);
|
|
return toolResult({ rowsAffected: result.rowsAffected }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_create_table", {
|
|
title: "Create SQL Server Table",
|
|
description: "Create a SQL Server table from validated column definitions. Requires ENABLE_DDL=true and READONLY=false.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
tableName: ObjectNameSchema,
|
|
columns: z.array(z.object({
|
|
name: z.string().min(1),
|
|
type: z.string().min(1).describe("SQL Server data type, e.g. int, bigint, nvarchar(100), decimal(18,2), datetime2."),
|
|
nullable: z.boolean().default(true),
|
|
primaryKey: z.boolean().default(false),
|
|
identity: z.boolean().default(false),
|
|
defaultValue: z.string().optional()
|
|
}).strict()).min(1),
|
|
confirm: z.literal(true),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
idempotentHint: false,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, tableName, columns, response_format }) => handleTool(async () => {
|
|
assertDdlAllowed(client);
|
|
const definitions = columns.map((column) => {
|
|
assertSafeDataType(column.type);
|
|
const pieces = [
|
|
quoteIdentifier(column.name),
|
|
column.type,
|
|
column.identity ? "IDENTITY(1,1)" : "",
|
|
column.nullable ? "NULL" : "NOT NULL",
|
|
column.defaultValue ? `DEFAULT ${column.defaultValue}` : ""
|
|
].filter(Boolean);
|
|
return pieces.join(" ");
|
|
});
|
|
const primaryKeys = columns.filter((column) => column.primaryKey).map((column) => quoteIdentifier(column.name));
|
|
if (primaryKeys.length > 0) {
|
|
definitions.push(`CONSTRAINT ${quoteIdentifier(`PK_${tableName}`)} PRIMARY KEY (${primaryKeys.join(", ")})`);
|
|
}
|
|
await client.query(databaseName, `CREATE TABLE ${qualifyName(schemaName, tableName)} (\n ${definitions.join(",\n ")}\n)`);
|
|
return toolResult({ created: true, table: `${schemaName}.${tableName}` }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_drop_table", {
|
|
title: "Drop SQL Server Table",
|
|
description: "Drop a SQL Server table if it exists. Requires ENABLE_DDL=true, READONLY=false, and confirm=true.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
tableName: ObjectNameSchema,
|
|
confirm: z.literal(true),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, tableName, response_format }) => handleTool(async () => {
|
|
assertDdlAllowed(client);
|
|
await client.query(databaseName, `DROP TABLE IF EXISTS ${qualifyName(schemaName, tableName)}`);
|
|
return toolResult({ dropped: true, table: `${schemaName}.${tableName}` }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_list_procedures", {
|
|
title: "List SQL Server Stored Procedures",
|
|
description: "List stored procedures in a configured SQL Server database, optionally filtered by schema.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: z.string().min(1).optional(),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, response_format }) => handleTool(async () => {
|
|
const params = [];
|
|
let where = "WHERE p.is_ms_shipped = 0";
|
|
if (schemaName) {
|
|
where += " AND s.name = @schemaName";
|
|
params.push({ name: "schemaName", value: schemaName });
|
|
}
|
|
const result = await client.query(databaseName, `
|
|
SELECT
|
|
s.name AS schema_name,
|
|
p.name AS procedure_name,
|
|
p.create_date,
|
|
p.modify_date
|
|
FROM sys.procedures p
|
|
JOIN sys.schemas s ON s.schema_id = p.schema_id
|
|
${where}
|
|
ORDER BY s.name, p.name`, params);
|
|
return toolResult({ count: result.recordset.length, procedures: result.recordset }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_describe_procedure", {
|
|
title: "Describe SQL Server Stored Procedure",
|
|
description: "Return definition and parameter metadata for a stored procedure.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
procedureName: ObjectNameSchema,
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, procedureName, response_format }) => handleTool(async () => {
|
|
const objectName = `${schemaName}.${procedureName}`;
|
|
const definition = await client.query(databaseName, `
|
|
SELECT
|
|
s.name AS schema_name,
|
|
p.name AS procedure_name,
|
|
p.create_date,
|
|
p.modify_date,
|
|
m.definition
|
|
FROM sys.procedures p
|
|
JOIN sys.schemas s ON s.schema_id = p.schema_id
|
|
LEFT JOIN sys.sql_modules m ON m.object_id = p.object_id
|
|
WHERE p.object_id = OBJECT_ID(@objectName)`, [{ name: "objectName", value: objectName }]);
|
|
const parameters = await client.query(databaseName, `
|
|
SELECT
|
|
parameter_id,
|
|
name,
|
|
TYPE_NAME(user_type_id) AS data_type,
|
|
max_length,
|
|
precision,
|
|
scale,
|
|
is_output
|
|
FROM sys.parameters
|
|
WHERE object_id = OBJECT_ID(@objectName)
|
|
ORDER BY parameter_id`, [{ name: "objectName", value: objectName }]);
|
|
return toolResult({
|
|
procedure: objectName,
|
|
metadata: definition.recordset[0] ?? null,
|
|
parameters: parameters.recordset
|
|
}, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_create_or_alter_procedure", {
|
|
title: "Create Or Alter SQL Server Stored Procedure",
|
|
description: "Create or alter a stored procedure. The definition must start with CREATE PROCEDURE, ALTER PROCEDURE, or CREATE OR ALTER PROCEDURE and match schemaName/procedureName. Requires ENABLE_DDL=true and READONLY=false.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
procedureName: ObjectNameSchema,
|
|
definition: z.string().min(1),
|
|
confirm: z.literal(true),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: false,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, procedureName, definition, response_format }) => handleTool(async () => {
|
|
assertDdlAllowed(client);
|
|
validateProcedureDefinition(definition, schemaName, procedureName);
|
|
await client.batch(databaseName, definition);
|
|
return toolResult({ saved: true, procedure: `${schemaName}.${procedureName}` }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_drop_procedure", {
|
|
title: "Drop SQL Server Stored Procedure",
|
|
description: "Drop a stored procedure if it exists. Requires ENABLE_DDL=true, READONLY=false, and confirm=true.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
procedureName: ObjectNameSchema,
|
|
confirm: z.literal(true),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: true,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, procedureName, response_format }) => handleTool(async () => {
|
|
assertDdlAllowed(client);
|
|
await client.query(databaseName, `DROP PROCEDURE IF EXISTS ${qualifyName(schemaName, procedureName)}`);
|
|
return toolResult({ dropped: true, procedure: `${schemaName}.${procedureName}` }, response_format);
|
|
}));
|
|
server.registerTool("sqlserver_execute_procedure", {
|
|
title: "Execute SQL Server Stored Procedure",
|
|
description: "Execute a stored procedure with named input parameters. Disabled when READONLY=true unless allowInReadOnly=true is explicitly set in the tool call.",
|
|
inputSchema: z.object({
|
|
databaseName: DatabaseNameSchema,
|
|
schemaName: SchemaNameSchema,
|
|
procedureName: ObjectNameSchema,
|
|
parameters: z.record(z.string(), JsonValueSchema).default({}),
|
|
allowInReadOnly: z.boolean().default(false),
|
|
confirm: z.literal(true),
|
|
response_format: ResponseFormatSchema
|
|
}).strict(),
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: false,
|
|
openWorldHint: true
|
|
}
|
|
}, async ({ databaseName, schemaName, procedureName, parameters, allowInReadOnly, response_format }) => handleTool(async () => {
|
|
if (client.readOnly && !allowInReadOnly) {
|
|
throw new Error("READONLY=true blocks stored procedure execution. Set allowInReadOnly=true only for procedures known to be read-only.");
|
|
}
|
|
const params = Object.entries(parameters).map(([name, value], index) => ({
|
|
sourceName: name,
|
|
param: { name: `p${index}`, value }
|
|
}));
|
|
const assignments = params.map(({ sourceName, param }) => `@${sourceName.replace(/^@/u, "")} = @${param.name}`);
|
|
const result = await client.query(databaseName, `EXEC ${qualifyName(schemaName, procedureName)} ${assignments.join(", ")}`, params.map(({ param }) => param));
|
|
return toolResult({
|
|
rowsAffected: result.rowsAffected,
|
|
recordset: result.recordset?.slice(0, client.maxRows) ?? []
|
|
}, response_format);
|
|
}));
|
|
}
|
|
function toolResult(data, responseFormat = "markdown") {
|
|
const text = responseFormat === "json" ? JSON.stringify(data, null, 2) : toMarkdown(data);
|
|
return {
|
|
content: [{ type: "text", text }]
|
|
};
|
|
}
|
|
async function handleTool(action) {
|
|
try {
|
|
return await action();
|
|
}
|
|
catch (error) {
|
|
return {
|
|
isError: true,
|
|
content: [{ type: "text", text: formatError(error) }]
|
|
};
|
|
}
|
|
}
|
|
function toMarkdown(data) {
|
|
return ["```json", JSON.stringify(data, null, 2), "```"].join("\n");
|
|
}
|
|
function formatError(error) {
|
|
if (error instanceof Error) {
|
|
return `Error: ${error.message}`;
|
|
}
|
|
return `Error: ${String(error)}`;
|
|
}
|
|
function assertWritable(client) {
|
|
if (client.readOnly) {
|
|
throw new Error("READONLY=true blocks write operations.");
|
|
}
|
|
}
|
|
function assertDdlAllowed(client) {
|
|
assertWritable(client);
|
|
if (!client.enableDdl) {
|
|
throw new Error("ENABLE_DDL=true is required for table and stored procedure DDL operations.");
|
|
}
|
|
}
|
|
function assertWriteLimit(client, rows) {
|
|
if (rows > client.maxWriteRows) {
|
|
throw new Error(`Write affects ${rows} rows, which exceeds MAX_WRITE_ROWS=${client.maxWriteRows}.`);
|
|
}
|
|
}
|
|
function assertAffectedRowsWithinLimit(client, rowsAffected) {
|
|
const total = rowsAffected.reduce((sum, count) => sum + count, 0);
|
|
assertWriteLimit(client, total);
|
|
}
|
|
async function insertRows(client, databaseName, schemaName, tableName, rows) {
|
|
const firstColumns = Object.keys(rows[0] ?? {});
|
|
if (firstColumns.length === 0) {
|
|
throw new Error("Insert rows must include at least one column.");
|
|
}
|
|
for (const row of rows) {
|
|
const columns = Object.keys(row);
|
|
if (columns.length !== firstColumns.length || columns.some((column) => !firstColumns.includes(column))) {
|
|
throw new Error("All insert rows must have the same columns.");
|
|
}
|
|
}
|
|
const params = [];
|
|
const values = rows.map((row, rowIndex) => {
|
|
const rowParams = firstColumns.map((column, columnIndex) => {
|
|
const name = `r${rowIndex}_${columnIndex}`;
|
|
params.push({ name, value: row[column] });
|
|
return `@${name}`;
|
|
});
|
|
return `(${rowParams.join(", ")})`;
|
|
});
|
|
return client.query(databaseName, `INSERT INTO ${qualifyName(schemaName, tableName)} (${firstColumns.map(quoteIdentifier).join(", ")}) VALUES ${values.join(", ")}`, params);
|
|
}
|
|
function buildAssignments(values, prefix) {
|
|
const entries = Object.entries(values).filter(([, value]) => value !== undefined);
|
|
if (entries.length === 0) {
|
|
throw new Error("SET values must include at least one field.");
|
|
}
|
|
const params = entries.map(([key, value], index) => ({
|
|
name: `${prefix}${index}`,
|
|
value,
|
|
key
|
|
}));
|
|
return {
|
|
clause: params.map((param) => `${quoteIdentifier(param.key)} = @${param.name}`).join(", "),
|
|
params: params.map(({ name, value }) => ({ name, value }))
|
|
};
|
|
}
|
|
function assertSafeDataType(type) {
|
|
const trimmed = type.trim();
|
|
if (!/^[a-zA-Z][a-zA-Z0-9\s(),]+$/u.test(trimmed)) {
|
|
throw new Error(`Unsafe SQL data type '${type}'.`);
|
|
}
|
|
if (/\b(drop|alter|create|insert|update|delete|exec|execute|merge|truncate)\b/iu.test(trimmed)) {
|
|
throw new Error(`Unsafe SQL data type '${type}'.`);
|
|
}
|
|
}
|
|
//# sourceMappingURL=tools.js.map
|