spindb 0.46.5 → 0.47.1
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/cli/commands/info.js +12 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/link.js +6 -0
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/list.js +4 -1
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/menu/container-handlers.js +25 -7
- package/dist/cli/commands/menu/container-handlers.js.map +1 -1
- package/dist/config/backup-formats.js +11 -11
- package/dist/config/backup-formats.js.map +1 -1
- package/dist/config/version.js +1 -1
- package/dist/core/credential-manager.js +34 -11
- package/dist/core/credential-manager.js.map +1 -1
- package/dist/core/query-parser.js +55 -1
- package/dist/core/query-parser.js.map +1 -1
- package/dist/core/remote-container.js +11 -0
- package/dist/core/remote-container.js.map +1 -1
- package/dist/engines/clickhouse/backup.js +42 -10
- package/dist/engines/clickhouse/backup.js.map +1 -1
- package/dist/engines/clickhouse/restore.js +41 -10
- package/dist/engines/clickhouse/restore.js.map +1 -1
- package/dist/engines/cockroachdb/backup.js +18 -22
- package/dist/engines/cockroachdb/backup.js.map +1 -1
- package/dist/engines/cockroachdb/cli-utils.js +66 -0
- package/dist/engines/cockroachdb/cli-utils.js.map +1 -1
- package/dist/engines/cockroachdb/index.js +199 -116
- package/dist/engines/cockroachdb/index.js.map +1 -1
- package/dist/engines/cockroachdb/restore.js +19 -26
- package/dist/engines/cockroachdb/restore.js.map +1 -1
- package/dist/engines/couchdb/backup.js +13 -4
- package/dist/engines/couchdb/backup.js.map +1 -1
- package/dist/engines/couchdb/index.js +93 -25
- package/dist/engines/couchdb/index.js.map +1 -1
- package/dist/engines/couchdb/restore.js +15 -4
- package/dist/engines/couchdb/restore.js.map +1 -1
- package/dist/engines/ferretdb/backup.js +88 -91
- package/dist/engines/ferretdb/backup.js.map +1 -1
- package/dist/engines/ferretdb/index.js +182 -227
- package/dist/engines/ferretdb/index.js.map +1 -1
- package/dist/engines/ferretdb/restore.js +223 -20
- package/dist/engines/ferretdb/restore.js.map +1 -1
- package/dist/engines/influxdb/api-client.js +1 -1
- package/dist/engines/influxdb/api-client.js.map +1 -1
- package/dist/engines/influxdb/backup.js +25 -5
- package/dist/engines/influxdb/backup.js.map +1 -1
- package/dist/engines/influxdb/index.js +165 -43
- package/dist/engines/influxdb/index.js.map +1 -1
- package/dist/engines/influxdb/restore.js +22 -2
- package/dist/engines/influxdb/restore.js.map +1 -1
- package/dist/engines/mariadb/backup.js +24 -15
- package/dist/engines/mariadb/backup.js.map +1 -1
- package/dist/engines/mariadb/env.js +11 -0
- package/dist/engines/mariadb/env.js.map +1 -0
- package/dist/engines/mariadb/index.js +192 -113
- package/dist/engines/mariadb/index.js.map +1 -1
- package/dist/engines/mariadb/restore.js +28 -5
- package/dist/engines/mariadb/restore.js.map +1 -1
- package/dist/engines/meilisearch/backup.js +8 -4
- package/dist/engines/meilisearch/backup.js.map +1 -1
- package/dist/engines/meilisearch/index.js +55 -58
- package/dist/engines/meilisearch/index.js.map +1 -1
- package/dist/engines/mongo-uri.js +11 -0
- package/dist/engines/mongo-uri.js.map +1 -0
- package/dist/engines/mongodb/backup.js +62 -13
- package/dist/engines/mongodb/backup.js.map +1 -1
- package/dist/engines/mongodb/index.js +170 -108
- package/dist/engines/mongodb/index.js.map +1 -1
- package/dist/engines/mongodb/restore.js +21 -1
- package/dist/engines/mongodb/restore.js.map +1 -1
- package/dist/engines/mysql/backup.js +24 -7
- package/dist/engines/mysql/backup.js.map +1 -1
- package/dist/engines/mysql/index.js +154 -89
- package/dist/engines/mysql/index.js.map +1 -1
- package/dist/engines/mysql/restore.js +14 -4
- package/dist/engines/mysql/restore.js.map +1 -1
- package/dist/engines/postgresql/backup.js +9 -2
- package/dist/engines/postgresql/backup.js.map +1 -1
- package/dist/engines/postgresql/index.js +45 -17
- package/dist/engines/postgresql/index.js.map +1 -1
- package/dist/engines/postgresql/restore.js +7 -3
- package/dist/engines/postgresql/restore.js.map +1 -1
- package/dist/engines/qdrant/backup.js +5 -1
- package/dist/engines/qdrant/backup.js.map +1 -1
- package/dist/engines/qdrant/index.js +31 -2
- package/dist/engines/qdrant/index.js.map +1 -1
- package/dist/engines/qdrant/restore.js +5 -3
- package/dist/engines/qdrant/restore.js.map +1 -1
- package/dist/engines/questdb/auth.js +26 -0
- package/dist/engines/questdb/auth.js.map +1 -0
- package/dist/engines/questdb/backup.js +10 -8
- package/dist/engines/questdb/backup.js.map +1 -1
- package/dist/engines/questdb/index.js +16 -8
- package/dist/engines/questdb/index.js.map +1 -1
- package/dist/engines/questdb/restore.js +7 -5
- package/dist/engines/questdb/restore.js.map +1 -1
- package/dist/engines/redis/backup.js +45 -16
- package/dist/engines/redis/backup.js.map +1 -1
- package/dist/engines/redis/cli-common.js +62 -0
- package/dist/engines/redis/cli-common.js.map +1 -0
- package/dist/engines/redis/index.js +31 -8
- package/dist/engines/redis/index.js.map +1 -1
- package/dist/engines/redis/restore.js +59 -9
- package/dist/engines/redis/restore.js.map +1 -1
- package/dist/engines/surrealdb/auth.js +98 -0
- package/dist/engines/surrealdb/auth.js.map +1 -0
- package/dist/engines/surrealdb/backup.js +72 -9
- package/dist/engines/surrealdb/backup.js.map +1 -1
- package/dist/engines/surrealdb/index.js +84 -144
- package/dist/engines/surrealdb/index.js.map +1 -1
- package/dist/engines/surrealdb/restore.js +32 -31
- package/dist/engines/surrealdb/restore.js.map +1 -1
- package/dist/engines/typedb/backup.js +9 -1
- package/dist/engines/typedb/backup.js.map +1 -1
- package/dist/engines/typedb/cli-utils.js +3 -3
- package/dist/engines/typedb/cli-utils.js.map +1 -1
- package/dist/engines/typedb/index.js +9 -2
- package/dist/engines/typedb/index.js.map +1 -1
- package/dist/engines/typedb/restore.js +19 -7
- package/dist/engines/typedb/restore.js.map +1 -1
- package/dist/engines/valkey/backup.js +37 -13
- package/dist/engines/valkey/backup.js.map +1 -1
- package/dist/engines/valkey/index.js +207 -58
- package/dist/engines/valkey/index.js.map +1 -1
- package/dist/engines/valkey/restore.js +21 -2
- package/dist/engines/valkey/restore.js.map +1 -1
- package/dist/engines/weaviate/backup.js +7 -2
- package/dist/engines/weaviate/backup.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- 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
|
|
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', '
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
522
|
+
async gracefulShutdown(containerName, port, pid, timeoutMs = 30000) {
|
|
494
523
|
try {
|
|
495
|
-
|
|
496
|
-
|
|
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(`
|
|
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
|
-
|
|
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:
|
|
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',
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
|
|
679
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|