Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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": {},
Expand Down Expand Up @@ -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
Expand All @@ -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": {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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".
Expand Down
Loading