@bytebase/dbhub 1.1.0 → 1.2.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,12 +1,12 @@
1
1
  import {
2
2
  MissingDatabaseError,
3
- extractAffectedRows,
4
3
  getMySQLFamilyHealthCheck,
4
+ isClientSideTimeout,
5
5
  isTiDBVersion,
6
- parseQueryResults,
6
+ parseQueryResultSets,
7
7
  requireDatabaseInDSN,
8
8
  withReadOnlyTransaction
9
- } from "./chunk-NIILHXTG.js";
9
+ } from "./chunk-QEFMUQJ2.js";
10
10
  import "./chunk-FU2ZJE4E.js";
11
11
  import {
12
12
  quoteIdentifier
@@ -14,13 +14,13 @@ import {
14
14
  import {
15
15
  SQLRowLimiter,
16
16
  closeQuietly
17
- } from "./chunk-S2V6RZ3C.js";
17
+ } from "./chunk-RSB7DPO2.js";
18
18
  import {
19
19
  ConnectorRegistry,
20
20
  SafeURL,
21
21
  obfuscateDSNPassword,
22
22
  splitSQLStatements
23
- } from "./chunk-JEZZN2YZ.js";
23
+ } from "./chunk-WIQ4DWGY.js";
24
24
 
25
25
  // src/connectors/mysql/index.ts
26
26
  import mysql from "mysql2/promise";
