@zz1996/dbhub-dameng 0.1.7 → 1.2.5-dameng.621

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 (32) hide show
  1. package/README.md +126 -137
  2. package/dbhub.dameng.toml.example +4 -11
  3. package/dist/{chunk-SQA2ISDE.js → chunk-2JL4KCP5.js} +21 -119
  4. package/dist/chunk-3RJRH3CN.js +121 -0
  5. package/dist/chunk-4276LPPU.js +179 -0
  6. package/dist/chunk-FU2ZJE4E.js +16 -0
  7. package/dist/chunk-HDPZAIDL.js +155 -0
  8. package/dist/{chunk-2A2QF3CS.js → chunk-HGU7ILEZ.js} +215 -68
  9. package/dist/{chunk-IPK7BYBL.js → chunk-JFWX35TB.js} +0 -1
  10. package/dist/chunk-O3PEWKBB.js +302 -0
  11. package/dist/dameng-NKF7NZKE.js +250 -0
  12. package/dist/index.js +461 -251
  13. package/dist/{mariadb-7F72IRB4.js → mariadb-MMC6WMEG.js} +86 -53
  14. package/dist/{mysql-A43SL7UM.js → mysql-TESH7KKV.js} +126 -50
  15. package/dist/{postgres-VFNLBWMF.js → postgres-C24SBQ4I.js} +154 -36
  16. package/dist/public/assets/index-BJ-1UrcV.css +1 -0
  17. package/dist/public/assets/index-BdBaFSHd.js +148 -0
  18. package/dist/public/assets/postgres-BpcazhJg.svg +22 -0
  19. package/dist/public/assets/sqlserver-ByfFYYpV.svg +11 -0
  20. package/dist/public/favicon.svg +57 -0
  21. package/dist/public/index.html +14 -0
  22. package/dist/public/logo-full-light.svg +58 -0
  23. package/dist/{registry-XSX2VZTD.js → registry-WVSFVS35.js} +3 -2
  24. package/dist/{sqlite-IOUAYHGE.js → sqlite-7BNGDMBZ.js} +57 -42
  25. package/dist/sqlserver-KZPPK67Q.js +1042 -0
  26. package/package.json +22 -65
  27. package/dist/chunk-RTB262PR.js +0 -60
  28. package/dist/chunk-ZNQTMARG.js +0 -127
  29. package/dist/dameng-5KYI5JBK.js +0 -898
  30. package/dist/sqlserver-OE4SFH4B.js +0 -587
  31. package/docs/README.md +0 -14
  32. package/docs/dameng-connector-plan.md +0 -143
@@ -1,19 +1,28 @@
1
1
  import {
2
- extractAffectedRows,
3
- parseQueryResults
4
- } from "./chunk-RTB262PR.js";
2
+ MissingDatabaseError,
3
+ getMySQLFamilyHealthCheck,
4
+ isTiDBVersion,
5
+ parseQueryResultSets,
6
+ requireDatabaseInDSN,
7
+ withReadOnlyTransaction
8
+ } from "./chunk-4276LPPU.js";
9
+ import "./chunk-FU2ZJE4E.js";
5
10
  import {
6
11
  quoteIdentifier
7
- } from "./chunk-IPK7BYBL.js";
12
+ } from "./chunk-JFWX35TB.js";
8
13
  import {
9
- SQLRowLimiter
10
- } from "./chunk-ZNQTMARG.js";
14
+ obfuscateDSNPassword
15
+ } from "./chunk-3RJRH3CN.js";
16
+ import {
17
+ SQLRowLimiter,
18
+ closeQuietly
19
+ } from "./chunk-O3PEWKBB.js";
20
+ import "./chunk-HDPZAIDL.js";
11
21
  import {
12
22
  ConnectorRegistry,
13
23
  SafeURL,
14
- obfuscateDSNPassword,
15
24
  splitSQLStatements
16
- } from "./chunk-SQA2ISDE.js";
25
+ } from "./chunk-2JL4KCP5.js";
17
26
 
18
27
  // src/connectors/mariadb/index.ts
