BsonDocument.CompareTo is not antisymmetric: documents with different keys compare greater in both directions
BsonDocument.CompareTo walks only the left-hand document's keys and looks each one up in the right-hand document, treating a missing key as Null. When the two documents have different key sets, each side finds a non-null value on its own side and Null on the other, so both a.CompareTo(b) and b.CompareTo(a) return 1. IComparable requires sign(a.CompareTo(b)) == -sign(b.CompareTo(a)).
Verified on dev (828d760f). The comparison code is unchanged by #2755; the randomised comparison test added there had to exclude documents because of this.
Reproduction
var a = new BsonDocument { ["x"] = 1 };
var b = new BsonDocument { ["y"] = 1 };
Console.WriteLine($"{a.CompareTo(b)} {b.CompareTo(a)}"); // 1 1
var c = new BsonDocument { ["x"] = 1, ["y"] = 0 };
var d = new BsonDocument { ["y"] = 1, ["x"] = 0 };
Console.WriteLine($"{c.CompareTo(d)} {d.CompareTo(c)}"); // 1 1 (same keys, different order)
var list = new List<BsonValue> { b, a, new BsonDocument { ["z"] = 1 } };
list.Sort(); // {"z":1} {"x":1} {"y":1}The second case shows it is not only about disjoint keys: the result depends on which document's key order is walked first.
Impact
Equalsis unaffected: a0result requires every left key to match and the counts to be equal, which is symmetric. Hash codes therefore stay consistent.- Ordering is affected: sorting a list of documents,
ORDER BYon a document-valued field, and B-tree index placement for document keys depend on comparison order and on which operand is on the left. Results can differ between runs or between insert orders, andList<T>.Sortmay produce an order that is not a total order.
Cause
LiteDB/Document/BsonDocument.cs: the loop iterates this.Keys and compares this[key] with otherDoc[key], then compares key counts. Keys present only in other are never visited.
Possible fix
Compare a canonical view of both documents, for example the ordinal-ignore-case sorted key sequence: walk the union of keys in sorted order, compare key names first, then values, then fall back to count. That keeps Equals semantics (same keys and equal values) while making the ordering a total order. Any change to document ordering must consider existing indexes on document-valued keys, since their on-disk order was produced by the current comparison.
Source: litedb-org/LiteDB