Skip to content

Test cases for MongoDB semantics not reproduced by the heuristics calculator - #1743

Open
LautaroPetaccio wants to merge 1 commit into
masterfrom
tests/mongo-semantics-defects
Open

Test cases for MongoDB semantics not reproduced by the heuristics calculator#1743
LautaroPetaccio wants to merge 1 commit into
masterfrom
tests/mongo-semantics-defects

Conversation

@LautaroPetaccio

@LautaroPetaccio LautaroPetaccio commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Test cases only, no change to the heuristics, as agreed: twenty-four defects in MongoHeuristicsCalculator, each as its own @Disabled test so they can be enabled one at a time and in any order.

Every expected value was obtained by running the same query and the same document against a real MongoDB 7.0.40 server, so the assertions state what the database does rather than an interpretation of the documentation. Enabling a test is a one-line change: delete its @Disabled.

The suite is green as it stands: 299 passing, 24 skipped. I also checked the other direction, that every one of the twenty-four actually fails when its annotation is removed.

Ten make the calculator throw

MongoHandler.computeFindDistance does not catch anything, so the exception escapes the heuristics computation for the whole action and the ExtraHeuristicsDto is lost, including the SQL heuristics computed just before it.

test trigger
testTypeWithAStringAlias {"a":{"$type":"string"}} is not parsed → NPE on the null operation
testTypeWithAListOfAliases {"$type":["string","int"]} is not parsed → NPE
testBitsWithAnIntegerBitmask {"a":{"$bitsAllSet":1}} is not parsed (only Long is accepted) → NPE
testBitsWithBitPositions {"$bitsAllSet":[0,2]} is not parsed → NPE
testNotWithMoreThanOneInnerOperator {"$not":{"$gt":1,"$lt":9}} and a nested $not are not parsed → NPE
testOperatorsThatAreNotModelledDoNotThrow $expr, $jsonSchema, $where, $text, $geoWithin, $geoIntersects, $comment are not parsed → NPE
testInAndNotInWithAnEmptyListOfValues {"$in":[]} aggregates over no element → IllegalArgumentException
testInAndNotInAgainstAnEmptyArrayField {"tags":[]} with $in → same
testEqualsBetweenEmptyArrays two empty arrays are of equal size, so their elements are aggregated → same
testOrderingComparisonWithNaN NaN orders against nothing, so the Truthness built for it has neither value equal to 1 and its own constructor rejects it
testFieldsHoldingASubDocument a sub-document, or binary data, reaches the "Unsupported type" branch → throws

The parse gaps are worth a look for how easily they hide: the E2E endpoints write $type with the numeric code 2 and their bitmasks as 1L/5L, which are exactly the two forms that do parse. QueryParserTest.testParseInvalidBitwiseValues also currently asserts that an Integer bitmask is invalid, which is the opposite of what MongoDB accepts, so that assertion will need removing along with the @Disabled.

Fourteen are answered, but not as MongoDB answers them

The ones reporting a match where MongoDB has none are the harmful direction: a condition no data can satisfy is recorded as covered, so the search stops working towards it.

test MongoDB today
testAllQuantifiesOverTheExpectedValues {$all:["a","b","c"]} vs ["a"] → no match match
testNotInAgainstAnArrayField {$nin:["a"]} vs ["a","b"] → no match match
testNotOnAMissingFieldWithAnInnerOperatorThatMatchesIt {$not:{$ne:5}} vs {} → no match match
testElemMatchOnAnArrayOfScalars {$elemMatch:{x:1}} vs [1,2,3] → no match match
testBitsDoesNotMatchANonIntegralNumber 3.5 is not truncated to 3 → no match match
testNullInsideAnArrayField {$ne:null} vs [1,null,3] → no match match
testOrderingComparisonBetweenAnObjectIdAndANumber {$gt:5.0} vs an ObjectId → no match match
testFieldsAreMatchedByAnElementOfTheArrayTheyHold {$gt:2} vs [1,2,3] → match no match
testRegexMatchingAnElementOfAnArrayField {$regex:"^ab"} vs ["abc","x"] → match no match
testNotEqualsBetweenIncomparableTypes {$ne:"abc"} vs 42 → match no match
testAllOnAScalarField {$all:["a"]} vs {f:"a"} → match no match
testInMatchingAnArrayFieldAsAWhole {$in:[["a","b"]]} vs ["a","b"] → match no match
testDottedFieldPath {"a.x":1} vs {a:{x:1}} → match no match

