Skip to content

feat(firestore): add BSON query ordering ranks - #18373

Draft
ohmayr wants to merge 1 commit into
bson-pr2-reads-decodingfrom
bson-pr3-query-ordering
Draft

ohmayr wants to merge 1 commit into
bson-pr2-reads-decodingfrom
bson-pr3-query-ordering

Conversation

@ohmayr

@ohmayr ohmayr commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

… 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:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

@ohmayr
ohmayr added this pull request to stack #18374 September 14, 2026 21:57

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +143 to +144
if isinstance(val, dict):
return {k: _extract_canonical_value(v) for k, v in val.items()}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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()}

Comment on lines +310 to +325
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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

@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from e46431f to b4c1777 Compare September 14, 2026 22:05
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from b4c1777 to f61ebf8 Compare September 14, 2026 22:59
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from f61ebf8 to 2412a63 Compare September 14, 2026 23:02
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from 2412a63 to 409fb55 Compare September 14, 2026 23:15
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch 2 times, most recently from 350ee6f to 4f103e5 Compare September 14, 2026 23:25
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from 4f103e5 to bc7df7d Compare September 14, 2026 23:29
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from bc7df7d to eb41a1e Compare September 14, 2026 23:51
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from eb41a1e to 7b9a0bb Compare September 14, 2026 23:56
@ohmayr
ohmayr force-pushed the bson-pr3-query-ordering branch from 7b9a0bb to 720c4c6 Compare September 15, 2026 00:02
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