fix(mongoc): a negated q on a sub-attribute path matched Entities without it - #56
Merged
Merged
Conversation
…hout it
q=a.b.c!=1 returned Entities that have no a.b.c at all. Under mongoc
only - it pushes `q` into the database, while corDB walks the Entity in
the broker (ldEntityMatch.c) and answered correctly all along.
The backend contradicted ITSELF, which is what makes this unambiguous
rather than a matter of spec reading. Over a fixture that truncates the
path at every depth:
E1 a.b.c = 1 E2 a.b.c = 2 E3 a.b (no c)
E4 a (no b) E5 no a at all
q=!a.b.c -> E3, E4, E5 "these do not have the path"
q=a.b.c!=1 -> E2, E3, E4 "...and E3/E4's a.b.c is not 1"
§ 4.9 is explicit that a term over an attrPath the Entity does not have
evaluates to FALSE: an Entity with no a.b.c does not "have a value
different from 1".
The cause is in the multi-instance $expr builder, and the comment above
it already states the right rule - "the Attribute must still be present,
since an Entity without it does not have a value different from X". It
enforces that with {"$type":"$<attr>"} == "object", which checks the
ATTRIBUTE and says nothing about the rest of the path. For a.b.c, E3
holds `a`, passes the guard, and then $not over a missing field is true.
E5 was excluded only because it lacks `a` - the guard working at the one
depth it covers.
So the second guard is added: the full per-instance expression - sub-path,
value and value-path included - must resolve in at least one instance
before the negation applies. At depth 0 it costs nothing, a stored
Attribute always has its value, which is why the existing tests never saw
this.
The test needs no machinery to be an oracle. It declares no REQUIRE_DB,
so the harness already runs it against both backends, and they evaluate
`q` in completely different places - any future divergence fails one of
the two suite runs on its own. Deep attrPaths were tested before, but
only with == and >; the negated operators are exactly where a pushdown
and an in-broker walk part company, because Mongo's $ne / $nin / $not
also match a missing field.
635/635 mongoc, 585/585 corDB.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
q=a.b.c!=1returned Entities that have noa.b.cat all — under mongoc only, which pushesqinto the database, while corDB walks the Entity in the broker (ldEntityMatch.c) and answered correctly all along.The backend contradicted itself
Over a fixture that truncates the path at every depth —
E1 a.b.c=1,E2 a.b.c=2,E3 a.b(noc),E4 a(nob),E5noa:q=a.b.c==1q=a.b.c!=1q=a.b.c!=99q=a.b.cq=!a.b.cq=!a.b.ccorrectly reports E3 and E4 as not having the path, whileq=a.b.c!=1simultaneously reports theira.b.cas unequal to 1. That is a contradiction inside one backend, not a difference of interpretation.§ 4.9 settles the reading anyway: a term over an attrPath the Entity does not have evaluates to false. An Entity with no
a.b.cdoes not "have a value different from 1".Cause
In the multi-instance
$exprbuilder — and the comment above it already states the correct rule:It enforces that with
{"$type":"$<attrPath>"} == "object", which checks the attribute and says nothing about the rest of the path. Fora.b.c, E3 holdsa, passes the guard, and then$notover a missing field is true. E5 was excluded only because it lacksa— the guard working at the one depth it covers.Fix
Add the guard the comment intends: the full per-instance expression — sub-path, value and value-path included — must resolve in at least one instance before the negation applies. At depth 0 it costs nothing, since a stored Attribute always has its value, which is why the existing tests never saw this.
The test is an oracle, and it was free
query_q_deep_path_negated.testdeclares noREQUIRE_DB, so the harness already runs it against both backends — and they evaluateqin completely different places. Any future divergence between the pushdown and the in-broker matcher fails one of the two suite runs on its own, with no special machinery.Deep attrPaths were tested before this, but only with
==and>. The negated operators are exactly where a pushdown and an in-broker walk part company, because Mongo's$ne/$nin/$notalso match a missing field.Found by following up the
attrInstanceOfentry in the never-entered bucket — zero under mongoc, covered under corDB — which is what flagged the two implementations as never having been compared.Verification: 635/635 mongoc, 585/585 corDB. Both backends now produce byte-identical output against one set of expects.