Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deterministic order on query and filter on gsi #8376

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 43 additions & 28 deletions moto/dynamodb/models/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,18 +746,9 @@ def query(

# SORT
if index_name:
if len(range_attrs) == 2:
# Convert to float if necessary to ensure proper ordering
def conv(x: DynamoType) -> Any:
return float(x.value) if x.type == "N" else x.value

possible_results.sort(
key=lambda item: ( # type: ignore
conv(item.attrs[range_attrs[0]]) # type: ignore
if item.attrs.get(range_attrs[0]) # type: ignore
else None
)
)
possible_results = self.sorted_items(
hash_attrs, range_attrs, possible_results
)
else:
possible_results.sort(key=lambda item: item.range_key) # type: ignore

Expand Down Expand Up @@ -883,10 +874,7 @@ def has_idx_items(self, index_name: str) -> List[Item]:
else:
if idx_col_set.issubset(set(hash_set.attrs)): # type: ignore
items.append(hash_set) # type: ignore
return sorted(
items,
key=lambda x: (x.hash_key, x.range_key) if x.range_key else x.hash_key,
)
return items

def scan(
self,
Expand Down Expand Up @@ -934,6 +922,7 @@ def scan(
range_attrs = [self.range_key_attr]

items = self.has_idx_items(index_name)
items = self.sorted_items(hash_attrs, range_attrs, items)
else:
hash_attrs = [self.hash_key_attr]
range_attrs = [self.range_key_attr]
Expand Down Expand Up @@ -1030,6 +1019,40 @@ def _item_comes_before_dct(
If that is the case, we compare by the RK of the main table instead
Related: https://github.com/getmoto/moto/issues/7761
"""
attrs_to_sort_by = self._generate_attr_to_sort_by(
hash_key_attrs, range_key_attrs
)
for attr in attrs_to_sort_by:
if (
attr is not None
and attr in item.attrs
and item.attrs[attr] != DynamoType(dct.get(attr)) # type: ignore
):
return (
(item.attrs[attr] < DynamoType(dct.get(attr))) # type: ignore
== scan_index_forward
)
# Keys were equal, items are identical
return True

def sorted_items(
self,
hash_key_attrs: List[str],
range_key_attrs: List[Optional[str]],
items: List[Item],
) -> List[Item]:
attrs_to_sort_by = self._generate_attr_to_sort_by(
hash_key_attrs, range_key_attrs
)
items.sort(
key=lambda x: tuple([x.attrs[key] for key in attrs_to_sort_by]),
)
return items

def _generate_attr_to_sort_by(
self, hash_key_attrs: List[str], range_key_attrs: List[Optional[str]]
) -> List[str]:
attrs_to_sort_by = []
gsi_hash_key = hash_key_attrs[0] if len(hash_key_attrs) == 2 else None
table_hash_key = str(
hash_key_attrs[0] if gsi_hash_key is None else hash_key_attrs[1]
Expand All @@ -1045,18 +1068,10 @@ def _item_comes_before_dct(
table_hash_key,
table_range_key,
]
for attr in attrs_to_sort_by:
if (
attr is not None
and attr in item.attrs
and item.attrs[attr] != DynamoType(dct.get(attr)) # type: ignore
):
return (
(item.attrs[attr] < DynamoType(dct.get(attr))) # type: ignore
== scan_index_forward
)
# Keys were equal, items are identical
return True
attrs_to_sort_by_final: List[str] = [
attr for attr in attrs_to_sort_by if attr is not None and attr != "None"
]
return attrs_to_sort_by_final

def _get_last_evaluated_key(
self, last_result: Item, index_name: Optional[str]
Expand Down
Loading