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
|
@@ -21,15 +21,18 @@ import { paths } from '../../config/paths.js';
|
|
|
21
21
|
import { getEngineDefaults } from '../../config/defaults.js';
|
|
22
22
|
import { platformService, isWindows } from '../../core/platform-service.js';
|
|
23
23
|
import { configManager } from '../../core/config-manager.js';
|
|
24
|
+
import { getDefaultUsername, loadCredentials } from '../../core/credential-manager.js';
|
|
24
25
|
import { containerManager } from '../../core/container-manager.js';
|
|
25
26
|
import { logDebug, logWarning, assertValidDatabaseName, assertValidUsername, } from '../../core/error-handler.js';
|
|
26
27
|
import { processManager } from '../../core/process-manager.js';
|
|
27
28
|
import { spawnAsync } from '../../core/spawn-utils.js';
|
|
28
29
|
import { ferretdbBinaryManager } from './binary-manager.js';
|
|
30
|
+
import { buildMongoUri, normalizeMongoHost, } from '../mongo-uri.js';
|
|
29
31
|
import { SUPPORTED_MAJOR_VERSIONS, FALLBACK_VERSION_MAP, DEFAULT_DOCUMENTDB_VERSION, DEFAULT_V1_POSTGRESQL_VERSION, normalizeVersion, normalizeDocumentDBVersion, isV1, } from './version-maps.js';
|
|
30
32
|
import { getBinaryUrls, isPlatformSupported } from './binary-urls.js';
|
|
31
33
|
import { detectBackupFormat as detectBackupFormatImpl, restoreBackup, } from './restore.js';
|
|
32
34
|
import { createBackup } from './backup.js';
|
|
35
|
+
import { Engine, } from '../../types/index.js';
|
|
33
36
|
import { parseMongoDBResult } from '../../core/query-parser.js';
|
|
34
37
|
const execAsync = promisify(exec);
|
|
35
38
|
const ENGINE = 'ferretdb';
|
|
@@ -974,6 +977,71 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
974
977
|
}
|
|
975
978
|
}
|
|
976
979
|
}
|
|
980
|
+
async getLocalAuth(containerName) {
|
|
981
|
+
const savedCreds = await loadCredentials(containerName, Engine.FerretDB, getDefaultUsername(Engine.FerretDB));
|
|
982
|
+
if (!savedCreds) {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
username: savedCreds.username,
|
|
987
|
+
password: savedCreds.password,
|
|
988
|
+
authDatabase: savedCreds.database || 'admin',
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
async buildLocalMongoshArgs(container, database, options) {
|
|
992
|
+
const savedCreds = await this.getLocalAuth(container.name);
|
|
993
|
+
const connectHost = normalizeMongoHost(container.bindAddress);
|
|
994
|
+
const args = savedCreds
|
|
995
|
+
? [
|
|
996
|
+
buildMongoUri(container.port, database, savedCreds, connectHost),
|
|
997
|
+
]
|
|
998
|
+
: ['--host', connectHost, '--port', String(container.port), database];
|
|
999
|
+
if (options?.quiet) {
|
|
1000
|
+
args.push('--quiet');
|
|
1001
|
+
}
|
|
1002
|
+
return args;
|
|
1003
|
+
}
|
|
1004
|
+
async runLocalMongosh(container, database, options) {
|
|
1005
|
+
const mongosh = await this.getMongoshPath();
|
|
1006
|
+
const args = await this.buildLocalMongoshArgs(container, database, {
|
|
1007
|
+
quiet: options.quiet,
|
|
1008
|
+
});
|
|
1009
|
+
if (options.eval) {
|
|
1010
|
+
args.push('--eval', options.eval);
|
|
1011
|
+
}
|
|
1012
|
+
if (options.file) {
|
|
1013
|
+
args.push('--file', options.file);
|
|
1014
|
+
}
|
|
1015
|
+
return new Promise((resolve, reject) => {
|
|
1016
|
+
const proc = spawn(mongosh, args, {
|
|
1017
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1018
|
+
});
|
|
1019
|
+
let stdout = '';
|
|
1020
|
+
let stderr = '';
|
|
1021
|
+
proc.stdout?.on('data', (data) => {
|
|
1022
|
+
stdout += data.toString();
|
|
1023
|
+
});
|
|
1024
|
+
proc.stderr?.on('data', (data) => {
|
|
1025
|
+
stderr += data.toString();
|
|
1026
|
+
});
|
|
1027
|
+
const timeout = setTimeout(() => {
|
|
1028
|
+
proc.kill('SIGTERM');
|
|
1029
|
+
reject(new Error(`${options.file ? 'mongosh file execution' : 'mongosh command'} timed out after ${(options.timeoutMs ?? 10000) / 1000} seconds`));
|
|
1030
|
+
}, options.timeoutMs ?? 10000);
|
|
1031
|
+
proc.on('error', (error) => {
|
|
1032
|
+
clearTimeout(timeout);
|
|
1033
|
+
reject(error);
|
|
1034
|
+
});
|
|
1035
|
+
proc.on('close', (code) => {
|
|
1036
|
+
clearTimeout(timeout);
|
|
1037
|
+
if (code !== 0) {
|
|
1038
|
+
reject(new Error(stderr || `mongosh exited with code ${code}`));
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
resolve({ stdout, stderr });
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
977
1045
|
// Get FerretDB server status
|
|
978
1046
|
async status(container) {
|
|
979
1047
|
const { name, port } = container;
|
|
@@ -1024,50 +1092,22 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1024
1092
|
}
|
|
1025
1093
|
// Restore a backup
|
|
1026
1094
|
async restore(container, backupPath, options = {}) {
|
|
1027
|
-
const
|
|
1028
|
-
const database = options.database || 'ferretdb';
|
|
1029
|
-
if (!backendPort) {
|
|
1030
|
-
throw new Error('Backend port not set - start the container first');
|
|
1031
|
-
}
|
|
1095
|
+
const database = options.database || container.database || 'test';
|
|
1032
1096
|
// Validate database name before restore (defense-in-depth)
|
|
1033
1097
|
assertValidDatabaseName(database);
|
|
1034
|
-
|
|
1098
|
+
return restoreBackup(container, backupPath, {
|
|
1099
|
+
containerName: container.name,
|
|
1100
|
+
port: container.port,
|
|
1035
1101
|
database,
|
|
1036
1102
|
drop: options.drop !== false,
|
|
1103
|
+
sourceDatabase: options.sourceDatabase,
|
|
1104
|
+
containerVersion: container.version,
|
|
1037
1105
|
});
|
|
1038
|
-
// Restart FerretDB proxy so it picks up the restored data.
|
|
1039
|
-
// pg_restore writes directly to PostgreSQL, but FerretDB's proxy
|
|
1040
|
-
// caches schema/collection metadata in memory and won't see
|
|
1041
|
-
// the restored collections until restarted.
|
|
1042
|
-
const containerDir = paths.getContainerPath(container.name, {
|
|
1043
|
-
engine: ENGINE,
|
|
1044
|
-
});
|
|
1045
|
-
try {
|
|
1046
|
-
await this.stopFerretDBProcess(containerDir);
|
|
1047
|
-
// start() detects PG is already running and only launches the proxy
|
|
1048
|
-
await this.start(container);
|
|
1049
|
-
}
|
|
1050
|
-
catch (error) {
|
|
1051
|
-
const err = error;
|
|
1052
|
-
logWarning(`Failed to restart FerretDB proxy after restore: ${err.message}`);
|
|
1053
|
-
// Retry once — transient issues (port race, slow PG) can resolve on second attempt
|
|
1054
|
-
try {
|
|
1055
|
-
await this.stopFerretDBProcess(containerDir).catch(() => { });
|
|
1056
|
-
await this.start(container);
|
|
1057
|
-
}
|
|
1058
|
-
catch {
|
|
1059
|
-
throw new Error(`Restore succeeded but FerretDB proxy failed to restart. ` +
|
|
1060
|
-
`Data is safely in PostgreSQL. Run 'spindb start ${container.name}' to restart manually. ` +
|
|
1061
|
-
`Original error: ${err.message}`);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
return result;
|
|
1065
1106
|
}
|
|
1066
1107
|
// Get connection string (MongoDB-compatible)
|
|
1067
1108
|
getConnectionString(container, database) {
|
|
1068
1109
|
const { port } = container;
|
|
1069
1110
|
const db = database || container.database || 'test';
|
|
1070
|
-
// No authentication required - FerretDB runs with --no-auth for local dev
|
|
1071
1111
|
return `mongodb://127.0.0.1:${port}/${db}`;
|
|
1072
1112
|
}
|
|
1073
1113
|
// Get PostgreSQL backend connection string (for debugging)
|
|
@@ -1098,14 +1138,14 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1098
1138
|
}
|
|
1099
1139
|
// Open mongosh interactive shell
|
|
1100
1140
|
async connect(container, database) {
|
|
1101
|
-
const
|
|
1102
|
-
const db = database || 'test';
|
|
1141
|
+
const db = database || container.database || 'test';
|
|
1103
1142
|
const mongosh = await this.getMongoshPath();
|
|
1143
|
+
const args = await this.buildLocalMongoshArgs(container, db);
|
|
1104
1144
|
const spawnOptions = {
|
|
1105
1145
|
stdio: 'inherit',
|
|
1106
1146
|
};
|
|
1107
1147
|
return new Promise((resolve, reject) => {
|
|
1108
|
-
const proc = spawn(mongosh,
|
|
1148
|
+
const proc = spawn(mongosh, args, spawnOptions);
|
|
1109
1149
|
proc.on('error', reject);
|
|
1110
1150
|
proc.on('close', () => resolve());
|
|
1111
1151
|
});
|
|
@@ -1118,19 +1158,16 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1118
1158
|
*/
|
|
1119
1159
|
async createDatabase(container, database) {
|
|
1120
1160
|
assertValidDatabaseName(database);
|
|
1121
|
-
const { port } = container;
|
|
1122
1161
|
try {
|
|
1123
|
-
const mongosh = await this.getMongoshPath();
|
|
1124
1162
|
// Create a temp collection then immediately drop it to force database creation
|
|
1125
1163
|
// without leaving any visible marker collections.
|
|
1126
1164
|
// Pre-drop in case a previous run was interrupted and left a stale collection.
|
|
1127
1165
|
// NOTE: Use db.getCollection() instead of db._spindb_init shorthand because
|
|
1128
1166
|
// mongosh doesn't support shorthand notation for collection names starting with underscore.
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
await execAsync(cmd, { timeout: 10000 });
|
|
1167
|
+
await this.runLocalMongosh(container, database, {
|
|
1168
|
+
eval: 'try { db.getCollection("_spindb_init").drop(); } catch(e) {} db.createCollection("_spindb_init"); db.getCollection("_spindb_init").drop();',
|
|
1169
|
+
timeoutMs: 10000,
|
|
1170
|
+
});
|
|
1134
1171
|
logDebug(`Database "${database}" created via temp collection`);
|
|
1135
1172
|
}
|
|
1136
1173
|
catch (error) {
|
|
@@ -1141,13 +1178,11 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1141
1178
|
// Drop a database
|
|
1142
1179
|
async dropDatabase(container, database) {
|
|
1143
1180
|
assertValidDatabaseName(database);
|
|
1144
|
-
const { port } = container;
|
|
1145
1181
|
try {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
await execAsync(cmd, { timeout: 10000 });
|
|
1182
|
+
await this.runLocalMongosh(container, database, {
|
|
1183
|
+
eval: 'db.dropDatabase()',
|
|
1184
|
+
timeoutMs: 10000,
|
|
1185
|
+
});
|
|
1151
1186
|
}
|
|
1152
1187
|
catch (error) {
|
|
1153
1188
|
logDebug(`dropDatabase result: ${error}`);
|
|
@@ -1155,16 +1190,15 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1155
1190
|
}
|
|
1156
1191
|
// Get the size of the database in bytes
|
|
1157
1192
|
async getDatabaseSize(container) {
|
|
1158
|
-
const {
|
|
1193
|
+
const { database } = container;
|
|
1159
1194
|
const db = database || 'test';
|
|
1160
1195
|
assertValidDatabaseName(db);
|
|
1161
1196
|
try {
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const { stdout } = await execAsync(cmd, { timeout: 10000 });
|
|
1197
|
+
const { stdout } = await this.runLocalMongosh(container, db, {
|
|
1198
|
+
eval: 'JSON.stringify(db.stats())',
|
|
1199
|
+
quiet: true,
|
|
1200
|
+
timeoutMs: 10000,
|
|
1201
|
+
});
|
|
1168
1202
|
// Extract JSON from output
|
|
1169
1203
|
const firstBrace = stdout.indexOf('{');
|
|
1170
1204
|
const lastBrace = stdout.lastIndexOf('}');
|
|
@@ -1228,70 +1262,22 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1228
1262
|
}
|
|
1229
1263
|
// Run a JavaScript file or inline script against the database
|
|
1230
1264
|
async runScript(container, options) {
|
|
1231
|
-
const { port } = container;
|
|
1232
1265
|
const db = options.database || container.database || 'test';
|
|
1233
|
-
const mongosh = await this.getMongoshPath();
|
|
1234
1266
|
if (options.file) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return new Promise((resolve, reject) => {
|
|
1239
|
-
const proc = spawn(mongosh, [
|
|
1240
|
-
'--host',
|
|
1241
|
-
'127.0.0.1',
|
|
1242
|
-
'--port',
|
|
1243
|
-
String(port),
|
|
1244
|
-
db,
|
|
1245
|
-
'--file',
|
|
1246
|
-
options.file,
|
|
1247
|
-
], spawnOptions);
|
|
1248
|
-
proc.on('error', reject);
|
|
1249
|
-
proc.on('close', (code) => {
|
|
1250
|
-
if (code === 0) {
|
|
1251
|
-
resolve();
|
|
1252
|
-
}
|
|
1253
|
-
else {
|
|
1254
|
-
reject(new Error(`mongosh exited with code ${code}`));
|
|
1255
|
-
}
|
|
1256
|
-
});
|
|
1267
|
+
await this.runLocalMongosh(container, db, {
|
|
1268
|
+
file: options.file,
|
|
1269
|
+
timeoutMs: 60000,
|
|
1257
1270
|
});
|
|
1258
1271
|
}
|
|
1259
1272
|
else if (options.sql) {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
const proc = spawn(mongosh, ['--host', '127.0.0.1', '--port', String(port), db, '--eval', script], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1264
|
-
let stdout = '';
|
|
1265
|
-
let stderr = '';
|
|
1266
|
-
proc.stdout?.on('data', (data) => {
|
|
1267
|
-
stdout += data.toString();
|
|
1268
|
-
});
|
|
1269
|
-
proc.stderr?.on('data', (data) => {
|
|
1270
|
-
stderr += data.toString();
|
|
1271
|
-
});
|
|
1272
|
-
// 60 second timeout
|
|
1273
|
-
const timeout = setTimeout(() => {
|
|
1274
|
-
proc.kill('SIGTERM');
|
|
1275
|
-
reject(new Error('mongosh timed out after 60 seconds'));
|
|
1276
|
-
}, 60000);
|
|
1277
|
-
proc.on('error', (err) => {
|
|
1278
|
-
clearTimeout(timeout);
|
|
1279
|
-
reject(err);
|
|
1280
|
-
});
|
|
1281
|
-
proc.on('close', (code) => {
|
|
1282
|
-
clearTimeout(timeout);
|
|
1283
|
-
if (stdout)
|
|
1284
|
-
process.stdout.write(stdout);
|
|
1285
|
-
if (stderr)
|
|
1286
|
-
process.stderr.write(stderr);
|
|
1287
|
-
if (code === 0) {
|
|
1288
|
-
resolve();
|
|
1289
|
-
}
|
|
1290
|
-
else {
|
|
1291
|
-
reject(new Error(`mongosh exited with code ${code}`));
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1273
|
+
const { stdout, stderr } = await this.runLocalMongosh(container, db, {
|
|
1274
|
+
eval: options.sql,
|
|
1275
|
+
timeoutMs: 60000,
|
|
1294
1276
|
});
|
|
1277
|
+
if (stdout)
|
|
1278
|
+
process.stdout.write(stdout);
|
|
1279
|
+
if (stderr)
|
|
1280
|
+
process.stderr.write(stderr);
|
|
1295
1281
|
}
|
|
1296
1282
|
else {
|
|
1297
1283
|
throw new Error('Either file or sql option must be provided');
|
|
@@ -1324,42 +1310,33 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1324
1310
|
'Shell commands like "show dbs" and "use dbname" are not supported in executeQuery.');
|
|
1325
1311
|
}
|
|
1326
1312
|
}
|
|
1327
|
-
// Wrap query in async IIFE to properly await cursor.toArray()
|
|
1328
|
-
// This prevents JSON.stringify from serializing a Promise
|
|
1329
1313
|
const script = `(async () => { const res = ${normalizedQuery}; return JSON.stringify(res.toArray ? await res.toArray() : await Promise.resolve(res)); })()`;
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const pass = encodeURIComponent(options.password);
|
|
1355
|
-
const auth = user ? `${user}:${pass}@` : '';
|
|
1356
|
-
const uri = `mongodb://${auth}127.0.0.1:${port}/${db}`;
|
|
1357
|
-
args = [uri, '--quiet', '--eval', script];
|
|
1358
|
-
}
|
|
1359
|
-
else {
|
|
1360
|
-
args = [
|
|
1314
|
+
let args;
|
|
1315
|
+
if (options?.host) {
|
|
1316
|
+
const user = options.username ? encodeURIComponent(options.username) : '';
|
|
1317
|
+
const pass = options.password ? encodeURIComponent(options.password) : '';
|
|
1318
|
+
const auth = user ? `${user}:${pass}@` : '';
|
|
1319
|
+
const host = options.host;
|
|
1320
|
+
const isSrv = options.scheme === 'mongodb+srv';
|
|
1321
|
+
const scheme = isSrv ? 'mongodb+srv' : 'mongodb';
|
|
1322
|
+
const portSuffix = isSrv ? '' : `:${port}`;
|
|
1323
|
+
const sslParam = options.ssl && !isSrv ? 'tls=true' : '';
|
|
1324
|
+
const uri = `${scheme}://${auth}${host}${portSuffix}/${db}${sslParam ? `?${sslParam}` : ''}`;
|
|
1325
|
+
args = [uri, '--quiet', '--eval', script];
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
const savedCreds = await this.getLocalAuth(container.name);
|
|
1329
|
+
const connectHost = normalizeMongoHost(container.bindAddress);
|
|
1330
|
+
args = savedCreds
|
|
1331
|
+
? [
|
|
1332
|
+
buildMongoUri(port, db, savedCreds, connectHost),
|
|
1333
|
+
'--quiet',
|
|
1334
|
+
'--eval',
|
|
1335
|
+
script,
|
|
1336
|
+
]
|
|
1337
|
+
: [
|
|
1361
1338
|
'--host',
|
|
1362
|
-
|
|
1339
|
+
connectHost,
|
|
1363
1340
|
'--port',
|
|
1364
1341
|
String(port),
|
|
1365
1342
|
db,
|
|
@@ -1367,17 +1344,18 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1367
1344
|
'--eval',
|
|
1368
1345
|
script,
|
|
1369
1346
|
];
|
|
1370
|
-
|
|
1347
|
+
}
|
|
1348
|
+
const { stdout, stderr } = await new Promise((resolve, reject) => {
|
|
1371
1349
|
const proc = spawn(mongosh, args, {
|
|
1372
|
-
stdio: ['
|
|
1350
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1373
1351
|
});
|
|
1374
|
-
let
|
|
1375
|
-
let
|
|
1352
|
+
let stdoutBuf = '';
|
|
1353
|
+
let stderrBuf = '';
|
|
1376
1354
|
proc.stdout?.on('data', (data) => {
|
|
1377
|
-
|
|
1355
|
+
stdoutBuf += data.toString();
|
|
1378
1356
|
});
|
|
1379
1357
|
proc.stderr?.on('data', (data) => {
|
|
1380
|
-
|
|
1358
|
+
stderrBuf += data.toString();
|
|
1381
1359
|
});
|
|
1382
1360
|
const timeout = setTimeout(() => {
|
|
1383
1361
|
proc.kill('SIGTERM');
|
|
@@ -1390,87 +1368,67 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1390
1368
|
proc.on('close', (code) => {
|
|
1391
1369
|
clearTimeout(timeout);
|
|
1392
1370
|
if (code !== 0) {
|
|
1393
|
-
reject(new Error(
|
|
1371
|
+
reject(new Error(`${stderrBuf || `mongosh exited with code ${code}`}${stdoutBuf ? `\nOutput: ${stdoutBuf}` : ''}`));
|
|
1394
1372
|
return;
|
|
1395
1373
|
}
|
|
1396
|
-
|
|
1397
|
-
// Extract JSON from output (mongosh may output extra info)
|
|
1398
|
-
const jsonStart = stdout.indexOf('[');
|
|
1399
|
-
const jsonEnd = stdout.lastIndexOf(']');
|
|
1400
|
-
if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
|
|
1401
|
-
const jsonStr = stdout.substring(jsonStart, jsonEnd + 1);
|
|
1402
|
-
resolve(parseMongoDBResult(jsonStr));
|
|
1403
|
-
}
|
|
1404
|
-
else {
|
|
1405
|
-
// Try parsing as single object or scalar
|
|
1406
|
-
const objStart = stdout.indexOf('{');
|
|
1407
|
-
const objEnd = stdout.lastIndexOf('}');
|
|
1408
|
-
if (objStart !== -1 && objEnd !== -1 && objEnd > objStart) {
|
|
1409
|
-
const jsonStr = stdout.substring(objStart, objEnd + 1);
|
|
1410
|
-
resolve(parseMongoDBResult(jsonStr));
|
|
1411
|
-
}
|
|
1412
|
-
else {
|
|
1413
|
-
// Return as scalar result
|
|
1414
|
-
resolve({
|
|
1415
|
-
columns: ['result'],
|
|
1416
|
-
rows: [{ result: stdout.trim() }],
|
|
1417
|
-
rowCount: 1,
|
|
1418
|
-
});
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
catch (error) {
|
|
1423
|
-
reject(new Error(`Failed to parse query result: ${error instanceof Error ? error.message : error}`));
|
|
1424
|
-
}
|
|
1374
|
+
resolve({ stdout: stdoutBuf, stderr: stderrBuf });
|
|
1425
1375
|
});
|
|
1426
1376
|
});
|
|
1377
|
+
if (stderr && !stdout.trim()) {
|
|
1378
|
+
throw new Error(`${stderr}${stdout ? `\nOutput: ${stdout}` : ''}`);
|
|
1379
|
+
}
|
|
1380
|
+
const jsonMatch = stdout.match(/\[[\s\S]*\]|\{[\s\S]*\}/);
|
|
1381
|
+
if (!jsonMatch) {
|
|
1382
|
+
return {
|
|
1383
|
+
columns: ['result'],
|
|
1384
|
+
rows: [{ result: stdout.trim() }],
|
|
1385
|
+
rowCount: 1,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
return parseMongoDBResult(jsonMatch[0]);
|
|
1427
1389
|
}
|
|
1428
1390
|
/**
|
|
1429
1391
|
* List all user databases, excluding system databases (admin, config, local).
|
|
1430
1392
|
* FerretDB uses MongoDB protocol, so same approach as MongoDB.
|
|
1431
1393
|
*/
|
|
1432
1394
|
async listDatabases(container) {
|
|
1433
|
-
const { port } = container;
|
|
1434
1395
|
const mongosh = await this.getMongoshPath();
|
|
1435
1396
|
return new Promise((resolve, reject) => {
|
|
1436
|
-
// Use JSON output for reliable parsing
|
|
1437
1397
|
const script = `JSON.stringify(db.adminCommand({listDatabases: 1}).databases.map(d => d.name))`;
|
|
1438
|
-
const
|
|
1439
|
-
'
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
'--
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
}
|
|
1473
|
-
});
|
|
1398
|
+
const launch = async () => {
|
|
1399
|
+
const args = await this.buildLocalMongoshArgs(container, 'admin', {
|
|
1400
|
+
quiet: true,
|
|
1401
|
+
});
|
|
1402
|
+
args.push('--eval', script);
|
|
1403
|
+
const proc = spawn(mongosh, args, {
|
|
1404
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1405
|
+
});
|
|
1406
|
+
let stdout = '';
|
|
1407
|
+
let stderr = '';
|
|
1408
|
+
proc.stdout?.on('data', (data) => {
|
|
1409
|
+
stdout += data.toString();
|
|
1410
|
+
});
|
|
1411
|
+
proc.stderr?.on('data', (data) => {
|
|
1412
|
+
stderr += data.toString();
|
|
1413
|
+
});
|
|
1414
|
+
proc.on('error', reject);
|
|
1415
|
+
proc.on('close', (code) => {
|
|
1416
|
+
if (code !== 0) {
|
|
1417
|
+
reject(new Error(stderr || `mongosh exited with code ${code}`));
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
try {
|
|
1421
|
+
const allDatabases = JSON.parse(stdout.trim());
|
|
1422
|
+
const systemDatabases = ['admin', 'config', 'local'];
|
|
1423
|
+
const databases = allDatabases.filter((db) => !systemDatabases.includes(db));
|
|
1424
|
+
resolve(databases);
|
|
1425
|
+
}
|
|
1426
|
+
catch (error) {
|
|
1427
|
+
reject(new Error(`Failed to parse database list: ${error}`));
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
};
|
|
1431
|
+
void launch().catch(reject);
|
|
1474
1432
|
});
|
|
1475
1433
|
}
|
|
1476
1434
|
async createUser(container, options) {
|
|
@@ -1480,12 +1438,9 @@ export class FerretDBEngine extends BaseEngine {
|
|
|
1480
1438
|
const db = database ?? container.database ?? 'admin';
|
|
1481
1439
|
assertValidDatabaseName(db);
|
|
1482
1440
|
const mongosh = await this.getMongoshPath();
|
|
1483
|
-
// Same as MongoDB - auth disabled with --no-auth but user is still created
|
|
1484
|
-
// Use JSON.stringify for password to safely escape all special characters in JS context
|
|
1485
|
-
// Pass script via stdin to avoid exposing passwords in process listings
|
|
1486
1441
|
const jsonPwd = JSON.stringify(password);
|
|
1487
1442
|
const script = `db.getSiblingDB('${db}').createUser({user:'${username}',pwd:${jsonPwd},roles:[{role:'readWrite',db:'${db}'}]})`;
|
|
1488
|
-
const mongoshArgs =
|
|
1443
|
+
const mongoshArgs = await this.buildLocalMongoshArgs(container, 'admin');
|
|
1489
1444
|
const runMongoshViaStdin = (js) => new Promise((resolve, reject) => {
|
|
1490
1445
|
const proc = spawn(mongosh, mongoshArgs, {
|
|
1491
1446
|
stdio: ['pipe', 'pipe', 'pipe'],
|