@bytebase/dbhub 1.3.1 → 1.5.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.
@@ -1,551 +0,0 @@
1
- import {
2
- MissingDatabaseError,
3
- getMySQLFamilyHealthCheck,
4
- isTiDBVersion,
5
- parseQueryResultSets,
6
- requireDatabaseInDSN,
7
- withReadOnlyTransaction
8
- } from "./chunk-6SZKW6GD.js";
9
- import "./chunk-FU2ZJE4E.js";
10
- import {
11
- quoteIdentifier
12
- } from "./chunk-X2EC47W4.js";
13
- import {
14
- SQLRowLimiter,
15
- closeQuietly
16
- } from "./chunk-YIGJHEU6.js";
17
- import "./chunk-ABTVMHD6.js";
18
- import {
19
- ConnectorRegistry,
20
- SafeURL,
21
- obfuscateDSNPassword,
22
- splitSQLStatements
23
- } from "./chunk-IXC4NQGC.js";
24
-
25
- // src/connectors/mariadb/index.ts
26
- import * as mariadb from "mariadb";
27
- var MariadbDSNParser = class {
28
- async parse(dsn, config) {
29
- const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
30
- const queryTimeoutSeconds = config?.queryTimeoutSeconds;
31
- if (!this.isValidDSN(dsn)) {
32
- const obfuscatedDSN = obfuscateDSNPassword(dsn);
33
- const expectedFormat = this.getSampleDSN();
34
- throw new Error(
35
- `Invalid MariaDB DSN format.
36
- Provided: ${obfuscatedDSN}
37
- Expected: ${expectedFormat}`
38
- );
39
- }
40
- try {
41
- const url = new SafeURL(dsn);
42
- const database = url.pathname ? url.pathname.substring(1) : "";
43
- requireDatabaseInDSN(database, dsn, "MariaDB");
44
- const connectionConfig = {
45
- host: url.hostname,
46
- port: url.port ? parseInt(url.port) : 3306,
47
- database,
48
- user: url.username,
49
- password: url.password,
50
- multipleStatements: true,
51
- // Enable native multi-statement support
52
- ...connectionTimeoutSeconds !== void 0 && {
53
- connectTimeout: connectionTimeoutSeconds * 1e3
54
- },
55
- ...queryTimeoutSeconds !== void 0 && {
56
- queryTimeout: queryTimeoutSeconds * 1e3
57
- },
58
- // Controls how the driver interprets DATETIME values ("Z", "local", or "±HH:MM").
59
- ...config?.timezone !== void 0 && {
60
- timezone: config.timezone
61
- },
62
- // Connection character set (e.g. "utf8mb4") / collation (e.g.
63
- // "utf8mb4_general_ci"). The mariadb driver exposes both as distinct
64
- // options, but a configured collation is authoritative (it implies its
65
- // character set) and the driver ignores `collation` when `charset` is also
66
- // passed. So forward the collation when present, otherwise the charset
67
- // (which uses that character set's default collation).
68
- ...config?.collation !== void 0 ? { collation: config.collation } : config?.charset !== void 0 ? { charset: config.charset } : {}
69
- };
70
- url.forEachSearchParam((value, key) => {
71
- if (key === "sslmode") {
72
- if (value === "disable") {
73
- connectionConfig.ssl = void 0;
74
- } else if (value === "require") {
75
- connectionConfig.ssl = { rejectUnauthorized: false };
76
- } else {
77
- connectionConfig.ssl = {};
78
- }
79
- }
80
- });
81
- if (url.password && url.password.includes("X-Amz-Credential")) {
82
- if (connectionConfig.ssl === void 0) {
83
- connectionConfig.ssl = { rejectUnauthorized: false };
84
- }
85
- }
86
- return connectionConfig;
87
- } catch (error) {
88
- if (error instanceof MissingDatabaseError) {
89
- throw error;
90
- }
91
- throw new Error(
92
- `Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
93
- );
94
- }
95
- }
96
- getSampleDSN() {
97
- return "mariadb://root:password@localhost:3306/db?sslmode=require";
98
- }
99
- isValidDSN(dsn) {
100
- try {
101
- return dsn.startsWith("mariadb://");
102
- } catch (error) {
103
- return false;
104
- }
105
- }
106
- };
107
- var MariaDBConnector = class _MariaDBConnector {
108
- constructor() {
109
- this.id = "mariadb";
110
- this.name = "MariaDB";
111
- this.dsnParser = new MariadbDSNParser();
112
- this.pool = null;
113
- // Source ID is set by ConnectorManager after cloning
114
- this.sourceId = "default";
115
- // TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
116
- // unless tidb_enable_noop_functions is on. Detected once at connect time.
117
- this.supportsReadOnlyTransaction = true;
118
- }
119
- getId() {
120
- return this.sourceId;
121
- }
122
- clone() {
123
- return new _MariaDBConnector();
124
- }
125
- async connect(dsn, initScript, config) {
126
- try {
127
- const connectionConfig = await this.dsnParser.parse(dsn, config);
128
- this.pool = mariadb.createPool(connectionConfig);
129
- this.pool.on("error", (err) => {
130
- console.error(
131
- `MariaDB pool (source "${this.sourceId}"): background connection error, pool will retry:`,
132
- err.message
133
- );
134
- });
135
- const rows = await this.pool.query("SELECT VERSION() AS version");
136
- this.supportsReadOnlyTransaction = !isTiDBVersion(rows?.[0]?.version);
137
- } catch (err) {
138
- if (this.pool) {
139
- const pool = this.pool;
140
- this.pool = null;
141
- await closeQuietly(() => pool.end());
142
- }
143
- console.error("Failed to connect to MariaDB database:", err);
144
- throw err;
145
- }
146
- }
147
- async disconnect() {
148
- if (this.pool) {
149
- await this.pool.end();
150
- this.pool = null;
151
- }
152
- }
153
- async getSchemas() {
154
- if (!this.pool) {
155
- throw new Error("Not connected to database");
156
- }
157
- try {
158
- const rows = await this.pool.query(`
159
- SELECT SCHEMA_NAME
160
- FROM INFORMATION_SCHEMA.SCHEMATA
161
- WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
162
- ORDER BY SCHEMA_NAME
163
- `);
164
- return rows.map((row) => row.SCHEMA_NAME);
165
- } catch (error) {
166
- console.error("Error getting schemas:", error);
167
- throw error;
168
- }
169
- }
170
- async getTables(schema) {
171
- if (!this.pool) {
172
- throw new Error("Not connected to database");
173
- }
174
- try {
175
- const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
176
- const queryParams = schema ? [schema] : [];
177
- const rows = await this.pool.query(
178
- `
179
- SELECT TABLE_NAME
180
- FROM INFORMATION_SCHEMA.TABLES
181
- ${schemaClause}
182
- AND TABLE_TYPE = 'BASE TABLE'
183
- ORDER BY TABLE_NAME
184
- `,
185
- queryParams
186
- );
187
- return rows.map((row) => row.TABLE_NAME);
188
- } catch (error) {
189
- console.error("Error getting tables:", error);
190
- throw error;
191
- }
192
- }
193
- async getViews(schema) {
194
- if (!this.pool) {
195
- throw new Error("Not connected to database");
196
- }
197
- try {
198
- const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
199
- const queryParams = schema ? [schema] : [];
200
- const rows = await this.pool.query(
201
- `
202
- SELECT TABLE_NAME
203
- FROM INFORMATION_SCHEMA.TABLES
204
- ${schemaClause}
205
- AND TABLE_TYPE = 'VIEW'
206
- ORDER BY TABLE_NAME
207
- `,
208
- queryParams
209
- );
210
- return rows.map((row) => row.TABLE_NAME);
211
- } catch (error) {
212
- console.error("Error getting views:", error);
213
- throw error;
214
- }
215
- }
216
- async tableExists(tableName, schema) {
217
- if (!this.pool) {
218
- throw new Error("Not connected to database");
219
- }
220
- try {
221
- const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
222
- const queryParams = schema ? [schema, tableName] : [tableName];
223
- const rows = await this.pool.query(
224
- `
225
- SELECT COUNT(*) AS COUNT
226
- FROM INFORMATION_SCHEMA.TABLES
227
- ${schemaClause}
228
- AND TABLE_NAME = ?
229
- `,
230
- queryParams
231
- );
232
- return rows[0].COUNT > 0;
233
- } catch (error) {
234
- console.error("Error checking if table exists:", error);
235
- throw error;
236
- }
237
- }
238
- async getTableIndexes(tableName, schema) {
239
- if (!this.pool) {
240
- throw new Error("Not connected to database");
241
- }
242
- try {
243
- const schemaClause = schema ? "TABLE_SCHEMA = ?" : "TABLE_SCHEMA = DATABASE()";
244
- const queryParams = schema ? [schema, tableName] : [tableName];
245
- const indexRows = await this.pool.query(
246
- `
247
- SELECT
248
- INDEX_NAME,
249
- COLUMN_NAME,
250
- NON_UNIQUE,
251
- SEQ_IN_INDEX
252
- FROM
253
- INFORMATION_SCHEMA.STATISTICS
254
- WHERE
255
- ${schemaClause}
256
- AND TABLE_NAME = ?
257
- ORDER BY
258
- INDEX_NAME,
259
- SEQ_IN_INDEX
260
- `,
261
- queryParams
262
- );
263
- const indexMap = /* @__PURE__ */ new Map();
264
- for (const row of indexRows) {
265
- const indexName = row.INDEX_NAME;
266
- const columnName = row.COLUMN_NAME;
267
- const isUnique = row.NON_UNIQUE === 0;
268
- const isPrimary = indexName === "PRIMARY";
269
- if (!indexMap.has(indexName)) {
270
- indexMap.set(indexName, {
271
- columns: [],
272
- is_unique: isUnique,
273
- is_primary: isPrimary
274
- });
275
- }
276
- const indexInfo = indexMap.get(indexName);
277
- indexInfo.columns.push(columnName);
278
- }
279
- const results = [];
280
- indexMap.forEach((indexInfo, indexName) => {
281
- results.push({
282
- index_name: indexName,
283
- column_names: indexInfo.columns,
284
- is_unique: indexInfo.is_unique,
285
- is_primary: indexInfo.is_primary
286
- });
287
- });
288
- return results;
289
- } catch (error) {
290
- console.error("Error getting table indexes:", error);
291
- throw error;
292
- }
293
- }
294
- async getTableSchema(tableName, schema) {
295
- if (!this.pool) {
296
- throw new Error("Not connected to database");
297
- }
298
- try {
299
- const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
300
- const queryParams = schema ? [schema, tableName] : [tableName];
301
- const rows = await this.pool.query(
302
- `
303
- SELECT
304
- COLUMN_NAME as column_name,
305
- DATA_TYPE as data_type,
306
- IS_NULLABLE as is_nullable,
307
- COLUMN_DEFAULT as column_default,
308
- COLUMN_COMMENT as description
309
- FROM INFORMATION_SCHEMA.COLUMNS
310
- ${schemaClause}
311
- AND TABLE_NAME = ?
312
- ORDER BY ORDINAL_POSITION
313
- `,
314
- queryParams
315
- );
316
- return rows.map((row) => ({
317
- ...row,
318
- description: row.description || null
319
- }));
320
- } catch (error) {
321
- console.error("Error getting table schema:", error);
322
- throw error;
323
- }
324
- }
325
- async getTableComment(tableName, schema) {
326
- if (!this.pool) {
327
- throw new Error("Not connected to database");
328
- }
329
- try {
330
- const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
331
- const queryParams = schema ? [schema, tableName] : [tableName];
332
- const rows = await this.pool.query(
333
- `
334
- SELECT TABLE_COMMENT
335
- FROM INFORMATION_SCHEMA.TABLES
336
- ${schemaClause}
337
- AND TABLE_NAME = ?
338
- `,
339
- queryParams
340
- );
341
- if (rows.length > 0) {
342
- return rows[0].TABLE_COMMENT || null;
343
- }
344
- return null;
345
- } catch (error) {
346
- return null;
347
- }
348
- }
349
- async getHealthCheck() {
350
- if (!this.pool) {
351
- throw new Error("Not connected to database");
352
- }
353
- const pool = this.pool;
354
- return getMySQLFamilyHealthCheck((sql) => pool.query(sql));
355
- }
356
- async getStoredProcedures(schema, routineType) {
357
- if (!this.pool) {
358
- throw new Error("Not connected to database");
359
- }
360
- try {
361
- const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
362
- const queryParams = schema ? [schema] : [];
363
- let typeFilter = "";
364
- if (routineType === "function") {
365
- typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
366
- } else if (routineType === "procedure") {
367
- typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
368
- }
369
- const rows = await this.pool.query(
370
- `
371
- SELECT ROUTINE_NAME
372
- FROM INFORMATION_SCHEMA.ROUTINES
373
- ${schemaClause}${typeFilter}
374
- ORDER BY ROUTINE_NAME
375
- `,
376
- queryParams
377
- );
378
- return rows.map((row) => row.ROUTINE_NAME);
379
- } catch (error) {
380
- console.error("Error getting stored procedures:", error);
381
- throw error;
382
- }
383
- }
384
- async getStoredProcedureDetail(procedureName, schema) {
385
- if (!this.pool) {
386
- throw new Error("Not connected to database");
387
- }
388
- try {
389
- const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
390
- const queryParams = schema ? [schema, procedureName] : [procedureName];
391
- const rows = await this.pool.query(
392
- `
393
- SELECT
394
- r.ROUTINE_NAME AS procedure_name,
395
- CASE
396
- WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
397
- ELSE 'function'
398
- END AS procedure_type,
399
- LOWER(r.ROUTINE_TYPE) AS routine_type,
400
- r.ROUTINE_DEFINITION,
401
- r.DTD_IDENTIFIER AS return_type,
402
- (
403
- SELECT GROUP_CONCAT(
404
- CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
405
- ORDER BY p.ORDINAL_POSITION
406
- SEPARATOR ', '
407
- )
408
- FROM INFORMATION_SCHEMA.PARAMETERS p
409
- WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
410
- AND p.SPECIFIC_NAME = r.ROUTINE_NAME
411
- AND p.PARAMETER_NAME IS NOT NULL
412
- ) AS parameter_list
413
- FROM INFORMATION_SCHEMA.ROUTINES r
414
- ${schemaClause}
415
- AND r.ROUTINE_NAME = ?
416
- `,
417
- queryParams
418
- );
419
- if (rows.length === 0) {
420
- const schemaName = schema || "current schema";
421
- throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
422
- }
423
- const procedure = rows[0];
424
- let definition = procedure.ROUTINE_DEFINITION;
425
- try {
426
- const schemaValue = schema || await this.getCurrentSchema();
427
- const quotedSchema = quoteIdentifier(schemaValue, "mariadb");
428
- const quotedProcName = quoteIdentifier(procedureName, "mariadb");
429
- if (procedure.procedure_type === "procedure") {
430
- try {
431
- const defRows = await this.pool.query(`
432
- SHOW CREATE PROCEDURE ${quotedSchema}.${quotedProcName}
433
- `);
434
- if (defRows && defRows.length > 0) {
435
- definition = defRows[0]["Create Procedure"];
436
- }
437
- } catch (err) {
438
- console.error(`Error getting procedure definition with SHOW CREATE: ${err}`);
439
- }
440
- } else {
441
- try {
442
- const defRows = await this.pool.query(`
443
- SHOW CREATE FUNCTION ${quotedSchema}.${quotedProcName}
444
- `);
445
- if (defRows && defRows.length > 0) {
446
- definition = defRows[0]["Create Function"];
447
- }
448
- } catch (innerErr) {
449
- console.error(`Error getting function definition with SHOW CREATE: ${innerErr}`);
450
- }
451
- }
452
- if (!definition) {
453
- const bodyRows = await this.pool.query(
454
- `
455
- SELECT ROUTINE_DEFINITION, ROUTINE_BODY
456
- FROM INFORMATION_SCHEMA.ROUTINES
457
- WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
458
- `,
459
- [schemaValue, procedureName]
460
- );
461
- if (bodyRows && bodyRows.length > 0) {
462
- if (bodyRows[0].ROUTINE_DEFINITION) {
463
- definition = bodyRows[0].ROUTINE_DEFINITION;
464
- } else if (bodyRows[0].ROUTINE_BODY) {
465
- definition = bodyRows[0].ROUTINE_BODY;
466
- }
467
- }
468
- }
469
- } catch (error) {
470
- console.error(`Error getting procedure/function details: ${error}`);
471
- }
472
- return {
473
- procedure_name: procedure.procedure_name,
474
- procedure_type: procedure.procedure_type,
475
- language: "sql",
476
- // MariaDB procedures are generally in SQL
477
- parameter_list: procedure.parameter_list || "",
478
- return_type: procedure.routine_type === "function" ? procedure.return_type : void 0,
479
- definition: definition || void 0
480
- };
481
- } catch (error) {
482
- console.error("Error getting stored procedure detail:", error);
483
- throw error;
484
- }
485
- }
486
- // Helper method to get current schema (database) name
487
- async getCurrentSchema() {
488
- const rows = await this.pool.query("SELECT DATABASE() AS DB");
489
- return rows[0].DB;
490
- }
491
- /**
492
- * Default search scope = the database named in the DSN. DATABASE() returns
493
- * null when the connection was opened without a database, in which case
494
- * callers fall back to the full server-wide schema list.
495
- */
496
- async getDefaultSchema() {
497
- if (!this.pool) {
498
- throw new Error("Not connected to database");
499
- }
500
- const rows = await this.pool.query("SELECT DATABASE() AS DB");
501
- return rows[0]?.DB ?? null;
502
- }
503
- async executeSQL(sql, options, parameters) {
504
- if (!this.pool) {
505
- throw new Error("Not connected to database");
506
- }
507
- const conn = await this.pool.getConnection();
508
- try {
509
- return await withReadOnlyTransaction(
510
- conn,
511
- options.readonly,
512
- this.supportsReadOnlyTransaction,
513
- async () => {
514
- const statements = splitSQLStatements(sql, "mariadb");
515
- let processedSQL = sql;
516
- let probes = [];
517
- if (options.maxRows) {
518
- const rewrites = statements.map(
519
- (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows, "mariadb")
520
- );
521
- probes = rewrites.map((rewrite) => rewrite.probeApplied);
522
- processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
523
- if (sql.trim().endsWith(";")) {
524
- processedSQL += ";";
525
- }
526
- }
527
- let results;
528
- if (parameters && parameters.length > 0) {
529
- results = await conn.query(processedSQL, parameters);
530
- } else {
531
- results = await conn.query(processedSQL);
532
- }
533
- const resultSets = parseQueryResultSets(results, statements);
534
- if (resultSets.length === probes.length) {
535
- resultSets.forEach(
536
- (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
537
- );
538
- }
539
- return { resultSets };
540
- }
541
- );
542
- } finally {
543
- conn.release();
544
- }
545
- }
546
- };
547
- var mariadbConnector = new MariaDBConnector();
548
- ConnectorRegistry.register(mariadbConnector);
549
- export {
550
- MariaDBConnector
551
- };