piccolo 1.5.1__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/apps/playground/commands/run.py +70 -18
- 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/base.py +1 -5
- 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.1.dist-info → piccolo-1.6.0.dist-info}/METADATA +1 -1
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/RECORD +35 -30
- tests/apps/migrations/commands/test_forwards_backwards.py +3 -0
- tests/apps/shell/commands/test_run.py +1 -0
- tests/conf/test_apps.py +6 -0
- tests/example_apps/music/tables.py +10 -0
- tests/query/test_functions.py +102 -0
- tests/table/test_output.py +88 -36
- tests/table/test_select.py +2 -9
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/LICENSE +0 -0
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/WHEEL +0 -0
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from piccolo.columns.base import Column
|
4
|
+
from piccolo.querystring import QueryString
|
5
|
+
|
6
|
+
from .base import Function
|
7
|
+
|
8
|
+
|
9
|
+
class Avg(Function):
|
10
|
+
"""
|
11
|
+
``AVG()`` SQL function. Column type must be numeric to run the query.
|
12
|
+
|
13
|
+
.. code-block:: python
|
14
|
+
|
15
|
+
await Band.select(Avg(Band.popularity)).run()
|
16
|
+
|
17
|
+
# We can use an alias. These two are equivalent:
|
18
|
+
|
19
|
+
await Band.select(
|
20
|
+
Avg(Band.popularity, alias="popularity_avg")
|
21
|
+
).run()
|
22
|
+
|
23
|
+
await Band.select(
|
24
|
+
Avg(Band.popularity).as_alias("popularity_avg")
|
25
|
+
).run()
|
26
|
+
|
27
|
+
"""
|
28
|
+
|
29
|
+
function_name = "AVG"
|
30
|
+
|
31
|
+
|
32
|
+
class Count(QueryString):
|
33
|
+
"""
|
34
|
+
Used in ``Select`` queries, usually in conjunction with the ``group_by``
|
35
|
+
clause::
|
36
|
+
|
37
|
+
>>> await Band.select(
|
38
|
+
... Band.manager.name.as_alias('manager_name'),
|
39
|
+
... Count(alias='band_count')
|
40
|
+
... ).group_by(Band.manager)
|
41
|
+
[{'manager_name': 'Guido', 'count': 1}, ...]
|
42
|
+
|
43
|
+
It can also be used without the ``group_by`` clause (though you may prefer
|
44
|
+
to the :meth:`Table.count <piccolo.table.Table.count>` method instead, as
|
45
|
+
it's more convenient)::
|
46
|
+
|
47
|
+
>>> await Band.select(Count())
|
48
|
+
[{'count': 3}]
|
49
|
+
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
column: t.Optional[Column] = None,
|
55
|
+
distinct: t.Optional[t.Sequence[Column]] = None,
|
56
|
+
alias: str = "count",
|
57
|
+
):
|
58
|
+
"""
|
59
|
+
:param column:
|
60
|
+
If specified, the count is for non-null values in that column.
|
61
|
+
:param distinct:
|
62
|
+
If specified, the count is for distinct values in those columns.
|
63
|
+
:param alias:
|
64
|
+
The name of the value in the response::
|
65
|
+
|
66
|
+
# These two are equivalent:
|
67
|
+
|
68
|
+
await Band.select(
|
69
|
+
Band.name, Count(alias="total")
|
70
|
+
).group_by(Band.name)
|
71
|
+
|
72
|
+
await Band.select(
|
73
|
+
Band.name,
|
74
|
+
Count().as_alias("total")
|
75
|
+
).group_by(Band.name)
|
76
|
+
|
77
|
+
"""
|
78
|
+
if distinct and column:
|
79
|
+
raise ValueError("Only specify `column` or `distinct`")
|
80
|
+
|
81
|
+
if distinct:
|
82
|
+
engine_type = distinct[0]._meta.engine_type
|
83
|
+
if engine_type == "sqlite":
|
84
|
+
# SQLite doesn't allow us to specify multiple columns, so
|
85
|
+
# instead we concatenate the values.
|
86
|
+
column_names = " || ".join("{}" for _ in distinct)
|
87
|
+
else:
|
88
|
+
column_names = ", ".join("{}" for _ in distinct)
|
89
|
+
|
90
|
+
return super().__init__(
|
91
|
+
f"COUNT(DISTINCT({column_names}))", *distinct, alias=alias
|
92
|
+
)
|
93
|
+
else:
|
94
|
+
if column:
|
95
|
+
return super().__init__("COUNT({})", column, alias=alias)
|
96
|
+
else:
|
97
|
+
return super().__init__("COUNT(*)", alias=alias)
|
98
|
+
|
99
|
+
|
100
|
+
class Min(Function):
|
101
|
+
"""
|
102
|
+
``MIN()`` SQL function.
|
103
|
+
|
104
|
+
.. code-block:: python
|
105
|
+
|
106
|
+
await Band.select(Min(Band.popularity)).run()
|
107
|
+
|
108
|
+
# We can use an alias. These two are equivalent:
|
109
|
+
|
110
|
+
await Band.select(
|
111
|
+
Min(Band.popularity, alias="popularity_min")
|
112
|
+
).run()
|
113
|
+
|
114
|
+
await Band.select(
|
115
|
+
Min(Band.popularity).as_alias("popularity_min")
|
116
|
+
).run()
|
117
|
+
|
118
|
+
"""
|
119
|
+
|
120
|
+
function_name = "MIN"
|
121
|
+
|
122
|
+
|
123
|
+
class Max(Function):
|
124
|
+
"""
|
125
|
+
``MAX()`` SQL function.
|
126
|
+
|
127
|
+
.. code-block:: python
|
128
|
+
|
129
|
+
await Band.select(
|
130
|
+
Max(Band.popularity)
|
131
|
+
).run()
|
132
|
+
|
133
|
+
# We can use an alias. These two are equivalent:
|
134
|
+
|
135
|
+
await Band.select(
|
136
|
+
Max(Band.popularity, alias="popularity_max")
|
137
|
+
).run()
|
138
|
+
|
139
|
+
await Band.select(
|
140
|
+
Max(Band.popularity).as_alias("popularity_max")
|
141
|
+
).run()
|
142
|
+
|
143
|
+
"""
|
144
|
+
|
145
|
+
function_name = "MAX"
|
146
|
+
|
147
|
+
|
148
|
+
class Sum(Function):
|
149
|
+
"""
|
150
|
+
``SUM()`` SQL function. Column type must be numeric to run the query.
|
151
|
+
|
152
|
+
.. code-block:: python
|
153
|
+
|
154
|
+
await Band.select(
|
155
|
+
Sum(Band.popularity)
|
156
|
+
).run()
|
157
|
+
|
158
|
+
# We can use an alias. These two are equivalent:
|
159
|
+
|
160
|
+
await Band.select(
|
161
|
+
Sum(Band.popularity, alias="popularity_sum")
|
162
|
+
).run()
|
163
|
+
|
164
|
+
await Band.select(
|
165
|
+
Sum(Band.popularity).as_alias("popularity_sum")
|
166
|
+
).run()
|
167
|
+
|
168
|
+
"""
|
169
|
+
|
170
|
+
function_name = "SUM"
|
171
|
+
|
172
|
+
|
173
|
+
__all__ = (
|
174
|
+
"Avg",
|
175
|
+
"Count",
|
176
|
+
"Min",
|
177
|
+
"Max",
|
178
|
+
"Sum",
|
179
|
+
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from piccolo.columns.base import Column
|
4
|
+
from piccolo.querystring import QueryString
|
5
|
+
|
6
|
+
|
7
|
+
class Function(QueryString):
|
8
|
+
function_name: str
|
9
|
+
|
10
|
+
def __init__(
|
11
|
+
self,
|
12
|
+
identifier: t.Union[Column, QueryString, str],
|
13
|
+
alias: t.Optional[str] = None,
|
14
|
+
):
|
15
|
+
alias = alias or self.__class__.__name__.lower()
|
16
|
+
|
17
|
+
super().__init__(
|
18
|
+
f"{self.function_name}({{}})",
|
19
|
+
identifier,
|
20
|
+
alias=alias,
|
21
|
+
)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
"""
|
2
|
+
These functions mirror their counterparts in the Postgresql docs:
|
3
|
+
|
4
|
+
https://www.postgresql.org/docs/current/functions-string.html
|
5
|
+
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .base import Function
|
9
|
+
|
10
|
+
|
11
|
+
class Length(Function):
|
12
|
+
"""
|
13
|
+
Returns the number of characters in the string.
|
14
|
+
"""
|
15
|
+
|
16
|
+
function_name = "LENGTH"
|
17
|
+
|
18
|
+
|
19
|
+
class Lower(Function):
|
20
|
+
"""
|
21
|
+
Converts the string to all lower case, according to the rules of the
|
22
|
+
database's locale.
|
23
|
+
"""
|
24
|
+
|
25
|
+
function_name = "LOWER"
|
26
|
+
|
27
|
+
|
28
|
+
class Ltrim(Function):
|
29
|
+
"""
|
30
|
+
Removes the longest string containing only characters in characters (a
|
31
|
+
space by default) from the start of string.
|
32
|
+
"""
|
33
|
+
|
34
|
+
function_name = "LTRIM"
|
35
|
+
|
36
|
+
|
37
|
+
class Reverse(Function):
|
38
|
+
"""
|
39
|
+
Return reversed string.
|
40
|
+
|
41
|
+
Not supported in SQLite.
|
42
|
+
|
43
|
+
"""
|
44
|
+
|
45
|
+
function_name = "REVERSE"
|
46
|
+
|
47
|
+
|
48
|
+
class Rtrim(Function):
|
49
|
+
"""
|
50
|
+
Removes the longest string containing only characters in characters (a
|
51
|
+
space by default) from the end of string.
|
52
|
+
"""
|
53
|
+
|
54
|
+
function_name = "RTRIM"
|
55
|
+
|
56
|
+
|
57
|
+
class Upper(Function):
|
58
|
+
"""
|
59
|
+
Converts the string to all upper case, according to the rules of the
|
60
|
+
database's locale.
|
61
|
+
"""
|
62
|
+
|
63
|
+
function_name = "UPPER"
|
64
|
+
|
65
|
+
|
66
|
+
__all__ = (
|
67
|
+
"Length",
|
68
|
+
"Lower",
|
69
|
+
"Ltrim",
|
70
|
+
"Reverse",
|
71
|
+
"Rtrim",
|
72
|
+
"Upper",
|
73
|
+
)
|
@@ -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
|
|