piccolo 1.8.0__py3-none-any.whl → 1.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- piccolo/__init__.py +1 -1
- piccolo/columns/base.py +55 -29
- piccolo/columns/defaults/base.py +5 -3
- piccolo/query/functions/__init__.py +5 -0
- piccolo/query/functions/math.py +48 -0
- piccolo/querystring.py +18 -0
- piccolo/schema.py +2 -1
- {piccolo-1.8.0.dist-info → piccolo-1.9.0.dist-info}/METADATA +1 -1
- {piccolo-1.8.0.dist-info → piccolo-1.9.0.dist-info}/RECORD +22 -15
- tests/columns/test_array.py +63 -19
- tests/columns/test_get_sql_value.py +66 -0
- tests/query/functions/__init__.py +0 -0
- tests/query/functions/base.py +34 -0
- tests/query/functions/test_functions.py +64 -0
- tests/query/functions/test_math.py +39 -0
- tests/query/functions/test_string.py +25 -0
- tests/query/functions/test_type_conversion.py +134 -0
- tests/query/test_querystring.py +136 -0
- tests/query/test_functions.py +0 -238
- {piccolo-1.8.0.dist-info → piccolo-1.9.0.dist-info}/LICENSE +0 -0
- {piccolo-1.8.0.dist-info → piccolo-1.9.0.dist-info}/WHEEL +0 -0
- {piccolo-1.8.0.dist-info → piccolo-1.9.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.8.0.dist-info → piccolo-1.9.0.dist-info}/top_level.txt +0 -0
piccolo/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "1.
|
1
|
+
__VERSION__ = "1.9.0"
|
piccolo/columns/base.py
CHANGED
@@ -830,7 +830,11 @@ class Column(Selectable):
|
|
830
830
|
engine_type=engine_type, with_alias=False
|
831
831
|
)
|
832
832
|
|
833
|
-
def get_sql_value(
|
833
|
+
def get_sql_value(
|
834
|
+
self,
|
835
|
+
value: t.Any,
|
836
|
+
delimiter: str = "'",
|
837
|
+
) -> str:
|
834
838
|
"""
|
835
839
|
When using DDL statements, we can't parameterise the values. An example
|
836
840
|
is when setting the default for a column. So we have to convert from
|
@@ -839,11 +843,18 @@ class Column(Selectable):
|
|
839
843
|
|
840
844
|
:param value:
|
841
845
|
The Python value to convert to a string usable in a DDL statement
|
842
|
-
e.g. 1
|
846
|
+
e.g. ``1``.
|
847
|
+
:param delimiter:
|
848
|
+
The string returned by this function is wrapped in delimiters,
|
849
|
+
ready to be added to a DDL statement. For example:
|
850
|
+
``'hello world'``.
|
843
851
|
:returns:
|
844
|
-
The string usable in the DDL statement e.g. '1'
|
852
|
+
The string usable in the DDL statement e.g. ``'1'``.
|
845
853
|
|
846
854
|
"""
|
855
|
+
from piccolo.engine.sqlite import ADAPTERS as sqlite_adapters
|
856
|
+
|
857
|
+
# Common across all DB engines
|
847
858
|
if isinstance(value, Default):
|
848
859
|
return getattr(value, self._meta.engine_type)
|
849
860
|
elif value is None:
|
@@ -851,37 +862,52 @@ class Column(Selectable):
|
|
851
862
|
elif isinstance(value, (float, decimal.Decimal)):
|
852
863
|
return str(value)
|
853
864
|
elif isinstance(value, str):
|
854
|
-
return f"
|
865
|
+
return f"{delimiter}{value}{delimiter}"
|
855
866
|
elif isinstance(value, bool):
|
856
867
|
return str(value).lower()
|
857
|
-
elif isinstance(value, datetime.datetime):
|
858
|
-
return f"'{value.isoformat().replace('T', ' ')}'"
|
859
|
-
elif isinstance(value, datetime.date):
|
860
|
-
return f"'{value.isoformat()}'"
|
861
|
-
elif isinstance(value, datetime.time):
|
862
|
-
return f"'{value.isoformat()}'"
|
863
|
-
elif isinstance(value, datetime.timedelta):
|
864
|
-
interval = IntervalCustom.from_timedelta(value)
|
865
|
-
return getattr(interval, self._meta.engine_type)
|
866
868
|
elif isinstance(value, bytes):
|
867
|
-
return f"
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
(
|
876
|
-
|
877
|
-
|
878
|
-
|
869
|
+
return f"{delimiter}{value.hex()}{delimiter}"
|
870
|
+
|
871
|
+
# SQLite specific
|
872
|
+
if self._meta.engine_type == "sqlite":
|
873
|
+
if adapter := sqlite_adapters.get(type(value)):
|
874
|
+
sqlite_value = adapter(value)
|
875
|
+
return (
|
876
|
+
f"{delimiter}{sqlite_value}{delimiter}"
|
877
|
+
if isinstance(sqlite_value, str)
|
878
|
+
else sqlite_value
|
879
|
+
)
|
880
|
+
|
881
|
+
# Postgres and Cockroach
|
882
|
+
if self._meta.engine_type in ["postgres", "cockroach"]:
|
883
|
+
if isinstance(value, datetime.datetime):
|
884
|
+
return f"{delimiter}{value.isoformat().replace('T', ' ')}{delimiter}" # noqa: E501
|
885
|
+
elif isinstance(value, datetime.date):
|
886
|
+
return f"{delimiter}{value.isoformat()}{delimiter}"
|
887
|
+
elif isinstance(value, datetime.time):
|
888
|
+
return f"{delimiter}{value.isoformat()}{delimiter}"
|
889
|
+
elif isinstance(value, datetime.timedelta):
|
890
|
+
interval = IntervalCustom.from_timedelta(value)
|
891
|
+
return getattr(interval, self._meta.engine_type)
|
892
|
+
elif isinstance(value, uuid.UUID):
|
893
|
+
return f"{delimiter}{value}{delimiter}"
|
894
|
+
elif isinstance(value, list):
|
895
|
+
# Convert to the array syntax.
|
896
|
+
return (
|
897
|
+
delimiter
|
898
|
+
+ "{"
|
899
|
+
+ ",".join(
|
900
|
+
self.get_sql_value(
|
901
|
+
i,
|
902
|
+
delimiter="" if isinstance(i, list) else '"',
|
903
|
+
)
|
904
|
+
for i in value
|
879
905
|
)
|
880
|
-
|
906
|
+
+ "}"
|
907
|
+
+ delimiter
|
881
908
|
)
|
882
|
-
|
883
|
-
|
884
|
-
return value
|
909
|
+
|
910
|
+
return str(value)
|
885
911
|
|
886
912
|
@property
|
887
913
|
def column_type(self):
|
piccolo/columns/defaults/base.py
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import typing as t
|
4
|
-
from abc import ABC, abstractmethod
|
4
|
+
from abc import ABC, abstractmethod
|
5
5
|
|
6
6
|
from piccolo.utils.repr import repr_class_instance
|
7
7
|
|
8
8
|
|
9
9
|
class Default(ABC):
|
10
|
-
@
|
10
|
+
@property
|
11
|
+
@abstractmethod
|
11
12
|
def postgres(self) -> str:
|
12
13
|
pass
|
13
14
|
|
14
|
-
@
|
15
|
+
@property
|
16
|
+
@abstractmethod
|
15
17
|
def sqlite(self) -> str:
|
16
18
|
pass
|
17
19
|
|
@@ -1,17 +1,22 @@
|
|
1
1
|
from .aggregate import Avg, Count, Max, Min, Sum
|
2
|
+
from .math import Abs, Ceil, Floor, Round
|
2
3
|
from .string import Length, Lower, Ltrim, Reverse, Rtrim, Upper
|
3
4
|
from .type_conversion import Cast
|
4
5
|
|
5
6
|
__all__ = (
|
7
|
+
"Abs",
|
6
8
|
"Avg",
|
7
9
|
"Cast",
|
10
|
+
"Ceil",
|
8
11
|
"Count",
|
12
|
+
"Floor",
|
9
13
|
"Length",
|
10
14
|
"Lower",
|
11
15
|
"Ltrim",
|
12
16
|
"Max",
|
13
17
|
"Min",
|
14
18
|
"Reverse",
|
19
|
+
"Round",
|
15
20
|
"Rtrim",
|
16
21
|
"Sum",
|
17
22
|
"Upper",
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""
|
2
|
+
These functions mirror their counterparts in the Postgresql docs:
|
3
|
+
|
4
|
+
https://www.postgresql.org/docs/current/functions-math.html
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .base import Function
|
9
|
+
|
10
|
+
|
11
|
+
class Abs(Function):
|
12
|
+
"""
|
13
|
+
Absolute value.
|
14
|
+
"""
|
15
|
+
|
16
|
+
function_name = "ABS"
|
17
|
+
|
18
|
+
|
19
|
+
class Ceil(Function):
|
20
|
+
"""
|
21
|
+
Nearest integer greater than or equal to argument.
|
22
|
+
"""
|
23
|
+
|
24
|
+
function_name = "CEIL"
|
25
|
+
|
26
|
+
|
27
|
+
class Floor(Function):
|
28
|
+
"""
|
29
|
+
Nearest integer less than or equal to argument.
|
30
|
+
"""
|
31
|
+
|
32
|
+
function_name = "FLOOR"
|
33
|
+
|
34
|
+
|
35
|
+
class Round(Function):
|
36
|
+
"""
|
37
|
+
Rounds to nearest integer.
|
38
|
+
"""
|
39
|
+
|
40
|
+
function_name = "ROUND"
|
41
|
+
|
42
|
+
|
43
|
+
__all__ = (
|
44
|
+
"Abs",
|
45
|
+
"Ceil",
|
46
|
+
"Floor",
|
47
|
+
"Round",
|
48
|
+
)
|
piccolo/querystring.py
CHANGED
@@ -282,12 +282,30 @@ class QueryString(Selectable):
|
|
282
282
|
def __le__(self, value) -> QueryString:
|
283
283
|
return QueryString("{} <= {}", self, value)
|
284
284
|
|
285
|
+
def __truediv__(self, value) -> QueryString:
|
286
|
+
return QueryString("{} / {}", self, value)
|
287
|
+
|
288
|
+
def __mul__(self, value) -> QueryString:
|
289
|
+
return QueryString("{} * {}", self, value)
|
290
|
+
|
291
|
+
def __pow__(self, value) -> QueryString:
|
292
|
+
return QueryString("{} ^ {}", self, value)
|
293
|
+
|
294
|
+
def __mod__(self, value) -> QueryString:
|
295
|
+
return QueryString("{} % {}", self, value)
|
296
|
+
|
285
297
|
def is_in(self, value) -> QueryString:
|
286
298
|
return QueryString("{} IN {}", self, value)
|
287
299
|
|
288
300
|
def not_in(self, value) -> QueryString:
|
289
301
|
return QueryString("{} NOT IN {}", self, value)
|
290
302
|
|
303
|
+
def like(self, value: str) -> QueryString:
|
304
|
+
return QueryString("{} LIKE {}", self, value)
|
305
|
+
|
306
|
+
def ilike(self, value: str) -> QueryString:
|
307
|
+
return QueryString("{} ILIKE {}", self, value)
|
308
|
+
|
291
309
|
|
292
310
|
class Unquoted(QueryString):
|
293
311
|
"""
|
piccolo/schema.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
piccolo/__init__.py,sha256=
|
1
|
+
piccolo/__init__.py,sha256=Gh6i3k_DUgCHpLwjrSRIPrDHNQum_zcZ2tZi3wiqRkU,22
|
2
2
|
piccolo/custom_types.py,sha256=7HMQAze-5mieNLfbQ5QgbRQgR2abR7ol0qehv2SqROY,604
|
3
3
|
piccolo/main.py,sha256=1VsFV67FWTUikPTysp64Fmgd9QBVa_9wcwKfwj2UCEA,5117
|
4
4
|
piccolo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
piccolo/querystring.py,sha256=
|
6
|
-
piccolo/schema.py,sha256=
|
5
|
+
piccolo/querystring.py,sha256=_3enTH0oBx77LfpS9UG_5OGp5fMxmu50Dod5s1Gn9mY,9655
|
6
|
+
piccolo/schema.py,sha256=S_0dwyOVPx90wWbCOP_Y9YHWtb-2JCuAJWAoLkTbFbU,7743
|
7
7
|
piccolo/table.py,sha256=DJT8jTgirPpzkydjSzaCgcG0DiC75XRtW_xtFqTyg80,49457
|
8
8
|
piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
|
9
9
|
piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -115,7 +115,7 @@ piccolo/apps/user/piccolo_migrations/2020-06-11T21-38-55.py,sha256=JG_LFPrEljnSE
|
|
115
115
|
piccolo/apps/user/piccolo_migrations/2021-04-30T16-14-15.py,sha256=Y_Dj4ROSxjnPsRDqcnpWeyk8UpF8c80T08_O2uq-GoA,1219
|
116
116
|
piccolo/apps/user/piccolo_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
117
117
|
piccolo/columns/__init__.py,sha256=OYhO_n9anMiU9nL-K6ATq9FhAtm8RyMpqYQ7fTVbhxI,1120
|
118
|
-
piccolo/columns/base.py,sha256=
|
118
|
+
piccolo/columns/base.py,sha256=sgMiBvq-xLW6_W86g6XZTMc_3cskyeoMF6yIvIlnXsA,32487
|
119
119
|
piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,723
|
120
120
|
piccolo/columns/column_types.py,sha256=CzbNnP_VWvz6_r4aaRcMHiHZOaWHeq5IGaN8WJ7JGPA,81685
|
121
121
|
piccolo/columns/combination.py,sha256=vMXC2dfY7pvnCFhsT71XFVyb4gdQzfRsCMaiduu04Ss,6900
|
@@ -124,7 +124,7 @@ piccolo/columns/m2m.py,sha256=vRJZqBcBP3TQ9Mmb7UEqTgg0QoxIIjIu6JfGLAi4X8Q,14595
|
|
124
124
|
piccolo/columns/readable.py,sha256=hganxUPfIK5ZXn-qgteBxsOJfBJucgr9U0QLsLFYcuI,1562
|
125
125
|
piccolo/columns/reference.py,sha256=FqE9rpMBMwNNkKXR3Wi4ce-fyT2Vh4KM8YpdC21s6gg,3574
|
126
126
|
piccolo/columns/defaults/__init__.py,sha256=7hpB13baEJgc1zbZjRKDFr-5hltxM2VGj8KnKfOiS8c,145
|
127
|
-
piccolo/columns/defaults/base.py,sha256=
|
127
|
+
piccolo/columns/defaults/base.py,sha256=kZ63QVI5nacQdVj2oZ_0Kr5KKtEZTkotKrFibUS6axk,1860
|
128
128
|
piccolo/columns/defaults/date.py,sha256=7tW_tTfsnzU8LXn9Qkrtk8OPyi80SESa-dY_UMlNTp0,2455
|
129
129
|
piccolo/columns/defaults/interval.py,sha256=QTx-iW0J5Eogv72_xXg8hWHEqbRx7jyTxrrV-eIq9HI,1947
|
130
130
|
piccolo/columns/defaults/time.py,sha256=UEtfdMkn8YdlzyEmqO6DrVnuwZrYUrvG_gTJ-SOMmwk,2355
|
@@ -149,9 +149,10 @@ piccolo/query/__init__.py,sha256=bcsMV4813rMRAIqGv4DxI4eyO4FmpXkDv9dfTk5pt3A,699
|
|
149
149
|
piccolo/query/base.py,sha256=G8Mwz0GcHY4Xs5Co9ubCNMI-3orfOsDdRDOnFRws7TU,15212
|
150
150
|
piccolo/query/mixins.py,sha256=1RyhORDRwTZF9m_2uEgc6sOSd2uViXivBAaFN8geq5g,21982
|
151
151
|
piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
|
152
|
-
piccolo/query/functions/__init__.py,sha256=
|
152
|
+
piccolo/query/functions/__init__.py,sha256=e-BEHlGR3JhE2efWG_rmXdURKL4Fa8tjdGmPsvH4kWo,403
|
153
153
|
piccolo/query/functions/aggregate.py,sha256=OdjDjr_zyD4S9UbrZ2C3V5mz4OT2sIfAFAdTGr4WL54,4248
|
154
154
|
piccolo/query/functions/base.py,sha256=Go2bg2r7GaVoyyX-wTb80WEQmtiU4OFYWQlq9eQ6Zcc,478
|
155
|
+
piccolo/query/functions/math.py,sha256=2Wapq0lpXZh77z0uzXUhnOfmUkbkM0xjQ4tiyuCsbiE,661
|
155
156
|
piccolo/query/functions/string.py,sha256=srxsQJFS6L4gPvFjvuAFQj7QtnCF7X6YoJNKARR2XP0,1236
|
156
157
|
piccolo/query/functions/type_conversion.py,sha256=OYbZc6TEk6b5yTwCMw2rmZ-UiQiUiWZOyxwMLzUjXwE,2583
|
157
158
|
piccolo/query/methods/__init__.py,sha256=tm4gLeV_obDqpgnouVjFbGubbaoJcqm_cbNd4LPo48Q,622
|
@@ -242,7 +243,7 @@ tests/apps/user/commands/test_change_permissions.py,sha256=uVKEiT1EKot3VA2TDETdQ
|
|
242
243
|
tests/apps/user/commands/test_create.py,sha256=iJ3Tti62rHwvdcTwNXrc5JPam6vR1qxKRdMN456vm3o,2250
|
243
244
|
tests/apps/user/commands/test_list.py,sha256=ipPfGdW6fH7q-Jc7JcYUvlioGmH9GQU0WImZGC2m-XQ,2840
|
244
245
|
tests/columns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
245
|
-
tests/columns/test_array.py,sha256=
|
246
|
+
tests/columns/test_array.py,sha256=uElfAcJhuB0m9_O1qTgURbrwE2Php5Bc7IB9hyXoQ_Q,10772
|
246
247
|
tests/columns/test_base.py,sha256=CTqCNcrqAJTjLXe3MCZgTczrmB3jcVRcOpU4FilpLoQ,3918
|
247
248
|
tests/columns/test_bigint.py,sha256=a0B4y1H02ww5qaW574X2lyenbY6o29ztOhiaqybPC0c,1149
|
248
249
|
tests/columns/test_boolean.py,sha256=kDESp6FnRtSZhuqIu0dBRwKMSpS5TFbbs3sz2MyZSs8,1720
|
@@ -253,6 +254,7 @@ tests/columns/test_date.py,sha256=lz3AF64CkQzulfniGs0fxuvbH2grR3pF2sxipXiyvHU,12
|
|
253
254
|
tests/columns/test_db_column_name.py,sha256=v0QFOQp_atqzMB1n40simVwHeBDi5nyN1N2bSPX5k6w,7670
|
254
255
|
tests/columns/test_defaults.py,sha256=rwlU1fXt3cCl7C51eLlZXqgWkE-K5W0pHvTrwkAKyCo,2896
|
255
256
|
tests/columns/test_double_precision.py,sha256=CuobfnQnuwqAIuuOPoh2mKHnY9A7gZosoMIGpY-ubfE,639
|
257
|
+
tests/columns/test_get_sql_value.py,sha256=mKgsInN374jzV99y9mg_ZiG-AvnJgz36SZi89xL7RZM,1768
|
256
258
|
tests/columns/test_interval.py,sha256=SbeRgTBWPBL5_LYQUdaP3qyN6eTNtTJtu8JXWollhQw,2993
|
257
259
|
tests/columns/test_json.py,sha256=ErGytVqMVO86YiqGmQIcqb2zUcAUY_ya-cY9tSKKXhQ,3920
|
258
260
|
tests/columns/test_jsonb.py,sha256=7MeH0h2SJt_uCUL6D5-6DLTBPlaz6qKSJcnyhuwvzHg,6914
|
@@ -296,10 +298,15 @@ tests/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
296
298
|
tests/query/test_await.py,sha256=imGazmG0l4qilveNPwsxvYQogFJtos4YB8N9iggPEFU,412
|
297
299
|
tests/query/test_camelcase.py,sha256=AcL2gZera1GfpVJNpuKuh5ZBosNCY_ezPWh6-duU5vU,1765
|
298
300
|
tests/query/test_freeze.py,sha256=p3iXqHzgV39YWlqzXtZvaDa7iKZaaaelOGX3UZ8CMf0,3887
|
299
|
-
tests/query/test_functions.py,sha256=B_M-giOf1xGvdCaYgJdKWLSoV8vtTiRQtEyvL4-7eCY,6401
|
300
301
|
tests/query/test_gather.py,sha256=okWANrBoh0Ut1RomWoffiWNpFqiITF6qti-Aa3uYtRk,730
|
301
|
-
tests/query/test_querystring.py,sha256=
|
302
|
+
tests/query/test_querystring.py,sha256=QrqyjwUlFlf5LrsJ7DgjCruq811I0UvrDFPud6rfZNI,5019
|
302
303
|
tests/query/test_slots.py,sha256=I9ZjAYqAJNSFAWg9UyAqy7bm-Z52KiyQ2C_yHk2qqqI,1010
|
304
|
+
tests/query/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
305
|
+
tests/query/functions/base.py,sha256=RLCzLT7iN_Z5DtIFZqVESTJGES2JKb8VDU25sv5OtN4,811
|
306
|
+
tests/query/functions/test_functions.py,sha256=510fqRrOrAZ9NyFoZtlF6lIdiiLriWhZ7vvveWZ8rsc,1984
|
307
|
+
tests/query/functions/test_math.py,sha256=Qw2MXqgY_y7vGd0bLtPhWW7HB3tJkot1o-Rh9nCmmBk,1273
|
308
|
+
tests/query/functions/test_string.py,sha256=7yNkpWNBaIowzXTP_qbmQg-mJZLWrTk0lx2mgY1NIfA,825
|
309
|
+
tests/query/functions/test_type_conversion.py,sha256=WeYR9UfJnbidle07-akQ1g9hFCd93qT8xUhDF3c58n4,3235
|
303
310
|
tests/query/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
304
311
|
tests/query/mixins/test_columns_delegate.py,sha256=Zw9uaqOEb7kpPQzzO9yz0jhQEeCfoPSjsy-BCLg_8XU,2032
|
305
312
|
tests/query/mixins/test_order_by_delegate.py,sha256=mOV3Gxs0XeliONxjWSOniI1z6lbZ_xTfcGYd53JLnaY,507
|
@@ -356,9 +363,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
|
|
356
363
|
tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
|
357
364
|
tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
|
358
365
|
tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
|
359
|
-
piccolo-1.
|
360
|
-
piccolo-1.
|
361
|
-
piccolo-1.
|
362
|
-
piccolo-1.
|
363
|
-
piccolo-1.
|
364
|
-
piccolo-1.
|
366
|
+
piccolo-1.9.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
|
367
|
+
piccolo-1.9.0.dist-info/METADATA,sha256=DPQmRGRjgG1-6bHmApuUOwew9PIbDzX1KOEbnyxqW20,5177
|
368
|
+
piccolo-1.9.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
|
369
|
+
piccolo-1.9.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
|
370
|
+
piccolo-1.9.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
|
371
|
+
piccolo-1.9.0.dist-info/RECORD,,
|
tests/columns/test_array.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import datetime
|
2
2
|
from unittest import TestCase
|
3
3
|
|
4
|
+
import pytest
|
5
|
+
|
4
6
|
from piccolo.columns.column_types import (
|
5
7
|
Array,
|
6
8
|
BigInt,
|
@@ -10,8 +12,9 @@ from piccolo.columns.column_types import (
|
|
10
12
|
Timestamp,
|
11
13
|
Timestamptz,
|
12
14
|
)
|
15
|
+
from piccolo.querystring import QueryString
|
13
16
|
from piccolo.table import Table
|
14
|
-
from tests.base import engines_only, sqlite_only
|
17
|
+
from tests.base import engines_only, engines_skip, sqlite_only
|
15
18
|
|
16
19
|
|
17
20
|
class MyTable(Table):
|
@@ -40,12 +43,18 @@ class TestArray(TestCase):
|
|
40
43
|
def tearDown(self):
|
41
44
|
MyTable.alter().drop_table().run_sync()
|
42
45
|
|
43
|
-
@
|
46
|
+
@pytest.mark.cockroach_array_slow
|
44
47
|
def test_storage(self):
|
45
48
|
"""
|
46
49
|
Make sure data can be stored and retrieved.
|
47
50
|
|
48
|
-
|
51
|
+
In CockroachDB <= v22.2.0 we had this error:
|
52
|
+
|
53
|
+
* https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
|
54
|
+
|
55
|
+
In newer CockroachDB versions, it runs but is very slow:
|
56
|
+
|
57
|
+
* https://github.com/piccolo-orm/piccolo/issues/1005
|
49
58
|
|
50
59
|
""" # noqa: E501
|
51
60
|
MyTable(value=[1, 2, 3]).save().run_sync()
|
@@ -54,12 +63,19 @@ class TestArray(TestCase):
|
|
54
63
|
assert row is not None
|
55
64
|
self.assertEqual(row.value, [1, 2, 3])
|
56
65
|
|
57
|
-
@
|
66
|
+
@engines_skip("sqlite")
|
67
|
+
@pytest.mark.cockroach_array_slow
|
58
68
|
def test_index(self):
|
59
69
|
"""
|
60
70
|
Indexes should allow individual array elements to be queried.
|
61
71
|
|
62
|
-
|
72
|
+
In CockroachDB <= v22.2.0 we had this error:
|
73
|
+
|
74
|
+
* https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
|
75
|
+
|
76
|
+
In newer CockroachDB versions, it runs but is very slow:
|
77
|
+
|
78
|
+
* https://github.com/piccolo-orm/piccolo/issues/1005
|
63
79
|
|
64
80
|
""" # noqa: E501
|
65
81
|
MyTable(value=[1, 2, 3]).save().run_sync()
|
@@ -68,66 +84,92 @@ class TestArray(TestCase):
|
|
68
84
|
MyTable.select(MyTable.value[0]).first().run_sync(), {"value": 1}
|
69
85
|
)
|
70
86
|
|
71
|
-
@
|
87
|
+
@engines_skip("sqlite")
|
88
|
+
@pytest.mark.cockroach_array_slow
|
72
89
|
def test_all(self):
|
73
90
|
"""
|
74
91
|
Make sure rows can be retrieved where all items in an array match a
|
75
92
|
given value.
|
76
93
|
|
77
|
-
|
94
|
+
In CockroachDB <= v22.2.0 we had this error:
|
95
|
+
|
96
|
+
* https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
|
97
|
+
|
98
|
+
In newer CockroachDB versions, it runs but is very slow:
|
99
|
+
|
100
|
+
* https://github.com/piccolo-orm/piccolo/issues/1005
|
78
101
|
|
79
102
|
""" # noqa: E501
|
80
103
|
MyTable(value=[1, 1, 1]).save().run_sync()
|
81
104
|
|
105
|
+
# We have to explicitly specify the type, so CockroachDB works.
|
82
106
|
self.assertEqual(
|
83
107
|
MyTable.select(MyTable.value)
|
84
|
-
.where(MyTable.value.all(1))
|
108
|
+
.where(MyTable.value.all(QueryString("{}::INTEGER", 1)))
|
85
109
|
.first()
|
86
110
|
.run_sync(),
|
87
111
|
{"value": [1, 1, 1]},
|
88
112
|
)
|
89
113
|
|
114
|
+
# We have to explicitly specify the type, so CockroachDB works.
|
90
115
|
self.assertEqual(
|
91
116
|
MyTable.select(MyTable.value)
|
92
|
-
.where(MyTable.value.all(0))
|
117
|
+
.where(MyTable.value.all(QueryString("{}::INTEGER", 0)))
|
93
118
|
.first()
|
94
119
|
.run_sync(),
|
95
120
|
None,
|
96
121
|
)
|
97
122
|
|
98
|
-
@
|
123
|
+
@engines_skip("sqlite")
|
124
|
+
@pytest.mark.cockroach_array_slow
|
99
125
|
def test_any(self):
|
100
126
|
"""
|
101
127
|
Make sure rows can be retrieved where any items in an array match a
|
102
128
|
given value.
|
103
129
|
|
104
|
-
|
130
|
+
In CockroachDB <= v22.2.0 we had this error:
|
131
|
+
|
132
|
+
* https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
|
133
|
+
|
134
|
+
In newer CockroachDB versions, it runs but is very slow:
|
135
|
+
|
136
|
+
* https://github.com/piccolo-orm/piccolo/issues/1005
|
105
137
|
|
106
138
|
""" # noqa: E501
|
139
|
+
|
107
140
|
MyTable(value=[1, 2, 3]).save().run_sync()
|
108
141
|
|
142
|
+
# We have to explicitly specify the type, so CockroachDB works.
|
109
143
|
self.assertEqual(
|
110
144
|
MyTable.select(MyTable.value)
|
111
|
-
.where(MyTable.value.any(1))
|
145
|
+
.where(MyTable.value.any(QueryString("{}::INTEGER", 1)))
|
112
146
|
.first()
|
113
147
|
.run_sync(),
|
114
148
|
{"value": [1, 2, 3]},
|
115
149
|
)
|
116
150
|
|
151
|
+
# We have to explicitly specify the type, so CockroachDB works.
|
117
152
|
self.assertEqual(
|
118
153
|
MyTable.select(MyTable.value)
|
119
|
-
.where(MyTable.value.any(0))
|
154
|
+
.where(MyTable.value.any(QueryString("{}::INTEGER", 0)))
|
120
155
|
.first()
|
121
156
|
.run_sync(),
|
122
157
|
None,
|
123
158
|
)
|
124
159
|
|
125
|
-
@
|
160
|
+
@engines_skip("sqlite")
|
161
|
+
@pytest.mark.cockroach_array_slow
|
126
162
|
def test_cat(self):
|
127
163
|
"""
|
128
164
|
Make sure values can be appended to an array.
|
129
165
|
|
130
|
-
|
166
|
+
In CockroachDB <= v22.2.0 we had this error:
|
167
|
+
|
168
|
+
* https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
|
169
|
+
|
170
|
+
In newer CockroachDB versions, it runs but is very slow:
|
171
|
+
|
172
|
+
* https://github.com/piccolo-orm/piccolo/issues/1005
|
131
173
|
|
132
174
|
""" # noqa: E501
|
133
175
|
MyTable(value=[1, 1, 1]).save().run_sync()
|
@@ -137,7 +179,8 @@ class TestArray(TestCase):
|
|
137
179
|
).run_sync()
|
138
180
|
|
139
181
|
self.assertEqual(
|
140
|
-
MyTable.select().run_sync(),
|
182
|
+
MyTable.select(MyTable.value).run_sync(),
|
183
|
+
[{"value": [1, 1, 1, 2]}],
|
141
184
|
)
|
142
185
|
|
143
186
|
# Try plus symbol
|
@@ -147,7 +190,8 @@ class TestArray(TestCase):
|
|
147
190
|
).run_sync()
|
148
191
|
|
149
192
|
self.assertEqual(
|
150
|
-
MyTable.select().run_sync(),
|
193
|
+
MyTable.select(MyTable.value).run_sync(),
|
194
|
+
[{"value": [1, 1, 1, 2, 3]}],
|
151
195
|
)
|
152
196
|
|
153
197
|
# Make sure non-list values work
|
@@ -157,8 +201,8 @@ class TestArray(TestCase):
|
|
157
201
|
).run_sync()
|
158
202
|
|
159
203
|
self.assertEqual(
|
160
|
-
MyTable.select().run_sync(),
|
161
|
-
[{"
|
204
|
+
MyTable.select(MyTable.value).run_sync(),
|
205
|
+
[{"value": [1, 1, 1, 2, 3, 4]}],
|
162
206
|
)
|
163
207
|
|
164
208
|
@sqlite_only
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import datetime
|
2
|
+
from unittest import TestCase
|
3
|
+
|
4
|
+
from tests.base import engines_only
|
5
|
+
from tests.example_apps.music.tables import Band
|
6
|
+
|
7
|
+
|
8
|
+
@engines_only("postgres", "cockroach")
|
9
|
+
class TestArrayPostgres(TestCase):
|
10
|
+
|
11
|
+
def test_string(self):
|
12
|
+
self.assertEqual(
|
13
|
+
Band.name.get_sql_value(["a", "b", "c"]),
|
14
|
+
'\'{"a","b","c"}\'',
|
15
|
+
)
|
16
|
+
|
17
|
+
def test_int(self):
|
18
|
+
self.assertEqual(
|
19
|
+
Band.name.get_sql_value([1, 2, 3]),
|
20
|
+
"'{1,2,3}'",
|
21
|
+
)
|
22
|
+
|
23
|
+
def test_nested(self):
|
24
|
+
self.assertEqual(
|
25
|
+
Band.name.get_sql_value([1, 2, 3, [4, 5, 6]]),
|
26
|
+
"'{1,2,3,{4,5,6}}'",
|
27
|
+
)
|
28
|
+
|
29
|
+
def test_time(self):
|
30
|
+
self.assertEqual(
|
31
|
+
Band.name.get_sql_value([datetime.time(hour=8, minute=0)]),
|
32
|
+
"'{\"08:00:00\"}'",
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
@engines_only("sqlite")
|
37
|
+
class TestArraySQLite(TestCase):
|
38
|
+
"""
|
39
|
+
Note, we use ``.replace(" ", "")`` because we serialise arrays using
|
40
|
+
Python's json library, and there is inconsistency between Python versions
|
41
|
+
(some output ``["a", "b", "c"]``, and others ``["a","b","c"]``).
|
42
|
+
"""
|
43
|
+
|
44
|
+
def test_string(self):
|
45
|
+
self.assertEqual(
|
46
|
+
Band.name.get_sql_value(["a", "b", "c"]).replace(" ", ""),
|
47
|
+
'\'["a","b","c"]\'',
|
48
|
+
)
|
49
|
+
|
50
|
+
def test_int(self):
|
51
|
+
self.assertEqual(
|
52
|
+
Band.name.get_sql_value([1, 2, 3]).replace(" ", ""),
|
53
|
+
"'[1,2,3]'",
|
54
|
+
)
|
55
|
+
|
56
|
+
def test_nested(self):
|
57
|
+
self.assertEqual(
|
58
|
+
Band.name.get_sql_value([1, 2, 3, [4, 5, 6]]).replace(" ", ""),
|
59
|
+
"'[1,2,3,[4,5,6]]'",
|
60
|
+
)
|
61
|
+
|
62
|
+
def test_time(self):
|
63
|
+
self.assertEqual(
|
64
|
+
Band.name.get_sql_value([datetime.time(hour=8, minute=0)]),
|
65
|
+
"'[\"08:00:00\"]'",
|
66
|
+
)
|
File without changes
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import typing as t
|
2
|
+
from unittest import TestCase
|
3
|
+
|
4
|
+
from piccolo.table import Table, create_db_tables_sync, drop_db_tables_sync
|
5
|
+
from tests.example_apps.music.tables import Band, Manager
|
6
|
+
|
7
|
+
|
8
|
+
class FunctionTest(TestCase):
|
9
|
+
tables: t.List[t.Type[Table]]
|
10
|
+
|
11
|
+
def setUp(self) -> None:
|
12
|
+
create_db_tables_sync(*self.tables)
|
13
|
+
|
14
|
+
def tearDown(self) -> None:
|
15
|
+
drop_db_tables_sync(*self.tables)
|
16
|
+
|
17
|
+
|
18
|
+
class BandTest(FunctionTest):
|
19
|
+
tables = [Band, Manager]
|
20
|
+
|
21
|
+
def setUp(self) -> None:
|
22
|
+
super().setUp()
|
23
|
+
|
24
|
+
manager = Manager({Manager.name: "Guido"})
|
25
|
+
manager.save().run_sync()
|
26
|
+
|
27
|
+
band = Band(
|
28
|
+
{
|
29
|
+
Band.name: "Pythonistas",
|
30
|
+
Band.manager: manager,
|
31
|
+
Band.popularity: 1000,
|
32
|
+
}
|
33
|
+
)
|
34
|
+
band.save().run_sync()
|
@@ -0,0 +1,64 @@
|
|
1
|
+
from piccolo.query.functions import Reverse, Upper
|
2
|
+
from piccolo.querystring import QueryString
|
3
|
+
from tests.base import engines_skip
|
4
|
+
from tests.example_apps.music.tables import Band
|
5
|
+
|
6
|
+
from .base import BandTest
|
7
|
+
|
8
|
+
|
9
|
+
@engines_skip("sqlite")
|
10
|
+
class TestNested(BandTest):
|
11
|
+
"""
|
12
|
+
Skip the the test for SQLite, as it doesn't support ``Reverse``.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def test_nested(self):
|
16
|
+
"""
|
17
|
+
Make sure we can nest functions.
|
18
|
+
"""
|
19
|
+
response = Band.select(Upper(Reverse(Band.name))).run_sync()
|
20
|
+
self.assertListEqual(response, [{"upper": "SATSINOHTYP"}])
|
21
|
+
|
22
|
+
def test_nested_with_joined_column(self):
|
23
|
+
"""
|
24
|
+
Make sure nested functions can be used on a column from a joined table.
|
25
|
+
"""
|
26
|
+
response = Band.select(Upper(Reverse(Band.manager._.name))).run_sync()
|
27
|
+
self.assertListEqual(response, [{"upper": "ODIUG"}])
|
28
|
+
|
29
|
+
def test_nested_within_querystring(self):
|
30
|
+
"""
|
31
|
+
If we wrap a function in a custom QueryString - make sure the columns
|
32
|
+
are still accessible, so joins are successful.
|
33
|
+
"""
|
34
|
+
response = Band.select(
|
35
|
+
QueryString("CONCAT({}, '!')", Upper(Band.manager._.name)),
|
36
|
+
).run_sync()
|
37
|
+
|
38
|
+
self.assertListEqual(response, [{"concat": "GUIDO!"}])
|
39
|
+
|
40
|
+
|
41
|
+
class TestWhereClause(BandTest):
|
42
|
+
|
43
|
+
def test_where(self):
|
44
|
+
"""
|
45
|
+
Make sure where clauses work with functions.
|
46
|
+
"""
|
47
|
+
response = (
|
48
|
+
Band.select(Band.name)
|
49
|
+
.where(Upper(Band.name) == "PYTHONISTAS")
|
50
|
+
.run_sync()
|
51
|
+
)
|
52
|
+
self.assertListEqual(response, [{"name": "Pythonistas"}])
|
53
|
+
|
54
|
+
def test_where_with_joined_column(self):
|
55
|
+
"""
|
56
|
+
Make sure where clauses work with functions, when a joined column is
|
57
|
+
used.
|
58
|
+
"""
|
59
|
+
response = (
|
60
|
+
Band.select(Band.name)
|
61
|
+
.where(Upper(Band.manager._.name) == "GUIDO")
|
62
|
+
.run_sync()
|
63
|
+
)
|
64
|
+
self.assertListEqual(response, [{"name": "Pythonistas"}])
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import decimal
|
2
|
+
|
3
|
+
from piccolo.columns import Numeric
|
4
|
+
from piccolo.query.functions.math import Abs, Ceil, Floor, Round
|
5
|
+
from piccolo.table import Table
|
6
|
+
|
7
|
+
from .base import FunctionTest
|
8
|
+
|
9
|
+
|
10
|
+
class Ticket(Table):
|
11
|
+
price = Numeric(digits=(5, 2))
|
12
|
+
|
13
|
+
|
14
|
+
class TestMath(FunctionTest):
|
15
|
+
|
16
|
+
tables = [Ticket]
|
17
|
+
|
18
|
+
def setUp(self):
|
19
|
+
super().setUp()
|
20
|
+
self.ticket = Ticket({Ticket.price: decimal.Decimal("36.50")})
|
21
|
+
self.ticket.save().run_sync()
|
22
|
+
|
23
|
+
def test_floor(self):
|
24
|
+
response = Ticket.select(Floor(Ticket.price, alias="price")).run_sync()
|
25
|
+
self.assertListEqual(response, [{"price": decimal.Decimal("36.00")}])
|
26
|
+
|
27
|
+
def test_ceil(self):
|
28
|
+
response = Ticket.select(Ceil(Ticket.price, alias="price")).run_sync()
|
29
|
+
self.assertListEqual(response, [{"price": decimal.Decimal("37.00")}])
|
30
|
+
|
31
|
+
def test_abs(self):
|
32
|
+
self.ticket.price = decimal.Decimal("-1.50")
|
33
|
+
self.ticket.save().run_sync()
|
34
|
+
response = Ticket.select(Abs(Ticket.price, alias="price")).run_sync()
|
35
|
+
self.assertListEqual(response, [{"price": decimal.Decimal("1.50")}])
|
36
|
+
|
37
|
+
def test_round(self):
|
38
|
+
response = Ticket.select(Round(Ticket.price, alias="price")).run_sync()
|
39
|
+
self.assertListEqual(response, [{"price": decimal.Decimal("37.00")}])
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from piccolo.query.functions.string import Upper
|
2
|
+
from tests.example_apps.music.tables import Band
|
3
|
+
|
4
|
+
from .base import BandTest
|
5
|
+
|
6
|
+
|
7
|
+
class TestUpperFunction(BandTest):
|
8
|
+
|
9
|
+
def test_column(self):
|
10
|
+
"""
|
11
|
+
Make sure we can uppercase a column's value.
|
12
|
+
"""
|
13
|
+
response = Band.select(Upper(Band.name)).run_sync()
|
14
|
+
self.assertListEqual(response, [{"upper": "PYTHONISTAS"}])
|
15
|
+
|
16
|
+
def test_alias(self):
|
17
|
+
response = Band.select(Upper(Band.name, alias="name")).run_sync()
|
18
|
+
self.assertListEqual(response, [{"name": "PYTHONISTAS"}])
|
19
|
+
|
20
|
+
def test_joined_column(self):
|
21
|
+
"""
|
22
|
+
Make sure we can uppercase a column's value from a joined table.
|
23
|
+
"""
|
24
|
+
response = Band.select(Upper(Band.manager._.name)).run_sync()
|
25
|
+
self.assertListEqual(response, [{"upper": "GUIDO"}])
|
@@ -0,0 +1,134 @@
|
|
1
|
+
from piccolo.columns import Integer, Text, Varchar
|
2
|
+
from piccolo.query.functions import Cast, Length
|
3
|
+
from tests.example_apps.music.tables import Band, Manager
|
4
|
+
|
5
|
+
from .base import BandTest
|
6
|
+
|
7
|
+
|
8
|
+
class TestCast(BandTest):
|
9
|
+
def test_varchar(self):
|
10
|
+
"""
|
11
|
+
Make sure that casting to ``Varchar`` works.
|
12
|
+
"""
|
13
|
+
response = Band.select(
|
14
|
+
Cast(
|
15
|
+
Band.popularity,
|
16
|
+
as_type=Varchar(),
|
17
|
+
)
|
18
|
+
).run_sync()
|
19
|
+
|
20
|
+
self.assertListEqual(
|
21
|
+
response,
|
22
|
+
[{"popularity": "1000"}],
|
23
|
+
)
|
24
|
+
|
25
|
+
def test_text(self):
|
26
|
+
"""
|
27
|
+
Make sure that casting to ``Text`` works.
|
28
|
+
"""
|
29
|
+
response = Band.select(
|
30
|
+
Cast(
|
31
|
+
Band.popularity,
|
32
|
+
as_type=Text(),
|
33
|
+
)
|
34
|
+
).run_sync()
|
35
|
+
|
36
|
+
self.assertListEqual(
|
37
|
+
response,
|
38
|
+
[{"popularity": "1000"}],
|
39
|
+
)
|
40
|
+
|
41
|
+
def test_integer(self):
|
42
|
+
"""
|
43
|
+
Make sure that casting to ``Integer`` works.
|
44
|
+
"""
|
45
|
+
Band.update({Band.name: "1111"}, force=True).run_sync()
|
46
|
+
|
47
|
+
response = Band.select(
|
48
|
+
Cast(
|
49
|
+
Band.name,
|
50
|
+
as_type=Integer(),
|
51
|
+
)
|
52
|
+
).run_sync()
|
53
|
+
|
54
|
+
self.assertListEqual(
|
55
|
+
response,
|
56
|
+
[{"name": 1111}],
|
57
|
+
)
|
58
|
+
|
59
|
+
def test_join(self):
|
60
|
+
"""
|
61
|
+
Make sure that casting works with joins.
|
62
|
+
"""
|
63
|
+
Manager.update({Manager.name: "1111"}, force=True).run_sync()
|
64
|
+
|
65
|
+
response = Band.select(
|
66
|
+
Band.name,
|
67
|
+
Cast(
|
68
|
+
Band.manager.name,
|
69
|
+
as_type=Integer(),
|
70
|
+
),
|
71
|
+
).run_sync()
|
72
|
+
|
73
|
+
self.assertListEqual(
|
74
|
+
response,
|
75
|
+
[
|
76
|
+
{
|
77
|
+
"name": "Pythonistas",
|
78
|
+
"manager.name": 1111,
|
79
|
+
}
|
80
|
+
],
|
81
|
+
)
|
82
|
+
|
83
|
+
def test_nested_inner(self):
|
84
|
+
"""
|
85
|
+
Make sure ``Cast`` can be passed into other functions.
|
86
|
+
"""
|
87
|
+
Band.update({Band.name: "1111"}, force=True).run_sync()
|
88
|
+
|
89
|
+
response = Band.select(
|
90
|
+
Length(
|
91
|
+
Cast(
|
92
|
+
Band.popularity,
|
93
|
+
as_type=Varchar(),
|
94
|
+
)
|
95
|
+
)
|
96
|
+
).run_sync()
|
97
|
+
|
98
|
+
self.assertListEqual(
|
99
|
+
response,
|
100
|
+
[{"length": 4}],
|
101
|
+
)
|
102
|
+
|
103
|
+
def test_nested_outer(self):
|
104
|
+
"""
|
105
|
+
Make sure a querystring can be passed into ``Cast`` (meaning it can be
|
106
|
+
nested).
|
107
|
+
"""
|
108
|
+
response = Band.select(
|
109
|
+
Cast(
|
110
|
+
Length(Band.name),
|
111
|
+
as_type=Varchar(),
|
112
|
+
alias="length",
|
113
|
+
)
|
114
|
+
).run_sync()
|
115
|
+
|
116
|
+
self.assertListEqual(
|
117
|
+
response,
|
118
|
+
[{"length": str(len("Pythonistas"))}],
|
119
|
+
)
|
120
|
+
|
121
|
+
def test_where_clause(self):
|
122
|
+
"""
|
123
|
+
Make sure ``Cast`` works in a where clause.
|
124
|
+
"""
|
125
|
+
response = (
|
126
|
+
Band.select(Band.name, Band.popularity)
|
127
|
+
.where(Cast(Band.popularity, Varchar()) == "1000")
|
128
|
+
.run_sync()
|
129
|
+
)
|
130
|
+
|
131
|
+
self.assertListEqual(
|
132
|
+
response,
|
133
|
+
[{"name": "Pythonistas", "popularity": 1000}],
|
134
|
+
)
|
tests/query/test_querystring.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from unittest import TestCase
|
2
2
|
|
3
3
|
from piccolo.querystring import QueryString
|
4
|
+
from tests.base import postgres_only
|
4
5
|
|
5
6
|
|
6
7
|
# TODO - add more extensive tests (increased nesting and argument count).
|
@@ -28,3 +29,138 @@ class TestQueryString(TestCase):
|
|
28
29
|
def test_querystring_with_no_args(self):
|
29
30
|
qs = QueryString("SELECT name FROM band")
|
30
31
|
self.assertEqual(qs.compile_string(), ("SELECT name FROM band", []))
|
32
|
+
|
33
|
+
|
34
|
+
@postgres_only
|
35
|
+
class TestQueryStringOperators(TestCase):
|
36
|
+
"""
|
37
|
+
Make sure basic operations can be used on ``QueryString``.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def test_add(self):
|
41
|
+
query = QueryString("SELECT price") + 1
|
42
|
+
self.assertIsInstance(query, QueryString)
|
43
|
+
self.assertEqual(
|
44
|
+
query.compile_string(),
|
45
|
+
("SELECT price + $1", [1]),
|
46
|
+
)
|
47
|
+
|
48
|
+
def test_multiply(self):
|
49
|
+
query = QueryString("SELECT price") * 2
|
50
|
+
self.assertIsInstance(query, QueryString)
|
51
|
+
self.assertEqual(
|
52
|
+
query.compile_string(),
|
53
|
+
("SELECT price * $1", [2]),
|
54
|
+
)
|
55
|
+
|
56
|
+
def test_divide(self):
|
57
|
+
query = QueryString("SELECT price") / 1
|
58
|
+
self.assertIsInstance(query, QueryString)
|
59
|
+
self.assertEqual(
|
60
|
+
query.compile_string(),
|
61
|
+
("SELECT price / $1", [1]),
|
62
|
+
)
|
63
|
+
|
64
|
+
def test_power(self):
|
65
|
+
query = QueryString("SELECT price") ** 2
|
66
|
+
self.assertIsInstance(query, QueryString)
|
67
|
+
self.assertEqual(
|
68
|
+
query.compile_string(),
|
69
|
+
("SELECT price ^ $1", [2]),
|
70
|
+
)
|
71
|
+
|
72
|
+
def test_subtract(self):
|
73
|
+
query = QueryString("SELECT price") - 1
|
74
|
+
self.assertIsInstance(query, QueryString)
|
75
|
+
self.assertEqual(
|
76
|
+
query.compile_string(),
|
77
|
+
("SELECT price - $1", [1]),
|
78
|
+
)
|
79
|
+
|
80
|
+
def test_modulus(self):
|
81
|
+
query = QueryString("SELECT price") % 1
|
82
|
+
self.assertIsInstance(query, QueryString)
|
83
|
+
self.assertEqual(
|
84
|
+
query.compile_string(),
|
85
|
+
("SELECT price % $1", [1]),
|
86
|
+
)
|
87
|
+
|
88
|
+
def test_like(self):
|
89
|
+
query = QueryString("strip(name)").like("Python%")
|
90
|
+
self.assertIsInstance(query, QueryString)
|
91
|
+
self.assertEqual(
|
92
|
+
query.compile_string(),
|
93
|
+
("strip(name) LIKE $1", ["Python%"]),
|
94
|
+
)
|
95
|
+
|
96
|
+
def test_ilike(self):
|
97
|
+
query = QueryString("strip(name)").ilike("Python%")
|
98
|
+
self.assertIsInstance(query, QueryString)
|
99
|
+
self.assertEqual(
|
100
|
+
query.compile_string(),
|
101
|
+
("strip(name) ILIKE $1", ["Python%"]),
|
102
|
+
)
|
103
|
+
|
104
|
+
def test_greater_than(self):
|
105
|
+
query = QueryString("SELECT price") > 10
|
106
|
+
self.assertIsInstance(query, QueryString)
|
107
|
+
self.assertEqual(
|
108
|
+
query.compile_string(),
|
109
|
+
("SELECT price > $1", [10]),
|
110
|
+
)
|
111
|
+
|
112
|
+
def test_greater_equal_than(self):
|
113
|
+
query = QueryString("SELECT price") >= 10
|
114
|
+
self.assertIsInstance(query, QueryString)
|
115
|
+
self.assertEqual(
|
116
|
+
query.compile_string(),
|
117
|
+
("SELECT price >= $1", [10]),
|
118
|
+
)
|
119
|
+
|
120
|
+
def test_less_than(self):
|
121
|
+
query = QueryString("SELECT price") < 10
|
122
|
+
self.assertIsInstance(query, QueryString)
|
123
|
+
self.assertEqual(
|
124
|
+
query.compile_string(),
|
125
|
+
("SELECT price < $1", [10]),
|
126
|
+
)
|
127
|
+
|
128
|
+
def test_less_equal_than(self):
|
129
|
+
query = QueryString("SELECT price") <= 10
|
130
|
+
self.assertIsInstance(query, QueryString)
|
131
|
+
self.assertEqual(
|
132
|
+
query.compile_string(),
|
133
|
+
("SELECT price <= $1", [10]),
|
134
|
+
)
|
135
|
+
|
136
|
+
def test_equals(self):
|
137
|
+
query = QueryString("SELECT price") == 10
|
138
|
+
self.assertIsInstance(query, QueryString)
|
139
|
+
self.assertEqual(
|
140
|
+
query.compile_string(),
|
141
|
+
("SELECT price = $1", [10]),
|
142
|
+
)
|
143
|
+
|
144
|
+
def test_not_equals(self):
|
145
|
+
query = QueryString("SELECT price") != 10
|
146
|
+
self.assertIsInstance(query, QueryString)
|
147
|
+
self.assertEqual(
|
148
|
+
query.compile_string(),
|
149
|
+
("SELECT price != $1", [10]),
|
150
|
+
)
|
151
|
+
|
152
|
+
def test_is_in(self):
|
153
|
+
query = QueryString("SELECT price").is_in([10, 20, 30])
|
154
|
+
self.assertIsInstance(query, QueryString)
|
155
|
+
self.assertEqual(
|
156
|
+
query.compile_string(),
|
157
|
+
("SELECT price IN $1", [[10, 20, 30]]),
|
158
|
+
)
|
159
|
+
|
160
|
+
def test_not_in(self):
|
161
|
+
query = QueryString("SELECT price").not_in([10, 20, 30])
|
162
|
+
self.assertIsInstance(query, QueryString)
|
163
|
+
self.assertEqual(
|
164
|
+
query.compile_string(),
|
165
|
+
("SELECT price NOT IN $1", [[10, 20, 30]]),
|
166
|
+
)
|
tests/query/test_functions.py
DELETED
@@ -1,238 +0,0 @@
|
|
1
|
-
from unittest import TestCase
|
2
|
-
|
3
|
-
from piccolo.columns import Integer, Text, Varchar
|
4
|
-
from piccolo.query.functions import Cast, Length, Reverse, Upper
|
5
|
-
from piccolo.querystring import QueryString
|
6
|
-
from piccolo.table import create_db_tables_sync, drop_db_tables_sync
|
7
|
-
from tests.base import engines_skip
|
8
|
-
from tests.example_apps.music.tables import Band, Manager
|
9
|
-
|
10
|
-
|
11
|
-
class FunctionTest(TestCase):
|
12
|
-
tables = (Band, Manager)
|
13
|
-
|
14
|
-
def setUp(self) -> None:
|
15
|
-
create_db_tables_sync(*self.tables)
|
16
|
-
|
17
|
-
manager = Manager({Manager.name: "Guido"})
|
18
|
-
manager.save().run_sync()
|
19
|
-
|
20
|
-
band = Band(
|
21
|
-
{
|
22
|
-
Band.name: "Pythonistas",
|
23
|
-
Band.manager: manager,
|
24
|
-
Band.popularity: 1000,
|
25
|
-
}
|
26
|
-
)
|
27
|
-
band.save().run_sync()
|
28
|
-
|
29
|
-
def tearDown(self) -> None:
|
30
|
-
drop_db_tables_sync(*self.tables)
|
31
|
-
|
32
|
-
|
33
|
-
class TestUpperFunction(FunctionTest):
|
34
|
-
|
35
|
-
def test_column(self):
|
36
|
-
"""
|
37
|
-
Make sure we can uppercase a column's value.
|
38
|
-
"""
|
39
|
-
response = Band.select(Upper(Band.name)).run_sync()
|
40
|
-
self.assertListEqual(response, [{"upper": "PYTHONISTAS"}])
|
41
|
-
|
42
|
-
def test_alias(self):
|
43
|
-
response = Band.select(Upper(Band.name, alias="name")).run_sync()
|
44
|
-
self.assertListEqual(response, [{"name": "PYTHONISTAS"}])
|
45
|
-
|
46
|
-
def test_joined_column(self):
|
47
|
-
"""
|
48
|
-
Make sure we can uppercase a column's value from a joined table.
|
49
|
-
"""
|
50
|
-
response = Band.select(Upper(Band.manager._.name)).run_sync()
|
51
|
-
self.assertListEqual(response, [{"upper": "GUIDO"}])
|
52
|
-
|
53
|
-
|
54
|
-
@engines_skip("sqlite")
|
55
|
-
class TestNested(FunctionTest):
|
56
|
-
"""
|
57
|
-
Skip the the test for SQLite, as it doesn't support ``Reverse``.
|
58
|
-
"""
|
59
|
-
|
60
|
-
def test_nested(self):
|
61
|
-
"""
|
62
|
-
Make sure we can nest functions.
|
63
|
-
"""
|
64
|
-
response = Band.select(Upper(Reverse(Band.name))).run_sync()
|
65
|
-
self.assertListEqual(response, [{"upper": "SATSINOHTYP"}])
|
66
|
-
|
67
|
-
def test_nested_with_joined_column(self):
|
68
|
-
"""
|
69
|
-
Make sure nested functions can be used on a column from a joined table.
|
70
|
-
"""
|
71
|
-
response = Band.select(Upper(Reverse(Band.manager._.name))).run_sync()
|
72
|
-
self.assertListEqual(response, [{"upper": "ODIUG"}])
|
73
|
-
|
74
|
-
def test_nested_within_querystring(self):
|
75
|
-
"""
|
76
|
-
If we wrap a function in a custom QueryString - make sure the columns
|
77
|
-
are still accessible, so joins are successful.
|
78
|
-
"""
|
79
|
-
response = Band.select(
|
80
|
-
QueryString("CONCAT({}, '!')", Upper(Band.manager._.name)),
|
81
|
-
).run_sync()
|
82
|
-
|
83
|
-
self.assertListEqual(response, [{"concat": "GUIDO!"}])
|
84
|
-
|
85
|
-
|
86
|
-
class TestWhereClause(FunctionTest):
|
87
|
-
|
88
|
-
def test_where(self):
|
89
|
-
"""
|
90
|
-
Make sure where clauses work with functions.
|
91
|
-
"""
|
92
|
-
response = (
|
93
|
-
Band.select(Band.name)
|
94
|
-
.where(Upper(Band.name) == "PYTHONISTAS")
|
95
|
-
.run_sync()
|
96
|
-
)
|
97
|
-
self.assertListEqual(response, [{"name": "Pythonistas"}])
|
98
|
-
|
99
|
-
def test_where_with_joined_column(self):
|
100
|
-
"""
|
101
|
-
Make sure where clauses work with functions, when a joined column is
|
102
|
-
used.
|
103
|
-
"""
|
104
|
-
response = (
|
105
|
-
Band.select(Band.name)
|
106
|
-
.where(Upper(Band.manager._.name) == "GUIDO")
|
107
|
-
.run_sync()
|
108
|
-
)
|
109
|
-
self.assertListEqual(response, [{"name": "Pythonistas"}])
|
110
|
-
|
111
|
-
|
112
|
-
class TestCast(FunctionTest):
|
113
|
-
def test_varchar(self):
|
114
|
-
"""
|
115
|
-
Make sure that casting to ``Varchar`` works.
|
116
|
-
"""
|
117
|
-
response = Band.select(
|
118
|
-
Cast(
|
119
|
-
Band.popularity,
|
120
|
-
as_type=Varchar(),
|
121
|
-
)
|
122
|
-
).run_sync()
|
123
|
-
|
124
|
-
self.assertListEqual(
|
125
|
-
response,
|
126
|
-
[{"popularity": "1000"}],
|
127
|
-
)
|
128
|
-
|
129
|
-
def test_text(self):
|
130
|
-
"""
|
131
|
-
Make sure that casting to ``Text`` works.
|
132
|
-
"""
|
133
|
-
response = Band.select(
|
134
|
-
Cast(
|
135
|
-
Band.popularity,
|
136
|
-
as_type=Text(),
|
137
|
-
)
|
138
|
-
).run_sync()
|
139
|
-
|
140
|
-
self.assertListEqual(
|
141
|
-
response,
|
142
|
-
[{"popularity": "1000"}],
|
143
|
-
)
|
144
|
-
|
145
|
-
def test_integer(self):
|
146
|
-
"""
|
147
|
-
Make sure that casting to ``Integer`` works.
|
148
|
-
"""
|
149
|
-
Band.update({Band.name: "1111"}, force=True).run_sync()
|
150
|
-
|
151
|
-
response = Band.select(
|
152
|
-
Cast(
|
153
|
-
Band.name,
|
154
|
-
as_type=Integer(),
|
155
|
-
)
|
156
|
-
).run_sync()
|
157
|
-
|
158
|
-
self.assertListEqual(
|
159
|
-
response,
|
160
|
-
[{"name": 1111}],
|
161
|
-
)
|
162
|
-
|
163
|
-
def test_join(self):
|
164
|
-
"""
|
165
|
-
Make sure that casting works with joins.
|
166
|
-
"""
|
167
|
-
Manager.update({Manager.name: "1111"}, force=True).run_sync()
|
168
|
-
|
169
|
-
response = Band.select(
|
170
|
-
Band.name,
|
171
|
-
Cast(
|
172
|
-
Band.manager.name,
|
173
|
-
as_type=Integer(),
|
174
|
-
),
|
175
|
-
).run_sync()
|
176
|
-
|
177
|
-
self.assertListEqual(
|
178
|
-
response,
|
179
|
-
[
|
180
|
-
{
|
181
|
-
"name": "Pythonistas",
|
182
|
-
"manager.name": 1111,
|
183
|
-
}
|
184
|
-
],
|
185
|
-
)
|
186
|
-
|
187
|
-
def test_nested_inner(self):
|
188
|
-
"""
|
189
|
-
Make sure ``Cast`` can be passed into other functions.
|
190
|
-
"""
|
191
|
-
Band.update({Band.name: "1111"}, force=True).run_sync()
|
192
|
-
|
193
|
-
response = Band.select(
|
194
|
-
Length(
|
195
|
-
Cast(
|
196
|
-
Band.popularity,
|
197
|
-
as_type=Varchar(),
|
198
|
-
)
|
199
|
-
)
|
200
|
-
).run_sync()
|
201
|
-
|
202
|
-
self.assertListEqual(
|
203
|
-
response,
|
204
|
-
[{"length": 4}],
|
205
|
-
)
|
206
|
-
|
207
|
-
def test_nested_outer(self):
|
208
|
-
"""
|
209
|
-
Make sure a querystring can be passed into ``Cast`` (meaning it can be
|
210
|
-
nested).
|
211
|
-
"""
|
212
|
-
response = Band.select(
|
213
|
-
Cast(
|
214
|
-
Length(Band.name),
|
215
|
-
as_type=Varchar(),
|
216
|
-
alias="length",
|
217
|
-
)
|
218
|
-
).run_sync()
|
219
|
-
|
220
|
-
self.assertListEqual(
|
221
|
-
response,
|
222
|
-
[{"length": str(len("Pythonistas"))}],
|
223
|
-
)
|
224
|
-
|
225
|
-
def test_where_clause(self):
|
226
|
-
"""
|
227
|
-
Make sure ``Cast`` works in a where clause.
|
228
|
-
"""
|
229
|
-
response = (
|
230
|
-
Band.select(Band.name, Band.popularity)
|
231
|
-
.where(Cast(Band.popularity, Varchar()) == "1000")
|
232
|
-
.run_sync()
|
233
|
-
)
|
234
|
-
|
235
|
-
self.assertListEqual(
|
236
|
-
response,
|
237
|
-
[{"name": "Pythonistas", "popularity": 1000}],
|
238
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|