@cmd233/mcp-database-server 1.1.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -105,6 +105,8 @@ node dist/src/index.js --mysql --host <host-name> --database <database-name> --p
105
105
  必需参数:
106
106
  - `--host`: MySQL 主机名或 IP 地址
107
107
  - `--database`: 数据库名称
108
+
109
+ 可选参数:
108
110
  - `--port`: 端口号(默认: 3306)
109
111
 
110
112
  可选参数:
@@ -285,15 +287,18 @@ MCP 数据库服务器提供以下可供 Claude 使用的工具:
285
287
  | 工具 | 描述 | 必需参数 |
286
288
  |------|-------------|---------------------|
287
289
  | `read_query` | 执行 SELECT 查询以读取数据 | `query`: SQL SELECT 语句 |
288
- | `write_query` | 执行 INSERT、UPDATE 或 DELETE 查询 | `query`: SQL 修改语句 |
289
- | `create_table` | 在数据库中创建新表 | `query`: CREATE TABLE 语句 |
290
- | `alter_table` | 修改现有表架构 | `query`: ALTER TABLE 语句 |
290
+ | `write_query` | 执行 INSERT、UPDATE、DELETETRUNCATE 查询 | `query`: SQL 修改语句<br>`confirm`: 安全标志(必须为 true) |
291
+ | `create_table` | 在数据库中创建新表 | `query`: CREATE TABLE 语句<br>`confirm`: 安全标志(必须为 true) |
292
+ | `alter_table` | 修改现有表架构 | `query`: ALTER TABLE 语句<br>`confirm`: 安全标志(必须为 true) |
291
293
  | `drop_table` | 从数据库中删除表 | `table_name`: 表名<br>`confirm`: 安全标志(必须为 true) |
292
294
  | `list_tables` | 获取所有表的列表 | 无 |
293
295
  | `describe_table` | 查看表的架构信息 | `table_name`: 表名 |
294
296
  | `export_query` | 将查询结果导出为 CSV/JSON | `query`: SQL SELECT 语句<br>`format`: "csv" 或 "json" |
295
- | `append_insight` | 添加业务洞察到备忘录 | `insight`: 洞察文本 |
296
- | `list_insights` | 列出所有业务洞察 | 无 |
297
+ | `append_insight` | 添加业务洞察到备忘录 (**仅 SQLite**) | `insight`: 洞察文本<br>`confirm`: 安全标志(必须为 true) |
298
+ | `list_insights` | 列出所有业务洞察 (**仅 SQLite**) | 无 |
299
+ | `list_views` | 列出所有视图 (**仅 SQL Server**) | 无 |
300
+ | `describe_view` | 获取视图结构 (**仅 SQL Server**) | `view_name`: 视图名 |
301
+ | `get_view_definition` | 获取视图定义 SQL (**仅 SQL Server**) | `view_name`: 视图名 |
297
302
 
298
303
  有关如何在 Claude 中使用这些工具的实际示例,请参阅[使用示例](docs/usage-examples.md)。
299
304
 
@@ -93,3 +93,95 @@ export function getDescribeTableQuery(tableName) {
93
93
  }
94
94
  return dbAdapter.getDescribeTableQuery(tableName);
95
95
  }
