@bytebase/dbhub 0.16.0 → 0.17.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.
|
@@ -1339,6 +1339,18 @@ function validateSourceConfig(source, configPath) {
|
|
|
1339
1339
|
);
|
|
1340
1340
|
}
|
|
1341
1341
|
}
|
|
1342
|
+
if (source.search_path !== void 0) {
|
|
1343
|
+
if (source.type !== "postgres") {
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
`Configuration file ${configPath}: source '${source.id}' has 'search_path' but it is only supported for PostgreSQL sources.`
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
if (typeof source.search_path !== "string" || source.search_path.trim().length === 0) {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
`Configuration file ${configPath}: source '${source.id}' has invalid search_path. Must be a non-empty string of comma-separated schema names (e.g., "myschema,public").`
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1342
1354
|
if (source.readonly !== void 0) {
|
|
1343
1355
|
throw new Error(
|
|
1344
1356
|
`Configuration file ${configPath}: source '${source.id}' has 'readonly' field, but readonly must be configured per-tool, not per-source. Move 'readonly' to [[tools]] configuration instead.`
|
|
@@ -1606,6 +1618,9 @@ var ConnectorManager = class {
|
|
|
1606
1618
|
if (source.readonly !== void 0) {
|
|
1607
1619
|
config.readonly = source.readonly;
|
|
1608
1620
|
}
|
|
1621
|
+
if (source.search_path) {
|
|
1622
|
+
config.searchPath = source.search_path;
|
|
1623
|
+
}
|
|
1609
1624
|
await connector.connect(actualDSN, source.init_script, config);
|
|
1610
1625
|
this.connectors.set(sourceId, connector);
|
|
1611
1626
|
if (!this.sourceIds.includes(sourceId)) {
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
resolveSourceConfigs,
|
|
17
17
|
resolveTransport,
|
|
18
18
|
stripCommentsAndStrings
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-YKDZH7G5.js";
|
|
20
20
|
|
|
21
21
|
// src/connectors/postgres/index.ts
|
|
22
22
|
import pg from "pg";
|
|
@@ -141,6 +141,36 @@ var SQLRowLimiter = class {
|
|
|
141
141
|
}
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
// src/utils/identifier-quoter.ts
|
|
145
|
+
function quoteIdentifier(identifier, dbType) {
|
|
146
|
+
if (/[\0\x08\x09\x1a\n\r]/.test(identifier)) {
|
|
147
|
+
throw new Error(`Invalid identifier: contains control characters: ${identifier}`);
|
|
148
|
+
}
|
|
149
|
+
if (!identifier) {
|
|
150
|
+
throw new Error("Identifier cannot be empty");
|
|
151
|
+
}
|
|
152
|
+
switch (dbType) {
|
|
153
|
+
case "postgres":
|
|
154
|
+
case "sqlite":
|
|
155
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
156
|
+
case "mysql":
|
|
157
|
+
case "mariadb":
|
|
158
|
+
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
159
|
+
case "sqlserver":
|
|
160
|
+
return `[${identifier.replace(/]/g, "]]")}]`;
|
|
161
|
+
default:
|
|
162
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function quoteQualifiedIdentifier(tableName, schemaName, dbType) {
|
|
166
|
+
const quotedTable = quoteIdentifier(tableName, dbType);
|
|
167
|
+
if (schemaName) {
|
|
168
|
+
const quotedSchema = quoteIdentifier(schemaName, dbType);
|
|
169
|
+
return `${quotedSchema}.${quotedTable}`;
|
|
170
|
+
}
|
|
171
|
+
return quotedTable;
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
// src/connectors/postgres/index.ts
|
|
145
175
|
var { Pool } = pg;
|
|
146
176
|
var PostgresDSNParser = class {
|
|
@@ -209,6 +239,8 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
209
239
|
this.pool = null;
|
|
210
240
|
// Source ID is set by ConnectorManager after cloning
|
|
211
241
|
this.sourceId = "default";
|
|
242
|
+
// Default schema for discovery methods (first entry from search_path, or "public")
|
|
243
|
+
this.defaultSchema = "public";
|
|
212
244
|
}
|
|
213
245
|
getId() {
|
|
214
246
|
return this.sourceId;
|
|
@@ -217,11 +249,21 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
217
249
|
return new _PostgresConnector();
|
|
218
250
|
}
|
|
219
251
|
async connect(dsn, initScript, config) {
|
|
252
|
+
this.defaultSchema = "public";
|
|
220
253
|
try {
|
|
221
254
|
const poolConfig = await this.dsnParser.parse(dsn, config);
|
|
222
255
|
if (config?.readonly) {
|
|
223
256
|
poolConfig.options = (poolConfig.options || "") + " -c default_transaction_read_only=on";
|
|
224
257
|
}
|
|
258
|
+
if (config?.searchPath) {
|
|
259
|
+
const schemas = config.searchPath.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
260
|
+
if (schemas.length > 0) {
|
|
261
|
+
this.defaultSchema = schemas[0];
|
|
262
|
+
const quotedSchemas = schemas.map((s) => quoteIdentifier(s, "postgres"));
|
|
263
|
+
const optionsValue = quotedSchemas.join(",").replace(/\\/g, "\\\\").replace(/ /g, "\\ ");
|
|
264
|
+
poolConfig.options = (poolConfig.options || "") + ` -c search_path=${optionsValue}`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
225
267
|
this.pool = new Pool(poolConfig);
|
|
226
268
|
const client = await this.pool.connect();
|
|
227
269
|
client.release();
|
|
@@ -259,7 +301,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
259
301
|
}
|
|
260
302
|
const client = await this.pool.connect();
|
|
261
303
|
try {
|
|
262
|
-
const schemaToUse = schema ||
|
|
304
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
263
305
|
const result = await client.query(
|
|
264
306
|
`
|
|
265
307
|
SELECT table_name
|
|
@@ -280,7 +322,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
280
322
|
}
|
|
281
323
|
const client = await this.pool.connect();
|
|
282
324
|
try {
|
|
283
|
-
const schemaToUse = schema ||
|
|
325
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
284
326
|
const result = await client.query(
|
|
285
327
|
`
|
|
286
328
|
SELECT EXISTS (
|
|
@@ -302,7 +344,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
302
344
|
}
|
|
303
345
|
const client = await this.pool.connect();
|
|
304
346
|
try {
|
|
305
|
-
const schemaToUse = schema ||
|
|
347
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
306
348
|
const result = await client.query(
|
|
307
349
|
`
|
|
308
350
|
SELECT
|
|
@@ -350,18 +392,27 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
350
392
|
}
|
|
351
393
|
const client = await this.pool.connect();
|
|
352
394
|
try {
|
|
353
|
-
const schemaToUse = schema ||
|
|
395
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
354
396
|
const result = await client.query(
|
|
355
397
|
`
|
|
356
|
-
SELECT
|
|
357
|
-
column_name,
|
|
358
|
-
data_type,
|
|
359
|
-
is_nullable,
|
|
360
|
-
column_default
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
398
|
+
SELECT
|
|
399
|
+
c.column_name,
|
|
400
|
+
c.data_type,
|
|
401
|
+
c.is_nullable,
|
|
402
|
+
c.column_default,
|
|
403
|
+
pgd.description
|
|
404
|
+
FROM information_schema.columns c
|
|
405
|
+
LEFT JOIN pg_catalog.pg_namespace nsp
|
|
406
|
+
ON nsp.nspname = c.table_schema
|
|
407
|
+
LEFT JOIN pg_catalog.pg_class cls
|
|
408
|
+
ON cls.relnamespace = nsp.oid
|
|
409
|
+
AND cls.relname = c.table_name
|
|
410
|
+
LEFT JOIN pg_catalog.pg_description pgd
|
|
411
|
+
ON pgd.objoid = cls.oid
|
|
412
|
+
AND pgd.objsubid = c.ordinal_position
|
|
413
|
+
WHERE c.table_schema = $1
|
|
414
|
+
AND c.table_name = $2
|
|
415
|
+
ORDER BY c.ordinal_position
|
|
365
416
|
`,
|
|
366
417
|
[schemaToUse, tableName]
|
|
367
418
|
);
|
|
@@ -370,22 +421,82 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
370
421
|
client.release();
|
|
371
422
|
}
|
|
372
423
|
}
|
|
373
|
-
async
|
|
424
|
+
async getTableRowCount(tableName, schema) {
|
|
374
425
|
if (!this.pool) {
|
|
375
426
|
throw new Error("Not connected to database");
|
|
376
427
|
}
|
|
377
428
|
const client = await this.pool.connect();
|
|
378
429
|
try {
|
|
379
|
-
const schemaToUse = schema ||
|
|
430
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
380
431
|
const result = await client.query(
|
|
381
432
|
`
|
|
382
|
-
SELECT
|
|
433
|
+
SELECT c.reltuples::bigint as count
|
|
434
|
+
FROM pg_class c
|
|
435
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
436
|
+
WHERE c.relname = $1
|
|
437
|
+
AND n.nspname = $2
|
|
438
|
+
AND c.relkind IN ('r','p','m','f')
|
|
439
|
+
`,
|
|
440
|
+
[tableName, schemaToUse]
|
|
441
|
+
);
|
|
442
|
+
if (result.rows.length > 0) {
|
|
443
|
+
const count = Number(result.rows[0].count);
|
|
444
|
+
return count >= 0 ? count : null;
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
} finally {
|
|
448
|
+
client.release();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async getTableComment(tableName, schema) {
|
|
452
|
+
if (!this.pool) {
|
|
453
|
+
throw new Error("Not connected to database");
|
|
454
|
+
}
|
|
455
|
+
const client = await this.pool.connect();
|
|
456
|
+
try {
|
|
457
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
458
|
+
const result = await client.query(
|
|
459
|
+
`
|
|
460
|
+
SELECT obj_description(c.oid) as table_comment
|
|
461
|
+
FROM pg_catalog.pg_class c
|
|
462
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
463
|
+
WHERE c.relname = $1
|
|
464
|
+
AND n.nspname = $2
|
|
465
|
+
AND c.relkind IN ('r','p','m','f')
|
|
466
|
+
`,
|
|
467
|
+
[tableName, schemaToUse]
|
|
468
|
+
);
|
|
469
|
+
if (result.rows.length > 0) {
|
|
470
|
+
return result.rows[0].table_comment || null;
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
473
|
+
} finally {
|
|
474
|
+
client.release();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async getStoredProcedures(schema, routineType) {
|
|
478
|
+
if (!this.pool) {
|
|
479
|
+
throw new Error("Not connected to database");
|
|
480
|
+
}
|
|
481
|
+
const client = await this.pool.connect();
|
|
482
|
+
try {
|
|
483
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
484
|
+
const params = [schemaToUse];
|
|
485
|
+
let typeFilter = "";
|
|
486
|
+
if (routineType === "function") {
|
|
487
|
+
typeFilter = " AND routine_type = 'FUNCTION'";
|
|
488
|
+
} else if (routineType === "procedure") {
|
|
489
|
+
typeFilter = " AND routine_type = 'PROCEDURE'";
|
|
490
|
+
}
|
|
491
|
+
const result = await client.query(
|
|
492
|
+
`
|
|
493
|
+
SELECT
|
|
383
494
|
routine_name
|
|
384
495
|
FROM information_schema.routines
|
|
385
|
-
WHERE routine_schema = $1
|
|
496
|
+
WHERE routine_schema = $1${typeFilter}
|
|
386
497
|
ORDER BY routine_name
|
|
387
498
|
`,
|
|
388
|
-
|
|
499
|
+
params
|
|
389
500
|
);
|
|
390
501
|
return result.rows.map((row) => row.routine_name);
|
|
391
502
|
} finally {
|
|
@@ -398,7 +509,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
398
509
|
}
|
|
399
510
|
const client = await this.pool.connect();
|
|
400
511
|
try {
|
|
401
|
-
const schemaToUse = schema ||
|
|
512
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
402
513
|
const result = await client.query(
|
|
403
514
|
`
|
|
404
515
|
SELECT
|
|
@@ -789,33 +900,78 @@ var SQLServerConnector = class _SQLServerConnector {
|
|
|
789
900
|
const schemaToUse = schema || "dbo";
|
|
790
901
|
const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
|
|
791
902
|
const query = `
|
|
792
|
-
SELECT COLUMN_NAME as column_name,
|
|
793
|
-
DATA_TYPE as data_type,
|
|
794
|
-
IS_NULLABLE as is_nullable,
|
|
795
|
-
COLUMN_DEFAULT as column_default
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
903
|
+
SELECT c.COLUMN_NAME as column_name,
|
|
904
|
+
c.DATA_TYPE as data_type,
|
|
905
|
+
c.IS_NULLABLE as is_nullable,
|
|
906
|
+
c.COLUMN_DEFAULT as column_default,
|
|
907
|
+
ep.value as description
|
|
908
|
+
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
909
|
+
LEFT JOIN sys.columns sc
|
|
910
|
+
ON sc.name = c.COLUMN_NAME
|
|
911
|
+
AND sc.object_id = OBJECT_ID(QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME))
|
|
912
|
+
LEFT JOIN sys.extended_properties ep
|
|
913
|
+
ON ep.major_id = sc.object_id
|
|
914
|
+
AND ep.minor_id = sc.column_id
|
|
915
|
+
AND ep.name = 'MS_Description'
|
|
916
|
+
WHERE c.TABLE_NAME = @tableName
|
|
917
|
+
AND c.TABLE_SCHEMA = @schema
|
|
918
|
+
ORDER BY c.ORDINAL_POSITION
|
|
800
919
|
`;
|
|
801
920
|
const result = await request.query(query);
|
|
802
|
-
return result.recordset
|
|
921
|
+
return result.recordset.map((row) => ({
|
|
922
|
+
...row,
|
|
923
|
+
description: row.description || null
|
|
924
|
+
}));
|
|
803
925
|
} catch (error) {
|
|
804
926
|
throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`);
|
|
805
927
|
}
|
|
806
928
|
}
|
|
807
|
-
async
|
|
929
|
+
async getTableComment(tableName, schema) {
|
|
930
|
+
if (!this.connection) {
|
|
931
|
+
throw new Error("Not connected to SQL Server database");
|
|
932
|
+
}
|
|
933
|
+
try {
|
|
934
|
+
const schemaToUse = schema || "dbo";
|
|
935
|
+
const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
|
|
936
|
+
const query = `
|
|
937
|
+
SELECT ep.value as table_comment
|
|
938
|
+
FROM sys.extended_properties ep
|
|
939
|
+
JOIN sys.tables t ON ep.major_id = t.object_id
|
|
940
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
941
|
+
WHERE ep.minor_id = 0
|
|
942
|
+
AND ep.name = 'MS_Description'
|
|
943
|
+
AND t.name = @tableName
|
|
944
|
+
AND s.name = @schema
|
|
945
|
+
`;
|
|
946
|
+
const result = await request.query(query);
|
|
947
|
+
if (result.recordset.length > 0) {
|
|
948
|
+
return result.recordset[0].table_comment || null;
|
|
949
|
+
}
|
|
950
|
+
return null;
|
|
951
|
+
} catch (error) {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async getStoredProcedures(schema, routineType) {
|
|
808
956
|
if (!this.connection) {
|
|
809
957
|
throw new Error("Not connected to SQL Server database");
|
|
810
958
|
}
|
|
811
959
|
try {
|
|
812
960
|
const schemaToUse = schema || "dbo";
|
|
813
961
|
const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
|
|
962
|
+
let typeFilter;
|
|
963
|
+
if (routineType === "function") {
|
|
964
|
+
typeFilter = "AND ROUTINE_TYPE = 'FUNCTION'";
|
|
965
|
+
} else if (routineType === "procedure") {
|
|
966
|
+
typeFilter = "AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
967
|
+
} else {
|
|
968
|
+
typeFilter = "AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')";
|
|
969
|
+
}
|
|
814
970
|
const query = `
|
|
815
971
|
SELECT ROUTINE_NAME
|
|
816
972
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
817
973
|
WHERE ROUTINE_SCHEMA = @schema
|
|
818
|
-
|
|
974
|
+
${typeFilter}
|
|
819
975
|
ORDER BY ROUTINE_NAME
|
|
820
976
|
`;
|
|
821
977
|
const result = await request.query(query);
|
|
@@ -948,38 +1104,6 @@ ConnectorRegistry.register(sqlServerConnector);
|
|
|
948
1104
|
|
|
949
1105
|
// src/connectors/sqlite/index.ts
|
|
950
1106
|
import Database from "better-sqlite3";
|
|
951
|
-
|
|
952
|
-
// src/utils/identifier-quoter.ts
|
|
953
|
-
function quoteIdentifier(identifier, dbType) {
|
|
954
|
-
if (/[\0\x08\x09\x1a\n\r]/.test(identifier)) {
|
|
955
|
-
throw new Error(`Invalid identifier: contains control characters: ${identifier}`);
|
|
956
|
-
}
|
|
957
|
-
if (!identifier) {
|
|
958
|
-
throw new Error("Identifier cannot be empty");
|
|
959
|
-
}
|
|
960
|
-
switch (dbType) {
|
|
961
|
-
case "postgres":
|
|
962
|
-
case "sqlite":
|
|
963
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
964
|
-
case "mysql":
|
|
965
|
-
case "mariadb":
|
|
966
|
-
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
967
|
-
case "sqlserver":
|
|
968
|
-
return `[${identifier.replace(/]/g, "]]")}]`;
|
|
969
|
-
default:
|
|
970
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
function quoteQualifiedIdentifier(tableName, schemaName, dbType) {
|
|
974
|
-
const quotedTable = quoteIdentifier(tableName, dbType);
|
|
975
|
-
if (schemaName) {
|
|
976
|
-
const quotedSchema = quoteIdentifier(schemaName, dbType);
|
|
977
|
-
return `${quotedSchema}.${quotedTable}`;
|
|
978
|
-
}
|
|
979
|
-
return quotedTable;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// src/connectors/sqlite/index.ts
|
|
983
1107
|
var SQLiteDSNParser = class {
|
|
984
1108
|
async parse(dsn, config) {
|
|
985
1109
|
if (!this.isValidDSN(dsn)) {
|
|
@@ -1181,14 +1305,15 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1181
1305
|
data_type: row.type,
|
|
1182
1306
|
// In SQLite, primary key columns are automatically NOT NULL even if notnull=0
|
|
1183
1307
|
is_nullable: row.notnull === 1 || row.pk > 0 ? "NO" : "YES",
|
|
1184
|
-
column_default: row.dflt_value
|
|
1308
|
+
column_default: row.dflt_value,
|
|
1309
|
+
description: null
|
|
1185
1310
|
}));
|
|
1186
1311
|
return columns;
|
|
1187
1312
|
} catch (error) {
|
|
1188
1313
|
throw error;
|
|
1189
1314
|
}
|
|
1190
1315
|
}
|
|
1191
|
-
async getStoredProcedures(schema) {
|
|
1316
|
+
async getStoredProcedures(schema, routineType) {
|
|
1192
1317
|
if (!this.db) {
|
|
1193
1318
|
throw new Error("Not connected to SQLite database");
|
|
1194
1319
|
}
|
|
@@ -1566,11 +1691,12 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1566
1691
|
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
1567
1692
|
const [rows] = await this.pool.query(
|
|
1568
1693
|
`
|
|
1569
|
-
SELECT
|
|
1570
|
-
COLUMN_NAME as column_name,
|
|
1571
|
-
DATA_TYPE as data_type,
|
|
1694
|
+
SELECT
|
|
1695
|
+
COLUMN_NAME as column_name,
|
|
1696
|
+
DATA_TYPE as data_type,
|
|
1572
1697
|
IS_NULLABLE as is_nullable,
|
|
1573
|
-
COLUMN_DEFAULT as column_default
|
|
1698
|
+
COLUMN_DEFAULT as column_default,
|
|
1699
|
+
COLUMN_COMMENT as description
|
|
1574
1700
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1575
1701
|
${schemaClause}
|
|
1576
1702
|
AND TABLE_NAME = ?
|
|
@@ -1578,24 +1704,57 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1578
1704
|
`,
|
|
1579
1705
|
queryParams
|
|
1580
1706
|
);
|
|
1581
|
-
return rows
|
|
1707
|
+
return rows.map((row) => ({
|
|
1708
|
+
...row,
|
|
1709
|
+
description: row.description || null
|
|
1710
|
+
}));
|
|
1582
1711
|
} catch (error) {
|
|
1583
1712
|
console.error("Error getting table schema:", error);
|
|
1584
1713
|
throw error;
|
|
1585
1714
|
}
|
|
1586
1715
|
}
|
|
1587
|
-
async
|
|
1716
|
+
async getTableComment(tableName, schema) {
|
|
1717
|
+
if (!this.pool) {
|
|
1718
|
+
throw new Error("Not connected to database");
|
|
1719
|
+
}
|
|
1720
|
+
try {
|
|
1721
|
+
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
1722
|
+
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
1723
|
+
const [rows] = await this.pool.query(
|
|
1724
|
+
`
|
|
1725
|
+
SELECT TABLE_COMMENT
|
|
1726
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
1727
|
+
${schemaClause}
|
|
1728
|
+
AND TABLE_NAME = ?
|
|
1729
|
+
`,
|
|
1730
|
+
queryParams
|
|
1731
|
+
);
|
|
1732
|
+
if (rows.length > 0) {
|
|
1733
|
+
return rows[0].TABLE_COMMENT || null;
|
|
1734
|
+
}
|
|
1735
|
+
return null;
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
return null;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
async getStoredProcedures(schema, routineType) {
|
|
1588
1741
|
if (!this.pool) {
|
|
1589
1742
|
throw new Error("Not connected to database");
|
|
1590
1743
|
}
|
|
1591
1744
|
try {
|
|
1592
1745
|
const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
|
|
1593
1746
|
const queryParams = schema ? [schema] : [];
|
|
1747
|
+
let typeFilter = "";
|
|
1748
|
+
if (routineType === "function") {
|
|
1749
|
+
typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
|
|
1750
|
+
} else if (routineType === "procedure") {
|
|
1751
|
+
typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
1752
|
+
}
|
|
1594
1753
|
const [rows] = await this.pool.query(
|
|
1595
1754
|
`
|
|
1596
1755
|
SELECT ROUTINE_NAME
|
|
1597
1756
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
1598
|
-
${schemaClause}
|
|
1757
|
+
${schemaClause}${typeFilter}
|
|
1599
1758
|
ORDER BY ROUTINE_NAME
|
|
1600
1759
|
`,
|
|
1601
1760
|
queryParams
|
|
@@ -1757,7 +1916,7 @@ var mysqlConnector = new MySQLConnector();
|
|
|
1757
1916
|
ConnectorRegistry.register(mysqlConnector);
|
|
1758
1917
|
|
|
1759
1918
|
// src/connectors/mariadb/index.ts
|
|
1760
|
-
import mariadb from "mariadb";
|
|
1919
|
+
import * as mariadb from "mariadb";
|
|
1761
1920
|
var MariadbDSNParser = class {
|
|
1762
1921
|
async parse(dsn, config) {
|
|
1763
1922
|
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
@@ -1979,11 +2138,12 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
1979
2138
|
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
1980
2139
|
const rows = await this.pool.query(
|
|
1981
2140
|
`
|
|
1982
|
-
SELECT
|
|
1983
|
-
COLUMN_NAME as column_name,
|
|
1984
|
-
DATA_TYPE as data_type,
|
|
2141
|
+
SELECT
|
|
2142
|
+
COLUMN_NAME as column_name,
|
|
2143
|
+
DATA_TYPE as data_type,
|
|
1985
2144
|
IS_NULLABLE as is_nullable,
|
|
1986
|
-
COLUMN_DEFAULT as column_default
|
|
2145
|
+
COLUMN_DEFAULT as column_default,
|
|
2146
|
+
COLUMN_COMMENT as description
|
|
1987
2147
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1988
2148
|
${schemaClause}
|
|
1989
2149
|
AND TABLE_NAME = ?
|
|
@@ -1991,24 +2151,57 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
1991
2151
|
`,
|
|
1992
2152
|
queryParams
|
|
1993
2153
|
);
|
|
1994
|
-
return rows
|
|
2154
|
+
return rows.map((row) => ({
|
|
2155
|
+
...row,
|
|
2156
|
+
description: row.description || null
|
|
2157
|
+
}));
|
|
1995
2158
|
} catch (error) {
|
|
1996
2159
|
console.error("Error getting table schema:", error);
|
|
1997
2160
|
throw error;
|
|
1998
2161
|
}
|
|
1999
2162
|
}
|
|
2000
|
-
async
|
|
2163
|
+
async getTableComment(tableName, schema) {
|
|
2164
|
+
if (!this.pool) {
|
|
2165
|
+
throw new Error("Not connected to database");
|
|
2166
|
+
}
|
|
2167
|
+
try {
|
|
2168
|
+
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
2169
|
+
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
2170
|
+
const rows = await this.pool.query(
|
|
2171
|
+
`
|
|
2172
|
+
SELECT TABLE_COMMENT
|
|
2173
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
2174
|
+
${schemaClause}
|
|
2175
|
+
AND TABLE_NAME = ?
|
|
2176
|
+
`,
|
|
2177
|
+
queryParams
|
|
2178
|
+
);
|
|
2179
|
+
if (rows.length > 0) {
|
|
2180
|
+
return rows[0].TABLE_COMMENT || null;
|
|
2181
|
+
}
|
|
2182
|
+
return null;
|
|
2183
|
+
} catch (error) {
|
|
2184
|
+
return null;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
async getStoredProcedures(schema, routineType) {
|
|
2001
2188
|
if (!this.pool) {
|
|
2002
2189
|
throw new Error("Not connected to database");
|
|
2003
2190
|
}
|
|
2004
2191
|
try {
|
|
2005
2192
|
const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
|
|
2006
2193
|
const queryParams = schema ? [schema] : [];
|
|
2194
|
+
let typeFilter = "";
|
|
2195
|
+
if (routineType === "function") {
|
|
2196
|
+
typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
|
|
2197
|
+
} else if (routineType === "procedure") {
|
|
2198
|
+
typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
2199
|
+
}
|
|
2007
2200
|
const rows = await this.pool.query(
|
|
2008
2201
|
`
|
|
2009
2202
|
SELECT ROUTINE_NAME
|
|
2010
2203
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
2011
|
-
${schemaClause}
|
|
2204
|
+
${schemaClause}${typeFilter}
|
|
2012
2205
|
ORDER BY ROUTINE_NAME
|
|
2013
2206
|
`,
|
|
2014
2207
|
queryParams
|
|
@@ -2389,7 +2582,7 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2389
2582
|
// src/tools/search-objects.ts
|
|
2390
2583
|
import { z as z2 } from "zod";
|
|
2391
2584
|
var searchDatabaseObjectsSchema = {
|
|
2392
|
-
object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Object type to search"),
|
|
2585
|
+
object_type: z2.enum(["schema", "table", "column", "procedure", "function", "index"]).describe("Object type to search"),
|
|
2393
2586
|
pattern: z2.string().optional().default("%").describe("LIKE pattern (% = any chars, _ = one char). Default: %"),
|
|
2394
2587
|
schema: z2.string().optional().describe("Filter to schema"),
|
|
2395
2588
|
table: z2.string().optional().describe("Filter to table (requires schema; column/index only)"),
|
|
@@ -2402,6 +2595,9 @@ function likePatternToRegex(pattern) {
|
|
|
2402
2595
|
}
|
|
2403
2596
|
async function getTableRowCount(connector, tableName, schemaName) {
|
|
2404
2597
|
try {
|
|
2598
|
+
if (connector.getTableRowCount) {
|
|
2599
|
+
return await connector.getTableRowCount(tableName, schemaName);
|
|
2600
|
+
}
|
|
2405
2601
|
const qualifiedTable = quoteQualifiedIdentifier(tableName, schemaName, connector.id);
|
|
2406
2602
|
const countQuery = `SELECT COUNT(*) as count FROM ${qualifiedTable}`;
|
|
2407
2603
|
const result = await connector.executeSQL(countQuery, { maxRows: 1 });
|
|
@@ -2413,6 +2609,16 @@ async function getTableRowCount(connector, tableName, schemaName) {
|
|
|
2413
2609
|
}
|
|
2414
2610
|
return null;
|
|
2415
2611
|
}
|
|
2612
|
+
async function getTableComment(connector, tableName, schemaName) {
|
|
2613
|
+
try {
|
|
2614
|
+
if (connector.getTableComment) {
|
|
2615
|
+
return await connector.getTableComment(tableName, schemaName);
|
|
2616
|
+
}
|
|
2617
|
+
return null;
|
|
2618
|
+
} catch (error) {
|
|
2619
|
+
return null;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2416
2622
|
async function searchSchemas(connector, pattern, detailLevel, limit) {
|
|
2417
2623
|
const schemas = await connector.getSchemas();
|
|
2418
2624
|
const regex = likePatternToRegex(pattern);
|
|
@@ -2463,11 +2669,13 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
|
|
|
2463
2669
|
try {
|
|
2464
2670
|
const columns = await connector.getTableSchema(tableName, schemaName);
|
|
2465
2671
|
const rowCount = await getTableRowCount(connector, tableName, schemaName);
|
|
2672
|
+
const comment = await getTableComment(connector, tableName, schemaName);
|
|
2466
2673
|
results.push({
|
|
2467
2674
|
name: tableName,
|
|
2468
2675
|
schema: schemaName,
|
|
2469
2676
|
column_count: columns.length,
|
|
2470
|
-
row_count: rowCount
|
|
2677
|
+
row_count: rowCount,
|
|
2678
|
+
...comment ? { comment } : {}
|
|
2471
2679
|
});
|
|
2472
2680
|
} catch (error) {
|
|
2473
2681
|
results.push({
|
|
@@ -2482,16 +2690,19 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
|
|
|
2482
2690
|
const columns = await connector.getTableSchema(tableName, schemaName);
|
|
2483
2691
|
const indexes = await connector.getTableIndexes(tableName, schemaName);
|
|
2484
2692
|
const rowCount = await getTableRowCount(connector, tableName, schemaName);
|
|
2693
|
+
const comment = await getTableComment(connector, tableName, schemaName);
|
|
2485
2694
|
results.push({
|
|
2486
2695
|
name: tableName,
|
|
2487
2696
|
schema: schemaName,
|
|
2488
2697
|
column_count: columns.length,
|
|
2489
2698
|
row_count: rowCount,
|
|
2699
|
+
...comment ? { comment } : {},
|
|
2490
2700
|
columns: columns.map((col) => ({
|
|
2491
2701
|
name: col.column_name,
|
|
2492
2702
|
type: col.data_type,
|
|
2493
2703
|
nullable: col.is_nullable === "YES",
|
|
2494
|
-
default: col.column_default
|
|
2704
|
+
default: col.column_default,
|
|
2705
|
+
...col.description ? { description: col.description } : {}
|
|
2495
2706
|
})),
|
|
2496
2707
|
indexes: indexes.map((idx) => ({
|
|
2497
2708
|
name: idx.index_name,
|
|
@@ -2553,7 +2764,8 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
|
|
|
2553
2764
|
schema: schemaName,
|
|
2554
2765
|
type: column.data_type,
|
|
2555
2766
|
nullable: column.is_nullable === "YES",
|
|
2556
|
-
default: column.column_default
|
|
2767
|
+
default: column.column_default,
|
|
2768
|
+
...column.description ? { description: column.description } : {}
|
|
2557
2769
|
});
|
|
2558
2770
|
}
|
|
2559
2771
|
}
|
|
@@ -2567,7 +2779,7 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
|
|
|
2567
2779
|
}
|
|
2568
2780
|
return results;
|
|
2569
2781
|
}
|
|
2570
|
-
async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit) {
|
|
2782
|
+
async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit, routineType) {
|
|
2571
2783
|
const regex = likePatternToRegex(pattern);
|
|
2572
2784
|
const results = [];
|
|
2573
2785
|
let schemasToSearch;
|
|
@@ -2579,7 +2791,7 @@ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, l
|
|
|
2579
2791
|
for (const schemaName of schemasToSearch) {
|
|
2580
2792
|
if (results.length >= limit) break;
|
|
2581
2793
|
try {
|
|
2582
|
-
const procedures = await connector.getStoredProcedures(schemaName);
|
|
2794
|
+
const procedures = await connector.getStoredProcedures(schemaName, routineType);
|
|
2583
2795
|
const matched = procedures.filter((proc) => regex.test(proc));
|
|
2584
2796
|
for (const procName of matched) {
|
|
2585
2797
|
if (results.length >= limit) break;
|
|
@@ -2716,7 +2928,10 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
|
2716
2928
|
results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
|
|
2717
2929
|
break;
|
|
2718
2930
|
case "procedure":
|
|
2719
|
-
results = await searchProcedures(connector, pattern, schema, detail_level, limit);
|
|
2931
|
+
results = await searchProcedures(connector, pattern, schema, detail_level, limit, "procedure");
|
|
2932
|
+
break;
|
|
2933
|
+
case "function":
|
|
2934
|
+
results = await searchProcedures(connector, pattern, schema, detail_level, limit, "function");
|
|
2720
2935
|
break;
|
|
2721
2936
|
case "index":
|
|
2722
2937
|
results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
|
|
@@ -2836,7 +3051,7 @@ function getSearchObjectsMetadata(sourceId) {
|
|
|
2836
3051
|
const isSingleSource = sourceIds.length === 1;
|
|
2837
3052
|
const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`;
|
|
2838
3053
|
const title = isSingleSource ? `Search Database Objects (${dbType})` : `Search Database Objects on ${sourceId} (${dbType})`;
|
|
2839
|
-
const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, indexes) on the '${sourceId}' ${dbType} database`;
|
|
3054
|
+
const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, functions, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, functions, indexes) on the '${sourceId}' ${dbType} database`;
|
|
2840
3055
|
return {
|
|
2841
3056
|
name: toolName,
|
|
2842
3057
|
description,
|
|
@@ -3392,7 +3607,7 @@ See documentation for more details on configuring database connections.
|
|
|
3392
3607
|
const sources = sourceConfigsData.sources;
|
|
3393
3608
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
3394
3609
|
await connectorManager.connectWithSources(sources);
|
|
3395
|
-
const { initializeToolRegistry } = await import("./registry-
|
|
3610
|
+
const { initializeToolRegistry } = await import("./registry-7HJVUJCM.js");
|
|
3396
3611
|
initializeToolRegistry({
|
|
3397
3612
|
sources: sourceConfigsData.sources,
|
|
3398
3613
|
tools: sourceConfigsData.tools
|
|
@@ -3430,9 +3645,6 @@ See documentation for more details on configuring database connections.
|
|
|
3430
3645
|
app.use(express.json());
|
|
3431
3646
|
app.use((req, res, next) => {
|
|
3432
3647
|
const origin = req.headers.origin;
|
|
3433
|
-
if (origin && !origin.startsWith("http://localhost") && !origin.startsWith("https://localhost")) {
|
|
3434
|
-
return res.status(403).json({ error: "Forbidden origin" });
|
|
3435
|
-
}
|
|
3436
3648
|
res.header("Access-Control-Allow-Origin", origin || "http://localhost");
|
|
3437
3649
|
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
3438
3650
|
res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
package/package.json
CHANGED