feat(mongodb): support db.collection.renameCollection()
This commit is contained in:
1 parent
392138f59a
commit
2db28fc236
16 files changed
+239
-17
No files matched your search
@@ -75,6 +75,7 @@ public final class AgentProtocol {
|
||||
public static final String MONGO_METHOD_UPDATE_DOCUMENTS = "update_documents";
|
||||
public static final String MONGO_METHOD_REPLACE_DOCUMENT = "replace_document";
|
||||
public static final String MONGO_METHOD_BULK_WRITE = "bulk_write";
|
||||
public static final String MONGO_METHOD_RENAME_COLLECTION = "rename_collection";
|
||||
public static final String MONGO_METHOD_DELETE_DOCUMENT = "delete_document";
|
||||
public static final String MONGO_METHOD_DELETE_DOCUMENTS = "delete_documents";
|
||||
public static final String MONGO_METHOD_RUN_COMMAND = "run_command";
|
||||
@@ -134,6 +135,7 @@ public final class AgentProtocol {
|
||||
public static final String CAPABILITY_MONGO_INSERT_DOCUMENTS = "mongo_insert_documents";
|
||||
public static final String CAPABILITY_MONGO_REPLACE_DOCUMENT = "mongo_replace_document";
|
||||
public static final String CAPABILITY_MONGO_BULK_WRITE = "mongo_bulk_write";
|
||||
public static final String CAPABILITY_MONGO_RENAME_COLLECTION = "mongo_rename_collection";
|
||||
public static final String CAPABILITY_MONGO_FIND_CURSOR = "mongo_find_cursor";
|
||||
public static final String CAPABILITY_MULTI_SESSION = "multi_session";
|
||||
public static final String CAPABILITY_STRUCTURED_ERROR_V1 = "structured_error_v1";
|
||||
@@ -173,6 +175,7 @@ public final class AgentProtocol {
|
||||
CAPABILITY_MONGO_INSERT_DOCUMENTS,
|
||||
CAPABILITY_MONGO_REPLACE_DOCUMENT,
|
||||
CAPABILITY_MONGO_BULK_WRITE,
|
||||
CAPABILITY_MONGO_RENAME_COLLECTION,
|
||||
CAPABILITY_MONGO_FIND_CURSOR
|
||||
));
|
||||
|
||||
@@ -239,6 +242,7 @@ public final class AgentProtocol {
|
||||
mongoCapabilities.add(CAPABILITY_MONGO_INSERT_DOCUMENTS);
|
||||
mongoCapabilities.add(CAPABILITY_MONGO_REPLACE_DOCUMENT);
|
||||
mongoCapabilities.add(CAPABILITY_MONGO_BULK_WRITE);
|
||||
mongoCapabilities.add(CAPABILITY_MONGO_RENAME_COLLECTION);
|
||||
mongoCapabilities.add(CAPABILITY_MONGO_FIND_CURSOR);
|
||||
MONGO_LEGACY_CAPABILITIES = Collections.unmodifiableList(mongoCapabilities);
|
||||
|
||||
@@ -249,6 +253,7 @@ public final class AgentProtocol {
|
||||
mongoMultiSessionCapabilities.add(CAPABILITY_MONGO_INSERT_DOCUMENTS);
|
||||
mongoMultiSessionCapabilities.add(CAPABILITY_MONGO_REPLACE_DOCUMENT);
|
||||
mongoMultiSessionCapabilities.add(CAPABILITY_MONGO_BULK_WRITE);
|
||||
mongoMultiSessionCapabilities.add(CAPABILITY_MONGO_RENAME_COLLECTION);
|
||||
mongoMultiSessionCapabilities.add(CAPABILITY_MONGO_FIND_CURSOR);
|
||||
MONGO_LEGACY_MULTI_SESSION_CAPABILITIES = Collections.unmodifiableList(mongoMultiSessionCapabilities);
|
||||
|
||||
@@ -293,6 +298,7 @@ public final class AgentProtocol {
|
||||
MONGO_METHOD_UPDATE_DOCUMENTS,
|
||||
MONGO_METHOD_REPLACE_DOCUMENT,
|
||||
MONGO_METHOD_BULK_WRITE,
|
||||
MONGO_METHOD_RENAME_COLLECTION,
|
||||
MONGO_METHOD_DELETE_DOCUMENT,
|
||||
MONGO_METHOD_DELETE_DOCUMENTS,
|
||||
MONGO_METHOD_RUN_COMMAND,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"protocolVersion": 1,
|
||||
"handshakeMethod": "handshake",
|
||||
"handshakeResponseFields": ["protocolVersion", "agentProtocolVersion", "capabilities"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_find_cursor"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_rename_collection", "mongo_find_cursor"],
|
||||
"capabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl"],
|
||||
"defaultSqlCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl"],
|
||||
"commonMethods": [
|
||||
@@ -42,6 +42,6 @@
|
||||
"disconnect",
|
||||
"shutdown"
|
||||
],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "rename_collection", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"kvMethods": ["kv_list_prefix", "kv_get", "kv_put", "kv_delete", "kv_rename", "kv_history", "kv_status", "etcd_compact", "etcd_defrag", "etcd_watch_start", "etcd_watch_poll", "etcd_watch_stop", "etcd_lease_list", "etcd_lease_get", "etcd_lease_grant", "etcd_lease_keepalive_once", "etcd_lease_revoke", "etcd_auth_user_list", "etcd_auth_user_get", "etcd_auth_user_add", "etcd_auth_user_delete", "etcd_auth_user_change_password", "etcd_auth_user_grant_role", "etcd_auth_user_revoke_role", "etcd_auth_role_list", "etcd_auth_role_get", "etcd_auth_role_add", "etcd_auth_role_delete", "etcd_auth_role_grant_permission", "etcd_auth_role_revoke_permission"]
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"protocolVersion": 2,
|
||||
"handshakeMethod": "handshake",
|
||||
"handshakeResponseFields": ["protocolVersion", "agentProtocolVersion", "capabilities"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_find_cursor", "multi_session", "structured_error_v1"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_rename_collection", "mongo_find_cursor", "multi_session", "structured_error_v1"],
|
||||
"capabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "multi_session"],
|
||||
"defaultSqlCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "multi_session", "structured_error_v1"],
|
||||
"commonMethods": [
|
||||
@@ -46,7 +46,7 @@
|
||||
"disconnect",
|
||||
"shutdown"
|
||||
],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "rename_collection", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"kvMethods": ["kv_list_prefix", "kv_get", "kv_put", "kv_delete", "kv_rename", "kv_history", "kv_status", "etcd_compact", "etcd_defrag", "etcd_watch_start", "etcd_watch_poll", "etcd_watch_stop", "etcd_lease_list", "etcd_lease_get", "etcd_lease_grant", "etcd_lease_keepalive_once", "etcd_lease_revoke", "etcd_auth_user_list", "etcd_auth_user_get", "etcd_auth_user_add", "etcd_auth_user_delete", "etcd_auth_user_change_password", "etcd_auth_user_grant_role", "etcd_auth_user_revoke_role", "etcd_auth_role_list", "etcd_auth_role_get", "etcd_auth_role_add", "etcd_auth_role_delete", "etcd_auth_role_grant_permission", "etcd_auth_role_revoke_permission"],
|
||||
"sessionField": "agentSessionId",
|
||||
"cursorSessionField": "sessionId"
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mongodb.ConnectionString;
|
||||
import com.mongodb.MongoBulkWriteException;
|
||||
import com.mongodb.MongoNamespace;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.ServerAddress;
|
||||
@@ -1166,6 +1167,32 @@ public final class MongoAgent {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object renameCollection(JsonObject params) {
|
||||
MongoClient c = requireClient();
|
||||
String database = params.get("database").getAsString();
|
||||
String collection = params.get("collection").getAsString();
|
||||
String newName = params.get("new_name").getAsString();
|
||||
requireRenameableCollectionNames(collection, newName);
|
||||
c.getDatabase(database).getCollection(collection).renameCollection(new MongoNamespace(database, newName));
|
||||
return Collections.singletonMap("ok", true);
|
||||
}
|
||||
|
||||
/** Mirrors the native driver's checks so both paths reject the same names before asking the server. */
|
||||
static void requireRenameableCollectionNames(String collection, String newName) {
|
||||
if (collection == null || collection.isBlank()) {
|
||||
throw new IllegalArgumentException("Collection name is required");
|
||||
}
|
||||
if (newName == null || newName.isBlank()) {
|
||||
throw new IllegalArgumentException("New collection name is required");
|
||||
}
|
||||
if (collection.equals(newName)) {
|
||||
throw new IllegalArgumentException("New collection name must differ from the current name");
|
||||
}
|
||||
if (collection.startsWith("system.") || newName.startsWith("system.")) {
|
||||
throw new IllegalArgumentException("System collections cannot be renamed");
|
||||
}
|
||||
}
|
||||
|
||||
private static Object dropCollection(JsonObject params) {
|
||||
MongoClient c = requireClient();
|
||||
String database = params.get("database").getAsString();
|
||||
@@ -2280,6 +2307,7 @@ public final class MongoAgent {
|
||||
case AgentProtocol.MONGO_METHOD_UPDATE_DOCUMENTS -> updateDocuments(params);
|
||||
case AgentProtocol.MONGO_METHOD_REPLACE_DOCUMENT -> replaceDocument(params);
|
||||
case AgentProtocol.MONGO_METHOD_BULK_WRITE -> bulkWrite(params);
|
||||
case AgentProtocol.MONGO_METHOD_RENAME_COLLECTION -> renameCollection(params);
|
||||
case AgentProtocol.MONGO_METHOD_DELETE_DOCUMENT -> deleteDocument(params);
|
||||
case AgentProtocol.MONGO_METHOD_DELETE_DOCUMENTS -> deleteDocuments(params);
|
||||
case AgentProtocol.MONGO_METHOD_RUN_COMMAND -> runCommand(params);
|
||||
|
||||
@@ -1387,6 +1387,34 @@ class MongoAgentTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void renameCollectionMethodIsRecognizedOverJsonRpc() {
|
||||
String response = MongoAgent.handleRequest(
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":13,\"method\":\"rename_collection\","
|
||||
+ "\"params\":{\"database\":\"app\",\"collection\":\"orders\",\"new_name\":\"orders_2024\"}}");
|
||||
|
||||
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
|
||||
assertEquals(13, json.get("id").getAsInt());
|
||||
assertEquals("Not connected", json.getAsJsonObject("error").get("message").getAsString());
|
||||
assertTrue(AgentProtocol.MONGO_LEGACY_METHODS.contains(AgentProtocol.MONGO_METHOD_RENAME_COLLECTION));
|
||||
assertTrue(AgentProtocol.MONGO_LEGACY_CAPABILITIES.contains(AgentProtocol.CAPABILITY_MONGO_RENAME_COLLECTION));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnrenameableCollectionNames() {
|
||||
MongoAgent.requireRenameableCollectionNames("orders", "orders_2024");
|
||||
for (String[] item : new String[][] {
|
||||
{"", "x", "Collection name is required"},
|
||||
{"orders", "", "New collection name is required"},
|
||||
{"orders", "orders", "must differ"},
|
||||
{"system.users", "x", "System collections cannot be renamed"},
|
||||
{"orders", "system.x", "System collections cannot be renamed"},
|
||||
}) {
|
||||
IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> MongoAgent.requireRenameableCollectionNames(item[0], item[1]));
|
||||
assertTrue(error.getMessage().contains(item[2]), error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsesBulkWriteOptions() {
|
||||
assertTrue(MongoAgent.bulkWriteOptionsForWrite(null).isOrdered());
|
||||
|
||||
@@ -108,6 +108,7 @@ const COLLECTION_METHODS = [
|
||||
{ label: "createIndex", detail: "Create an index", apply: "createIndex({ ${field}: 1 })" },
|
||||
{ label: "dropIndex", detail: "Drop one index", apply: 'dropIndex("${indexName}")' },
|
||||
{ label: "dropIndexes", detail: "Drop collection indexes", apply: "dropIndexes()" },
|
||||
{ label: "renameCollection", detail: "Rename the collection", apply: 'renameCollection("${newName}")' },
|
||||
{ label: "drop", detail: "Drop the collection", apply: "drop()" },
|
||||
] as const;
|
||||
|
||||
@@ -136,6 +137,7 @@ const COLLECTION_METHOD_BOOST: Record<(typeof COLLECTION_METHODS)[number]["label
|
||||
dataSize: 75,
|
||||
storageSize: 70,
|
||||
totalIndexSize: 65,
|
||||
renameCollection: 55,
|
||||
dropIndex: 50,
|
||||
dropIndexes: 45,
|
||||
drop: 30,
|
||||
|
||||
@@ -50,6 +50,7 @@ const COLLECTION_METHOD_SHAPES: Record<string, MongoMethodShape> = {
|
||||
updateMany: { expects: "a filter, an update, and optional options", roles: ["filter", "update", "options"] },
|
||||
replaceOne: { expects: "a filter, a replacement document, and optional options", roles: ["filter", "replacement", "options"] },
|
||||
bulkWrite: { expects: "an array of operations and optional options", roles: ["operations", "options"] },
|
||||
renameCollection: { expects: "the new collection name", roles: ["newName"] },
|
||||
deleteOne: { expects: "a filter", roles: ["filter"] },
|
||||
deleteMany: { expects: "a filter", roles: ["filter"] },
|
||||
findOneAndUpdate: { expects: "a filter, an update, and optional options", roles: ["filter", "update", "options"] },
|
||||
@@ -80,6 +81,7 @@ const SUPPORTED_COLLECTION_METHODS = [
|
||||
"updateMany",
|
||||
"replaceOne",
|
||||
"bulkWrite",
|
||||
"renameCollection",
|
||||
"deleteOne",
|
||||
"deleteMany",
|
||||
"findOneAndUpdate",
|
||||
@@ -168,6 +170,12 @@ function diagnoseMongoCommand(source: string): string | null {
|
||||
return `${method}() returns a result, not a cursor, so nothing can be chained after it: "${tail.length > 40 ? `${tail.slice(0, 40)}…` : tail}".`;
|
||||
}
|
||||
if (tail) return `Unexpected text after ${method}(...): "${tail.length > 40 ? `${tail.slice(0, 40)}…` : tail}".`;
|
||||
if (method === "renameCollection") {
|
||||
if (args.length > 1) return "renameCollection() dropTarget is not supported; drop the target collection first.";
|
||||
const newName = args[0] ? /^(["'])([^"']*)\1$/.exec(args[0].trim())?.[2] : undefined;
|
||||
if (newName !== undefined && (!newName.trim() || newName.includes("$"))) return "renameCollection() requires a valid collection name.";
|
||||
if (newName !== undefined && shape.groups?.collection === newName) return "renameCollection() new name must differ from the current name.";
|
||||
}
|
||||
if (method === "bulkWrite" && args[0]) {
|
||||
const operations = normalizeJsonArgument(args[0]);
|
||||
const problem = operations ? validateBulkWriteOperations(operations) : null;
|
||||
@@ -287,7 +295,7 @@ export interface MongoDistinctCommand {
|
||||
filter?: string;
|
||||
}
|
||||
|
||||
type MongoWriteKind = "runCommand" | "insert" | "update" | "replace" | "bulkWrite" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
|
||||
type MongoWriteKind = "runCommand" | "insert" | "update" | "replace" | "bulkWrite" | "delete" | "createIndex" | "createUser" | "dropIndex" | "dropIndexes" | "dropCollection" | "renameCollection" | "findOneAndUpdate" | "findOneAndReplace" | "findOneAndDelete";
|
||||
|
||||
export type MongoCommand =
|
||||
| ({ kind: "find" } & MongoFindCommand)
|
||||
@@ -312,6 +320,7 @@ export type MongoCommand =
|
||||
| { kind: "dropIndex"; collection: string; index: string }
|
||||
| { kind: "dropIndexes"; collection: string; indexes?: string }
|
||||
| { kind: "dropCollection"; collection: string }
|
||||
| { kind: "renameCollection"; collection: string; newName: string }
|
||||
| { kind: "findOneAndUpdate"; collection: string; filter: string; update: string; options?: string }
|
||||
| { kind: "findOneAndReplace"; collection: string; filter: string; replacement: string; options?: string }
|
||||
| { kind: "findOneAndDelete"; collection: string; filter: string; options?: string };
|
||||
@@ -889,6 +898,17 @@ export function parseMongoWriteCommand(input: string): MongoWriteCommand | null
|
||||
return indexes !== null ? { kind: "dropIndexes", collection: dropIndexes.collection, ...(indexes ? { indexes } : {}) } : null;
|
||||
}
|
||||
|
||||
const renameCollection = parseCollectionMethodTarget(source, "renameCollection");
|
||||
if (renameCollection) {
|
||||
const args = parseMethodArgs(source, renameCollection.methodCallIndex);
|
||||
// A second argument would be dropTarget, which deletes an existing collection of the
|
||||
// new name; it is rejected (see the diagnostics) rather than silently ignored.
|
||||
if (!args || args.length !== 1) return null;
|
||||
const newName = /^(["'])([^"'$]+)\1$/.exec(args[0]!.trim())?.[2]?.trim();
|
||||
if (!newName || newName === renameCollection.collection) return null;
|
||||
return { kind: "renameCollection", collection: renameCollection.collection, newName };
|
||||
}
|
||||
|
||||
const dropCollection = parseCollectionMethodTarget(source, "drop");
|
||||
if (dropCollection) {
|
||||
const args = parseMethodArgs(source, dropCollection.methodCallIndex);
|
||||
@@ -1184,6 +1204,15 @@ export function mongoUseToQueryResult(database: string, executionTimeMs: number)
|
||||
};
|
||||
}
|
||||
|
||||
export function mongoScalarToQueryResult(column: string, value: string, executionTimeMs: number): QueryResult {
|
||||
return {
|
||||
columns: [column],
|
||||
rows: [[value]],
|
||||
affected_rows: 1,
|
||||
execution_time_ms: Math.max(0, Math.round(executionTimeMs)),
|
||||
};
|
||||
}
|
||||
|
||||
export function mongoVersionToQueryResult(version: string, executionTimeMs: number): QueryResult {
|
||||
return {
|
||||
columns: ["version"],
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
mongoUseToQueryResult,
|
||||
mongoVersionToQueryResult,
|
||||
mongoBulkWriteToQueryResult,
|
||||
mongoScalarToQueryResult,
|
||||
mongoWriteToQueryResult,
|
||||
splitMongoCommandRanges,
|
||||
type MongoAggregateSafetyOptions,
|
||||
@@ -6745,6 +6746,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
case "createUser":
|
||||
case "dropIndex":
|
||||
case "dropIndexes":
|
||||
case "renameCollection":
|
||||
case "dropCollection": {
|
||||
if (options?.mongoSafety) {
|
||||
const safety = evaluateMongoWriteSafety(mongoCommand, options.mongoSafety);
|
||||
@@ -6782,6 +6784,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
} finally {
|
||||
await refreshLoadedMongoIndexesAfterMutation(executionConnectionId, currentDatabase, mongoCommand.collection, traceId);
|
||||
}
|
||||
} else if (mongoCommand.kind === "renameCollection") {
|
||||
await api.mongoRenameCollection(executionConnectionId, currentDatabase, mongoCommand.collection, mongoCommand.newName);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoScalarToQueryResult("renamed", `${mongoCommand.collection} -> ${mongoCommand.newName}`, performance.now() - commandStartedAt))));
|
||||
} else if (mongoCommand.kind === "dropCollection") {
|
||||
await api.mongoDropCollection(executionConnectionId, currentDatabase, mongoCommand.collection);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoWriteToQueryResult(1, performance.now() - commandStartedAt))));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"protocolVersion": 1,
|
||||
"handshakeMethod": "handshake",
|
||||
"handshakeResponseFields": ["protocolVersion", "agentProtocolVersion", "capabilities"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_find_cursor"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_rename_collection", "mongo_find_cursor"],
|
||||
"capabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl"],
|
||||
"defaultSqlCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl"],
|
||||
"commonMethods": [
|
||||
@@ -42,6 +42,6 @@
|
||||
"disconnect",
|
||||
"shutdown"
|
||||
],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "rename_collection", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"kvMethods": ["kv_list_prefix", "kv_get", "kv_put", "kv_delete", "kv_rename", "kv_history", "kv_status", "etcd_compact", "etcd_defrag", "etcd_watch_start", "etcd_watch_poll", "etcd_watch_stop", "etcd_lease_list", "etcd_lease_get", "etcd_lease_grant", "etcd_lease_keepalive_once", "etcd_lease_revoke", "etcd_auth_user_list", "etcd_auth_user_get", "etcd_auth_user_add", "etcd_auth_user_delete", "etcd_auth_user_change_password", "etcd_auth_user_grant_role", "etcd_auth_user_revoke_role", "etcd_auth_role_list", "etcd_auth_role_get", "etcd_auth_role_add", "etcd_auth_role_delete", "etcd_auth_role_grant_permission", "etcd_auth_role_revoke_permission"]
|
||||
}
|
||||
@@ -95,7 +95,23 @@ pub async fn mongo_rename_collection_core(
|
||||
let pool = state.pool_handle(connection_id).await.ok_or("Not found")?;
|
||||
match &pool {
|
||||
PoolKind::MongoDb(client) => mongo_driver::rename_collection(client, database, collection, new_name).await,
|
||||
PoolKind::Agent(_) => Err("MongoDB legacy agent does not support rename collection".to_string()),
|
||||
PoolKind::Agent(client) => {
|
||||
let mut client = client.lock().await;
|
||||
if !client.supports_capability(AgentCapability::MongoRenameCollection) {
|
||||
return Err(
|
||||
"MongoDB Legacy Agent does not support renameCollection; upgrade or reinstall the MongoDB Legacy driver"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
let _: serde_json::Value = client
|
||||
.mongo_rename_collection(serde_json::json!({
|
||||
"database": database,
|
||||
"collection": collection,
|
||||
"new_name": new_name,
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("Not a MongoDB connection".to_string()),
|
||||
}
|
||||
}
|
||||
@@ -1200,6 +1216,10 @@ pub async fn execute_mongo_command_core(
|
||||
mongo_drop_collection_core(state, connection_id, database, collection).await?;
|
||||
Ok(affected_query_result(1))
|
||||
}
|
||||
MongoCommand::RenameCollection { collection, new_name } => {
|
||||
mongo_rename_collection_core(state, connection_id, database, collection, new_name).await?;
|
||||
Ok(scalar_query_result("renamed", Value::String(format!("{collection} -> {new_name}"))))
|
||||
}
|
||||
MongoCommand::FindOneAndUpdate { collection, filter, update, options } => {
|
||||
let result = mongo_find_one_and_update_core(
|
||||
state,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"protocolVersion": 2,
|
||||
"handshakeMethod": "handshake",
|
||||
"handshakeResponseFields": ["protocolVersion", "agentProtocolVersion", "capabilities"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_find_cursor", "multi_session", "structured_error_v1"],
|
||||
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "mongo_insert_documents", "mongo_replace_document", "mongo_bulk_write", "mongo_rename_collection", "mongo_find_cursor", "multi_session", "structured_error_v1"],
|
||||
"capabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "multi_session"],
|
||||
"defaultSqlCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "multi_session", "structured_error_v1"],
|
||||
"commonMethods": [
|
||||
@@ -46,7 +46,7 @@
|
||||
"disconnect",
|
||||
"shutdown"
|
||||
],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "insert_documents", "update_document", "update_documents", "replace_document", "bulk_write", "rename_collection", "delete_document", "delete_documents", "run_command", "start_find_cursor", "fetch_find_cursor", "close_find_cursor"],
|
||||
"kvMethods": ["kv_list_prefix", "kv_get", "kv_put", "kv_delete", "kv_rename", "kv_history", "kv_status", "etcd_compact", "etcd_defrag", "etcd_watch_start", "etcd_watch_poll", "etcd_watch_stop", "etcd_lease_list", "etcd_lease_get", "etcd_lease_grant", "etcd_lease_keepalive_once", "etcd_lease_revoke", "etcd_auth_user_list", "etcd_auth_user_get", "etcd_auth_user_add", "etcd_auth_user_delete", "etcd_auth_user_change_password", "etcd_auth_user_grant_role", "etcd_auth_user_revoke_role", "etcd_auth_role_list", "etcd_auth_role_get", "etcd_auth_role_add", "etcd_auth_role_delete", "etcd_auth_role_grant_permission", "etcd_auth_role_revoke_permission"],
|
||||
"sessionField": "agentSessionId",
|
||||
"cursorSessionField": "sessionId"
|
||||
|
||||
@@ -1043,6 +1043,7 @@ pub enum AgentCapability {
|
||||
MongoInsertDocuments,
|
||||
MongoReplaceDocument,
|
||||
MongoBulkWrite,
|
||||
MongoRenameCollection,
|
||||
MongoFindCursor,
|
||||
MultiSession,
|
||||
StructuredErrorV1,
|
||||
@@ -1126,7 +1127,7 @@ fn parse_agent_rpc_error_header(header: &str) -> (Option<i64>, String) {
|
||||
}
|
||||
|
||||
impl AgentCapability {
|
||||
pub const ALL: [Self; 27] = [
|
||||
pub const ALL: [Self; 28] = [
|
||||
Self::Connect,
|
||||
Self::TestConnection,
|
||||
Self::Metadata,
|
||||
@@ -1151,6 +1152,7 @@ impl AgentCapability {
|
||||
Self::MongoInsertDocuments,
|
||||
Self::MongoReplaceDocument,
|
||||
Self::MongoBulkWrite,
|
||||
Self::MongoRenameCollection,
|
||||
Self::MongoFindCursor,
|
||||
Self::MultiSession,
|
||||
Self::StructuredErrorV1,
|
||||
@@ -1182,6 +1184,7 @@ impl AgentCapability {
|
||||
Self::MongoInsertDocuments => "mongo_insert_documents",
|
||||
Self::MongoReplaceDocument => "mongo_replace_document",
|
||||
Self::MongoBulkWrite => "mongo_bulk_write",
|
||||
Self::MongoRenameCollection => "mongo_rename_collection",
|
||||
Self::MongoFindCursor => "mongo_find_cursor",
|
||||
Self::MultiSession => "multi_session",
|
||||
Self::StructuredErrorV1 => "structured_error_v1",
|
||||
@@ -1387,6 +1390,7 @@ pub enum MongoAgentMethod {
|
||||
UpdateDocuments,
|
||||
ReplaceDocument,
|
||||
BulkWrite,
|
||||
RenameCollection,
|
||||
DeleteDocument,
|
||||
DeleteDocuments,
|
||||
RunCommand,
|
||||
@@ -1396,7 +1400,7 @@ pub enum MongoAgentMethod {
|
||||
}
|
||||
|
||||
impl MongoAgentMethod {
|
||||
pub const ALL: [Self; 27] = [
|
||||
pub const ALL: [Self; 28] = [
|
||||
Self::ListDatabases,
|
||||
Self::ListCollections,
|
||||
Self::FindDocuments,
|
||||
@@ -1418,6 +1422,7 @@ impl MongoAgentMethod {
|
||||
Self::UpdateDocuments,
|
||||
Self::ReplaceDocument,
|
||||
Self::BulkWrite,
|
||||
Self::RenameCollection,
|
||||
Self::DeleteDocument,
|
||||
Self::DeleteDocuments,
|
||||
Self::RunCommand,
|
||||
@@ -1449,6 +1454,7 @@ impl MongoAgentMethod {
|
||||
Self::UpdateDocuments => "update_documents",
|
||||
Self::ReplaceDocument => "replace_document",
|
||||
Self::BulkWrite => "bulk_write",
|
||||
Self::RenameCollection => "rename_collection",
|
||||
Self::DeleteDocument => "delete_document",
|
||||
Self::DeleteDocuments => "delete_documents",
|
||||
Self::RunCommand => "run_command",
|
||||
@@ -2859,6 +2865,13 @@ impl AgentDriverClient {
|
||||
self.call_mongo_method(MongoAgentMethod::BulkWrite, params).await
|
||||
}
|
||||
|
||||
pub async fn mongo_rename_collection<T: DeserializeOwned + Send + 'static>(
|
||||
&mut self,
|
||||
params: Value,
|
||||
) -> Result<T, String> {
|
||||
self.call_mongo_method(MongoAgentMethod::RenameCollection, params).await
|
||||
}
|
||||
|
||||
pub async fn mongo_delete_document<T: DeserializeOwned + Send + 'static>(
|
||||
&mut self,
|
||||
params: Value,
|
||||
@@ -3016,6 +3029,7 @@ pub fn agent_supports_capability(handshake: Option<&AgentHandshake>, capability:
|
||||
| AgentCapability::MongoInsertDocuments
|
||||
| AgentCapability::MongoReplaceDocument
|
||||
| AgentCapability::MongoBulkWrite
|
||||
| AgentCapability::MongoRenameCollection
|
||||
| AgentCapability::MongoFindCursor
|
||||
) {
|
||||
return handshake.map(|value| value.supports(capability)).unwrap_or(false);
|
||||
@@ -5052,10 +5066,11 @@ for line in sys.stdin:
|
||||
assert_eq!(AgentCapability::MongoInsertDocuments.as_str(), "mongo_insert_documents");
|
||||
assert_eq!(AgentCapability::MongoReplaceDocument.as_str(), "mongo_replace_document");
|
||||
assert_eq!(AgentCapability::MongoBulkWrite.as_str(), "mongo_bulk_write");
|
||||
assert_eq!(AgentCapability::MongoRenameCollection.as_str(), "mongo_rename_collection");
|
||||
assert_eq!(AgentCapability::MongoFindCursor.as_str(), "mongo_find_cursor");
|
||||
assert_eq!(AgentCapability::MultiSession.as_str(), "multi_session");
|
||||
assert_eq!(AgentCapability::StructuredErrorV1.as_str(), "structured_error_v1");
|
||||
assert_eq!(AgentCapability::ALL.len(), 27);
|
||||
assert_eq!(AgentCapability::ALL.len(), 28);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5119,6 +5134,7 @@ for line in sys.stdin:
|
||||
assert_eq!(MongoAgentMethod::UpdateDocuments.as_str(), "update_documents");
|
||||
assert_eq!(MongoAgentMethod::ReplaceDocument.as_str(), "replace_document");
|
||||
assert_eq!(MongoAgentMethod::BulkWrite.as_str(), "bulk_write");
|
||||
assert_eq!(MongoAgentMethod::RenameCollection.as_str(), "rename_collection");
|
||||
assert_eq!(MongoAgentMethod::DeleteDocument.as_str(), "delete_document");
|
||||
assert_eq!(MongoAgentMethod::DeleteDocuments.as_str(), "delete_documents");
|
||||
assert_eq!(MongoAgentMethod::StartFindCursor.as_str(), "start_find_cursor");
|
||||
|
||||
@@ -78,6 +78,12 @@ pub enum MongoCommand {
|
||||
DropIndexes { collection: String, indexes: Option<String>, single: bool },
|
||||
#[serde(rename = "dropCollection")]
|
||||
DropCollection { collection: String },
|
||||
#[serde(rename = "renameCollection")]
|
||||
RenameCollection {
|
||||
collection: String,
|
||||
#[serde(rename = "newName")]
|
||||
new_name: String,
|
||||
},
|
||||
#[serde(rename = "findOneAndUpdate")]
|
||||
FindOneAndUpdate { collection: String, filter: String, update: String, options: Option<String> },
|
||||
#[serde(rename = "findOneAndReplace")]
|
||||
@@ -108,6 +114,7 @@ impl MongoCommand {
|
||||
| Self::CreateIndex { .. }
|
||||
| Self::DropIndexes { .. }
|
||||
| Self::DropCollection { .. }
|
||||
| Self::RenameCollection { .. }
|
||||
| Self::FindOneAndUpdate { .. }
|
||||
| Self::FindOneAndReplace { .. }
|
||||
| Self::FindOneAndDelete { .. }
|
||||
@@ -863,6 +870,27 @@ pub fn parse(input: &str) -> Result<MongoCommand, String> {
|
||||
single: false,
|
||||
});
|
||||
}
|
||||
if let Some((args, tail)) = method_call(source, prefix_end, "renameCollection") {
|
||||
if !tail.is_empty() || args.is_empty() {
|
||||
return Err("MongoDB renameCollection() requires the new collection name.".to_string());
|
||||
}
|
||||
if args.len() > 1 {
|
||||
// The second argument is dropTarget, which would delete an existing collection
|
||||
// of the new name; reject it rather than silently ignore it.
|
||||
return Err(
|
||||
"MongoDB renameCollection() dropTarget is not supported; drop the target collection first.".to_string()
|
||||
);
|
||||
}
|
||||
let new_name = parse_string_arg(&args[0])?;
|
||||
if new_name.trim().is_empty() || new_name.contains('$') {
|
||||
return Err("MongoDB renameCollection() requires a valid collection name.".to_string());
|
||||
}
|
||||
if new_name == collection {
|
||||
return Err("MongoDB renameCollection() new name must differ from the current name.".to_string());
|
||||
}
|
||||
return Ok(MongoCommand::RenameCollection { collection, new_name });
|
||||
}
|
||||
|
||||
if let Some((args, tail)) = method_call(source, prefix_end, "drop") {
|
||||
if !tail.is_empty() || !args.is_empty() {
|
||||
return Err("Invalid MongoDB drop() command.".to_string());
|
||||
@@ -2219,6 +2247,32 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_rename_collection() {
|
||||
let command = parse(r#"db.orders.renameCollection("orders_2024");"#).unwrap();
|
||||
assert_eq!(
|
||||
command,
|
||||
MongoCommand::RenameCollection { collection: "orders".to_string(), new_name: "orders_2024".to_string() }
|
||||
);
|
||||
assert!(command.is_mutating());
|
||||
assert!(!command.is_dangerous());
|
||||
assert_eq!(serde_json::to_value(&command).unwrap()["newName"], "orders_2024");
|
||||
assert!(matches!(parse(r#"db["a-b"].renameCollection('c')"#).unwrap(), MongoCommand::RenameCollection { .. }));
|
||||
|
||||
for (source, expected) in [
|
||||
("db.orders.renameCollection()", "requires the new collection name"),
|
||||
("db.orders.renameCollection(1)", "must be a string"),
|
||||
(r#"db.orders.renameCollection("")"#, "valid collection name"),
|
||||
(r#"db.orders.renameCollection("a$b")"#, "valid collection name"),
|
||||
(r#"db.orders.renameCollection("orders")"#, "must differ"),
|
||||
(r#"db.orders.renameCollection("x", true)"#, "dropTarget is not supported"),
|
||||
(r#"db.orders.renameCollection("x").y()"#, "requires the new collection name"),
|
||||
] {
|
||||
let error = parse(source).unwrap_err();
|
||||
assert!(error.contains(expected), "{source}\n expected: {expected}\n got: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_show_databases_aliases_as_read_only() {
|
||||
for source in [
|
||||
|
||||
@@ -2020,6 +2020,20 @@ impl DbxBackend for WebBackend {
|
||||
.collect::<Vec<_>>();
|
||||
Ok(mongo_drop_indexes_query_result(dropped_names, failures, affected_rows_from_value(&value)))
|
||||
}
|
||||
MongoCommand::RenameCollection { collection, new_name } => {
|
||||
self.request(
|
||||
reqwest::Method::POST,
|
||||
"/api/mongo/rename-collection",
|
||||
Some(json!({
|
||||
"connectionId": connection_id,
|
||||
"database": database,
|
||||
"collection": collection,
|
||||
"newName": new_name,
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
Ok(scalar_query_result("renamed", Value::String(format!("{collection} -> {new_name}"))))
|
||||
}
|
||||
MongoCommand::DropCollection { collection } => {
|
||||
self.request(
|
||||
reqwest::Method::POST,
|
||||
|
||||
+2
-1
@@ -502,8 +502,9 @@ test("suggests only helpers the shell parser accepts", () => {
|
||||
assert.ok(methodLabels.includes("estimatedDocumentCount"));
|
||||
assert.ok(methodLabels.includes("replaceOne"));
|
||||
assert.ok(methodLabels.includes("bulkWrite"));
|
||||
assert.ok(methodLabels.includes("renameCollection"));
|
||||
// Suggesting a helper DBX cannot run just hands the user a command that fails.
|
||||
for (const unsupported of ["renameCollection", "mapReduce", "watch"]) {
|
||||
for (const unsupported of ["mapReduce", "watch", "validate"]) {
|
||||
assert.equal(methodLabels.includes(unsupported), false, `${unsupported} is not executable`);
|
||||
}
|
||||
// Cursor methods are not collection methods.
|
||||
|
||||
+22
-3
@@ -747,6 +747,25 @@ test("parseMongoWriteCommand accepts unquoted insert and update commands", () =>
|
||||
});
|
||||
});
|
||||
|
||||
test("parseMongoWriteCommand reads renameCollection", () => {
|
||||
assert.deepEqual(parseMongoWriteCommand('db.orders.renameCollection("orders_2024");'), { kind: "renameCollection", collection: "orders", newName: "orders_2024" });
|
||||
assert.equal(parseMongoWriteCommand("db[\"a-b\"].renameCollection('c')")?.kind, "renameCollection");
|
||||
assert.equal(parseMongoWriteCommand('db.getCollection("a").renameCollection("b")')?.kind, "renameCollection");
|
||||
|
||||
for (const [source, expected] of [
|
||||
["db.orders.renameCollection()", /expects the new collection name/],
|
||||
["db.orders.renameCollection(1)", /expects the new collection name/],
|
||||
['db.orders.renameCollection("")', /requires a valid collection name/],
|
||||
['db.orders.renameCollection("a$b")', /requires a valid collection name/],
|
||||
['db.orders.renameCollection("orders")', /must differ from the current name/],
|
||||
['db.orders.renameCollection("x", true)', /dropTarget is not supported; drop the target collection first/],
|
||||
['db.orders.renameCollection("x").y()', /Unexpected text after renameCollection/],
|
||||
] as const) {
|
||||
assert.equal(parseMongoCommand(source), null, source);
|
||||
assert.match(describeMongoCommandParseFailure(source), expected, source);
|
||||
}
|
||||
});
|
||||
|
||||
test("parseMongoWriteCommand reads bulkWrite with every operation kind", () => {
|
||||
const command = parseMongoWriteCommand(`db.orders.bulkWrite([
|
||||
{ insertOne: { document: { sku: "A1", stock: 1 } } },
|
||||
@@ -1383,10 +1402,10 @@ test("describeMongoCommandParseFailure explains the argument shape a known metho
|
||||
});
|
||||
|
||||
test("describeMongoCommandParseFailure names unsupported methods and points at alternatives", () => {
|
||||
const rename = describeMongoCommandParseFailure('db.c.renameCollection("d")');
|
||||
assert.match(rename, /^Collection method renameCollection\(\) is not supported\. Supported collection methods: find, findOne/);
|
||||
const mapReduce = describeMongoCommandParseFailure("db.c.mapReduce()");
|
||||
assert.match(mapReduce, /^Collection method mapReduce\(\) is not supported\. Supported collection methods: find, findOne/);
|
||||
// Bracket and getCollection targets are recognised too.
|
||||
assert.match(describeMongoCommandParseFailure('db["my-coll"].renameCollection("x")'), /renameCollection\(\) is not supported/);
|
||||
assert.match(describeMongoCommandParseFailure('db["my-coll"].validate()'), /validate\(\) is not supported/);
|
||||
assert.match(describeMongoCommandParseFailure('db.getCollection("my-coll").watch()'), /watch\(\) is not supported/);
|
||||
|
||||
assert.match(describeMongoCommandParseFailure('db.createCollection("c")'), /db\.createCollection\(\) is not supported; collections are created on first insert/);
|
||||
|
||||
Reference in new issue
Block a user