spindb 0.46.5 → 0.47.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.
Files changed (127) hide show
  1. package/dist/cli/commands/info.js +12 -2
  2. package/dist/cli/commands/info.js.map +1 -1
  3. package/dist/cli/commands/link.js +6 -0
  4. package/dist/cli/commands/link.js.map +1 -1
  5. package/dist/cli/commands/list.js +4 -1
  6. package/dist/cli/commands/list.js.map +1 -1
  7. package/dist/cli/commands/menu/container-handlers.js +25 -7
  8. package/dist/cli/commands/menu/container-handlers.js.map +1 -1
  9. package/dist/config/backup-formats.js +11 -11
  10. package/dist/config/backup-formats.js.map +1 -1
  11. package/dist/config/version.js +1 -1
  12. package/dist/core/credential-manager.js +34 -11
  13. package/dist/core/credential-manager.js.map +1 -1
  14. package/dist/core/query-parser.js +55 -1
  15. package/dist/core/query-parser.js.map +1 -1
  16. package/dist/core/remote-container.js +11 -0
  17. package/dist/core/remote-container.js.map +1 -1
  18. package/dist/engines/clickhouse/backup.js +42 -10
  19. package/dist/engines/clickhouse/backup.js.map +1 -1
  20. package/dist/engines/clickhouse/restore.js +41 -10
  21. package/dist/engines/clickhouse/restore.js.map +1 -1
  22. package/dist/engines/cockroachdb/backup.js +18 -22
  23. package/dist/engines/cockroachdb/backup.js.map +1 -1
  24. package/dist/engines/cockroachdb/cli-utils.js +66 -0
  25. package/dist/engines/cockroachdb/cli-utils.js.map +1 -1
  26. package/dist/engines/cockroachdb/index.js +199 -116
  27. package/dist/engines/cockroachdb/index.js.map +1 -1
  28. package/dist/engines/cockroachdb/restore.js +19 -26
  29. package/dist/engines/cockroachdb/restore.js.map +1 -1
  30. package/dist/engines/couchdb/backup.js +13 -4
  31. package/dist/engines/couchdb/backup.js.map +1 -1
  32. package/dist/engines/couchdb/index.js +93 -25
  33. package/dist/engines/couchdb/index.js.map +1 -1
  34. package/dist/engines/couchdb/restore.js +15 -4
  35. package/dist/engines/couchdb/restore.js.map +1 -1
  36. package/dist/engines/ferretdb/backup.js +88 -91
  37. package/dist/engines/ferretdb/backup.js.map +1 -1
  38. package/dist/engines/ferretdb/index.js +179 -226
  39. package/dist/engines/ferretdb/index.js.map +1 -1
  40. package/dist/engines/ferretdb/restore.js +223 -20
  41. package/dist/engines/ferretdb/restore.js.map +1 -1
  42. package/dist/engines/influxdb/api-client.js +1 -1
  43. package/dist/engines/influxdb/api-client.js.map +1 -1
  44. package/dist/engines/influxdb/backup.js +25 -5
  45. package/dist/engines/influxdb/backup.js.map +1 -1
  46. package/dist/engines/influxdb/index.js +165 -43
  47. package/dist/engines/influxdb/index.js.map +1 -1
  48. package/dist/engines/influxdb/restore.js +22 -2
  49. package/dist/engines/influxdb/restore.js.map +1 -1
  50. package/dist/engines/mariadb/backup.js +24 -15
  51. package/dist/engines/mariadb/backup.js.map +1 -1
  52. package/dist/engines/mariadb/env.js +11 -0
  53. package/dist/engines/mariadb/env.js.map +1 -0
  54. package/dist/engines/mariadb/index.js +192 -113
  55. package/dist/engines/mariadb/index.js.map +1 -1
  56. package/dist/engines/mariadb/restore.js +21 -5
  57. package/dist/engines/mariadb/restore.js.map +1 -1
  58. package/dist/engines/meilisearch/backup.js +8 -4
  59. package/dist/engines/meilisearch/backup.js.map +1 -1
  60. package/dist/engines/meilisearch/index.js +55 -58
  61. package/dist/engines/meilisearch/index.js.map +1 -1
  62. package/dist/engines/mongo-uri.js +8 -0
  63. package/dist/engines/mongo-uri.js.map +1 -0
  64. package/dist/engines/mongodb/backup.js +62 -13
  65. package/dist/engines/mongodb/backup.js.map +1 -1
  66. package/dist/engines/mongodb/index.js +170 -108
  67. package/dist/engines/mongodb/index.js.map +1 -1
  68. package/dist/engines/mongodb/restore.js +21 -1
  69. package/dist/engines/mongodb/restore.js.map +1 -1
  70. package/dist/engines/mysql/backup.js +24 -7
  71. package/dist/engines/mysql/backup.js.map +1 -1
  72. package/dist/engines/mysql/index.js +154 -89
  73. package/dist/engines/mysql/index.js.map +1 -1
  74. package/dist/engines/mysql/restore.js +14 -4
  75. package/dist/engines/mysql/restore.js.map +1 -1
  76. package/dist/engines/postgresql/backup.js +9 -2
  77. package/dist/engines/postgresql/backup.js.map +1 -1
  78. package/dist/engines/postgresql/index.js +10 -4
  79. package/dist/engines/postgresql/index.js.map +1 -1
  80. package/dist/engines/postgresql/restore.js +7 -3
  81. package/dist/engines/postgresql/restore.js.map +1 -1
  82. package/dist/engines/qdrant/backup.js +5 -1
  83. package/dist/engines/qdrant/backup.js.map +1 -1
  84. package/dist/engines/qdrant/index.js +31 -2
  85. package/dist/engines/qdrant/index.js.map +1 -1
  86. package/dist/engines/qdrant/restore.js +5 -3
  87. package/dist/engines/qdrant/restore.js.map +1 -1
  88. package/dist/engines/questdb/auth.js +26 -0
  89. package/dist/engines/questdb/auth.js.map +1 -0
  90. package/dist/engines/questdb/backup.js +10 -8
  91. package/dist/engines/questdb/backup.js.map +1 -1
  92. package/dist/engines/questdb/index.js +16 -8
  93. package/dist/engines/questdb/index.js.map +1 -1
  94. package/dist/engines/questdb/restore.js +7 -5
  95. package/dist/engines/questdb/restore.js.map +1 -1
  96. package/dist/engines/redis/backup.js +48 -15
  97. package/dist/engines/redis/backup.js.map +1 -1
  98. package/dist/engines/redis/index.js +31 -8
  99. package/dist/engines/redis/index.js.map +1 -1
  100. package/dist/engines/redis/restore.js +21 -2
  101. package/dist/engines/redis/restore.js.map +1 -1
  102. package/dist/engines/surrealdb/auth.js +98 -0
  103. package/dist/engines/surrealdb/auth.js.map +1 -0
  104. package/dist/engines/surrealdb/backup.js +72 -9
  105. package/dist/engines/surrealdb/backup.js.map +1 -1
  106. package/dist/engines/surrealdb/index.js +84 -144
  107. package/dist/engines/surrealdb/index.js.map +1 -1
  108. package/dist/engines/surrealdb/restore.js +32 -31
  109. package/dist/engines/surrealdb/restore.js.map +1 -1
  110. package/dist/engines/typedb/backup.js +9 -1
  111. package/dist/engines/typedb/backup.js.map +1 -1
  112. package/dist/engines/typedb/cli-utils.js +3 -3
  113. package/dist/engines/typedb/cli-utils.js.map +1 -1
  114. package/dist/engines/typedb/index.js +9 -2
  115. package/dist/engines/typedb/index.js.map +1 -1
  116. package/dist/engines/typedb/restore.js +19 -7
  117. package/dist/engines/typedb/restore.js.map +1 -1
  118. package/dist/engines/valkey/backup.js +37 -13
  119. package/dist/engines/valkey/backup.js.map +1 -1
  120. package/dist/engines/valkey/index.js +207 -58
  121. package/dist/engines/valkey/index.js.map +1 -1
  122. package/dist/engines/valkey/restore.js +21 -2
  123. package/dist/engines/valkey/restore.js.map +1 -1
  124. package/dist/engines/weaviate/backup.js +7 -2
  125. package/dist/engines/weaviate/backup.js.map +1 -1
  126. package/dist/types/index.js.map +1 -1
  127. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  * MySQL Engine implementation
