@bytebase/dbhub 0.22.6 → 0.24.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 +4 -0
- package/dist/{chunk-BXXHWEQS.js → chunk-D2BIPC7V.js} +10 -1
- package/dist/{chunk-RTB262PR.js → chunk-DUCNPMNW.js} +54 -1
- package/dist/chunk-JO7YQPBQ.js +130 -0
- package/dist/{chunk-WPXPKRDU.js → chunk-MQR6VX5F.js} +173 -72
- package/dist/index.js +14 -103
- package/dist/{mariadb-JPEKVFEW.js → mariadb-VZMU7LYA.js} +66 -46
- package/dist/{mysql-EK6TCER5.js → mysql-MHIZK2UY.js} +65 -46
- package/dist/{postgres-BKBCGDZS.js → postgres-ZQFXCBYA.js} +8 -2
- package/dist/{registry-IPRZ55YC.js → registry-ZU6ZFNQ2.js} +1 -1
- package/dist/{sqlite-CQ4LCBB6.js → sqlite-JH5CNV5E.js} +8 -2
- package/dist/{sqlserver-47APXMSF.js → sqlserver-LHWZ7WOP.js} +140 -5
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,11 @@ import {
|
|
|
15
15
|
resolveSourceConfigs,
|
|
16
16
|
resolveTomlConfigPath,
|
|
17
17
|
resolveTransport
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-MQR6VX5F.js";
|
|
19
|
+
import {
|
|
20
|
+
allowedKeywords,
|
|
21
|
+
isReadOnlySQL
|
|
22
|
+
} from "./chunk-JO7YQPBQ.js";
|
|
19
23
|
import {
|
|
20
24
|
loadConnectors
|
|
21
25
|
} from "./chunk-WVVMH6FJ.js";
|
|
@@ -27,8 +31,7 @@ import {
|
|
|
27
31
|
getDatabaseTypeFromDSN,
|
|
28
32
|
getDefaultPortForType,
|
|
29
33
|
parseConnectionInfoFromDSN,
|
|
30
|
-
splitSQLStatements
|
|
31
|
-
stripCommentsAndStrings
|
|
34
|
+
splitSQLStatements
|
|
32
35
|
} from "./chunk-JEZZN2YZ.js";
|
|
33
36
|
|
|
34
37
|
// src/server.ts
|
|
@@ -95,98 +98,6 @@ function createToolSuccessResponse(data, meta = {}) {
|
|
|
95
98
|
};
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
// src/utils/allowed-keywords.ts
|
|
99
|
-
var allowedKeywords = {
|
|
100
|
-
postgres: ["select", "with", "explain", "show"],
|
|
101
|
-
mysql: ["select", "with", "explain", "show", "describe", "desc"],
|
|
102
|
-
mariadb: ["select", "with", "explain", "show", "describe", "desc"],
|
|
103
|
-
sqlite: ["select", "with", "explain", "pragma"],
|
|
104
|
-
// SQL Server has no native EXPLAIN statement; the connector translates a
|
|
105
|
-
// leading `EXPLAIN` into a SET SHOWPLAN_XML request (see SQLServerConnector).
|
|
106
|
-
sqlserver: ["select", "with", "explain"]
|
|
107
|
-
};
|
|
108
|
-
var mutatingKeywords = [
|
|
109
|
-
"insert",
|
|
110
|
-
"update",
|
|
111
|
-
"delete",
|
|
112
|
-
"drop",
|
|
113
|
-
"alter",
|
|
114
|
-
"create",
|
|
115
|
-
"truncate",
|
|
116
|
-
"merge",
|
|
117
|
-
"grant",
|
|
118
|
-
"revoke",
|
|
119
|
-
"rename"
|
|
120
|
-
];
|
|
121
|
-
var mutatingPattern = new RegExp(
|
|
122
|
-
`\\b(?:${mutatingKeywords.join("|")})\\b`,
|
|
123
|
-
"i"
|
|
124
|
-
);
|
|
125
|
-
var mutatingPatternWithReplace = new RegExp(
|
|
126
|
-
`\\b(?:${mutatingKeywords.join("|")}|replace\\s+(?:(?:low_priority|delayed)\\s+)?into)\\b`,
|
|
127
|
-
"i"
|
|
128
|
-
);
|
|
129
|
-
var mutatingPatterns = {
|
|
130
|
-
postgres: mutatingPattern,
|
|
131
|
-
mysql: mutatingPatternWithReplace,
|
|
132
|
-
mariadb: mutatingPatternWithReplace,
|
|
133
|
-
sqlite: mutatingPatternWithReplace,
|
|
134
|
-
sqlserver: mutatingPattern
|
|
135
|
-
};
|
|
136
|
-
var selectIntoPattern = /\bselect\b[\s\S]+\binto\b/i;
|
|
137
|
-
var sqliteReadOnlyArgPragmas = /* @__PURE__ */ new Set([
|
|
138
|
-
"table_info",
|
|
139
|
-
"index_info",
|
|
140
|
-
"index_list",
|
|
141
|
-
"foreign_key_list"
|
|
142
|
-
]);
|
|
143
|
-
var sqlitePragmaParenPattern = /^pragma\s+(?:[a-z0-9_]+\.)?([a-z0-9_]+)\s*\(/;
|
|
144
|
-
var explainAnalyzePattern = /^explain\s+(?:\([^)]*\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)[^)]*\)|\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)(?:\s+verbose\b)?)/i;
|
|
145
|
-
function isReadOnlySQL(sql, connectorType) {
|
|
146
|
-
return checkReadOnly(
|
|
147
|
-
stripCommentsAndStrings(sql, connectorType).trim().toLowerCase(),
|
|
148
|
-
connectorType
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
function checkReadOnly(cleanedSQL, connectorType) {
|
|
152
|
-
if (!cleanedSQL) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
const firstWord = cleanedSQL.match(/\S+/)?.[0] ?? "";
|
|
156
|
-
const keywordList = allowedKeywords[connectorType] || [];
|
|
157
|
-
if (!keywordList.includes(firstWord)) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
if (firstWord === "with") {
|
|
161
|
-
const pattern = mutatingPatterns[connectorType] ?? mutatingPattern;
|
|
162
|
-
if (pattern.test(cleanedSQL)) {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (firstWord === "pragma" && connectorType === "sqlite") {
|
|
167
|
-
if (cleanedSQL.includes("=")) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
const parenMatch = cleanedSQL.match(sqlitePragmaParenPattern);
|
|
171
|
-
if (parenMatch && !sqliteReadOnlyArgPragmas.has(parenMatch[1])) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if ((firstWord === "select" || firstWord === "with") && selectIntoPattern.test(cleanedSQL)) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
if (firstWord === "explain") {
|
|
179
|
-
const m = explainAnalyzePattern.exec(cleanedSQL);
|
|
180
|
-
if (m) {
|
|
181
|
-
const afterExplain = cleanedSQL.slice(m[0].length).trim();
|
|
182
|
-
if (afterExplain && !checkReadOnly(afterExplain, connectorType)) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
101
|
// src/requests/store.ts
|
|
191
102
|
var RequestStore = class {
|
|
192
103
|
constructor() {
|
|
@@ -373,7 +284,7 @@ async function getTableRowCount(connector, tableName, schemaName) {
|
|
|
373
284
|
}
|
|
374
285
|
const qualifiedTable = quoteQualifiedIdentifier(tableName, schemaName, connector.id);
|
|
375
286
|
const countQuery = `SELECT COUNT(*) as count FROM ${qualifiedTable}`;
|
|
376
|
-
const result = await connector.executeSQL(countQuery, { maxRows: 1 });
|
|
287
|
+
const result = await connector.executeSQL(countQuery, { maxRows: 1, readonly: true });
|
|
377
288
|
if (result.rows && result.rows.length > 0) {
|
|
378
289
|
return Number(result.rows[0].count || result.rows[0].COUNT || 0);
|
|
379
290
|
}
|
|
@@ -506,7 +417,7 @@ async function searchViews(connector, pattern, schemaFilter, detailLevel, limit)
|
|
|
506
417
|
if (schemaFilter) {
|
|
507
418
|
schemasToSearch = [schemaFilter];
|
|
508
419
|
} else {
|
|
509
|
-
schemasToSearch = await connector
|
|
420
|
+
schemasToSearch = await resolveDefaultSchemas(connector);
|
|
510
421
|
}
|
|
511
422
|
for (const schemaName of schemasToSearch) {
|
|
512
423
|
if (results.length >= limit) break;
|
|
@@ -1672,7 +1583,7 @@ See documentation for more details on configuring database connections.
|
|
|
1672
1583
|
const sources = sourceConfigsData.sources;
|
|
1673
1584
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
1674
1585
|
await connectorManager.connectWithSources(sources);
|
|
1675
|
-
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-
|
|
1586
|
+
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-ZU6ZFNQ2.js");
|
|
1676
1587
|
initializeToolRegistry2({
|
|
1677
1588
|
sources: sourceConfigsData.sources,
|
|
1678
1589
|
tools: sourceConfigsData.tools
|
|
@@ -1826,11 +1737,11 @@ See documentation for more details on configuring database connections.
|
|
|
1826
1737
|
|
|
1827
1738
|
// src/index.ts
|
|
1828
1739
|
var connectorModules = [
|
|
1829
|
-
{ load: () => import("./postgres-
|
|
1830
|
-
{ load: () => import("./sqlserver-
|
|
1831
|
-
{ load: () => import("./sqlite-
|
|
1832
|
-
{ load: () => import("./mysql-
|
|
1833
|
-
{ load: () => import("./mariadb-
|
|
1740
|
+
{ load: () => import("./postgres-ZQFXCBYA.js"), name: "PostgreSQL", driver: "pg" },
|
|
1741
|
+
{ load: () => import("./sqlserver-LHWZ7WOP.js"), name: "SQL Server", driver: "mssql" },
|
|
1742
|
+
{ load: () => import("./sqlite-JH5CNV5E.js"), name: "SQLite", driver: "node:sqlite" },
|
|
1743
|
+
{ load: () => import("./mysql-MHIZK2UY.js"), name: "MySQL", driver: "mysql2" },
|
|
1744
|
+
{ load: () => import("./mariadb-VZMU7LYA.js"), name: "MariaDB", driver: "mariadb" }
|
|
1834
1745
|
];
|
|
1835
1746
|
loadConnectors(connectorModules).then(() => main()).catch((error) => {
|
|
1836
1747
|
console.error("Fatal error:", error);
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MissingDatabaseError,
|
|
2
3
|
extractAffectedRows,
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
isTiDBVersion,
|
|
5
|
+
parseQueryResults,
|
|
6
|
+
requireDatabaseInDSN,
|
|
7
|
+
withReadOnlyTransaction
|
|
8
|
+
} from "./chunk-DUCNPMNW.js";
|
|
5
9
|
import {
|
|
6
10
|
quoteIdentifier
|
|
7
11
|
} from "./chunk-JFWX35TB.js";
|
|
8
12
|
import {
|
|
9
|
-
SQLRowLimiter
|
|
10
|
-
|
|
13
|
+
SQLRowLimiter,
|
|
14
|
+
closeQuietly
|
|
15
|
+
} from "./chunk-D2BIPC7V.js";
|
|
11
16
|
import {
|
|
12
17
|
ConnectorRegistry,
|
|
13
18
|
SafeURL,
|
|
@@ -32,11 +37,12 @@ Expected: ${expectedFormat}`
|
|
|
32
37
|
}
|
|
33
38
|
try {
|
|
34
39
|
const url = new SafeURL(dsn);
|
|
40
|
+
const database = url.pathname ? url.pathname.substring(1) : "";
|
|
41
|
+
requireDatabaseInDSN(database, dsn, "MariaDB");
|
|
35
42
|
const connectionConfig = {
|
|
36
43
|
host: url.hostname,
|
|
37
44
|
port: url.port ? parseInt(url.port) : 3306,
|
|
38
|
-
database
|
|
39
|
-
// Remove leading '/' if exists
|
|
45
|
+
database,
|
|
40
46
|
user: url.username,
|
|
41
47
|
password: url.password,
|
|
42
48
|
multipleStatements: true,
|
|
@@ -50,7 +56,14 @@ Expected: ${expectedFormat}`
|
|
|
50
56
|
// Controls how the driver interprets DATETIME values ("Z", "local", or "±HH:MM").
|
|
51
57
|
...config?.timezone !== void 0 && {
|
|
52
58
|
timezone: config.timezone
|
|
53
|
-
}
|
|
59
|
+
},
|
|
60
|
+
// Connection character set (e.g. "utf8mb4") / collation (e.g.
|
|
61
|
+
// "utf8mb4_general_ci"). The mariadb driver exposes both as distinct
|
|
62
|
+
// options, but a configured collation is authoritative (it implies its
|
|
63
|
+
// character set) and the driver ignores `collation` when `charset` is also
|
|
64
|
+
// passed. So forward the collation when present, otherwise the charset
|
|
65
|
+
// (which uses that character set's default collation).
|
|
66
|
+
...config?.collation !== void 0 ? { collation: config.collation } : config?.charset !== void 0 ? { charset: config.charset } : {}
|
|
54
67
|
};
|
|
55
68
|
url.forEachSearchParam((value, key) => {
|
|
56
69
|
if (key === "sslmode") {
|
|
@@ -70,6 +83,9 @@ Expected: ${expectedFormat}`
|
|
|
70
83
|
}
|
|
71
84
|
return connectionConfig;
|
|
72
85
|
} catch (error) {
|
|
86
|
+
if (error instanceof MissingDatabaseError) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
73
89
|
throw new Error(
|
|
74
90
|
`Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
75
91
|
);
|
|
@@ -94,6 +110,9 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
94
110
|
this.pool = null;
|
|
95
111
|
// Source ID is set by ConnectorManager after cloning
|
|
96
112
|
this.sourceId = "default";
|
|
113
|
+
// TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
|
|
114
|
+
// unless tidb_enable_noop_functions is on. Detected once at connect time.
|
|
115
|
+
this.supportsReadOnlyTransaction = true;
|
|
97
116
|
}
|
|
98
117
|
getId() {
|
|
99
118
|
return this.sourceId;
|
|
@@ -105,8 +124,14 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
105
124
|
try {
|
|
106
125
|
const connectionConfig = await this.dsnParser.parse(dsn, config);
|
|
107
126
|
this.pool = mariadb.createPool(connectionConfig);
|
|
108
|
-
await this.pool.query("SELECT
|
|
127
|
+
const rows = await this.pool.query("SELECT VERSION() AS version");
|
|
128
|
+
this.supportsReadOnlyTransaction = !isTiDBVersion(rows?.[0]?.version);
|
|
109
129
|
} catch (err) {
|
|
130
|
+
if (this.pool) {
|
|
131
|
+
const pool = this.pool;
|
|
132
|
+
this.pool = null;
|
|
133
|
+
await closeQuietly(() => pool.end());
|
|
134
|
+
}
|
|
110
135
|
console.error("Failed to connect to MariaDB database:", err);
|
|
111
136
|
throw err;
|
|
112
137
|
}
|
|
@@ -466,46 +491,41 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
466
491
|
}
|
|
467
492
|
const conn = await this.pool.getConnection();
|
|
468
493
|
try {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
494
|
+
return await withReadOnlyTransaction(
|
|
495
|
+
conn,
|
|
496
|
+
options.readonly,
|
|
497
|
+
this.supportsReadOnlyTransaction,
|
|
498
|
+
async () => {
|
|
499
|
+
let processedSQL = sql;
|
|
500
|
+
if (options.maxRows) {
|
|
501
|
+
const statements = splitSQLStatements(sql, "mariadb");
|
|
502
|
+
const processedStatements = statements.map(
|
|
503
|
+
(statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
|
|
504
|
+
);
|
|
505
|
+
processedSQL = processedStatements.join("; ");
|
|
506
|
+
if (sql.trim().endsWith(";")) {
|
|
507
|
+
processedSQL += ";";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
let results;
|
|
511
|
+
if (parameters && parameters.length > 0) {
|
|
512
|
+
try {
|
|
513
|
+
results = await conn.query(processedSQL, parameters);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.error(`[MariaDB executeSQL] ERROR: ${error.message}`);
|
|
516
|
+
console.error(`[MariaDB executeSQL] SQL: ${processedSQL}`);
|
|
517
|
+
console.error(`[MariaDB executeSQL] Parameters: ${JSON.stringify(parameters)}`);
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
results = await conn.query(processedSQL);
|
|
522
|
+
}
|
|
523
|
+
const rows = parseQueryResults(results);
|
|
524
|
+
const rowCount = extractAffectedRows(results);
|
|
525
|
+
return { rows, rowCount };
|
|
492
526
|
}
|
|
493
|
-
|
|
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 };
|
|
527
|
+
);
|
|
502
528
|
} catch (error) {
|
|
503
|
-
if (options.readonly) {
|
|
504
|
-
try {
|
|
505
|
-
await conn.query("ROLLBACK");
|
|
506
|
-
} catch {
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
529
|
console.error("Error executing query:", error);
|
|
510
530
|
throw error;
|
|
511
531
|
} finally {
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MissingDatabaseError,
|
|
2
3
|
extractAffectedRows,
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
isTiDBVersion,
|
|
5
|
+
parseQueryResults,
|
|
6
|
+
requireDatabaseInDSN,
|
|
7
|
+
withReadOnlyTransaction
|
|
8
|
+
} from "./chunk-DUCNPMNW.js";
|
|
5
9
|
import {
|
|
6
10
|
quoteIdentifier
|
|
7
11
|
} from "./chunk-JFWX35TB.js";
|
|
8
12
|
import {
|
|
9
|
-
SQLRowLimiter
|
|
10
|
-
|
|
13
|
+
SQLRowLimiter,
|
|
14
|
+
closeQuietly
|
|
15
|
+
} from "./chunk-D2BIPC7V.js";
|
|
11
16
|
import {
|
|
12
17
|
ConnectorRegistry,
|
|
13
18
|
SafeURL,
|
|
@@ -21,6 +26,8 @@ var MySQLDSNParser = class {
|
|
|
21
26
|
async parse(dsn, config) {
|
|
22
27
|
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
23
28
|
const timezone = config?.timezone;
|
|
29
|
+
const charset = config?.charset;
|
|
30
|
+
const collation = config?.collation;
|
|
24
31
|
if (!this.isValidDSN(dsn)) {
|
|
25
32
|
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
26
33
|
const expectedFormat = this.getSampleDSN();
|
|
@@ -32,11 +39,12 @@ Expected: ${expectedFormat}`
|
|
|
32
39
|
}
|
|
33
40
|
try {
|
|
34
41
|
const url = new SafeURL(dsn);
|
|
42
|
+
const database = url.pathname ? url.pathname.substring(1) : "";
|
|
43
|
+
requireDatabaseInDSN(database, dsn, "MySQL");
|
|
35
44
|
const config2 = {
|
|
36
45
|
host: url.hostname,
|
|
37
46
|
port: url.port ? parseInt(url.port) : 3306,
|
|
38
|
-
database
|
|
39
|
-
// Remove leading '/' if exists
|
|
47
|
+
database,
|
|
40
48
|
user: url.username,
|
|
41
49
|
password: url.password,
|
|
42
50
|
multipleStatements: true,
|
|
@@ -61,6 +69,10 @@ Expected: ${expectedFormat}`
|
|
|
61
69
|
if (timezone !== void 0) {
|
|
62
70
|
config2.timezone = timezone;
|
|
63
71
|
}
|
|
72
|
+
const charsetOrCollation = collation ?? charset;
|
|
73
|
+
if (charsetOrCollation !== void 0) {
|
|
74
|
+
config2.charset = charsetOrCollation;
|
|
75
|
+
}
|
|
64
76
|
if (url.password && url.password.includes("X-Amz-Credential")) {
|
|
65
77
|
config2.authPlugins = {
|
|
66
78
|
mysql_clear_password: () => () => {
|
|
@@ -73,6 +85,9 @@ Expected: ${expectedFormat}`
|
|
|
73
85
|
}
|
|
74
86
|
return config2;
|
|
75
87
|
} catch (error) {
|
|
88
|
+
if (error instanceof MissingDatabaseError) {
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
76
91
|
throw new Error(
|
|
77
92
|
`Failed to parse MySQL DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
78
93
|
);
|
|
@@ -97,6 +112,9 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
97
112
|
this.pool = null;
|
|
98
113
|
// Source ID is set by ConnectorManager after cloning
|
|
99
114
|
this.sourceId = "default";
|
|
115
|
+
// TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
|
|
116
|
+
// unless tidb_enable_noop_functions is on. Detected once at connect time.
|
|
117
|
+
this.supportsReadOnlyTransaction = true;
|
|
100
118
|
}
|
|
101
119
|
getId() {
|
|
102
120
|
return this.sourceId;
|
|
@@ -111,8 +129,14 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
111
129
|
if (config?.queryTimeoutSeconds !== void 0) {
|
|
112
130
|
this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
|
|
113
131
|
}
|
|
114
|
-
const [rows] = await this.pool.query("SELECT
|
|
132
|
+
const [rows] = await this.pool.query("SELECT VERSION() AS version");
|
|
133
|
+
this.supportsReadOnlyTransaction = !isTiDBVersion(rows[0]?.version);
|
|
115
134
|
} catch (err) {
|
|
135
|
+
if (this.pool) {
|
|
136
|
+
const pool = this.pool;
|
|
137
|
+
this.pool = null;
|
|
138
|
+
await closeQuietly(() => pool.end());
|
|
139
|
+
}
|
|
116
140
|
console.error("Failed to connect to MySQL database:", err);
|
|
117
141
|
throw err;
|
|
118
142
|
}
|
|
@@ -472,47 +496,42 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
472
496
|
}
|
|
473
497
|
const conn = await this.pool.getConnection();
|
|
474
498
|
try {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
499
|
+
return await withReadOnlyTransaction(
|
|
500
|
+
conn,
|
|
501
|
+
options.readonly,
|
|
502
|
+
this.supportsReadOnlyTransaction,
|
|
503
|
+
async () => {
|
|
504
|
+
let processedSQL = sql;
|
|
505
|
+
if (options.maxRows) {
|
|
506
|
+
const statements = splitSQLStatements(sql, "mysql");
|
|
507
|
+
const processedStatements = statements.map(
|
|
508
|
+
(statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
|
|
509
|
+
);
|
|
510
|
+
processedSQL = processedStatements.join("; ");
|
|
511
|
+
if (sql.trim().endsWith(";")) {
|
|
512
|
+
processedSQL += ";";
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
let results;
|
|
516
|
+
if (parameters && parameters.length > 0) {
|
|
517
|
+
try {
|
|
518
|
+
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs }, parameters);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error(`[MySQL executeSQL] ERROR: ${error.message}`);
|
|
521
|
+
console.error(`[MySQL executeSQL] SQL: ${processedSQL}`);
|
|
522
|
+
console.error(`[MySQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
|
|
527
|
+
}
|
|
528
|
+
const [firstResult] = results;
|
|
529
|
+
const rows = parseQueryResults(firstResult);
|
|
530
|
+
const rowCount = extractAffectedRows(firstResult);
|
|
531
|
+
return { rows, rowCount };
|
|
498
532
|
}
|
|
499
|
-
|
|
500
|
-
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
|
|
501
|
-
}
|
|
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 };
|
|
533
|
+
);
|
|
509
534
|
} catch (error) {
|
|
510
|
-
if (options.readonly) {
|
|
511
|
-
try {
|
|
512
|
-
await conn.query("ROLLBACK");
|
|
513
|
-
} catch {
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
535
|
console.error("Error executing query:", error);
|
|
517
536
|
throw error;
|
|
518
537
|
} finally {
|
|
@@ -2,8 +2,9 @@ import {
|
|
|
2
2
|
quoteIdentifier
|
|
3
3
|
} from "./chunk-JFWX35TB.js";
|
|
4
4
|
import {
|
|
5
|
-
SQLRowLimiter
|
|
6
|
-
|
|
5
|
+
SQLRowLimiter,
|
|
6
|
+
closeQuietly
|
|
7
|
+
} from "./chunk-D2BIPC7V.js";
|
|
7
8
|
import {
|
|
8
9
|
ConnectorRegistry,
|
|
9
10
|
SafeURL,
|
|
@@ -148,6 +149,11 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
148
149
|
const client = await this.pool.connect();
|
|
149
150
|
client.release();
|
|
150
151
|
} catch (err) {
|
|
152
|
+
if (this.pool) {
|
|
153
|
+
const pool = this.pool;
|
|
154
|
+
this.pool = null;
|
|
155
|
+
await closeQuietly(() => pool.end());
|
|
156
|
+
}
|
|
151
157
|
console.error("Failed to connect to PostgreSQL database:", err);
|
|
152
158
|
throw err;
|
|
153
159
|
}
|
|
@@ -2,8 +2,9 @@ import {
|
|
|
2
2
|
quoteIdentifier
|
|
3
3
|
} from "./chunk-JFWX35TB.js";
|
|
4
4
|
import {
|
|
5
|
-
SQLRowLimiter
|
|
6
|
-
|
|
5
|
+
SQLRowLimiter,
|
|
6
|
+
closeQuietly
|
|
7
|
+
} from "./chunk-D2BIPC7V.js";
|
|
7
8
|
import {
|
|
8
9
|
ConnectorRegistry,
|
|
9
10
|
SafeURL,
|
|
@@ -150,6 +151,11 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
150
151
|
this.db.exec(initScript);
|
|
151
152
|
}
|
|
152
153
|
} catch (error) {
|
|
154
|
+
if (this.db) {
|
|
155
|
+
const db = this.db;
|
|
156
|
+
this.db = null;
|
|
157
|
+
await closeQuietly(() => db.close());
|
|
158
|
+
}
|
|
153
159
|
console.error("Failed to connect to SQLite database:", error);
|
|
154
160
|
throw error;
|
|
155
161
|
}
|