universal-db-mcp 2.10.1 → 2.12.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.
Files changed (65) hide show
  1. package/README.md +3 -0
  2. package/README.zh-CN.md +3 -0
  3. package/dist/adapters/dm.d.ts +11 -0
  4. package/dist/adapters/dm.d.ts.map +1 -1
  5. package/dist/adapters/dm.js +223 -131
  6. package/dist/adapters/dm.js.map +1 -1
  7. package/dist/adapters/gaussdb.d.ts +13 -1
  8. package/dist/adapters/gaussdb.d.ts.map +1 -1
  9. package/dist/adapters/gaussdb.js +126 -75
  10. package/dist/adapters/gaussdb.js.map +1 -1
  11. package/dist/adapters/goldendb.d.ts +14 -1
  12. package/dist/adapters/goldendb.d.ts.map +1 -1
  13. package/dist/adapters/goldendb.js +114 -80
  14. package/dist/adapters/goldendb.js.map +1 -1
  15. package/dist/adapters/highgo.d.ts +19 -1
  16. package/dist/adapters/highgo.d.ts.map +1 -1
  17. package/dist/adapters/highgo.js +139 -74
  18. package/dist/adapters/highgo.js.map +1 -1
  19. package/dist/adapters/kingbase.d.ts +18 -1
  20. package/dist/adapters/kingbase.d.ts.map +1 -1
  21. package/dist/adapters/kingbase.js +204 -146
  22. package/dist/adapters/kingbase.js.map +1 -1
  23. package/dist/adapters/mysql.d.ts +14 -1
  24. package/dist/adapters/mysql.d.ts.map +1 -1
  25. package/dist/adapters/mysql.js +116 -81
  26. package/dist/adapters/mysql.js.map +1 -1
  27. package/dist/adapters/oceanbase.d.ts +14 -1
  28. package/dist/adapters/oceanbase.d.ts.map +1 -1
  29. package/dist/adapters/oceanbase.js +114 -80
  30. package/dist/adapters/oceanbase.js.map +1 -1
  31. package/dist/adapters/oracle.d.ts +7 -1
  32. package/dist/adapters/oracle.d.ts.map +1 -1
  33. package/dist/adapters/oracle.js +178 -118
  34. package/dist/adapters/oracle.js.map +1 -1
  35. package/dist/adapters/polardb.d.ts +14 -1
  36. package/dist/adapters/polardb.d.ts.map +1 -1
  37. package/dist/adapters/polardb.js +68 -34
  38. package/dist/adapters/polardb.js.map +1 -1
  39. package/dist/adapters/postgres.d.ts +19 -1
  40. package/dist/adapters/postgres.d.ts.map +1 -1
  41. package/dist/adapters/postgres.js +205 -150
  42. package/dist/adapters/postgres.js.map +1 -1
  43. package/dist/adapters/sqlserver.d.ts +1 -0
  44. package/dist/adapters/sqlserver.d.ts.map +1 -1
  45. package/dist/adapters/sqlserver.js +54 -35
  46. package/dist/adapters/sqlserver.js.map +1 -1
  47. package/dist/adapters/tidb.d.ts +14 -1
  48. package/dist/adapters/tidb.d.ts.map +1 -1
  49. package/dist/adapters/tidb.js +68 -34
  50. package/dist/adapters/tidb.js.map +1 -1
  51. package/dist/adapters/vastbase.d.ts +19 -1
  52. package/dist/adapters/vastbase.d.ts.map +1 -1
  53. package/dist/adapters/vastbase.js +122 -57
  54. package/dist/adapters/vastbase.js.map +1 -1
  55. package/dist/core/database-service.d.ts +6 -1
  56. package/dist/core/database-service.d.ts.map +1 -1
  57. package/dist/core/database-service.js +37 -3
  58. package/dist/core/database-service.js.map +1 -1
  59. package/dist/http/routes/schema.js +2 -2
  60. package/dist/http/routes/schema.js.map +1 -1
  61. package/dist/mcp/mcp-server.js +4 -4
  62. package/dist/mcp/mcp-server.js.map +1 -1
  63. package/dist/types/adapter.d.ts +3 -1
  64. package/dist/types/adapter.d.ts.map +1 -1
  65. package/package.json +1 -1
package/README.md CHANGED
@@ -59,7 +59,9 @@ AI: Let me query that for you...
59
59
  - **Intelligent Caching** - Schema caching with configurable TTL for blazing-fast performance
60
60
  - **Batch Query Optimization** - Up to 100x faster schema retrieval for large databases
61
61
  - **Schema Enhancement** - Table comments, implicit relationship inference for better Text2SQL accuracy
62
+ - **Multi-Schema Support** - Automatic discovery of all user schemas (PostgreSQL, SQL Server, Oracle, DM, and more)
62
63
  - **Data Masking** - Automatic sensitive data protection (phone, email, ID card, bank card, etc.)
64
+ - **Connection Stability** - Connection pooling, TCP Keep-Alive, and automatic reconnection for long-running sessions
63
65
 
64
66
  ### Performance Improvements
65
67
 
@@ -236,6 +238,7 @@ See [Dify Integration Guide](./docs/integrations/DIFY.md) for detailed setup ins
236
238
  │ ┌──────────────────────────────────────────────────────────────────┐ │
237
239
  │ │ Database Adapter Layer │ │
