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