From 5bca38462d4eb58aa5339c45357f97556b11aeaa Mon Sep 17 00:00:00 2001 From: Samar Farooqi Date: Tue, 13 Oct 2026 19:31:53 +0000 Subject: [PATCH 1/2] Create the collection up front in session command tests The abortTransaction/commitTransaction tests insert into the fixture's collection inside the transaction, but the root collection fixture does not create it. The first insert therefore also creates the collection implicitly inside the transaction, so these tests all depend on implicit collection creation in a transaction. An engine without that capability fails them at the insert, before the command under test (commit, abort, comment, writeConcern, field types) is reached. Add an autouse fixture in core/sessions/commands/conftest.py that creates the test's collection before the test runs, and create the second collection in the two atomicity tests. Implicit creation in a transaction is now covered on its own by two new tests in test_commitTransaction_supported_operations.py, which insert into a separate, not-yet-created collection. Signed-off-by: Samar Farooqi --- .../test_abortTransaction_atomicity.py | 2 +- .../test_commitTransaction_atomicity.py | 2 +- ..._commitTransaction_supported_operations.py | 31 +++++++++++++++++++ .../tests/core/sessions/commands/conftest.py | 18 +++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/core/sessions/commands/conftest.py diff --git a/documentdb_tests/compatibility/tests/core/sessions/commands/abortTransaction/test_abortTransaction_atomicity.py b/documentdb_tests/compatibility/tests/core/sessions/commands/abortTransaction/test_abortTransaction_atomicity.py index 39ce445ca..f2be5871b 100644 --- a/documentdb_tests/compatibility/tests/core/sessions/commands/abortTransaction/test_abortTransaction_atomicity.py +++ b/documentdb_tests/compatibility/tests/core/sessions/commands/abortTransaction/test_abortTransaction_atomicity.py @@ -17,7 +17,7 @@ def test_abort_rolls_back_writes_across_collections(collection): """Aborting rolls back writes made to a second collection in the same txn.""" client = collection.database.client - other = collection.database[f"{collection.name}_other"] + other = collection.database.create_collection(f"{collection.name}_other") with client.start_session() as session: session.start_transaction() collection.insert_one({"_id": 1}, session=session) diff --git a/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_atomicity.py b/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_atomicity.py index 628e98aee..587cf42b9 100644 --- a/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_atomicity.py +++ b/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_atomicity.py @@ -18,7 +18,7 @@ def test_commit_persists_writes_across_collections(collection): """Committing persists writes made to a second collection in the same txn.""" client = collection.database.client - other = collection.database[f"{collection.name}_other"] + other = collection.database.create_collection(f"{collection.name}_other") with client.start_session() as session: session.start_transaction() collection.insert_one({"_id": 1}, session=session) diff --git a/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_supported_operations.py b/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_supported_operations.py index 60e72f74c..9cc3b8dc2 100644 --- a/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_supported_operations.py +++ b/documentdb_tests/compatibility/tests/core/sessions/commands/commitTransaction/test_commitTransaction_supported_operations.py @@ -131,6 +131,37 @@ def test_create_collection_runs_in_transaction(collection): assertSuccessPartial(result, {"ok": 1.0}) +def test_insert_implicitly_creates_collection_in_transaction(collection): + """An insert into a collection that does not exist yet is accepted in a transaction. + + The insert implicitly creates the collection inside the transaction. The + transaction is left uncommitted (it aborts when the session ends) so the + insert's own response is what is asserted. + """ + new_collection = f"{collection.name}_new" + client = collection.database.client + with client.start_session() as session: + session.start_transaction() + result = execute_command( + collection, + {"insert": new_collection, "documents": [{"_id": 1}]}, + session=session, + ) + assertSuccessPartial(result, {"ok": 1.0, "n": 1}) + + +def test_implicitly_created_collection_persists_after_commit(collection): + """A collection implicitly created by an insert in a transaction exists after commit.""" + new_collection = collection.database[f"{collection.name}_new"] + client = collection.database.client + with client.start_session() as session: + session.start_transaction() + new_collection.insert_one({"_id": 1}, session=session) + session.commit_transaction() + result = execute_command(collection, {"find": new_collection.name, "filter": {}}) + assertSuccess(result, [{"_id": 1}]) + + def test_createIndexes_on_new_collection_runs_in_transaction(collection): """createIndexes is accepted on a collection created earlier in the same transaction.""" client = collection.database.client diff --git a/documentdb_tests/compatibility/tests/core/sessions/commands/conftest.py b/documentdb_tests/compatibility/tests/core/sessions/commands/conftest.py new file mode 100644 index 000000000..f7f7a9624 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/sessions/commands/conftest.py @@ -0,0 +1,18 @@ +"""Shared fixtures for session and transaction command tests.""" + +import pytest + + +@pytest.fixture(autouse=True) +def create_collection_up_front(request): + """Create the test's ``collection`` before the test body runs. + + The root ``collection`` fixture does not create the collection, so a test's + first write inside a transaction would also create it implicitly. Creating + it here keeps these tests independent of implicit collection creation in a + transaction, which is tested on its own in + test_commitTransaction_supported_operations.py. + """ + if "collection" in request.fixturenames: + coll = request.getfixturevalue("collection") + coll.database.create_collection(coll.name) From 3412e424a45e517ff3224e0e5fb1bd503269898e Mon Sep 17 00:00:00 2001 From: Samar Farooqi Date: Tue, 13 Oct 2026 19:04:34 +0000 Subject: [PATCH 2/2] Insert the JavaScript document only in JavaScript cases test_type_matching.py and test_delete_document_with_bson_type insert a shared all-BSON-types fixture that includes one JavaScript (Code) document into every case. An engine that does not support the JavaScript BSON type rejects the whole insert, so every case fails at setup, including the date, string, null and other cases that are not about JavaScript, and $type / delete is never exercised for them. Insert the JavaScript document only in the cases that are about it ($type 13, $type "javascript", the javascript code/alias equivalence pair, and the javascript delete case). The other cases insert the same documents minus that one. Expected results are unchanged: they still index ALL_TYPES_DOCS, which holds the same document objects. Signed-off-by: Samar Farooqi --- .../query/misc/type/test_type_matching.py | 73 ++++++++++--------- .../delete/test_delete_null_and_types.py | 5 +- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/documentdb_tests/compatibility/tests/core/operator/query/misc/type/test_type_matching.py b/documentdb_tests/compatibility/tests/core/operator/query/misc/type/test_type_matching.py index 64144060b..d74850360 100644 --- a/documentdb_tests/compatibility/tests/core/operator/query/misc/type/test_type_matching.py +++ b/documentdb_tests/compatibility/tests/core/operator/query/misc/type/test_type_matching.py @@ -41,6 +41,13 @@ {"_id": 17, "x": MaxKey()}, # maxKey ] +# ALL_TYPES_DOCS without the JavaScript document. Engines that do not support +# the JavaScript BSON type reject the whole insert when it contains one, so +# cases that are not about JavaScript use this set; the JavaScript cases keep +# ALL_TYPES_DOCS. The expected results still index ALL_TYPES_DOCS, since both +# lists hold the same document objects. +TYPES_DOCS_WITHOUT_JS: list[dict] = [d for d in ALL_TYPES_DOCS if not isinstance(d["x"], Code)] + MIXED_DOCS: list[dict] = [ {"_id": 1, "x": 1.5}, # double {"_id": 2, "x": 42}, # int32 @@ -57,70 +64,70 @@ QueryTestCase( id="numeric_code_1_double", filter={"x": {"$type": 1}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[0]], msg="$type: 1 should match double fields", ), QueryTestCase( id="numeric_code_2_string", filter={"x": {"$type": 2}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[1]], msg="$type: 2 should match string fields", ), QueryTestCase( id="numeric_code_3_object", filter={"x": {"$type": 3}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[2]], msg="$type: 3 should match object fields", ), QueryTestCase( id="numeric_code_4_array", filter={"x": {"$type": 4}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[3]], msg="$type: 4 should match array fields", ), QueryTestCase( id="numeric_code_5_binData", filter={"x": {"$type": 5}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[{"_id": 5, "x": b"\x01\x02"}], msg="$type: 5 should match binData fields", ), QueryTestCase( id="numeric_code_7_objectId", filter={"x": {"$type": 7}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[5]], msg="$type: 7 should match objectId fields", ), QueryTestCase( id="numeric_code_8_bool", filter={"x": {"$type": 8}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[6]], msg="$type: 8 should match bool fields", ), QueryTestCase( id="numeric_code_9_date", filter={"x": {"$type": 9}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[7]], msg="$type: 9 should match date fields", ), QueryTestCase( id="numeric_code_10_null", filter={"x": {"$type": 10}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[8]], msg="$type: 10 should match null fields", ), QueryTestCase( id="numeric_code_11_regex", filter={"x": {"$type": 11}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[9]], msg="$type: 11 should match regex fields", ), @@ -134,42 +141,42 @@ QueryTestCase( id="numeric_code_16_int", filter={"x": {"$type": 16}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[3], ALL_TYPES_DOCS[11]], msg="$type: 16 should match int32 fields and arrays containing int32 elements", ), QueryTestCase( id="numeric_code_17_timestamp", filter={"x": {"$type": 17}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[12]], msg="$type: 17 should match timestamp fields", ), QueryTestCase( id="numeric_code_18_long", filter={"x": {"$type": 18}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[13]], msg="$type: 18 should match long fields", ), QueryTestCase( id="numeric_code_19_decimal", filter={"x": {"$type": 19}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[14]], msg="$type: 19 should match decimal128 fields", ), QueryTestCase( id="numeric_code_neg1_minKey", filter={"x": {"$type": -1}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[15]], msg="$type: -1 should match minKey fields", ), QueryTestCase( id="numeric_code_127_maxKey", filter={"x": {"$type": 127}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[16]], msg="$type: 127 should match maxKey fields", ), @@ -179,70 +186,70 @@ QueryTestCase( id="string_alias_double", filter={"x": {"$type": "double"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[0]], msg="$type: 'double' should match double fields", ), QueryTestCase( id="string_alias_string", filter={"x": {"$type": "string"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[1]], msg="$type: 'string' should match string fields", ), QueryTestCase( id="string_alias_object", filter={"x": {"$type": "object"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[2]], msg="$type: 'object' should match object fields", ), QueryTestCase( id="string_alias_array", filter={"x": {"$type": "array"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[3]], msg="$type: 'array' should match array fields", ), QueryTestCase( id="string_alias_binData", filter={"x": {"$type": "binData"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[{"_id": 5, "x": b"\x01\x02"}], msg="$type: 'binData' should match binData fields", ), QueryTestCase( id="string_alias_objectId", filter={"x": {"$type": "objectId"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[5]], msg="$type: 'objectId' should match objectId fields", ), QueryTestCase( id="string_alias_bool", filter={"x": {"$type": "bool"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[6]], msg="$type: 'bool' should match bool fields", ), QueryTestCase( id="string_alias_date", filter={"x": {"$type": "date"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[7]], msg="$type: 'date' should match date fields", ), QueryTestCase( id="string_alias_null", filter={"x": {"$type": "null"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[8]], msg="$type: 'null' should match null fields", ), QueryTestCase( id="string_alias_regex", filter={"x": {"$type": "regex"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[9]], msg="$type: 'regex' should match regex fields", ), @@ -256,42 +263,42 @@ QueryTestCase( id="string_alias_int", filter={"x": {"$type": "int"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[3], ALL_TYPES_DOCS[11]], msg="$type: 'int' should match int32 fields and arrays containing int32 elements", ), QueryTestCase( id="string_alias_timestamp", filter={"x": {"$type": "timestamp"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[12]], msg="$type: 'timestamp' should match timestamp fields", ), QueryTestCase( id="string_alias_long", filter={"x": {"$type": "long"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[13]], msg="$type: 'long' should match long fields", ), QueryTestCase( id="string_alias_decimal", filter={"x": {"$type": "decimal"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[14]], msg="$type: 'decimal' should match decimal128 fields", ), QueryTestCase( id="string_alias_minKey", filter={"x": {"$type": "minKey"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[15]], msg="$type: 'minKey' should match minKey fields", ), QueryTestCase( id="string_alias_maxKey", filter={"x": {"$type": "maxKey"}}, - doc=ALL_TYPES_DOCS, + doc=TYPES_DOCS_WITHOUT_JS, expected=[ALL_TYPES_DOCS[16]], msg="$type: 'maxKey' should match maxKey fields", ), @@ -508,7 +515,7 @@ def test_type_matching(collection, test): ) def test_type_numeric_string_equivalence(collection, alias, code): """Test that numeric type code and string alias return identical results.""" - collection.insert_many(ALL_TYPES_DOCS) + collection.insert_many(ALL_TYPES_DOCS if alias == "javascript" else TYPES_DOCS_WITHOUT_JS) codec = CodecOptions(tz_aware=True, tzinfo=timezone.utc) result_code = execute_command( collection, diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/commands/delete/test_delete_null_and_types.py b/documentdb_tests/compatibility/tests/core/query_and_write/commands/delete/test_delete_null_and_types.py index d5f702f97..4840b7f6c 100644 --- a/documentdb_tests/compatibility/tests/core/query_and_write/commands/delete/test_delete_null_and_types.py +++ b/documentdb_tests/compatibility/tests/core/query_and_write/commands/delete/test_delete_null_and_types.py @@ -147,7 +147,10 @@ def test_delete_null_and_types(database_client, collection, test): @pytest.mark.parametrize("type_id", _BSON_TYPE_PARAMS) def test_delete_document_with_bson_type(collection, type_id): """Test delete can remove documents containing each BSON type.""" - collection.insert_many(_BSON_TYPE_DOCS) + # Only the javascript case inserts the JavaScript document: engines without + # that BSON type reject the whole insert, which would block every other case. + docs = [d for d in _BSON_TYPE_DOCS if d["_id"] == type_id or not isinstance(d["val"], Code)] + collection.insert_many(docs) result = execute_command( collection, {"delete": collection.name, "deletes": [{"q": {"_id": type_id}, "limit": 1}]},