96
+ /**
97
+ * 获取列出视图的数据库特定查询
98
+ * 仅 SQL Server 支持
99
+ * @returns SQL 查询字符串
100
+ * @throws 如果数据库不支持视图功能
101
+ */
102
+ export function getListViewsQuery() {
103
+ if (!dbAdapter) {
104
+ throw new Error("数据库未初始化");
105
+ }
106
+ if (dbAdapter.getListViewsQuery) {
107
+ return dbAdapter.getListViewsQuery();
108
+ }
109
+ // 统一错误处理策略:不支持视图时抛出明确错误
110
+ throw new Error("当前数据库不支持视图功能");
111
+ }
112
+ /**
113
+ * 获取视图定义的数据库特定查询
114
+ * 仅 SQL Server 支持
115
+ * @param viewName 视图名
116
+ */
117
+ export function getViewDefinitionQuery(viewName) {
118
+ if (!dbAdapter) {
119
+ throw new Error("数据库未初始化");
120
+ }
121
+ if (dbAdapter.getViewDefinitionQuery) {
122
+ return dbAdapter.getViewDefinitionQuery(viewName);
123
+ }
124
+ throw new Error("当前数据库不支持视图功能");
125
+ }
126
+ /**
127
+ * 检查数据库是否支持视图功能
128
+ */
129
+ export function supportsViews() {
130
+ if (!dbAdapter) {
131
+ return false;
132
+ }
133
+ return dbAdapter.supportsViews ? dbAdapter.supportsViews() : false;
134
+ }
135
+ /**
136
+ * 检查存储过程功能是否可用
137
+ * @returns 可用的数据库适配器
138
+ * @throws 如果数据库未初始化或不支持存储过程功能
139
+ */
140
+ function requireProcedureSupport() {
141
+ if (!dbAdapter) {
142
+ throw new Error('数据库未初始化');
143
+ }
144
+ if (!dbAdapter.getListProceduresQuery) {
145
+ throw new Error('当前数据库不支持存储过程功能');
146
+ }
147
+ return dbAdapter;
148
+ }
149
+ /**
150
+ * 获取列出存储过程的查询
151
+ * 仅 SQL Server 支持
152
+ * @returns SQL 查询字符串
153
+ * @throws 如果数据库不支持存储过程功能
154
+ */
155
+ export function getListProceduresQuery() {
156
+ return requireProcedureSupport().getListProceduresQuery();
157
+ }
158
+ /**
159
+ * 获取存储过程参数信息的查询
160
+ * 仅 SQL Server 支持
161
+ * @param procedureName 存储过程名
162
+ * @returns SQL 查询字符串
163
+ * @throws 如果数据库不支持存储过程功能
164
+ */
165
+ export function getDescribeProcedureQuery(procedureName) {
166
+ return requireProcedureSupport().getDescribeProcedureQuery(procedureName);
167
+ }
168
+ /**
169
+ * 获取存储过程定义的查询
170
+ * 仅 SQL Server 支持
171
+ * @param procedureName 存储过程名
172
+ * @returns SQL 查询字符串
173
+ * @throws 如果数据库不支持存储过程功能
174
+ */
175
+ export function getProcedureDefinitionQuery(procedureName) {
176
+ return requireProcedureSupport().getProcedureDefinitionQuery(procedureName);
177
+ }
178
+ /**
179
+ * 检查数据库是否支持存储过程功能
180
+ * @returns 如果支持返回 true,否则返回 false
181
+ */
182
+ export function supportsProcedures() {
183
+ if (!dbAdapter) {
184
+ return false;
185
+ }
186
+ return dbAdapter.supportsProcedures ? dbAdapter.supportsProcedures() : false;
187
+ }
@@ -1,3 +1,4 @@
1
+ import { validateForbiddenOperations } from "./sql-validator.js";
1
2
  import mysql from "mysql2/promise";
2
3
  import { Signer } from "@aws-sdk/rds-signer";
3
4
  /**
@@ -55,7 +56,7 @@ export class MysqlAdapter {
55
56
  throw new Error("AWS IAM 认证需要 AWS 用户名参数");
56
57
  }
57
58
  try {
58
- console.info(`[INFO] Generating AWS auth token for region: ${this.awsRegion}, host: ${this.host}, user: ${this.config.user}`);
59
+ console.info(`[INFO] 正在为区域 ${this.awsRegion} 生成 AWS 认证令牌, 主机: ${this.host}, 用户: ${this.config.user}`);
59
60
  const signer = new Signer({
60
61
  region: this.awsRegion,
61
62
  hostname: this.host,
@@ -63,7 +64,7 @@ export class MysqlAdapter {
63
64
  username: this.config.user,
64
65
  });
65
66
  const token = await signer.getAuthToken();
66
- console.info(`[INFO] AWS auth token generated successfully`);
67
+ console.info(`[INFO] AWS 认证令牌生成成功`);
67
68
  return token;
68
69
  }
69
70
  catch (err) {
@@ -76,10 +77,10 @@ export class MysqlAdapter {
76
77
  */
