@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
package/dist/mysql-XXIHWQGR.js
DELETED
|
@@ -1,601 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MissingDatabaseError,
|
|
3
|
-
getMySQLFamilyHealthCheck,
|
|
4
|
-
isClientSideTimeout,
|
|
5
|
-
isTiDBVersion,
|
|
6
|
-
parseQueryResultSets,
|
|
7
|
-
requireDatabaseInDSN,
|
|
8
|
-
withReadOnlyTransaction
|
|
9
|
-
} from "./chunk-6SZKW6GD.js";
|
|
10
|
-
import "./chunk-FU2ZJE4E.js";
|
|
11
|
-
import {
|
|
12
|
-
quoteIdentifier
|
|
13
|
-
} from "./chunk-X2EC47W4.js";
|
|
14
|
-
import {
|
|
15
|
-
SQLRowLimiter,
|
|
16
|
-
closeQuietly
|
|
17
|
-
} from "./chunk-YIGJHEU6.js";
|
|
18
|
-
import "./chunk-ABTVMHD6.js";
|
|
19
|
-
import {
|
|
20
|
-
ConnectorRegistry,
|
|
21
|
-
SafeURL,
|
|
22
|
-
obfuscateDSNPassword,
|
|
23
|
-
splitSQLStatements
|
|
24
|
-
} from "./chunk-IXC4NQGC.js";
|
|
25
|
-
|
|
26
|
-
// src/connectors/mysql/index.ts
|
|
27
|
-
import mysql from "mysql2/promise";
|
|
28
|
-
var MySQLDSNParser = class {
|
|
29
|
-
async parse(dsn, config) {
|
|
30
|
-
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
31
|
-
const timezone = config?.timezone;
|
|
32
|
-
const charset = config?.charset;
|
|
33
|
-
const collation = config?.collation;
|
|
34
|
-
if (!this.isValidDSN(dsn)) {
|
|
35
|
-
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
36
|
-
const expectedFormat = this.getSampleDSN();
|
|
37
|
-
throw new Error(
|
|
38
|
-
`Invalid MySQL DSN format.
|
|
39
|
-
Provided: ${obfuscatedDSN}
|
|
40
|
-
Expected: ${expectedFormat}`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
try {
|
|
44
|
-
const url = new SafeURL(dsn);
|
|
45
|
-
const database = url.pathname ? url.pathname.substring(1) : "";
|
|
46
|
-
requireDatabaseInDSN(database, dsn, "MySQL");
|
|
47
|
-
const config2 = {
|
|
48
|
-
host: url.hostname,
|
|
49
|
-
port: url.port ? parseInt(url.port) : 3306,
|
|
50
|
-
database,
|
|
51
|
-
user: url.username,
|
|
52
|
-
password: url.password,
|
|
53
|
-
multipleStatements: true,
|
|
54
|
-
// Enable native multi-statement support
|
|
55
|
-
supportBigNumbers: true
|
|
56
|
-
// Return BIGINT as string when value exceeds Number.MAX_SAFE_INTEGER
|
|
57
|
-
};
|
|
58
|
-
url.forEachSearchParam((value, key) => {
|
|
59
|
-
if (key === "sslmode") {
|
|
60
|
-
if (value === "disable") {
|
|
61
|
-
config2.ssl = void 0;
|
|
62
|
-
} else if (value === "require") {
|
|
63
|
-
config2.ssl = { rejectUnauthorized: false };
|
|
64
|
-
} else {
|
|
65
|
-
config2.ssl = {};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
if (connectionTimeoutSeconds !== void 0) {
|
|
70
|
-
config2.connectTimeout = connectionTimeoutSeconds * 1e3;
|
|
71
|
-
}
|
|
72
|
-
if (timezone !== void 0) {
|
|
73
|
-
config2.timezone = timezone;
|
|
74
|
-
}
|
|
75
|
-
const charsetOrCollation = collation ?? charset;
|
|
76
|
-
if (charsetOrCollation !== void 0) {
|
|
77
|
-
config2.charset = charsetOrCollation;
|
|
78
|
-
}
|
|
79
|
-
if (url.password && url.password.includes("X-Amz-Credential")) {
|
|
80
|
-
config2.authPlugins = {
|
|
81
|
-
mysql_clear_password: () => () => {
|
|
82
|
-
return Buffer.from(url.password + "\0");
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
if (config2.ssl === void 0) {
|
|
86
|
-
config2.ssl = { rejectUnauthorized: false };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return config2;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
if (error instanceof MissingDatabaseError) {
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
throw new Error(
|
|
95
|
-
`Failed to parse MySQL DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
getSampleDSN() {
|
|
100
|
-
return "mysql://root:password@localhost:3306/mysql?sslmode=require";
|
|
101
|
-
}
|
|
102
|
-
isValidDSN(dsn) {
|
|
103
|
-
try {
|
|
104
|
-
return dsn.startsWith("mysql://");
|
|
105
|
-
} catch (error) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
var _MySQLConnector = class _MySQLConnector {
|
|
111
|
-
constructor() {
|
|
112
|
-
this.id = "mysql";
|
|
113
|
-
this.name = "MySQL";
|
|
114
|
-
this.dsnParser = new MySQLDSNParser();
|
|
115
|
-
this.pool = null;
|
|
116
|
-
// Source ID is set by ConnectorManager after cloning
|
|
117
|
-
this.sourceId = "default";
|
|
118
|
-
// TiDB speaks the MySQL protocol but rejects `START TRANSACTION READ ONLY`
|
|
119
|
-
// unless tidb_enable_noop_functions is on. Detected once at connect time.
|
|
120
|
-
this.supportsReadOnlyTransaction = true;
|
|
121
|
-
}
|
|
122
|
-
getId() {
|
|
123
|
-
return this.sourceId;
|
|
124
|
-
}
|
|
125
|
-
clone() {
|
|
126
|
-
return new _MySQLConnector();
|
|
127
|
-
}
|
|
128
|
-
async connect(dsn, initScript, config) {
|
|
129
|
-
try {
|
|
130
|
-
const connectionOptions = await this.dsnParser.parse(dsn, config);
|
|
131
|
-
this.pool = mysql.createPool(connectionOptions);
|
|
132
|
-
if (config?.queryTimeoutSeconds !== void 0) {
|
|
133
|
-
this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
|
|
134
|
-
}
|
|
135
|
-
const [rows] = await this.pool.query("SELECT VERSION() AS version");
|
|
136
|
-
this.supportsReadOnlyTransaction = !isTiDBVersion(rows[0]?.version);
|
|
137
|
-
} catch (err) {
|
|
138
|
-
if (this.pool) {
|
|
139
|
-
const pool = this.pool;
|
|
140
|
-
this.pool = null;
|
|
141
|
-
await closeQuietly(() => pool.end());
|
|
142
|
-
}
|
|
143
|
-
console.error("Failed to connect to MySQL database:", err);
|
|
144
|
-
throw err;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async disconnect() {
|
|
148
|
-
if (this.pool) {
|
|
149
|
-
await this.pool.end();
|
|
150
|
-
this.pool = null;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
async getSchemas() {
|
|
154
|
-
if (!this.pool) {
|
|
155
|
-
throw new Error("Not connected to database");
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
const [rows] = await this.pool.query(`
|
|
159
|
-
SELECT SCHEMA_NAME
|
|
160
|
-
FROM INFORMATION_SCHEMA.SCHEMATA
|
|
161
|
-
WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
|
|
162
|
-
ORDER BY SCHEMA_NAME
|
|
163
|
-
`);
|
|
164
|
-
return rows.map((row) => row.SCHEMA_NAME);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
console.error("Error getting schemas:", error);
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
async getTables(schema) {
|
|
171
|
-
if (!this.pool) {
|
|
172
|
-
throw new Error("Not connected to database");
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
176
|
-
const queryParams = schema ? [schema] : [];
|
|
177
|
-
const [rows] = await this.pool.query(
|
|
178
|
-
`
|
|
179
|
-
SELECT TABLE_NAME
|
|
180
|
-
FROM INFORMATION_SCHEMA.TABLES
|
|
181
|
-
${schemaClause}
|
|
182
|
-
AND TABLE_TYPE = 'BASE TABLE'
|
|
183
|
-
ORDER BY TABLE_NAME
|
|
184
|
-
`,
|
|
185
|
-
queryParams
|
|
186
|
-
);
|
|
187
|
-
return rows.map((row) => row.TABLE_NAME);
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error("Error getting tables:", error);
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async getViews(schema) {
|
|
194
|
-
if (!this.pool) {
|
|
195
|
-
throw new Error("Not connected to database");
|
|
196
|
-
}
|
|
197
|
-
try {
|
|
198
|
-
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
199
|
-
const queryParams = schema ? [schema] : [];
|
|
200
|
-
const [rows] = await this.pool.query(
|
|
201
|
-
`
|
|
202
|
-
SELECT TABLE_NAME
|
|
203
|
-
FROM INFORMATION_SCHEMA.TABLES
|
|
204
|
-
${schemaClause}
|
|
205
|
-
AND TABLE_TYPE = 'VIEW'
|
|
206
|
-
ORDER BY TABLE_NAME
|
|
207
|
-
`,
|
|
208
|
-
queryParams
|
|
209
|
-
);
|
|
210
|
-
return rows.map((row) => row.TABLE_NAME);
|
|
211
|
-
} catch (error) {
|
|
212
|
-
console.error("Error getting views:", error);
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
async tableExists(tableName, schema) {
|
|
217
|
-
if (!this.pool) {
|
|
218
|
-
throw new Error("Not connected to database");
|
|
219
|
-
}
|
|
220
|
-
try {
|
|
221
|
-
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
222
|
-
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
223
|
-
const [rows] = await this.pool.query(
|
|
224
|
-
`
|
|
225
|
-
SELECT COUNT(*) AS COUNT
|
|
226
|
-
FROM INFORMATION_SCHEMA.TABLES
|
|
227
|
-
${schemaClause}
|
|
228
|
-
AND TABLE_NAME = ?
|
|
229
|
-
`,
|
|
230
|
-
queryParams
|
|
231
|
-
);
|
|
232
|
-
return rows[0].COUNT > 0;
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error("Error checking if table exists:", error);
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async getTableIndexes(tableName, schema) {
|
|
239
|
-
if (!this.pool) {
|
|
240
|
-
throw new Error("Not connected to database");
|
|
241
|
-
}
|
|
242
|
-
try {
|
|
243
|
-
const schemaClause = schema ? "TABLE_SCHEMA = ?" : "TABLE_SCHEMA = DATABASE()";
|
|
244
|
-
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
245
|
-
const [indexRows] = await this.pool.query(
|
|
246
|
-
`
|
|
247
|
-
SELECT
|
|
248
|
-
INDEX_NAME,
|
|
249
|
-
COLUMN_NAME,
|
|
250
|
-
NON_UNIQUE,
|
|
251
|
-
SEQ_IN_INDEX
|
|
252
|
-
FROM
|
|
253
|
-
INFORMATION_SCHEMA.STATISTICS
|
|
254
|
-
WHERE
|
|
255
|
-
${schemaClause}
|
|
256
|
-
AND TABLE_NAME = ?
|
|
257
|
-
ORDER BY
|
|
258
|
-
INDEX_NAME,
|
|
259
|
-
SEQ_IN_INDEX
|
|
260
|
-
`,
|
|
261
|
-
queryParams
|
|
262
|
-
);
|
|
263
|
-
const indexMap = /* @__PURE__ */ new Map();
|
|
264
|
-
for (const row of indexRows) {
|
|
265
|
-
const indexName = row.INDEX_NAME;
|
|
266
|
-
const columnName = row.COLUMN_NAME;
|
|
267
|
-
const isUnique = row.NON_UNIQUE === 0;
|
|
268
|
-
const isPrimary = indexName === "PRIMARY";
|
|
269
|
-
if (!indexMap.has(indexName)) {
|
|
270
|
-
indexMap.set(indexName, {
|
|
271
|
-
columns: [],
|
|
272
|
-
is_unique: isUnique,
|
|
273
|
-
is_primary: isPrimary
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
const indexInfo = indexMap.get(indexName);
|
|
277
|
-
indexInfo.columns.push(columnName);
|
|
278
|
-
}
|
|
279
|
-
const results = [];
|
|
280
|
-
indexMap.forEach((indexInfo, indexName) => {
|
|
281
|
-
results.push({
|
|
282
|
-
index_name: indexName,
|
|
283
|
-
column_names: indexInfo.columns,
|
|
284
|
-
is_unique: indexInfo.is_unique,
|
|
285
|
-
is_primary: indexInfo.is_primary
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
return results;
|
|
289
|
-
} catch (error) {
|
|
290
|
-
console.error("Error getting table indexes:", error);
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
async getTableSchema(tableName, schema) {
|
|
295
|
-
if (!this.pool) {
|
|
296
|
-
throw new Error("Not connected to database");
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
300
|
-
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
301
|
-
const [rows] = await this.pool.query(
|
|
302
|
-
`
|
|
303
|
-
SELECT
|
|
304
|
-
COLUMN_NAME as column_name,
|
|
305
|
-
DATA_TYPE as data_type,
|
|
306
|
-
IS_NULLABLE as is_nullable,
|
|
307
|
-
COLUMN_DEFAULT as column_default,
|
|
308
|
-
COLUMN_COMMENT as description
|
|
309
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
|
310
|
-
${schemaClause}
|
|
311
|
-
AND TABLE_NAME = ?
|
|
312
|
-
ORDER BY ORDINAL_POSITION
|
|
313
|
-
`,
|
|
314
|
-
queryParams
|
|
315
|
-
);
|
|
316
|
-
return rows.map((row) => ({
|
|
317
|
-
...row,
|
|
318
|
-
description: row.description || null
|
|
319
|
-
}));
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("Error getting table schema:", error);
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
async getTableComment(tableName, schema) {
|
|
326
|
-
if (!this.pool) {
|
|
327
|
-
throw new Error("Not connected to database");
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
331
|
-
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
332
|
-
const [rows] = await this.pool.query(
|
|
333
|
-
`
|
|
334
|
-
SELECT TABLE_COMMENT
|
|
335
|
-
FROM INFORMATION_SCHEMA.TABLES
|
|
336
|
-
${schemaClause}
|
|
337
|
-
AND TABLE_NAME = ?
|
|
338
|
-
`,
|
|
339
|
-
queryParams
|
|
340
|
-
);
|
|
341
|
-
if (rows.length > 0) {
|
|
342
|
-
return rows[0].TABLE_COMMENT || null;
|
|
343
|
-
}
|
|
344
|
-
return null;
|
|
345
|
-
} catch (error) {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
async getHealthCheck() {
|
|
350
|
-
if (!this.pool) {
|
|
351
|
-
throw new Error("Not connected to database");
|
|
352
|
-
}
|
|
353
|
-
const pool = this.pool;
|
|
354
|
-
return getMySQLFamilyHealthCheck(async (sql) => {
|
|
355
|
-
const [rows] = await pool.query(sql);
|
|
356
|
-
return rows;
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
async getStoredProcedures(schema, routineType) {
|
|
360
|
-
if (!this.pool) {
|
|
361
|
-
throw new Error("Not connected to database");
|
|
362
|
-
}
|
|
363
|
-
try {
|
|
364
|
-
const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
|
|
365
|
-
const queryParams = schema ? [schema] : [];
|
|
366
|
-
let typeFilter = "";
|
|
367
|
-
if (routineType === "function") {
|
|
368
|
-
typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
|
|
369
|
-
} else if (routineType === "procedure") {
|
|
370
|
-
typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
371
|
-
}
|
|
372
|
-
const [rows] = await this.pool.query(
|
|
373
|
-
`
|
|
374
|
-
SELECT ROUTINE_NAME
|
|
375
|
-
FROM INFORMATION_SCHEMA.ROUTINES
|
|
376
|
-
${schemaClause}${typeFilter}
|
|
377
|
-
ORDER BY ROUTINE_NAME
|
|
378
|
-
`,
|
|
379
|
-
queryParams
|
|
380
|
-
);
|
|
381
|
-
return rows.map((row) => row.ROUTINE_NAME);
|
|
382
|
-
} catch (error) {
|
|
383
|
-
console.error("Error getting stored procedures:", error);
|
|
384
|
-
throw error;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
async getStoredProcedureDetail(procedureName, schema) {
|
|
388
|
-
if (!this.pool) {
|
|
389
|
-
throw new Error("Not connected to database");
|
|
390
|
-
}
|
|
391
|
-
try {
|
|
392
|
-
const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
|
|
393
|
-
const queryParams = schema ? [schema, procedureName] : [procedureName];
|
|
394
|
-
const [rows] = await this.pool.query(
|
|
395
|
-
`
|
|
396
|
-
SELECT
|
|
397
|
-
r.ROUTINE_NAME AS procedure_name,
|
|
398
|
-
CASE
|
|
399
|
-
WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
|
|
400
|
-
ELSE 'function'
|
|
401
|
-
END AS procedure_type,
|
|
402
|
-
LOWER(r.ROUTINE_TYPE) AS routine_type,
|
|
403
|
-
r.ROUTINE_DEFINITION,
|
|
404
|
-
r.DTD_IDENTIFIER AS return_type,
|
|
405
|
-
(
|
|
406
|
-
SELECT GROUP_CONCAT(
|
|
407
|
-
CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
|
|
408
|
-
ORDER BY p.ORDINAL_POSITION
|
|
409
|
-
SEPARATOR ', '
|
|
410
|
-
)
|
|
411
|
-
FROM INFORMATION_SCHEMA.PARAMETERS p
|
|
412
|
-
WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
|
|
413
|
-
AND p.SPECIFIC_NAME = r.ROUTINE_NAME
|
|
414
|
-
AND p.PARAMETER_NAME IS NOT NULL
|
|
415
|
-
) AS parameter_list
|
|
416
|
-
FROM INFORMATION_SCHEMA.ROUTINES r
|
|
417
|
-
${schemaClause}
|
|
418
|
-
AND r.ROUTINE_NAME = ?
|
|
419
|
-
`,
|
|
420
|
-
queryParams
|
|
421
|
-
);
|
|
422
|
-
if (rows.length === 0) {
|
|
423
|
-
const schemaName = schema || "current schema";
|
|
424
|
-
throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
|
|
425
|
-
}
|
|
426
|
-
const procedure = rows[0];
|
|
427
|
-
let definition = procedure.ROUTINE_DEFINITION;
|
|
428
|
-
try {
|
|
429
|
-
const schemaValue = schema || await this.getCurrentSchema();
|
|
430
|
-
const quotedSchema = quoteIdentifier(schemaValue, "mysql");
|
|
431
|
-
const quotedProcName = quoteIdentifier(procedureName, "mysql");
|
|
432
|
-
if (procedure.procedure_type === "procedure") {
|
|
433
|
-
try {
|
|
434
|
-
const [defRows] = await this.pool.query(`
|
|
435
|
-
SHOW CREATE PROCEDURE ${quotedSchema}.${quotedProcName}
|
|
436
|
-
`);
|
|
437
|
-
if (defRows && defRows.length > 0) {
|
|
438
|
-
definition = defRows[0]["Create Procedure"];
|
|
439
|
-
}
|
|
440
|
-
} catch (err) {
|
|
441
|
-
console.error(`Error getting procedure definition with SHOW CREATE: ${err}`);
|
|
442
|
-
}
|
|
443
|
-
} else {
|
|
444
|
-
try {
|
|
445
|
-
const [defRows] = await this.pool.query(`
|
|
446
|
-
SHOW CREATE FUNCTION ${quotedSchema}.${quotedProcName}
|
|
447
|
-
`);
|
|
448
|
-
if (defRows && defRows.length > 0) {
|
|
449
|
-
definition = defRows[0]["Create Function"];
|
|
450
|
-
}
|
|
451
|
-
} catch (innerErr) {
|
|
452
|
-
console.error(`Error getting function definition with SHOW CREATE: ${innerErr}`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (!definition) {
|
|
456
|
-
const [bodyRows] = await this.pool.query(
|
|
457
|
-
`
|
|
458
|
-
SELECT ROUTINE_DEFINITION, ROUTINE_BODY
|
|
459
|
-
FROM INFORMATION_SCHEMA.ROUTINES
|
|
460
|
-
WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
|
|
461
|
-
`,
|
|
462
|
-
[schemaValue, procedureName]
|
|
463
|
-
);
|
|
464
|
-
if (bodyRows && bodyRows.length > 0) {
|
|
465
|
-
if (bodyRows[0].ROUTINE_DEFINITION) {
|
|
466
|
-
definition = bodyRows[0].ROUTINE_DEFINITION;
|
|
467
|
-
} else if (bodyRows[0].ROUTINE_BODY) {
|
|
468
|
-
definition = bodyRows[0].ROUTINE_BODY;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.error(`Error getting procedure/function details: ${error}`);
|
|
474
|
-
}
|
|
475
|
-
return {
|
|
476
|
-
procedure_name: procedure.procedure_name,
|
|
477
|
-
procedure_type: procedure.procedure_type,
|
|
478
|
-
language: "sql",
|
|
479
|
-
// MySQL procedures are generally in SQL
|
|
480
|
-
parameter_list: procedure.parameter_list || "",
|
|
481
|
-
return_type: procedure.routine_type === "function" ? procedure.return_type : void 0,
|
|
482
|
-
definition: definition || void 0
|
|
483
|
-
};
|
|
484
|
-
} catch (error) {
|
|
485
|
-
console.error("Error getting stored procedure detail:", error);
|
|
486
|
-
throw error;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
// Helper method to get current schema (database) name
|
|
490
|
-
async getCurrentSchema() {
|
|
491
|
-
const [rows] = await this.pool.query("SELECT DATABASE() AS DB");
|
|
492
|
-
return rows[0].DB;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Default search scope = the database named in the DSN. DATABASE() returns
|
|
496
|
-
* null when the connection was opened without a database, in which case
|
|
497
|
-
* callers fall back to the full server-wide schema list.
|
|
498
|
-
*/
|
|
499
|
-
async getDefaultSchema() {
|
|
500
|
-
if (!this.pool) {
|
|
501
|
-
throw new Error("Not connected to database");
|
|
502
|
-
}
|
|
503
|
-
const [rows] = await this.pool.query("SELECT DATABASE() AS DB");
|
|
504
|
-
return rows[0]?.DB ?? null;
|
|
505
|
-
}
|
|
506
|
-
async executeSQL(sql, options, parameters) {
|
|
507
|
-
if (!this.pool) {
|
|
508
|
-
throw new Error("Not connected to database");
|
|
509
|
-
}
|
|
510
|
-
const conn = await this.pool.getConnection();
|
|
511
|
-
const threadId = conn.threadId;
|
|
512
|
-
let isConnectionPoisoned = false;
|
|
513
|
-
try {
|
|
514
|
-
return await withReadOnlyTransaction(
|
|
515
|
-
conn,
|
|
516
|
-
options.readonly,
|
|
517
|
-
this.supportsReadOnlyTransaction,
|
|
518
|
-
async () => {
|
|
519
|
-
const statements = splitSQLStatements(sql, "mysql");
|
|
520
|
-
let processedSQL = sql;
|
|
521
|
-
let probes = [];
|
|
522
|
-
if (options.maxRows) {
|
|
523
|
-
const rewrites = statements.map(
|
|
524
|
-
(statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows, "mysql")
|
|
525
|
-
);
|
|
526
|
-
probes = rewrites.map((rewrite) => rewrite.probeApplied);
|
|
527
|
-
processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
|
|
528
|
-
if (sql.trim().endsWith(";")) {
|
|
529
|
-
processedSQL += ";";
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
let results;
|
|
533
|
-
if (parameters && parameters.length > 0) {
|
|
534
|
-
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs }, parameters);
|
|
535
|
-
} else {
|
|
536
|
-
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
|
|
537
|
-
}
|
|
538
|
-
const [firstResult] = results;
|
|
539
|
-
const resultSets = parseQueryResultSets(firstResult, statements);
|
|
540
|
-
if (resultSets.length === probes.length) {
|
|
541
|
-
resultSets.forEach(
|
|
542
|
-
(set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
return { resultSets };
|
|
546
|
-
}
|
|
547
|
-
);
|
|
548
|
-
} catch (error) {
|
|
549
|
-
if (isClientSideTimeout(error)) {
|
|
550
|
-
isConnectionPoisoned = true;
|
|
551
|
-
await this.killQuery(threadId);
|
|
552
|
-
}
|
|
553
|
-
throw error;
|
|
554
|
-
} finally {
|
|
555
|
-
if (isConnectionPoisoned) {
|
|
556
|
-
conn.destroy();
|
|
557
|
-
} else {
|
|
558
|
-
conn.release();
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Best-effort server-side kill for a query abandoned by mysql2's client-side
|
|
564
|
-
* timeout. Uses a separate connection: the original connection's command
|
|
565
|
-
* queue is stuck behind the abandoned statement (see isClientSideTimeout)
|
|
566
|
-
* and cannot itself be used to send KILL QUERY.
|
|
567
|
-
*
|
|
568
|
-
* Bounded by its own short timeout, independent of the user's (possibly
|
|
569
|
-
* long or unset) query_timeout — KILL QUERY is metadata-only and should
|
|
570
|
-
* return almost immediately on a healthy server, so cleanup must not stall
|
|
571
|
-
* indefinitely if it doesn't.
|
|
572
|
-
*/
|
|
573
|
-
async killQuery(threadId) {
|
|
574
|
-
if (!this.pool) return;
|
|
575
|
-
let killer;
|
|
576
|
-
let killerPoisoned = false;
|
|
577
|
-
try {
|
|
578
|
-
killer = await this.pool.getConnection();
|
|
579
|
-
await killer.query({ sql: `KILL QUERY ${threadId}`, timeout: _MySQLConnector.KILL_QUERY_TIMEOUT_MS });
|
|
580
|
-
} catch (error) {
|
|
581
|
-
killerPoisoned = isClientSideTimeout(error);
|
|
582
|
-
} finally {
|
|
583
|
-
if (killer) {
|
|
584
|
-
if (killerPoisoned) {
|
|
585
|
-
killer.destroy();
|
|
586
|
-
} else {
|
|
587
|
-
killer.release();
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
// Bounds the KILL QUERY cleanup call issued after a client-side query
|
|
594
|
-
// timeout; see killQuery.
|
|
595
|
-
_MySQLConnector.KILL_QUERY_TIMEOUT_MS = 5e3;
|
|
596
|
-
var MySQLConnector = _MySQLConnector;
|
|
597
|
-
var mysqlConnector = new MySQLConnector();
|
|
598
|
-
ConnectorRegistry.register(mysqlConnector);
|
|
599
|
-
export {
|
|
600
|
-
MySQLConnector
|
|
601
|
-
};
|