From b122bdf11bf99ef84c2d6b7b75da65e22bc20f95 Mon Sep 17 00:00:00 2001
From: Florian Spreckelsen <f.spreckelsen@indiscale.com>
Date: Fri, 21 Jun 2024 16:37:14 +0200
Subject: [PATCH] DOC: Add documentation of paginated queries.

---
 src/doc/tutorials/index.rst             |  1 +
 src/doc/tutorials/paginated_queries.rst | 63 +++++++++++++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 src/doc/tutorials/paginated_queries.rst

diff --git a/src/doc/tutorials/index.rst b/src/doc/tutorials/index.rst
index 706e26c2..e745482a 100644
--- a/src/doc/tutorials/index.rst
+++ b/src/doc/tutorials/index.rst
@@ -15,6 +15,7 @@ advanced usage of the Python client.
    Data-Insertion
    errors
    Entity-Getters
+   paginated_queries
    caching
    data-model-interface
    complex_data_models
diff --git a/src/doc/tutorials/paginated_queries.rst b/src/doc/tutorials/paginated_queries.rst
new file mode 100644
index 00000000..c250223f
--- /dev/null
+++ b/src/doc/tutorials/paginated_queries.rst
@@ -0,0 +1,63 @@
+Query pagination
+================
+
+When retrieving many entities, you may not want to retrieve all at once, e.g.,
+for performance reasons or to prevent connection timeouts, but rather in a
+chunked way. For that purpose, there is the ``page_length`` parameter in the
+:py:meth:`~linkahead.common.models.execute_query` function. If this is set to a
+non-zero integer, the behavior of the function changes in that it returns a
+Python `generator <https://docs.python.org/3/glossary.html#term-generator>`_
+which can be used, e.g., in loops or in list comprehension. The generator yields
+a :py:class:`~linkahead.common.models.Container` containing the next
+``page_length`` many entities from the query result.
+
+The following example illustrates this on the demo server.
+
+.. code-block:: python
+
+   import linkahead as db
+
+   # 10 at the time of writing of this example
+   print(db.execute_query("FIND MusicalInstrument"))
+
+   # Retrieve in pages of length 5 and iterate over the pages
+   for page in db.execute_query("FIND MusicalInstrument", page_length=5):
+       # each page is a container
+       print(type(page))
+       # exactly page_length=5 for the first N-1 pages,
+       # and possibly less for the last page
+       print(len(page))
+       # the items on each page are subclasses of Entity
+       print(type(page[0]))
+       # The id of the first entity on the page is different for all pages
+       print(page[0].id)
+
+   # You can use this in a list comprehension to fill a container
+   container_paginated = db.Container().extend(
+       [ent for page in db.execute_query("FIND MusicalInstrument", page_length=5) for ent in page]
+   )
+   # The result is the same as in the unpaginated case, but the
+   # following can cause connection timeouts in case of very large
+   # retrievals
+   container_at_once = db.execute_query("FIND MusicalInstrument")
+   for ent1, ent2 in zip(container_paginated, container_at_once):
+      print(ent1.id == ent2.id)  # always true
+
+As you can see, you can iterate over a paginated query and then access the
+entities on each page during the iteration.
+
+.. note::
+
+   The ``page_length`` keyword is ignored for ``COUNT`` queries where
+   :py:meth:`~linkahead.common.models.execute_query` always returns the integer
+   result and in case of ``unique=True`` where always exactly one
+   :py:class:`~linkahead.common.models.Entity` is returned.
+
+
+.. warning::
+
+   Be careful when combining query pagination with insert, update, or delete
+   operations. If your database changes while iterating over a paginated query,
+   the client will raise a
+   :py:exc:`~linkahead.exceptions.PagingConsistencyError` since the server
+   can't guarantee that the query results haven't changed in the meantime.
-- 
GitLab