piccolo 1.5.2__py3-none-any.whl → 1.6.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 +31 -40
- piccolo/columns/column_types.py +11 -8
- piccolo/columns/m2m.py +16 -6
- piccolo/columns/readable.py +9 -7
- piccolo/query/__init__.py +1 -4
- piccolo/query/functions/__init__.py +16 -0
- piccolo/query/functions/aggregate.py +179 -0
- piccolo/query/functions/base.py +21 -0
- piccolo/query/functions/string.py +73 -0
- piccolo/query/methods/__init__.py +18 -1
- piccolo/query/methods/count.py +3 -3
- piccolo/query/methods/delete.py +1 -1
- piccolo/query/methods/exists.py +1 -1
- piccolo/query/methods/objects.py +1 -1
- piccolo/query/methods/select.py +17 -232
- piccolo/query/methods/update.py +1 -1
- piccolo/query/mixins.py +9 -2
- piccolo/querystring.py +101 -13
- piccolo/table.py +8 -24
- {piccolo-1.5.2.dist-info → piccolo-1.6.0.dist-info}/METADATA +1 -1
- {piccolo-1.5.2.dist-info → piccolo-1.6.0.dist-info}/RECORD +28 -23
- tests/query/test_functions.py +102 -0
- tests/table/test_select.py +2 -9
- {piccolo-1.5.2.dist-info → piccolo-1.6.0.dist-info}/LICENSE +0 -0
- {piccolo-1.5.2.dist-info → piccolo-1.6.0.dist-info}/WHEEL +0 -0
- {piccolo-1.5.2.dist-info → piccolo-1.6.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.5.2.dist-info → piccolo-1.6.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,23 @@ from .insert import Insert
|
|
9
9
|
from .objects import Objects
|
10
10
|
from .raw import Raw
|
11
11
|
from .refresh import Refresh
|
12
|
-
from .select import
|
12
|
+
from .select import Select
|
13
13
|
from .table_exists import TableExists
|
14
14
|
from .update import Update
|
15
|
+
|
16
|
+
__all__ = (
|
17
|
+
"Alter",
|
18
|
+
"Count",
|
19
|
+
"Create",
|
20
|
+
"CreateIndex",
|
21
|
+
"Delete",
|
22
|
+
"DropIndex",
|
23
|
+
"Exists",
|
24
|
+
"Insert",
|
25
|
+
"Objects",
|
26
|
+
"Raw",
|
27
|
+
"Refresh",
|
28
|
+
"Select",
|
29
|
+
"TableExists",
|
30
|
+
"Update",
|
31
|
+
)
|
piccolo/query/methods/count.py
CHANGED
@@ -4,7 +4,7 @@ import typing as t
|
|
4
4
|
|
5
5
|
from piccolo.custom_types import Combinable
|
6
6
|
from piccolo.query.base import Query
|
7
|
-
from piccolo.query.
|
7
|
+
from piccolo.query.functions.aggregate import Count as CountFunction
|
8
8
|
from piccolo.query.mixins import WhereDelegate
|
9
9
|
from piccolo.querystring import QueryString
|
10
10
|
|
@@ -32,7 +32,7 @@ class Count(Query):
|
|
32
32
|
###########################################################################
|
33
33
|
# Clauses
|
34
34
|
|
35
|
-
def where(self: Self, *where: Combinable) -> Self:
|
35
|
+
def where(self: Self, *where: t.Union[Combinable, QueryString]) -> Self:
|
36
36
|
self.where_delegate.where(*where)
|
37
37
|
return self
|
38
38
|
|
@@ -50,7 +50,7 @@ class Count(Query):
|
|
50
50
|
table: t.Type[Table] = self.table
|
51
51
|
|
52
52
|
query = table.select(
|
53
|
-
|
53
|
+
CountFunction(column=self.column, distinct=self._distinct)
|
54
54
|
)
|
55
55
|
|
56
56
|
query.where_delegate._where = self.where_delegate._where
|
piccolo/query/methods/delete.py
CHANGED
@@ -30,7 +30,7 @@ class Delete(Query):
|
|
30
30
|
self.returning_delegate = ReturningDelegate()
|
31
31
|
self.where_delegate = WhereDelegate()
|
32
32
|
|
33
|
-
def where(self: Self, *where: Combinable) -> Self:
|
33
|
+
def where(self: Self, *where: t.Union[Combinable, QueryString]) -> Self:
|
34
34
|
self.where_delegate.where(*where)
|
35
35
|
return self
|
36
36
|
|
piccolo/query/methods/exists.py
CHANGED
@@ -16,7 +16,7 @@ class Exists(Query[TableInstance, bool]):
|
|
16
16
|
super().__init__(table, **kwargs)
|
17
17
|
self.where_delegate = WhereDelegate()
|
18
18
|
|
19
|
-
def where(self: Self, *where: Combinable) -> Self:
|
19
|
+
def where(self: Self, *where: t.Union[Combinable, QueryString]) -> Self:
|
20
20
|
self.where_delegate.where(*where)
|
21
21
|
return self
|
22
22
|
|
piccolo/query/methods/objects.py
CHANGED
@@ -262,7 +262,7 @@ class Objects(
|
|
262
262
|
self.order_by_delegate.order_by(*_columns, ascending=ascending)
|
263
263
|
return self
|
264
264
|
|
265
|
-
def where(self: Self, *where: Combinable) -> Self:
|
265
|
+
def where(self: Self, *where: t.Union[Combinable, QueryString]) -> Self:
|
266
266
|
self.where_delegate.where(*where)
|
267
267
|
return self
|
268
268
|
|
piccolo/query/methods/select.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import decimal
|
4
3
|
import itertools
|
5
4
|
import typing as t
|
6
5
|
from collections import OrderedDict
|
@@ -36,9 +35,8 @@ if t.TYPE_CHECKING: # pragma: no cover
|
|
36
35
|
from piccolo.custom_types import Combinable
|
37
36
|
from piccolo.table import Table # noqa
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
return column.value_type in (int, decimal.Decimal, float)
|
38
|
+
# Here to avoid breaking changes - will be removed in the future.
|
39
|
+
from piccolo.query.functions.aggregate import Count # noqa: F401
|
42
40
|
|
43
41
|
|
44
42
|
class SelectRaw(Selectable):
|
@@ -59,224 +57,8 @@ class SelectRaw(Selectable):
|
|
59
57
|
|
60
58
|
def get_select_string(
|
61
59
|
self, engine_type: str, with_alias: bool = True
|
62
|
-
) ->
|
63
|
-
return self.querystring
|
64
|
-
|
65
|
-
|
66
|
-
class Avg(Selectable):
|
67
|
-
"""
|
68
|
-
``AVG()`` SQL function. Column type must be numeric to run the query.
|
69
|
-
|
70
|
-
.. code-block:: python
|
71
|
-
|
72
|
-
await Band.select(Avg(Band.popularity)).run()
|
73
|
-
|
74
|
-
# We can use an alias. These two are equivalent:
|
75
|
-
|
76
|
-
await Band.select(
|
77
|
-
Avg(Band.popularity, alias="popularity_avg")
|
78
|
-
).run()
|
79
|
-
|
80
|
-
await Band.select(
|
81
|
-
Avg(Band.popularity).as_alias("popularity_avg")
|
82
|
-
).run()
|
83
|
-
|
84
|
-
"""
|
85
|
-
|
86
|
-
def __init__(self, column: Column, alias: str = "avg"):
|
87
|
-
if is_numeric_column(column):
|
88
|
-
self.column = column
|
89
|
-
else:
|
90
|
-
raise ValueError("Column type must be numeric to run the query.")
|
91
|
-
self._alias = alias
|
92
|
-
|
93
|
-
def get_select_string(
|
94
|
-
self, engine_type: str, with_alias: bool = True
|
95
|
-
) -> str:
|
96
|
-
column_name = self.column._meta.get_full_name(with_alias=False)
|
97
|
-
return f'AVG({column_name}) AS "{self._alias}"'
|
98
|
-
|
99
|
-
|
100
|
-
class Count(Selectable):
|
101
|
-
"""
|
102
|
-
Used in ``Select`` queries, usually in conjunction with the ``group_by``
|
103
|
-
clause::
|
104
|
-
|
105
|
-
>>> await Band.select(
|
106
|
-
... Band.manager.name.as_alias('manager_name'),
|
107
|
-
... Count(alias='band_count')
|
108
|
-
... ).group_by(Band.manager)
|
109
|
-
[{'manager_name': 'Guido', 'count': 1}, ...]
|
110
|
-
|
111
|
-
It can also be used without the ``group_by`` clause (though you may prefer
|
112
|
-
to the :meth:`Table.count <piccolo.table.Table.count>` method instead, as
|
113
|
-
it's more convenient)::
|
114
|
-
|
115
|
-
>>> await Band.select(Count())
|
116
|
-
[{'count': 3}]
|
117
|
-
|
118
|
-
"""
|
119
|
-
|
120
|
-
def __init__(
|
121
|
-
self,
|
122
|
-
column: t.Optional[Column] = None,
|
123
|
-
distinct: t.Optional[t.Sequence[Column]] = None,
|
124
|
-
alias: str = "count",
|
125
|
-
):
|
126
|
-
"""
|
127
|
-
:param column:
|
128
|
-
If specified, the count is for non-null values in that column.
|
129
|
-
:param distinct:
|
130
|
-
If specified, the count is for distinct values in those columns.
|
131
|
-
:param alias:
|
132
|
-
The name of the value in the response::
|
133
|
-
|
134
|
-
# These two are equivalent:
|
135
|
-
|
136
|
-
await Band.select(
|
137
|
-
Band.name, Count(alias="total")
|
138
|
-
).group_by(Band.name)
|
139
|
-
|
140
|
-
await Band.select(
|
141
|
-
Band.name,
|
142
|
-
Count().as_alias("total")
|
143
|
-
).group_by(Band.name)
|
144
|
-
|
145
|
-
"""
|
146
|
-
if distinct and column:
|
147
|
-
raise ValueError("Only specify `column` or `distinct`")
|
148
|
-
|
149
|
-
self.column = column
|
150
|
-
self.distinct = distinct
|
151
|
-
self._alias = alias
|
152
|
-
|
153
|
-
def get_select_string(
|
154
|
-
self, engine_type: str, with_alias: bool = True
|
155
|
-
) -> str:
|
156
|
-
expression: str
|
157
|
-
|
158
|
-
if self.distinct:
|
159
|
-
if engine_type == "sqlite":
|
160
|
-
# SQLite doesn't allow us to specify multiple columns, so
|
161
|
-
# instead we concatenate the values.
|
162
|
-
column_names = " || ".join(
|
163
|
-
i._meta.get_full_name(with_alias=False)
|
164
|
-
for i in self.distinct
|
165
|
-
)
|
166
|
-
else:
|
167
|
-
column_names = ", ".join(
|
168
|
-
i._meta.get_full_name(with_alias=False)
|
169
|
-
for i in self.distinct
|
170
|
-
)
|
171
|
-
|
172
|
-
expression = f"DISTINCT ({column_names})"
|
173
|
-
else:
|
174
|
-
if self.column:
|
175
|
-
expression = self.column._meta.get_full_name(with_alias=False)
|
176
|
-
else:
|
177
|
-
expression = "*"
|
178
|
-
|
179
|
-
return f'COUNT({expression}) AS "{self._alias}"'
|
180
|
-
|
181
|
-
|
182
|
-
class Max(Selectable):
|
183
|
-
"""
|
184
|
-
``MAX()`` SQL function.
|
185
|
-
|
186
|
-
.. code-block:: python
|
187
|
-
|
188
|
-
await Band.select(
|
189
|
-
Max(Band.popularity)
|
190
|
-
).run()
|
191
|
-
|
192
|
-
# We can use an alias. These two are equivalent:
|
193
|
-
|
194
|
-
await Band.select(
|
195
|
-
Max(Band.popularity, alias="popularity_max")
|
196
|
-
).run()
|
197
|
-
|
198
|
-
await Band.select(
|
199
|
-
Max(Band.popularity).as_alias("popularity_max")
|
200
|
-
).run()
|
201
|
-
|
202
|
-
"""
|
203
|
-
|
204
|
-
def __init__(self, column: Column, alias: str = "max"):
|
205
|
-
self.column = column
|
206
|
-
self._alias = alias
|
207
|
-
|
208
|
-
def get_select_string(
|
209
|
-
self, engine_type: str, with_alias: bool = True
|
210
|
-
) -> str:
|
211
|
-
column_name = self.column._meta.get_full_name(with_alias=False)
|
212
|
-
return f'MAX({column_name}) AS "{self._alias}"'
|
213
|
-
|
214
|
-
|
215
|
-
class Min(Selectable):
|
216
|
-
"""
|
217
|
-
``MIN()`` SQL function.
|
218
|
-
|
219
|
-
.. code-block:: python
|
220
|
-
|
221
|
-
await Band.select(Min(Band.popularity)).run()
|
222
|
-
|
223
|
-
# We can use an alias. These two are equivalent:
|
224
|
-
|
225
|
-
await Band.select(
|
226
|
-
Min(Band.popularity, alias="popularity_min")
|
227
|
-
).run()
|
228
|
-
|
229
|
-
await Band.select(
|
230
|
-
Min(Band.popularity).as_alias("popularity_min")
|
231
|
-
).run()
|
232
|
-
|
233
|
-
"""
|
234
|
-
|
235
|
-
def __init__(self, column: Column, alias: str = "min"):
|
236
|
-
self.column = column
|
237
|
-
self._alias = alias
|
238
|
-
|
239
|
-
def get_select_string(
|
240
|
-
self, engine_type: str, with_alias: bool = True
|
241
|
-
) -> str:
|
242
|
-
column_name = self.column._meta.get_full_name(with_alias=False)
|
243
|
-
return f'MIN({column_name}) AS "{self._alias}"'
|
244
|
-
|
245
|
-
|
246
|
-
class Sum(Selectable):
|
247
|
-
"""
|
248
|
-
``SUM()`` SQL function. Column type must be numeric to run the query.
|
249
|
-
|
250
|
-
.. code-block:: python
|
251
|
-
|
252
|
-
await Band.select(
|
253
|
-
Sum(Band.popularity)
|
254
|
-
).run()
|
255
|
-
|
256
|
-
# We can use an alias. These two are equivalent:
|
257
|
-
|
258
|
-
await Band.select(
|
259
|
-
Sum(Band.popularity, alias="popularity_sum")
|
260
|
-
).run()
|
261
|
-
|
262
|
-
await Band.select(
|
263
|
-
Sum(Band.popularity).as_alias("popularity_sum")
|
264
|
-
).run()
|
265
|
-
|
266
|
-
"""
|
267
|
-
|
268
|
-
def __init__(self, column: Column, alias: str = "sum"):
|
269
|
-
if is_numeric_column(column):
|
270
|
-
self.column = column
|
271
|
-
else:
|
272
|
-
raise ValueError("Column type must be numeric to run the query.")
|
273
|
-
self._alias = alias
|
274
|
-
|
275
|
-
def get_select_string(
|
276
|
-
self, engine_type: str, with_alias: bool = True
|
277
|
-
) -> str:
|
278
|
-
column_name = self.column._meta.get_full_name(with_alias=False)
|
279
|
-
return f'SUM({column_name}) AS "{self._alias}"'
|
60
|
+
) -> QueryString:
|
61
|
+
return self.querystring
|
280
62
|
|
281
63
|
|
282
64
|
OptionalDict = t.Optional[t.Dict[str, t.Any]]
|
@@ -645,7 +427,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
|
|
645
427
|
self.callback_delegate.callback(callbacks, on=on)
|
646
428
|
return self
|
647
429
|
|
648
|
-
def where(self: Self, *where: Combinable) -> Self:
|
430
|
+
def where(self: Self, *where: t.Union[Combinable, QueryString]) -> Self:
|
649
431
|
self.where_delegate.where(*where)
|
650
432
|
return self
|
651
433
|
|
@@ -678,23 +460,25 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
|
|
678
460
|
for readable in readables:
|
679
461
|
columns += readable.columns
|
680
462
|
|
463
|
+
querystrings: t.List[QueryString] = [
|
464
|
+
i for i in columns if isinstance(i, QueryString)
|
465
|
+
]
|
466
|
+
for querystring in querystrings:
|
467
|
+
if querystring_columns := getattr(querystring, "columns", []):
|
468
|
+
columns += querystring_columns
|
469
|
+
|
681
470
|
for column in columns:
|
682
471
|
if not isinstance(column, Column):
|
683
472
|
continue
|
684
473
|
|
685
474
|
_joins: t.List[str] = []
|
686
475
|
for index, key in enumerate(column._meta.call_chain, 0):
|
687
|
-
table_alias =
|
688
|
-
f"{_key._meta.table._meta.tablename}${_key._meta.name}"
|
689
|
-
for _key in column._meta.call_chain[: index + 1]
|
690
|
-
)
|
691
|
-
|
692
|
-
key._meta.table_alias = table_alias
|
476
|
+
table_alias = key.table_alias
|
693
477
|
|
694
478
|
if index > 0:
|
695
479
|
left_tablename = column._meta.call_chain[
|
696
480
|
index - 1
|
697
|
-
].
|
481
|
+
].table_alias
|
698
482
|
else:
|
699
483
|
left_tablename = (
|
700
484
|
key._meta.table._meta.get_formatted_tablename()
|
@@ -761,11 +545,10 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
|
|
761
545
|
|
762
546
|
engine_type = self.table._meta.db.engine_type
|
763
547
|
|
764
|
-
select_strings: t.List[
|
548
|
+
select_strings: t.List[QueryString] = [
|
765
549
|
c.get_select_string(engine_type=engine_type)
|
766
550
|
for c in self.columns_delegate.selected_columns
|
767
551
|
]
|
768
|
-
columns_str = ", ".join(select_strings)
|
769
552
|
|
770
553
|
#######################################################################
|
771
554
|
|
@@ -779,7 +562,9 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
|
|
779
562
|
query += "{}"
|
780
563
|
args.append(distinct.querystring)
|
781
564
|
|
565
|
+
columns_str = ", ".join("{}" for i in select_strings)
|
782
566
|
query += f" {columns_str} FROM {self.table._meta.get_formatted_tablename()}" # noqa: E501
|
567
|
+
args.extend(select_strings)
|
783
568
|
|
784
569
|
for join in joins:
|
785
570
|
query += f" {join}"
|
piccolo/query/methods/update.py
CHANGED
@@ -50,7 +50,7 @@ class Update(Query[TableInstance, t.List[t.Any]]):
|
|
50
50
|
self.values_delegate.values(values)
|
51
51
|
return self
|
52
52
|
|
53
|
-
def where(self, *where: Combinable) -> Update:
|
53
|
+
def where(self, *where: t.Union[Combinable, QueryString]) -> Update:
|
54
54
|
self.where_delegate.where(*where)
|
55
55
|
return self
|
56
56
|
|
piccolo/query/mixins.py
CHANGED
@@ -9,13 +9,14 @@ from enum import Enum, auto
|
|
9
9
|
|
10
10
|
from piccolo.columns import And, Column, Or, Where
|
11
11
|
from piccolo.columns.column_types import ForeignKey
|
12
|
+
from piccolo.columns.combination import WhereRaw
|
12
13
|
from piccolo.custom_types import Combinable
|
13
14
|
from piccolo.querystring import QueryString
|
14
15
|
from piccolo.utils.list import flatten
|
15
16
|
from piccolo.utils.sql_values import convert_to_sql_value
|
16
17
|
|
17
18
|
if t.TYPE_CHECKING: # pragma: no cover
|
18
|
-
from piccolo.
|
19
|
+
from piccolo.querystring import Selectable
|
19
20
|
from piccolo.table import Table # noqa
|
20
21
|
|
21
22
|
|
@@ -254,8 +255,10 @@ class WhereDelegate:
|
|
254
255
|
elif isinstance(combinable, (And, Or)):
|
255
256
|
self._extract_columns(combinable.first)
|
256
257
|
self._extract_columns(combinable.second)
|
258
|
+
elif isinstance(combinable, WhereRaw):
|
259
|
+
self._where_columns.extend(combinable.querystring.columns)
|
257
260
|
|
258
|
-
def where(self, *where: Combinable):
|
261
|
+
def where(self, *where: t.Union[Combinable, QueryString]):
|
259
262
|
for arg in where:
|
260
263
|
if isinstance(arg, bool):
|
261
264
|
raise ValueError(
|
@@ -265,6 +268,10 @@ class WhereDelegate:
|
|
265
268
|
"`.where(MyTable.some_column.is_null())`."
|
266
269
|
)
|
267
270
|
|
271
|
+
if isinstance(arg, QueryString):
|
272
|
+
# If a raw QueryString is passed in.
|
273
|
+
arg = WhereRaw(arg.template, *arg.args)
|
274
|
+
|
268
275
|
self._where = And(self._where, arg) if self._where else arg
|
269
276
|
|
270
277
|
|
piccolo/querystring.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import typing as t
|
4
|
+
from abc import ABCMeta, abstractmethod
|
4
5
|
from dataclasses import dataclass
|
5
6
|
from datetime import datetime
|
6
7
|
from importlib.util import find_spec
|
@@ -8,6 +9,7 @@ from string import Formatter
|
|
8
9
|
|
9
10
|
if t.TYPE_CHECKING: # pragma: no cover
|
10
11
|
from piccolo.table import Table
|
12
|
+
from piccolo.columns import Column
|
11
13
|
|
12
14
|
from uuid import UUID
|
13
15
|
|
@@ -17,22 +19,32 @@ else:
|
|
17
19
|
apgUUID = UUID
|
18
20
|
|
19
21
|
|
20
|
-
|
21
|
-
class Unquoted:
|
22
|
+
class Selectable(metaclass=ABCMeta):
|
22
23
|
"""
|
23
|
-
|
24
|
-
keyword - for example DEFAULT.
|
24
|
+
Anything which inherits from this can be used in a select query.
|
25
25
|
"""
|
26
26
|
|
27
|
-
__slots__ = ("
|
27
|
+
__slots__ = ("_alias",)
|
28
28
|
|
29
|
-
|
29
|
+
_alias: t.Optional[str]
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
@abstractmethod
|
32
|
+
def get_select_string(
|
33
|
+
self, engine_type: str, with_alias: bool = True
|
34
|
+
) -> QueryString:
|
35
|
+
"""
|
36
|
+
In a query, what to output after the select statement - could be a
|
37
|
+
column name, a sub query, a function etc. For a column it will be the
|
38
|
+
column name.
|
39
|
+
"""
|
40
|
+
raise NotImplementedError()
|
33
41
|
|
34
|
-
def
|
35
|
-
|
42
|
+
def as_alias(self, alias: str) -> Selectable:
|
43
|
+
"""
|
44
|
+
Allows column names to be changed in the result of a select.
|
45
|
+
"""
|
46
|
+
self._alias = alias
|
47
|
+
return self
|
36
48
|
|
37
49
|
|
38
50
|
@dataclass
|
@@ -42,7 +54,7 @@ class Fragment:
|
|
42
54
|
no_arg: bool = False
|
43
55
|
|
44
56
|
|
45
|
-
class QueryString:
|
57
|
+
class QueryString(Selectable):
|
46
58
|
"""
|
47
59
|
When we're composing complex queries, we're combining QueryStrings, rather
|
48
60
|
than concatenating strings directly. The reason for this is QueryStrings
|
@@ -56,6 +68,7 @@ class QueryString:
|
|
56
68
|
"query_type",
|
57
69
|
"table",
|
58
70
|
"_frozen_compiled_strings",
|
71
|
+
"columns",
|
59
72
|
)
|
60
73
|
|
61
74
|
def __init__(
|
@@ -64,6 +77,7 @@ class QueryString:
|
|
64
77
|
*args: t.Any,
|
65
78
|
query_type: str = "generic",
|
66
79
|
table: t.Optional[t.Type[Table]] = None,
|
80
|
+
alias: t.Optional[str] = None,
|
67
81
|
) -> None:
|
68
82
|
"""
|
69
83
|
:param template:
|
@@ -83,12 +97,42 @@ class QueryString:
|
|
83
97
|
|
84
98
|
"""
|
85
99
|
self.template = template
|
86
|
-
self.args = args
|
87
100
|
self.query_type = query_type
|
88
101
|
self.table = table
|
89
102
|
self._frozen_compiled_strings: t.Optional[
|
90
103
|
t.Tuple[str, t.List[t.Any]]
|
91
104
|
] = None
|
105
|
+
self._alias = alias
|
106
|
+
self.args, self.columns = self.process_args(args)
|
107
|
+
|
108
|
+
def process_args(
|
109
|
+
self, args: t.Sequence[t.Any]
|
110
|
+
) -> t.Tuple[t.Sequence[t.Any], t.Sequence[Column]]:
|
111
|
+
"""
|
112
|
+
If a Column is passed in, we convert it to the name of the column
|
113
|
+
(including joins).
|
114
|
+
"""
|
115
|
+
from piccolo.columns import Column
|
116
|
+
|
117
|
+
processed_args = []
|
118
|
+
columns = []
|
119
|
+
|
120
|
+
for arg in args:
|
121
|
+
if isinstance(arg, Column):
|
122
|
+
columns.append(arg)
|
123
|
+
arg = QueryString(
|
124
|
+
f"{arg._meta.get_full_name(with_alias=False)}"
|
125
|
+
)
|
126
|
+
elif isinstance(arg, QueryString):
|
127
|
+
columns.extend(arg.columns)
|
128
|
+
|
129
|
+
processed_args.append(arg)
|
130
|
+
|
131
|
+
return (processed_args, columns)
|
132
|
+
|
133
|
+
def as_alias(self, alias: str) -> QueryString:
|
134
|
+
self._alias = alias
|
135
|
+
return self
|
92
136
|
|
93
137
|
def __str__(self):
|
94
138
|
"""
|
@@ -143,7 +187,7 @@ class QueryString:
|
|
143
187
|
fragment.no_arg = True
|
144
188
|
bundled.append(fragment)
|
145
189
|
else:
|
146
|
-
if isinstance(value,
|
190
|
+
if isinstance(value, QueryString):
|
147
191
|
fragment.no_arg = True
|
148
192
|
bundled.append(fragment)
|
149
193
|
|
@@ -195,3 +239,47 @@ class QueryString:
|
|
195
239
|
self._frozen_compiled_strings = self.compile_string(
|
196
240
|
engine_type=engine_type
|
197
241
|
)
|
242
|
+
|
243
|
+
###########################################################################
|
244
|
+
|
245
|
+
def get_select_string(
|
246
|
+
self, engine_type: str, with_alias: bool = True
|
247
|
+
) -> QueryString:
|
248
|
+
if with_alias and self._alias:
|
249
|
+
return QueryString("{} AS " + self._alias, self)
|
250
|
+
else:
|
251
|
+
return self
|
252
|
+
|
253
|
+
def get_where_string(self, engine_type: str) -> QueryString:
|
254
|
+
return self.get_select_string(
|
255
|
+
engine_type=engine_type, with_alias=False
|
256
|
+
)
|
257
|
+
|
258
|
+
###########################################################################
|
259
|
+
# Basic logic
|
260
|
+
|
261
|
+
def __eq__(self, value) -> QueryString: # type: ignore[override]
|
262
|
+
return QueryString("{} = {}", self, value)
|
263
|
+
|
264
|
+
def __ne__(self, value) -> QueryString: # type: ignore[override]
|
265
|
+
return QueryString("{} != {}", self, value)
|
266
|
+
|
267
|
+
def __add__(self, value) -> QueryString:
|
268
|
+
return QueryString("{} + {}", self, value)
|
269
|
+
|
270
|
+
def __sub__(self, value) -> QueryString:
|
271
|
+
return QueryString("{} - {}", self, value)
|
272
|
+
|
273
|
+
def is_in(self, value) -> QueryString:
|
274
|
+
return QueryString("{} IN {}", self, value)
|
275
|
+
|
276
|
+
def not_in(self, value) -> QueryString:
|
277
|
+
return QueryString("{} NOT IN {}", self, value)
|
278
|
+
|
279
|
+
|
280
|
+
class Unquoted(QueryString):
|
281
|
+
"""
|
282
|
+
This is deprecated - just use QueryString directly.
|
283
|
+
"""
|
284
|
+
|
285
|
+
pass
|
piccolo/table.py
CHANGED
@@ -48,7 +48,7 @@ from piccolo.query.methods.create_index import CreateIndex
|
|
48
48
|
from piccolo.query.methods.indexes import Indexes
|
49
49
|
from piccolo.query.methods.objects import First
|
50
50
|
from piccolo.query.methods.refresh import Refresh
|
51
|
-
from piccolo.querystring import QueryString
|
51
|
+
from piccolo.querystring import QueryString
|
52
52
|
from piccolo.utils import _camel_to_snake
|
53
53
|
from piccolo.utils.graphlib import TopologicalSorter
|
54
54
|
from piccolo.utils.sql_values import convert_to_sql_value
|
@@ -56,7 +56,7 @@ from piccolo.utils.sync import run_sync
|
|
56
56
|
from piccolo.utils.warnings import colored_warning
|
57
57
|
|
58
58
|
if t.TYPE_CHECKING: # pragma: no cover
|
59
|
-
from piccolo.
|
59
|
+
from piccolo.querystring import Selectable
|
60
60
|
|
61
61
|
PROTECTED_TABLENAMES = ("user",)
|
62
62
|
TABLENAME_WARNING = (
|
@@ -796,30 +796,14 @@ class Table(metaclass=TableMetaclass):
|
|
796
796
|
"""
|
797
797
|
Used when inserting rows.
|
798
798
|
"""
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
args_dict[column_name] = value
|
804
|
-
|
805
|
-
def is_unquoted(arg):
|
806
|
-
return isinstance(arg, Unquoted)
|
807
|
-
|
808
|
-
# Strip out any args which are unquoted.
|
809
|
-
filtered_args = [i for i in args_dict.values() if not is_unquoted(i)]
|
799
|
+
args = [
|
800
|
+
convert_to_sql_value(value=self[column._meta.name], column=column)
|
801
|
+
for column in self._meta.columns
|
802
|
+
]
|
810
803
|
|
811
804
|
# If unquoted, dump it straight into the query.
|
812
|
-
query = ",".join(
|
813
|
-
|
814
|
-
(
|
815
|
-
args_dict[column._meta.name].value
|
816
|
-
if is_unquoted(args_dict[column._meta.name])
|
817
|
-
else "{}"
|
818
|
-
)
|
819
|
-
for column in self._meta.columns
|
820
|
-
]
|
821
|
-
)
|
822
|
-
return QueryString(f"({query})", *filtered_args)
|
805
|
+
query = ",".join(["{}" for _ in args])
|
806
|
+
return QueryString(f"({query})", *args)
|
823
807
|
|
824
808
|
def __str__(self) -> str:
|
825
809
|
return self.querystring.__str__()
|