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
@@ -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"
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)}])
1 change: 1 addition & 0 deletions documentdb_tests/framework/error_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,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
Expand Down