3
3
  * Manages MySQL database containers using pre-built binaries from hostdb
4
4
  */
5
- import { spawn, exec } from 'child_process';
5
+ import { spawn, exec, execFile } from 'child_process';
6
6
  import { promisify } from 'util';
7
7
  import { existsSync, createReadStream } from 'fs';
8
8
  import { mkdir, writeFile, readFile, unlink, rm } from 'fs/promises';
@@ -12,6 +12,7 @@ import { paths } from '../../config/paths.js';
12
12
  import { getEngineDefaults } from '../../config/defaults.js';
13
13
  import { platformService, isWindows, getWindowsSpawnOptions, } from '../../core/platform-service.js';
14
14
  import { configManager } from '../../core/config-manager.js';
15
+ import { getDefaultUsername, loadCredentials, } from '../../core/credential-manager.js';
15
16
  import { logDebug, logWarning, ErrorCodes, SpinDBError, assertValidDatabaseName, assertValidUsername, } from '../../core/error-handler.js';
16
17
  import { mysqlBinaryManager } from './binary-manager.js';
17
18
  import { getBinaryUrl } from './binary-urls.js';
@@ -19,11 +20,21 @@ import { fetchAvailableVersions, getLatestVersion, fetchDeprecatedVersions, } fr
19
20
  import { SUPPORTED_MAJOR_VERSIONS, FALLBACK_VERSION_MAP } from './version-maps.js';