Several of these are one underlying rule, in case that helps in grouping the work: MongoDB matches a field when its value satisfies the condition or when it holds an array of which any element does, and that applies to every condition on a field, not only to equality. $all is the same rule quantified the other way round, over the expected values.

$type has a second problem behind the parse gap: the check compares actualValue.getClass().getTypeName() against a class name from BsonTypeClassMap, so $type:"array" compares java.util.ArrayList against java.util.List and cannot match. Fixing only the parsing would turn that crash into a silent wrong answer.

Four tests of behaviour that is already correct

Added as a guard while the heuristic is being changed: ordering operators correctly not matching across incomparable BSON types, $eq against a whole array including the order of its elements, $type with a numeric code and $bits with a Long bitmask, and testNotEqualsAgainstAnArrayField.

That last one is worth singling out. It answers correctly today, but only because two of the reported defects cancel out: $ne does not look at the elements of the array, which alone would answer true, and the comparison between incomparable BSON types answers false regardless of the operator, which brings it back to false. Fixing either one on its own turns it into a false positive.

What held up

The scores themselves hold up: across 928 combinations of the five most used operators and fourteen value types, every Truthness stays within range, none is NaN, and none reports a full score for a document MongoDB does not return, so there is no silent full coverage beyond the wrong answers listed above. The gradient over numbers is monotone for $eq, $gt and $lt, which is the property the search depends on:

$eq target=100   a=0:0.1089  a=50:0.1176  a=90:0.1818  a=99:0.5500

$and, $or and $nor with an empty array do make the calculator throw, but MongoDB rejects those queries itself, so they cannot reach it from a system under test and are not reported.

A note on the operators that are not modelled

testOperatorsThatAreNotModelledDoNotThrow is the one test that does not ask for an operator to be supported. Whether $expr or $where should ever be modelled, and what the score should be when they are not, is a decision for the heuristic, so the test asserts only that nothing is thrown. $comment is the one worth looking at first: it attaches to an otherwise ordinary query, so {"a": 1, "$comment": "..."} is by itself enough to lose the heuristics of the action.

Replaces

#1739 and #1740, both closed. No heuristic changes here.

… calculator

Twenty-four defects in MongoHeuristicsCalculator, each as its own @disabled test, so
they can be enabled one at a time and in any order as the behaviour is implemented.
Every expected value was obtained by running the same query and the same document
against a real MongoDB 7.0.40 server, so the assertions state what the database does
rather than an interpretation of the documentation.

Ten of them make the calculator throw. As MongoHandler does not catch anything, the
exception escapes the heuristics computation for the action, so the ExtraHeuristicsDto
is lost, including the SQL heuristics computed before it:

  $type with a string alias or with a list of aliases, a bitmask given as an Integer or
  as a list of bit positions, and $not holding more than one operator, are not parsed,
  and the calculator throws a NullPointerException on the resulting null operation;
  an operator that is not modelled at all is not parsed either, which covers $expr,
  $jsonSchema, $where, $text, $geoWithin, $geoIntersects and $comment. The last one
  attaches to an otherwise ordinary query, so {"a": 1, "$comment": "..."} is enough to
  lose the heuristics of the action;
  an empty list of values, an empty array in the document, and comparing two empty
  arrays aggregate over no element and throw IllegalArgumentException;
  an ordering comparison involving NaN builds a Truthness with neither of its values
  equal to 1, which its own constructor rejects. Any double field can hold NaN;
  a value the calculator cannot compare, such as a sub-document or binary data, reaches
  the "Unsupported type" branch and throws.

The test for the operators that are not modelled asserts only that nothing is thrown,
not any particular score: whether such an operator should be supported, and what it
should answer, is a decision for the heuristic. What it should not do is cost the
action its heuristics.

The other fourteen are answered, but not the way MongoDB answers them. Those that
report a match where MongoDB has none are the harmful direction, as a condition no data
can satisfy is recorded as covered and the search stops working towards it. Several
share one rule: MongoDB matches a field when its value satisfies the condition, or when
it holds an array of which any element does, and that applies to every condition on a
field rather than only to equality. $all is the same rule quantified the other way
round, over the expected values.

Four tests of behaviour that is already correct are added as well, as a guard while the
heuristic is changed. One of them, testNotEqualsAgainstAnArrayField, answers correctly
only because two of the reported defects cancel each other out; fixing either one alone
turns it into a false positive, which is why it is worth keeping visible.
@LautaroPetaccio
LautaroPetaccio force-pushed the tests/mongo-semantics-defects branch from aded92d to c293276 Compare September 7, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant