From f974c492dac219caa6125dc274380e14371389e8 Mon Sep 17 00:00:00 2001 From: Kevin Albertson Date: Tue, 1 Aug 2023 17:53:06 +0000 Subject: [PATCH] CDRIVER-4612 document management of search indexes (#1366) * add index-management tests From PR https://github.com/mongodb/specifications/pull/1442 * add test search operations and implement without helpers * document management of search indexes --- src/libmongoc/CMakeLists.txt | 1 + .../doc/manage-collection-indexes.rst | 37 +++ .../examples/example-manage-search-indexes.c | 230 ++++++++++++++++++ src/libmongoc/tests/bsonutil/bson-match.c | 4 +- src/libmongoc/tests/bsonutil/bson-parser.c | 2 +- .../index-management/createSearchIndex.json | 136 +++++++++++ .../index-management/createSearchIndexes.json | 172 +++++++++++++ .../index-management/dropSearchIndex.json | 74 ++++++ .../index-management/dropSearchIndexes.json | 73 ++++++ .../index-management/listSearchIndexes.json | 156 ++++++++++++ .../index-management/updateSearchIndex.json | 76 ++++++ src/libmongoc/tests/unified/operation.c | 230 ++++++++++++++++++ src/libmongoc/tests/unified/runner.c | 2 + 13 files changed, 1191 insertions(+), 2 deletions(-) create mode 100644 src/libmongoc/examples/example-manage-search-indexes.c create mode 100644 src/libmongoc/tests/json/index-management/createSearchIndex.json create mode 100644 src/libmongoc/tests/json/index-management/createSearchIndexes.json create mode 100644 src/libmongoc/tests/json/index-management/dropSearchIndex.json create mode 100644 src/libmongoc/tests/json/index-management/dropSearchIndexes.json create mode 100644 src/libmongoc/tests/json/index-management/listSearchIndexes.json create mode 100644 src/libmongoc/tests/json/index-management/updateSearchIndex.json diff --git a/src/libmongoc/CMakeLists.txt b/src/libmongoc/CMakeLists.txt index db0bb42127..264aea7420 100644 --- a/src/libmongoc/CMakeLists.txt +++ b/src/libmongoc/CMakeLists.txt @@ -1095,6 +1095,7 @@ if (ENABLE_EXAMPLES) mongoc_add_example (example-command-monitoring ${PROJECT_SOURCE_DIR}/examples/example-command-monitoring.c) mongoc_add_example (example-command-with-opts ${PROJECT_SOURCE_DIR}/examples/example-command-with-opts.c) mongoc_add_example (example-manage-collection-indexes ${PROJECT_SOURCE_DIR}/examples/example-manage-collection-indexes.c) + mongoc_add_example (example-manage-search-indexes ${PROJECT_SOURCE_DIR}/examples/example-manage-search-indexes.c) mongoc_add_example (example-gridfs ${PROJECT_SOURCE_DIR}/examples/example-gridfs.c) mongoc_add_example (example-gridfs-bucket ${PROJECT_SOURCE_DIR}/examples/example-gridfs-bucket.c) if (NOT WIN32 AND ENABLE_EXAMPLES) diff --git a/src/libmongoc/doc/manage-collection-indexes.rst b/src/libmongoc/doc/manage-collection-indexes.rst index 0e70e51215..ebfb77cb18 100644 --- a/src/libmongoc/doc/manage-collection-indexes.rst +++ b/src/libmongoc/doc/manage-collection-indexes.rst @@ -28,3 +28,40 @@ To drop an index, use :symbol:`mongoc_collection_drop_index_with_opts`. The inde :dedent: 6 For a full example, see `example-manage-collection-indexes.c `_. + +Manage Atlas Search Indexes +--------------------------- + +To create an Atlas Search Index, use the ``createSearchIndexes`` command: + +.. literalinclude:: ../examples/example-manage-search-indexes.c + :language: c + :start-after: // Create an Atlas Search Index ... begin + :end-before: // Create an Atlas Search Index ... end + :dedent: 6 + +To list Atlas Search Indexes, use the ``$listSearchIndexes`` aggregation stage: + +.. literalinclude:: ../examples/example-manage-search-indexes.c + :language: c + :start-after: // List Atlas Search Indexes ... begin + :end-before: // List Atlas Search Indexes ... end + :dedent: 6 + +To update an Atlas Search Index, use the ``updateSearchIndex`` command: + +.. literalinclude:: ../examples/example-manage-search-indexes.c + :language: c + :start-after: // Update an Atlas Search Index ... begin + :end-before: // Update an Atlas Search Index ... end + :dedent: 6 + +To drop an Atlas Search Index, use the ``dropSearchIndex`` command: + +.. literalinclude:: ../examples/example-manage-search-indexes.c + :language: c + :start-after: // Drop an Atlas Search Index ... begin + :end-before: // Drop an Atlas Search Index ... end + :dedent: 6 + +For a full example, see `example-manage-search-indexes.c `_. diff --git a/src/libmongoc/examples/example-manage-search-indexes.c b/src/libmongoc/examples/example-manage-search-indexes.c new file mode 100644 index 0000000000..dac5c5bb59 --- /dev/null +++ b/src/libmongoc/examples/example-manage-search-indexes.c @@ -0,0 +1,230 @@ +// example-manage-search-indexes creates, lists, updates, and deletes an Atlas +// search index from the `test.test` collection. +// Example is expected to be run against a MongoDB Atlas cluster. + +#include +#include // abort + +#define HANDLE_ERROR(...) \ + if (1) { \ + fprintf (stderr, __VA_ARGS__); \ + fprintf (stderr, "\n"); \ + goto fail; \ + } else \ + (void) 0 + +#define ASSERT(stmt) \ + if (!stmt) { \ + fprintf (stderr, \ + "assertion failed on line: %d, statement: %s\n", \ + __LINE__, \ + #stmt); \ + abort (); \ + } else \ + (void) 0 + +int +main (int argc, char *argv[]) +{ + mongoc_client_t *client = NULL; + const char *uri_string = + "mongodb://127.0.0.1/?appname=create-search-indexes-example"; + mongoc_uri_t *uri = NULL; + mongoc_collection_t *coll = NULL; + bson_error_t error; + bool ok = false; + + mongoc_init (); + + if (argc > 2) { + HANDLE_ERROR ( + "Unexpected arguments. Expected usage: %s [CONNECTION_STRING]", + argv[0]); + } + + if (argc > 1) { + uri_string = argv[1]; + } + + uri = mongoc_uri_new_with_error (uri_string, &error); + if (!uri) { + HANDLE_ERROR ("Failed to parse URI: %s", error.message); + } + client = mongoc_client_new_from_uri_with_error (uri, &error); + if (!client) { + HANDLE_ERROR ("Failed to create client: %s", error.message); + } + + // Create a random collection name. + char collname[25]; + { + // There is a server-side limitation that prevents multiple search indexes + // from being created with the same name, definition and collection name. + // Atlas search index management operations are asynchronous. Dropping a + // collection may not result in the index being dropped immediately. Use a + // randomly generated collection name to avoid errors. + bson_oid_t oid; + bson_oid_init (&oid, NULL); + bson_oid_to_string (&oid, collname); + } + + // Create collection object. + { + // Create the collection server-side to avoid the server error: + // "Collection 'test.' does not exist." + mongoc_database_t *db = mongoc_client_get_database (client, "test"); + coll = mongoc_database_create_collection ( + db, collname, NULL /* options */, &error); + if (!coll) { + mongoc_database_destroy (db); + HANDLE_ERROR ("Failed to create collection: %s", error.message); + } + mongoc_database_destroy (db); + } + + // Check that $listSearchIndexes pipeline stage is supported. + // This is intended to check that a MongoDB Atlas cluster is used. + { + const char *pipeline_str = + BSON_STR ({"pipeline" : [ {"$listSearchIndexes" : {}} ]}); + bson_t pipeline; + ASSERT (bson_init_from_json (&pipeline, pipeline_str, -1, &error)); + mongoc_cursor_t *cursor = + mongoc_collection_aggregate (coll, + MONGOC_QUERY_NONE, + &pipeline, + NULL /* opts */, + NULL /* read_prefs */); + const bson_t *got; + while (mongoc_cursor_next (cursor, &got)) + ; + if (mongoc_cursor_error (cursor, &error)) { + bson_destroy (&pipeline); + mongoc_cursor_destroy (cursor); + HANDLE_ERROR ("Failed to run $listSearchIndexes with error: %s\n" + "Does the URI point to a MongoDB Atlas cluster? %s", + error.message, + uri_string); + } + bson_destroy (&pipeline); + mongoc_cursor_destroy (cursor); + } + + { + // Create an Atlas Search Index ... begin + bson_t cmd; + // Create command. + { + char *cmd_str = bson_strdup_printf ( + BSON_STR ({ + "createSearchIndexes" : "%s", + "indexes" : [ { + "definition" : {"mappings" : {"dynamic" : false}}, + "name" : "test-index" + } ] + }), + collname); + ASSERT (bson_init_from_json (&cmd, cmd_str, -1, &error)); + bson_free (cmd_str); + } + if (!mongoc_collection_command_simple ( + coll, &cmd, NULL /* read_prefs */, NULL /* reply */, &error)) { + bson_destroy (&cmd); + HANDLE_ERROR ("Failed to run createSearchIndexes: %s", error.message); + } + printf ("Created index: \"test-index\"\n"); + bson_destroy (&cmd); + // Create an Atlas Search Index ... end + } + + { + // List Atlas Search Indexes ... begin + const char *pipeline_str = + BSON_STR ({"pipeline" : [ {"$listSearchIndexes" : {}} ]}); + bson_t pipeline; + ASSERT (bson_init_from_json (&pipeline, pipeline_str, -1, &error)); + mongoc_cursor_t *cursor = + mongoc_collection_aggregate (coll, + MONGOC_QUERY_NONE, + &pipeline, + NULL /* opts */, + NULL /* read_prefs */); + printf ("Listing indexes:\n"); + const bson_t *got; + while (mongoc_cursor_next (cursor, &got)) { + char *got_str = bson_as_canonical_extended_json (got, NULL); + printf (" %s\n", got_str); + bson_free (got_str); + } + if (mongoc_cursor_error (cursor, &error)) { + bson_destroy (&pipeline); + mongoc_cursor_destroy (cursor); + HANDLE_ERROR ("Failed to run $listSearchIndexes: %s", error.message); + } + bson_destroy (&pipeline); + mongoc_cursor_destroy (cursor); + // List Atlas Search Indexes ... end + } + + { + // Update an Atlas Search Index ... begin + bson_t cmd; + // Create command. + { + char *cmd_str = bson_strdup_printf ( + BSON_STR ({ + "updateSearchIndex" : "%s", + "definition" : {"mappings" : {"dynamic" : true}}, + "name" : "test-index" + }), + collname); + ASSERT (bson_init_from_json (&cmd, cmd_str, -1, &error)); + bson_free (cmd_str); + } + if (!mongoc_collection_command_simple ( + coll, &cmd, NULL /* read_prefs */, NULL /* reply */, &error)) { + bson_destroy (&cmd); + HANDLE_ERROR ("Failed to run updateSearchIndex: %s", error.message); + } + printf ("Updated index: \"test-index\"\n"); + bson_destroy (&cmd); + // Update an Atlas Search Index ... end + } + + { + // Drop an Atlas Search Index ... begin + bson_t cmd; + // Create command. + { + char *cmd_str = bson_strdup_printf ( + BSON_STR ({"dropSearchIndex" : "%s", "name" : "test-index"}), + collname); + ASSERT (bson_init_from_json (&cmd, cmd_str, -1, &error)); + bson_free (cmd_str); + } + if (!mongoc_collection_command_simple ( + coll, &cmd, NULL /* read_prefs */, NULL /* reply */, &error)) { + bson_destroy (&cmd); + HANDLE_ERROR ("Failed to run dropSearchIndex: %s", error.message); + } + printf ("Dropped index: \"test-index\"\n"); + bson_destroy (&cmd); + // Drop an Atlas Search Index ... end + } + + // Drop created collection. + { + if (!mongoc_collection_drop (coll, &error)) { + HANDLE_ERROR ( + "Failed to drop collection '%s': %s", collname, error.message); + } + } + + ok = true; +fail: + mongoc_collection_destroy (coll); + mongoc_client_destroy (client); + mongoc_uri_destroy (uri); + mongoc_cleanup (); + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/libmongoc/tests/bsonutil/bson-match.c b/src/libmongoc/tests/bsonutil/bson-match.c index f49725d043..ed129ab16a 100644 --- a/src/libmongoc/tests/bsonutil/bson-match.c +++ b/src/libmongoc/tests/bsonutil/bson-match.c @@ -565,7 +565,9 @@ bson_matcher_match (bson_matcher_t *matcher, memcpy (&tmp_error, error, sizeof (bson_error_t)); test_set_error (error, - "BSON match failed: %s\nExpected: %s\nActual: %s", + "BSON match failed: %s\n" + "Expected: %s\n" + "Actual: %s", tmp_error.message, bson_val_to_json (expected), bson_val_to_json (actual)); diff --git a/src/libmongoc/tests/bsonutil/bson-parser.c b/src/libmongoc/tests/bsonutil/bson-parser.c index cab3b38dfe..ff4ada2a60 100644 --- a/src/libmongoc/tests/bsonutil/bson-parser.c +++ b/src/libmongoc/tests/bsonutil/bson-parser.c @@ -708,7 +708,7 @@ bson_parser_parse (bson_parser_t *parser, bson_t *in, bson_error_t *error) { if (!entry->optional && !entry->set) { test_set_error (error, - "Required field %s was not found parsing: %s", + "Required field '%s' was not found parsing: %s", entry->key, tmp_json (in)); return false; diff --git a/src/libmongoc/tests/json/index-management/createSearchIndex.json b/src/libmongoc/tests/json/index-management/createSearchIndex.json new file mode 100644 index 0000000000..04cffbe9c9 --- /dev/null +++ b/src/libmongoc/tests/json/index-management/createSearchIndex.json @@ -0,0 +1,136 @@ +{ + "description": "createSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "no name provided for an index definition", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + } + } + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + } + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "name provided for an index definition", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index" + } + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/index-management/createSearchIndexes.json b/src/libmongoc/tests/json/index-management/createSearchIndexes.json new file mode 100644 index 0000000000..95dbedde77 --- /dev/null +++ b/src/libmongoc/tests/json/index-management/createSearchIndexes.json @@ -0,0 +1,172 @@ +{ + "description": "createSearchIndexes", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "empty index definition array", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [] + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "no name provided for an index definition", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [ + { + "definition": { + "mappings": { + "dynamic": true + } + } + } + ] + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + } + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "name provided for an index definition", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index" + } + ] + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/index-management/dropSearchIndex.json b/src/libmongoc/tests/json/index-management/dropSearchIndex.json new file mode 100644 index 0000000000..0f21a5b68d --- /dev/null +++ b/src/libmongoc/tests/json/index-management/dropSearchIndex.json @@ -0,0 +1,74 @@ +{ + "description": "dropSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "sends the correct command", + "operations": [ + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "dropSearchIndex": "collection0", + "name": "test index", + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/index-management/dropSearchIndexes.json b/src/libmongoc/tests/json/index-management/dropSearchIndexes.json new file mode 100644 index 0000000000..b73447f602 --- /dev/null +++ b/src/libmongoc/tests/json/index-management/dropSearchIndexes.json @@ -0,0 +1,73 @@ +{ + "description": "dropSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "sends the correct command", + "operations": [ + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "dropSearchIndex": "collection0", + "name": "test index", + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/index-management/listSearchIndexes.json b/src/libmongoc/tests/json/index-management/listSearchIndexes.json new file mode 100644 index 0000000000..24c51ad88c --- /dev/null +++ b/src/libmongoc/tests/json/index-management/listSearchIndexes.json @@ -0,0 +1,156 @@ +{ + "description": "listSearchIndexes", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "when no name is provided, it does not populate the filter", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "pipeline": [ + { + "$listSearchIndexes": {} + } + ] + } + } + } + ] + } + ] + }, + { + "description": "when a name is provided, it is present in the filter", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "pipeline": [ + { + "$listSearchIndexes": { + "name": "test index" + } + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "aggregation cursor options are supported", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "arguments": { + "name": "test index", + "aggregationOptions": { + "batchSize": 10 + } + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "cursor": { + "batchSize": 10 + }, + "pipeline": [ + { + "$listSearchIndexes": { + "name": "test index" + } + } + ], + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/index-management/updateSearchIndex.json b/src/libmongoc/tests/json/index-management/updateSearchIndex.json new file mode 100644 index 0000000000..88a46a3069 --- /dev/null +++ b/src/libmongoc/tests/json/index-management/updateSearchIndex.json @@ -0,0 +1,76 @@ +{ + "description": "updateSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "sends the correct command", + "operations": [ + { + "name": "updateSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index", + "definition": {} + }, + "expectError": { + "isError": true, + "errorContains": "Search index commands are only supported with Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "updateSearchIndex": "collection0", + "name": "test index", + "definition": {}, + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/unified/operation.c b/src/libmongoc/tests/unified/operation.c index 00c752f714..f4e3a08139 100644 --- a/src/libmongoc/tests/unified/operation.c +++ b/src/libmongoc/tests/unified/operation.c @@ -23,6 +23,7 @@ #include "test-libmongoc.h" #include "util.h" #include "utlist.h" +#include "bson-dsl.h" typedef struct { char *name; @@ -3524,6 +3525,230 @@ operation_rename (test_t *test, return ret; } +static bool +operation_createSearchIndex (test_t *test, + operation_t *op, + result_t *result, + bson_error_t *error) +{ + bool ret = false; + bson_parser_t *bp = bson_parser_new (); + bson_t *model = NULL; + mongoc_collection_t *coll = NULL; + bson_error_t op_error; + bson_t op_reply = BSON_INITIALIZER; + bson_t *cmd = bson_new (); + + // Parse arguments. + bson_parser_doc (bp, "model", &model); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + + coll = entity_map_get_collection (test->entity_map, op->object, error); + if (!coll) { + goto done; + } + + // Build command. + bsonBuildAppend (*cmd, + kv ("createSearchIndexes", cstr (coll->collection)), + kv ("indexes", array (bson (*model)))); + ASSERT (!bsonBuildError); + + mongoc_collection_command_simple ( + coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error); + result_from_val_and_reply (result, NULL, &op_reply, &op_error); + ret = true; +done: + bson_destroy (cmd); + bson_parser_destroy_with_parsed_fields (bp); + bson_destroy (&op_reply); + return ret; +} + +static bool +operation_createSearchIndexes (test_t *test, + operation_t *op, + result_t *result, + bson_error_t *error) +{ + bool ret = false; + bson_parser_t *bp = bson_parser_new (); + bson_t *models = NULL; + mongoc_collection_t *coll = NULL; + bson_error_t op_error; + bson_t op_reply = BSON_INITIALIZER; + bson_t *cmd = bson_new (); + + // Parse arguments. + bson_parser_array (bp, "models", &models); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + + coll = entity_map_get_collection (test->entity_map, op->object, error); + if (!coll) { + goto done; + } + + // Build command. + bsonBuildAppend (*cmd, + kv ("createSearchIndexes", cstr (coll->collection)), + kv ("indexes", bsonArray (*models))); + ASSERT (!bsonBuildError); + + mongoc_collection_command_simple ( + coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error); + result_from_val_and_reply (result, NULL, &op_reply, &op_error); + ret = true; +done: + bson_destroy (cmd); + bson_parser_destroy_with_parsed_fields (bp); + bson_destroy (&op_reply); + return ret; +} + +static bool +operation_dropSearchIndex (test_t *test, + operation_t *op, + result_t *result, + bson_error_t *error) +{ + bool ret = false; + bson_parser_t *bp = bson_parser_new (); + char *name = NULL; + mongoc_collection_t *coll = NULL; + bson_error_t op_error; + bson_t op_reply = BSON_INITIALIZER; + bson_t *cmd = bson_new (); + + // Parse arguments. + bson_parser_utf8 (bp, "name", &name); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + + coll = entity_map_get_collection (test->entity_map, op->object, error); + if (!coll) { + goto done; + } + + // Build command. + bsonBuildAppend (*cmd, + kv ("dropSearchIndex", cstr (coll->collection)), + kv ("name", cstr (name))); + ASSERT (!bsonBuildError); + + mongoc_collection_command_simple ( + coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error); + result_from_val_and_reply (result, NULL, &op_reply, &op_error); + ret = true; +done: + bson_destroy (cmd); + bson_parser_destroy_with_parsed_fields (bp); + bson_destroy (&op_reply); + return ret; +} + +static bool +operation_listSearchIndexes (test_t *test, + operation_t *op, + result_t *result, + bson_error_t *error) +{ + bool ret = false; + bson_parser_t *bp = bson_parser_new (); + bson_t *aggregateOptions = NULL; + char *name = NULL; + mongoc_collection_t *coll = NULL; + bson_t *pipeline = bson_new (); + mongoc_cursor_t *cursor = NULL; + + // Parse arguments. + if (op->arguments) { + bson_parser_utf8_optional (bp, "name", &name); + bson_parser_doc_optional (bp, "aggregationOptions", &aggregateOptions); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + } + + coll = entity_map_get_collection (test->entity_map, op->object, error); + if (!coll) { + goto done; + } + + // Build command. + bsonBuildAppend ( + *pipeline, + kv ("pipeline", + array (doc (kv ("$listSearchIndexes", + if (name != NULL, + then (doc (kv ("name", cstr (name)))), + else (doc ()))))))); + ASSERT (!bsonBuildError); + + cursor = mongoc_collection_aggregate (coll, + MONGOC_QUERY_NONE, + pipeline, + aggregateOptions /* opts */, + NULL /* read_prefs */); + + result_from_cursor (result, cursor); + ret = true; +done: + mongoc_cursor_destroy (cursor); + bson_destroy (pipeline); + bson_parser_destroy_with_parsed_fields (bp); + return ret; +} + +static bool +operation_updateSearchIndex (test_t *test, + operation_t *op, + result_t *result, + bson_error_t *error) +{ + bool ret = false; + bson_parser_t *bp = bson_parser_new (); + bson_t *definition = NULL; + char *name = NULL; + mongoc_collection_t *coll = NULL; + bson_error_t op_error; + bson_t op_reply = BSON_INITIALIZER; + bson_t *cmd = bson_new (); + + // Parse arguments. + bson_parser_doc (bp, "definition", &definition); + bson_parser_utf8_optional (bp, "name", &name); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + + coll = entity_map_get_collection (test->entity_map, op->object, error); + if (!coll) { + goto done; + } + + // Build command. + bsonBuildAppend (*cmd, + kv ("updateSearchIndex", cstr (coll->collection)), + kv ("definition", bson (*definition)), + if (name != NULL, then (kv ("name", cstr (name))))); + ASSERT (!bsonBuildError); + + mongoc_collection_command_simple ( + coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error); + result_from_val_and_reply (result, NULL, &op_reply, &op_error); + ret = true; +done: + bson_destroy (cmd); + bson_parser_destroy_with_parsed_fields (bp); + bson_destroy (&op_reply); + return ret; +} + typedef struct { const char *op; bool (*fn) (test_t *, operation_t *, result_t *, bson_error_t *); @@ -3581,6 +3806,11 @@ operation_run (test_t *test, bson_t *op_bson, bson_error_t *error) {"updateOne", operation_update_one}, {"updateMany", operation_update_many}, {"rename", operation_rename}, + {"createSearchIndex", operation_createSearchIndex}, + {"createSearchIndexes", operation_createSearchIndexes}, + {"dropSearchIndex", operation_dropSearchIndex}, + {"listSearchIndexes", operation_listSearchIndexes}, + {"updateSearchIndex", operation_updateSearchIndex}, /* Change stream and cursor operations */ {"iterateUntilDocumentOrError", diff --git a/src/libmongoc/tests/unified/runner.c b/src/libmongoc/tests/unified/runner.c index 85ce959538..c9b657a2f6 100644 --- a/src/libmongoc/tests/unified/runner.c +++ b/src/libmongoc/tests/unified/runner.c @@ -1875,4 +1875,6 @@ test_install_unified (TestSuite *suite) run_unified_tests (suite, JSON_DIR, "retryable_reads/unified"); run_unified_tests (suite, JSON_DIR, "retryable_writes/unified"); + + run_unified_tests (suite, JSON_DIR, "index-management"); }