piccolo 1.5.2__py3-none-any.whl → 1.7.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 +37 -13
- piccolo/columns/m2m.py +16 -6
- piccolo/columns/readable.py +9 -7
- piccolo/engine/sqlite.py +171 -57
- 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.7.0.dist-info}/METADATA +1 -1
- {piccolo-1.5.2.dist-info → piccolo-1.7.0.dist-info}/RECORD +30 -25
- tests/columns/test_array.py +89 -2
- tests/query/test_functions.py +102 -0
- tests/table/test_select.py +2 -9
- {piccolo-1.5.2.dist-info → piccolo-1.7.0.dist-info}/LICENSE +0 -0
- {piccolo-1.5.2.dist-info → piccolo-1.7.0.dist-info}/WHEEL +0 -0
- {piccolo-1.5.2.dist-info → piccolo-1.7.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.5.2.dist-info → piccolo-1.7.0.dist-info}/top_level.txt +0 -0
piccolo/engine/sqlite.py
CHANGED
@@ -9,6 +9,7 @@ import typing as t
|
|
9
9
|
import uuid
|
10
10
|
from dataclasses import dataclass
|
11
11
|
from decimal import Decimal
|
12
|
+
from functools import partial, wraps
|
12
13
|
|
13
14
|
from piccolo.engine.base import Batch, Engine, validate_savepoint_name
|
14
15
|
from piccolo.engine.exceptions import TransactionError
|
@@ -35,14 +36,14 @@ if t.TYPE_CHECKING: # pragma: no cover
|
|
35
36
|
# In
|
36
37
|
|
37
38
|
|
38
|
-
def convert_numeric_in(value):
|
39
|
+
def convert_numeric_in(value: Decimal) -> float:
|
39
40
|
"""
|
40
41
|
Convert any Decimal values into floats.
|
41
42
|
"""
|
42
43
|
return float(value)
|
43
44
|
|
44
45
|
|
45
|
-
def convert_uuid_in(value) -> str:
|
46
|
+
def convert_uuid_in(value: uuid.UUID) -> str:
|
46
47
|
"""
|
47
48
|
Converts the UUID value being passed into sqlite.
|
48
49
|
"""
|
@@ -56,7 +57,7 @@ def convert_time_in(value: datetime.time) -> str:
|
|
56
57
|
return value.isoformat()
|
57
58
|
|
58
59
|
|
59
|
-
def convert_date_in(value: datetime.date):
|
60
|
+
def convert_date_in(value: datetime.date) -> str:
|
60
61
|
"""
|
61
62
|
Converts the date value being passed into sqlite.
|
62
63
|
"""
|
@@ -74,122 +75,235 @@ def convert_datetime_in(value: datetime.datetime) -> str:
|
|
74
75
|
return str(value)
|
75
76
|
|
76
77
|
|
77
|
-
def convert_timedelta_in(value: datetime.timedelta):
|
78
|
+
def convert_timedelta_in(value: datetime.timedelta) -> float:
|
78
79
|
"""
|
79
80
|
Converts the timedelta value being passed into sqlite.
|
80
81
|
"""
|
81
82
|
return value.total_seconds()
|
82
83
|
|
83
84
|
|
84
|
-
def convert_array_in(value: list):
|
85
|
+
def convert_array_in(value: list) -> str:
|
85
86
|
"""
|
86
|
-
Converts a list value into a string
|
87
|
+
Converts a list value into a string (it handles nested lists, and type like
|
88
|
+
dateime/ time / date which aren't usually JSON serialisable.).
|
89
|
+
|
87
90
|
"""
|
88
|
-
if value and type(value[0]) not in [str, int, float, list]:
|
89
|
-
raise ValueError("Can only serialise str, int, float, and list.")
|
90
91
|
|
91
|
-
|
92
|
+
def serialise(data: list):
|
93
|
+
output = []
|
94
|
+
|
95
|
+
for item in data:
|
96
|
+
if isinstance(item, list):
|
97
|
+
output.append(serialise(item))
|
98
|
+
elif isinstance(
|
99
|
+
item, (datetime.datetime, datetime.time, datetime.date)
|
100
|
+
):
|
101
|
+
if adapter := ADAPTERS.get(type(item)):
|
102
|
+
output.append(adapter(item))
|
103
|
+
else:
|
104
|
+
raise ValueError("The adapter wasn't found.")
|
105
|
+
elif item is None or isinstance(item, (str, int, float, list)):
|
106
|
+
# We can safely JSON serialise these.
|
107
|
+
output.append(item)
|
108
|
+
else:
|
109
|
+
raise ValueError("We can't currently serialise this value.")
|
110
|
+
|
111
|
+
return output
|
112
|
+
|
113
|
+
return dump_json(serialise(value))
|
114
|
+
|
115
|
+
|
116
|
+
###############################################################################
|
117
|
+
|
118
|
+
# Register adapters
|
119
|
+
|
120
|
+
ADAPTERS: t.Dict[t.Type, t.Callable[[t.Any], t.Any]] = {
|
121
|
+
Decimal: convert_numeric_in,
|
122
|
+
uuid.UUID: convert_uuid_in,
|
123
|
+
datetime.time: convert_time_in,
|
124
|
+
datetime.date: convert_date_in,
|
125
|
+
datetime.datetime: convert_datetime_in,
|
126
|
+
datetime.timedelta: convert_timedelta_in,
|
127
|
+
list: convert_array_in,
|
128
|
+
}
|
92
129
|
|
130
|
+
for value_type, adapter in ADAPTERS.items():
|
131
|
+
sqlite3.register_adapter(value_type, adapter)
|
132
|
+
|
133
|
+
###############################################################################
|
93
134
|
|
94
135
|
# Out
|
95
136
|
|
96
137
|
|
97
|
-
def
|
138
|
+
def decode_to_string(converter: t.Callable[[str], t.Any]):
|
139
|
+
"""
|
140
|
+
This means we can use our converters with string and bytes. They are
|
141
|
+
passed bytes when used directly via SQLite, and are passed strings when
|
142
|
+
used by the array converters.
|
143
|
+
"""
|
144
|
+
|
145
|
+
@wraps(converter)
|
146
|
+
def wrapper(value: t.Union[str, bytes]) -> t.Any:
|
147
|
+
if isinstance(value, bytes):
|
148
|
+
return converter(value.decode("utf8"))
|
149
|
+
elif isinstance(value, str):
|
150
|
+
return converter(value)
|
151
|
+
else:
|
152
|
+
raise ValueError("Unsupported type")
|
153
|
+
|
154
|
+
return wrapper
|
155
|
+
|
156
|
+
|
157
|
+
@decode_to_string
|
158
|
+
def convert_numeric_out(value: str) -> Decimal:
|
98
159
|
"""
|
99
160
|
Convert float values into Decimals.
|
100
161
|
"""
|
101
|
-
return Decimal(value
|
162
|
+
return Decimal(value)
|
102
163
|
|
103
164
|
|
104
|
-
|
165
|
+
@decode_to_string
|
166
|
+
def convert_int_out(value: str) -> int:
|
105
167
|
"""
|
106
168
|
Make sure Integer values are actually of type int.
|
107
169
|
"""
|
108
170
|
return int(float(value))
|
109
171
|
|
110
172
|
|
111
|
-
|
173
|
+
@decode_to_string
|
174
|
+
def convert_uuid_out(value: str) -> uuid.UUID:
|
112
175
|
"""
|
113
176
|
If the value is a uuid, convert it to a UUID instance.
|
114
177
|
"""
|
115
|
-
return uuid.UUID(value
|
178
|
+
return uuid.UUID(value)
|
116
179
|
|
117
180
|
|
118
|
-
|
119
|
-
|
181
|
+
@decode_to_string
|
182
|
+
def convert_date_out(value: str) -> datetime.date:
|
183
|
+
return datetime.date.fromisoformat(value)
|
120
184
|
|
121
185
|
|
122
|
-
|
186
|
+
@decode_to_string
|
187
|
+
def convert_time_out(value: str) -> datetime.time:
|
123
188
|
"""
|
124
189
|
If the value is a time, convert it to a UUID instance.
|
125
190
|
"""
|
126
|
-
return datetime.time.fromisoformat(value
|
191
|
+
return datetime.time.fromisoformat(value)
|
127
192
|
|
128
193
|
|
129
|
-
|
194
|
+
@decode_to_string
|
195
|
+
def convert_seconds_out(value: str) -> datetime.timedelta:
|
130
196
|
"""
|
131
197
|
If the value is from a seconds column, convert it to a timedelta instance.
|
132
198
|
"""
|
133
|
-
return datetime.timedelta(seconds=float(value
|
199
|
+
return datetime.timedelta(seconds=float(value))
|
134
200
|
|
135
201
|
|
136
|
-
|
202
|
+
@decode_to_string
|
203
|
+
def convert_boolean_out(value: str) -> bool:
|
137
204
|
"""
|
138
205
|
If the value is from a boolean column, convert it to a bool value.
|
139
206
|
"""
|
140
|
-
|
141
|
-
return _value == "1"
|
207
|
+
return value == "1"
|
142
208
|
|
143
209
|
|
144
|
-
|
210
|
+
@decode_to_string
|
211
|
+
def convert_timestamp_out(value: str) -> datetime.datetime:
|
145
212
|
"""
|
146
213
|
If the value is from a timestamp column, convert it to a datetime value.
|
147
214
|
"""
|
148
|
-
return datetime.datetime.fromisoformat(value
|
215
|
+
return datetime.datetime.fromisoformat(value)
|
149
216
|
|
150
217
|
|
151
|
-
|
218
|
+
@decode_to_string
|
219
|
+
def convert_timestamptz_out(value: str) -> datetime.datetime:
|
152
220
|
"""
|
153
221
|
If the value is from a timestamptz column, convert it to a datetime value,
|
154
222
|
with a timezone of UTC.
|
155
223
|
"""
|
156
|
-
|
157
|
-
|
158
|
-
|
224
|
+
return datetime.datetime.fromisoformat(value).replace(
|
225
|
+
tzinfo=datetime.timezone.utc
|
226
|
+
)
|
159
227
|
|
160
228
|
|
161
|
-
|
229
|
+
@decode_to_string
|
230
|
+
def convert_array_out(value: str) -> t.List:
|
162
231
|
"""
|
163
232
|
If the value if from an array column, deserialise the string back into a
|
164
233
|
list.
|
165
234
|
"""
|
166
|
-
return load_json(value
|
167
|
-
|
168
|
-
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
235
|
+
return load_json(value)
|
236
|
+
|
237
|
+
|
238
|
+
def convert_complex_array_out(value: bytes, converter: t.Callable):
|
239
|
+
"""
|
240
|
+
This is used to handle arrays of things like timestamps, which we can't
|
241
|
+
just load from JSON without doing additional work to convert the elements
|
242
|
+
back into Python objects.
|
243
|
+
"""
|
244
|
+
parsed = load_json(value.decode("utf8"))
|
245
|
+
|
246
|
+
def convert_list(list_value: t.List):
|
247
|
+
output = []
|
248
|
+
|
249
|
+
for value in list_value:
|
250
|
+
if isinstance(value, list):
|
251
|
+
# For nested arrays
|
252
|
+
output.append(convert_list(value))
|
253
|
+
elif isinstance(value, str):
|
254
|
+
output.append(converter(value))
|
255
|
+
else:
|
256
|
+
output.append(value)
|
257
|
+
|
258
|
+
return output
|
259
|
+
|
260
|
+
if isinstance(parsed, list):
|
261
|
+
return convert_list(parsed)
|
262
|
+
else:
|
263
|
+
return parsed
|
264
|
+
|
265
|
+
|
266
|
+
@decode_to_string
|
267
|
+
def convert_M2M_out(value: str) -> t.List:
|
268
|
+
return value.split(",")
|
269
|
+
|
270
|
+
|
271
|
+
###############################################################################
|
272
|
+
# Register the basic converters
|
273
|
+
|
274
|
+
CONVERTERS = {
|
275
|
+
"NUMERIC": convert_numeric_out,
|
276
|
+
"INTEGER": convert_int_out,
|
277
|
+
"UUID": convert_uuid_out,
|
278
|
+
"DATE": convert_date_out,
|
279
|
+
"TIME": convert_time_out,
|
280
|
+
"SECONDS": convert_seconds_out,
|
281
|
+
"BOOLEAN": convert_boolean_out,
|
282
|
+
"TIMESTAMP": convert_timestamp_out,
|
283
|
+
"TIMESTAMPTZ": convert_timestamptz_out,
|
284
|
+
"M2M": convert_M2M_out,
|
285
|
+
}
|
286
|
+
|
287
|
+
for column_name, converter in CONVERTERS.items():
|
288
|
+
sqlite3.register_converter(column_name, converter)
|
289
|
+
|
290
|
+
###############################################################################
|
291
|
+
# Register the array converters
|
292
|
+
|
293
|
+
# The ARRAY column type handles values which can be easily serialised to and
|
294
|
+
# from JSON.
|
295
|
+
sqlite3.register_converter("ARRAY", convert_array_out)
|
296
|
+
|
297
|
+
# We have special column types for arrays of timestamps etc, as simply loading
|
298
|
+
# the JSON isn't sufficient.
|
299
|
+
for column_name in ("TIMESTAMP", "TIMESTAMPTZ", "DATE", "TIME"):
|
300
|
+
sqlite3.register_converter(
|
301
|
+
f"ARRAY_{column_name}",
|
302
|
+
partial(
|
303
|
+
convert_complex_array_out,
|
304
|
+
converter=CONVERTERS[column_name],
|
305
|
+
),
|
306
|
+
)
|
193
307
|
|
194
308
|
###############################################################################
|
195
309
|
|
piccolo/query/__init__.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
from piccolo.columns.combination import WhereRaw
|
2
2
|
|
3
3
|
from .base import Query
|
4
|
+
from .functions.aggregate import Avg, Max, Min, Sum
|
4
5
|
from .methods import (
|
5
6
|
Alter,
|
6
|
-
Avg,
|
7
7
|
Count,
|
8
8
|
Create,
|
9
9
|
CreateIndex,
|
@@ -11,12 +11,9 @@ from .methods import (
|
|
11
11
|
DropIndex,
|
12
12
|
Exists,
|
13
13
|
Insert,
|
14
|
-
Max,
|
15
|
-
Min,
|
16
14
|
Objects,
|
17
15
|
Raw,
|
18
16
|
Select,
|
19
|
-
Sum,
|
20
17
|
TableExists,
|
21
18
|
Update,
|
22
19
|
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from .aggregate import Avg, Count, Max, Min, Sum
|
2
|
+
from .string import Length, Lower, Ltrim, Reverse, Rtrim, Upper
|
3
|
+
|
4
|
+
__all__ = (
|
5
|
+
"Avg",
|
6
|
+
"Count",
|
7
|
+
"Length",
|
8
|
+
"Lower",
|
9
|
+
"Ltrim",
|
10
|
+
"Max",
|
11
|
+
"Min",
|
12
|
+
"Reverse",
|
13
|
+
"Rtrim",
|
14
|
+
"Sum",
|
15
|
+
"Upper",
|
16
|
+
)
|
@@ -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
|