19
28
  import * as mariadb from "mariadb";
@@ -32,11 +41,12 @@ Expected: ${expectedFormat}`
32
41
  }
33
42
  try {
34
43
  const url = new SafeURL(dsn);
44
+ const database = url.pathname ? url.pathname.substring(1) : "";
45
+ requireDatabaseInDSN(database, dsn, "MariaDB");
35
46
  const connectionConfig = {
36
47
  host: url.hostname,
37
48
  port: url.port ? parseInt(url.port) : 3306,
38
- database: url.pathname ? url.pathname.substring(1) : "",
39
- // Remove leading '/' if exists
49
+ database,
40
50
  user: url.username,
41
51
  password: url.password,
42
52
  multipleStatements: true,
@@ -50,7 +60,14 @@ Expected: ${expectedFormat}`
50
60
  // Controls how the driver interprets DATETIME values ("Z", "local", or "±HH:MM").
51
61
  ...config?.timezone !== void 0 && {
52
62
  timezone: config.timezone
53
- }
63
+ },
64
+ // Connection character set (e.g. "utf8mb4") / collation (e.g.
65
+ // "utf8mb4_general_ci"). The mariadb driver exposes both as distinct
66
+ // options, but a configured collation is authoritative (it implies its
67
+ // character set) and the driver ignores `collation` when `charset` is also
68
+ // passed. So forward the collation when present, otherwise the charset
69
+ // (which uses that character set's default collation).
70
+ ...config?.collation !== void 0 ? { collation: config.collation } : config?.charset !== void 0 ? { charset: config.charset } : {}
54
71
  };
