@bytebase/dbhub 1.0.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,24 +1,25 @@
1
1
  import {
2
2
  MissingDatabaseError,
3
- extractAffectedRows,
3
+ getMySQLFamilyHealthCheck,
4
4
  isTiDBVersion,
5
- parseQueryResults,
5
+ parseQueryResultSets,
6
6
  requireDatabaseInDSN,
7
7
  withReadOnlyTransaction
8
- } from "./chunk-DUCNPMNW.js";
8
+ } from "./chunk-QEFMUQJ2.js";
9
+ import "./chunk-FU2ZJE4E.js";
9
10
  import {
10
11
  quoteIdentifier
11
12
  } from "./chunk-JFWX35TB.js";
12
13
  import {
13
14
  SQLRowLimiter,
14
15
  closeQuietly
15
- } from "./chunk-S2V6RZ3C.js";
16
+ } from "./chunk-RSB7DPO2.js";
16
17
  import {
17
18
  ConnectorRegistry,
18
19
  SafeURL,
19
20
  obfuscateDSNPassword,
20
21
  splitSQLStatements
21
- } from "./chunk-JEZZN2YZ.js";
22
+ } from "./chunk-WIQ4DWGY.js";
22
23
 
23
24
  // src/connectors/mariadb/index.ts
24
25
  import * as mariadb from "mariadb";
@@ -338,6 +339,13 @@ var MariaDBConnector = class _MariaDBConnector {
338
339
  return null;
339
340
  }
340
341
  }