77
78
  async init() {
78
79
  try {
79
- console.info(`[INFO] Connecting to MySQL: ${this.host}, Database: ${this.database}`);
80
+ console.info(`[INFO] 正在连接 MySQL: ${this.host}, 数据库: ${this.database}`);
80
81
  // 处理 AWS IAM 认证
81
82
  if (this.awsIamAuth) {
82
- console.info(`[INFO] Using AWS IAM authentication for user: ${this.config.user}`);
83
+ console.info(`[INFO] 正在为用户 ${this.config.user} 使用 AWS IAM 认证`);
83
84
  try {
84
85
  const authToken = await this.generateAwsAuthToken();
85
86
  // 使用生成的令牌作为密码创建新配置
@@ -97,10 +98,10 @@ export class MysqlAdapter {
97
98
  else {
98
99
  this.connection = await mysql.createConnection(this.config);
99
100
  }
100
- console.info(`[INFO] MySQL connection established successfully`);
101
+ console.info(`[INFO] MySQL 连接成功建立`);
101
102
  }
102
103
  catch (err) {
103
- console.error(`[ERROR] MySQL connection error: ${err.message}`);
104
+ console.error(`[ERROR] MySQL 连接错误: ${err.message}`);
104
105
  if (this.awsIamAuth) {
105
106
  throw new Error(`使用 AWS IAM 认证连接 MySQL 失败: ${err.message}。请验证您的 AWS 凭据、IAM 权限和 RDS 配置。`);
106
107
  }
@@ -116,6 +117,8 @@ export class MysqlAdapter {
116
117
  if (!this.connection) {
117
118
  throw new Error("数据库未初始化");
118
119
  }
120
+ // 验证禁用的操作(防止恶意查询)
121
+ validateForbiddenOperations(query);
119
122
  try {
120
123
  const [rows] = await this.connection.execute(query, params);
121
124
  return Array.isArray(rows) ? rows : [];
@@ -131,6 +134,8 @@ export class MysqlAdapter {
131
134
  if (!this.connection) {
132
135
  throw new Error("数据库未初始化");
133
136
  }
137
+ // 验证禁用的操作
138
+ validateForbiddenOperations(query);
134
139
  try {
135
140
  const [result] = await this.connection.execute(query, params);
136
141
  const changes = result.affectedRows || 0;
@@ -148,6 +153,8 @@ export class MysqlAdapter {
148
153
  if (!this.connection) {
149
154
  throw new Error("数据库未初始化");
150
155
  }
156
+ // 验证禁用的操作
157
+ validateForbiddenOperations(query);
151
158
  try {
152
159
  await this.connection.query(query);
153
160
  }
@@ -1,3 +1,4 @@
1
+ import { validateForbiddenOperations } from "./sql-validator.js";
1
2
  import pg from 'pg';
2
3
  /**
3
4
  * PostgreSQL 数据库适配器实现
@@ -24,7 +25,7 @@ export class PostgresqlAdapter {
24
25
  */
25
26
  async init() {
26
27
  try {
27
- console.error(`[INFO] Connecting to PostgreSQL: ${this.host}, Database: ${this.database}`);
28
+ console.error(`[INFO] 正在连接 PostgreSQL: ${this.host}, 数据库: ${this.database}`);
28
29
  console.error(`[DEBUG] Connection details:`, {
29
30
  host: this.host,
30
31
  database: this.database,
@@ -35,10 +36,10 @@ export class PostgresqlAdapter {
35
36
  });
36
37
  this.client = new pg.Client(this.config);
37
38
  await this.client.connect();
38
- console.error(`[INFO] PostgreSQL connection established successfully`);
39
+ console.error(`[INFO] PostgreSQL 连接成功建立`);
39
40
  }
40
41
  catch (err) {
41
- console.error(`[ERROR] PostgreSQL connection error: ${err.message}`);
42
+ console.error(`[ERROR] PostgreSQL 连接错误: ${err.message}`);
42
43
  throw new Error(`连接 PostgreSQL 失败: ${err.message}`);
43
44
  }
44
45
  }
@@ -52,9 +53,12 @@ export class PostgresqlAdapter {
52
53
  if (!this.client) {
53
54
  throw new Error("数据库未初始化");
54
55
  }
56
+ // 验证禁用的操作(防止恶意查询)
57
+ validateForbiddenOperations(query);
55
58
  try {
56
59
  // PostgreSQL 使用 $1, $2 等作为参数化查询的占位符
57
- const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
60
+ let paramIndex = 0;
61
+ const preparedQuery = query.replace(/\?/g, () => `$${++paramIndex}`);
58
62
  const result = await this.client.query(preparedQuery, params);
59
63
  return result.rows;
60
64
  }
@@ -72,9 +76,12 @@ export class PostgresqlAdapter {
72
76
  if (!this.client) {
73
77
  throw new Error("数据库未初始化");
74
78
  }
79
+ // 验证禁用的操作
80
+ validateForbiddenOperations(query);
75
81
  try {
76
82
  // 将 ? 替换为编号参数
77
- const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
83
+ let paramIndex = 0;
84
+ const preparedQuery = query.replace(/\?/g, () => `$${++paramIndex}`);
78
85
  let lastID = 0;
79
86
  let changes = 0;
80
87
  // 对于 INSERT 查询,尝试获取插入的 ID
@@ -106,6 +113,8 @@ export class PostgresqlAdapter {
106
113
  if (!this.client) {
107
114
  throw new Error("数据库未初始化");
108
115
  }
116
+ // 验证禁用的操作
117
+ validateForbiddenOperations(query);
109
118
  try {
110
119
  await this.client.query(query);
111
120
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SQL 操作验证工具
3
+ * 集中管理禁用的 SQL 操作,确保数据库安全
4
+ */
5
+ /**
6
+ * 禁用的 SQL 操作类型及其错误消息
7
+ * 使用更严格的模式,检测注释后的危险语句
8
+ */
9
+ const FORBIDDEN_OPERATIONS = [
10
+ {
11
+ type: 'DROP',
12
+ // 匹配 DROP,忽略前导空白和单行注释
13
+ pattern: /^(\s*|--[^\n]*\n)*DROP\s/i,
14
+ message: 'DROP 操作已被禁用,此类操作应由 DBA 在数据库层面处理'
15
+ },
16
+ {
17
+ type: 'TRUNCATE',
18
+ // 匹配 TRUNCATE,忽略前导空白和单行注释
19
+ pattern: /^(\s*|--[^\n]*\n)*TRUNCATE\s/i,
20
+ message: 'TRUNCATE 操作已被禁用,因为它不可回滚且不触发触发器'
21
+ }
22
+ ];
23
+ /**
24
+ * 验证 SQL 查询是否包含禁用的操作
25
+ * @param query 要验证的 SQL 查询
26
+ * @throws Error 如果查询包含禁用的操作
27
+ */
28
+ export function validateForbiddenOperations(query) {
29
+ for (const { pattern, message } of FORBIDDEN_OPERATIONS) {
30
+ if (pattern.test(query)) {
31
+ throw new Error(message);
32
+ }
33
+ }
34
+ }
@@ -1,4 +1,5 @@
1
1
  import sqlite3 from "sqlite3";
2
+ import { validateForbiddenOperations } from "./sql-validator.js";
2
3
  /**
3
4
  * SQLite 数据库适配器实现
4
5
  */
@@ -13,14 +14,14 @@ export class SqliteAdapter {
13
14
  async init() {
14
15
  return new Promise((resolve, reject) => {
15
16
  // 确保数据库路径可访问
16
- console.error(`[INFO] Opening SQLite database at: ${this.dbPath}`);
17
+ console.error(`[INFO] 正在打开 SQLite 数据库: ${this.dbPath}`);
17
18
  this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
18
19
  if (err) {
19
- console.error(`[ERROR] SQLite connection error: ${err.message}`);
20
+ console.error(`[ERROR] SQLite 连接错误: ${err.message}`);
20
21
  reject(err);
21
22
  }
22
23
  else {
23
- console.error("[INFO] SQLite database opened successfully");
24
+ console.error("[INFO] SQLite 数据库成功打开");
24
25
  resolve();
25
26
  }
26
27
  });
@@ -36,6 +37,8 @@ export class SqliteAdapter {
36
37
  if (!this.db) {
37
38
  throw new Error("数据库未初始化");
38
39
  }
40
+ // 验证禁用的操作(防止恶意查询)
41
+ validateForbiddenOperations(query);
39
42
  return new Promise((resolve, reject) => {
40
43
  this.db.all(query, params, (err, rows) => {
41
44
  if (err) {
@@ -57,6 +60,8 @@ export class SqliteAdapter {
57
60
  if (!this.db) {
58
61
  throw new Error("数据库未初始化");
59
62
  }
63
+ // 验证禁用的操作
64
+ validateForbiddenOperations(query);
60
65
  return new Promise((resolve, reject) => {
61
66
  this.db.run(query, params, function (err) {
62
67
  if (err) {
@@ -77,6 +82,8 @@ export class SqliteAdapter {
77
82
  if (!this.db) {
78
83
  throw new Error("数据库未初始化");
79
84
  }
85
+ // 验证禁用的操作
86
+ validateForbiddenOperations(query);
80
87
  return new Promise((resolve, reject) => {
81
88
  this.db.exec(query, (err) => {
82
89
  if (err) {
@@ -1,4 +1,31 @@
1
+ import { validateForbiddenOperations } from "./sql-validator.js";
1
2
  import sql from 'mssql';
3
+ /**
4
+ * 验证并转义 SQL Server 标识符名称
5
+ * 防止 SQL 注入攻击
6
+ * @param name 标识符名称(表名、视图名、存储过程名等)
7
+ * @returns 转义后的安全标识符
8
+ * @throws 如果标识符包含非法字符
9
+ */
10
+ function escapeIdentifier(name) {
11
+ // 检查名称是否为空
12
+ if (!name || typeof name !== 'string') {
13
+ throw new Error('标识符名称不能为空');
14
+ }
15
+ // 检查名称长度(SQL Server 限制为 128 字符)
16
+ if (name.length > 128) {
17
+ throw new Error('标识符名称长度超过限制(最大 128 字符)');
18
+ }
19
+ // 检查是否包含危险的 SQL 字符
20
+ // 只允许字母、数字、下划线和常见的安全字符
21
+ const validNamePattern = /^[a-zA-Z_][a-zA-Z0-9_@$#]*$/;
22
+ if (!validNamePattern.test(name)) {
23
+ throw new Error(`标识符名称包含非法字符: ${name.substring(0, 20)}...`);
24
+ }
25
+ // 使用方括号转义标识符
26
+ // 方括号内的右方括号需要转义为两个右方括号
27
+ return `[${name.replace(/]/g, ']]')}]`;
28
+ }
2
29
  /**
3
30
  * SQL Server 数据库适配器实现
4
31
  */
@@ -68,27 +95,27 @@ export class SqlServerAdapter {
68
95
  await this.pool.close();
69
96
  }
70
97
  catch (closeErr) {
71
- console.error(`[WARN] Error closing old connection pool: ${closeErr.message}`);
98
+ console.error(`[WARN] 关闭旧连接池时出错: ${closeErr.message}`);
72
99
  }
73
100
  this.pool = null;
74
101
  }
75
- console.error(`[INFO] Connecting to SQL Server: ${this.server}, Database: ${this.database}`);
102
+ console.error(`[INFO] 正在连接 SQL Server: ${this.server}, 数据库: ${this.database}`);
76
103
  const pool = new sql.ConnectionPool(this.config);
77
104
  // 使用单次监听器防止内存泄漏
78
105
  pool.once('error', (err) => {
79
- console.error(`[ERROR] SQL Server connection pool error: ${err.message}`);
106
+ console.error(`[ERROR] SQL Server 连接池错误: ${err.message}`);
80
107
  // 标记连接池为不可用,下次查询时会自动重连
81
108
  if (this.pool === pool) {
82
109
  this.pool = null;
83
110
  }
84
111
  });
85
112
  this.pool = await pool.connect();
86
- console.error(`[INFO] SQL Server connection established successfully`);
113
+ console.error(`[INFO] SQL Server 连接成功建立`);
87
114
  return this.pool;
88
115
  }
89
116
  catch (err) {
90
117
  this.pool = null;
91
- console.error(`[ERROR] SQL Server connection error: ${err.message}`);
118
+ console.error(`[ERROR] SQL Server 连接错误: ${err.message}`);
92
119
  throw new Error(`连接 SQL Server 失败: ${err.message}`);
93
120
  }
94
121
  finally {
@@ -139,7 +166,7 @@ export class SqlServerAdapter {
139
166
  // 只有在获取连接池后(即 poolAcquired = true)发生的连接错误才重试
140
167
  // ensureConnection 本身的错误(如认证失败、连接超时)不应重试
141
168
  if (isConnectionError && poolAcquired && attempt < retries && this.pool !== null) {
142
- console.error(`[WARN] Connection error detected, attempting reconnect (attempt ${attempt + 1}/${retries}): ${lastError.message}`);
169
+ console.error(`[WARN] 检测到连接错误,正在尝试重新连接 (尝试 ${attempt + 1}/${retries}): ${lastError.message}`);
143
170
  this.pool = null;
144
171
  poolAcquired = false; // 重置标记
145
172
  await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
@@ -163,7 +190,7 @@ export class SqlServerAdapter {
163
190
  await this.pool.close();
164
191
  }
165
192
  catch (err) {
166
- console.error(`[WARN] Error closing connection pool: ${err.message}`);
193
+ console.error(`[WARN] 关闭连接池时出错: ${err.message}`);
167
194
  }
168
195
  this.pool = null;
169
196
  }
@@ -176,6 +203,8 @@ export class SqlServerAdapter {
176
203
  * @returns 包含查询结果的 Promise
177
204
  */
178
205
  async all(query, params = []) {
206
+ // 验证禁用的操作(防止恶意查询)
207
+ validateForbiddenOperations(query);
179
208
  return this.executeWithRetry(async (pool) => {
180
209
  const request = pool.request();
181
210
  // 向请求添加参数
@@ -196,6 +225,8 @@ export class SqlServerAdapter {
196
225
  * @returns 包含结果信息的 Promise
197
226
  */
198
227
  async run(query, params = []) {
228
+ // 验证禁用的操作
229
+ validateForbiddenOperations(query);
199
230
  return this.executeWithRetry(async (pool) => {
200
231
  const request = pool.request();
201
232
  // 向请求添加参数
@@ -207,17 +238,22 @@ export class SqlServerAdapter {
207
238
  const preparedQuery = query.replace(/\?/g, () => `@param${paramIndex++}`);
208
239
  // 如果是 INSERT,添加标识值的输出参数
209
240
  let lastID = 0;
241
+ let changes = 0;
210
242
  if (query.trim().toUpperCase().startsWith('INSERT')) {
211
243
  request.output('insertedId', sql.Int, 0);
212
244
  const updatedQuery = `${preparedQuery}; SELECT @insertedId = SCOPE_IDENTITY();`;
213
245
  const result = await request.query(updatedQuery);
214
246
  lastID = result.output.insertedId || 0;
247
+ // 使用 rowsAffected 获取受影响行数
248
+ changes = result.rowsAffected?.[0] || (lastID > 0 ? 1 : 0);
215
249
  }
216
250
  else {
217
- await request.query(preparedQuery);
251
+ const result = await request.query(preparedQuery);
252
+ // 使用 rowsAffected 获取受影响行数
253
+ changes = result.rowsAffected?.[0] || 0;
218
254
  }
219
255
  return {
220
- changes: this.getAffectedRows(query, lastID),
256
+ changes: changes,
221
257
  lastID: lastID
222
258
  };
223
259
  });
@@ -228,6 +264,8 @@ export class SqlServerAdapter {
228
264
  * @returns 执行完成后解析的 Promise
229
265
  */
230
266
  async exec(query) {
267
+ // 验证禁用的操作
268
+ validateForbiddenOperations(query);
231
269
  return this.executeWithRetry(async (pool) => {
232
270
  const request = pool.request();
233
271
  await request.batch(query);
@@ -251,10 +289,13 @@ export class SqlServerAdapter {
251
289
  return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME";
252
290
  }
253
291
  /**
254
- * 获取描述表的数据库特定查询
255
- * @param tableName 表名
292
+ * 获取描述表或视图的数据库特定查询
293
+ * @param tableName 表名或视图名
294
+ * @returns SQL 查询字符串
256
295
  */
257
296
  getDescribeTableQuery(tableName) {
297
+ // 验证并转义表名,防止 SQL 注入
298
+ const escapedTableName = escapeIdentifier(tableName);
258
299
  return `
259
300
  SELECT
260
301
  c.COLUMN_NAME as name,
@@ -272,27 +313,89 @@ export class SqlServerAdapter {
272
313
  LEFT JOIN
273
314
  sys.extended_properties ep
274
315
  ON ep.major_id = (
275
- SELECT t.object_id
276
- FROM sys.tables t
277
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
278
- WHERE t.name = '${tableName}' AND s.name = c.TABLE_SCHEMA
316
+ SELECT o.object_id
317
+ FROM sys.objects o
318
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
319
+ WHERE o.name = ${escapedTableName} AND s.name = c.TABLE_SCHEMA
320
+ AND o.type IN ('U', 'V')
279
321
  )
280
322
  AND ep.minor_id = c.ORDINAL_POSITION
281
323
  AND ep.name = 'MS_Description'
282
324
  WHERE
283
- c.TABLE_NAME = '${tableName}'
325
+ c.TABLE_NAME = ${escapedTableName}
284
326
  ORDER BY
285
327
  c.ORDINAL_POSITION
286
328
  `;
287
329
  }
288
330
  /**
289
- * 根据查询类型获取受影响行数的辅助方法
331
+ * 获取列出视图的数据库特定查询
290
332
  */
291
- getAffectedRows(query, lastID) {
292
- const queryType = query.trim().split(' ')[0].toUpperCase();
293
- if (queryType === 'INSERT' && lastID > 0) {
294
- return 1;
295
- }
296
- return 0; // 对于 SELECT 返回 0,对于 UPDATE/DELETE 在没有额外查询的情况下未知
333
+ getListViewsQuery() {
334
+ return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.VIEWS ORDER BY TABLE_NAME";
335
+ }
336
+ /**
337
+ * 获取视图定义的数据库特定查询
338
+ * @param viewName 视图名
339
+ * @returns SQL 查询字符串
340
+ * 注意: 使用 WITH ENCRYPTION 创建的视图无法获取定义
341
+ */
342
+ getViewDefinitionQuery(viewName) {
343
+ // 验证并转义视图名,防止 SQL 注入
344
+ const escapedViewName = escapeIdentifier(viewName);
345
+ return `SELECT VIEW_DEFINITION as definition FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = ${escapedViewName}`;
346
+ }
347
+ /**
348
+ * 检查数据库是否支持视图功能
349
+ */
350
+ supportsViews() {
351
+ return true;
352
+ }
353
+ /**
354
+ * 检查数据库是否支持存储过程功能
355
+ */
356
+ supportsProcedures() {
357
+ return true;
358
+ }
359
+ /**
360
+ * 获取列出存储过程的数据库特定查询
361
+ */
362
+ getListProceduresQuery() {
363
+ return "SELECT ROUTINE_NAME as name FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = 'PROCEDURE' ORDER BY ROUTINE_NAME";
364
+ }
365
+ /**
366
+ * 获取存储过程参数信息的查询
367
+ * @param procedureName 存储过程名
368
+ * @returns SQL 查询字符串
369
+ */
370
+ getDescribeProcedureQuery(procedureName) {
371
+ // 验证并转义存储过程名,防止 SQL 注入
372
+ const escapedProcedureName = escapeIdentifier(procedureName);
373
+ return `
374
+ SELECT
375
+ PARAMETER_NAME as name,
376
+ DATA_TYPE +
377
+ CASE
378
+ WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL
379
+ THEN '(' + CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR) + ')'
380
+ ELSE ''
381
+ END as type,
382
+ PARAMETER_MODE as direction,
383
+ CASE WHEN PARAMETER_MODE IN ('OUT', 'INOUT') THEN 1 ELSE 0 END as is_output,
384
+ NULL as default_value
385
+ FROM INFORMATION_SCHEMA.PARAMETERS
386
+ WHERE SPECIFIC_NAME = ${escapedProcedureName}
387
+ ORDER BY ORDINAL_POSITION
388
+ `;
389
+ }
390
+ /**
391
+ * 获取存储过程定义的查询
392
+ * @param procedureName 存储过程名
393
+ * @returns SQL 查询字符串
394
+ * 注意: 使用 WITH ENCRYPTION 创建的存储过程无法获取定义
395
+ */
396
+ getProcedureDefinitionQuery(procedureName) {
397
+ // 验证并转义存储过程名,防止 SQL 注入
398
+ const escapedProcedureName = escapeIdentifier(procedureName);
399
+ return `SELECT ROUTINE_DEFINITION as definition FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_NAME = ${escapedProcedureName}`;
297
400
  }
298
401
  }