238
240
  │ │ MySQL │ PostgreSQL │ Redis │ Oracle │ MongoDB │ SQLite │ ... │ │
241
+ │ │ (Connection Pool + TCP Keep-Alive + Auto-Retry) │ │
239
242
  │ └──────────────────────────────────────────────────────────────────┘ │
240
243
  │ │
241
244
  └─────────────────────────────────────────────────────────────────────────┘
package/README.zh-CN.md CHANGED
@@ -58,7 +58,9 @@ AI: 让我帮你查询一下...
58
58
  - **智能缓存** - Schema 缓存支持可配置的 TTL,性能极速
59
59
  - **批量查询优化** - 大型数据库的 Schema 获取速度提升高达 100 倍
60
60
  - **Schema 增强** - 表注释、隐式关系推断,提升 Text2SQL 准确性
61
+ - **多 Schema 支持** - 自动发现所有用户 Schema(PostgreSQL、SQL Server、Oracle、达梦等)
61
62
  - **数据脱敏** - 自动保护敏感数据(手机号、邮箱、身份证、银行卡等)
63
+ - **连接稳定性** - 连接池、TCP Keep-Alive、断线自动重试,保障长时间会话稳定运行
62
64
 
63
65
  ### 性能提升
64
66
 
@@ -235,6 +237,7 @@ POST http://localhost:3000/mcp
235
237
  │ ┌──────────────────────────────────────────────────────────────────┐ │
236
238
  │ │ 数据库适配器层 │ │
237
239
  │ │ MySQL │ PostgreSQL │ Redis │ Oracle │ MongoDB │ SQLite │ ... │ │
240
+ │ │ (连接池 + TCP Keep-Alive + 断线自动重试) │ │
238
241
  │ └──────────────────────────────────────────────────────────────────┘ │
239
242
  │ │
240
243
  └─────────────────────────────────────────────────────────────────────────┘
@@ -5,10 +5,14 @@
5
5
  * dmdb 驱动会作为可选依赖自动安装
6
6
  *
7
7
  * 性能优化:使用批量查询获取 Schema 信息,避免 N+1 查询问题
8
+ *
9
+ * 连接管理:使用心跳保活 + 断线自动重连 + 操作自动重试,确保长连接稳定性
8
10
  */
9
11
  import type { DbAdapter, QueryResult, SchemaInfo } from '../types/adapter.js';
