Skip to content

Commit d505378

Browse files
committed
added offset clause for select and objects queries
1 parent 1553c28 commit d505378

File tree

9 files changed

+125
-8
lines changed

9 files changed

+125
-8
lines changed

docs/src/piccolo/query_clauses/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Query Clauses
88

99
./first
1010
./limit
11+
./offset
1112
./order_by
1213
./where
1314
./batch
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.. _offset:
2+
3+
offset
4+
======
5+
6+
You can use ``offset`` clauses with the following queries:
7+
8+
* :ref:`Objects`
9+
* :ref:`Select`
10+
11+
This will omit the first X rows from the response. It's useful for things like
12+
pagination.
13+
14+
It's highly recommended to use it along with an :ref:`order_by` clause,
15+
otherwise the results returned could be different each time.
16+
17+
.. code-block:: python
18+
19+
>>> Band.select(Band.name).offset(1).order_by(Band.name).run_sync()
20+
[{'name': 'Pythonistas'}, {'name': 'Rustaceans'}]
21+
22+
Likewise, with objects:
23+
24+
.. code-block:: python
25+
26+
>>> Band.objects().offset(1).order_by(Band.name).run_sync()
27+
[Band2, Band3]

docs/src/piccolo/query_types/objects.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ limit
8484

8585
See  :ref:`limit`.
8686

87+
offset
88+
~~~~~~
89+
90+
See  :ref:`offset`.
91+
8792
first
8893
~~~~~
8994

docs/src/piccolo/query_types/select.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ limit
119119

120120
See  :ref:`limit`.
121121

122+
offset
123+
~~~~~~
124+
125+
See  :ref:`offset`.
126+
122127
order_by
123128
~~~~~~~~
124129

