diff --git a/packages/django-cf/django_cf/db/base_engine.py b/packages/django-cf/django_cf/db/base_engine.py index 76858de..020c95d 100644 --- a/packages/django-cf/django_cf/db/base_engine.py +++ b/packages/django-cf/django_cf/db/base_engine.py @@ -201,6 +201,7 @@ class CFResult: def __init__(self, data): self.data = data + self._position = 0 def __iter__(self): return iter(self.data) @@ -212,29 +213,23 @@ def set_rowcount(self, value): self.rowcount = value def fetchone(self): - if len(self.data) > 0: - return self.data.pop() + if self._position < len(self.data): + row = self.data[self._position] + self._position += 1 + return row return None def fetchall(self): - ret = [] - while True: - row = self.fetchone() - if row is None: - break - ret.append(row) + ret = self.data[self._position :] + self._position = len(self.data) return ret def fetchmany(self, size=1): - ret = [] - while size > 0: - row = self.fetchone() - if row is None: - break - ret.append(row) - if size is not None: - size -= 1 - + if size <= 0: + return [] + end = min(self._position + size, len(self.data)) + ret = self.data[self._position : end] + self._position = end return ret @staticmethod diff --git a/packages/django-cf/tests/in_worker/worker/src/test_asgi_d1.py b/packages/django-cf/tests/in_worker/worker/src/test_asgi_d1.py index d42fe14..ee548e7 100644 --- a/packages/django-cf/tests/in_worker/worker/src/test_asgi_d1.py +++ b/packages/django-cf/tests/in_worker/worker/src/test_asgi_d1.py @@ -58,12 +58,13 @@ async def update_view(request): async def iterate_view(request): - del request for value in ["charlie", "alpha", "bravo"]: await AsgiD1Record.objects.using("d1").acreate(value=value) + order_field = "-value" if request.GET.get("direction") == "desc" else "value" + values = [] - queryset = AsgiD1Record.objects.using("d1").order_by("value") + queryset = AsgiD1Record.objects.using("d1").order_by(order_field) async for record in queryset: values.append(record.value) @@ -148,9 +149,18 @@ async def test_asgi_d1_orm_update_returns_affected_rows_and_persists_value(): @pytest.mark.asyncio -async def test_asgi_d1_orm_async_iteration_returns_all_rows(): +async def test_asgi_d1_orm_async_iteration_returns_all_rows_ascending(): response, payload = await _run_d1_request("/asgi/d1/iterate/") assert response.status == 200 assert payload is not None - assert sorted(payload["values"]) == ["alpha", "bravo", "charlie"] + assert payload["values"] == ["alpha", "bravo", "charlie"] + + +@pytest.mark.asyncio +async def test_asgi_d1_orm_async_iteration_returns_all_rows_descending(): + response, payload = await _run_d1_request("/asgi/d1/iterate/?direction=desc") + + assert response.status == 200 + assert payload is not None + assert payload["values"] == ["charlie", "bravo", "alpha"] diff --git a/packages/django-cf/tests/in_worker/worker/src/test_base_engine.py b/packages/django-cf/tests/in_worker/worker/src/test_base_engine.py index f6793e8..0a837e4 100644 --- a/packages/django-cf/tests/in_worker/worker/src/test_base_engine.py +++ b/packages/django-cf/tests/in_worker/worker/src/test_base_engine.py @@ -50,8 +50,10 @@ def test_fetchone_with_data(self): result = CFResult(data) row = result.fetchone() - assert row == (3, "c") - assert len(result.data) == 2 + assert row == (1, "a") + assert result.data == data + assert result.fetchall() == [(2, "b"), (3, "c")] + assert result.fetchone() is None def test_fetchone_empty(self): from django_cf.db.base_engine import CFResult @@ -66,8 +68,9 @@ def test_fetchall(self): data = [(1, "a"), (2, "b"), (3, "c")] result = CFResult(data) - assert result.fetchall() == [(3, "c"), (2, "b"), (1, "a")] - assert len(result.data) == 0 + assert result.fetchall() == [(1, "a"), (2, "b"), (3, "c")] + assert result.data == data + assert result.fetchall() == [] def test_fetchall_empty(self): from django_cf.db.base_engine import CFResult @@ -80,8 +83,8 @@ def test_fetchmany_default(self): data = [(1, "a"), (2, "b"), (3, "c")] result = CFResult(data) - assert result.fetchmany() == [(3, "c")] - assert len(result.data) == 2 + assert result.fetchmany() == [(1, "a")] + assert result.data == data def test_fetchmany_specific_size(self): from django_cf.db.base_engine import CFResult @@ -90,8 +93,24 @@ def test_fetchmany_specific_size(self): result = CFResult(data) rows = result.fetchmany(2) - assert len(rows) == 2 - assert len(result.data) == 1 + assert rows == [(1, "a"), (2, "b")] + assert result.data == data + assert result.fetchmany(2) == [(3, "c")] + assert result.fetchmany(2) == [] + + def test_fetchmany_non_positive_size_does_not_advance(self): + from django_cf.db.base_engine import CFResult + + data = [(1, "a"), (2, "b"), (3, "c")] + result = CFResult(data) + + assert result.fetchmany(0) == [] + assert result.fetchone() == (1, "a") + + result = CFResult(data) + + assert result.fetchmany(-1) == [] + assert result.fetchone() == (1, "a") def test_fetchmany_more_than_available(self): from django_cf.db.base_engine import CFResult @@ -100,8 +119,20 @@ def test_fetchmany_more_than_available(self): result = CFResult(data) rows = result.fetchmany(5) - assert len(rows) == 2 - assert len(result.data) == 0 + assert rows == [(1, "a"), (2, "b")] + assert result.data == data + assert result.fetchone() is None + + def test_fetchone_then_fetchall_returns_remaining_rows(self): + from django_cf.db.base_engine import CFResult + + data = [(1, "a"), (2, "b"), (3, "c")] + result = CFResult(data) + + assert result.fetchone() == (1, "a") + assert result.fetchall() == [(2, "b"), (3, "c")] + assert result.data == data + assert result.fetchone() is None def test_from_object_with_list_rows(self): from django_cf.db.base_engine import CFResult