Skip to content

Commit 323aa12

Browse files
committed
Merge branch 'master' into pr/1364
2 parents 73aa7c2 + 97715b8 commit 323aa12

File tree

9 files changed

+150
-1
lines changed

9 files changed

+150
-1
lines changed

docs/src/piccolo/api_reference/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,15 @@ QueryString
145145
.. currentmodule:: piccolo.querystring
146146

147147
.. autoclass:: QueryString
148+
149+
-------------------------------------------------------------------------------
150+
151+
Custom Types
152+
------------
153+
154+
BasicTypes
155+
~~~~~~~~~~
156+
157+
.. currentmodule:: piccolo.custom_types
158+
159+
.. autoclass:: BasicTypes
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Conditional functions
2+
=====================
3+
4+
.. currentmodule:: piccolo.query.functions.conditional
5+
6+
Coalesce
7+
--------
8+
9+
.. autoclass:: Coalesce

docs/src/piccolo/functions/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Functions can be used to modify how queries are run, and what is returned.
1313
./basic_usage
1414
./aggregate
1515
./array
16+
./conditional
1617
./datetime
1718
./math
1819
./string

piccolo/apps/playground/commands/run.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class Album(Table):
153153
id: Serial
154154
name = Varchar()
155155
band = ForeignKey(Band)
156-
release_date = Date()
156+
release_date = Date(null=True, default=None)
157157
recorded_at = ForeignKey(RecordingStudio)
158158
awards = Array(Varchar())
159159

@@ -323,6 +323,15 @@ def populate():
323323
Album.awards: ["Mercury Prize 2022"],
324324
}
325325
),
326+
Album(
327+
{
328+
Album.name: "Awesome album 4",
329+
Album.recorded_at: recording_studio_2,
330+
Album.band: rustaceans,
331+
Album.release_date: None,
332+
Album.awards: [],
333+
}
334+
),
326335
).run_sync()
327336

328337
genres = Genre.insert(

piccolo/columns/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
if TYPE_CHECKING: # pragma: no cover
4848
from piccolo.columns.column_types import ForeignKey
49+
from piccolo.query.functions.conditional import Coalesce
4950
from piccolo.query.methods.select import Select
5051
from piccolo.table import Table
5152

@@ -676,6 +677,11 @@ def ilike(self, value: str) -> Where:
676677
def not_like(self, value: str) -> Where:
677678
return Where(column=self, value=value, operator=NotLike)
678679

680+
def __or__(self, value) -> Coalesce:
681+
from piccolo.query.functions.conditional import Coalesce
682+
683+
return Coalesce(self, value)
684+
679685
def __lt__(self, value) -> Where:
680686
return Where(column=self, value=value, operator=LessThan)
681687

piccolo/query/functions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
ArrayRemove,
77
ArrayReplace,
88
)
9+
from .conditional import Coalesce
910
from .datetime import Day, Extract, Hour, Month, Second, Strftime, Year
1011
from .math import Abs, Ceil, Floor, Round
1112
from .string import Concat, Length, Lower, Ltrim, Reverse, Rtrim, Upper
@@ -16,6 +17,7 @@
1617
"Avg",
1718
"Cast",
1819
"Ceil",
20+
"Coalesce",
1921
"Concat",
2022
"Count",
2123
"Day",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Optional, Union
4+
5+
from piccolo.custom_types import BasicTypes
6+
from piccolo.querystring import QueryString
7+
8+
if TYPE_CHECKING:
9+
from piccolo.columns import Column
10+
11+
12+
class Coalesce(QueryString):
13+
def __init__(
14+
self,
15+
*args: Union[Column, QueryString, BasicTypes],
16+
alias: Optional[str] = None,
17+
):
18+
"""
19+
Returns the first non-null value.
20+
21+
Here's an example to try in the playground::
22+
23+
>>> await Album.select(Album.release_date)
24+
[
25+
{'release_date': datetime.date(2021, 1, 1)},
26+
{'release_date': datetime.date(2025, 1, 1)},
27+
{'release_date': datetime.date(2022, 2, 2)},
28+
{'release_date': None}
29+
]
30+
31+
One of the values is null - we can specify a fallback value::
32+
33+
>>> from piccolo.functions.conditional import Coalesce
34+
>>> await Album.select(
35+
... Coalesce(Album.release_date, datetime.date(2050, 1, 1))
36+
... )
37+
[
38+
{'release_date': datetime.date(2021, 1, 1)},
39+
{'release_date': datetime.date(2025, 1, 1)},
40+
{'release_date': datetime.date(2022, 2, 2)},
41+
{'release_date': datetime.date(2050, 1, 1)}
42+
]
43+
44+
Or us this abbreviated syntax::
45+
46+
>>> await Album.select(
47+
... Album.release_date | datetime.date(2050, 1, 1)
48+
... )
49+
[
50+
{'release_date': datetime.date(2021, 1, 1)},
51+
{'release_date': datetime.date(2025, 1, 1)},
52+
{'release_date': datetime.date(2022, 2, 2)},
53+
{'release_date': datetime.date(2050, 1, 1)}
54+
]
55+
56+
"""
57+
if len(args) < 2:
58+
raise ValueError("At least two values must be passed in.")
59+
60+
#######################################################################
61+
# Preserve the original alias from the column.
62+
63+
from piccolo.columns import Column
64+
65+
first_arg = args[0]
66+
67+
if isinstance(first_arg, Column):
68+
alias = (
69+
alias
70+
or first_arg._alias
71+
or first_arg._meta.get_default_alias()
72+
)
73+
elif isinstance(first_arg, QueryString):
74+
alias = alias or first_arg._alias
75+
76+
#######################################################################
77+
78+
placeholders = ", ".join("{}" for _ in args)
79+
80+
super().__init__(f"COALESCE({placeholders})", *args, alias=alias)

piccolo/querystring.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ def eq(self, value) -> QueryString:
275275
def ne(self, value) -> QueryString:
276276
return self.__ne__(value)
277277

278+
def __or__(self, value) -> QueryString:
279+
from piccolo.query.functions.conditional import Coalesce
280+
281+
return Coalesce(self, value, alias=self._alias)
282+
278283
def __add__(self, value) -> QueryString:
279284
return QueryString("{} + {}", self, value)
280285

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from piccolo.columns import Integer
2+
from piccolo.query.functions.conditional import Coalesce
3+
from piccolo.table import Table
4+
from piccolo.testing.test_case import AsyncTableTest
5+
6+
7+
class Band(Table):
8+
popularity = Integer(null=True, default=None)
9+
10+
11+
class TestCoalesce(AsyncTableTest):
12+
13+
tables = [Band]
14+
15+
async def asyncSetUp(self):
16+
await super().asyncSetUp()
17+
await Band({Band.popularity: None}).save()
18+
19+
async def test_coalesce(self):
20+
response = await Band.select(Coalesce(Band.popularity, 10))
21+
self.assertListEqual(response, [{"popularity": 10}])
22+
23+
async def test_coalesce_pipe_syntax(self):
24+
response = await Band.select(Band.popularity | 10)
25+
self.assertListEqual(response, [{"popularity": 10}])

0 commit comments

Comments
 (0)