10
12
  export declare class DMAdapter implements DbAdapter {
11
13
  private connection;
14
+ private heartbeatTimer;
15
+ private connectionConfig;
12
16
  private config;
13
17
  constructor(config: {
14
18
  host: string;
@@ -17,6 +21,11 @@ export declare class DMAdapter implements DbAdapter {
17
21
  password?: string;
18
22
  database?: string;
19
23
  });
24
+ private isConnectionError;
25
+ private reconnect;
26
+ private startHeartbeat;
27
+ private stopHeartbeat;
28
+ private withRetry;
20
29
  /**
21
30
  * 连接到达梦数据库
22
31
  */
@@ -41,10 +50,12 @@ export declare class DMAdapter implements DbAdapter {
41
50
  * 因此需要按索引位置访问数据。
42
51
  */
43
52
  getSchema(): Promise<SchemaInfo>;
53
+ private _getSchemaImpl;
44
54
  /**
45
55
  * 按索引获取对象的值(dmdb 驱动返回的列名是数字索引)
46
56
  */
47
57
  private getValueByIndex;
58
+ private makeTableKey;
48
59
  /**
49
60
  * 组装 Schema 信息(处理 dmdb 驱动返回的数字索引列名)
50
61
  */
@@ -1 +1 @@
1
- {"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/adapters/dm.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,UAAU,EAMX,MAAM,qBAAqB,CAAC;AAyB7B,qBAAa,SAAU,YAAW,SAAS;IACzC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAMZ;gBAEU,MAAM,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB;IAID;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC9B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWjC;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAkE3E;;;;;;;;;;OAUG;IACG,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;IAkItC;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAiQrC;;OAEG;IACH,OAAO,CAAC,YAAY;IAuEpB;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CA+BzC"}
1
+ {"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/adapters/dm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,UAAU,EAMX,MAAM,qBAAqB,CAAC;AAyB7B,qBAAa,SAAU,YAAW,SAAS;IACzC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,MAAM,CAMZ;gBAEU,MAAM,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB;IAID,OAAO,CAAC,iBAAiB;YAKX,SAAS;IAWvB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,aAAa;YAIP,SAAS;IAOvB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC9B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAkE3E;;;;;;;;;;OAUG;IACG,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;YAcxB,cAAc;IAkI5B;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAyRrC;;OAEG;IACH,OAAO,CAAC,YAAY;IAuEpB;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CA+BzC"}
@@ -5,6 +5,8 @@
5
5
  * dmdb 驱动会作为可选依赖自动安装
6
6
  *
7
7
  * 性能优化:使用批量查询获取 Schema 信息,避免 N+1 查询问题
8
+ *
9
+ * 连接管理:使用心跳保活 + 断线自动重连 + 操作自动重试,确保长连接稳定性
8
10
  */
9
11
  import { isWriteOperation as checkWriteOperation } from '../utils/safety.js';
10
12
  // 动态导入 dmdb,因为它是可选依赖
@@ -27,10 +29,64 @@ async function loadDMDB() {
27
29
  }
28
30
  export class DMAdapter {
29
31
  connection = null;
32
+ heartbeatTimer = null;
33
+ connectionConfig = null;
30
34
  config;
31
35
  constructor(config) {
32
36
  this.config = config;
33
37
  }
38
+ isConnectionError(error) {
39
+ const msg = String(error?.message || '');
40
+ return /closed|ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|网络|连接/.test(msg);
41
+ }
42
+ async reconnect() {
43
+ try {
44
+ if (this.connection) {
45
+ try {
46
+ await this.connection.close();
47
+ }
48
+ catch { }
49
+ this.connection = null;
50
+ }
51
+ const DM = await loadDMDB();
52
+ this.connection = await DM.getConnection(this.connectionConfig);
53
+ console.error('达梦数据库重连成功');
54
+ }
55
+ catch (error) {
56
+ console.error('达梦数据库重连失败:', error instanceof Error ? error.message : String(error));
57
+ }
58
+ }
59
+ startHeartbeat() {
60
+ this.stopHeartbeat();
61
+ this.heartbeatTimer = setInterval(async () => {
62
+ try {
63
+ if (this.connection) {
64
+ await this.connection.execute('SELECT 1 FROM DUAL', []);
65
+ }
66
+ }
67
+ catch {
68
+ await this.reconnect();
69
+ }
70
+ }, 30000);
71
+ }
72
+ stopHeartbeat() {
73
+ if (this.heartbeatTimer) {
74
+ clearInterval(this.heartbeatTimer);
75
+ this.heartbeatTimer = null;
76
+ }
77
+ }
78
+ async withRetry(fn) {
79
+ try {
80
+ return await fn();
81
+ }
82
+ catch (error) {
83
+ if (this.isConnectionError(error)) {
84
+ await this.reconnect();
85
+ return await fn();
86
+ }
87
+ throw error;
88
+ }
89
+ }
34
90
  /**
35
91
  * 连接到达梦数据库
36
92
  */
@@ -50,8 +106,10 @@ export class DMAdapter {
50
106
  loginEncrypt: false,
51
107
  };
52
108
  this.connection = await DM.getConnection(connectionConfig);
109
+ this.connectionConfig = connectionConfig;
53
110
  // 测试连接
54
111
  await this.connection.execute('SELECT 1 FROM DUAL', []);
112
+ this.startHeartbeat();
55
113
  }
56
114
  catch (error) {
57
115
  // 翻译常见错误
@@ -69,6 +127,7 @@ export class DMAdapter {
69
127
  * 断开数据库连接
70
128
  */
71
129
  async disconnect() {
130
+ this.stopHeartbeat();
72
131
  if (this.connection) {
73
132
  try {
74
133
  await this.connection.close();
@@ -94,9 +153,9 @@ export class DMAdapter {
94
153
  cleanQuery = cleanQuery.slice(0, -1).trim();
95
154
  }
96
155
  // 执行查询
97
- const result = await this.connection.execute(cleanQuery, params || [], {
156
+ const result = await this.withRetry(() => this.connection.execute(cleanQuery, params || [], {
98
157
  autoCommit: false,
99
- });
158
+ }));
100
159
  const executionTime = Date.now() - startTime;
101
160
  // 处理查询结果
102
161
  if (result.rows && result.rows.length > 0) {
@@ -159,85 +218,97 @@ export class DMAdapter {
159
218
  throw new Error('数据库未连接');
160
219
  }
161
220
  try {
162
- // 获取达梦数据库版本
163
- const versionResult = await this.connection.execute(`SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1`, []);
164
- const version = versionResult.rows?.[0]
165
- ? this.getValueByIndex(versionResult.rows[0], 0)
166
- : 'unknown';
167
- // 获取当前 schema(在达梦中,schema 通常与用户名相同)
168
- const schemaResult = await this.connection.execute(`SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') FROM DUAL`, []);
169
- const currentSchema = schemaResult.rows?.[0]
170
- ? this.getValueByIndex(schemaResult.rows[0], 0)
171
- : '';
172
- // 获取当前用户(作为备选)
173
- const userResult = await this.connection.execute('SELECT USER FROM DUAL', []);
174
- const currentUser = userResult.rows?.[0]
175
- ? this.getValueByIndex(userResult.rows[0], 0)
176
- : '';
177
- // schema 名称:优先使用当前 schema,其次使用当前用户,最后使用配置的用户名
178
- const schemaName = (currentSchema || currentUser || this.config.user || '').toUpperCase();
179
- const databaseName = schemaName || 'unknown';
180
- // 获取所有表的列信息
181
- // 列顺序: 0=TABLE_NAME, 1=COLUMN_NAME, 2=DATA_TYPE, 3=DATA_LENGTH,
182
- // 4=DATA_PRECISION, 5=DATA_SCALE, 6=NULLABLE, 7=DATA_DEFAULT, 8=COLUMN_ID
183
- const allColumnsResult = await this.connection.execute(`SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION,
221
+ return await this.withRetry(() => this._getSchemaImpl());
222
+ }
223
+ catch (error) {
224
+ throw new Error(`获取数据库结构失败: ${error instanceof Error ? error.message : String(error)}`);
225
+ }
226
+ }
227
+ async _getSchemaImpl() {
228
+ // 获取达梦数据库版本
229
+ const versionResult = await this.connection.execute(`SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1`, []);
230
+ const version = versionResult.rows?.[0]
231
+ ? this.getValueByIndex(versionResult.rows[0], 0)
232
+ : 'unknown';
233
+ // 获取当前 schema(在达梦中,schema 通常与用户名相同)
234
+ const schemaResult = await this.connection.execute(`SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') FROM DUAL`, []);
235
+ const currentSchema = schemaResult.rows?.[0]
236
+ ? this.getValueByIndex(schemaResult.rows[0], 0)
237
+ : '';
238
+ // 获取当前用户(作为备选)
239
+ const userResult = await this.connection.execute('SELECT USER FROM DUAL', []);
240
+ const currentUser = userResult.rows?.[0]
241
+ ? this.getValueByIndex(userResult.rows[0], 0)
242
+ : '';
243
+ // schema 名称:优先使用当前 schema,其次使用当前用户,最后使用配置的用户名
244
+ const schemaName = (currentSchema || currentUser || this.config.user || '').toUpperCase();
245
+ const databaseName = schemaName || 'unknown';
246
+ // 获取所有表的列信息
247
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=COLUMN_NAME, 3=DATA_TYPE, 4=DATA_LENGTH,
248
+ // 5=DATA_PRECISION, 6=DATA_SCALE, 7=NULLABLE, 8=DATA_DEFAULT, 9=COLUMN_ID
249
+ const allColumnsResult = await this.connection.execute(`SELECT OWNER, TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION,
184
250
  DATA_SCALE, NULLABLE, DATA_DEFAULT, COLUMN_ID
185
- FROM USER_TAB_COLUMNS
186
- ORDER BY TABLE_NAME, COLUMN_ID`, []);
187
- // 获取所有列注释
188
- // 列顺序: 0=TABLE_NAME, 1=COLUMN_NAME, 2=COMMENTS
189
- const allCommentsResult = await this.connection.execute(`SELECT TABLE_NAME, COLUMN_NAME, COMMENTS
190
- FROM USER_COL_COMMENTS
191
- WHERE COMMENTS IS NOT NULL`, []);
192
- // 获取所有主键信息
193
- // 列顺序: 0=TABLE_NAME, 1=COLUMN_NAME, 2=POSITION
194
- const allPrimaryKeysResult = await this.connection.execute(`SELECT cons.TABLE_NAME, cols.COLUMN_NAME, cols.POSITION
195
- FROM USER_CONSTRAINTS cons
196
- JOIN USER_CONS_COLUMNS cols
197
- ON cons.CONSTRAINT_NAME = cols.CONSTRAINT_NAME
251
+ FROM ALL_TAB_COLUMNS
252
+ WHERE OWNER NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'SYSDBA', 'CTISYS')
253
+ ORDER BY OWNER, TABLE_NAME, COLUMN_ID`, []);
254
+ // 获取所有列注释
255
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=COLUMN_NAME, 3=COMMENTS
256
+ const allCommentsResult = await this.connection.execute(`SELECT OWNER, TABLE_NAME, COLUMN_NAME, COMMENTS
257
+ FROM ALL_COL_COMMENTS
258
+ WHERE OWNER NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'SYSDBA', 'CTISYS')
259
+ AND COMMENTS IS NOT NULL`, []);
260
+ // 获取所有主键信息
261
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=COLUMN_NAME, 3=POSITION
262
+ const allPrimaryKeysResult = await this.connection.execute(`SELECT cons.OWNER, cons.TABLE_NAME, cols.COLUMN_NAME, cols.POSITION
263
+ FROM ALL_CONSTRAINTS cons
264
+ JOIN ALL_CONS_COLUMNS cols
265
+ ON cons.CONSTRAINT_NAME = cols.CONSTRAINT_NAME AND cons.OWNER = cols.OWNER
198
266
  WHERE cons.CONSTRAINT_TYPE = 'P'
199
- ORDER BY cons.TABLE_NAME, cols.POSITION`, []);
200
- // 获取所有索引信息
201
- // 列顺序: 0=TABLE_NAME, 1=INDEX_NAME, 2=UNIQUENESS, 3=COLUMN_NAME, 4=COLUMN_POSITION
202
- const allIndexesResult = await this.connection.execute(`SELECT i.TABLE_NAME, i.INDEX_NAME, i.UNIQUENESS, ic.COLUMN_NAME, ic.COLUMN_POSITION
203
- FROM USER_INDEXES i
204
- JOIN USER_IND_COLUMNS ic
205
- ON i.INDEX_NAME = ic.INDEX_NAME
206
- ORDER BY i.TABLE_NAME, i.INDEX_NAME, ic.COLUMN_POSITION`, []);
207
- // 获取所有表的行数估算和表注释
208
- // 列顺序: 0=TABLE_NAME, 1=NUM_ROWS, 2=TABLE_COMMENT
209
- const allStatsResult = await this.connection.execute(`SELECT t.TABLE_NAME, t.NUM_ROWS, c.COMMENTS AS TABLE_COMMENT
210
- FROM USER_TABLES t
211
- LEFT JOIN USER_TAB_COMMENTS c ON t.TABLE_NAME = c.TABLE_NAME`, []);
212
- // 获取所有外键信息
213
- // 列顺序: 0=TABLE_NAME, 1=CONSTRAINT_NAME, 2=COLUMN_NAME, 3=REFERENCED_TABLE, 4=REFERENCED_COLUMN, 5=DELETE_RULE, 6=POSITION
214
- let allForeignKeys = [];
215
- try {
216
- const allForeignKeysResult = await this.connection.execute(`SELECT
267
+ AND cons.OWNER NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'SYSDBA', 'CTISYS')
268
+ ORDER BY cons.OWNER, cons.TABLE_NAME, cols.POSITION`, []);
269
+ // 获取所有索引信息
270
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=INDEX_NAME, 3=UNIQUENESS, 4=COLUMN_NAME, 5=COLUMN_POSITION
271
+ const allIndexesResult = await this.connection.execute(`SELECT i.OWNER, i.TABLE_NAME, i.INDEX_NAME, i.UNIQUENESS, ic.COLUMN_NAME, ic.COLUMN_POSITION
272
+ FROM ALL_INDEXES i
273
+ JOIN ALL_IND_COLUMNS ic
274
+ ON i.INDEX_NAME = ic.INDEX_NAME AND i.OWNER = ic.INDEX_OWNER
275
+ WHERE i.OWNER NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'SYSDBA', 'CTISYS')
276
+ ORDER BY i.OWNER, i.TABLE_NAME, i.INDEX_NAME, ic.COLUMN_POSITION`, []);
277
+ // 获取所有表的行数估算和表注释
278
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=NUM_ROWS, 3=TABLE_COMMENT
279
+ const allStatsResult = await this.connection.execute(`SELECT t.OWNER, t.TABLE_NAME, t.NUM_ROWS, c.COMMENTS AS TABLE_COMMENT
280
+ FROM ALL_TABLES t
281
+ LEFT JOIN ALL_TAB_COMMENTS c ON t.TABLE_NAME = c.TABLE_NAME AND t.OWNER = c.OWNER
282
+ WHERE t.OWNER NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'SYSDBA', 'CTISYS')`, []);
283
+ // 获取所有外键信息
284
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=CONSTRAINT_NAME, 3=COLUMN_NAME, 4=REFERENCED_TABLE,
285
+ // 5=REFERENCED_COLUMN, 6=DELETE_RULE, 7=REF_OWNER, 8=POSITION
286
+ let allForeignKeys = [];
287
+ try {
288
+ const allForeignKeysResult = await this.connection.execute(`SELECT
289
+ c.OWNER,
217
290
  c.TABLE_NAME,
218
291
  c.CONSTRAINT_NAME,
219
292
  cc.COLUMN_NAME,
220
293
  rc.TABLE_NAME AS REFERENCED_TABLE,
221
294
  rcc.COLUMN_NAME AS REFERENCED_COLUMN,
222
295
  c.DELETE_RULE,
296
+ rc.OWNER AS REF_OWNER,
223
297
  cc.POSITION
224
- FROM USER_CONSTRAINTS c
225
- JOIN USER_CONS_COLUMNS cc ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
226
- JOIN USER_CONSTRAINTS rc ON c.R_CONSTRAINT_NAME = rc.CONSTRAINT_NAME
227
- JOIN USER_CONS_COLUMNS rcc ON rc.CONSTRAINT_NAME = rcc.CONSTRAINT_NAME AND cc.POSITION = rcc.POSITION
298
+ FROM ALL_CONSTRAINTS c
299
+ JOIN ALL_CONS_COLUMNS cc ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME AND c.OWNER = cc.OWNER
300
+ JOIN ALL_CONSTRAINTS rc ON c.R_CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND c.R_OWNER = rc.OWNER
301
+ JOIN ALL_CONS_COLUMNS rcc ON rc.CONSTRAINT_NAME = rcc.CONSTRAINT_NAME AND rc.OWNER = rcc.OWNER AND cc.POSITION = rcc.POSITION
228
302
  WHERE c.CONSTRAINT_TYPE = 'R'
229
- ORDER BY c.TABLE_NAME, c.CONSTRAINT_NAME, cc.POSITION`, []);
230
- allForeignKeys = allForeignKeysResult.rows || [];
231
- }
232
- catch (error) {
233
- // 外键查询失败时忽略,返回空数组
234
- console.error('获取外键信息失败,跳过:', error instanceof Error ? error.message : String(error));
235
- }
236
- return this.assembleSchemaFromIndexedRows(databaseName, version, allColumnsResult.rows || [], allCommentsResult.rows || [], allPrimaryKeysResult.rows || [], allIndexesResult.rows || [], allStatsResult.rows || [], allForeignKeys);
303
+ AND c.OWNER NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'SYSDBA', 'CTISYS')
304
+ ORDER BY c.OWNER, c.TABLE_NAME, c.CONSTRAINT_NAME, cc.POSITION`, []);
305
+ allForeignKeys = allForeignKeysResult.rows || [];
237
306
  }
238
307
  catch (error) {
239
- throw new Error(`获取数据库结构失败: ${error instanceof Error ? error.message : String(error)}`);
308
+ // 外键查询失败时忽略,返回空数组
309
+ console.error('获取外键信息失败,跳过:', error instanceof Error ? error.message : String(error));
240
310
  }
311
+ return this.assembleSchemaFromIndexedRows(databaseName, version, allColumnsResult.rows || [], allCommentsResult.rows || [], allPrimaryKeysResult.rows || [], allIndexesResult.rows || [], allStatsResult.rows || [], allForeignKeys, schemaName);
241
312
  }
242
313
  /**
243
314
  * 按索引获取对象的值(dmdb 驱动返回的列名是数字索引)
@@ -258,31 +329,38 @@ export class DMAdapter {
258
329
  const values = Object.values(obj);
259
330
  return values.length > index ? values[index] : undefined;
260
331
  }
332
+ makeTableKey(owner, tableName, currentUser) {
333
+ return owner.toUpperCase() === currentUser.toUpperCase() ? tableName : `${owner}.${tableName}`;
334
+ }
261
335
  /**
262
336
  * 组装 Schema 信息(处理 dmdb 驱动返回的数字索引列名)
263
337
  */
264
- assembleSchemaFromIndexedRows(databaseName, version, allColumns, allComments, allPrimaryKeys, allIndexes, allStats, allForeignKeys) {
338
+ assembleSchemaFromIndexedRows(databaseName, version, allColumns, allComments, allPrimaryKeys, allIndexes, allStats, allForeignKeys, currentUser) {
265
339
  // 按表名分组列信息
266
- // 列顺序: 0=TABLE_NAME, 1=COLUMN_NAME, 2=DATA_TYPE, 3=DATA_LENGTH,
267
- // 4=DATA_PRECISION, 5=DATA_SCALE, 6=NULLABLE, 7=DATA_DEFAULT, 8=COLUMN_ID
340
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=COLUMN_NAME, 3=DATA_TYPE, 4=DATA_LENGTH,
341
+ // 5=DATA_PRECISION, 6=DATA_SCALE, 7=NULLABLE, 8=DATA_DEFAULT, 9=COLUMN_ID
268
342
  const columnsByTable = new Map();
343
+ const schemaByTable = new Map();
269
344
  for (const col of allColumns) {
270
- const tableName = this.getValueByIndex(col, 0);
271
- const columnName = this.getValueByIndex(col, 1);
272
- const dataType = this.getValueByIndex(col, 2);
273
- const dataLength = this.getValueByIndex(col, 3);
274
- const dataPrecision = this.getValueByIndex(col, 4);
275
- const dataScale = this.getValueByIndex(col, 5);
276
- const nullable = this.getValueByIndex(col, 6);
277
- const dataDefault = this.getValueByIndex(col, 7);
345
+ const owner = this.getValueByIndex(col, 0);
346
+ const tableName = this.getValueByIndex(col, 1);
347
+ const columnName = this.getValueByIndex(col, 2);
348
+ const dataType = this.getValueByIndex(col, 3);
349
+ const dataLength = this.getValueByIndex(col, 4);
350
+ const dataPrecision = this.getValueByIndex(col, 5);
351
+ const dataScale = this.getValueByIndex(col, 6);
352
+ const nullable = this.getValueByIndex(col, 7);
353
+ const dataDefault = this.getValueByIndex(col, 8);
278
354
  // 跳过无效数据
279
355
  if (!tableName || !columnName) {
280
356
  continue;
281
357
  }
282
- if (!columnsByTable.has(tableName)) {
283
- columnsByTable.set(tableName, []);
358
+ const tableKey = this.makeTableKey(owner, tableName, currentUser);
359
+ if (!columnsByTable.has(tableKey)) {
360
+ columnsByTable.set(tableKey, []);
361
+ schemaByTable.set(tableKey, owner);
284
362
  }
285
- columnsByTable.get(tableName).push({
363
+ columnsByTable.get(tableKey).push({
286
364
  name: String(columnName).toLowerCase(),
287
365
  type: this.formatDMType(dataType, dataLength, dataPrecision, dataScale),
288
366
  nullable: nullable === 'Y',
@@ -290,24 +368,26 @@ export class DMAdapter {
290
368
  });
291
369
  }
292
370
  // 按表名分组列注释
293
- // 列顺序: 0=TABLE_NAME, 1=COLUMN_NAME, 2=COMMENTS
371
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=COLUMN_NAME, 3=COMMENTS
294
372
  const commentsByTable = new Map();
295
373
  for (const comment of allComments) {
296
- const tableName = this.getValueByIndex(comment, 0);
297
- const columnName = this.getValueByIndex(comment, 1);
298
- const comments = this.getValueByIndex(comment, 2);
374
+ const owner = this.getValueByIndex(comment, 0);
375
+ const tableName = this.getValueByIndex(comment, 1);
376
+ const columnName = this.getValueByIndex(comment, 2);
377
+ const comments = this.getValueByIndex(comment, 3);
299
378
  // 跳过无效数据
300
379
  if (!tableName || !columnName || !comments) {
301
380
  continue;
302
381
  }
303
- if (!commentsByTable.has(tableName)) {
304
- commentsByTable.set(tableName, new Map());
382
+ const tableKey = this.makeTableKey(owner, tableName, currentUser);
383
+ if (!commentsByTable.has(tableKey)) {
384
+ commentsByTable.set(tableKey, new Map());
305
385
  }
306
- commentsByTable.get(tableName).set(String(columnName).toLowerCase(), String(comments));
386
+ commentsByTable.get(tableKey).set(String(columnName).toLowerCase(), String(comments));
307
387
  }
308
388
  // 将注释添加到列信息中
309
- for (const [tableName, columns] of columnsByTable.entries()) {
310
- const tableComments = commentsByTable.get(tableName);
389
+ for (const [tableKey, columns] of columnsByTable.entries()) {
390
+ const tableComments = commentsByTable.get(tableKey);
311
391
  if (tableComments) {
312
392
  for (const col of columns) {
313
393
  if (tableComments.has(col.name)) {
@@ -317,28 +397,31 @@ export class DMAdapter {
317
397
  }
318
398
  }
319
399
  // 按表名分组主键信息
320
- // 列顺序: 0=TABLE_NAME, 1=COLUMN_NAME, 2=POSITION
400
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=COLUMN_NAME, 3=POSITION
321
401
  const primaryKeysByTable = new Map();
322
402
  for (const pk of allPrimaryKeys) {
323
- const tableName = this.getValueByIndex(pk, 0);
324
- const columnName = this.getValueByIndex(pk, 1);
403
+ const owner = this.getValueByIndex(pk, 0);
404
+ const tableName = this.getValueByIndex(pk, 1);
405
+ const columnName = this.getValueByIndex(pk, 2);
325
406
  // 跳过无效数据
326
407
  if (!tableName || !columnName) {
327
408
  continue;
328
409
  }
329
- if (!primaryKeysByTable.has(tableName)) {
330
- primaryKeysByTable.set(tableName, []);
410
+ const tableKey = this.makeTableKey(owner, tableName, currentUser);
411
+ if (!primaryKeysByTable.has(tableKey)) {
412
+ primaryKeysByTable.set(tableKey, []);
331
413
  }
332
- primaryKeysByTable.get(tableName).push(String(columnName).toLowerCase());
414
+ primaryKeysByTable.get(tableKey).push(String(columnName).toLowerCase());
333
415
  }
334
416
  // 按表名分组索引信息
335
- // 列顺序: 0=TABLE_NAME, 1=INDEX_NAME, 2=UNIQUENESS, 3=COLUMN_NAME, 4=COLUMN_POSITION
417
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=INDEX_NAME, 3=UNIQUENESS, 4=COLUMN_NAME, 5=COLUMN_POSITION
336
418
  const indexesByTable = new Map();
337
419
  for (const idx of allIndexes) {
338
- const tableName = this.getValueByIndex(idx, 0);
339
- const indexName = this.getValueByIndex(idx, 1);
340
- const uniqueness = this.getValueByIndex(idx, 2);
341
- const columnName = this.getValueByIndex(idx, 3);
420
+ const owner = this.getValueByIndex(idx, 0);
421
+ const tableName = this.getValueByIndex(idx, 1);
422
+ const indexName = this.getValueByIndex(idx, 2);
423
+ const uniqueness = this.getValueByIndex(idx, 3);
424
+ const columnName = this.getValueByIndex(idx, 4);
342
425
  // 跳过无效数据
343
426
  if (!tableName || !indexName || !columnName) {
344
427
  continue;
@@ -348,10 +431,11 @@ export class DMAdapter {
348
431
  if (idxNameStr.includes('PK_') || idxNameStr.startsWith('INDEX') || idxNameStr.includes('SYS_')) {
349
432
  continue;
350
433
  }
351
- if (!indexesByTable.has(tableName)) {
352
- indexesByTable.set(tableName, new Map());
434
+ const tableKey = this.makeTableKey(owner, tableName, currentUser);
435
+ if (!indexesByTable.has(tableKey)) {
436
+ indexesByTable.set(tableKey, new Map());
353
437
  }
354
- const tableIndexes = indexesByTable.get(tableName);
438
+ const tableIndexes = indexesByTable.get(tableKey);
355
439
  if (!tableIndexes.has(indexName)) {
356
440
  tableIndexes.set(indexName, {
357
441
  columns: [],
@@ -361,41 +445,48 @@ export class DMAdapter {
361
445
  tableIndexes.get(indexName).columns.push(String(columnName).toLowerCase());
362
446
  }
363
447
  // 按表名分组行数统计
364
- // 列顺序: 0=TABLE_NAME, 1=NUM_ROWS, 2=TABLE_COMMENT
448
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=NUM_ROWS, 3=TABLE_COMMENT
365
449
  const rowsByTable = new Map();
366
450
  const tableCommentsByTable = new Map();
367
451
  for (const stat of allStats) {
368
- const tableName = this.getValueByIndex(stat, 0);
369
- const numRows = this.getValueByIndex(stat, 1);
370
- const tableComment = this.getValueByIndex(stat, 2);
452
+ const owner = this.getValueByIndex(stat, 0);
453
+ const tableName = this.getValueByIndex(stat, 1);
454
+ const numRows = this.getValueByIndex(stat, 2);
455
+ const tableComment = this.getValueByIndex(stat, 3);
371
456
  if (tableName) {
372
- rowsByTable.set(tableName, Number(numRows) || 0);
457
+ const tableKey = this.makeTableKey(owner, tableName, currentUser);
458
+ rowsByTable.set(tableKey, Number(numRows) || 0);
373
459
  if (tableComment) {
374
- tableCommentsByTable.set(tableName, tableComment);
460
+ tableCommentsByTable.set(tableKey, tableComment);
375
461
  }
376
462
  }
377
463
  }
378
464
  // 按表名分组外键信息
379
- // 列顺序: 0=TABLE_NAME, 1=CONSTRAINT_NAME, 2=COLUMN_NAME, 3=REFERENCED_TABLE, 4=REFERENCED_COLUMN, 5=DELETE_RULE, 6=POSITION
465
+ // 列顺序: 0=OWNER, 1=TABLE_NAME, 2=CONSTRAINT_NAME, 3=COLUMN_NAME, 4=REFERENCED_TABLE,
466
+ // 5=REFERENCED_COLUMN, 6=DELETE_RULE, 7=REF_OWNER, 8=POSITION
380
467
  const foreignKeysByTable = new Map();
381
468
  const relationships = [];
382
469
  for (const fk of allForeignKeys) {
383
- const tableName = this.getValueByIndex(fk, 0);
384
- const constraintName = this.getValueByIndex(fk, 1);
385
- const columnName = this.getValueByIndex(fk, 2);
386
- const referencedTable = this.getValueByIndex(fk, 3);
387
- const referencedColumn = this.getValueByIndex(fk, 4);
388
- const deleteRule = this.getValueByIndex(fk, 5);
470
+ const owner = this.getValueByIndex(fk, 0);
471
+ const tableName = this.getValueByIndex(fk, 1);
472
+ const constraintName = this.getValueByIndex(fk, 2);
473
+ const columnName = this.getValueByIndex(fk, 3);
474
+ const referencedTable = this.getValueByIndex(fk, 4);
475
+ const referencedColumn = this.getValueByIndex(fk, 5);
476
+ const deleteRule = this.getValueByIndex(fk, 6);
477
+ const refOwner = this.getValueByIndex(fk, 7);
389
478
  if (!tableName || !constraintName)
390
479
  continue;
391
- if (!foreignKeysByTable.has(tableName)) {
392
- foreignKeysByTable.set(tableName, new Map());
480
+ const tableKey = this.makeTableKey(owner, tableName, currentUser);
481
+ const refTableKey = this.makeTableKey(refOwner, referencedTable, currentUser);
482
+ if (!foreignKeysByTable.has(tableKey)) {
483
+ foreignKeysByTable.set(tableKey, new Map());
393
484
  }
394
- const tableForeignKeys = foreignKeysByTable.get(tableName);
485
+ const tableForeignKeys = foreignKeysByTable.get(tableKey);
395
486
  if (!tableForeignKeys.has(constraintName)) {
396
487
  tableForeignKeys.set(constraintName, {
397
488
  columns: [],
398
- referencedTable: String(referencedTable).toLowerCase(),
489
+ referencedTable: String(refTableKey).toLowerCase(),
399
490
  referencedColumns: [],
400
491
  onDelete: deleteRule,
401
492
  });
@@ -405,10 +496,10 @@ export class DMAdapter {
405
496
  fkInfo.referencedColumns.push(String(referencedColumn).toLowerCase());
406
497
  }
407
498
  // 生成全局关系视图
408
- for (const [tableName, tableForeignKeys] of foreignKeysByTable.entries()) {
499
+ for (const [tableKey, tableForeignKeys] of foreignKeysByTable.entries()) {
409
500
  for (const [constraintName, fkInfo] of tableForeignKeys.entries()) {
410
501
  relationships.push({
411
- fromTable: String(tableName).toLowerCase(),
502
+ fromTable: String(tableKey).toLowerCase(),
412
503
  fromColumns: fkInfo.columns,
413
504
  toTable: fkInfo.referencedTable,
414
505
  toColumns: fkInfo.referencedColumns,
@@ -419,8 +510,8 @@ export class DMAdapter {
419
510
  }
420
511
  // 组装表信息
421
512
  const tableInfos = [];
422
- for (const [tableName, columns] of columnsByTable.entries()) {
423
- const tableIndexes = indexesByTable.get(tableName);
513
+ for (const [tableKey, columns] of columnsByTable.entries()) {
514
+ const tableIndexes = indexesByTable.get(tableKey);
424
515
  const indexInfos = [];
425
516
  if (tableIndexes) {
426
517
  for (const [indexName, indexData] of tableIndexes.entries()) {
@@ -432,7 +523,7 @@ export class DMAdapter {
432
523
  }
433
524
  }
434
525
  // 组装外键信息
435
- const tableForeignKeys = foreignKeysByTable.get(tableName);
526
+ const tableForeignKeys = foreignKeysByTable.get(tableKey);
436
527
  const foreignKeyInfos = [];
437
528
  if (tableForeignKeys) {
438
529
  for (const [constraintName, fkData] of tableForeignKeys.entries()) {
@@ -446,13 +537,14 @@ export class DMAdapter {
446
537
  }
447
538
  }
448
539
  tableInfos.push({
449
- name: String(tableName).toLowerCase(),
450
- comment: tableCommentsByTable.get(tableName) || undefined,
540
+ name: String(tableKey).toLowerCase(),
541
+ schema: schemaByTable.get(tableKey),
542
+ comment: tableCommentsByTable.get(tableKey) || undefined,
451
543
  columns,
452
- primaryKeys: primaryKeysByTable.get(tableName) || [],
544
+ primaryKeys: primaryKeysByTable.get(tableKey) || [],
453
545
  indexes: indexInfos,
454
546
  foreignKeys: foreignKeyInfos.length > 0 ? foreignKeyInfos : undefined,
455
- estimatedRows: rowsByTable.get(tableName) || 0,
547
+ estimatedRows: rowsByTable.get(tableKey) || 0,
456
548
  });
457
549
  }
458
550
  // 按表名排序