BLQL range operators match fields of another BSON type: null under $lt, strings under $gt
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 42/100
Research direction
Read src/BLite.Core/Query/Blql/BlqlFilter.cs and BsonValueComparer.cs to understand how range comparisons use the total BSON type order, then compare that behavior with the index scan in src/BLite.Core/DynamicCollection.cs. Use the reproductions and affected oracle cases in tests/BLite.Tests/Oracles/DynamicDifferentialTests.cs; done when range queries return the same results with and without an index while equality and sorting retain their documented behavior.
Written by the indexing model from the issue text.
Description
Without an index, BLQL's $lt, $lte, $gt and $gte match values of other BSON types: an explicit null is below every number, so {m: {$lt: 10}} matches documents whose m is null, and a string is above every number, so {m: {$gt: 0}} matches m: "x". With an index on the field the same query returns only the numbers. An index changes the answer.
Found by the DynamicCollection oracle tests (tests/BLite.Tests/Oracles/DynamicDifferentialTests.cs, PR to follow, stacked on #263), which carry the affected rows (4: plain/blql, both oracles, maybe <= 0 and maybe < 9.5 (double bound); e.g. expected 72 documents, got 178, every document whose maybecount is null) as a known candidate until this is fixed. The indexed/blql rows of the same queries agree with LINQ.
Repro
// m: null ("null"), -1 ("minus1"), 5 ("five"), "x" ("text")
col.Query(BlqlFilter.Lte("m", 0)).ToList(); // expected [minus1], actual [minus1, null]
col.Query("{\"m\": {\"$lt\": 10}}").ToList(); // expected [minus1, five], actual [minus1, null, five]
col.Query(BlqlFilter.Gt("m", 0)).ToList(); // expected [five], actual [five, text]
// after await col.CreateIndexAsync("m"): [minus1], [minus1, five], [five]
Expected: the same answer with and without an index. MQL brackets range operators by type ($lt: 10 only matches numbers), and LINQ over int? agrees (null <= 0 is false). A missing field already behaves that way in BLQL (CompareFilter.Matches returns false for every operator but $ne, BlqlFilter.cs:315-319); an explicit null does not.
Actual: see above.
Mechanism (verified on 0450b97)
CompareFilter.CompareValues(src/BLite.Core/Query/Blql/BlqlFilter.cs:330) applies the operator toBsonValueComparer.Compare, which is a total order across types: null first (BsonValueComparer.cs:21-24), then byTypeOrder(:90), as its summary describes ("Null < Boolean < (Int32 | Int64 | Double | Decimal) < String < ObjectId < DateTime",BsonValueComparer.cs:10).- With an index,
GetScanSourcescans only the bound's key range:QueryIndexAsync(null, upper)starts after the null bucket (src/BLite.Core/DynamicCollection.cs:1065), and string keys sort below the integer bound. The residual filter can only drop documents, so the indexed collection answers like LINQ.
Affected versions: the comparer since BLQL (a86d458, v1.9.0). Reproduced on v5.2.3 / 0450b97.
Fix considerations
$gt,$gte,$lt,$lteare false unless both values are of one comparable kind (the numeric family, the date family, or the same type);$eq,$neand sorting keep the total order, which sorting needs.- The total order is documented in the comparer's summary, so whether range operators should bracket by type (MQL) or keep the total order (and make the index scan include the other types) is your call; the two ways should agree either way.
- This is a separate fix from #268, which is in the numeric branch of the same comparer.
Related: #248 (open, the typed != and stored nulls), and the other BLQL rows of the same tests: #264, #265, #266, #268.
- Dominant language
- C#
- Stars
- 66
- Forks
- 6
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 43
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from EntglDb/BLite
-
area/indexing bug package/core triage
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
area/linq bug package/core triage
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
area/indexing bug package/core triage
Difficulty 4/5 3-5 days Newbie friendliness 57/100
Maintainers usually reply within 1 day
-
area/indexing bug package/core triage
Difficulty 5/5 Over a week Newbie friendliness 35/100
Maintainers usually reply within 1 day
-
area/linq bug package/core triage
Difficulty 3/5 1-2 days Newbie friendliness 55/100
Maintainers usually reply within 1 day
Similar issues
-
area: design documentation
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
eyalgolan/statecraft-israel#883 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 3 days
-
enhancement needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
elastic/docs-builder#4495 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
StreamProtoReader pool state corruption after interrupted deserializationPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
protobuf-net/protobuf-net#1375 · 2 comments ·
Maintainers usually reply within 2 days