@bytebase/dbhub 1.3.1 → 1.5.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/dist/{chunk-ABTVMHD6.js → chunk-55X2VY5D.js} +1 -1
- package/dist/chunk-5XSBOK3W.js +63 -0
- package/dist/{chunk-5FL4XMWG.js → chunk-PDKPK25P.js} +356 -106
- package/dist/chunk-SJ5BRPQA.js +500 -0
- package/dist/{chunk-YIGJHEU6.js → chunk-X5OCFRNL.js} +170 -27
- package/dist/index.js +116 -32
- package/dist/mariadb-RWOMZTA6.js +270 -0
- package/dist/mysql-ZXO4TWLG.js +312 -0
- package/dist/{oracle-OUKBU52X.js → oracle-UAVN3F4U.js} +2 -2
- package/dist/{postgres-6XJ7V4J4.js → postgres-KCFNDQLG.js} +179 -29
- package/dist/{registry-ZTHDPLKP.js → registry-ZIVIYCB2.js} +2 -1
- package/dist/{sqlite-AO2BKIC6.js → sqlite-WKY6543Q.js} +2 -2
- package/dist/{sqlserver-AJUK6L55.js → sqlserver-25MAE77Q.js} +312 -372
- package/package.json +1 -1
- package/dist/chunk-6SZKW6GD.js +0 -179
- package/dist/mariadb-L7Z55ROY.js +0 -551
- package/dist/mysql-XXIHWQGR.js +0 -601
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MissingDatabaseError,
|
|
3
|
+
getDefaultSchema,
|
|
4
|
+
getMySQLFamilyHealthCheck,
|
|
5
|
+
getSchemas,
|
|
6
|
+
getStoredProcedureDetail,
|
|
7
|
+
getStoredProcedures,
|
|
8
|
+
getTableComment,
|
|
9
|
+
getTableIndexes,
|
|
10
|
+
getTableSchema,
|
|
11
|
+
getTables,
|
|
12
|
+
getViews,
|
|
13
|
+
isTiDBVersion,
|
|
14
|
+
parseQueryResultSets,
|
|
15
|
+
requireDatabaseInDSN,
|
|
16
|
+
tableExists,
|
|
17
|
+
withReadOnlyTransaction
|
|
18
|
+
} from "./chunk-SJ5BRPQA.js";
|
|
19
|
+
import {
|
|
20
|
+
clientQueryTimeoutMs,
|
|
21
|
+
isClientSideTimeout,
|
|
22
|
+
withClientQueryDeadline
|
|
23
|
+
} from "./chunk-5XSBOK3W.js";
|
|
24
|
+
import "./chunk-FU2ZJE4E.js";
|
|
25
|
+
import "./chunk-X2EC47W4.js";
|
|
26
|
+
import {
|
|
27
|
+
SQLRowLimiter,
|
|
28
|
+
closeQuietly
|
|
29
|
+
} from "./chunk-X5OCFRNL.js";
|
|
30
|
+
import "./chunk-55X2VY5D.js";
|
|
31
|
+
import {
|
|
32
|
+
ConnectorRegistry,
|
|
33
|
+
SafeURL,
|
|
34
|
+
obfuscateDSNPassword,
|
|
35
|
+
splitSQLStatements
|
|
36
|
+
} from "./chunk-IXC4NQGC.js";
|
|
37
|
+
|
|
38
|
+
// src/connectors/mariadb/index.ts
|
|
39
|
+
import * as mariadb from "mariadb";
|
|
40
|
+
var MariadbDSNParser = class {
|
|
41
|
+
async parse(dsn, config) {
|
|
42
|
+
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
43
|
+
const queryTimeoutSeconds = config?.queryTimeoutSeconds;
|
|
44
|
+
if (!this.isValidDSN(dsn)) {
|
|
45
|
+
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
46
|
+
const expectedFormat = this.getSampleDSN();
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Invalid MariaDB DSN format.
|
|
49
|
+
Provided: ${obfuscatedDSN}
|
|
50
|
+
Expected: ${expectedFormat}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const url = new SafeURL(dsn);
|
|
55
|
+
const database = url.pathname ? url.pathname.substring(1) : "";
|
|
56
|
+
requireDatabaseInDSN(database, dsn, "MariaDB");
|
|
57
|
+
const connectionConfig = {
|
|
58
|
+
host: url.hostname,
|
|
59
|
+
port: url.port ? parseInt(url.port) : 3306,
|
|
60
|
+
database,
|
|
61
|
+
user: url.username,
|
|
62
|
+
password: url.password,
|
|
63
|
+
multipleStatements: true,
|
|
64
|
+
// Enable native multi-statement support
|
|
65
|
+
...connectionTimeoutSeconds !== void 0 && {
|
|
66
|
+
connectTimeout: connectionTimeoutSeconds * 1e3
|
|
67
|
+
},
|
|
68
|
+
// Server-side limit: the driver applies `queryTimeout` on each new
|
|
69
|
+
// connection as the session variable max_statement_time.
|
|
70
|
+
...queryTimeoutSeconds !== void 0 && {
|
|
71
|
+
queryTimeout: queryTimeoutSeconds * 1e3
|
|
72
|
+
},
|
|
73
|
+
// Controls how the driver interprets DATETIME values ("Z", "local", or "±HH:MM").
|
|
74
|
+
...config?.timezone !== void 0 && {
|
|
75
|
+
timezone: config.timezone
|
|
76
|
+
},
|
|
77
|
+
// Connection character set (e.g. "utf8mb4") / collation (e.g.
|
|
78
|
+
// "utf8mb4_general_ci"). The mariadb driver exposes both as distinct
|
|
79
|
+
// options, but a configured collation is authoritative (it implies its
|
|
80
|
+
// character set) and the driver ignores `collation` when `charset` is also
|
|
81
|
+
// passed. So forward the collation when present, otherwise the charset
|
|
82
|
+
// (which uses that character set's default collation).
|
|
83
|
+
...config?.collation !== void 0 ? { collation: config.collation } : config?.charset !== void 0 ? { charset: config.charset } : {}
|
|
84
|
+
};
|
|
85
|
+
url.forEachSearchParam((value, key) => {
|
|
86
|
+
if (key === "sslmode") {
|
|
87
|
+
if (value === "disable") {
|
|
88
|
+
connectionConfig.ssl = void 0;
|
|
89
|
+
} else if (value === "require") {
|
|
90
|
+
connectionConfig.ssl = { rejectUnauthorized: false };
|
|
91
|
+
} else {
|
|
92
|
+
connectionConfig.ssl = {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (url.password && url.password.includes("X-Amz-Credential")) {
|
|
97
|
+
if (connectionConfig.ssl === void 0) {
|
|
98
|
+
connectionConfig.ssl = { rejectUnauthorized: false };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return connectionConfig;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof MissingDatabaseError) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
getSampleDSN() {
|
|
112
|
+
return "mariadb://root:password@localhost:3306/db?sslmode=require";
|
|
113
|
+
}
|
|
114
|
+
isValidDSN(dsn) {
|
|
115
|
+
try {
|
|
116
|
+
return dsn.startsWith("mariadb://");
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var MariaDBConnector = class _MariaDBConnector {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.id = "mariadb";
|
|
125
|
+
this.name = "MariaDB";
|
|
126
|
+
this.dsnParser = new MariadbDSNParser();
|
|
127
|
+
this.pool = null;
|
|
128
|
+
// Source ID is set by ConnectorManager after cloning
|
|
129
|
+
this.sourceId = "default";
|
|
130
|
+
// TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
|
|
131
|
+
// unless tidb_enable_noop_functions is on. Detected once at connect time.
|
|
132
|
+
this.supportsReadOnlyTransaction = true;
|
|
133
|
+
}
|
|
134
|
+
getId() {
|
|
135
|
+
return this.sourceId;
|
|
136
|
+
}
|
|
137
|
+
clone() {
|
|
138
|
+
return new _MariaDBConnector();
|
|
139
|
+
}
|
|
140
|
+
async connect(dsn, initScript, config) {
|
|
141
|
+
try {
|
|
142
|
+
const connectionConfig = await this.dsnParser.parse(dsn, config);
|
|
143
|
+
this.clientQueryTimeoutMs = config?.queryTimeoutSeconds !== void 0 ? clientQueryTimeoutMs(config.queryTimeoutSeconds * 1e3) : void 0;
|
|
144
|
+
this.pool = mariadb.createPool(connectionConfig);
|
|
145
|
+
this.pool.on("error", (err) => {
|
|
146
|
+
console.error(
|
|
147
|
+
`MariaDB pool (source "${this.sourceId}"): background connection error, pool will retry:`,
|
|
148
|
+
err.message
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
const rows = await this.pool.query("SELECT VERSION() AS version");
|
|
152
|
+
this.supportsReadOnlyTransaction = !isTiDBVersion(rows?.[0]?.version);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (this.pool) {
|
|
155
|
+
const pool = this.pool;
|
|
156
|
+
this.pool = null;
|
|
157
|
+
await closeQuietly(() => pool.end());
|
|
158
|
+
}
|
|
159
|
+
console.error("Failed to connect to MariaDB database:", err);
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async disconnect() {
|
|
164
|
+
if (this.pool) {
|
|
165
|
+
await this.pool.end();
|
|
166
|
+
this.pool = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Row-array query adapter over the pool for the shared MySQL-family
|
|
171
|
+
* introspection and health-check code. Throws when not connected, so the
|
|
172
|
+
* shared functions never see a missing pool. The mariadb driver already
|
|
173
|
+
* resolves to the rows themselves.
|
|
174
|
+
*/
|
|
175
|
+
requireQuery() {
|
|
176
|
+
if (!this.pool) {
|
|
177
|
+
throw new Error("Not connected to database");
|
|
178
|
+
}
|
|
179
|
+
const pool = this.pool;
|
|
180
|
+
return (sql, params) => params === void 0 ? pool.query(sql) : pool.query(sql, params);
|
|
181
|
+
}
|
|
182
|
+
async getSchemas() {
|
|
183
|
+
return getSchemas(this.requireQuery());
|
|
184
|
+
}
|
|
185
|
+
async getTables(schema) {
|
|
186
|
+
return getTables(this.requireQuery(), schema);
|
|
187
|
+
}
|
|
188
|
+
async getViews(schema) {
|
|
189
|
+
return getViews(this.requireQuery(), schema);
|
|
190
|
+
}
|
|
191
|
+
async tableExists(tableName, schema) {
|
|
192
|
+
return tableExists(this.requireQuery(), tableName, schema);
|
|
193
|
+
}
|
|
194
|
+
async getTableIndexes(tableName, schema) {
|
|
195
|
+
return getTableIndexes(this.requireQuery(), tableName, schema);
|
|
196
|
+
}
|
|
197
|
+
async getTableSchema(tableName, schema) {
|
|
198
|
+
return getTableSchema(this.requireQuery(), tableName, schema);
|
|
199
|
+
}
|
|
200
|
+
async getTableComment(tableName, schema) {
|
|
201
|
+
return getTableComment(this.requireQuery(), tableName, schema);
|
|
202
|
+
}
|
|
203
|
+
async getHealthCheck() {
|
|
204
|
+
return getMySQLFamilyHealthCheck(this.requireQuery());
|
|
205
|
+
}
|
|
206
|
+
async getStoredProcedures(schema, routineType) {
|
|
207
|
+
return getStoredProcedures(this.requireQuery(), schema, routineType);
|
|
208
|
+
}
|
|
209
|
+
async getStoredProcedureDetail(procedureName, schema) {
|
|
210
|
+
return getStoredProcedureDetail(this.requireQuery(), procedureName, schema);
|
|
211
|
+
}
|
|
212
|
+
async getDefaultSchema() {
|
|
213
|
+
return getDefaultSchema(this.requireQuery());
|
|
214
|
+
}
|
|
215
|
+
async executeSQL(sql, options, parameters) {
|
|
216
|
+
if (!this.pool) {
|
|
217
|
+
throw new Error("Not connected to database");
|
|
218
|
+
}
|
|
219
|
+
const conn = await this.pool.getConnection();
|
|
220
|
+
let isConnectionDiscarded = false;
|
|
221
|
+
try {
|
|
222
|
+
return await withReadOnlyTransaction(
|
|
223
|
+
conn,
|
|
224
|
+
options.readonly,
|
|
225
|
+
this.supportsReadOnlyTransaction,
|
|
226
|
+
async () => {
|
|
227
|
+
const statements = splitSQLStatements(sql, "mariadb");
|
|
228
|
+
let processedSQL = sql;
|
|
229
|
+
let probes = [];
|
|
230
|
+
if (options.maxRows) {
|
|
231
|
+
const rewrites = statements.map(
|
|
232
|
+
(statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows, "mariadb")
|
|
233
|
+
);
|
|
234
|
+
probes = rewrites.map((rewrite) => rewrite.probeApplied);
|
|
235
|
+
processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
|
|
236
|
+
if (sql.trim().endsWith(";")) {
|
|
237
|
+
processedSQL += ";";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const results = await withClientQueryDeadline(
|
|
241
|
+
parameters && parameters.length > 0 ? conn.query(processedSQL, parameters) : conn.query(processedSQL),
|
|
242
|
+
this.clientQueryTimeoutMs
|
|
243
|
+
);
|
|
244
|
+
const resultSets = parseQueryResultSets(results, statements);
|
|
245
|
+
if (resultSets.length === probes.length) {
|
|
246
|
+
resultSets.forEach(
|
|
247
|
+
(set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return { resultSets };
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (isClientSideTimeout(error)) {
|
|
255
|
+
isConnectionDiscarded = true;
|
|
256
|
+
conn.destroy();
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
} finally {
|
|
260
|
+
if (!isConnectionDiscarded) {
|
|
261
|
+
conn.release();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
var mariadbConnector = new MariaDBConnector();
|
|
267
|
+
ConnectorRegistry.register(mariadbConnector);
|
|
268
|
+
export {
|
|
269
|
+
MariaDBConnector
|
|
270
|
+
};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MissingDatabaseError,
|
|
3
|
+
getDefaultSchema,
|
|
4
|
+
getMySQLFamilyHealthCheck,
|
|
5
|
+
getSchemas,
|
|
6
|
+
getStoredProcedureDetail,
|
|
7
|
+
getStoredProcedures,
|
|
8
|
+
getTableComment,
|
|
9
|
+
getTableIndexes,
|
|
10
|
+
getTableSchema,
|
|
11
|
+
getTables,
|
|
12
|
+
getViews,
|
|
13
|
+
isTiDBVersion,
|
|
14
|
+
parseQueryResultSets,
|
|
15
|
+
requireDatabaseInDSN,
|
|
16
|
+
tableExists,
|
|
17
|
+
withReadOnlyTransaction
|
|
18
|
+
} from "./chunk-SJ5BRPQA.js";
|
|
19
|
+
import {
|
|
20
|
+
CANCEL_QUERY_TIMEOUT_MS,
|
|
21
|
+
clientQueryTimeoutMs,
|
|
22
|
+
isClientSideTimeout
|
|
23
|
+
} from "./chunk-5XSBOK3W.js";
|
|
24
|
+
import "./chunk-FU2ZJE4E.js";
|
|
25
|
+
import "./chunk-X2EC47W4.js";
|
|
26
|
+
import {
|
|
27
|
+
SQLRowLimiter,
|
|
28
|
+
closeQuietly
|
|
29
|
+
} from "./chunk-X5OCFRNL.js";
|
|
30
|
+
import "./chunk-55X2VY5D.js";
|
|
31
|
+
import {
|
|
32
|
+
ConnectorRegistry,
|
|
33
|
+
SafeURL,
|
|
34
|
+
obfuscateDSNPassword,
|
|
35
|
+
splitSQLStatements
|
|
36
|
+
} from "./chunk-IXC4NQGC.js";
|
|
37
|
+
|
|
38
|
+
// src/connectors/mysql/index.ts
|
|
39
|
+
import mysql from "mysql2/promise";
|
|
40
|
+
var MySQLDSNParser = class {
|
|
41
|
+
async parse(dsn, config) {
|
|
42
|
+
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
43
|
+
const timezone = config?.timezone;
|
|
44
|
+
const charset = config?.charset;
|
|
45
|
+
const collation = config?.collation;
|
|
46
|
+
if (!this.isValidDSN(dsn)) {
|
|
47
|
+
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
48
|
+
const expectedFormat = this.getSampleDSN();
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Invalid MySQL DSN format.
|
|
51
|
+
Provided: ${obfuscatedDSN}
|
|
52
|
+
Expected: ${expectedFormat}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const url = new SafeURL(dsn);
|
|
57
|
+
const database = url.pathname ? url.pathname.substring(1) : "";
|
|
58
|
+
requireDatabaseInDSN(database, dsn, "MySQL");
|
|
59
|
+
const config2 = {
|
|
60
|
+
host: url.hostname,
|
|
61
|
+
port: url.port ? parseInt(url.port) : 3306,
|
|
62
|
+
database,
|
|
63
|
+
user: url.username,
|
|
64
|
+
password: url.password,
|
|
65
|
+
multipleStatements: true,
|
|
66
|
+
// Enable native multi-statement support
|
|
67
|
+
supportBigNumbers: true
|
|
68
|
+
// Return BIGINT as string when value exceeds Number.MAX_SAFE_INTEGER
|
|
69
|
+
};
|
|
70
|
+
url.forEachSearchParam((value, key) => {
|
|
71
|
+
if (key === "sslmode") {
|
|
72
|
+
if (value === "disable") {
|
|
73
|
+
config2.ssl = void 0;
|
|
74
|
+
} else if (value === "require") {
|
|
75
|
+
config2.ssl = { rejectUnauthorized: false };
|
|
76
|
+
} else {
|
|
77
|
+
config2.ssl = {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (connectionTimeoutSeconds !== void 0) {
|
|
82
|
+
config2.connectTimeout = connectionTimeoutSeconds * 1e3;
|
|
83
|
+
}
|
|
84
|
+
if (timezone !== void 0) {
|
|
85
|
+
config2.timezone = timezone;
|
|
86
|
+
}
|
|
87
|
+
const charsetOrCollation = collation ?? charset;
|
|
88
|
+
if (charsetOrCollation !== void 0) {
|
|
89
|
+
config2.charset = charsetOrCollation;
|
|
90
|
+
}
|
|
91
|
+
if (url.password && url.password.includes("X-Amz-Credential")) {
|
|
92
|
+
config2.authPlugins = {
|
|
93
|
+
mysql_clear_password: () => () => {
|
|
94
|
+
return Buffer.from(url.password + "\0");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
if (config2.ssl === void 0) {
|
|
98
|
+
config2.ssl = { rejectUnauthorized: false };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return config2;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof MissingDatabaseError) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Failed to parse MySQL DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
getSampleDSN() {
|
|
112
|
+
return "mysql://root:password@localhost:3306/mysql?sslmode=require";
|
|
113
|
+
}
|
|
114
|
+
isValidDSN(dsn) {
|
|
115
|
+
try {
|
|
116
|
+
return dsn.startsWith("mysql://");
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var MySQLConnector = class _MySQLConnector {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.id = "mysql";
|
|
125
|
+
this.name = "MySQL";
|
|
126
|
+
this.dsnParser = new MySQLDSNParser();
|
|
127
|
+
this.pool = null;
|
|
128
|
+
// Source ID is set by ConnectorManager after cloning
|
|
129
|
+
this.sourceId = "default";
|
|
130
|
+
// TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
|
|
131
|
+
// unless tidb_enable_noop_functions is on. Detected once at connect time.
|
|
132
|
+
this.supportsReadOnlyTransaction = true;
|
|
133
|
+
}
|
|
134
|
+
getId() {
|
|
135
|
+
return this.sourceId;
|
|
136
|
+
}
|
|
137
|
+
clone() {
|
|
138
|
+
return new _MySQLConnector();
|
|
139
|
+
}
|
|
140
|
+
async connect(dsn, initScript, config) {
|
|
141
|
+
try {
|
|
142
|
+
const connectionOptions = await this.dsnParser.parse(dsn, config);
|
|
143
|
+
this.pool = mysql.createPool(connectionOptions);
|
|
144
|
+
if (config?.queryTimeoutSeconds !== void 0) {
|
|
145
|
+
const queryTimeoutMs = Math.ceil(config.queryTimeoutSeconds * 1e3);
|
|
146
|
+
this.queryTimeoutMs = queryTimeoutMs;
|
|
147
|
+
const setServerLimit = `SET SESSION max_execution_time = ${queryTimeoutMs}`;
|
|
148
|
+
this.pool.on(
|
|
149
|
+
"connection",
|
|
150
|
+
(connection) => {
|
|
151
|
+
connection.query(setServerLimit, () => {
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const [rows] = await this.pool.query("SELECT VERSION() AS version");
|
|
157
|
+
this.supportsReadOnlyTransaction = !isTiDBVersion(rows[0]?.version);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (this.pool) {
|
|
160
|
+
const pool = this.pool;
|
|
161
|
+
this.pool = null;
|
|
162
|
+
await closeQuietly(() => pool.end());
|
|
163
|
+
}
|
|
164
|
+
console.error("Failed to connect to MySQL database:", err);
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async disconnect() {
|
|
169
|
+
if (this.pool) {
|
|
170
|
+
await this.pool.end();
|
|
171
|
+
this.pool = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Row-array query adapter over the pool for the shared MySQL-family
|
|
176
|
+
* introspection and health-check code. Throws when not connected, so the
|
|
177
|
+
* shared functions never see a missing pool. mysql2 resolves to
|
|
178
|
+
* [rows, fields]; only the rows are handed on.
|
|
179
|
+
*/
|
|
180
|
+
requireQuery() {
|
|
181
|
+
if (!this.pool) {
|
|
182
|
+
throw new Error("Not connected to database");
|
|
183
|
+
}
|
|
184
|
+
const pool = this.pool;
|
|
185
|
+
return async (sql, params) => {
|
|
186
|
+
const [rows] = await (params === void 0 ? pool.query(sql) : pool.query(sql, params));
|
|
187
|
+
return rows;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async getSchemas() {
|
|
191
|
+
return getSchemas(this.requireQuery());
|
|
192
|
+
}
|
|
193
|
+
async getTables(schema) {
|
|
194
|
+
return getTables(this.requireQuery(), schema);
|
|
195
|
+
}
|
|
196
|
+
async getViews(schema) {
|
|
197
|
+
return getViews(this.requireQuery(), schema);
|
|
198
|
+
}
|
|
199
|
+
async tableExists(tableName, schema) {
|
|
200
|
+
return tableExists(this.requireQuery(), tableName, schema);
|
|
201
|
+
}
|
|
202
|
+
async getTableIndexes(tableName, schema) {
|
|
203
|
+
return getTableIndexes(this.requireQuery(), tableName, schema);
|
|
204
|
+
}
|
|
205
|
+
async getTableSchema(tableName, schema) {
|
|
206
|
+
return getTableSchema(this.requireQuery(), tableName, schema);
|
|
207
|
+
}
|
|
208
|
+
async getTableComment(tableName, schema) {
|
|
209
|
+
return getTableComment(this.requireQuery(), tableName, schema);
|
|
210
|
+
}
|
|
211
|
+
async getHealthCheck() {
|
|
212
|
+
return getMySQLFamilyHealthCheck(this.requireQuery());
|
|
213
|
+
}
|
|
214
|
+
async getStoredProcedures(schema, routineType) {
|
|
215
|
+
return getStoredProcedures(this.requireQuery(), schema, routineType);
|
|
216
|
+
}
|
|
217
|
+
async getStoredProcedureDetail(procedureName, schema) {
|
|
218
|
+
return getStoredProcedureDetail(this.requireQuery(), procedureName, schema);
|
|
219
|
+
}
|
|
220
|
+
async getDefaultSchema() {
|
|
221
|
+
return getDefaultSchema(this.requireQuery());
|
|
222
|
+
}
|
|
223
|
+
async executeSQL(sql, options, parameters) {
|
|
224
|
+
if (!this.pool) {
|
|
225
|
+
throw new Error("Not connected to database");
|
|
226
|
+
}
|
|
227
|
+
const conn = await this.pool.getConnection();
|
|
228
|
+
const threadId = conn.threadId;
|
|
229
|
+
let isConnectionDiscarded = false;
|
|
230
|
+
const timeout = this.queryTimeoutMs !== void 0 ? clientQueryTimeoutMs(this.queryTimeoutMs) : void 0;
|
|
231
|
+
try {
|
|
232
|
+
return await withReadOnlyTransaction(
|
|
233
|
+
conn,
|
|
234
|
+
options.readonly,
|
|
235
|
+
this.supportsReadOnlyTransaction,
|
|
236
|
+
async () => {
|
|
237
|
+
const statements = splitSQLStatements(sql, "mysql");
|
|
238
|
+
let processedSQL = sql;
|
|
239
|
+
let probes = [];
|
|
240
|
+
if (options.maxRows) {
|
|
241
|
+
const rewrites = statements.map(
|
|
242
|
+
(statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows, "mysql")
|
|
243
|
+
);
|
|
244
|
+
probes = rewrites.map((rewrite) => rewrite.probeApplied);
|
|
245
|
+
processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
|
|
246
|
+
if (sql.trim().endsWith(";")) {
|
|
247
|
+
processedSQL += ";";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let results;
|
|
251
|
+
if (parameters && parameters.length > 0) {
|
|
252
|
+
results = await conn.query({ sql: processedSQL, timeout }, parameters);
|
|
253
|
+
} else {
|
|
254
|
+
results = await conn.query({ sql: processedSQL, timeout });
|
|
255
|
+
}
|
|
256
|
+
const [firstResult] = results;
|
|
257
|
+
const resultSets = parseQueryResultSets(firstResult, statements);
|
|
258
|
+
if (resultSets.length === probes.length) {
|
|
259
|
+
resultSets.forEach(
|
|
260
|
+
(set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return { resultSets };
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (isClientSideTimeout(error)) {
|
|
268
|
+
isConnectionDiscarded = true;
|
|
269
|
+
conn.destroy();
|
|
270
|
+
await this.killQuery(threadId);
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
} finally {
|
|
274
|
+
if (!isConnectionDiscarded) {
|
|
275
|
+
conn.release();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Best-effort server-side kill for a query abandoned by mysql2's client-side
|
|
281
|
+
* timeout. Uses a separate connection: the original connection's command
|
|
282
|
+
* queue is stuck behind the abandoned statement (see isClientSideTimeout)
|
|
283
|
+
* and cannot itself be used to send KILL QUERY.
|
|
284
|
+
*
|
|
285
|
+
* Bounded by its own short timeout (CANCEL_QUERY_TIMEOUT_MS), independent
|
|
286
|
+
* of the user's query_timeout.
|
|
287
|
+
*/
|
|
288
|
+
async killQuery(threadId) {
|
|
289
|
+
if (!this.pool) return;
|
|
290
|
+
let killer;
|
|
291
|
+
let killerPoisoned = false;
|
|
292
|
+
try {
|
|
293
|
+
killer = await this.pool.getConnection();
|
|
294
|
+
await killer.query({ sql: `KILL QUERY ${threadId}`, timeout: CANCEL_QUERY_TIMEOUT_MS });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
killerPoisoned = isClientSideTimeout(error);
|
|
297
|
+
} finally {
|
|
298
|
+
if (killer) {
|
|
299
|
+
if (killerPoisoned) {
|
|
300
|
+
killer.destroy();
|
|
301
|
+
} else {
|
|
302
|
+
killer.release();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var mysqlConnector = new MySQLConnector();
|
|
309
|
+
ConnectorRegistry.register(mysqlConnector);
|
|
310
|
+
export {
|
|
311
|
+
MySQLConnector
|
|
312
|
+
};
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
SQLRowLimiter,
|
|
7
7
|
closeQuietly
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X5OCFRNL.js";
|
|
9
9
|
import {
|
|
10
10
|
isReadOnlySQL
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-55X2VY5D.js";
|
|
12
12
|
import {
|
|
13
13
|
ConnectorRegistry,
|
|
14
14
|
LEADING_SQL_NOISE,
|