Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the Firestore ordering semantics in order.py to support a 17-rank BSON and Firestore data type priority order matching backend database indexes, along with adding corresponding unit and system tests. The review feedback identifies two critical issues: first, dictionaries representing vectors are incorrectly extracted as plain dictionaries, causing them to be classified as objects rather than vectors; second, when number conversion fails and returns None in compare_numbers, returning 0 violates comparison transitivity, which should be resolved by treating None as NaN to prevent sorting instability.
| if isinstance(val, dict): | ||
| return {k: _extract_canonical_value(v) for k, v in val.items()} |
There was a problem hiding this comment.
When _extract_canonical_value is called on a dictionary representing a vector (e.g., {"__type__": "__vector__", "value": [...]}), it currently falls back to returning a plain dictionary. This causes TypeOrder.from_value to classify it as TypeOrder.OBJECT instead of TypeOrder.VECTOR, leading to incorrect comparison behavior. We should explicitly check for the vector type signature and convert it to a Vector object.
| if isinstance(val, dict): | |
| return {k: _extract_canonical_value(v) for k, v in val.items()} | |
| if isinstance(val, dict): | |
| if val.get("__type__") == "__vector__": | |
| return Vector(val.get("value", [])) | |
| return {k: _extract_canonical_value(v) for k, v in val.items()} |
| left_is_nan = d_left == "NaN" or (isinstance(d_left, decimal.Decimal) and d_left.is_nan()) | ||
| right_is_nan = d_right == "NaN" or (isinstance(d_right, decimal.Decimal) and d_right.is_nan()) | ||
|
|
||
| if left_is_nan and right_is_nan: | ||
| return 0 | ||
| if left_is_nan: | ||
| return -1 | ||
| if right_is_nan: | ||
| return 1 | ||
|
|
||
| seconds = Order._compare_to(left.seconds or 0, right.seconds or 0) | ||
| if seconds != 0: | ||
| return seconds | ||
| if d_left is None or d_right is None: | ||
| return 0 | ||
|
|
||
| return Order._compare_to(left.nanos or 0, right.nanos or 0) | ||
| if d_left == d_right: | ||
| return 0 | ||
| return 1 if d_left > d_right else -1 |
There was a problem hiding this comment.
If a number cannot be converted to a decimal (e.g., an invalid BSONDecimal128 or unsupported type), _to_decimal_or_nan returns None. Currently, if either d_left or d_right is None, compare_numbers returns 0 (equal). This violates the transitivity of comparison (e.g., an invalid decimal would be considered equal to any valid number), which can cause sorting algorithms like Timsort to crash or produce unstable results. Treating None as a NaN (which is consistently sorted as smaller than any valid number) preserves transitivity and ensures robust comparison.
| left_is_nan = d_left == "NaN" or (isinstance(d_left, decimal.Decimal) and d_left.is_nan()) | |
| right_is_nan = d_right == "NaN" or (isinstance(d_right, decimal.Decimal) and d_right.is_nan()) | |
| if left_is_nan and right_is_nan: | |
| return 0 | |
| if left_is_nan: | |
| return -1 | |
| if right_is_nan: | |
| return 1 | |
| seconds = Order._compare_to(left.seconds or 0, right.seconds or 0) | |
| if seconds != 0: | |
| return seconds | |
| if d_left is None or d_right is None: | |
| return 0 | |
| return Order._compare_to(left.nanos or 0, right.nanos or 0) | |
| if d_left == d_right: | |
| return 0 | |
| return 1 if d_left > d_right else -1 | |
| left_is_nan = d_left is None or d_left == "NaN" or (isinstance(d_left, decimal.Decimal) and d_left.is_nan()) | |
| right_is_nan = d_right is None or d_right == "NaN" or (isinstance(d_right, decimal.Decimal) and d_right.is_nan()) | |
| if left_is_nan and right_is_nan: | |
| return 0 | |
| if left_is_nan: | |
| return -1 | |
| if right_is_nan: | |
| return 1 | |
| if d_left == d_right: | |
| return 0 | |
| return 1 if d_left > d_right else -1 |
e46431f to
b4c1777
Compare
b4c1777 to
f61ebf8
Compare
f61ebf8 to
2412a63
Compare
2412a63 to
409fb55
Compare
350ee6f to
4f103e5
Compare
4f103e5 to
bc7df7d
Compare
bc7df7d to
eb41a1e
Compare
eb41a1e to
7b9a0bb
Compare
7b9a0bb to
720c4c6
Compare
… tests (PR 3)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
Fixes #<issue_number_goes_here> 🦕