-
Notifications
You must be signed in to change notification settings - Fork 10
Add $nor query tests #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add $nor query tests #183
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
a7562c6
Initial nor tests
PatersonProjects 8b25d6e
Removed out of scope cases, added some missing coverage of edge cases
PatersonProjects a0432c2
Removed nested operators (covered by and PR)
PatersonProjects aaa6ba4
Moved tests to more concisely group together
PatersonProjects 0ded51a
Added tests to cover holes in coverage
PatersonProjects d7c0a86
Used pytest.approx to fix nan comparison
PatersonProjects 92cc4ec
Merge branch 'main' into nor_query_tests
PatersonProjects 39cf8d3
Changed to use contants
PatersonProjects d6adb2c
Docstring edits, removed duplicates, added PR requested tests
PatersonProjects 8e90cf8
Added mixed scalar and array test
PatersonProjects 50651fb
Added nested elemMatch test case
PatersonProjects de7f0ad
Merge branch 'main' into nor_query_tests
PatersonProjects 587f691
Removed out of scope tests
PatersonProjects 9db3721
Merge branch 'main' into nor_query_tests
eerxuan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
176 changes: 176 additions & 0 deletions
176
...b_tests/compatibility/tests/core/operator/query/logical/nor/test_nor_argument_handling.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| """ | ||
| Tests for $nor query operator argument handling. | ||
|
|
||
| Covers valid array argument variations: single expression, multiple expressions, | ||
| many expressions, empty object in array, multiple fields in a single expression, | ||
| and clause behavior (ordering invariance, nested double negation, combined filters). | ||
| """ | ||
|
|
||
| import pytest | ||
|
|
||
| from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import ( | ||
| QueryTestCase, | ||
| ) | ||
| from documentdb_tests.framework.assertions import assertResult | ||
| from documentdb_tests.framework.executor import execute_command | ||
| from documentdb_tests.framework.parametrize import pytest_params | ||
|
|
||
| DOCS = [{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}] | ||
|
|
||
| VALID_ARRAY_TESTS: list[QueryTestCase] = [ | ||
| QueryTestCase( | ||
| id="single_expression", | ||
| filter={"$nor": [{"a": 1}]}, | ||
| doc=DOCS, | ||
| expected=[{"_id": 2, "a": 2, "b": 1}], | ||
| msg="$nor with single expression should exclude matching docs", | ||
| ), | ||
| QueryTestCase( | ||
| id="two_expressions", | ||
| filter={"$nor": [{"a": 1}, {"b": 1}]}, | ||
| doc=[{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}, {"_id": 3, "a": 2, "b": 2}], | ||
| expected=[{"_id": 3, "a": 2, "b": 2}], | ||
| msg="$nor with two expressions should return docs failing both", | ||
| ), | ||
| QueryTestCase( | ||
| id="many_expressions", | ||
| filter={"$nor": [{"a": 1}, {"b": 1}, {"a": 3}, {"b": 3}, {"a": 4}]}, | ||
| doc=[{"_id": 1, "a": 1}, {"_id": 2, "a": 2, "b": 2}], | ||
| expected=[{"_id": 2, "a": 2, "b": 2}], | ||
| msg="$nor with many expressions should return docs failing all", | ||
| ), | ||
| QueryTestCase( | ||
| id="all_docs_match_at_least_one", | ||
| filter={"$nor": [{"a": 1}, {"a": 2}]}, | ||
| doc=[ | ||
| {"_id": 1, "a": 1, "b": 1}, | ||
| {"_id": 2, "a": 1, "b": 2}, | ||
| {"_id": 3, "a": 2, "b": 1}, | ||
| {"_id": 4, "a": 2, "b": 2}, | ||
| ], | ||
| expected=[], | ||
| msg="$nor should return empty when all docs match at least one condition", | ||
| ), | ||
| QueryTestCase( | ||
| id="no_docs_match_any", | ||
| filter={"$nor": [{"a": 99}, {"b": 99}]}, | ||
| doc=[ | ||
| {"_id": 1, "a": 1, "b": 1}, | ||
| {"_id": 2, "a": 1, "b": 2}, | ||
| {"_id": 3, "a": 2, "b": 1}, | ||
| {"_id": 4, "a": 2, "b": 2}, | ||
| ], | ||
| expected=[ | ||
| {"_id": 1, "a": 1, "b": 1}, | ||
| {"_id": 2, "a": 1, "b": 2}, | ||
| {"_id": 3, "a": 2, "b": 1}, | ||
| {"_id": 4, "a": 2, "b": 2}, | ||
| ], | ||
| msg="$nor should return all docs when none match any condition", | ||
| ), | ||
| QueryTestCase( | ||
| id="duplicate_expressions", | ||
| filter={"$nor": [{"a": 1}, {"a": 1}]}, | ||
| doc=[ | ||
| {"_id": 1, "a": 1, "b": 1}, | ||
| {"_id": 2, "a": 1, "b": 2}, | ||
| {"_id": 3, "a": 2, "b": 1}, | ||
| {"_id": 4, "a": 2, "b": 2}, | ||
| ], | ||
| expected=[{"_id": 3, "a": 2, "b": 1}, {"_id": 4, "a": 2, "b": 2}], | ||
| msg="$nor with duplicate expressions should behave same as single", | ||
| ), | ||
| QueryTestCase( | ||
| id="empty_object_in_array", | ||
| filter={"$nor": [{}]}, | ||
| doc=DOCS, | ||
| expected=[], | ||
| msg="$nor with empty object matches all docs so returns empty", | ||
| ), | ||
| ] | ||
|
|
||
| MULTIPLE_FIELDS_IN_EXPRESSION_TESTS: list[QueryTestCase] = [ | ||
| QueryTestCase( | ||
| id="implicit_and_in_expression", | ||
| filter={"$nor": [{"a": 1, "b": 2}]}, | ||
| doc=[ | ||
| {"_id": 1, "a": 1, "b": 1}, | ||
| {"_id": 2, "a": 1, "b": 2}, | ||
| {"_id": 3, "a": 2, "b": 1}, | ||
| {"_id": 4, "a": 2, "b": 2}, | ||
| ], | ||
| expected=[ | ||
| {"_id": 1, "a": 1, "b": 1}, | ||
| {"_id": 3, "a": 2, "b": 1}, | ||
| {"_id": 4, "a": 2, "b": 2}, | ||
| ], | ||
| msg="$nor with multiple fields in one expression is implicit AND within", | ||
| ), | ||
| QueryTestCase( | ||
| id="overlapping_field_conditions", | ||
| filter={"$nor": [{"a": {"$gt": 5}}, {"a": {"$lt": 2}}]}, | ||
| doc=[{"_id": 1, "a": 1}, {"_id": 2, "a": 3}, {"_id": 3, "a": 7}], | ||
| expected=[{"_id": 2, "a": 3}], | ||
| msg="$nor with overlapping conditions returns docs in the gap", | ||
| ), | ||
| QueryTestCase( | ||
| id="conflicting_operators_same_field", | ||
| filter={"$nor": [{"val": {"$gt": 10}}, {"val": {"$lt": 5}}, {"val": {"$eq": 7}}]}, | ||
| doc=[ | ||
| {"_id": 1, "val": 3}, | ||
| {"_id": 2, "val": 7}, | ||
| {"_id": 3, "val": 8}, | ||
| {"_id": 4, "val": 12}, | ||
| ], | ||
| expected=[{"_id": 3, "val": 8}], | ||
| msg="$nor with conflicting operators on same field returns docs failing all", | ||
| ), | ||
| ] | ||
|
|
||
| CLAUSE_BEHAVIOR_TESTS: list[QueryTestCase] = [ | ||
| QueryTestCase( | ||
| id="clause_ordering_invariance", | ||
| filter={"$nor": [{"b": 1}, {"a": 1}]}, | ||
| doc=[{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}, {"_id": 3, "a": 2, "b": 2}], | ||
| expected=[{"_id": 3, "a": 2, "b": 2}], | ||
| msg="$nor with clauses in different order should produce identical results", | ||
| ), | ||
| QueryTestCase( | ||
| id="nested_nor_double_negation", | ||
| filter={"$nor": [{"$nor": [{"a": 1}, {"b": 1}]}]}, | ||
| doc=[ | ||
| {"_id": 1, "a": 1, "b": 2}, | ||
| {"_id": 2, "a": 2, "b": 1}, | ||
| {"_id": 3, "a": 2, "b": 2}, | ||
| ], | ||
| expected=[{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}], | ||
| msg="$nor inside $nor (double negation) should be equivalent to $or", | ||
| ), | ||
|
PatersonProjects marked this conversation as resolved.
|
||
| QueryTestCase( | ||
| id="combined_with_top_level_filter", | ||
| filter={"x": 1, "$nor": [{"a": 1}, {"b": 2}]}, | ||
| doc=[ | ||
| {"_id": 1, "x": 1, "a": 1, "b": 1}, | ||
| {"_id": 2, "x": 1, "a": 2, "b": 1}, | ||
| {"_id": 3, "x": 2, "a": 2, "b": 1}, | ||
| ], | ||
| expected=[{"_id": 2, "x": 1, "a": 2, "b": 1}], | ||
| msg="$nor combined with top-level field filter applies implicit AND", | ||
| ), | ||
| ] | ||
|
|
||
| ALL_TESTS = VALID_ARRAY_TESTS + MULTIPLE_FIELDS_IN_EXPRESSION_TESTS + CLAUSE_BEHAVIOR_TESTS | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) | ||
| def test_nor_argument_handling(collection, test): | ||
| """Test $nor query operator argument validation.""" | ||
| collection.insert_many(test.doc) | ||
| result = execute_command(collection, {"find": collection.name, "filter": test.filter}) | ||
| assertResult( | ||
| result, | ||
| expected=test.expected, | ||
| error_code=test.error_code, | ||
| ignore_doc_order=True, | ||
| msg=test.msg, | ||
| ) | ||
170 changes: 170 additions & 0 deletions
170
...mentdb_tests/compatibility/tests/core/operator/query/logical/nor/test_nor_array_fields.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| """ | ||
| Tests for $nor query operator with array fields. | ||
|
|
||
| Covers element matching in arrays, nested array paths, empty arrays, | ||
| arrays of objects with dot notation, $elemMatch and $all on arrays, | ||
| null elements in arrays, mixed scalar and array values, and multi-element | ||
| clause matching. | ||
| """ | ||
|
|
||
| import pytest | ||
|
|
||
| from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import ( | ||
| QueryTestCase, | ||
| ) | ||
| from documentdb_tests.framework.assertions import assertResult | ||
| from documentdb_tests.framework.executor import execute_command | ||
| from documentdb_tests.framework.parametrize import pytest_params | ||
|
|
||
| ARRAY_FIELD_TESTS: list[QueryTestCase] = [ | ||
| QueryTestCase( | ||
| id="element_matches_in_array", | ||
| filter={"$nor": [{"val": 1}]}, | ||
| doc=[{"_id": 1, "val": [1, 2, 3]}, {"_id": 2, "val": [4, 5, 6]}], | ||
| expected=[{"_id": 2, "val": [4, 5, 6]}], | ||
| msg="$nor should exclude docs where array contains matching element", | ||
| ), | ||
| QueryTestCase( | ||
| id="no_element_matches_in_array", | ||
| filter={"$nor": [{"val": 1}]}, | ||
| doc=[{"_id": 1, "val": [4, 5, 6]}, {"_id": 2, "val": [7, 8]}], | ||
| expected=[{"_id": 1, "val": [4, 5, 6]}, {"_id": 2, "val": [7, 8]}], | ||
| msg="$nor should return docs where array does not contain matching element", | ||
| ), | ||
| QueryTestCase( | ||
| id="empty_array_with_size", | ||
| filter={"$nor": [{"val": {"$size": 0}}]}, | ||
| doc=[{"_id": 1, "val": []}, {"_id": 2, "val": [1]}], | ||
| expected=[{"_id": 2, "val": [1]}], | ||
| msg="$nor with $size:0 should exclude docs with empty array", | ||
| ), | ||
| QueryTestCase( | ||
| id="nested_arrays", | ||
| filter={"$nor": [{"val": [1, 2]}]}, | ||
| doc=[{"_id": 1, "val": [[1, 2], [3, 4]]}, {"_id": 2, "val": [[5, 6]]}], | ||
| expected=[{"_id": 2, "val": [[5, 6]]}], | ||
| msg="$nor should exclude docs where nested array contains matching sub-array", | ||
| ), | ||
| QueryTestCase( | ||
| id="array_of_objects_dot_notation", | ||
| filter={"$nor": [{"items.x": 1}]}, | ||
| doc=[ | ||
| {"_id": 1, "items": [{"x": 1}, {"x": 2}]}, | ||
| {"_id": 2, "items": [{"x": 3}, {"x": 4}]}, | ||
| ], | ||
| expected=[{"_id": 2, "items": [{"x": 3}, {"x": 4}]}], | ||
| msg="$nor with dot notation on array of objects should exclude matching docs", | ||
| ), | ||
| QueryTestCase( | ||
| id="nested_array_path", | ||
| filter={"$nor": [{"arr.field": "value"}]}, | ||
| doc=[ | ||
| {"_id": 1, "arr": [{"field": "value"}, {"field": "other"}]}, | ||
| {"_id": 2, "arr": [{"field": "other"}]}, | ||
| ], | ||
| expected=[{"_id": 2, "arr": [{"field": "other"}]}], | ||
| msg="$nor with nested array path should exclude docs with matching element", | ||
| ), | ||
| QueryTestCase( | ||
| id="elemMatch_on_array_of_objects", | ||
| filter={"$nor": [{"items": {"$elemMatch": {"qty": {"$gt": 5}, "price": {"$lt": 10}}}}]}, | ||
| doc=[ | ||
| {"_id": 1, "items": [{"qty": 10, "price": 5}]}, | ||
| {"_id": 2, "items": [{"qty": 3, "price": 5}]}, | ||
| ], | ||
| expected=[{"_id": 2, "items": [{"qty": 3, "price": 5}]}], | ||
| msg="$nor with $elemMatch on array of objects should exclude matching docs", | ||
| ), | ||
| QueryTestCase( | ||
| id="all_on_array", | ||
| filter={"$nor": [{"tags": {"$all": ["a", "b"]}}]}, | ||
| doc=[ | ||
| {"_id": 1, "tags": ["a", "b", "c"]}, | ||
| {"_id": 2, "tags": ["a", "c"]}, | ||
| ], | ||
| expected=[{"_id": 2, "tags": ["a", "c"]}], | ||
| msg="$nor with $all should exclude docs where array contains all specified elements", | ||
| ), | ||
| QueryTestCase( | ||
| id="array_containing_null", | ||
| filter={"$nor": [{"val": None}]}, | ||
| doc=[ | ||
| {"_id": 1, "val": [1, None, 3]}, | ||
| {"_id": 2, "val": [1, 2, 3]}, | ||
| {"_id": 3, "val": [None]}, | ||
| ], | ||
| expected=[{"_id": 2, "val": [1, 2, 3]}], | ||
| msg="$nor with null should exclude docs where array contains null element", | ||
| ), | ||
| QueryTestCase( | ||
| id="multiple_elements_match_different_clauses", | ||
| filter={"$nor": [{"val": 1}, {"val": 5}]}, | ||
| doc=[ | ||
| {"_id": 1, "val": [1, 5, 10]}, | ||
| {"_id": 2, "val": [2, 3]}, | ||
| {"_id": 3, "val": [5, 6]}, | ||
| {"_id": 4, "val": [10, 20]}, | ||
| ], | ||
| expected=[{"_id": 2, "val": [2, 3]}, {"_id": 4, "val": [10, 20]}], | ||
| msg="$nor should exclude doc when different array elements match different clauses", | ||
| ), | ||
| QueryTestCase( | ||
| id="mixed_scalar_and_array_same_field", | ||
| filter={"$nor": [{"val": 2}]}, | ||
| doc=[ | ||
| {"_id": 1, "val": 2}, | ||
| {"_id": 2, "val": [1, 2, 3]}, | ||
| {"_id": 3, "val": 5}, | ||
| {"_id": 4, "val": [4, 5, 6]}, | ||
| ], | ||
| expected=[{"_id": 3, "val": 5}, {"_id": 4, "val": [4, 5, 6]}], | ||
| msg="$nor should exclude docs whether matching value is a scalar or array element", | ||
| ), | ||
| QueryTestCase( | ||
| id="dot_notation_array_of_arrays", | ||
| filter={"$nor": [{"a.0.0": 1}]}, | ||
| doc=[ | ||
| {"_id": 1, "a": [[1, 2], [3, 4]]}, | ||
| {"_id": 2, "a": [[5, 6], [7, 8]]}, | ||
| ], | ||
| expected=[{"_id": 2, "a": [[5, 6], [7, 8]]}], | ||
| msg="$nor with dot notation into array of arrays should exclude matching docs", | ||
| ), | ||
| QueryTestCase( | ||
| id="elemMatch_with_nested_nor", | ||
| filter={ | ||
| "$nor": [ | ||
| {"items": {"$elemMatch": {"$nor": [{"qty": {"$gt": 5}}, {"price": {"$lt": 3}}]}}} | ||
| ] | ||
| }, | ||
| doc=[ | ||
| {"_id": 1, "items": [{"qty": 2, "price": 5}]}, | ||
| {"_id": 2, "items": [{"qty": 10, "price": 5}]}, | ||
| {"_id": 3, "items": [{"qty": 2, "price": 1}]}, | ||
| {"_id": 4, "items": [{"qty": 10, "price": 1}]}, | ||
| ], | ||
| expected=[ | ||
| {"_id": 2, "items": [{"qty": 10, "price": 5}]}, | ||
| {"_id": 3, "items": [{"qty": 2, "price": 1}]}, | ||
| {"_id": 4, "items": [{"qty": 10, "price": 1}]}, | ||
| ], | ||
| msg="$nor with $elemMatch containing nested $nor should exclude docs where an element " | ||
| "satisfies neither condition (qty<=5 AND price>=3)", | ||
| ), | ||
| ] | ||
|
|
||
| ALL_TESTS = ARRAY_FIELD_TESTS | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) | ||
| def test_nor_array_fields(collection, test): | ||
| """Test $nor query operator with array fields.""" | ||
| collection.insert_many(test.doc) | ||
| result = execute_command(collection, {"find": collection.name, "filter": test.filter}) | ||
| assertResult( | ||
| result, | ||
| expected=test.expected, | ||
| error_code=test.error_code, | ||
| ignore_doc_order=True, | ||
| msg=test.msg, | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.