diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/test_expressions_combination_variables.py b/documentdb_tests/compatibility/tests/core/operator/expressions/test_expressions_combination_variables.py index ff55a776f..43882418e 100644 --- a/documentdb_tests/compatibility/tests/core/operator/expressions/test_expressions_combination_variables.py +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/test_expressions_combination_variables.py @@ -160,12 +160,12 @@ def test_let_nested_combinations(collection, test): # --------------------------------------------------------------------------- def test_let_two_lets_same_projection(collection): """Test two separate $let expressions in same projection with same variable name.""" + collection.insert_one({}) result = execute_command( collection, { - "aggregate": 1, + "aggregate": collection.name, "pipeline": [ - {"$documents": [{}]}, { "$project": { "_id": 0, @@ -248,12 +248,12 @@ def test_let_across_multiple_documents(collection): def test_let_error_cross_let_variable_ref(collection): """Test $let where variable defined in one $let is referenced in sibling $let.""" + collection.insert_one({}) result = execute_command( collection, { - "aggregate": 1, + "aggregate": collection.name, "pipeline": [ - {"$documents": [{}]}, { "$project": { "_id": 0, diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/utils/utils.py b/documentdb_tests/compatibility/tests/core/operator/expressions/utils/utils.py index ec6d21c75..f8d45ce5e 100644 --- a/documentdb_tests/compatibility/tests/core/operator/expressions/utils/utils.py +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/utils/utils.py @@ -39,6 +39,19 @@ def execute_project(collection, project): """ Execute a projection with literal input values. + Evaluates the projection against a single empty document. The document is + inserted into the collection and the pipeline runs over that collection, + rather than synthesizing the row with a ``$documents`` stage. This keeps the + helper free of any dependency on ``$documents`` support while producing the + same single-row input the projection sees. + + Note: the inserted document carries an auto-generated ``_id`` and the helper + aggregates over the whole collection. The output projection excludes ``_id``, + so literal expressions and missing-field references behave identically to a + ``$documents: [{}]`` row. Callers that need a truly field-less input (e.g. + ``$$ROOT`` must be ``{}``) or exactly one row over a pre-populated collection + must shape their own pipeline instead of using this helper. + Args: collection: MongoDB collection object project: Fields to project. Do not include _id; the function always @@ -51,12 +64,12 @@ def execute_project(collection, project): >>> execute_project(collection, {"sum": {"$add": [1, 2]}}) # Returns result with {"sum": 3} in firstBatch """ + collection.insert_one({}) return execute_command( collection, { - "aggregate": 1, + "aggregate": collection.name, "pipeline": [ - {"$documents": [{}]}, {"$project": {**materialize(project), "_id": 0}}, ], "cursor": {}, @@ -100,10 +113,22 @@ def execute_project_with_insert(collection, document, project): def execute_expression(collection, expression): """ - Execute an aggregation expression using $documents stage. - - Evaluates an expression against an empty document using the $documents - stage. Useful for testing expressions with literal values. + Execute an aggregation expression against a single empty document. + + Evaluates an expression against an empty document. The document is inserted + into the collection and the pipeline runs over that collection, rather than + synthesizing the row with a ``$documents`` stage. This keeps the helper free + of any dependency on ``$documents`` support while producing the same + single-row input the expression is evaluated against. Useful for testing + expressions with literal values; field references resolve to missing, just + as they would against a ``$documents: [{}]`` row. + + Note: the inserted document carries an auto-generated ``_id`` and the helper + aggregates over the whole collection. The output projection excludes ``_id``, + so literal expressions and missing-field references are unaffected. Callers + that need a truly field-less input (e.g. ``$$ROOT`` must be ``{}``) or exactly + one row over a pre-populated collection must shape their own pipeline instead + of using this helper. Args: collection: MongoDB collection object @@ -117,12 +142,12 @@ def execute_expression(collection, expression): >>> execute_expression(collection, {"$add": [1, 2]}) # Returns result with {"result": 3} in firstBatch """ + collection.insert_one({}) return execute_command( collection, { - "aggregate": 1, + "aggregate": collection.name, "pipeline": [ - {"$documents": [{}]}, {"$project": {"_id": 0, "result": expression}}, ], "cursor": {}, diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/now/test_now_core_semantics.py b/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/now/test_now_core_semantics.py index 8dc17e8ec..bf1db1b59 100644 --- a/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/now/test_now_core_semantics.py +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/now/test_now_core_semantics.py @@ -198,10 +198,23 @@ def test_now_identical_across_getmore_batches(collection): seen.extend(doc["t"] for doc in batch["cursor"]["nextBatch"]) cursor_id = batch["cursor"]["id"] - result = execute_expression(collection, {"$size": {"$setUnion": [seen]}}) - assert_expression_result( + # The collection is pre-populated (300 docs), so ``execute_expression`` — which + # aggregates over the whole collection — would emit one row per document. A + # ``$limit: 1`` reduces it to the single row this assertion expects. + result = execute_command( + collection, + { + "aggregate": collection.name, + "pipeline": [ + {"$limit": 1}, + {"$project": {"_id": 0, "result": {"$size": {"$setUnion": [seen]}}}}, + ], + "cursor": {}, + }, + ) + assertSuccess( result, - expected=1, + [{"result": 1}], msg="$$NOW should be identical across every getMore batch of one cursor", ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/root/test_root_core_behavior.py b/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/root/test_root_core_behavior.py index 22ed192ed..38b93ab90 100644 --- a/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/root/test_root_core_behavior.py +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/variable/system_variables/root/test_root_core_behavior.py @@ -17,10 +17,10 @@ ) from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( assert_expression_result, - execute_expression, execute_expression_with_insert, ) from documentdb_tests.framework.assertions import assertSuccess +from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.test_constants import DOUBLE_PRECISION_LOSS, INT64_MAX @@ -122,27 +122,32 @@ def test_root_echoes_doc(collection, test): # Property [Empty Document]: $$ROOT is an empty object when the input document has # no fields. -ROOT_EMPTY_DOCUMENT_TESTS: list[ExpressionTestCase] = [ - ExpressionTestCase( - id="empty_document", - expression="$$ROOT", - doc=None, - expected={}, - msg="$$ROOT should return an empty object when the input document has no fields", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(ROOT_EMPTY_DOCUMENT_TESTS)) -def test_root_empty_document(collection, test): +def test_root_empty_document(collection): """$$ROOT over a field-less input document. - ``doc=None`` selects execute_expression, which evaluates the expression over - a ``$documents: [{}]`` stage rather than inserting a document, since an - inserted document would always be given an ``_id``. + This case needs a truly field-less input row, so it cannot use the shared + ``execute_expression`` helper: that helper inserts a document (which always + carries an auto-generated ``_id``), which would make ``$$ROOT`` a one-field + document. Instead a document is inserted and ``$replaceWith: {$literal: {}}`` + strips it back to a field-less row before ``$$ROOT`` is read. """ - result = execute_expression(collection, test.expression) - assert_expression_result(result, expected=test.expected, msg=test.msg) + collection.insert_one({}) + result = execute_command( + collection, + { + "aggregate": collection.name, + "pipeline": [ + {"$replaceWith": {"$literal": {}}}, + {"$project": {"_id": 0, "result": "$$ROOT"}}, + ], + "cursor": {}, + }, + ) + assertSuccess( + result, + [{"result": {}}], + msg="$$ROOT should return an empty object when the input document has no fields", + ) # Property [Reported Type]: $$ROOT always reports BSON type "object".