55
72
  url.forEachSearchParam((value, key) => {
56
73
  if (key === "sslmode") {
@@ -70,6 +87,9 @@ Expected: ${expectedFormat}`
70
87
  }
71
88
  return connectionConfig;
72
89
  } catch (error) {
90
+ if (error instanceof MissingDatabaseError) {
91
+ throw error;
92
+ }
73
93
  throw new Error(
74
94
  `Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
75
95
  );
@@ -94,6 +114,9 @@ var MariaDBConnector = class _MariaDBConnector {
94
114
  this.pool = null;
95
115
  // Source ID is set by ConnectorManager after cloning
96
116
  this.sourceId = "default";
117
+ // TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
118
+ // unless tidb_enable_noop_functions is on. Detected once at connect time.
119
+ this.supportsReadOnlyTransaction = true;
97
120
  }
98
121
  getId() {
99
122
  return this.sourceId;
@@ -105,8 +128,20 @@ var MariaDBConnector = class _MariaDBConnector {
105
128
  try {
106
129
  const connectionConfig = await this.dsnParser.parse(dsn, config);
107
130
  this.pool = mariadb.createPool(connectionConfig);
108
- await this.pool.query("SELECT 1");
131
+ this.pool.on("error", (err) => {
132
+ console.error(
133
+ `MariaDB pool (source "${this.sourceId}"): background connection error, pool will retry:`,
134
+ err.message
135
+ );
136
+ });
137
+ const rows = await this.pool.query("SELECT VERSION() AS version");
138
+ this.supportsReadOnlyTransaction = !isTiDBVersion(rows?.[0]?.version);
109
139
  } catch (err) {
140
+ if (this.pool) {
141
+ const pool = this.pool;
142
+ this.pool = null;
143
+ await closeQuietly(() => pool.end());
144
+ }
110
145
  console.error("Failed to connect to MariaDB database:", err);
111
146
  throw err;
112
147
  }
@@ -313,6 +348,13 @@ var MariaDBConnector = class _MariaDBConnector {
313
348
  return null;
314
349
  }
315
350
  }
351
+ async getHealthCheck() {
352
+ if (!this.pool) {
353
+ throw new Error("Not connected to database");
354
+ }
355
+ const pool = this.pool;
356
+ return getMySQLFamilyHealthCheck((sql) => pool.query(sql));
357
+ }
316
358
  async getStoredProcedures(schema, routineType) {
317
359
  if (!this.pool) {
318
360
  throw new Error("Not connected to database");
@@ -466,48 +508,39 @@ var MariaDBConnector = class _MariaDBConnector {
466
508
  }
467
509
  const conn = await this.pool.getConnection();
468
510
  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 {
511
+ return await withReadOnlyTransaction(
512
+ conn,
513
+ options.readonly,
514
+ this.supportsReadOnlyTransaction,
515
+ async () => {
516
+ const statements = splitSQLStatements(sql, "mariadb");
517
+ let processedSQL = sql;
518
+ let probes = [];
519
+ if (options.maxRows) {
520
+ const rewrites = statements.map(
521
+ (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows, "mariadb")
522
+ );
523
+ probes = rewrites.map((rewrite) => rewrite.probeApplied);
524
+ processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
525
+ if (sql.trim().endsWith(";")) {
526
+ processedSQL += ";";
527
+ }
528
+ }
529
+ let results;
530
+ if (parameters && parameters.length > 0) {
531
+ results = await conn.query(processedSQL, parameters);
532
+ } else {
533
+ results = await conn.query(processedSQL);
534
+ }
535
+ const resultSets = parseQueryResultSets(results, statements);
536
+ if (resultSets.length === probes.length) {
537
+ resultSets.forEach(
538
+ (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
539
+ );
540
+ }
541
+ return { resultSets };
507
542
  }
508
- }
509
- console.error("Error executing query:", error);
510
- throw error;
543
+ );
511
544
  } finally {
512
545
  conn.release();
513
546
  }
@@ -1,19 +1,29 @@
1
1
  import {
2
- extractAffectedRows,
3
- parseQueryResults
4
- } from "./chunk-RTB262PR.js";
2
+ MissingDatabaseError,
3
+ getMySQLFamilyHealthCheck,
4
+ isClientSideTimeout,
5
+ isTiDBVersion,
6
+ parseQueryResultSets,
7
+ requireDatabaseInDSN,
8
+ withReadOnlyTransaction
9
+ } from "./chunk-4276LPPU.js";
10
+ import "./chunk-FU2ZJE4E.js";
5
11
  import {
6
12
  quoteIdentifier
7
- } from "./chunk-IPK7BYBL.js";
13
+ } from "./chunk-JFWX35TB.js";
8
14
  import {
9
- SQLRowLimiter
10
- } from "./chunk-ZNQTMARG.js";
15
+ obfuscateDSNPassword
16
+ } from "./chunk-3RJRH3CN.js";
17
+ import {
18
+ SQLRowLimiter,
19
+ closeQuietly
20
+ } from "./chunk-O3PEWKBB.js";
21
+ import "./chunk-HDPZAIDL.js";
11
22
  import {
12
23
  ConnectorRegistry,
13
24
  SafeURL,
14
- obfuscateDSNPassword,
15
25
  splitSQLStatements
16
- } from "./chunk-SQA2ISDE.js";
26
+ } from "./chunk-2JL4KCP5.js";
17
27
 
18
28
  // src/connectors/mysql/index.ts
19
29
  import mysql from "mysql2/promise";
@@ -21,6 +31,8 @@ var MySQLDSNParser = class {
21
31
  async parse(dsn, config) {
22
32
  const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
23
33
  const timezone = config?.timezone;
34
+ const charset = config?.charset;
35
+ const collation = config?.collation;
24
36
  if (!this.isValidDSN(dsn)) {
25
37
  const obfuscatedDSN = obfuscateDSNPassword(dsn);
26
38
  const expectedFormat = this.getSampleDSN();
@@ -32,11 +44,12 @@ Expected: ${expectedFormat}`
32
44
  }
33
45
  try {
34
46
  const url = new SafeURL(dsn);
47
+ const database = url.pathname ? url.pathname.substring(1) : "";
48
+ requireDatabaseInDSN(database, dsn, "MySQL");
35
49
  const config2 = {
36
50
  host: url.hostname,
37
51
  port: url.port ? parseInt(url.port) : 3306,
38
- database: url.pathname ? url.pathname.substring(1) : "",
39
- // Remove leading '/' if exists
52
+ database,
40
53
  user: url.username,
41
54
  password: url.password,
42
55
  multipleStatements: true,
@@ -61,6 +74,10 @@ Expected: ${expectedFormat}`
61
74
  if (timezone !== void 0) {
62
75
  config2.timezone = timezone;
63
76
  }
77
+ const charsetOrCollation = collation ?? charset;
78
+ if (charsetOrCollation !== void 0) {
79
+ config2.charset = charsetOrCollation;
80
+ }
64
81
  if (url.password && url.password.includes("X-Amz-Credential")) {
65
82
  config2.authPlugins = {
66
83
  mysql_clear_password: () => () => {
@@ -73,6 +90,9 @@ Expected: ${expectedFormat}`
73
90
  }
74
91
  return config2;
75
92
  } catch (error) {
93
+ if (error instanceof MissingDatabaseError) {
94
+ throw error;
95
+ }
76
96
  throw new Error(
77
97
  `Failed to parse MySQL DSN: ${error instanceof Error ? error.message : String(error)}`
78
98
  );
@@ -89,7 +109,7 @@ Expected: ${expectedFormat}`
89
109
  }
90
110
  }
91
111
  };
92
- var MySQLConnector = class _MySQLConnector {
112
+ var _MySQLConnector = class _MySQLConnector {
93
113
  constructor() {
94
114
  this.id = "mysql";
95
115
  this.name = "MySQL";
@@ -97,6 +117,9 @@ var MySQLConnector = class _MySQLConnector {
97
117
  this.pool = null;
98
118
  // Source ID is set by ConnectorManager after cloning
99
119
  this.sourceId = "default";
120
+ // TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
121
+ // unless tidb_enable_noop_functions is on. Detected once at connect time.
122
+ this.supportsReadOnlyTransaction = true;
100
123
  }
101
124
  getId() {
102
125
  return this.sourceId;
@@ -111,8 +134,14 @@ var MySQLConnector = class _MySQLConnector {
111
134
  if (config?.queryTimeoutSeconds !== void 0) {
112
135
  this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
113
136
  }
114
- const [rows] = await this.pool.query("SELECT 1");
137
+ const [rows] = await this.pool.query("SELECT VERSION() AS version");
138
+ this.supportsReadOnlyTransaction = !isTiDBVersion(rows[0]?.version);
115
139
  } catch (err) {
140
+ if (this.pool) {
141
+ const pool = this.pool;
142
+ this.pool = null;
143
+ await closeQuietly(() => pool.end());
144
+ }
116
145
  console.error("Failed to connect to MySQL database:", err);
117
146
  throw err;
118
147
  }
@@ -319,6 +348,16 @@ var MySQLConnector = class _MySQLConnector {
319
348
  return null;
320
349
  }
321
350
  }
351
+ async getHealthCheck() {
352
+ if (!this.pool) {
353
+ throw new Error("Not connected to database");
354
+ }
355
+ const pool = this.pool;
356
+ return getMySQLFamilyHealthCheck(async (sql) => {
357
+ const [rows] = await pool.query(sql);
358
+ return rows;
359
+ });
360
+ }
322
361
  async getStoredProcedures(schema, routineType) {
323
362
  if (!this.pool) {
324
363
  throw new Error("Not connected to database");
@@ -471,55 +510,92 @@ var MySQLConnector = class _MySQLConnector {
471
510
  throw new Error("Not connected to database");
472
511
  }
473
512
  const conn = await this.pool.getConnection();
513
+ const threadId = conn.threadId;
514
+ let isConnectionPoisoned = false;
474
515
  try {
475
- if (options.readonly) {
476
- await conn.query("START TRANSACTION READ ONLY");
477
- }
478
- let processedSQL = sql;
479
- if (options.maxRows) {
480
- const statements = splitSQLStatements(sql, "mysql");
481
- const processedStatements = statements.map(
482
- (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
483
- );
484
- processedSQL = processedStatements.join("; ");
485
- if (sql.trim().endsWith(";")) {
486
- processedSQL += ";";
516
+ return await withReadOnlyTransaction(
517
+ conn,
518
+ options.readonly,
519
+ this.supportsReadOnlyTransaction,
520
+ async () => {
521
+ const statements = splitSQLStatements(sql, "mysql");
522
+ let processedSQL = sql;
523
+ let probes = [];
524
+ if (options.maxRows) {
525
+ const rewrites = statements.map(
526
+ (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows, "mysql")
527
+ );
528
+ probes = rewrites.map((rewrite) => rewrite.probeApplied);
529
+ processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
530
+ if (sql.trim().endsWith(";")) {
531
+ processedSQL += ";";
532
+ }
533
+ }
534
+ let results;
535
+ if (parameters && parameters.length > 0) {
536
+ results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs }, parameters);
537
+ } else {
538
+ results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
539
+ }
540
+ const [firstResult] = results;
541
+ const resultSets = parseQueryResultSets(firstResult, statements);
542
+ if (resultSets.length === probes.length) {
543
+ resultSets.forEach(
544
+ (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
545
+ );
546
+ }
547
+ return { resultSets };
487
548
  }
549
+ );
550
+ } catch (error) {
551
+ if (isClientSideTimeout(error)) {
552
+ isConnectionPoisoned = true;
553
+ await this.killQuery(threadId);
488
554
  }
489
- let results;
490
- if (parameters && parameters.length > 0) {
491
- try {
492
- results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs }, parameters);
493
- } catch (error) {
494
- console.error(`[MySQL executeSQL] ERROR: ${error.message}`);
495
- console.error(`[MySQL executeSQL] SQL: ${processedSQL}`);
496
- console.error(`[MySQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
497
- throw error;
498
- }
555
+ throw error;
556
+ } finally {
557
+ if (isConnectionPoisoned) {
558
+ conn.destroy();
499
559
  } else {
500
- results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
560
+ conn.release();
501
561
  }
502
- const [firstResult] = results;
503
- const rows = parseQueryResults(firstResult);
504
- const rowCount = extractAffectedRows(firstResult);
505
- if (options.readonly) {
506
- await conn.query("COMMIT");
507
- }
508
- return { rows, rowCount };
562
+ }
563
+ }
564
+ /**
565
+ * Best-effort server-side kill for a query abandoned by mysql2's client-side
566
+ * timeout. Uses a separate connection: the original connection's command
567
+ * queue is stuck behind the abandoned statement (see isClientSideTimeout)
568
+ * and cannot itself be used to send KILL QUERY.
569
+ *
570
+ * Bounded by its own short timeout, independent of the user's (possibly
571
+ * long or unset) query_timeout — KILL QUERY is metadata-only and should
572
+ * return almost immediately on a healthy server, so cleanup must not stall
573
+ * indefinitely if it doesn't.
574
+ */
575
+ async killQuery(threadId) {
576
+ if (!this.pool) return;
577
+ let killer;
578
+ let killerPoisoned = false;
579
+ try {
580
+ killer = await this.pool.getConnection();
581
+ await killer.query({ sql: `KILL QUERY ${threadId}`, timeout: _MySQLConnector.KILL_QUERY_TIMEOUT_MS });
509
582
  } catch (error) {
510
- if (options.readonly) {
511
- try {
512
- await conn.query("ROLLBACK");
513
- } catch {
583
+ killerPoisoned = isClientSideTimeout(error);
584
+ } finally {
585
+ if (killer) {
586
+ if (killerPoisoned) {
587
+ killer.destroy();
588
+ } else {
589
+ killer.release();
514
590
  }
515
591
  }
516
- console.error("Error executing query:", error);
517
- throw error;
518
- } finally {
519
- conn.release();
520
592
  }
521
593
  }
522
594
  };
595
+ // Bounds the KILL QUERY cleanup call issued after a client-side query
596
+ // timeout; see killQuery.
597
+ _MySQLConnector.KILL_QUERY_TIMEOUT_MS = 5e3;
598
+ var MySQLConnector = _MySQLConnector;
523
599
  var mysqlConnector = new MySQLConnector();
524
600
  ConnectorRegistry.register(mysqlConnector);
525
601
  export {