342
+ async getHealthCheck() {
343
+ if (!this.pool) {
344
+ throw new Error("Not connected to database");
345
+ }
346
+ const pool = this.pool;
347
+ return getMySQLFamilyHealthCheck((sql) => pool.query(sql));
348
+ }
341
349
  async getStoredProcedures(schema, routineType) {
342
350
  if (!this.pool) {
343
351
  throw new Error("Not connected to database");
@@ -496,9 +504,9 @@ var MariaDBConnector = class _MariaDBConnector {
496
504
  options.readonly,
497
505
  this.supportsReadOnlyTransaction,
498
506
  async () => {
507
+ const statements = splitSQLStatements(sql, "mariadb");
499
508
  let processedSQL = sql;
500
509
  if (options.maxRows) {
501
- const statements = splitSQLStatements(sql, "mariadb");
502
510
  const processedStatements = statements.map(
503
511
  (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
504
512
  );
@@ -513,9 +521,8 @@ var MariaDBConnector = class _MariaDBConnector {
513
521
  } else {
514
522
  results = await conn.query(processedSQL);
515
523
  }
516
- const rows = parseQueryResults(results);
517
- const rowCount = extractAffectedRows(results);
518
- return { rows, rowCount };
524
+ const resultSets = parseQueryResultSets(results, statements);
525
+ return { resultSets };
519
526
  }
520
527
  );
521
528
  } finally {
@@ -1,24 +1,26 @@
1
1
  import {
2
2
  MissingDatabaseError,
3
- extractAffectedRows,
3
+ getMySQLFamilyHealthCheck,
4
+ isClientSideTimeout,
4
5
  isTiDBVersion,
5
- parseQueryResults,
6
+ parseQueryResultSets,
6
7
  requireDatabaseInDSN,
7
8
  withReadOnlyTransaction
8
- } from "./chunk-DUCNPMNW.js";
9
+ } from "./chunk-QEFMUQJ2.js";
10
+ import "./chunk-FU2ZJE4E.js";
9
11
  import {
10
12
  quoteIdentifier
11
13
  } from "./chunk-JFWX35TB.js";
12
14
  import {
13
15
  SQLRowLimiter,
14
16
  closeQuietly
15
- } from "./chunk-S2V6RZ3C.js";
17
+ } from "./chunk-RSB7DPO2.js";
16
18
  import {
17
19
  ConnectorRegistry,
18
20
  SafeURL,
19
21
  obfuscateDSNPassword,
20
22
  splitSQLStatements
21
- } from "./chunk-JEZZN2YZ.js";
23
+ } from "./chunk-WIQ4DWGY.js";
22
24
 
23
25
  // src/connectors/mysql/index.ts
24
26
  import mysql from "mysql2/promise";
@@ -104,7 +106,7 @@ Expected: ${expectedFormat}`
104
106
  }
105
107
  }
106
108
  };
107
- var MySQLConnector = class _MySQLConnector {
109
+ var _MySQLConnector = class _MySQLConnector {
108
110
  constructor() {
109
111
  this.id = "mysql";
110
112
  this.name = "MySQL";
@@ -343,6 +345,16 @@ var MySQLConnector = class _MySQLConnector {
343
345
  return null;
344
346
  }
345
347
  }
348
+ async getHealthCheck() {
349
+ if (!this.pool) {
350
+ throw new Error("Not connected to database");
351
+ }
352
+ const pool = this.pool;
353
+ return getMySQLFamilyHealthCheck(async (sql) => {
354
+ const [rows] = await pool.query(sql);
355
+ return rows;
356
+ });
357
+ }
346
358
  async getStoredProcedures(schema, routineType) {
347
359
  if (!this.pool) {
348
360
  throw new Error("Not connected to database");
@@ -495,15 +507,17 @@ var MySQLConnector = class _MySQLConnector {
495
507
  throw new Error("Not connected to database");
496
508
  }
497
509
  const conn = await this.pool.getConnection();
510
+ const threadId = conn.threadId;
511
+ let isConnectionPoisoned = false;
498
512
  try {
499
513
  return await withReadOnlyTransaction(
500
514
  conn,
501
515
  options.readonly,
502
516
  this.supportsReadOnlyTransaction,
503
517
  async () => {
518
+ const statements = splitSQLStatements(sql, "mysql");
504
519
  let processedSQL = sql;
505
520
  if (options.maxRows) {
506
- const statements = splitSQLStatements(sql, "mysql");
507
521
  const processedStatements = statements.map(
508
522
  (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
509
523
  );
@@ -519,16 +533,59 @@ var MySQLConnector = class _MySQLConnector {
519
533
  results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
520
534
  }
521
535
  const [firstResult] = results;
522
- const rows = parseQueryResults(firstResult);
523
- const rowCount = extractAffectedRows(firstResult);
524
- return { rows, rowCount };
536
+ const resultSets = parseQueryResultSets(firstResult, statements);
537
+ return { resultSets };
525
538
  }
526
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);
527
574
  } finally {
528
- conn.release();
575
+ if (killer) {
576
+ if (killerPoisoned) {
577
+ killer.destroy();
578
+ } else {
579
+ killer.release();
580
+ }
581
+ }
529
582
  }
530
583
  }
531
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;
532
589
  var mysqlConnector = new MySQLConnector();
533
590
  ConnectorRegistry.register(mysqlConnector);
534
591
  export {
@@ -1,16 +1,19 @@
1
+ import {
2
+ toNullableNumber
3
+ } from "./chunk-FU2ZJE4E.js";
1
4
  import {
2
5
  quoteIdentifier
3
6
  } from "./chunk-JFWX35TB.js";
4
7
  import {
5
8
  SQLRowLimiter,
6
9
  closeQuietly
7
- } from "./chunk-S2V6RZ3C.js";
10
+ } from "./chunk-RSB7DPO2.js";
8
11
  import {
9
12
  ConnectorRegistry,
10
13
  SafeURL,
11
14
  obfuscateDSNPassword,
12
15
  splitSQLStatements
13
- } from "./chunk-JEZZN2YZ.js";
16
+ } from "./chunk-WIQ4DWGY.js";
14
17
 
15
18
  // src/connectors/postgres/index.ts
16
19
  import fs from "fs";
@@ -383,6 +386,64 @@ var PostgresConnector = class _PostgresConnector {
383
386
  client.release();
384
387
  }
385
388
  }
389
+ async getHealthCheck() {
390
+ if (!this.pool) {
391
+ throw new Error("Not connected to database");
392
+ }
393
+ const client = await this.pool.connect();
394
+ try {
395
+ const [connectionsResult, maxConnectionsResult, bufferCacheResult] = await Promise.all([
396
+ client.query(`
397
+ SELECT
398
+ count(*) AS total,
399
+ count(*) FILTER (WHERE state = 'active') AS active,
400
+ count(*) FILTER (WHERE state = 'idle') AS idle,
401
+ count(*) FILTER (WHERE state = 'idle in transaction') AS idle_in_transaction,
402
+ count(*) FILTER (WHERE state = 'idle in transaction (aborted)') AS idle_in_transaction_aborted,
403
+ max(EXTRACT(EPOCH FROM (now() - state_change)))
404
+ FILTER (WHERE state = 'idle in transaction') AS longest_idle_in_transaction_seconds,
405
+ max(EXTRACT(EPOCH FROM (now() - query_start)))
406
+ FILTER (WHERE state = 'active') AS longest_active_query_seconds
407
+ FROM pg_stat_activity
408
+ WHERE pid <> pg_backend_pid()
409
+ `),
410
+ client.query(`SHOW max_connections`),
411
+ client.query(
412
+ `
413
+ SELECT
414
+ blks_hit,
415
+ blks_read,
416
+ CASE WHEN (blks_hit + blks_read) = 0 THEN NULL
417
+ ELSE round(100.0 * blks_hit / (blks_hit + blks_read), 2)
418
+ END AS hit_ratio_pct
419
+ FROM pg_stat_database
420
+ WHERE datname = current_database()
421
+ `
422
+ )
423
+ ]);
424
+ const conn = connectionsResult.rows[0];
425
+ const cache = bufferCacheResult.rows[0];
426
+ return {
427
+ connections: {
428
+ total: Number(conn.total),
429
+ active: Number(conn.active),
430
+ idle: Number(conn.idle),
431
+ idleInTransaction: Number(conn.idle_in_transaction),
432
+ idleInTransactionAborted: Number(conn.idle_in_transaction_aborted),
433
+ maxConnections: Number(maxConnectionsResult.rows[0].max_connections),
434
+ longestIdleInTransactionSeconds: toNullableNumber(conn.longest_idle_in_transaction_seconds),
435
+ longestActiveQuerySeconds: toNullableNumber(conn.longest_active_query_seconds)
436
+ },
437
+ bufferCache: {
438
+ hitRatioPct: toNullableNumber(cache.hit_ratio_pct),
439
+ blocksHit: Number(cache.blks_hit),
440
+ blocksRead: Number(cache.blks_read)
441
+ }
442
+ };
443
+ } finally {
444
+ client.release();
445
+ }
446
+ }
386
447
  async getStoredProcedures(schema, routineType) {
387
448
  if (!this.pool) {
388
449
  throw new Error("Not connected to database");
@@ -502,7 +563,11 @@ var PostgresConnector = class _PostgresConnector {
502
563
  try {
503
564
  const result2 = parameters && parameters.length > 0 ? await client.query(processedStatement, parameters) : await client.query(processedStatement);
504
565
  await client.query("COMMIT");
505
- 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
+ };
506
571
  } catch (error) {
507
572
  try {
508
573
  await client.query("ROLLBACK");
@@ -517,24 +582,26 @@ var PostgresConnector = class _PostgresConnector {
517
582
  } else {
518
583
  result = await client.query(processedStatement);
519
584
  }
520
- 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
+ };
521
590
  } else {
522
591
  if (parameters && parameters.length > 0) {
523
592
  throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
524
593
  }
525
- let allRows = [];
526
- let totalRowCount = 0;
594
+ const resultSets = [];
527
595
  await client.query(options.readonly ? "BEGIN READ ONLY" : "BEGIN");
528
596
  try {
529
597
  for (let statement of statements) {
530
598
  const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
531
599
  const result = await client.query(processedStatement);
532
- if (result.rows && result.rows.length > 0) {
533
- allRows.push(...result.rows);
534
- }
535
- if (result.rowCount) {
536
- totalRowCount += result.rowCount;
537
- }
600
+ resultSets.push({
601
+ sql: statement,
602
+ rows: result.rows ?? [],
603
+ rowCount: result.rowCount ?? result.rows?.length ?? 0
604
+ });
538
605
  }
539
606
  await client.query("COMMIT");
540
607
  } catch (error) {
@@ -544,7 +611,7 @@ var PostgresConnector = class _PostgresConnector {
544
611
  }
545
612
  throw error;
546
613
  }
547
- return { rows: allRows, rowCount: totalRowCount };
614
+ return { resultSets };
548
615
  }
549
616
  } finally {
550
617
  client.release();