From 545cf5d1654569bee44a460a0d9912ba3e18e213 Mon Sep 17 00:00:00 2001 From: Nishant Singh Date: Thu, 4 Jul 2024 15:59:46 +0530 Subject: [PATCH] empty queryset handling --- .../service.py | 25 +++++++++++++++---- setup.py | 2 +- .../tests/test_count_wrapper_for_postgres.py | 22 +++++++++------- .../test_get_or_none_wrapper_for_postgres.py | 10 ++++++++ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/django_querysets_single_query_fetch/service.py b/django_querysets_single_query_fetch/service.py index d926b94..cb2d91b 100644 --- a/django_querysets_single_query_fetch/service.py +++ b/django_querysets_single_query_fetch/service.py @@ -51,6 +51,8 @@ def __init__(self, queryset: QuerySet) -> None: QuerysetWrapperType = Union[QuerySet, QuerysetCountWrapper, QuerysetGetOrNoneWrapper] +RESULT_PLACEHOLDER = object() + class QuerysetsSingleQueryFetch: """ @@ -379,6 +381,17 @@ def _convert_raw_results_to_final_queryset_results( return queryset_results + def _get_empty_queryset_value(self, queryset: QuerysetWrapperType) -> Any: + if isinstance(queryset, QuerysetCountWrapper): + empty_sql_val = 0 + elif isinstance(queryset, QuerysetGetOrNoneWrapper): + empty_sql_val = None + else: + # normal queryset + empty_sql_val = [] + + return empty_sql_val + def execute(self) -> list[list[Any]]: django_sqls_for_querysets = [ self._get_django_sql_for_queryset(queryset=queryset) @@ -387,12 +400,14 @@ def execute(self) -> list[list[Any]]: final_result_list: List[Any] = [] - for queryset_sql in django_sqls_for_querysets: + for queryset_sql, queryset in zip(django_sqls_for_querysets, self.querysets): if not queryset_sql: - final_result_list.append([]) + final_result_list.append( + self._get_empty_queryset_value(queryset=queryset) + ) else: final_result_list.append( - None + RESULT_PLACEHOLDER ) # will be replaced by actual result below non_empty_django_sqls_for_querysets = [ @@ -415,8 +430,8 @@ def execute(self) -> list[list[Any]]: final_result = [] index = 0 for queryset, result in zip(self.querysets, final_result_list): - if result is not None: - # empty case EmptyResultSet + if result is not RESULT_PLACEHOLDER: + # empty sql case final_result.append(result) continue final_result.append( diff --git a/setup.py b/setup.py index f54b9d9..37b793e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setuptools.setup( name="django_querysets_single_query_fetch", - version="0.0.9", + version="0.0.10", description="Execute multiple Django querysets in a single SQL query", long_description="Utility which executes multiple Django querysets over a single network/query call and returns results which would have been returned in normal evaluation of querysets", author="Nishant Singh", diff --git a/testapp/tests/test_count_wrapper_for_postgres.py b/testapp/tests/test_count_wrapper_for_postgres.py index 13ddcf5..1cab3f1 100644 --- a/testapp/tests/test_count_wrapper_for_postgres.py +++ b/testapp/tests/test_count_wrapper_for_postgres.py @@ -24,13 +24,7 @@ def setUp(self) -> None: StoreProduct, store=self.store, category=self.category, selling_price=100.33 ) - def test_fetch_count(self): - """ - - test fetch count works in single query - _ test fetch count works with filter querysets - _ test fetch count works with other querysets - """ - # test fetch count works in single query + def test_works_in_simple_case(self): count_queryset = StoreProduct.objects.filter() with self.assertNumQueries(1): results = QuerysetsSingleQueryFetch( @@ -41,7 +35,7 @@ def test_fetch_count(self): self.assertEqual(len(results), 1) self.assertEqual(results[0], count_queryset.count()) - # test fetch count works with filter querysets + def test_works_with_filtered_queryset(self): count_filter_queryset = StoreProduct.objects.filter(id=self.product_1.id) with self.assertNumQueries(1): results = QuerysetsSingleQueryFetch( @@ -52,7 +46,7 @@ def test_fetch_count(self): self.assertEqual(len(results), 1) self.assertEqual(results[0], count_filter_queryset.count()) - # test fetch count works with other querysets + def test_works_with_other_querysets(self): count_queryset = StoreProduct.objects.filter() count_filter_queryset = StoreProduct.objects.filter(id=self.product_1.id) queryset = StoreProduct.objects.filter() @@ -68,3 +62,13 @@ def test_fetch_count(self): self.assertEqual(results[0], count_queryset.count()) self.assertEqual(results[1], count_filter_queryset.count()) self.assertEqual(results[2], list(queryset)) + + def test_count_is_returned_as_zero_for_empty_queryset(self): + with self.assertNumQueries(0): + results = QuerysetsSingleQueryFetch( + querysets=[ + QuerysetCountWrapper(StoreProduct.objects.none()), + ] + ).execute() + self.assertEqual(len(results), 1) + self.assertEqual(results[0], 0) diff --git a/testapp/tests/test_get_or_none_wrapper_for_postgres.py b/testapp/tests/test_get_or_none_wrapper_for_postgres.py index 3250815..6f72b36 100644 --- a/testapp/tests/test_get_or_none_wrapper_for_postgres.py +++ b/testapp/tests/test_get_or_none_wrapper_for_postgres.py @@ -60,3 +60,13 @@ def test_get_or_none_wrapper_with_multiple_rows_matching(self): self.assertTrue( (product.id == self.product_1.id) or (product.id == self.product_2.id) ) + + def test_get_or_none_wrapper_with_empty_queryset(self): + with self.assertNumQueries(0): + results = QuerysetsSingleQueryFetch( + querysets=[ + QuerysetGetOrNoneWrapper(StoreProduct.objects.none()), + ] + ).execute() + self.assertEqual(len(results), 1) + self.assertIsNone(results[0])