2026-02-01 14:34:07 +00:00
|
|
|
import * as plugins from '../../tsmdb.plugins.js';
|
2026-01-31 11:33:11 +00:00
|
|
|
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* AdminHandler - Handles administrative commands
|
|
|
|
|
*/
|
|
|
|
|
export class AdminHandler implements ICommandHandler {
|
|
|
|
|
async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { command } = context;
|
|
|
|
|
|
|
|
|
|
// Determine which command to handle
|
|
|
|
|
if (command.ping !== undefined) {
|
|
|
|
|
return this.handlePing(context);
|
|
|
|
|
} else if (command.listDatabases !== undefined) {
|
|
|
|
|
return this.handleListDatabases(context);
|
|
|
|
|
} else if (command.listCollections !== undefined) {
|
|
|
|
|
return this.handleListCollections(context);
|
|
|
|
|
} else if (command.drop !== undefined) {
|
|
|
|
|
return this.handleDrop(context);
|
|
|
|
|
} else if (command.dropDatabase !== undefined) {
|
|
|
|
|
return this.handleDropDatabase(context);
|
|
|
|
|
} else if (command.create !== undefined) {
|
|
|
|
|
return this.handleCreate(context);
|
|
|
|
|
} else if (command.serverStatus !== undefined) {
|
|
|
|
|
return this.handleServerStatus(context);
|
|
|
|
|
} else if (command.buildInfo !== undefined) {
|
|
|
|
|
return this.handleBuildInfo(context);
|
|
|
|
|
} else if (command.whatsmyuri !== undefined) {
|
|
|
|
|
return this.handleWhatsMyUri(context);
|
|
|
|
|
} else if (command.getLog !== undefined) {
|
|
|
|
|
return this.handleGetLog(context);
|
|
|
|
|
} else if (command.hostInfo !== undefined) {
|
|
|
|
|
return this.handleHostInfo(context);
|
|
|
|
|
} else if (command.replSetGetStatus !== undefined) {
|
|
|
|
|
return this.handleReplSetGetStatus(context);
|
|
|
|
|
} else if (command.saslStart !== undefined) {
|
|
|
|
|
return this.handleSaslStart(context);
|
|
|
|
|
} else if (command.saslContinue !== undefined) {
|
|
|
|
|
return this.handleSaslContinue(context);
|
|
|
|
|
} else if (command.endSessions !== undefined) {
|
|
|
|
|
return this.handleEndSessions(context);
|
|
|
|
|
} else if (command.abortTransaction !== undefined) {
|
|
|
|
|
return this.handleAbortTransaction(context);
|
|
|
|
|
} else if (command.commitTransaction !== undefined) {
|
|
|
|
|
return this.handleCommitTransaction(context);
|
|
|
|
|
} else if (command.collStats !== undefined) {
|
|
|
|
|
return this.handleCollStats(context);
|
|
|
|
|
} else if (command.dbStats !== undefined) {
|
|
|
|
|
return this.handleDbStats(context);
|
|
|
|
|
} else if (command.connectionStatus !== undefined) {
|
|
|
|
|
return this.handleConnectionStatus(context);
|
|
|
|
|
} else if (command.currentOp !== undefined) {
|
|
|
|
|
return this.handleCurrentOp(context);
|
|
|
|
|
} else if (command.collMod !== undefined) {
|
|
|
|
|
return this.handleCollMod(context);
|
|
|
|
|
} else if (command.renameCollection !== undefined) {
|
|
|
|
|
return this.handleRenameCollection(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: 'Unknown admin command',
|
|
|
|
|
code: 59,
|
|
|
|
|
codeName: 'CommandNotFound',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle ping command
|
|
|
|
|
*/
|
|
|
|
|
private async handlePing(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle listDatabases command
|
|
|
|
|
*/
|
|
|
|
|
private async handleListDatabases(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, command } = context;
|
|
|
|
|
|
|
|
|
|
const dbNames = await storage.listDatabases();
|
|
|
|
|
const nameOnly = command.nameOnly || false;
|
|
|
|
|
|
|
|
|
|
if (nameOnly) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
databases: dbNames.map(name => ({ name })),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build database list with sizes
|
|
|
|
|
const databases: plugins.bson.Document[] = [];
|
|
|
|
|
let totalSize = 0;
|
|
|
|
|
|
|
|
|
|
for (const name of dbNames) {
|
|
|
|
|
const collections = await storage.listCollections(name);
|
|
|
|
|
let dbSize = 0;
|
|
|
|
|
|
|
|
|
|
for (const collName of collections) {
|
|
|
|
|
const docs = await storage.findAll(name, collName);
|
|
|
|
|
// Estimate size (rough approximation)
|
|
|
|
|
dbSize += docs.reduce((sum, doc) => sum + JSON.stringify(doc).length, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totalSize += dbSize;
|
|
|
|
|
|
|
|
|
|
databases.push({
|
|
|
|
|
name,
|
|
|
|
|
sizeOnDisk: dbSize,
|
|
|
|
|
empty: dbSize === 0,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
databases,
|
|
|
|
|
totalSize,
|
|
|
|
|
totalSizeMb: totalSize / (1024 * 1024),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle listCollections command
|
|
|
|
|
*/
|
|
|
|
|
private async handleListCollections(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, database, command } = context;
|
|
|
|
|
|
|
|
|
|
const filter = command.filter || {};
|
|
|
|
|
const nameOnly = command.nameOnly || false;
|
|
|
|
|
const cursor = command.cursor || {};
|
|
|
|
|
const batchSize = cursor.batchSize || 101;
|
|
|
|
|
|
|
|
|
|
const collNames = await storage.listCollections(database);
|
|
|
|
|
|
|
|
|
|
let collections: plugins.bson.Document[] = [];
|
|
|
|
|
|
|
|
|
|
for (const name of collNames) {
|
|
|
|
|
// Apply name filter
|
|
|
|
|
if (filter.name && filter.name !== name) {
|
|
|
|
|
// Check regex
|
|
|
|
|
if (filter.name.$regex) {
|
|
|
|
|
const regex = new RegExp(filter.name.$regex, filter.name.$options);
|
|
|
|
|
if (!regex.test(name)) continue;
|
|
|
|
|
} else {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nameOnly) {
|
|
|
|
|
collections.push({ name });
|
|
|
|
|
} else {
|
|
|
|
|
collections.push({
|
|
|
|
|
name,
|
|
|
|
|
type: 'collection',
|
|
|
|
|
options: {},
|
|
|
|
|
info: {
|
|
|
|
|
readOnly: false,
|
|
|
|
|
uuid: new plugins.bson.UUID(),
|
|
|
|
|
},
|
|
|
|
|
idIndex: {
|
|
|
|
|
v: 2,
|
|
|
|
|
key: { _id: 1 },
|
|
|
|
|
name: '_id_',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
cursor: {
|
|
|
|
|
id: plugins.bson.Long.fromNumber(0),
|
|
|
|
|
ns: `${database}.$cmd.listCollections`,
|
|
|
|
|
firstBatch: collections,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle drop command (drop collection)
|
|
|
|
|
*/
|
|
|
|
|
private async handleDrop(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, database, command } = context;
|
|
|
|
|
|
|
|
|
|
const collection = command.drop;
|
|
|
|
|
|
|
|
|
|
const existed = await storage.dropCollection(database, collection);
|
|
|
|
|
|
|
|
|
|
if (!existed) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: `ns not found ${database}.${collection}`,
|
|
|
|
|
code: 26,
|
|
|
|
|
codeName: 'NamespaceNotFound',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { ok: 1, ns: `${database}.${collection}` };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle dropDatabase command
|
|
|
|
|
*/
|
|
|
|
|
private async handleDropDatabase(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, database } = context;
|
|
|
|
|
|
|
|
|
|
await storage.dropDatabase(database);
|
|
|
|
|
|
|
|
|
|
return { ok: 1, dropped: database };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle create command (create collection)
|
|
|
|
|
*/
|
|
|
|
|
private async handleCreate(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, database, command } = context;
|
|
|
|
|
|
|
|
|
|
const collection = command.create;
|
|
|
|
|
|
|
|
|
|
// Check if already exists
|
|
|
|
|
const exists = await storage.collectionExists(database, collection);
|
|
|
|
|
if (exists) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: `Collection ${database}.${collection} already exists.`,
|
|
|
|
|
code: 48,
|
|
|
|
|
codeName: 'NamespaceExists',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await storage.createCollection(database, collection);
|
|
|
|
|
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle serverStatus command
|
|
|
|
|
*/
|
|
|
|
|
private async handleServerStatus(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { server } = context;
|
|
|
|
|
|
|
|
|
|
const uptime = server.getUptime();
|
|
|
|
|
const connections = server.getConnectionCount();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
host: `${server.host}:${server.port}`,
|
|
|
|
|
version: '7.0.0',
|
2026-02-01 14:34:07 +00:00
|
|
|
process: 'tsmdb',
|
2026-01-31 11:33:11 +00:00
|
|
|
pid: process.pid,
|
|
|
|
|
uptime,
|
|
|
|
|
uptimeMillis: uptime * 1000,
|
|
|
|
|
uptimeEstimate: uptime,
|
|
|
|
|
localTime: new Date(),
|
|
|
|
|
mem: {
|
|
|
|
|
resident: Math.floor(process.memoryUsage().rss / (1024 * 1024)),
|
|
|
|
|
virtual: Math.floor(process.memoryUsage().heapTotal / (1024 * 1024)),
|
|
|
|
|
supported: true,
|
|
|
|
|
},
|
|
|
|
|
connections: {
|
|
|
|
|
current: connections,
|
|
|
|
|
available: 1000 - connections,
|
|
|
|
|
totalCreated: connections,
|
|
|
|
|
active: connections,
|
|
|
|
|
},
|
|
|
|
|
network: {
|
|
|
|
|
bytesIn: 0,
|
|
|
|
|
bytesOut: 0,
|
|
|
|
|
numRequests: 0,
|
|
|
|
|
},
|
|
|
|
|
storageEngine: {
|
2026-02-01 14:34:07 +00:00
|
|
|
name: 'tsmdb',
|
2026-01-31 11:33:11 +00:00
|
|
|
supportsCommittedReads: true,
|
|
|
|
|
persistent: false,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle buildInfo command
|
|
|
|
|
*/
|
|
|
|
|
private async handleBuildInfo(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
version: '7.0.0',
|
2026-02-01 14:34:07 +00:00
|
|
|
gitVersion: 'tsmdb',
|
2026-01-31 11:33:11 +00:00
|
|
|
modules: [],
|
|
|
|
|
allocator: 'system',
|
|
|
|
|
javascriptEngine: 'none',
|
|
|
|
|
sysInfo: 'deprecated',
|
|
|
|
|
versionArray: [7, 0, 0, 0],
|
|
|
|
|
openssl: {
|
|
|
|
|
running: 'disabled',
|
|
|
|
|
compiled: 'disabled',
|
|
|
|
|
},
|
|
|
|
|
buildEnvironment: {
|
2026-02-01 14:34:07 +00:00
|
|
|
distmod: 'tsmdb',
|
2026-01-31 11:33:11 +00:00
|
|
|
distarch: process.arch,
|
|
|
|
|
cc: '',
|
|
|
|
|
ccflags: '',
|
|
|
|
|
cxx: '',
|
|
|
|
|
cxxflags: '',
|
|
|
|
|
linkflags: '',
|
|
|
|
|
target_arch: process.arch,
|
|
|
|
|
target_os: process.platform,
|
|
|
|
|
},
|
|
|
|
|
bits: 64,
|
|
|
|
|
debug: false,
|
|
|
|
|
maxBsonObjectSize: 16777216,
|
2026-02-01 14:34:07 +00:00
|
|
|
storageEngines: ['tsmdb'],
|
2026-01-31 11:33:11 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle whatsmyuri command
|
|
|
|
|
*/
|
|
|
|
|
private async handleWhatsMyUri(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { server } = context;
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
you: `127.0.0.1:${server.port}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle getLog command
|
|
|
|
|
*/
|
|
|
|
|
private async handleGetLog(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { command } = context;
|
|
|
|
|
|
|
|
|
|
if (command.getLog === '*') {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
names: ['global', 'startupWarnings'],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
totalLinesWritten: 0,
|
|
|
|
|
log: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle hostInfo command
|
|
|
|
|
*/
|
|
|
|
|
private async handleHostInfo(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
system: {
|
|
|
|
|
currentTime: new Date(),
|
|
|
|
|
hostname: 'localhost',
|
|
|
|
|
cpuAddrSize: 64,
|
|
|
|
|
memSizeMB: Math.floor(process.memoryUsage().heapTotal / (1024 * 1024)),
|
|
|
|
|
numCores: 1,
|
|
|
|
|
cpuArch: process.arch,
|
|
|
|
|
numaEnabled: false,
|
|
|
|
|
},
|
|
|
|
|
os: {
|
|
|
|
|
type: process.platform,
|
|
|
|
|
name: process.platform,
|
|
|
|
|
version: process.version,
|
|
|
|
|
},
|
|
|
|
|
extra: {},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle replSetGetStatus command
|
|
|
|
|
*/
|
|
|
|
|
private async handleReplSetGetStatus(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
// We're standalone, not a replica set
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: 'not running with --replSet',
|
|
|
|
|
code: 76,
|
|
|
|
|
codeName: 'NoReplicationEnabled',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle saslStart command (authentication)
|
|
|
|
|
*/
|
|
|
|
|
private async handleSaslStart(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
// We don't require authentication, but we need to respond properly
|
|
|
|
|
// to let drivers know auth is "successful"
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
conversationId: 1,
|
|
|
|
|
done: true,
|
|
|
|
|
payload: Buffer.from([]),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle saslContinue command
|
|
|
|
|
*/
|
|
|
|
|
private async handleSaslContinue(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
conversationId: 1,
|
|
|
|
|
done: true,
|
|
|
|
|
payload: Buffer.from([]),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle endSessions command
|
|
|
|
|
*/
|
|
|
|
|
private async handleEndSessions(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle abortTransaction command
|
|
|
|
|
*/
|
|
|
|
|
private async handleAbortTransaction(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
// Transactions are not fully supported, but acknowledge the command
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle commitTransaction command
|
|
|
|
|
*/
|
|
|
|
|
private async handleCommitTransaction(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
// Transactions are not fully supported, but acknowledge the command
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle collStats command
|
|
|
|
|
*/
|
|
|
|
|
private async handleCollStats(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, database, command } = context;
|
|
|
|
|
|
|
|
|
|
const collection = command.collStats;
|
|
|
|
|
|
|
|
|
|
const exists = await storage.collectionExists(database, collection);
|
|
|
|
|
if (!exists) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: `ns not found ${database}.${collection}`,
|
|
|
|
|
code: 26,
|
|
|
|
|
codeName: 'NamespaceNotFound',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const docs = await storage.findAll(database, collection);
|
|
|
|
|
const size = docs.reduce((sum, doc) => sum + JSON.stringify(doc).length, 0);
|
|
|
|
|
const count = docs.length;
|
|
|
|
|
const avgObjSize = count > 0 ? size / count : 0;
|
|
|
|
|
|
|
|
|
|
const indexes = await storage.getIndexes(database, collection);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
ns: `${database}.${collection}`,
|
|
|
|
|
count,
|
|
|
|
|
size,
|
|
|
|
|
avgObjSize,
|
|
|
|
|
storageSize: size,
|
|
|
|
|
totalIndexSize: 0,
|
|
|
|
|
indexSizes: indexes.reduce((acc: any, idx: any) => {
|
|
|
|
|
acc[idx.name] = 0;
|
|
|
|
|
return acc;
|
|
|
|
|
}, {}),
|
|
|
|
|
nindexes: indexes.length,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle dbStats command
|
|
|
|
|
*/
|
|
|
|
|
private async handleDbStats(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, database } = context;
|
|
|
|
|
|
|
|
|
|
const collections = await storage.listCollections(database);
|
|
|
|
|
let totalSize = 0;
|
|
|
|
|
let totalObjects = 0;
|
|
|
|
|
|
|
|
|
|
for (const collName of collections) {
|
|
|
|
|
const docs = await storage.findAll(database, collName);
|
|
|
|
|
totalObjects += docs.length;
|
|
|
|
|
totalSize += docs.reduce((sum, doc) => sum + JSON.stringify(doc).length, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
db: database,
|
|
|
|
|
collections: collections.length,
|
|
|
|
|
views: 0,
|
|
|
|
|
objects: totalObjects,
|
|
|
|
|
avgObjSize: totalObjects > 0 ? totalSize / totalObjects : 0,
|
|
|
|
|
dataSize: totalSize,
|
|
|
|
|
storageSize: totalSize,
|
|
|
|
|
indexes: collections.length, // At least _id index per collection
|
|
|
|
|
indexSize: 0,
|
|
|
|
|
totalSize,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle connectionStatus command
|
|
|
|
|
*/
|
|
|
|
|
private async handleConnectionStatus(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
authInfo: {
|
|
|
|
|
authenticatedUsers: [],
|
|
|
|
|
authenticatedUserRoles: [],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle currentOp command
|
|
|
|
|
*/
|
|
|
|
|
private async handleCurrentOp(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
return {
|
|
|
|
|
ok: 1,
|
|
|
|
|
inprog: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle collMod command
|
|
|
|
|
*/
|
|
|
|
|
private async handleCollMod(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
// We don't support modifying collection options, but acknowledge the command
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle renameCollection command
|
|
|
|
|
*/
|
|
|
|
|
private async handleRenameCollection(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
|
|
|
const { storage, command } = context;
|
|
|
|
|
|
|
|
|
|
const from = command.renameCollection;
|
|
|
|
|
const to = command.to;
|
|
|
|
|
const dropTarget = command.dropTarget || false;
|
|
|
|
|
|
|
|
|
|
if (!from || !to) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: 'renameCollection requires both source and target',
|
|
|
|
|
code: 2,
|
|
|
|
|
codeName: 'BadValue',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse namespace (format: "db.collection")
|
|
|
|
|
const fromParts = from.split('.');
|
|
|
|
|
const toParts = to.split('.');
|
|
|
|
|
|
|
|
|
|
if (fromParts.length < 2 || toParts.length < 2) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: 'Invalid namespace format',
|
|
|
|
|
code: 73,
|
|
|
|
|
codeName: 'InvalidNamespace',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fromDb = fromParts[0];
|
|
|
|
|
const fromColl = fromParts.slice(1).join('.');
|
|
|
|
|
const toDb = toParts[0];
|
|
|
|
|
const toColl = toParts.slice(1).join('.');
|
|
|
|
|
|
|
|
|
|
// Check if source exists
|
|
|
|
|
const sourceExists = await storage.collectionExists(fromDb, fromColl);
|
|
|
|
|
if (!sourceExists) {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: `source namespace ${from} does not exist`,
|
|
|
|
|
code: 26,
|
|
|
|
|
codeName: 'NamespaceNotFound',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if target exists
|
|
|
|
|
const targetExists = await storage.collectionExists(toDb, toColl);
|
|
|
|
|
if (targetExists) {
|
|
|
|
|
if (dropTarget) {
|
|
|
|
|
await storage.dropCollection(toDb, toColl);
|
|
|
|
|
} else {
|
|
|
|
|
return {
|
|
|
|
|
ok: 0,
|
|
|
|
|
errmsg: `target namespace ${to} already exists`,
|
|
|
|
|
code: 48,
|
|
|
|
|
codeName: 'NamespaceExists',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same database rename
|
|
|
|
|
if (fromDb === toDb) {
|
|
|
|
|
await storage.renameCollection(fromDb, fromColl, toColl);
|
|
|
|
|
} else {
|
|
|
|
|
// Cross-database rename: copy documents then drop source
|
|
|
|
|
await storage.createCollection(toDb, toColl);
|
|
|
|
|
const docs = await storage.findAll(fromDb, fromColl);
|
|
|
|
|
|
|
|
|
|
for (const doc of docs) {
|
|
|
|
|
await storage.insertOne(toDb, toColl, doc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await storage.dropCollection(fromDb, fromColl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { ok: 1 };
|
|
|
|
|
}
|
|
|
|
|
}
|