Skip to content

Commit 378dc67

Browse files
andrewmathew1Andrew MathewCopilot
authored
Added Tests and Samples for Paginated Queries (#43472)
* added tests and samples for paginated queries * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * added single partition pagination sample --------- Co-authored-by: Andrew Mathew <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 0caa472 commit 378dc67

File tree

6 files changed

+532
-0
lines changed

6 files changed

+532
-0
lines changed

sdk/cosmos/azure-cosmos/samples/document_management.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,74 @@ def query_items_with_continuation_token(container):
109109
print('The single items in the second page are {}.'.format(second_page_items_with_continuation[0].get("id")))
110110

111111

112+
def query_items_single_partition_with_pagination(container):
113+
print('\n1.5a Querying with Pagination - Demonstrating max_item_count and Counting Results\n')
114+
115+
# max_item_count controls how many items are returned per page, not the total number of results
116+
# This is useful for controlling memory usage and processing items in batches
117+
max_items_per_page = 5
118+
119+
# Specify a partition key to query within a single partition
120+
partition_key_value = "SalesOrder1" # Or any partition key value you want
121+
122+
query_iterable = container.query_items(
123+
query="SELECT * FROM c",
124+
partition_key=partition_key_value, # Query single partition
125+
max_item_count=max_items_per_page
126+
)
127+
128+
# Iterate through pages and count both pages and total items
129+
total_item_count = 0
130+
page_count = 0
131+
132+
item_pages = query_iterable.by_page()
133+
for page in item_pages:
134+
page_count += 1
135+
items_in_page = list(page)
136+
items_in_current_page = len(items_in_page)
137+
total_item_count += items_in_current_page
138+
139+
print(f'Page {page_count}: Retrieved {items_in_current_page} items (max per page: {max_items_per_page})')
140+
141+
# Process items in this page
142+
for item in items_in_page:
143+
# Do something with each item
144+
pass
145+
146+
print(f'\nTotal pages processed: {page_count}')
147+
print(f'Total items retrieved: {total_item_count}')
148+
print(f'Note: max_item_count limits items PER PAGE, not total results\n')
149+
150+
151+
def query_items_cross_partition_with_pagination(container):
152+
print('\n1.5b Cross-Partition Query with Pagination\n')
153+
154+
# When querying across partitions, max_item_count still controls page size
155+
# but the results are gathered from multiple partitions
156+
max_items_per_page = 3
157+
158+
query_iterable = container.query_items(
159+
query="SELECT * FROM c ORDER BY c._ts", # Order by timestamp across all partitions
160+
enable_cross_partition_query=True,
161+
max_item_count=max_items_per_page
162+
)
163+
164+
total_item_count = 0
165+
page_count = 0
166+
167+
item_pages = query_iterable.by_page()
168+
for page in item_pages:
169+
page_count += 1
170+
items_in_page = list(page)
171+
total_item_count += len(items_in_page)
172+
173+
print(f'Page {page_count}: {len(items_in_page)} items from across partitions')
174+
175+
print(f'\nCross-partition query completed:')
176+
print(f' - Pages: {page_count}')
177+
print(f' - Total items: {total_item_count}')
178+
179+
112180
def replace_item(container, doc_id):
113181
print('\n1.6 Replace an Item\n')
114182

@@ -525,6 +593,8 @@ def run_sample():
525593
read_items(container)
526594
query_items(container, 'SalesOrder1')
527595
query_items_with_continuation_token(container)
596+
query_items_single_partition_with_pagination(container)
597+
query_items_cross_partition_with_pagination(container)
528598
replace_item(container, 'SalesOrder1')
529599
replace_item_using_etags(container, 'SalesOrder1')
530600
upsert_item(container, 'SalesOrder1')

sdk/cosmos/azure-cosmos/samples/document_management_async.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,73 @@ async def query_items_with_continuation_token(container):
126126
print('The single items in the second page are {}'.format(second_page_items_with_continuation[0].get("id")))
127127

128128

129+
async def query_items_single_partition_with_pagination(container):
130+
print('\n1.5a Querying with Pagination - Demonstrating max_item_count and Counting Results\n')
131+
132+
# max_item_count controls how many items are returned per page, not the total number of results
133+
# This is useful for controlling memory usage and processing items in batches
134+
max_items_per_page = 5
135+
136+
# Specify a partition key to query within a single partition
137+
partition_key_value = "SalesOrder1" # Or any partition key value you want
138+
139+
query_iterable = container.query_items(
140+
query="SELECT * FROM c",
141+
partition_key=partition_key_value, # Query single partition
142+
max_item_count=max_items_per_page
143+
)
144+
145+
# Iterate through pages and count both pages and total items
146+
total_item_count = 0
147+
page_count = 0
148+
149+
item_pages = query_iterable.by_page()
150+
async for page in item_pages:
151+
page_count += 1
152+
items_in_page = [item async for item in page]
153+
items_in_current_page = len(items_in_page)
154+
total_item_count += items_in_current_page
155+
156+
print(f'Page {page_count}: Retrieved {items_in_current_page} items (max per page: {max_items_per_page})')
157+
158+
# Process items in this page
159+
for item in items_in_page:
160+
# Do something with each item
161+
pass
162+
163+
print(f'\nTotal pages processed: {page_count}')
164+
print(f'Total items retrieved: {total_item_count}')
165+
print(f'Note: max_item_count limits items PER PAGE, not total results\n')
166+
167+
168+
async def query_items_cross_partition_with_pagination(container):
169+
print('\n1.5b Cross-Partition Query with Pagination\n')
170+
171+
# When querying across partitions, max_item_count still controls page size
172+
# but the results are gathered from multiple partitions
173+
max_items_per_page = 3
174+
175+
query_iterable = container.query_items(
176+
query="SELECT * FROM c ORDER BY c._ts", # Order by timestamp across all partitions
177+
max_item_count=max_items_per_page
178+
)
179+
180+
total_item_count = 0
181+
page_count = 0
182+
183+
item_pages = query_iterable.by_page()
184+
async for page in item_pages:
185+
page_count += 1
186+
items_in_page = [item async for item in page]
187+
total_item_count += len(items_in_page)
188+
189+
print(f'Page {page_count}: {len(items_in_page)} items from across partitions')
190+
191+
print(f'\nCross-partition query completed:')
192+
print(f' - Pages: {page_count}')
193+
print(f' - Total items: {total_item_count}')
194+
195+
129196
async def replace_item(container, doc_id):
130197
print('\n1.6 Replace an Item\n')
131198

@@ -546,6 +613,8 @@ async def run_sample():
546613
await read_items(container)
547614
await query_items(container, 'SalesOrder1')
548615
await query_items_with_continuation_token(container)
616+
await query_items_single_partition_with_pagination(container)
617+
await query_items_cross_partition_with_pagination(container)
549618
await replace_item(container, 'SalesOrder1')
550619
await replace_item_using_etags(container, 'SalesOrder1')
551620
await upsert_item(container, 'SalesOrder1')

sdk/cosmos/azure-cosmos/tests/test_query.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,91 @@ def test_query_request_params_none_retry_policy(self):
598598
retry_utility.ExecuteFunction = self.OriginalExecuteFunction
599599
self.created_db.delete_container(created_collection.id)
600600

601+
def test_query_pagination_with_max_item_count(self):
602+
"""Test pagination showing per-page limits and total results counting."""
603+
created_collection = self.created_db.create_container(
604+
"pagination_test_" + str(uuid.uuid4()),
605+
PartitionKey(path="/pk"))
606+
607+
# Create 20 items in a single partition
608+
total_items = 20
609+
partition_key_value = "test_pk"
610+
for i in range(total_items):
611+
document_definition = {
612+
'pk': partition_key_value,
613+
'id': f'item_{i}',
614+
'value': i
615+
}
616+
created_collection.create_item(body=document_definition)
617+
618+
# Test pagination with max_item_count limiting items per page
619+
max_items_per_page = 7
620+
query = "SELECT * FROM c WHERE c.pk = @pk ORDER BY c.value"
621+
query_iterable = created_collection.query_items(
622+
query=query,
623+
parameters=[{"name": "@pk", "value": partition_key_value}],
624+
partition_key=partition_key_value,
625+
max_item_count=max_items_per_page
626+
)
627+
628+
# Iterate through pages and verify per-page counts
629+
all_fetched_results = []
630+
page_count = 0
631+
item_pages = query_iterable.by_page()
632+
633+
for page in item_pages:
634+
page_count += 1
635+
items_in_page = list(page)
636+
all_fetched_results.extend(items_in_page)
637+
638+
# Each page should have at most max_item_count items
639+
# (last page may have fewer)
640+
self.assertLessEqual(len(items_in_page), max_items_per_page)
641+
642+
# Verify total results match expected count
643+
self.assertEqual(len(all_fetched_results), total_items)
644+
645+
# Verify we got the expected number of pages
646+
# 20 items with max 7 per page = 3 pages (7, 7, 6)
647+
self.assertEqual(page_count, 3)
648+
649+
# Verify ordering is maintained
650+
for i, item in enumerate(all_fetched_results):
651+
self.assertEqual(item['value'], i)
652+
653+
self.created_db.delete_container(created_collection.id)
654+
655+
def test_query_pagination_without_max_item_count(self):
656+
"""Test pagination behavior without specifying max_item_count."""
657+
created_collection = self.created_db.create_container(
658+
"pagination_no_max_test_" + str(uuid.uuid4()),
659+
PartitionKey(path="/pk"))
660+
661+
# Create 15 items in a single partition
662+
total_items = 15
663+
partition_key_value = "test_pk_2"
664+
for i in range(total_items):
665+
document_definition = {
666+
'pk': partition_key_value,
667+
'id': f'item_{i}',
668+
'value': i
669+
}
670+
created_collection.create_item(body=document_definition)
671+
672+
# Query without specifying max_item_count
673+
query = "SELECT * FROM c WHERE c.pk = @pk"
674+
query_iterable = created_collection.query_items(
675+
query=query,
676+
parameters=[{"name": "@pk", "value": partition_key_value}],
677+
partition_key=partition_key_value
678+
)
679+
680+
# Count total results
681+
all_results = list(query_iterable)
682+
self.assertEqual(len(all_results), total_items)
683+
684+
self.created_db.delete_container(created_collection.id)
685+
601686
def test_query_positional_args(self):
602687
container = self.created_db.get_container_client(self.config.TEST_MULTI_PARTITION_CONTAINER_ID)
603688
partition_key_value1 = "pk1"

sdk/cosmos/azure-cosmos/tests/test_query_async.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,91 @@ async def test_partitioned_query_response_hook_async(self):
645645
assert response_hook.count == 1
646646
await self.created_db.delete_container(created_collection.id)
647647

648+
async def test_query_pagination_with_max_item_count_async(self):
649+
"""Test pagination showing per-page limits and total results counting."""
650+
created_collection = await self.created_db.create_container(
651+
"pagination_test_" + str(uuid.uuid4()),
652+
PartitionKey(path="/pk"))
653+
654+
# Create 20 items in a single partition
655+
total_items = 20
656+
partition_key_value = "test_pk"
657+
for i in range(total_items):
658+
document_definition = {
659+
'pk': partition_key_value,
660+
'id': f'item_{i}',
661+
'value': i
662+
}
663+
await created_collection.create_item(body=document_definition)
664+
665+
# Test pagination with max_item_count limiting items per page
666+
max_items_per_page = 7
667+
query = "SELECT * FROM c WHERE c.pk = @pk ORDER BY c.value"
668+
query_iterable = created_collection.query_items(
669+
query=query,
670+
parameters=[{"name": "@pk", "value": partition_key_value}],
671+
partition_key=partition_key_value,
672+
max_item_count=max_items_per_page
673+
)
674+
675+
# Iterate through pages and verify per-page counts
676+
all_fetched_results = []
677+
page_count = 0
678+
item_pages = query_iterable.by_page()
679+
680+
async for page in item_pages:
681+
page_count += 1
682+
items_in_page = [item async for item in page]
683+
all_fetched_results.extend(items_in_page)
684+
685+
# Each page should have at most max_item_count items
686+
# (last page may have fewer)
687+
assert len(items_in_page) <= max_items_per_page
688+
689+
# Verify total results match expected count
690+
assert len(all_fetched_results) == total_items
691+
692+
# Verify we got the expected number of pages
693+
# 20 items with max 7 per page = 3 pages (7, 7, 6)
694+
assert page_count == 3
695+
696+
# Verify ordering is maintained
697+
for i, item in enumerate(all_fetched_results):
698+
assert item['value'] == i
699+
700+
await self.created_db.delete_container(created_collection.id)
701+
702+
async def test_query_pagination_without_max_item_count_async(self):
703+
"""Test pagination behavior without specifying max_item_count."""
704+
created_collection = await self.created_db.create_container(
705+
"pagination_no_max_test_" + str(uuid.uuid4()),
706+
PartitionKey(path="/pk"))
707+
708+
# Create 15 items in a single partition
709+
total_items = 15
710+
partition_key_value = "test_pk_2"
711+
for i in range(total_items):
712+
document_definition = {
713+
'pk': partition_key_value,
714+
'id': f'item_{i}',
715+
'value': i
716+
}
717+
await created_collection.create_item(body=document_definition)
718+
719+
# Query without specifying max_item_count
720+
query = "SELECT * FROM c WHERE c.pk = @pk"
721+
query_iterable = created_collection.query_items(
722+
query=query,
723+
parameters=[{"name": "@pk", "value": partition_key_value}],
724+
partition_key=partition_key_value
725+
)
726+
727+
# Count total results
728+
all_results = [item async for item in query_iterable]
729+
assert len(all_results) == total_items
730+
731+
await self.created_db.delete_container(created_collection.id)
732+
648733
async def _MockExecuteFunctionSessionRetry(self, function, *args, **kwargs):
649734
if args:
650735
if args[1].operation_type == 'SqlQuery':

0 commit comments

Comments
 (0)