20
21
  import { detectBackupFormat as detectBackupFormatImpl, restoreBackup, parseConnectionString, } from './restore.js';
21
22
  import { createBackup } from './backup.js';
22
- import { Platform, } from '../../types/index.js';
23
+ import { Engine, Platform, } from '../../types/index.js';
23
24
  import { parseTSVToQueryResult } from '../../core/query-parser.js';
24
- const execAsync = promisify(exec);
25
+ const execFileAsync = promisify(execFile);
25
26
  const ENGINE = 'mysql';
26
27
  const engineDef = getEngineDefaults(ENGINE);
28
+ function buildMysqlEnv(password) {
29
+ const env = { ...process.env };
30
+ if (password !== undefined) {
31
+ env.MYSQL_PWD = password;
32
+ }
33
+ else {
34
+ delete env.MYSQL_PWD;
35
+ }
36
+ return env;
37
+ }
27
38
  /**
28
39
  * Build a Windows-safe mysql command string for either a file or inline SQL.
29
40
  * This is exported for unit testing.
@@ -68,6 +79,28 @@ export class MySQLEngine extends BaseEngine {
68
79
  displayName = 'MySQL';
69
80
  defaultPort = engineDef.defaultPort;
70
81
  supportedVersions = SUPPORTED_MAJOR_VERSIONS;
82
+ async getLocalAdminAuth(containerName) {
83
+ const savedCreds = await loadCredentials(containerName, Engine.MySQL, getDefaultUsername(Engine.MySQL));
84
+ return {
85
+ user: savedCreds?.username || engineDef.superuser,
86
+ password: savedCreds?.password,
87
+ };
88
+ }
89
+ async pingLocalAdmin(containerName, port) {
90
+ const mysql = await this.getMysqlClientPath();
91
+ const auth = await this.getLocalAdminAuth(containerName);
92
+ await execFileAsync(mysql, ['-h', '127.0.0.1', '-P', String(port), '-u', auth.user, '-N', '-e', 'SELECT 1'], {
93
+ env: buildMysqlEnv(auth.password),
94
+ });
95
+ }
96
+ async shutdownLocalAdmin(containerName, port) {
97
+ const mysql = await this.getMysqlClientPath();
98
+ const auth = await this.getLocalAdminAuth(containerName);
99
+ await execFileAsync(mysql, ['-h', '127.0.0.1', '-P', String(port), '-u', auth.user, '-N', '-e', 'SHUTDOWN'], {
100
+ env: buildMysqlEnv(auth.password),
101
+ timeout: 5000,
102
+ });
103
+ }
71
104
  async fetchAvailableVersions() {
72
105
  return fetchAvailableVersions();
73
106
  }
@@ -334,16 +367,10 @@ export class MySQLEngine extends BaseEngine {
334
367
  let proc = null;
335
368
  if (isWindows()) {
336
369
  proc = spawn(mysqld, args, {
337
- stdio: ['ignore', 'pipe', 'pipe'],
370
+ stdio: ['ignore', 'ignore', 'ignore'],
338
371
  detached: true,
339
372
  windowsHide: true,
340
373
  });
341
- proc.stdout?.on('data', (data) => {
342
- logDebug(`mysqld stdout: ${data.toString()}`);
343
- });
344
- proc.stderr?.on('data', (data) => {
345
- logDebug(`mysqld stderr: ${data.toString()}`);
346
- });
347
374
  proc.unref();
348
375
  }
349
376
  else {
@@ -386,8 +413,7 @@ export class MySQLEngine extends BaseEngine {
386
413
  return;
387
414
  attempts++;
388
415
  try {
389
- const mysqladmin = await this.getMysqladminPath();
390
- await execAsync(`"${mysqladmin}" -h 127.0.0.1 -P ${port} -u root ping`);
416
+ await this.pingLocalAdmin(container.name, port);
391
417
  if (settled)
392
418
  return;
393
419
  settled = true;
@@ -441,17 +467,20 @@ export class MySQLEngine extends BaseEngine {
441
467
  if (pid === null) {
442
468
  logDebug('No valid PID, checking if MySQL is responding on port');
443
469
  try {
444
- const mysqladmin = await this.getMysqladminPath();
445
- await execAsync(`"${mysqladmin}" -h 127.0.0.1 -P ${port} -u root ping`, { timeout: 2000 });
446
- logWarning(`MySQL responding on port ${port} but no valid PID file`);
447
- await this.gracefulShutdown(port);
470
+ await this.pingLocalAdmin(name, port);
448
471
  }
449
472
  catch {
450
473
  logDebug('MySQL not responding, nothing to stop');
474
+ return;
475
+ }
476
+ logWarning(`MySQL responding on port ${port} but no valid PID file`);
477
+ const gracefulSuccess = await this.gracefulShutdown(name, port);
478
+ if (!gracefulSuccess) {
479
+ throw new SpinDBError(ErrorCodes.PROCESS_STOP_TIMEOUT, `MySQL on port ${port} responded to ping but did not shut down cleanly`, 'error', 'Check credentials and stop the process manually if it is still running.');
451
480
  }
452
481
  return;
453
482
  }
454
- const gracefulSuccess = await this.gracefulShutdown(port, pid);
483
+ const gracefulSuccess = await this.gracefulShutdown(name, port, pid);
455
484
  if (gracefulSuccess) {
456
485
  await this.cleanupPidFile(pidFile);
457
486
  logDebug('MySQL stopped gracefully');
@@ -490,15 +519,14 @@ export class MySQLEngine extends BaseEngine {
490
519
  return null;
491
520
  }
492
521
  }
493
- async gracefulShutdown(port, pid, timeoutMs = 10000) {
522
+ async gracefulShutdown(containerName, port, pid, timeoutMs = 30000) {
494
523
  try {
495
- const mysqladmin = await this.getMysqladminPath();
496
- logDebug('Attempting mysqladmin shutdown');
497
- await execAsync(`"${mysqladmin}" -h 127.0.0.1 -P ${port} -u root shutdown`, { timeout: 5000 });
524
+ logDebug('Attempting authenticated MySQL shutdown');
525
+ await this.shutdownLocalAdmin(containerName, port);
498
526
  }
499
527
  catch (error) {
500
528
  const e = error;
501
- logDebug(`mysqladmin shutdown failed: ${e.message}`);
529
+ logDebug(`Authenticated shutdown failed: ${e.message}`);
502
530
  if (pid) {
503
531
  try {
504
532
  await platformService.terminateProcess(pid, false);
@@ -514,6 +542,7 @@ export class MySQLEngine extends BaseEngine {
514
542
  return false;
515
543
  }
516
544
  }
545
+ return false;
517
546
  }
518
547
  if (pid) {
519
548
  const startTime = Date.now();
@@ -594,8 +623,7 @@ export class MySQLEngine extends BaseEngine {
594
623
  return { running: false, message: 'MySQL is not running' };
595
624
  }
596
625
  try {
597
- const mysqladmin = await this.getMysqladminPath();
598
- await execAsync(`"${mysqladmin}" -h 127.0.0.1 -P ${port} -u root ping`);
626
+ await this.pingLocalAdmin(name, port);
599
627
  return { running: true, message: 'MySQL is running' };
600
628
  }
601
629
  catch {
@@ -606,16 +634,19 @@ export class MySQLEngine extends BaseEngine {
606
634
  return detectBackupFormatImpl(filePath);
607
635
  }
608
636
  async restore(container, backupPath, options = {}) {
609
- const { port, version } = container;
637
+ const { name, port, version } = container;
610
638
  const database = options.database || container.database;
611
639
  const binPath = this.getBinaryPath(version);
640
+ const auth = await this.getLocalAdminAuth(name);
612
641
  if (options.createDatabase !== false) {
613
642
  await this.createDatabase(container, database);
614
643
  }
615
644
  return restoreBackup(backupPath, {
645
+ containerName: name,
616
646
  port,
617
647
  database,
618
- user: engineDef.superuser,
648
+ user: auth.user,
649
+ password: auth.password,
619
650
  createDatabase: false,
620
651
  validateVersion: options.validateVersion !== false,
621
652
  binPath,
@@ -642,26 +673,39 @@ export class MySQLEngine extends BaseEngine {
642
673
  ' spindb engines download mysql');
643
674
  }
644
675
  async connect(container, database) {
645
- const { port } = container;
676
+ const { name, port } = container;
646
677
  const db = database || container.database || 'mysql';
647
678
  const mysql = await this.getMysqlClientPath();
679
+ const auth = await this.getLocalAdminAuth(name);
648
680
  const spawnOptions = {
649
681
  stdio: 'inherit',
682
+ env: buildMysqlEnv(auth.password),
650
683
  ...getWindowsSpawnOptions(),
651
684
  };
652
685
  return new Promise((resolve, reject) => {
653
- const proc = spawn(mysql, ['-h', '127.0.0.1', '-P', String(port), '-u', engineDef.superuser, db], spawnOptions);
686
+ const proc = spawn(mysql, ['-h', '127.0.0.1', '-P', String(port), '-u', auth.user, db], spawnOptions);
654
687
  proc.on('error', reject);
655
688
  proc.on('close', () => resolve());
656
689
  });
657
690
  }
658
691
  async createDatabase(container, database) {
659
692
  assertValidDatabaseName(database);
660
- const { port } = container;
693
+ const { name, port } = container;
661
694
  const mysql = await this.getMysqlClientPath();
695
+ const auth = await this.getLocalAdminAuth(name);
662
696
  try {
663
- const cmd = buildMysqlInlineCommand(mysql, port, engineDef.superuser, `CREATE DATABASE IF NOT EXISTS \`${database}\``);
664
- await execAsync(cmd);
697
+ await execFileAsync(mysql, [
698
+ '-h',
699
+ '127.0.0.1',
700
+ '-P',
701
+ String(port),
702
+ '-u',
703
+ auth.user,
704
+ '-e',
705
+ `CREATE DATABASE IF NOT EXISTS \`${database}\``,
706
+ ], {
707
+ env: buildMysqlEnv(auth.password),
708
+ });
665
709
  }
666
710
  catch (error) {
667
711
  const err = error;
@@ -672,11 +716,22 @@ export class MySQLEngine extends BaseEngine {
672
716
  }
673
717
  async dropDatabase(container, database) {
674
718
  assertValidDatabaseName(database);
675
- const { port } = container;
719
+ const { name, port } = container;
676
720
  const mysql = await this.getMysqlClientPath();
721
+ const auth = await this.getLocalAdminAuth(name);
677
722
  try {
678
- const cmd = buildMysqlInlineCommand(mysql, port, engineDef.superuser, `DROP DATABASE IF EXISTS \`${database}\``);
679
- await execAsync(cmd);
723
+ await execFileAsync(mysql, [
724
+ '-h',
725
+ '127.0.0.1',
726
+ '-P',
727
+ String(port),
728
+ '-u',
729
+ auth.user,
730
+ '-e',
731
+ `DROP DATABASE IF EXISTS \`${database}\``,
732
+ ], {
733
+ env: buildMysqlEnv(auth.password),
734
+ });
680
735
  }
681
736
  catch (error) {
682
737
  const err = error;
@@ -686,12 +741,25 @@ export class MySQLEngine extends BaseEngine {
686
741
  }
687
742
  }
688
743
  async getDatabaseSize(container) {
689
- const { port, database } = container;
744
+ const { name, port, database } = container;
690
745
  const db = database || 'mysql';
691
746
  assertValidDatabaseName(db);
692
747
  try {
693
748
  const mysql = await this.getMysqlClientPath();
694
- const { stdout } = await execAsync(`"${mysql}" -h 127.0.0.1 -P ${port} -u ${engineDef.superuser} -N -e "SELECT COALESCE(SUM(data_length + index_length), 0) FROM information_schema.tables WHERE table_schema = '${db}'"`);
749
+ const auth = await this.getLocalAdminAuth(name);
750
+ const { stdout } = await execFileAsync(mysql, [
751
+ '-h',
752
+ '127.0.0.1',
753
+ '-P',
754
+ String(port),
755
+ '-u',
756
+ auth.user,
757
+ '-N',
758
+ '-e',
759
+ `SELECT COALESCE(SUM(data_length + index_length), 0) FROM information_schema.tables WHERE table_schema = '${db}'`,
760
+ ], {
761
+ env: buildMysqlEnv(auth.password),
762
+ });
695
763
  const size = parseInt(stdout.trim(), 10);
696
764
  return isNaN(size) ? null : size;
697
765
  }
@@ -702,26 +770,6 @@ export class MySQLEngine extends BaseEngine {
702
770
  async dumpFromConnectionString(connectionString, outputPath) {
703
771
  const dumpPath = await this.getDumpPath();
704
772
  const { host, port, user, password, database } = parseConnectionString(connectionString);
705
- if (isWindows()) {
706
- const cmd = `"${dumpPath}" -h ${host} -P ${port} -u ${user} --result-file "${outputPath}" ${database}`;
707
- const execOptions = {};
708
- if (password) {
709
- execOptions.env = { ...process.env, MYSQL_PWD: password };
710
- }
711
- try {
712
- logDebug('Executing mysqldump command', { cmd });
713
- await execAsync(cmd, execOptions);
714
- return {
715
- filePath: outputPath,
716
- stdout: '',
717
- stderr: '',
718
- code: 0,
719
- };
720
- }
721
- catch (error) {
722
- throw new Error(error.message);
723
- }
724
- }
725
773
  const args = [
726
774
  '-h',
727
775
  host,
@@ -735,7 +783,6 @@ export class MySQLEngine extends BaseEngine {
735
783
  ];
736
784
  const spawnOptions = {
737
785
  stdio: ['pipe', 'pipe', 'pipe'],
738
- ...getWindowsSpawnOptions(),
739
786
  env: password ? { ...process.env, MYSQL_PWD: password } : process.env,
740
787
  };
741
788
  return new Promise((resolve, reject) => {
@@ -776,13 +823,25 @@ export class MySQLEngine extends BaseEngine {
776
823
  }
777
824
  async terminateConnections(container, database) {
778
825
  assertValidDatabaseName(database);
779
- const { port } = container;
826
+ const { name, port } = container;
780
827
  const mysql = await this.getMysqlClientPath();
828
+ const auth = await this.getLocalAdminAuth(name);
781
829
  // Get all connection IDs for the target database and kill them
782
830
  // We need to do this in two steps since MySQL doesn't support subqueries in KILL
783
- const getIdsCmd = buildMysqlInlineCommand(mysql, port, engineDef.superuser, `SELECT ID FROM information_schema.PROCESSLIST WHERE DB = '${database}' AND ID != CONNECTION_ID()`);
784
831
  try {
785
- const { stdout } = await execAsync(getIdsCmd);
832
+ const { stdout } = await execFileAsync(mysql, [
833
+ '-h',
834
+ '127.0.0.1',
835
+ '-P',
836
+ String(port),
837
+ '-u',
838
+ auth.user,
839
+ '-N',
840
+ '-e',
841
+ `SELECT ID FROM information_schema.PROCESSLIST WHERE DB = '${database}' AND ID != CONNECTION_ID()`,
842
+ ], {
843
+ env: buildMysqlEnv(auth.password),
844
+ });
786
845
  const lines = stdout
787
846
  .trim()
788
847
  .split('\n')
@@ -793,9 +852,19 @@ export class MySQLEngine extends BaseEngine {
793
852
  .map((l) => l.trim())
794
853
  .filter((l) => /^\d+$/.test(l));
795
854
  for (const id of ids) {
796
- const killCmd = buildMysqlInlineCommand(mysql, port, engineDef.superuser, `KILL CONNECTION ${id}`);
797
855
  try {
798
- await execAsync(killCmd);
856
+ await execFileAsync(mysql, [
857
+ '-h',
858
+ '127.0.0.1',
859
+ '-P',
860
+ String(port),
861
+ '-u',
862
+ auth.user,
863
+ '-e',
864
+ `KILL CONNECTION ${id}`,
865
+ ], {
866
+ env: buildMysqlEnv(auth.password),
867
+ });
799
868
  }
800
869
  catch {
801
870
  // Connection may already be gone
@@ -807,38 +876,25 @@ export class MySQLEngine extends BaseEngine {
807
876
  }
808
877
  }
809
878
  async runScript(container, options) {
810
- const { port } = container;
879
+ const { name, port } = container;
811
880
  const db = options.database || container.database || 'mysql';
812
881
  assertValidDatabaseName(db);
813
882
  const mysql = await this.getMysqlClientPath();
814
- if (isWindows()) {
815
- const cmd = buildWindowsMysqlCommand(mysql, port, engineDef.superuser, db, options);
816
- try {
817
- const { stdout, stderr } = await execAsync(cmd);
818
- if (stdout)
819
- process.stdout.write(stdout);
820
- if (stderr)
821
- process.stderr.write(stderr);
822
- return;
823
- }
824
- catch (error) {
825
- const err = error;
826
- throw new Error(`mysql client failed: ${err.message}`);
827
- }
828
- }
883
+ const auth = await this.getLocalAdminAuth(name);
829
884
  const args = [
830
885
  '-h',
831
886
  '127.0.0.1',
832
887
  '-P',
833
888
  String(port),
834
889
  '-u',
835
- engineDef.superuser,
890
+ auth.user,
836
891
  db,
837
892
  ];
838
893
  if (options.sql) {
839
894
  args.push('-e', options.sql);
840
895
  const spawnOptions = {
841
896
  stdio: 'inherit',
897
+ env: buildMysqlEnv(auth.password),
842
898
  };
843
899
  return new Promise((resolve, reject) => {
844
900
  const proc = spawn(mysql, args, spawnOptions);
@@ -856,6 +912,7 @@ export class MySQLEngine extends BaseEngine {
856
912
  else if (options.file) {
857
913
  const spawnOptions = {
858
914
  stdio: ['pipe', 'inherit', 'inherit'],
915
+ env: buildMysqlEnv(auth.password),
859
916
  };
860
917
  return new Promise((resolve, reject) => {
861
918
  const fileStream = createReadStream(options.file);
@@ -881,12 +938,17 @@ export class MySQLEngine extends BaseEngine {
881
938
  }
882
939
  }
883
940
  async executeQuery(container, query, options) {
884
- const { port } = container;
941
+ const { name, port } = container;
885
942
  const db = options?.database || container.database || 'mysql';
886
943
  assertValidDatabaseName(db);
887
944
  const mysql = await this.getMysqlClientPath();
888
945
  const host = options?.host ?? '127.0.0.1';
889
- const user = options?.username || engineDef.superuser;
946
+ const localAuth = !options?.username &&
947
+ !options?.password &&
948
+ (host === '127.0.0.1' || host === 'localhost')
949
+ ? await this.getLocalAdminAuth(name)
950
+ : null;
951
+ const user = options?.username || localAuth?.user || engineDef.superuser;
890
952
  // Use -B (batch mode) for tab-separated output
891
953
  const args = [
892
954
  '-h',
@@ -904,9 +966,7 @@ export class MySQLEngine extends BaseEngine {
904
966
  args.push('--ssl-mode=REQUIRED');
905
967
  }
906
968
  // Pass password via env to avoid exposing it in process listings
907
- const env = options?.password
908
- ? { ...process.env, MYSQL_PWD: options.password }
909
- : process.env;
969
+ const env = buildMysqlEnv(options?.password ?? localAuth?.password);
910
970
  return new Promise((resolve, reject) => {
911
971
  const proc = spawn(mysql, args, {
912
972
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -936,8 +996,9 @@ export class MySQLEngine extends BaseEngine {
936
996
  * (information_schema, mysql, performance_schema, sys).
937
997
  */
