From b414a3b6c08a306c1ac244efa1ce07a88a85bebe Mon Sep 17 00:00:00 2001 From: jmarkerink Date: Mon, 12 Jan 2026 18:51:44 +0100 Subject: [PATCH 1/5] feat: introduce enum for Commands wrapped in DatabaseCommand value object --- .../de/bwaldvogel/mongo/MongoBackend.java | 3 +- .../de/bwaldvogel/mongo/MongoDatabase.java | 3 +- .../mongo/backend/AbstractMongoBackend.java | 194 ++++++++++-------- .../mongo/backend/AbstractMongoDatabase.java | 134 ++++++------ .../de/bwaldvogel/mongo/backend/Command.java | 75 +++++++ .../mongo/backend/DatabaseCommand.java | 32 +++ .../mongo/backend/ReadOnlyProxy.java | 50 +++-- .../mongo/wire/MongoDatabaseHandler.java | 12 +- .../backend/AbstractMongoBackendTest.java | 6 +- .../mongo/wire/MongoDatabaseHandlerTest.java | 8 +- .../mongo/backend/AbstractBackendTest.java | 14 +- .../mongo/backend/AbstractTest.java | 6 +- .../mongo/RealMongoBackendTest.java | 8 +- 13 files changed, 337 insertions(+), 208 deletions(-) create mode 100644 core/src/main/java/de/bwaldvogel/mongo/backend/Command.java create mode 100644 core/src/main/java/de/bwaldvogel/mongo/backend/DatabaseCommand.java diff --git a/core/src/main/java/de/bwaldvogel/mongo/MongoBackend.java b/core/src/main/java/de/bwaldvogel/mongo/MongoBackend.java index 1c003a9a..ffdf0d03 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/MongoBackend.java +++ b/core/src/main/java/de/bwaldvogel/mongo/MongoBackend.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.List; +import de.bwaldvogel.mongo.backend.DatabaseCommand; import de.bwaldvogel.mongo.backend.QueryResult; import de.bwaldvogel.mongo.bson.Document; import de.bwaldvogel.mongo.wire.message.MongoMessage; @@ -14,7 +15,7 @@ public interface MongoBackend { void handleClose(Channel channel); - Document handleCommand(Channel channel, String database, String command, Document query); + Document handleCommand(Channel channel, String database, DatabaseCommand command, Document query); QueryResult handleQuery(MongoQuery query); diff --git a/core/src/main/java/de/bwaldvogel/mongo/MongoDatabase.java b/core/src/main/java/de/bwaldvogel/mongo/MongoDatabase.java index cc632e9e..62fdaad5 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/MongoDatabase.java +++ b/core/src/main/java/de/bwaldvogel/mongo/MongoDatabase.java @@ -1,6 +1,7 @@ package de.bwaldvogel.mongo; import de.bwaldvogel.mongo.backend.CollectionOptions; +import de.bwaldvogel.mongo.backend.DatabaseCommand; import de.bwaldvogel.mongo.backend.DatabaseResolver; import de.bwaldvogel.mongo.backend.QueryResult; import de.bwaldvogel.mongo.bson.Document; @@ -14,7 +15,7 @@ public interface MongoDatabase { void handleClose(Channel channel); - Document handleCommand(Channel channel, String command, Document query, DatabaseResolver databaseResolver, Oplog oplog); + Document handleCommand(Channel channel, DatabaseCommand command, Document query, DatabaseResolver databaseResolver, Oplog oplog); QueryResult handleQuery(MongoQuery query); diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java index eaf9dfaf..8c943e4e 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,57 +151,69 @@ private Document getLog(String argument) { return response; } - private Document handleAdminCommand(String command, Document query) { - if (command.equalsIgnoreCase("listdatabases")) { - List databases = listDatabaseNames().stream() - .sorted() - .map(databaseName -> { - MongoDatabase database = openOrCreateDatabase(databaseName); - Document dbObj = new Document("name", database.getDatabaseName()); - dbObj.put("empty", Boolean.valueOf(database.isEmpty())); - return dbObj; - }) - .collect(Collectors.toList()); - Document response = new Document(); - response.put("databases", databases); - Utils.markOkay(response); - return response; - } else if (command.equalsIgnoreCase("find")) { - String collectionName = (String) query.get(command); - if (collectionName.equals("$cmd.sys.inprog")) { - return Utils.firstBatchCursorResponse(collectionName, new Document("inprog", Collections.emptyList())); - } else { - throw new NoSuchCommandException(new Document(command, collectionName).toString()); + private Document handleAdminCommand(DatabaseCommand command, Document query) { + switch (command.getCommand()) { + case LIST_DATABASES: { + List databases = listDatabaseNames().stream() + .sorted() + .map(databaseName -> { + MongoDatabase database = openOrCreateDatabase(databaseName); + Document dbObj = new Document("name", database.getDatabaseName()); + dbObj.put("empty", Boolean.valueOf(database.isEmpty())); + return dbObj; + }) + .toList(); + Document response = new Document(); + response.put("databases", databases); + Utils.markOkay(response); + return response; + } + case FIND: { + String collectionName = query.get(command.getQueryValue()).toString(); + if (collectionName.equals("$cmd.sys.inprog")) { + return Utils.firstBatchCursorResponse(collectionName, new Document("inprog", Collections.emptyList())); + } else { + throw new NoSuchCommandException(new Document(command.getQueryValue(), collectionName).toString()); + } + } + case REPL_SET_GET_STATUS: { + throw new NoReplicationEnabledException(); + } + case GET_LOG: { + final Object argument = query.get(command.getQueryValue()); + return getLog(argument == null ? null : argument.toString()); + } + case RENAME_COLLECTION: { + return handleRenameCollection(command.getQueryValue(), query); + } + case GET_LAST_ERROR: { + log.debug("getLastError on admin database"); + return successResponse(); + } + case CONNECTION_STATUS: { + Document response = new Document(); + response.append("authInfo", new Document() + .append("authenticatedUsers", Collections.emptyList()) + .append("authenticatedUserRoles", Collections.emptyList()) + ); + Utils.markOkay(response); + return response; + } + case HOST_INFO: { + return handleHostInfo(); + } + case GET_CMD_LINE_OPTS: { + return handleGetCmdLineOpts(); + } + case GET_FREE_MONITORING_STATUS: { + return handleGetFreeMonitoringStatus(); } - } else if (command.equalsIgnoreCase("replSetGetStatus")) { - throw new NoReplicationEnabledException(); - } else if (command.equalsIgnoreCase("getLog")) { - final Object argument = query.get(command); - return getLog(argument == null ? null : argument.toString()); - } else if (command.equalsIgnoreCase("renameCollection")) { - return handleRenameCollection(command, query); - } else if (command.equalsIgnoreCase("getLastError")) { - log.debug("getLastError on admin database"); - return successResponse(); - } else if (command.equalsIgnoreCase("connectionStatus")) { - Document response = new Document(); - response.append("authInfo", new Document() - .append("authenticatedUsers", Collections.emptyList()) - .append("authenticatedUserRoles", Collections.emptyList()) - ); - Utils.markOkay(response); - return response; - } else if (command.equalsIgnoreCase("hostInfo")) { - return handleHostInfo(); - } else if (command.equalsIgnoreCase("getCmdLineOpts")) { - return handleGetCmdLineOpts(); - } else if (command.equalsIgnoreCase("getFreeMonitoringStatus")) { - return handleGetFreeMonitoringStatus(); - } else if (command.equalsIgnoreCase("endSessions")) { - log.debug("endSessions on admin database"); - return successResponse(); - } else { - throw new NoSuchCommandException(command); + case END_SESSIONS: { + log.debug("endSessions on admin database"); + return successResponse(); + } + default: + throw new NoSuchCommandException(command.getQueryValue()); } } @@ -299,45 +310,46 @@ private MongoCollection resolveCollection(String namespace) { protected abstract MongoDatabase openOrCreateDatabase(String databaseName); @Override - public Document handleCommand(Channel channel, String databaseName, String command, Document query) { - if (command.equalsIgnoreCase("whatsmyuri")) { - Document response = new Document(); - InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress(); - response.put("you", remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort()); - Utils.markOkay(response); - return response; - } else if (command.equalsIgnoreCase("ismaster")) { - Document response = new Document("ismaster", Boolean.TRUE); - response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); - response.put("maxWriteBatchSize", Integer.valueOf(MongoWireProtocolHandler.MAX_WRITE_BATCH_SIZE)); - response.put("maxMessageSizeBytes", Integer.valueOf(MongoWireProtocolHandler.MAX_MESSAGE_SIZE_BYTES)); - response.put("maxWireVersion", Integer.valueOf(version.getWireVersion())); - response.put("minWireVersion", Integer.valueOf(0)); - response.put("localTime", Instant.now(clock)); - Utils.markOkay(response); - return response; - } else if (command.equalsIgnoreCase("buildinfo")) { - Document response = new Document("version", version.toVersionString()); - response.put("versionArray", version.getVersionArray()); - response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); - Utils.markOkay(response); - return response; - } else if (command.equalsIgnoreCase("dropDatabase")) { - return handleDropDatabase(databaseName); - } else if (command.equalsIgnoreCase("getMore")) { - return handleGetMore(databaseName, command, query); - } else if (command.equalsIgnoreCase("killCursors")) { - return handleKillCursors(query); - } else if (command.equalsIgnoreCase("ping")) { - return successResponse(); - } else if (command.equalsIgnoreCase("serverStatus")) { - return getServerStatus(); - } else if (databaseName.equals(ADMIN_DB_NAME)) { - return handleAdminCommand(command, query); - } - - MongoDatabase mongoDatabase = resolveDatabase(databaseName); - return mongoDatabase.handleCommand(channel, command, query, this::resolveDatabase, oplog); + public Document handleCommand(Channel channel, String databaseName, DatabaseCommand command, Document query) { + return switch (command.getCommand()) { + case WHATS_MY_URI -> { + Document response = new Document(); + InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress(); + response.put("you", remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort()); + Utils.markOkay(response); + yield response; + } + case IS_MASTER -> { + Document response = new Document("ismaster", Boolean.TRUE); + response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); + response.put("maxWriteBatchSize", Integer.valueOf(MongoWireProtocolHandler.MAX_WRITE_BATCH_SIZE)); + response.put("maxMessageSizeBytes", Integer.valueOf(MongoWireProtocolHandler.MAX_MESSAGE_SIZE_BYTES)); + response.put("maxWireVersion", Integer.valueOf(version.getWireVersion())); + response.put("minWireVersion", Integer.valueOf(0)); + response.put("localTime", Instant.now(clock)); + Utils.markOkay(response); + yield response; + } + case BUILD_INFO -> { + Document response = new Document("version", version.toVersionString()); + response.put("versionArray", version.getVersionArray()); + response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); + Utils.markOkay(response); + yield response; + } + case DROP_DATABASE -> handleDropDatabase(databaseName); + case GET_MORE -> handleGetMore(databaseName, command.getQueryValue(), query); + case KILL_CURSORS -> handleKillCursors(query); + case PING -> successResponse(); + case SERVER_STATUS -> getServerStatus(); + default -> { + if (databaseName.equals(ADMIN_DB_NAME)) { + yield handleAdminCommand(command, query); + } + MongoDatabase mongoDatabase = resolveDatabase(databaseName); + yield mongoDatabase.handleCommand(channel, command, query, this::resolveDatabase, oplog); + } + }; } @Override @@ -409,7 +421,7 @@ public Document handleMessage(MongoMessage message) { Channel channel = message.getChannel(); String databaseName = message.getDatabaseName(); Document query = message.getDocument(); - String command = query.keySet().iterator().next(); + DatabaseCommand command = DatabaseCommand.of(query.keySet().iterator().next()); return handleCommand(channel, databaseName, command, query); } diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java index 5d91a64d..54b06946 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java @@ -94,18 +94,18 @@ public String toString() { return getClass().getSimpleName() + "(" + getDatabaseName() + ")"; } - private Document commandError(Channel channel, String command, Document query) { + private Document commandError(Channel channel, DatabaseCommand command, Document query) { // getlasterror must not clear the last error - if (command.equalsIgnoreCase("getlasterror")) { - return commandGetLastError(channel, command, query); - } else if (command.equalsIgnoreCase("reseterror")) { + if (command.getCommand() == Command.GET_LAST_ERROR) { + return commandGetLastError(channel, command.getQueryValue(), query); + } else if (command.getCommand() == Command.RESET_ERROR) { return commandResetError(channel); } return null; } @Override - public Document handleCommand(Channel channel, String command, Document query, DatabaseResolver databaseResolver, Oplog oplog) { + public Document handleCommand(Channel channel, DatabaseCommand command, Document query, DatabaseResolver databaseResolver, Oplog oplog) { Document commandErrorDocument = commandError(channel, command, query); if (commandErrorDocument != null) { return commandErrorDocument; @@ -113,72 +113,60 @@ public Document handleCommand(Channel channel, String command, Document query, D clearLastStatus(channel); - if (command.equalsIgnoreCase("find")) { - return commandFind(command, query); - } else if (command.equalsIgnoreCase("insert")) { - return commandInsert(channel, command, query, oplog); - } else if (command.equalsIgnoreCase("update")) { - return commandUpdate(channel, command, query, oplog); - } else if (command.equalsIgnoreCase("delete")) { - return commandDelete(channel, command, query, oplog); - } else if (command.equalsIgnoreCase("create")) { - return commandCreate(command, query); - } else if (command.equalsIgnoreCase("createIndexes")) { - String collectionName = (String) query.get(command); - return commandCreateIndexes(query, collectionName); - } else if (command.equalsIgnoreCase("count")) { - return commandCount(command, query); - } else if (command.equalsIgnoreCase("aggregate")) { - return commandAggregate(command, query, databaseResolver, oplog); - } else if (command.equalsIgnoreCase("distinct")) { - MongoCollection

collection = resolveCollection(command, query); - if (collection == null) { - Document response = new Document("values", Collections.emptyList()); - Utils.markOkay(response); - return response; - } else { - return collection.handleDistinct(query); + final String commandName = command.getQueryValue(); + return switch (command.getCommand()) { + case FIND -> commandFind(commandName, query); + case INSERT -> commandInsert(channel, commandName, query, oplog); + case UPDATE -> commandUpdate(channel, commandName, query, oplog); + case DELETE -> commandDelete(channel, commandName, query, oplog); + case CREATE -> commandCreate(commandName, query); + case CREATE_INDEXES -> commandCreateIndexes(query, query.get(commandName).toString()); + case COUNT -> commandCount(commandName, query); + case AGGREGATE -> commandAggregate(commandName, query, databaseResolver, oplog); + case DISTINCT -> { + MongoCollection

collection = resolveCollection(commandName, query); + if (collection == null) { + Document response = new Document("values", Collections.emptyList()); + Utils.markOkay(response); + yield response; + } else { + yield collection.handleDistinct(query); + } } - } else if (command.equalsIgnoreCase("drop")) { - return commandDrop(query, oplog); - } else if (command.equalsIgnoreCase("dropIndexes")) { - return commandDropIndexes(query); - } else if (command.equalsIgnoreCase("dbstats")) { - return commandDatabaseStats(); - } else if (command.equalsIgnoreCase("collstats")) { - MongoCollection

collection = resolveCollection(command, query); - if (collection == null) { - Document emptyStats = new Document() - .append("count", 0) - .append("size", 0); - Utils.markOkay(emptyStats); - return emptyStats; - } else { - return collection.getStats(); + case DROP -> commandDrop(commandName, query, oplog); + case DROP_INDEXES -> commandDropIndexes(commandName, query); + case DB_STATS -> commandDatabaseStats(); + case COLL_STATS -> { + MongoCollection

collection = resolveCollection(commandName, query); + if (collection == null) { + Document emptyStats = new Document() + .append("count", 0) + .append("size", 0); + Utils.markOkay(emptyStats); + yield emptyStats; + } else { + yield collection.getStats(); + } } - } else if (command.equalsIgnoreCase("validate")) { - MongoCollection

collection = resolveCollection(command, query); - if (collection == null) { - String collectionName = query.get(command).toString(); - String fullCollectionName = getDatabaseName() + "." + collectionName; - throw new MongoServerError(26, "NamespaceNotFound", "Collection '" + fullCollectionName + "' does not exist to validate."); + case VALIDATE -> { + MongoCollection

collection = resolveCollection(commandName, query); + if (collection == null) { + String collectionName = query.get(commandName).toString(); + String fullCollectionName = getDatabaseName() + "." + collectionName; + throw new MongoServerError(26, "NamespaceNotFound", "Collection '" + fullCollectionName + "' does not exist to validate."); + } + yield collection.validate(); } - return collection.validate(); - } else if (command.equalsIgnoreCase("findAndModify")) { - String collectionName = query.get(command).toString(); - MongoCollection

collection = resolveOrCreateCollection(collectionName); - return collection.findAndModify(query); - } else if (command.equalsIgnoreCase("listCollections")) { - return listCollections(); - } else if (command.equalsIgnoreCase("listIndexes")) { - String collectionName = query.get(command).toString(); - return listIndexes(collectionName); - } else if (command.equals("triggerInternalException")) { - throw new NullPointerException("For testing purposes"); - } else { - log.error("unknown query: {}", query); - } - throw new NoSuchCommandException(command); + case FIND_AND_MODIFY -> { + String collectionName = query.get(commandName).toString(); + MongoCollection

collection = resolveOrCreateCollection(collectionName); + yield collection.findAndModify(query); + } + case LIST_COLLECTIONS -> listCollections(); + case LIST_INDEXES -> listIndexes(query.get(commandName).toString()); + case TRIGGER_INTERNAL_EXCEPTION -> throw new NullPointerException("For testing purposes"); + default -> throw new NoSuchCommandException(command.getQueryValue()); + }; } private Document listCollections() { @@ -244,7 +232,7 @@ protected MongoCollection

resolveOrCreateCollection(String collectionName) { } private Document commandFind(String command, Document query) { - String collectionName = (String) query.get(command); + String collectionName = query.get(command).toString(); MongoCollection

collection = resolveCollection(collectionName, false); if (collection == null) { return Utils.firstBatchCursorResponse(getFullCollectionNamespace(collectionName), Collections.emptyList()); @@ -412,8 +400,8 @@ private Document commandCreateIndexes(Document query, String collectionName) { return response; } - private Document commandDropIndexes(Document query) { - String collectionName = (String) query.get("dropIndexes"); + private Document commandDropIndexes(String command, Document query) { + String collectionName = query.get(command).toString(); MongoCollection

collection = resolveCollection(collectionName, false); if (collection != null) { dropIndexes(collection, query); @@ -517,8 +505,8 @@ private Document commandDatabaseStats() { protected abstract long getStorageSize(); - private Document commandDrop(Document query, Oplog oplog) { - String collectionName = query.get("drop").toString(); + private Document commandDrop(String command, Document query, Oplog oplog) { + String collectionName = query.get(command).toString(); MongoCollection

collection = resolveCollection(collectionName, false); if (collection == null) { diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java b/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java new file mode 100644 index 00000000..94f24844 --- /dev/null +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java @@ -0,0 +1,75 @@ +package de.bwaldvogel.mongo.backend; + +import java.util.Arrays; + +public enum Command { + // User + AGGREGATE("aggregate"), + COUNT("count"), + DISTINCT("distinct"), + + // Query and write operation + DELETE("delete"), + FIND("find"), + FIND_AND_MODIFY("findAndModify"), + GET_LAST_ERROR("getLastError"), // removed in 5.1 + GET_MORE("getMore"), + INSERT("insert"), + RESET_ERROR("resetError"), + UPDATE("update"), + + // Replication + IS_MASTER("isMaster"), + REPL_SET_GET_STATUS("replSetGetStatus"), + + // Session + END_SESSIONS("endSessions"), + + // Administration + CREATE("create"), + CREATE_INDEXES("createIndexes"), + DROP("drop"), + DROP_DATABASE("dropDatabase"), + DROP_INDEXES("dropIndexes"), + KILL_CURSORS("killCursors"), + LIST_COLLECTIONS("listCollections"), + LIST_DATABASES("listDatabases"), + LIST_INDEXES("listIndexes"), + RENAME_COLLECTION("renameCollection"), + + // Diagnostic + BUILD_INFO("buildInfo"), + COLL_STATS("collStats"), + CONNECTION_STATUS("connectionStatus"), + DB_STATS("dbStats"), + GET_CMD_LINE_OPTS("getCmdLineOpts"), + GET_LOG("getLog"), + HOST_INFO("hostInfo"), + PING("ping"), + SERVER_STATUS("serverStatus"), + VALIDATE("validate"), + WHATS_MY_URI("whatsmyuri"), + + // Monitoring + GET_FREE_MONITORING_STATUS("getFreeMonitoringStatus"), + + TRIGGER_INTERNAL_EXCEPTION("triggerInternalException"), + UNKNOWN("unknown"); + + private final String name; + + Command(String name) { + this.name = name; + } + + String getName() { + return name; + } + + public static Command parseString(String commandName) { + return Arrays.stream(Command.values()) + .filter(command -> command.name.equalsIgnoreCase(commandName)) + .findFirst() + .orElse(UNKNOWN); + } +} diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/DatabaseCommand.java b/core/src/main/java/de/bwaldvogel/mongo/backend/DatabaseCommand.java new file mode 100644 index 00000000..dd36a49d --- /dev/null +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/DatabaseCommand.java @@ -0,0 +1,32 @@ +package de.bwaldvogel.mongo.backend; + +public class DatabaseCommand { + private final Command command; + private final String queryValue; + + private DatabaseCommand(String queryCommand) { + this.command = Command.parseString(queryCommand); + this.queryValue = queryCommand; + } + + private DatabaseCommand(Command command) { + this.command = command; + this.queryValue = command.getName(); + } + + public static DatabaseCommand of(String queryValue) { + return new DatabaseCommand(queryValue); + } + + public static DatabaseCommand of(Command command) { + return new DatabaseCommand(command); + } + + public Command getCommand() { + return command; + } + + public String getQueryValue() { + return queryValue; + } +} diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/ReadOnlyProxy.java b/core/src/main/java/de/bwaldvogel/mongo/backend/ReadOnlyProxy.java index 390690e9..1460a5bd 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/ReadOnlyProxy.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/ReadOnlyProxy.java @@ -1,5 +1,17 @@ package de.bwaldvogel.mongo.backend; +import static de.bwaldvogel.mongo.backend.Command.BUILD_INFO; +import static de.bwaldvogel.mongo.backend.Command.COLL_STATS; +import static de.bwaldvogel.mongo.backend.Command.COUNT; +import static de.bwaldvogel.mongo.backend.Command.DB_STATS; +import static de.bwaldvogel.mongo.backend.Command.DISTINCT; +import static de.bwaldvogel.mongo.backend.Command.FIND; +import static de.bwaldvogel.mongo.backend.Command.GET_LAST_ERROR; +import static de.bwaldvogel.mongo.backend.Command.GET_MORE; +import static de.bwaldvogel.mongo.backend.Command.IS_MASTER; +import static de.bwaldvogel.mongo.backend.Command.LIST_DATABASES; +import static de.bwaldvogel.mongo.backend.Command.SERVER_STATUS; + import java.time.Clock; import java.util.Collection; import java.util.List; @@ -18,18 +30,18 @@ public class ReadOnlyProxy implements MongoBackend { - private static final Set allowedCommands = Set.of( - "ismaster", - "find", - "listdatabases", - "count", - "dbstats", - "distinct", - "collstats", - "serverstatus", - "buildinfo", - "getlasterror", - "getmore" + private static final Set allowedCommands = Set.of( + IS_MASTER, + FIND, + LIST_DATABASES, + COUNT, + DB_STATS, + DISTINCT, + COLL_STATS, + SERVER_STATUS, + BUILD_INFO, + GET_LAST_ERROR, + GET_MORE ); private final MongoBackend backend; @@ -54,19 +66,19 @@ public void handleClose(Channel channel) { } @Override - public Document handleCommand(Channel channel, String database, String command, Document query) { - if (isAllowed(command, query)) { + public Document handleCommand(Channel channel, String database, DatabaseCommand command, Document query) { + if (isAllowed(command.getCommand(), query)) { return backend.handleCommand(channel, database, command, query); } - throw new NoSuchCommandException(command); + throw new NoSuchCommandException(command.getQueryValue()); } - private static boolean isAllowed(String command, Document query) { - if (allowedCommands.contains(command.toLowerCase())) { + private static boolean isAllowed(Command command, Document query) { + if (allowedCommands.contains(command)) { return true; } - if (command.equalsIgnoreCase("aggregate")) { + if (command == Command.AGGREGATE) { List pipeline = Aggregation.parse(query.get("pipeline")); Aggregation aggregation = Aggregation.fromPipeline(pipeline, null, null, null, null); if (aggregation.isModifying()) { @@ -82,7 +94,7 @@ private static boolean isAllowed(String command, Document query) { public Document handleMessage(MongoMessage message) { Document document = message.getDocument(); String command = document.keySet().iterator().next().toLowerCase(); - if (isAllowed(command, document)) { + if (isAllowed(Command.parseString(command), document)) { return backend.handleMessage(message); } throw new NoSuchCommandException(command); diff --git a/core/src/main/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandler.java b/core/src/main/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandler.java index 227814e0..c3c1b213 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandler.java +++ b/core/src/main/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandler.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import de.bwaldvogel.mongo.MongoBackend; +import de.bwaldvogel.mongo.backend.DatabaseCommand; import de.bwaldvogel.mongo.backend.QueryResult; import de.bwaldvogel.mongo.backend.Utils; import de.bwaldvogel.mongo.bson.Document; @@ -127,12 +128,12 @@ Document handleCommand(MongoQuery query) { } else if ("$cmd".equals(collectionName)) { String command = query.getQuery().keySet().iterator().next(); - - switch (command) { - case "serverStatus": + DatabaseCommand cmd = DatabaseCommand.of(command); + switch (cmd.getCommand()) { + case SERVER_STATUS: return mongoBackend.getServerStatus(); - case "ping": + case PING: Document response = new Document(); Utils.markOkay(response); return response; @@ -141,9 +142,10 @@ Document handleCommand(MongoQuery query) { Document actualQuery = query.getQuery(); if ("$query".equals(command)) { command = ((Document) query.getQuery().get("$query")).keySet().iterator().next(); + cmd = DatabaseCommand.of(command); actualQuery = (Document) actualQuery.get("$query"); } - return mongoBackend.handleCommand(query.getChannel(), query.getDatabaseName(), command, actualQuery); + return mongoBackend.handleCommand(query.getChannel(), query.getDatabaseName(), cmd, actualQuery); } } diff --git a/core/src/test/java/de/bwaldvogel/mongo/backend/AbstractMongoBackendTest.java b/core/src/test/java/de/bwaldvogel/mongo/backend/AbstractMongoBackendTest.java index c79bfd91..f0bea93d 100644 --- a/core/src/test/java/de/bwaldvogel/mongo/backend/AbstractMongoBackendTest.java +++ b/core/src/test/java/de/bwaldvogel/mongo/backend/AbstractMongoBackendTest.java @@ -58,7 +58,7 @@ void testHandleCommand() { Channel channel = Mockito.mock(Channel.class); when(channel.remoteAddress()).thenReturn(new InetSocketAddress("127.0.1.254", 27017)); - Document response = backend.handleCommand(channel, null, "whatsmyuri", null); + Document response = backend.handleCommand(channel, null, DatabaseCommand.of(Command.WHATS_MY_URI), null); assertThat(response).isNotNull(); assertThat(response.get("ok")).isEqualTo(1.0); assertThat(response.get("you")).isEqualTo("127.0.1.254:27017"); @@ -68,7 +68,7 @@ void testHandleCommand() { void testHandleAdminCommand() { Channel channel = Mockito.mock(Channel.class); - Document response = backend.handleCommand(channel, ADMIN_DB_NAME, "ping", null); + Document response = backend.handleCommand(channel, ADMIN_DB_NAME, DatabaseCommand.of(Command.PING), null); assertThat(response).isNotNull(); assertThat(response.get("ok")).isEqualTo(1.0); } @@ -77,7 +77,7 @@ void testHandleAdminCommand() { void testMongoDatabaseHandleCommand() { Channel channel = Mockito.mock(Channel.class); - Document response = backend.handleCommand(channel, "mockDatabase", "find", null); + Document response = backend.handleCommand(channel, "mockDatabase", DatabaseCommand.of(Command.FIND), null); assertThat(response).isNotNull(); assertThat(response.get("ok")).isEqualTo(1.0); assertThat(response.get("message")).isEqualTo("fakeResponse"); diff --git a/core/src/test/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandlerTest.java b/core/src/test/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandlerTest.java index d4a06ab5..a6b33023 100644 --- a/core/src/test/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandlerTest.java +++ b/core/src/test/java/de/bwaldvogel/mongo/wire/MongoDatabaseHandlerTest.java @@ -7,6 +7,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import de.bwaldvogel.mongo.backend.Command; + +import de.bwaldvogel.mongo.backend.DatabaseCommand; + import org.junit.jupiter.api.Test; import de.bwaldvogel.mongo.MongoBackend; @@ -31,7 +35,7 @@ void testWrappedCommand() throws Exception { handler.handleCommand(query); - verify(backend).handleCommand(channel, "dbName", "count", subQueryDoc); + verify(backend).handleCommand(channel, "dbName", DatabaseCommand.of(Command.COUNT), subQueryDoc); } @Test @@ -46,7 +50,7 @@ void testNonWrappedCommand() throws Exception { handler.handleCommand(query); - verify(backend).handleCommand(channel, "dbName", "count", queryDoc); + verify(backend).handleCommand(channel, "dbName", DatabaseCommand.of(Command.COUNT), queryDoc); } @Test diff --git a/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractBackendTest.java b/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractBackendTest.java index 40314f0b..e6c14df5 100755 --- a/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractBackendTest.java +++ b/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractBackendTest.java @@ -2492,7 +2492,7 @@ void testServerStatusWithOpenCursors() { try (MongoCursor cursor1 = collection.find().batchSize(10).cursor(); MongoCursor cursor2 = collection.find().batchSize(10).cursor()) { log.debug("Opened {} and {}", cursor1.getServerCursor(), cursor2.getServerCursor()); - Document serverStatus = runCommand("serverStatus"); + Document serverStatus = runCommand(Command.SERVER_STATUS); assertThat(serverStatus.getDouble("ok")).isEqualTo(1); Document metrics = serverStatus.get("metrics", Document.class); @@ -2507,7 +2507,7 @@ void testServerStatusWithOpenCursors() { @Test void testPing() { - assertThat(runCommand("ping").getDouble("ok")).isEqualTo(1.0); + assertThat(runCommand(Command.PING).getDouble("ok")).isEqualTo(1.0); assertThat(runCommand(json("ping: true")).getDouble("ok")).isEqualTo(1.0); assertThat(runCommand(json("ping: 2.0")).getDouble("ok")).isEqualTo(1.0); assertThat(getDatabase().runCommand(json("ping: true")).getDouble("ok")).isEqualTo(1.0); @@ -2516,7 +2516,7 @@ void testPing() { @Test void testReplSetGetStatus() { assertThatExceptionOfType(MongoCommandException.class) - .isThrownBy(() -> runCommand("replSetGetStatus")) + .isThrownBy(() -> runCommand(Command.REPL_SET_GET_STATUS)) .withMessageStartingWith("Command execution failed on MongoDB server with error 76 (NoReplicationEnabled): 'not running with --replSet'"); } @@ -5230,7 +5230,7 @@ void testValidate() { void testGetLastError() { collection.insertOne(json("_id: 1")); - Document actual = db.runCommand(json("getlasterror: 1")); + Document actual = db.runCommand(json("getLastError: 1")); assertThat(actual.get("n")).isEqualTo(0); assertThat(actual).containsKey("err"); assertThat(actual.get("err")).isNull(); @@ -5240,7 +5240,7 @@ void testGetLastError() { .isThrownBy(() -> collection.insertOne(json("_id: 1.0"))) .withMessageContaining("E11000 duplicate key error collection: testdb.testcoll index: _id_ dup key: { _id: 1.0 }"); - Document lastError = db.runCommand(json("getlasterror: 1")); + Document lastError = db.runCommand(json("getLastError: 1")); assertThat(lastError.get("code")).isEqualTo(11000); assertThat(lastError.getString("err")).contains("duplicate key"); assertThat(lastError.getString("codeName")).isEqualTo("DuplicateKey"); @@ -5255,10 +5255,10 @@ public void testResetError() { .isThrownBy(() -> collection.insertOne(json("_id: 1.0"))) .withMessageContaining("duplicate key error collection: testdb.testcoll index: _id_ dup key: { _id: 1.0 }"); - assertThat(db.runCommand(json("reseterror: 1"))) + assertThat(db.runCommand(json("resetError: 1"))) .isEqualTo(json("ok: 1.0")); - assertThat(db.runCommand(json("getlasterror: 1"))) + assertThat(db.runCommand(json("getLastError: 1"))) .containsAllEntriesOf(json("err: null, n: 0, ok: 1.0")); } diff --git a/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractTest.java b/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractTest.java index 67905494..46e888ba 100644 --- a/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractTest.java +++ b/test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractTest.java @@ -200,8 +200,8 @@ protected List listDatabaseNames() { return databaseNames; } - protected Document runCommand(String commandName) { - return runCommand(new Document(commandName, 1)); + protected Document runCommand(Command commandName) { + return runCommand(new Document(commandName.getName(), 1)); } protected Document runCommand(Document command) { @@ -213,7 +213,7 @@ protected MongoDatabase getAdminDb() { } protected long getNumberOfOpenCursors() { - Document serverStatus = runCommand("serverStatus"); + Document serverStatus = runCommand(Command.SERVER_STATUS); assertThat(serverStatus.getDouble("ok")).isEqualTo(1); Document metrics = serverStatus.get("metrics", Document.class); Document cursorMetrics = metrics.get("cursor", Document.class); diff --git a/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java b/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java index 262e4f03..a121637c 100644 --- a/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java +++ b/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java @@ -5,6 +5,8 @@ import java.util.Date; +import de.bwaldvogel.mongo.backend.Command; + import org.bson.Document; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; @@ -115,8 +117,8 @@ public void testQueryWithSubdocumentIndex() { @Test @Override public void testServerStatus() { - verifyServerStatus(runCommand("serverStatus")); - verifyServerStatus(db.runCommand(json("serverStatus:1"))); + verifyServerStatus(runCommand(Command.SERVER_STATUS)); + verifyServerStatus(db.runCommand(json("serverStatus: 1"))); } private void verifyServerStatus(Document serverStatus) { @@ -150,6 +152,6 @@ public void testResetError() { public void testCommandThatTriggersAnInternalException() { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> db.runCommand(json("triggerInternalException: 1"))) - .withMessageStartingWith("Command execution failed on MongoDB server with error 59 (CommandNotFound): 'no such command: 'triggerInternalException'"); + .withMessageStartingWith("Command failed with error 59 (CommandNotFound): 'no such command: 'triggerInternalException'"); } } From dff1a7760b488ee401cc07acdfe7ff5389bdd735 Mon Sep 17 00:00:00 2001 From: jmarkerink Date: Sat, 17 Jan 2026 15:38:15 +0100 Subject: [PATCH 2/5] feat: speed improvements with lookup --- .../de/bwaldvogel/mongo/backend/Command.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java b/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java index 94f24844..b835c407 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/Command.java @@ -1,6 +1,9 @@ package de.bwaldvogel.mongo.backend; import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; public enum Command { // User @@ -53,10 +56,20 @@ public enum Command { // Monitoring GET_FREE_MONITORING_STATUS("getFreeMonitoringStatus"), + // internal use TRIGGER_INTERNAL_EXCEPTION("triggerInternalException"), UNKNOWN("unknown"); private final String name; + private static final Map LOOKUP; + + static { + LOOKUP = Arrays.stream(Command.values()) + .collect(Collectors.toMap( + c -> c.name.toLowerCase(), + Function.identity() + )); + } Command(String name) { this.name = name; @@ -67,9 +80,6 @@ String getName() { } public static Command parseString(String commandName) { - return Arrays.stream(Command.values()) - .filter(command -> command.name.equalsIgnoreCase(commandName)) - .findFirst() - .orElse(UNKNOWN); + return LOOKUP.getOrDefault(commandName.toLowerCase(), UNKNOWN); } } From 1e8402f576e5e59fe94c966921c812f1357c17df Mon Sep 17 00:00:00 2001 From: jmarkerink Date: Sat, 17 Jan 2026 15:43:29 +0100 Subject: [PATCH 3/5] feat: moved code to functions --- .../mongo/backend/AbstractMongoBackend.java | 120 ++++++++++-------- .../mongo/backend/AbstractMongoDatabase.java | 80 ++++++------ 2 files changed, 113 insertions(+), 87 deletions(-) diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java index 8c943e4e..b8d20504 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java @@ -154,27 +154,10 @@ private Document getLog(String argument) { private Document handleAdminCommand(DatabaseCommand command, Document query) { switch (command.getCommand()) { case LIST_DATABASES: { - List databases = listDatabaseNames().stream() - .sorted() - .map(databaseName -> { - MongoDatabase database = openOrCreateDatabase(databaseName); - Document dbObj = new Document("name", database.getDatabaseName()); - dbObj.put("empty", Boolean.valueOf(database.isEmpty())); - return dbObj; - }) - .toList(); - Document response = new Document(); - response.put("databases", databases); - Utils.markOkay(response); - return response; + return handleListDatabases(); } case FIND: { - String collectionName = query.get(command.getQueryValue()).toString(); - if (collectionName.equals("$cmd.sys.inprog")) { - return Utils.firstBatchCursorResponse(collectionName, new Document("inprog", Collections.emptyList())); - } else { - throw new NoSuchCommandException(new Document(command.getQueryValue(), collectionName).toString()); - } + return handleFind(command, query); } case REPL_SET_GET_STATUS: { throw new NoReplicationEnabledException(); @@ -191,13 +174,7 @@ private Document handleAdminCommand(DatabaseCommand command, Document query) { return successResponse(); } case CONNECTION_STATUS: { - Document response = new Document(); - response.append("authInfo", new Document() - .append("authenticatedUsers", Collections.emptyList()) - .append("authenticatedUserRoles", Collections.emptyList()) - ); - Utils.markOkay(response); - return response; + return handleConnectionStatus(); } case HOST_INFO: { return handleHostInfo(); @@ -217,12 +194,47 @@ private Document handleAdminCommand(DatabaseCommand command, Document query) { } } + private Document handleListDatabases() { + List databases = listDatabaseNames().stream() + .sorted() + .map(databaseName -> { + MongoDatabase database = openOrCreateDatabase(databaseName); + Document dbObj = new Document("name", database.getDatabaseName()); + dbObj.put("empty", Boolean.valueOf(database.isEmpty())); + return dbObj; + }) + .toList(); + Document response = new Document(); + response.put("databases", databases); + Utils.markOkay(response); + return response; + } + + private static Document handleFind(DatabaseCommand command, Document query) { + String collectionName = query.get(command.getQueryValue()).toString(); + if (collectionName.equals("$cmd.sys.inprog")) { + return Utils.firstBatchCursorResponse(collectionName, new Document("inprog", Collections.emptyList())); + } else { + throw new NoSuchCommandException(new Document(command.getQueryValue(), collectionName).toString()); + } + } + private static Document successResponse() { Document response = new Document(); Utils.markOkay(response); return response; } + private static Document handleConnectionStatus() { + Document response = new Document(); + response.append("authInfo", new Document() + .append("authenticatedUsers", Collections.emptyList()) + .append("authenticatedUserRoles", Collections.emptyList()) + ); + Utils.markOkay(response); + return response; + } + private Document handleHostInfo() { Document response = new Document(); String osName = System.getProperty("os.name"); @@ -312,31 +324,9 @@ private MongoCollection resolveCollection(String namespace) { @Override public Document handleCommand(Channel channel, String databaseName, DatabaseCommand command, Document query) { return switch (command.getCommand()) { - case WHATS_MY_URI -> { - Document response = new Document(); - InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress(); - response.put("you", remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort()); - Utils.markOkay(response); - yield response; - } - case IS_MASTER -> { - Document response = new Document("ismaster", Boolean.TRUE); - response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); - response.put("maxWriteBatchSize", Integer.valueOf(MongoWireProtocolHandler.MAX_WRITE_BATCH_SIZE)); - response.put("maxMessageSizeBytes", Integer.valueOf(MongoWireProtocolHandler.MAX_MESSAGE_SIZE_BYTES)); - response.put("maxWireVersion", Integer.valueOf(version.getWireVersion())); - response.put("minWireVersion", Integer.valueOf(0)); - response.put("localTime", Instant.now(clock)); - Utils.markOkay(response); - yield response; - } - case BUILD_INFO -> { - Document response = new Document("version", version.toVersionString()); - response.put("versionArray", version.getVersionArray()); - response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); - Utils.markOkay(response); - yield response; - } + case WHATS_MY_URI -> handleWhatsMyUri(channel); + case IS_MASTER -> handleIsMaster(); + case BUILD_INFO -> handleBuildInfo(); case DROP_DATABASE -> handleDropDatabase(databaseName); case GET_MORE -> handleGetMore(databaseName, command.getQueryValue(), query); case KILL_CURSORS -> handleKillCursors(query); @@ -368,6 +358,34 @@ public void closeCursors(List cursorIds) { cursorIds.forEach(cursorRegistry::remove); } + private static Document handleWhatsMyUri(Channel channel) { + Document response = new Document(); + InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress(); + response.put("you", remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort()); + Utils.markOkay(response); + return response; + } + + private Document handleIsMaster() { + Document response = new Document("ismaster", Boolean.TRUE); + response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); + response.put("maxWriteBatchSize", Integer.valueOf(MongoWireProtocolHandler.MAX_WRITE_BATCH_SIZE)); + response.put("maxMessageSizeBytes", Integer.valueOf(MongoWireProtocolHandler.MAX_MESSAGE_SIZE_BYTES)); + response.put("maxWireVersion", Integer.valueOf(version.getWireVersion())); + response.put("minWireVersion", Integer.valueOf(0)); + response.put("localTime", Instant.now(clock)); + Utils.markOkay(response); + return response; + } + + private Document handleBuildInfo() { + Document response = new Document("version", version.toVersionString()); + response.put("versionArray", version.getVersionArray()); + response.put("maxBsonObjectSize", Integer.valueOf(BsonConstants.MAX_BSON_OBJECT_SIZE)); + Utils.markOkay(response); + return response; + } + protected Document handleKillCursors(Document query) { List cursorIds = (List) query.get("cursors"); List cursorsKilled = new ArrayList<>(); diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java index 54b06946..e2d63367 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoDatabase.java @@ -123,45 +123,13 @@ public Document handleCommand(Channel channel, DatabaseCommand command, Document case CREATE_INDEXES -> commandCreateIndexes(query, query.get(commandName).toString()); case COUNT -> commandCount(commandName, query); case AGGREGATE -> commandAggregate(commandName, query, databaseResolver, oplog); - case DISTINCT -> { - MongoCollection

collection = resolveCollection(commandName, query); - if (collection == null) { - Document response = new Document("values", Collections.emptyList()); - Utils.markOkay(response); - yield response; - } else { - yield collection.handleDistinct(query); - } - } + case DISTINCT -> commandDistinct(query, commandName); case DROP -> commandDrop(commandName, query, oplog); case DROP_INDEXES -> commandDropIndexes(commandName, query); case DB_STATS -> commandDatabaseStats(); - case COLL_STATS -> { - MongoCollection

collection = resolveCollection(commandName, query); - if (collection == null) { - Document emptyStats = new Document() - .append("count", 0) - .append("size", 0); - Utils.markOkay(emptyStats); - yield emptyStats; - } else { - yield collection.getStats(); - } - } - case VALIDATE -> { - MongoCollection

collection = resolveCollection(commandName, query); - if (collection == null) { - String collectionName = query.get(commandName).toString(); - String fullCollectionName = getDatabaseName() + "." + collectionName; - throw new MongoServerError(26, "NamespaceNotFound", "Collection '" + fullCollectionName + "' does not exist to validate."); - } - yield collection.validate(); - } - case FIND_AND_MODIFY -> { - String collectionName = query.get(commandName).toString(); - MongoCollection

collection = resolveOrCreateCollection(collectionName); - yield collection.findAndModify(query); - } + case COLL_STATS -> commandCollStats(query, commandName); + case VALIDATE -> commandValidate(query, commandName); + case FIND_AND_MODIFY -> findAndModify(query, commandName); case LIST_COLLECTIONS -> listCollections(); case LIST_INDEXES -> listIndexes(query.get(commandName).toString()); case TRIGGER_INTERNAL_EXCEPTION -> throw new NullPointerException("For testing purposes"); @@ -501,6 +469,35 @@ private Document commandDatabaseStats() { return response; } + private Document commandCollStats(Document query, String commandName) { + MongoCollection

collection = resolveCollection(commandName, query); + if (collection == null) { + Document emptyStats = new Document() + .append("count", 0) + .append("size", 0); + Utils.markOkay(emptyStats); + return emptyStats; + } else { + return collection.getStats(); + } + } + + private Document commandValidate(Document query, String commandName) { + MongoCollection

collection = resolveCollection(commandName, query); + if (collection == null) { + String collectionName = query.get(commandName).toString(); + String fullCollectionName = getDatabaseName() + "." + collectionName; + throw new MongoServerError(26, "NamespaceNotFound", "Collection '" + fullCollectionName + "' does not exist to validate."); + } + return collection.validate(); + } + + private Document findAndModify(Document query, String commandName) { + String collectionName = query.get(commandName).toString(); + MongoCollection

collection = resolveOrCreateCollection(collectionName); + return collection.findAndModify(query); + } + protected abstract long getFileSize(); protected abstract long getStorageSize(); @@ -611,6 +608,17 @@ private Document commandAggregate(String command, Document query, DatabaseResolv return Utils.firstBatchCursorResponse(getFullCollectionNamespace(collectionName), aggregation.computeResult()); } + private Document commandDistinct(Document query, String commandName) { + MongoCollection

collection = resolveCollection(commandName, query); + if (collection == null) { + Document response = new Document("values", Collections.emptyList()); + Utils.markOkay(response); + return response; + } else { + return collection.handleDistinct(query); + } + } + private Aggregation getAggregation(List pipeline, Document query, DatabaseResolver databaseResolver, MongoCollection collection, Oplog oplog) { Aggregation aggregation = Aggregation.fromPipeline(pipeline, databaseResolver, this, collection, oplog); From eb221c4f7d0eb2b4c4e15863a0b5ff632bc81cbd Mon Sep 17 00:00:00 2001 From: jmarkerink Date: Sat, 17 Jan 2026 15:44:55 +0100 Subject: [PATCH 4/5] feat: adopt switch expression --- .../mongo/backend/AbstractMongoBackend.java | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java index b8d20504..5598e5e1 100644 --- a/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java +++ b/core/src/main/java/de/bwaldvogel/mongo/backend/AbstractMongoBackend.java @@ -152,46 +152,29 @@ private Document getLog(String argument) { } private Document handleAdminCommand(DatabaseCommand command, Document query) { - switch (command.getCommand()) { - case LIST_DATABASES: { - return handleListDatabases(); - } - case FIND: { - return handleFind(command, query); - } - case REPL_SET_GET_STATUS: { - throw new NoReplicationEnabledException(); - } - case GET_LOG: { + return switch (command.getCommand()) { + case LIST_DATABASES -> handleListDatabases(); + case FIND -> handleFind(command, query); + case REPL_SET_GET_STATUS -> throw new NoReplicationEnabledException(); + case GET_LOG -> { final Object argument = query.get(command.getQueryValue()); - return getLog(argument == null ? null : argument.toString()); - } - case RENAME_COLLECTION: { - return handleRenameCollection(command.getQueryValue(), query); + yield getLog(argument == null ? null : argument.toString()); } - case GET_LAST_ERROR: { + case RENAME_COLLECTION -> handleRenameCollection(command.getQueryValue(), query); + case GET_LAST_ERROR -> { log.debug("getLastError on admin database"); - return successResponse(); - } - case CONNECTION_STATUS: { - return handleConnectionStatus(); + yield successResponse(); } - case HOST_INFO: { - return handleHostInfo(); - } - case GET_CMD_LINE_OPTS: { - return handleGetCmdLineOpts(); - } - case GET_FREE_MONITORING_STATUS: { - return handleGetFreeMonitoringStatus(); - } - case END_SESSIONS: { + case CONNECTION_STATUS -> handleConnectionStatus(); + case HOST_INFO -> handleHostInfo(); + case GET_CMD_LINE_OPTS -> handleGetCmdLineOpts(); + case GET_FREE_MONITORING_STATUS -> handleGetFreeMonitoringStatus(); + case END_SESSIONS -> { log.debug("endSessions on admin database"); - return successResponse(); + yield successResponse(); } - default: - throw new NoSuchCommandException(command.getQueryValue()); - } + default -> throw new NoSuchCommandException(command.getQueryValue()); + }; } private Document handleListDatabases() { From 1991f1d5d61e53dd5767cda8914ec879da355c4d Mon Sep 17 00:00:00 2001 From: jmarkerink Date: Sat, 17 Jan 2026 15:52:37 +0100 Subject: [PATCH 5/5] reverted --- .../src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java b/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java index a121637c..88e0f350 100644 --- a/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java +++ b/test-common/src/test/java/de/bwaldvogel/mongo/RealMongoBackendTest.java @@ -152,6 +152,6 @@ public void testResetError() { public void testCommandThatTriggersAnInternalException() { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> db.runCommand(json("triggerInternalException: 1"))) - .withMessageStartingWith("Command failed with error 59 (CommandNotFound): 'no such command: 'triggerInternalException'"); + .withMessageStartingWith("Command execution failed on MongoDB server with error 59 (CommandNotFound): 'no such command: 'triggerInternalException'"); } }