piccolo/query/methods/objects.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from piccolo.query.base import Query
77
from piccolo.query.mixins import (
88
LimitDelegate,
9+
OffsetDelegate,
910
OrderByDelegate,
1011
WhereDelegate,
1112
OutputDelegate,
@@ -26,13 +27,15 @@ class Objects(Query):
2627

2728
__slots__ = (
2829
"limit_delegate",
30+
"offset_delegate",
2931
"order_by_delegate",
3032
"output_delegate",
3133
"where_delegate",
3234
)
3335

3436
def _setup_delegates(self):
3537
self.limit_delegate = LimitDelegate()
38+
self.offset_delegate = OffsetDelegate()
3639
self.order_by_delegate = OrderByDelegate()
3740
self.output_delegate = OutputDelegate()
3841
self.output_delegate._output.as_objects = True
@@ -46,6 +49,10 @@ def first(self) -> Objects:
4649
self.limit_delegate.first()
4750
return self
4851

52+
def offset(self, number: int) -> Objects:
53+
self.offset_delegate.offset(number)
54+
return self
55+
4956
def order_by(self, *columns: Column, ascending=True) -> Objects:
5057
self.order_by_delegate.order_by(*columns, ascending=ascending)
5158
return self
@@ -77,6 +84,7 @@ def querystring(self) -> QueryString:
7784
for attr in (
7885
"limit_delegate",
7986
"where_delegate",
87+
"offset_delegate",
8088
"output_delegate",
8189
"order_by_delegate",
8290
):

piccolo/query/methods/select.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
ColumnsDelegate,
1111
DistinctDelegate,
1212
LimitDelegate,
13+
OffsetDelegate,
1314
OrderByDelegate,
1415
OutputDelegate,
1516
WhereDelegate,
@@ -27,6 +28,7 @@ class Select(Query):
2728
"columns_delegate",
2829
"distinct_delegate",
2930
"limit_delegate",
31+
"offset_delegate",
3032
"order_by_delegate",
3133
"output_delegate",
3234
"where_delegate",
@@ -45,6 +47,7 @@ def _setup_delegates(self):
4547
self.columns_delegate = ColumnsDelegate()
4648
self.distinct_delegate = DistinctDelegate()
4749
self.limit_delegate = LimitDelegate()
50+
self.offset_delegate = OffsetDelegate()
4851
self.order_by_delegate = OrderByDelegate()
4952
self.output_delegate = OutputDelegate()
5053
self.where_delegate = WhereDelegate()
@@ -66,6 +69,10 @@ def first(self) -> Select:
6669
self.limit_delegate.first()
6770
return self
6871

72+
def offset(self, number: int) -> Select:
73+
self.offset_delegate.offset(number)
74+
return self
75+
6976
async def response_handler(self, response):
7077
if self.limit_delegate._first:
7178
if len(response) == 0:
@@ -210,6 +217,10 @@ def querystring(self) -> QueryString:
210217
query += " {}"
211218
args.append(self.limit_delegate._limit.querystring)
212219

220+
if self.offset_delegate._offset:
221+
query += " {}"
222+
args.append(self.offset_delegate._offset.querystring)
223+
213224
querystring = QueryString(query, *args)
214225

215226
return querystring

piccolo/query/mixins.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ def __str__(self) -> str:
2828
return self.querystring.__str__()
2929

3030

31+
@dataclass
32+
class Offset:
33+
__slots__ = ("number",)
34+
35+
number: int
36+
37+
def __post_init__(self):
38+
if type(self.number) != int:
39+
raise TypeError("Limit must be an integer")
40+
41+
@property
42+
def querystring(self) -> QueryString:
43+
return QueryString(f" OFFSET {self.number}")
44+
45+
def __str__(self) -> str:
46+
return self.querystring.__str__()
47+
48+
3149
@dataclass
3250
class OrderBy:
3351
__slots__ = ("columns", "ascending")
@@ -192,3 +210,21 @@ class ValuesDelegate:
192210

193211
def values(self, values: t.Dict[Column, t.Any]):
194212
self._values.update(values)
213+
214+
215+
@dataclass
216+
class OffsetDelegate:
217+
"""
218+
Used to offset the results - for example, to return row 100 and onward.
219+
220+
Typically used in conjunction with order_by and limit.
221+
222+
Example usage:
223+
224+
.offset(100)
225+
"""
226+
227+
_offset: t.Optional[Offset] = None
228+
229+
def offset(self, number: int = 0):
230+
self._offset = Offset(number)

tests/table/test_objects.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44

55
class TestObjects(DBTestCase):
6-
76
def test_get_all(self):
87
self.insert_row()
98

@@ -14,17 +13,25 @@ def test_get_all(self):
1413
instance = response[0]
1514

1615
self.assertTrue(isinstance(instance, Band))
17-
self.assertTrue(instance.name == 'Pythonistas')
16+
self.assertTrue(instance.name == "Pythonistas")
1817

1918
# Now try changing the value and saving it.
20-
instance.name = 'Rustaceans'
19+
instance.name = "Rustaceans"
2120
save_query = instance.save()
2221
save_query.run_sync()
2322

2423
self.assertTrue(
25-
Band.select().columns(
26-
Band.name
27-
).output(
28-
as_list=True
29-
).run_sync()[0] == 'Rustaceans'
24+
Band.select().columns(Band.name).output(as_list=True).run_sync()[0]
25+
== "Rustaceans"
26+
)
27+
28+
def test_offset(self):
29+
self.insert_rows()
30+
31+
response = Band.objects().order_by(Band.name).offset(1).run_sync()
32+
33+
print(f"response = {response}")
34+
35+
self.assertEqual(
36+
[i.name for i in response], ["Pythonistas", "Rustaceans"]
3037
)

tests/table/test_select.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,23 @@ def test_limit(self):
218218

219219
self.assertEqual(response, [{"name": "CSharps"}])
220220

221+
def test_offset(self):
222+
self.insert_rows()
223+
224+
response = (
225+
Band.select()
226+
.columns(Band.name)
227+
.order_by(Band.name)
228+
.offset(1)
229+
.run_sync()
230+
)
231+
232+
print(f"response = {response}")
233+
234+
self.assertEqual(
235+
response, [{"name": "Pythonistas"}, {"name": "Rustaceans"}]
236+
)
237+
221238
def test_first(self):
222239
self.insert_rows()
223240

0 commit comments

Comments
 (0)