938
998
  async listDatabases(container) {
939
- const { port } = container;
999
+ const { name, port } = container;
940
1000
  const mysql = await this.getMysqlClientPath();
1001
+ const auth = await this.getLocalAdminAuth(name);
941
1002
  // Query for all non-system databases
942
1003
  const sql = `SHOW DATABASES WHERE \`Database\` NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')`;
943
1004
  const args = [
@@ -946,7 +1007,7 @@ export class MySQLEngine extends BaseEngine {
946
1007
  '-P',
947
1008
  String(port),
948
1009
  '-u',
949
- engineDef.superuser,
1010
+ auth.user,
950
1011
  '-N', // Skip column names
951
1012
  '-B', // Batch mode (no formatting)
952
1013
  '-e',
@@ -955,6 +1016,7 @@ export class MySQLEngine extends BaseEngine {
955
1016
  return new Promise((resolve, reject) => {
956
1017
  const proc = spawn(mysql, args, {
957
1018
  stdio: ['pipe', 'pipe', 'pipe'],
1019
+ env: buildMysqlEnv(auth.password),
958
1020
  });
959
1021
  let stdout = '';
960
1022
  let stderr = '';
@@ -983,13 +1045,14 @@ export class MySQLEngine extends BaseEngine {
983
1045
  async createUser(container, options) {
984
1046
  const { username, password, database } = options;
985
1047
  assertValidUsername(username);
986
- const { port } = container;
1048
+ const { name, port } = container;
987
1049
  const db = database || container.database;
988
1050
  if (!db) {
989
1051
  throw new Error('No target database specified. Provide a database name with --database or ensure the container has a default database.');
990
1052
  }
991
1053
  assertValidDatabaseName(db);
992
1054
  const mysql = await this.getMysqlClientPath();
1055
+ const auth = await this.getLocalAdminAuth(name);
993
1056
  // Check if NO_BACKSLASH_ESCAPES is enabled — if so, only escape single quotes
994
1057
  let noBackslashEscapes = false;
995
1058
  try {
@@ -999,7 +1062,7 @@ export class MySQLEngine extends BaseEngine {
999
1062
  '-P',
1000
1063
  String(port),
1001
1064
  '-u',
1002
- engineDef.superuser,
1065
+ auth.user,
1003
1066
  '-N',
1004
1067
  '-B',
1005
1068
  '-e',
@@ -1008,6 +1071,7 @@ export class MySQLEngine extends BaseEngine {
1008
1071
  const modeResult = await new Promise((resolve, reject) => {
1009
1072
  const proc = spawn(mysql, modeArgs, {
1010
1073
  stdio: ['pipe', 'pipe', 'pipe'],
1074
+ env: buildMysqlEnv(auth.password),
1011
1075
  });
1012
1076
  let stdout = '';
1013
1077
  proc.stdout?.on('data', (data) => {
@@ -1041,11 +1105,12 @@ export class MySQLEngine extends BaseEngine {
1041
1105
  '-P',
1042
1106
  String(port),
1043
1107
  '-u',
1044
- engineDef.superuser,
1108
+ auth.user,
1045
1109
  ];
1046
1110
  await new Promise((resolve, reject) => {
1047
1111
  const proc = spawn(mysql, args, {
1048
1112
  stdio: ['pipe', 'pipe', 'pipe'],
1113
+ env: buildMysqlEnv(auth.password),
1049
1114
  });
1050
1115
  let stderr = '';
1051
1116
  proc.stderr?.on('data', (data) => {