@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.
- package/README.md +10 -52
- package/dist/{chunk-MQR6VX5F.js → chunk-CUBIR72I.js} +30 -5
- package/dist/chunk-FU2ZJE4E.js +16 -0
- package/dist/{chunk-JO7YQPBQ.js → chunk-PBN2UXB5.js} +25 -3
- package/dist/chunk-QEFMUQJ2.js +179 -0
- package/dist/{chunk-S2V6RZ3C.js → chunk-RSB7DPO2.js} +64 -3
- package/dist/{chunk-JEZZN2YZ.js → chunk-WIQ4DWGY.js} +16 -0
- package/dist/index.js +341 -57
- package/dist/{mariadb-72C2BNCG.js → mariadb-E33EXGJZ.js} +16 -9
- package/dist/{mysql-CDUKJMNN.js → mysql-RLDQI3SJ.js} +68 -11
- package/dist/{postgres-YAYHDXVD.js → postgres-6XY2GPQV.js} +80 -13
- package/dist/public/assets/{index-DBYlgGks.js → index-DOZ5HOZ9.js} +21 -20
- package/dist/public/index.html +1 -1
- package/dist/{registry-ZU6ZFNQ2.js → registry-XMEJF7WW.js} +2 -2
- package/dist/{sqlite-5M7SDAQZ.js → sqlite-CZE6Z36Y.js} +14 -13
- package/dist/{sqlserver-COKW7RIK.js → sqlserver-IIGRXGYK.js} +148 -13
- package/package.json +4 -2
- package/dist/chunk-DUCNPMNW.js +0 -113
package/dist/public/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>DBHub - Minimal Database MCP Server</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DOZ5HOZ9.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-BJ-1UrcV.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ToolRegistry,
|
|
3
3
|
getToolRegistry,
|
|
4
4
|
initializeToolRegistry
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CUBIR72I.js";
|
|
6
6
|
import "./chunk-WVVMH6FJ.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-WIQ4DWGY.js";
|
|
8
8
|
export {
|
|
9
9
|
ToolRegistry,
|
|
10
10
|
getToolRegistry,
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
SQLRowLimiter,
|
|
6
6
|
closeQuietly
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-RSB7DPO2.js";
|
|
8
8
|
import {
|
|
9
9
|
ConnectorRegistry,
|
|
10
10
|
SafeURL,
|
|
11
11
|
obfuscateDSNPassword,
|
|
12
12
|
splitSQLStatements
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WIQ4DWGY.js";
|
|
14
14
|
|
|
15
15
|
// src/connectors/sqlite/suppress-experimental-warning.ts
|
|
16
16
|
var installed = false;
|
|
@@ -340,10 +340,10 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
340
340
|
if (isReadStatement) {
|
|
341
341
|
if (parameters && parameters.length > 0) {
|
|
342
342
|
const rows = this.prepare(processedStatement).all(...parameters);
|
|
343
|
-
return { rows, rowCount: rows.length };
|
|
343
|
+
return { resultSets: [{ sql: statements[0], rows, rowCount: rows.length }] };
|
|
344
344
|
} else {
|
|
345
345
|
const rows = this.prepare(processedStatement).all();
|
|
346
|
-
return { rows, rowCount: rows.length };
|
|
346
|
+
return { resultSets: [{ sql: statements[0], rows, rowCount: rows.length }] };
|
|
347
347
|
}
|
|
348
348
|
} else {
|
|
349
349
|
let result;
|
|
@@ -352,7 +352,9 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
352
352
|
} else {
|
|
353
353
|
result = this.prepare(processedStatement).run();
|
|
354
354
|
}
|
|
355
|
-
return {
|
|
355
|
+
return {
|
|
356
|
+
resultSets: [{ sql: statements[0], rows: [], rowCount: Number(result.changes) }]
|
|
357
|
+
};
|
|
356
358
|
}
|
|
357
359
|
} else {
|
|
358
360
|
if (parameters && parameters.length > 0) {
|
|
@@ -368,24 +370,23 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
368
370
|
writeStatements.push(statement);
|
|
369
371
|
}
|
|
370
372
|
}
|
|
371
|
-
|
|
373
|
+
const resultSets = [];
|
|
372
374
|
for (const statement of writeStatements) {
|
|
373
375
|
if (options.readonly) {
|
|
374
376
|
this.db.exec("PRAGMA query_only = ON");
|
|
375
377
|
}
|
|
376
378
|
const result = this.prepare(statement).run();
|
|
377
|
-
|
|
379
|
+
resultSets.push({ sql: statement, rows: [], rowCount: Number(result.changes) });
|
|
378
380
|
}
|
|
379
|
-
|
|
380
|
-
for (let statement of readStatements) {
|
|
381
|
+
for (const statement of readStatements) {
|
|
381
382
|
if (options.readonly) {
|
|
382
383
|
this.db.exec("PRAGMA query_only = ON");
|
|
383
384
|
}
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
385
|
+
const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
|
|
386
|
+
const rows = this.prepare(processedStatement).all();
|
|
387
|
+
resultSets.push({ sql: statement, rows, rowCount: rows.length });
|
|
387
388
|
}
|
|
388
|
-
return {
|
|
389
|
+
return { resultSets };
|
|
389
390
|
}
|
|
390
391
|
} finally {
|
|
391
392
|
if (options.readonly) {
|
|
@@ -3,20 +3,25 @@ import {
|
|
|
3
3
|
sqlServerDynamicSqlPattern,
|
|
4
4
|
sqlServerPassThroughKeywords,
|
|
5
5
|
sqlServerPassThroughPattern
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-PBN2UXB5.js";
|
|
7
7
|
import {
|
|
8
8
|
isDriverNotInstalled
|
|
9
9
|
} from "./chunk-WVVMH6FJ.js";
|
|
10
|
+
import {
|
|
11
|
+
computeHitRatioPct,
|
|
12
|
+
toNullableNumber
|
|
13
|
+
} from "./chunk-FU2ZJE4E.js";
|
|
10
14
|
import {
|
|
11
15
|
SQLRowLimiter,
|
|
12
16
|
closeQuietly
|
|
13
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-RSB7DPO2.js";
|
|
14
18
|
import {
|
|
15
19
|
ConnectorRegistry,
|
|
16
20
|
SafeURL,
|
|
17
21
|
obfuscateDSNPassword,
|
|
22
|
+
splitSQLStatements,
|
|
18
23
|
stripCommentsAndStrings
|
|
19
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-WIQ4DWGY.js";
|
|
20
25
|
|
|
21
26
|
// src/connectors/sqlserver/index.ts
|
|
22
27
|
import sql from "mssql";
|
|
@@ -372,6 +377,81 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
372
377
|
return null;
|
|
373
378
|
}
|
|
374
379
|
}
|
|
380
|
+
async getHealthCheck() {
|
|
381
|
+
if (!this.connection) {
|
|
382
|
+
throw new Error("Not connected to SQL Server database");
|
|
383
|
+
}
|
|
384
|
+
const notes = [];
|
|
385
|
+
const result = {};
|
|
386
|
+
try {
|
|
387
|
+
const [connResult, maxConnResult] = await Promise.all([
|
|
388
|
+
this.connection.request().query(`
|
|
389
|
+
SELECT
|
|
390
|
+
COUNT(*) AS total,
|
|
391
|
+
SUM(CASE WHEN r.session_id IS NOT NULL THEN 1 ELSE 0 END) AS active,
|
|
392
|
+
SUM(CASE WHEN r.session_id IS NULL THEN 1 ELSE 0 END) AS idle,
|
|
393
|
+
SUM(CASE WHEN r.session_id IS NULL AND t.session_id IS NOT NULL THEN 1 ELSE 0 END) AS idle_in_transaction,
|
|
394
|
+
MAX(CASE WHEN r.session_id IS NULL AND t.session_id IS NOT NULL
|
|
395
|
+
THEN DATEDIFF(SECOND, s.last_request_end_time, SYSDATETIME()) ELSE NULL END) AS longest_idle_in_transaction_seconds,
|
|
396
|
+
MAX(CASE WHEN r.session_id IS NOT NULL
|
|
397
|
+
THEN DATEDIFF(SECOND, r.start_time, SYSDATETIME()) ELSE NULL END) AS longest_active_query_seconds
|
|
398
|
+
FROM sys.dm_exec_sessions s
|
|
399
|
+
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
|
|
400
|
+
LEFT JOIN (SELECT DISTINCT session_id FROM sys.dm_tran_session_transactions) t ON t.session_id = s.session_id
|
|
401
|
+
WHERE s.is_user_process = 1 AND s.session_id <> @@SPID
|
|
402
|
+
`),
|
|
403
|
+
this.connection.request().query(`
|
|
404
|
+
SELECT CAST(value_in_use AS INT) AS max_connections
|
|
405
|
+
FROM sys.configurations
|
|
406
|
+
WHERE name = 'user connections'
|
|
407
|
+
`)
|
|
408
|
+
]);
|
|
409
|
+
const conn = connResult.recordset[0];
|
|
410
|
+
const maxConnections = maxConnResult.recordset.length > 0 && maxConnResult.recordset[0].max_connections > 0 ? maxConnResult.recordset[0].max_connections : null;
|
|
411
|
+
result.connections = {
|
|
412
|
+
total: conn.total ?? 0,
|
|
413
|
+
active: conn.active ?? 0,
|
|
414
|
+
idle: conn.idle ?? 0,
|
|
415
|
+
idleInTransaction: conn.idle_in_transaction ?? 0,
|
|
416
|
+
// SQL Server has no equivalent of Postgres's "idle in transaction
|
|
417
|
+
// (aborted)" state - a failed statement doesn't leave the session's
|
|
418
|
+
// transaction in a distinct aborted-but-open state the way Postgres does.
|
|
419
|
+
maxConnections,
|
|
420
|
+
longestIdleInTransactionSeconds: toNullableNumber(conn.longest_idle_in_transaction_seconds),
|
|
421
|
+
longestActiveQuerySeconds: toNullableNumber(conn.longest_active_query_seconds)
|
|
422
|
+
};
|
|
423
|
+
} catch {
|
|
424
|
+
notes.push(
|
|
425
|
+
"Connection pool metrics unavailable: connecting user lacks the VIEW SERVER STATE permission (VIEW DATABASE STATE on Azure SQL Database)."
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const bufferResult = await this.connection.request().query(`
|
|
430
|
+
SELECT counter_name, CAST(cntr_value AS BIGINT) AS cntr_value
|
|
431
|
+
FROM sys.dm_os_performance_counters
|
|
432
|
+
WHERE object_name LIKE '%Buffer Manager%'
|
|
433
|
+
AND counter_name IN ('Page lookups/sec', 'Page reads/sec')
|
|
434
|
+
`);
|
|
435
|
+
const counters = Object.fromEntries(
|
|
436
|
+
bufferResult.recordset.map((row) => [row.counter_name.trim(), Number(row.cntr_value)])
|
|
437
|
+
);
|
|
438
|
+
const pageLookups = counters["Page lookups/sec"] ?? 0;
|
|
439
|
+
const pageReads = counters["Page reads/sec"] ?? 0;
|
|
440
|
+
result.bufferCache = {
|
|
441
|
+
hitRatioPct: computeHitRatioPct(pageLookups, pageReads),
|
|
442
|
+
blocksHit: pageLookups - pageReads,
|
|
443
|
+
blocksRead: pageReads
|
|
444
|
+
};
|
|
445
|
+
} catch {
|
|
446
|
+
notes.push(
|
|
447
|
+
"Buffer cache metrics unavailable: connecting user lacks the VIEW SERVER STATE permission (VIEW DATABASE STATE on Azure SQL Database)."
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
if (notes.length > 0) {
|
|
451
|
+
result.notes = notes;
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
375
455
|
async getStoredProcedures(schema, routineType) {
|
|
376
456
|
if (!this.connection) {
|
|
377
457
|
throw new Error("Not connected to SQL Server database");
|
|
@@ -485,8 +565,9 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
485
565
|
if (options.maxRows) {
|
|
486
566
|
processedSQL = SQLRowLimiter.applyMaxRowsForSQLServer(sqlQuery, options.maxRows);
|
|
487
567
|
}
|
|
568
|
+
const isSingleStatement = splitSQLStatements(processedSQL, "sqlserver").length === 1;
|
|
488
569
|
if (options.readonly) {
|
|
489
|
-
return await this.executeReadOnly(processedSQL, parameters);
|
|
570
|
+
return await this.executeReadOnly(processedSQL, parameters, isSingleStatement);
|
|
490
571
|
}
|
|
491
572
|
const request = this.connection.request();
|
|
492
573
|
const messages = [];
|
|
@@ -505,14 +586,57 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
505
586
|
_SQLServerConnector.bindParameters(request, parameters);
|
|
506
587
|
const result = await request.query(processedSQL);
|
|
507
588
|
return {
|
|
508
|
-
|
|
509
|
-
|
|
589
|
+
resultSets: _SQLServerConnector.buildResultSets(
|
|
590
|
+
result.recordsets,
|
|
591
|
+
result.rowsAffected,
|
|
592
|
+
isSingleStatement ? processedSQL : void 0
|
|
593
|
+
),
|
|
510
594
|
...messages.length > 0 ? { messages } : {}
|
|
511
595
|
};
|
|
512
596
|
} catch (error) {
|
|
513
597
|
throw new Error(`Failed to execute query: ${error.message}`);
|
|
514
598
|
}
|
|
515
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Builds one result set per SELECT-producing statement in the batch, plus
|
|
602
|
+
* (if any) a trailing result set summarizing the write-only statements.
|
|
603
|
+
*
|
|
604
|
+
* node-mssql only pushes a `recordsets` entry for statements that produce
|
|
605
|
+
* columns (SELECT); INSERT/UPDATE/DELETE statements in the same batch
|
|
606
|
+
* contribute to `rowsAffected` only, with no way to recover which
|
|
607
|
+
* `rowsAffected` entry belongs to which statement from the driver's final
|
|
608
|
+
* arrays (`recordsets`/`rowsAffected` aren't index-aligned - see the
|
|
609
|
+
* tedious `doneHandler`, which always pushes to `rowsAffected` but only
|
|
610
|
+
* conditionally to `recordsets`). So a batch of pure writes collapses to a
|
|
611
|
+
* single summed result set, and a batch mixing writes with selects gets
|
|
612
|
+
* one result set per select plus one trailing "writes" set for the rest -
|
|
613
|
+
* not a truly per-statement breakdown, but no read statement's rows are
|
|
614
|
+
* ever merged with another's, which is what mattered for #380.
|
|
615
|
+
*
|
|
616
|
+
* `sourceSql`, when given, is attributed to the single resulting set - it
|
|
617
|
+
* must be omitted (pass `undefined`) unless the caller has already
|
|
618
|
+
* confirmed the batch is unambiguously one statement, since for a genuine
|
|
619
|
+
* multi-statement batch there's no reliable way to say which source
|
|
620
|
+
* statement a given recordset (or the trailing writes set) came from.
|
|
621
|
+
*/
|
|
622
|
+
static buildResultSets(recordsets, rowsAffected, sourceSql) {
|
|
623
|
+
const sets = (recordsets ?? []).map((recordset) => {
|
|
624
|
+
const rows = recordset ?? [];
|
|
625
|
+
return { rows, rowCount: rows.length };
|
|
626
|
+
});
|
|
627
|
+
const totalAffected = (rowsAffected ?? []).reduce((total, count) => total + (count ?? 0), 0);
|
|
628
|
+
const accountedFor = sets.reduce((total, set) => total + set.rowCount, 0);
|
|
629
|
+
const writesOnly = totalAffected - accountedFor;
|
|
630
|
+
if (sets.length === 0) {
|
|
631
|
+
sets.push({ rows: [], rowCount: totalAffected });
|
|
632
|
+
} else if (writesOnly > 0) {
|
|
633
|
+
sets.push({ rows: [], rowCount: writesOnly });
|
|
634
|
+
}
|
|
635
|
+
if (sets.length === 1 && sourceSql !== void 0) {
|
|
636
|
+
sets[0].sql = sourceSql;
|
|
637
|
+
}
|
|
638
|
+
return sets;
|
|
639
|
+
}
|
|
516
640
|
/**
|
|
517
641
|
* Bind positional parameters as @p1, @p2, ... inferring the SQL Server type
|
|
518
642
|
* from each JavaScript value. Shared by every execution path so they cannot
|
|
@@ -594,7 +718,7 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
594
718
|
* Dangerous constructs are rejected before the transaction opens; see
|
|
595
719
|
* assertNoReadOnlyEscapes.
|
|
596
720
|
*/
|
|
597
|
-
async executeReadOnly(processedSQL, parameters) {
|
|
721
|
+
async executeReadOnly(processedSQL, parameters, isSingleStatement) {
|
|
598
722
|
this.assertNoReadOnlyEscapes(processedSQL, { transactionControl: true });
|
|
599
723
|
const transaction = new sql.Transaction(this.connection);
|
|
600
724
|
await transaction.begin();
|
|
@@ -631,8 +755,11 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
631
755
|
}
|
|
632
756
|
}
|
|
633
757
|
return {
|
|
634
|
-
|
|
635
|
-
|
|
758
|
+
resultSets: _SQLServerConnector.buildResultSets(
|
|
759
|
+
result.recordsets,
|
|
760
|
+
result.rowsAffected,
|
|
761
|
+
isSingleStatement ? processedSQL : void 0
|
|
762
|
+
),
|
|
636
763
|
...messages.length > 0 ? { messages } : {}
|
|
637
764
|
};
|
|
638
765
|
}
|
|
@@ -677,8 +804,12 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
677
804
|
const planRow = planResult.recordset?.[0];
|
|
678
805
|
const planXml = planRow ? Object.values(planRow)[0] : null;
|
|
679
806
|
return {
|
|
680
|
-
|
|
681
|
-
|
|
807
|
+
resultSets: [
|
|
808
|
+
{
|
|
809
|
+
rows: planXml != null ? [{ plan: planXml }] : [],
|
|
810
|
+
rowCount: planXml != null ? 1 : 0
|
|
811
|
+
}
|
|
812
|
+
]
|
|
682
813
|
};
|
|
683
814
|
} catch (error) {
|
|
684
815
|
throw new Error(`Failed to explain query: ${error.message}`);
|
|
@@ -799,8 +930,12 @@ var _SQLServerConnector = class _SQLServerConnector {
|
|
|
799
930
|
}
|
|
800
931
|
const planXml = _SQLServerConnector.extractPlanXml(planResult);
|
|
801
932
|
return {
|
|
802
|
-
|
|
803
|
-
|
|
933
|
+
resultSets: [
|
|
934
|
+
{
|
|
935
|
+
rows: planXml != null ? [{ plan: planXml }] : [],
|
|
936
|
+
rowCount: planXml != null ? 1 : 0
|
|
937
|
+
}
|
|
938
|
+
]
|
|
804
939
|
};
|
|
805
940
|
} catch (error) {
|
|
806
941
|
throw new Error(`Failed to explain analyze query: ${error.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebase/dbhub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.bytebase/dbhub",
|
|
5
5
|
"description": "Minimal, token-efficient Database MCP Server for PostgreSQL, MySQL, SQL Server, SQLite, MariaDB",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"test:unit": "vitest run --project unit",
|
|
34
34
|
"test:watch": "vitest",
|
|
35
35
|
"test:integration": "vitest run --project integration",
|
|
36
|
-
"test:build": "node scripts/smoke-test-build.mjs"
|
|
36
|
+
"test:build": "node scripts/smoke-test-build.mjs",
|
|
37
|
+
"sync-version": "node scripts/sync-version.mjs",
|
|
38
|
+
"version": "pnpm run sync-version && git add plugin/.claude-plugin/plugin.json plugin/.mcp.json server.json"
|
|
37
39
|
},
|
|
38
40
|
"engines": {
|
|
39
41
|
"node": ">=22.5.0"
|
package/dist/chunk-DUCNPMNW.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
obfuscateDSNPassword
|
|
3
|
-
} from "./chunk-JEZZN2YZ.js";
|
|
4
|
-
|
|
5
|
-
// src/utils/dsn-database.ts
|
|
6
|
-
var MissingDatabaseError = class extends Error {
|
|
7
|
-
constructor(message) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = "MissingDatabaseError";
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
function requireDatabaseInDSN(database, dsn, label) {
|
|
13
|
-
if (database) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
throw new MissingDatabaseError(
|
|
17
|
-
`${label} DSN must name a database.
|
|
18
|
-
Provided: ${obfuscateDSNPassword(dsn)}
|
|
19
|
-
Add the database to the DSN, e.g. ...:3306/mydb
|
|
20
|
-
To work with several databases, define one [[sources]] entry per database in a TOML config file: https://dbhub.ai/config/toml`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// src/utils/multi-statement-result-parser.ts
|
|
25
|
-
function isMetadataObject(element) {
|
|
26
|
-
if (!element || typeof element !== "object" || Array.isArray(element)) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
return "affectedRows" in element || "insertId" in element || "fieldCount" in element || "warningStatus" in element;
|
|
30
|
-
}
|
|
31
|
-
function isMultiStatementResult(results) {
|
|
32
|
-
if (!Array.isArray(results) || results.length === 0) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
const firstElement = results[0];
|
|
36
|
-
return isMetadataObject(firstElement) || Array.isArray(firstElement);
|
|
37
|
-
}
|
|
38
|
-
function extractRowsFromMultiStatement(results) {
|
|
39
|
-
if (!Array.isArray(results)) {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
const allRows = [];
|
|
43
|
-
for (const result of results) {
|
|
44
|
-
if (Array.isArray(result)) {
|
|
45
|
-
allRows.push(...result);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return allRows;
|
|
49
|
-
}
|
|
50
|
-
function extractAffectedRows(results) {
|
|
51
|
-
if (isMetadataObject(results)) {
|
|
52
|
-
return results.affectedRows || 0;
|
|
53
|
-
}
|
|
54
|
-
if (!Array.isArray(results)) {
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
if (isMultiStatementResult(results)) {
|
|
58
|
-
let totalAffected = 0;
|
|
59
|
-
for (const result of results) {
|
|
60
|
-
if (isMetadataObject(result)) {
|
|
61
|
-
totalAffected += result.affectedRows || 0;
|
|
62
|
-
} else if (Array.isArray(result)) {
|
|
63
|
-
totalAffected += result.length;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return totalAffected;
|
|
67
|
-
}
|
|
68
|
-
return results.length;
|
|
69
|
-
}
|
|
70
|
-
function parseQueryResults(results) {
|
|
71
|
-
if (!Array.isArray(results)) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
if (isMultiStatementResult(results)) {
|
|
75
|
-
return extractRowsFromMultiStatement(results);
|
|
76
|
-
}
|
|
77
|
-
return results;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/utils/readonly-transaction.ts
|
|
81
|
-
async function withReadOnlyTransaction(conn, readonly, supportsReadOnlyTransaction, execute) {
|
|
82
|
-
if (!readonly) {
|
|
83
|
-
return execute();
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
await conn.query(
|
|
87
|
-
supportsReadOnlyTransaction ? "START TRANSACTION READ ONLY" : "START TRANSACTION"
|
|
88
|
-
);
|
|
89
|
-
const result = await execute();
|
|
90
|
-
await conn.query(supportsReadOnlyTransaction ? "COMMIT" : "ROLLBACK");
|
|
91
|
-
return result;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
try {
|
|
94
|
-
await conn.query("ROLLBACK");
|
|
95
|
-
} catch {
|
|
96
|
-
}
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/utils/server-flavor.ts
|
|
102
|
-
function isTiDBVersion(version) {
|
|
103
|
-
return typeof version === "string" && /tidb/i.test(version);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export {
|
|
107
|
-
MissingDatabaseError,
|
|
108
|
-
requireDatabaseInDSN,
|
|
109
|
-
extractAffectedRows,
|
|
110
|
-
parseQueryResults,
|
|
111
|
-
withReadOnlyTransaction,
|
|
112
|
-
isTiDBVersion
|
|
113
|
-
};
|