velocious 1.0.595 → 1.0.596
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 +5 -0
- package/build/database/drivers/base.js +135 -1
- package/build/database/drivers/mysql/index.js +92 -0
- package/build/src/database/drivers/base.d.ts +21 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +131 -2
- package/build/src/database/drivers/mysql/index.d.ts +22 -0
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +91 -1
- package/package.json +1 -1
- package/src/database/drivers/base.js +135 -1
- package/src/database/drivers/mysql/index.js +92 -0
|
@@ -31,6 +31,7 @@ import Update from "./sql/update.js"
|
|
|
31
31
|
* realistic critical section) instead.
|
|
32
32
|
*/
|
|
33
33
|
const MYSQL_INDEFINITE_LOCK_TIMEOUT_SECONDS = 60 * 60 * 24 * 365
|
|
34
|
+
const INNODB_DEADLOCK_CAPTURE_TIMEOUT_MS = 250
|
|
34
35
|
|
|
35
36
|
export default class VelociousDatabaseDriversMysql extends Base{
|
|
36
37
|
/** @type {import("mysql").Pool | undefined} */
|
|
@@ -392,6 +393,97 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
392
393
|
}
|
|
393
394
|
}
|
|
394
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Adds a redacted, bounded excerpt from MySQL's latest InnoDB deadlock report. Capture uses a
|
|
398
|
+
* separate short-lived connection so it cannot queue ahead of rollback or the next retry on this
|
|
399
|
+
* driver's single-connection pool.
|
|
400
|
+
* @returns {Promise<Record<string, ReturnType<typeof JSON.parse>>>} - Safe diagnostic context.
|
|
401
|
+
*/
|
|
402
|
+
async _deadlockDiagnosticContext() {
|
|
403
|
+
try {
|
|
404
|
+
const status = await this._captureInnodbDeadlockStatus()
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
innodbDeadlockSummary: this._innodbDeadlockSummary(status),
|
|
408
|
+
statusCapture: "captured"
|
|
409
|
+
}
|
|
410
|
+
} catch {
|
|
411
|
+
return {statusCapture: "failed"}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Captures SHOW ENGINE INNODB STATUS on a bounded throwaway connection.
|
|
417
|
+
* @returns {Promise<string>} - Raw server status, retained only inside the redaction path.
|
|
418
|
+
*/
|
|
419
|
+
async _captureInnodbDeadlockStatus() {
|
|
420
|
+
const poolWithConfig = /** @type {{config?: {connectionConfig?: ReturnType<typeof JSON.parse>}} | undefined} */ (this.pool)
|
|
421
|
+
const connectionConfig = poolWithConfig?.config?.connectionConfig
|
|
422
|
+
const captureConfig = connectionConfig || this.connectArgs()
|
|
423
|
+
|
|
424
|
+
return await new Promise((resolve, reject) => {
|
|
425
|
+
/** @type {import("mysql").Connection | undefined} */
|
|
426
|
+
let connection
|
|
427
|
+
let settled = false
|
|
428
|
+
/**
|
|
429
|
+
* Finishes the status capture once and destroys its temporary connection.
|
|
430
|
+
* @param {Error | undefined} error - Capture error, when present.
|
|
431
|
+
* @param {string} [status] - Captured status.
|
|
432
|
+
* @returns {void}
|
|
433
|
+
*/
|
|
434
|
+
const finish = (error, status = "") => {
|
|
435
|
+
if (settled) return
|
|
436
|
+
settled = true
|
|
437
|
+
clearTimeout(timeout)
|
|
438
|
+
if (connection) connection.destroy()
|
|
439
|
+
if (error) reject(error)
|
|
440
|
+
else resolve(status)
|
|
441
|
+
}
|
|
442
|
+
const timeout = setTimeout(() => finish(new Error("InnoDB status capture timed out")), INNODB_DEADLOCK_CAPTURE_TIMEOUT_MS)
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
connection = mysql.createConnection(captureConfig)
|
|
446
|
+
connection.on("error", (error) => finish(error))
|
|
447
|
+
connection.query("SHOW ENGINE INNODB STATUS", (error, rows) => {
|
|
448
|
+
if (error) {
|
|
449
|
+
finish(error)
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const firstRow = Array.isArray(rows) ? rows[0] : undefined
|
|
454
|
+
const status = firstRow && typeof firstRow.Status == "string" ? firstRow.Status : ""
|
|
455
|
+
|
|
456
|
+
finish(undefined, status)
|
|
457
|
+
})
|
|
458
|
+
} catch (error) {
|
|
459
|
+
finish(error instanceof Error ? error : new Error("InnoDB status capture failed"))
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Extracts only fixed-format deadlock counters. The server report contains raw SQL, identifiers,
|
|
466
|
+
* and physical record data, so no source text is ever included in an application diagnostic.
|
|
467
|
+
* @param {string} status - SHOW ENGINE INNODB STATUS text.
|
|
468
|
+
* @returns {{transactions: number, victimTransaction: number | null}} - Structural deadlock summary.
|
|
469
|
+
*/
|
|
470
|
+
_innodbDeadlockSummary(status) {
|
|
471
|
+
const deadlockStart = status.indexOf("LATEST DETECTED DEADLOCK")
|
|
472
|
+
const candidate = deadlockStart >= 0 ? status.slice(deadlockStart) : status
|
|
473
|
+
let transactions = 0
|
|
474
|
+
/** @type {number | null} */
|
|
475
|
+
let victimTransaction = null
|
|
476
|
+
|
|
477
|
+
for (const line of candidate.split(/\r?\n/)) {
|
|
478
|
+
const trimmed = line.trim()
|
|
479
|
+
if (/^\*\*\* \(\d+\) TRANSACTION:$/.test(trimmed)) transactions++
|
|
480
|
+
const victimMatch = /^\*\*\* WE ROLL BACK TRANSACTION \((\d+)\)$/.exec(trimmed)
|
|
481
|
+
if (victimMatch) victimTransaction = Number(victimMatch[1])
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return {transactions, victimTransaction}
|
|
485
|
+
}
|
|
486
|
+
|
|
395
487
|
/**
|
|
396
488
|
* Runs query actual.
|
|
397
489
|
* @param {string} sql - SQL string.
|