From b1f26520a8ca801a8d48d310183dc8bdd69647ac Mon Sep 17 00:00:00 2001 From: RyanGarfinkel <113050972+RyanGarfinkel@users.noreply.github.com> Date: Sun, 21 Jun 2026 11:27:14 -0700 Subject: [PATCH 1/3] Add $tsIncrement second-pass tests Signed-off-by: RyanGarfinkel <113050972+RyanGarfinkel@users.noreply.github.com> --- .../timestamp/tsIncrement/test_tsIncrement.py | 177 ++++++++++++++++++ documentdb_tests/framework/error_codes.py | 1 + 2 files changed, 178 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/timestamp/tsIncrement/test_tsIncrement.py diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/timestamp/tsIncrement/test_tsIncrement.py b/documentdb_tests/compatibility/tests/core/operator/expressions/timestamp/tsIncrement/test_tsIncrement.py new file mode 100644 index 000000000..3899283c7 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/timestamp/tsIncrement/test_tsIncrement.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import Any + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import TS_INCREMENT_TYPE_ERROR +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_case import BaseTestCase + +pytestmark = pytest.mark.aggregate + + +@dataclass(frozen=True) +class TsIncrementTest(BaseTestCase): + doc: dict[str, Any] | None = None + + +TESTS: list[TsIncrementTest] = [ + TsIncrementTest( + "null_input", + doc={"ts": None}, + expected=None, + msg="null ts field returns null", + ), + TsIncrementTest( + "missing_field", + doc={}, + expected=None, + msg="missing ts field returns null", + ), + TsIncrementTest( + "ordinal_zero", + doc={"ts": Timestamp(100, 0)}, + expected=Int64(0), + msg="ordinal=0 is returned as Int64(0)", + ), + TsIncrementTest( + "ordinal_one", + doc={"ts": Timestamp(0, 1)}, + expected=Int64(1), + msg="ordinal=1 is returned as Int64(1)", + ), + TsIncrementTest( + "ordinal_max_uint32", + doc={"ts": Timestamp(0, 4294967295)}, + expected=Int64(4294967295), + msg="max uint32 ordinal (4294967295) is preserved as Int64", + ), + TsIncrementTest( + "seconds_ignored", + doc={"ts": Timestamp(4294967295, 42)}, + expected=Int64(42), + msg="only the ordinal component is returned; seconds field is ignored", + ), + TsIncrementTest( + "type_double", + doc={"ts": 3.14}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="double input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_string", + doc={"ts": "hello"}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="string input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_object", + doc={"ts": {"a": 1}}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="object input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_array", + doc={"ts": [1, 2]}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="array input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_bindata", + doc={"ts": Binary(b"\x00\x01")}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="Binary input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_objectid", + doc={"ts": ObjectId("000000000000000000000000")}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="ObjectId input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_bool", + doc={"ts": True}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="bool input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_date", + doc={"ts": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="date input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_regex", + doc={"ts": Regex("^abc")}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="Regex input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_code", + doc={"ts": Code("function() {}")}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="Code (JavaScript) input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_int", + doc={"ts": 42}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="int32 input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_long", + doc={"ts": Int64(100)}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="int64 input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_decimal128", + doc={"ts": Decimal128("1")}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="Decimal128 input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_minkey", + doc={"ts": MinKey()}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="MinKey input raises TS_INCREMENT_TYPE_ERROR", + ), + TsIncrementTest( + "type_maxkey", + doc={"ts": MaxKey()}, + error_code=TS_INCREMENT_TYPE_ERROR, + msg="MaxKey input raises TS_INCREMENT_TYPE_ERROR", + ), +] + + +@pytest.mark.parametrize("test_case", pytest_params(TESTS)) +def test_tsIncrement(collection, test_case: TsIncrementTest): + """Test $tsIncrement operator: null propagation, boundary values, type rejection.""" + result = execute_expression_with_insert(collection, {"$tsIncrement": "$ts"}, test_case.doc) + assert_expression_result( + result, + expected=test_case.expected, + error_code=test_case.error_code, + msg=test_case.msg, + ) + + +def test_tsIncrement_nested_field(collection): + """$tsIncrement resolves a nested field path ($a.b) to extract the ordinal.""" + result = execute_expression_with_insert( + collection, + {"$tsIncrement": "$a.b"}, + {"a": {"b": Timestamp(100, 7)}}, + ) + assert_expression_result( + result, expected=Int64(7), msg="nested path $a.b resolves Timestamp correctly" + ) diff --git a/documentdb_tests/framework/error_codes.py b/documentdb_tests/framework/error_codes.py index fd892adbc..91f080d75 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -460,6 +460,7 @@ DATETRUNC_INVALID_BINSIZE_VALUE_ERROR = 5439018 COLLSTATS_ARG_NOT_OBJECT_ERROR = 5447000 NEAR_NOT_ALLOWED_ERROR = 5626500 +TS_INCREMENT_TYPE_ERROR = 5687302 GEO_NEAR_KEY_DEPTH_LIMIT_ERROR = 5729100 MODULO_DECIMAL128_ZERO_REMAINDER_ERROR = 5733415 MERGE_INTO_EMPTY_STRING_ERROR = 5786800 From 7baa339c45519ecc3de8f8e212f305d6e68a346a Mon Sep 17 00:00:00 2001 From: RyanGarfinkel <113050972+RyanGarfinkel@users.noreply.github.com> Date: Sun, 21 Jun 2026 11:38:12 -0700 Subject: [PATCH 2/3] Add $tsIncrement wiring test to $match with $expr Signed-off-by: RyanGarfinkel <113050972+RyanGarfinkel@users.noreply.github.com> --- .../stages/match/test_match_with_expr.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/documentdb_tests/compatibility/tests/core/operator/stages/match/test_match_with_expr.py b/documentdb_tests/compatibility/tests/core/operator/stages/match/test_match_with_expr.py index 4fe92512b..0979ab5f5 100644 --- a/documentdb_tests/compatibility/tests/core/operator/stages/match/test_match_with_expr.py +++ b/documentdb_tests/compatibility/tests/core/operator/stages/match/test_match_with_expr.py @@ -6,6 +6,9 @@ behavior, and $match with $expr after other pipeline stages. """ +import pytest +from bson import Int64, Timestamp + from documentdb_tests.framework.assertions import ( assertFailureCode, assertSuccess, @@ -195,3 +198,23 @@ def test_expr_match_with_aggregate_let(collection): }, ) assertSuccess(result, [{"_id": 1, "a": 5, "b": 3}]) + + +@pytest.mark.aggregate +def test_tsIncrement_in_match_expr(collection): + """$tsIncrement works inside $match with $expr to filter documents by ordinal.""" + collection.insert_many( + [ + {"_id": 1, "ts": Timestamp(100, 5)}, + {"_id": 2, "ts": Timestamp(200, 10)}, + ] + ) + result = execute_command( + collection, + { + "aggregate": collection.name, + "pipeline": [{"$match": {"$expr": {"$gt": [{"$tsIncrement": "$ts"}, Int64(6)]}}}], + "cursor": {}, + }, + ) + assertSuccess(result, [{"_id": 2, "ts": Timestamp(200, 10)}]) From 3e5f020c6cbe2483f87ed23413e517b3a2ca5d80 Mon Sep 17 00:00:00 2001 From: RyanGarfinkel <113050972+RyanGarfinkel@users.noreply.github.com> Date: Sun, 21 Jun 2026 12:25:37 -0700 Subject: [PATCH 3/3] Add $tsIncrement tests for $group expressions Signed-off-by: RyanGarfinkel <113050972+RyanGarfinkel@users.noreply.github.com> --- .../stages/group/test_group_expr_operators.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/core/operator/stages/group/test_group_expr_operators.py diff --git a/documentdb_tests/compatibility/tests/core/operator/stages/group/test_group_expr_operators.py b/documentdb_tests/compatibility/tests/core/operator/stages/group/test_group_expr_operators.py new file mode 100644 index 000000000..07a26b48c --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/stages/group/test_group_expr_operators.py @@ -0,0 +1,66 @@ +"""Tests that expression operators work within $group expressions.""" + +from __future__ import annotations + +from typing import Any + +import pytest +from bson import Int64, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.stages.utils.stage_test_case import ( + StageTestCase, + populate_collection, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +GROUP_EXPRESSION_TESTS: list[StageTestCase] = [ + StageTestCase( + id="group_id_tsIncrement", + docs=[ + {"_id": 1, "ts": Timestamp(100, 5)}, + {"_id": 2, "ts": Timestamp(200, 5)}, + {"_id": 3, "ts": Timestamp(300, 10)}, + ], + pipeline=[ + {"$group": {"_id": {"$tsIncrement": "$ts"}, "count": {"$sum": 1}}}, + {"$sort": {"_id": 1}}, + ], + expected=[{"_id": Int64(5), "count": 2}, {"_id": Int64(10), "count": 1}], + msg="$tsIncrement should work as a $group _id expression", + ), + StageTestCase( + id="group_accumulator_tsIncrement", + docs=[ + {"_id": 1, "ts": Timestamp(100, 3)}, + {"_id": 2, "ts": Timestamp(200, 7)}, + ], + pipeline=[ + {"$group": {"_id": None, "max_ordinal": {"$max": {"$tsIncrement": "$ts"}}}}, + ], + expected=[{"_id": None, "max_ordinal": Int64(7)}], + msg="$tsIncrement should work inside an accumulator expression in $group", + ), +] + + +@pytest.mark.aggregate +@pytest.mark.parametrize("test_case", pytest_params(GROUP_EXPRESSION_TESTS)) +def test_group_expression_cases(collection: Any, test_case: StageTestCase): + """Test that expression operators work within $group.""" + coll = populate_collection(collection, test_case) + result = execute_command( + coll, + { + "aggregate": coll.name, + "pipeline": test_case.pipeline, + "cursor": {}, + }, + ) + assertResult( + result, + expected=test_case.expected, + error_code=test_case.error_code, + msg=test_case.msg, + )