@@ -106,7 +106,7 @@ Expected: ${expectedFormat}`
106
106
  }
107
107
  }
108
108
  };
109
- var MySQLConnector = class _MySQLConnector {
109
+ var _MySQLConnector = class _MySQLConnector {
110
110
  constructor() {
111
111
  this.id = "mysql";
112
112
  this.name = "MySQL";
@@ -507,15 +507,17 @@ var MySQLConnector = class _MySQLConnector {
507
507
  throw new Error("Not connected to database");
508
508
  }
509
509
  const conn = await this.pool.getConnection();
510
+ const threadId = conn.threadId;
511
+ let isConnectionPoisoned = false;
510
512
  try {
511
513
  return await withReadOnlyTransaction(
512
514
  conn,
513
515
  options.readonly,
514
516
  this.supportsReadOnlyTransaction,
515
517
  async () => {
518
+ const statements = splitSQLStatements(sql, "mysql");
516
519
  let processedSQL = sql;
517
520
  if (options.maxRows) {
518
- const statements = splitSQLStatements(sql, "mysql");
519
521
  const processedStatements = statements.map(
520
522
  (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
521
523
  );
@@ -531,16 +533,59 @@ var MySQLConnector = class _MySQLConnector {
531
533
  results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
532
534
  }
533
535
  const [firstResult] = results;
534
- const rows = parseQueryResults(firstResult);
535
- const rowCount = extractAffectedRows(firstResult);
536
- return { rows, rowCount };
536
+ const resultSets = parseQueryResultSets(firstResult, statements);
537
+ return { resultSets };
537
538
  }
538
539
  );
540
+ } catch (error) {
541
+ if (isClientSideTimeout(error)) {
542
+ isConnectionPoisoned = true;
543
+ await this.killQuery(threadId);
544
+ }
545
+ throw error;
546
+ } finally {
547
+ if (isConnectionPoisoned) {
548
+ conn.destroy();
549
+ } else {
550
+ conn.release();
551
+ }
552
+ }
553
+ }
554
+ /**
555
+ * Best-effort server-side kill for a query abandoned by mysql2's client-side
556
+ * timeout. Uses a separate connection: the original connection's command
557
+ * queue is stuck behind the abandoned statement (see isClientSideTimeout)
558
+ * and cannot itself be used to send KILL QUERY.
559
+ *
560
+ * Bounded by its own short timeout, independent of the user's (possibly
561
+ * long or unset) query_timeout — KILL QUERY is metadata-only and should
562
+ * return almost immediately on a healthy server, so cleanup must not stall
563
+ * indefinitely if it doesn't.
564
+ */
565
+ async killQuery(threadId) {
566
+ if (!this.pool) return;
567
+ let killer;
568
+ let killerPoisoned = false;
569
+ try {
570
+ killer = await this.pool.getConnection();
571
+ await killer.query({ sql: `KILL QUERY ${threadId}`, timeout: _MySQLConnector.KILL_QUERY_TIMEOUT_MS });
572
+ } catch (error) {
573
+ killerPoisoned = isClientSideTimeout(error);
539
574
  } finally {
540
- conn.release();
575
+ if (killer) {
576
+ if (killerPoisoned) {
577
+ killer.destroy();
578
+ } else {
579
+ killer.release();
580
+ }
581
+ }
541
582
  }
542
583
  }
543
584
  };
585
+ // Bounds the KILL QUERY cleanup call issued after a client-side query
586
+ // timeout; see killQuery.
587
+ _MySQLConnector.KILL_QUERY_TIMEOUT_MS = 5e3;
588
+ var MySQLConnector = _MySQLConnector;
544
589
  var mysqlConnector = new MySQLConnector();
545
590
  ConnectorRegistry.register(mysqlConnector);
546
591
  export {
@@ -7,13 +7,13 @@ import {
7
7
  import {
8
8
  SQLRowLimiter,
9
9
  closeQuietly
10
- } from "./chunk-S2V6RZ3C.js";
10
+ } from "./chunk-RSB7DPO2.js";
11
11
  import {
12
12
  ConnectorRegistry,
13
13
  SafeURL,
14
14
  obfuscateDSNPassword,
15
15
  splitSQLStatements
16
- } from "./chunk-JEZZN2YZ.js";
16
+ } from "./chunk-WIQ4DWGY.js";
17
17
 
18
18
  // src/connectors/postgres/index.ts
19
19
  import fs from "fs";
@@ -563,7 +563,11 @@ var PostgresConnector = class _PostgresConnector {
563
563
  try {
564
564
  const result2 = parameters && parameters.length > 0 ? await client.query(processedStatement, parameters) : await client.query(processedStatement);
565
565
  await client.query("COMMIT");
566
- return { rows: result2.rows, rowCount: result2.rowCount ?? result2.rows.length };
566
+ return {
567
+ resultSets: [
568
+ { sql: statements[0], rows: result2.rows, rowCount: result2.rowCount ?? result2.rows.length }
569
+ ]
570
+ };
567
571
  } catch (error) {
568
572
  try {
569
573
  await client.query("ROLLBACK");
@@ -578,24 +582,26 @@ var PostgresConnector = class _PostgresConnector {
578
582
  } else {
579
583
  result = await client.query(processedStatement);
580
584
  }
581
- return { rows: result.rows, rowCount: result.rowCount ?? result.rows.length };
585
+ return {
586
+ resultSets: [
587
+ { sql: statements[0], rows: result.rows, rowCount: result.rowCount ?? result.rows.length }
588
+ ]
589
+ };
582
590
  } else {
583
591
  if (parameters && parameters.length > 0) {
584
592
  throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
585
593
  }
586
- let allRows = [];
587
- let totalRowCount = 0;
594
+ const resultSets = [];
588
595
  await client.query(options.readonly ? "BEGIN READ ONLY" : "BEGIN");
589
596
  try {
590
597
  for (let statement of statements) {
591
598
  const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
592
599
  const result = await client.query(processedStatement);
593
- if (result.rows && result.rows.length > 0) {
594
- allRows.push(...result.rows);
595
- }
596
- if (result.rowCount) {
597
- totalRowCount += result.rowCount;
598
- }
600
+ resultSets.push({
601
+ sql: statement,
602
+ rows: result.rows ?? [],
603
+ rowCount: result.rowCount ?? result.rows?.length ?? 0
604
+ });
599
605
  }
600
606
  await client.query("COMMIT");
601
607
  } catch (error) {
@@ -605,7 +611,7 @@ var PostgresConnector = class _PostgresConnector {
605
611
  }
606
612
  throw error;
607
613
  }
608
- return { rows: allRows, rowCount: totalRowCount };
614
+ return { resultSets };
609
615
  }
610
616
  } finally {
611
617
  client.release();