piccolo 1.27.1__py3-none-any.whl → 1.28.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/app/commands/new.py +3 -3
- piccolo/apps/asgi/commands/new.py +1 -2
- piccolo/apps/fixtures/commands/dump.py +8 -8
- piccolo/apps/fixtures/commands/load.py +5 -5
- piccolo/apps/fixtures/commands/shared.py +9 -9
- piccolo/apps/migrations/auto/diffable_table.py +12 -12
- piccolo/apps/migrations/auto/migration_manager.py +59 -66
- piccolo/apps/migrations/auto/operations.py +14 -14
- piccolo/apps/migrations/auto/schema_differ.py +35 -34
- piccolo/apps/migrations/auto/schema_snapshot.py +3 -4
- piccolo/apps/migrations/auto/serialisation.py +27 -24
- piccolo/apps/migrations/auto/serialisation_legacy.py +2 -2
- piccolo/apps/migrations/commands/backwards.py +1 -2
- piccolo/apps/migrations/commands/base.py +12 -12
- piccolo/apps/migrations/commands/check.py +2 -3
- piccolo/apps/migrations/commands/clean.py +3 -3
- piccolo/apps/migrations/commands/forwards.py +1 -2
- piccolo/apps/migrations/commands/new.py +6 -6
- piccolo/apps/migrations/tables.py +3 -3
- piccolo/apps/playground/commands/run.py +29 -13
- piccolo/apps/schema/commands/generate.py +49 -49
- piccolo/apps/schema/commands/graph.py +5 -5
- piccolo/apps/shell/commands/run.py +1 -2
- piccolo/apps/sql_shell/commands/run.py +4 -4
- piccolo/apps/tester/commands/run.py +3 -3
- piccolo/apps/user/commands/change_permissions.py +6 -6
- piccolo/apps/user/commands/create.py +7 -7
- piccolo/apps/user/commands/list.py +2 -2
- piccolo/apps/user/tables.py +8 -8
- piccolo/columns/base.py +84 -52
- piccolo/columns/choices.py +2 -2
- piccolo/columns/column_types.py +297 -175
- piccolo/columns/combination.py +15 -12
- piccolo/columns/defaults/base.py +4 -4
- piccolo/columns/defaults/date.py +4 -3
- piccolo/columns/defaults/interval.py +4 -3
- piccolo/columns/defaults/time.py +4 -3
- piccolo/columns/defaults/timestamp.py +4 -3
- piccolo/columns/defaults/timestamptz.py +4 -3
- piccolo/columns/defaults/uuid.py +3 -2
- piccolo/columns/m2m.py +28 -35
- piccolo/columns/readable.py +4 -3
- piccolo/columns/reference.py +9 -9
- piccolo/conf/apps.py +53 -54
- piccolo/custom_types.py +28 -6
- piccolo/engine/base.py +14 -14
- piccolo/engine/cockroach.py +5 -4
- piccolo/engine/finder.py +2 -2
- piccolo/engine/postgres.py +20 -19
- piccolo/engine/sqlite.py +23 -22
- piccolo/query/base.py +30 -29
- piccolo/query/functions/__init__.py +12 -0
- piccolo/query/functions/aggregate.py +4 -3
- piccolo/query/functions/array.py +151 -0
- piccolo/query/functions/base.py +3 -3
- piccolo/query/functions/datetime.py +22 -22
- piccolo/query/functions/string.py +4 -4
- piccolo/query/functions/type_conversion.py +30 -15
- piccolo/query/methods/alter.py +47 -46
- piccolo/query/methods/count.py +11 -10
- piccolo/query/methods/create.py +6 -5
- piccolo/query/methods/create_index.py +9 -8
- piccolo/query/methods/delete.py +7 -6
- piccolo/query/methods/drop_index.py +7 -6
- piccolo/query/methods/exists.py +6 -5
- piccolo/query/methods/indexes.py +4 -4
- piccolo/query/methods/insert.py +21 -14
- piccolo/query/methods/objects.py +60 -50
- piccolo/query/methods/raw.py +7 -6
- piccolo/query/methods/refresh.py +8 -7
- piccolo/query/methods/select.py +56 -49
- piccolo/query/methods/table_exists.py +5 -5
- piccolo/query/methods/update.py +8 -7
- piccolo/query/mixins.py +56 -61
- piccolo/query/operators/json.py +11 -11
- piccolo/query/proxy.py +8 -9
- piccolo/querystring.py +14 -15
- piccolo/schema.py +10 -10
- piccolo/table.py +93 -94
- piccolo/table_reflection.py +9 -9
- piccolo/testing/model_builder.py +12 -11
- piccolo/testing/random_builder.py +2 -2
- piccolo/testing/test_case.py +4 -4
- piccolo/utils/dictionary.py +3 -3
- piccolo/utils/encoding.py +5 -5
- piccolo/utils/lazy_loader.py +3 -3
- piccolo/utils/list.py +7 -8
- piccolo/utils/objects.py +4 -6
- piccolo/utils/pydantic.py +21 -24
- piccolo/utils/sql_values.py +3 -3
- piccolo/utils/sync.py +4 -3
- piccolo/utils/warnings.py +1 -2
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/METADATA +1 -1
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/RECORD +122 -121
- tests/apps/fixtures/commands/test_dump_load.py +1 -2
- tests/apps/migrations/auto/integration/test_migrations.py +32 -7
- tests/apps/migrations/auto/test_migration_manager.py +2 -2
- tests/apps/migrations/auto/test_schema_differ.py +22 -23
- tests/apps/migrations/commands/test_forwards_backwards.py +3 -3
- tests/columns/m2m/base.py +2 -2
- tests/columns/test_array.py +176 -10
- tests/columns/test_boolean.py +2 -4
- tests/columns/test_combination.py +29 -1
- tests/columns/test_db_column_name.py +2 -2
- tests/engine/test_extra_nodes.py +2 -2
- tests/engine/test_pool.py +3 -3
- tests/engine/test_transaction.py +4 -4
- tests/query/test_freeze.py +4 -4
- tests/table/instance/test_get_related.py +2 -2
- tests/table/test_alter.py +4 -4
- tests/table/test_indexes.py +1 -2
- tests/table/test_refresh.py +2 -2
- tests/table/test_select.py +58 -0
- tests/table/test_update.py +3 -3
- tests/testing/test_model_builder.py +1 -2
- tests/utils/test_pydantic.py +36 -36
- tests/utils/test_table_reflection.py +1 -2
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/WHEEL +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/licenses/LICENSE +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/top_level.txt +0 -0
piccolo/engine/sqlite.py
CHANGED
@@ -5,11 +5,12 @@ import datetime
|
|
5
5
|
import enum
|
6
6
|
import os
|
7
7
|
import sqlite3
|
8
|
-
import typing as t
|
9
8
|
import uuid
|
9
|
+
from collections.abc import Callable
|
10
10
|
from dataclasses import dataclass
|
11
11
|
from decimal import Decimal
|
12
12
|
from functools import partial, wraps
|
13
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
13
14
|
|
14
15
|
from typing_extensions import Self
|
15
16
|
|
@@ -30,7 +31,7 @@ from piccolo.utils.sync import run_sync
|
|
30
31
|
aiosqlite = LazyLoader("aiosqlite", globals(), "aiosqlite")
|
31
32
|
|
32
33
|
|
33
|
-
if
|
34
|
+
if TYPE_CHECKING: # pragma: no cover
|
34
35
|
from aiosqlite import Connection, Cursor # type: ignore
|
35
36
|
|
36
37
|
from piccolo.table import Table
|
@@ -125,7 +126,7 @@ def convert_array_in(value: list) -> str:
|
|
125
126
|
|
126
127
|
# Register adapters
|
127
128
|
|
128
|
-
ADAPTERS:
|
129
|
+
ADAPTERS: dict[type, Callable[[Any], Any]] = {
|
129
130
|
Decimal: convert_numeric_in,
|
130
131
|
uuid.UUID: convert_uuid_in,
|
131
132
|
datetime.time: convert_time_in,
|
@@ -143,7 +144,7 @@ for value_type, adapter in ADAPTERS.items():
|
|
143
144
|
# Out
|
144
145
|
|
145
146
|
|
146
|
-
def decode_to_string(converter:
|
147
|
+
def decode_to_string(converter: Callable[[str], Any]):
|
147
148
|
"""
|
148
149
|
This means we can use our converters with string and bytes. They are
|
149
150
|
passed bytes when used directly via SQLite, and are passed strings when
|
@@ -151,7 +152,7 @@ def decode_to_string(converter: t.Callable[[str], t.Any]):
|
|
151
152
|
"""
|
152
153
|
|
153
154
|
@wraps(converter)
|
154
|
-
def wrapper(value:
|
155
|
+
def wrapper(value: Union[str, bytes]) -> Any:
|
155
156
|
if isinstance(value, bytes):
|
156
157
|
return converter(value.decode("utf8"))
|
157
158
|
elif isinstance(value, str):
|
@@ -247,7 +248,7 @@ def convert_timestamptz_out(value: str) -> datetime.datetime:
|
|
247
248
|
|
248
249
|
|
249
250
|
@decode_to_string
|
250
|
-
def convert_array_out(value: str) ->
|
251
|
+
def convert_array_out(value: str) -> list:
|
251
252
|
"""
|
252
253
|
If the value if from an array column, deserialise the string back into a
|
253
254
|
list.
|
@@ -255,7 +256,7 @@ def convert_array_out(value: str) -> t.List:
|
|
255
256
|
return load_json(value)
|
256
257
|
|
257
258
|
|
258
|
-
def convert_complex_array_out(value: bytes, converter:
|
259
|
+
def convert_complex_array_out(value: bytes, converter: Callable):
|
259
260
|
"""
|
260
261
|
This is used to handle arrays of things like timestamps, which we can't
|
261
262
|
just load from JSON without doing additional work to convert the elements
|
@@ -263,7 +264,7 @@ def convert_complex_array_out(value: bytes, converter: t.Callable):
|
|
263
264
|
"""
|
264
265
|
parsed = load_json(value.decode("utf8"))
|
265
266
|
|
266
|
-
def convert_list(list_value:
|
267
|
+
def convert_list(list_value: list):
|
267
268
|
output = []
|
268
269
|
|
269
270
|
for value in list_value:
|
@@ -284,7 +285,7 @@ def convert_complex_array_out(value: bytes, converter: t.Callable):
|
|
284
285
|
|
285
286
|
|
286
287
|
@decode_to_string
|
287
|
-
def convert_M2M_out(value: str) ->
|
288
|
+
def convert_M2M_out(value: str) -> list:
|
288
289
|
return value.split(",")
|
289
290
|
|
290
291
|
|
@@ -335,7 +336,7 @@ class AsyncBatch(BaseBatch):
|
|
335
336
|
batch_size: int
|
336
337
|
|
337
338
|
# Set internally
|
338
|
-
_cursor:
|
339
|
+
_cursor: Optional[Cursor] = None
|
339
340
|
|
340
341
|
@property
|
341
342
|
def cursor(self) -> Cursor:
|
@@ -343,14 +344,14 @@ class AsyncBatch(BaseBatch):
|
|
343
344
|
raise ValueError("_cursor not set")
|
344
345
|
return self._cursor
|
345
346
|
|
346
|
-
async def next(self) ->
|
347
|
+
async def next(self) -> list[dict]:
|
347
348
|
data = await self.cursor.fetchmany(self.batch_size)
|
348
349
|
return await self.query._process_results(data)
|
349
350
|
|
350
351
|
def __aiter__(self: Self) -> Self:
|
351
352
|
return self
|
352
353
|
|
353
|
-
async def __anext__(self) ->
|
354
|
+
async def __anext__(self) -> list[dict]:
|
354
355
|
response = await self.next()
|
355
356
|
if response == []:
|
356
357
|
raise StopAsyncIteration()
|
@@ -404,9 +405,9 @@ class Atomic(BaseAtomic):
|
|
404
405
|
):
|
405
406
|
self.engine = engine
|
406
407
|
self.transaction_type = transaction_type
|
407
|
-
self.queries:
|
408
|
+
self.queries: list[Union[Query, DDL]] = []
|
408
409
|
|
409
|
-
def add(self, *query:
|
410
|
+
def add(self, *query: Union[Query, DDL]):
|
410
411
|
self.queries += list(query)
|
411
412
|
|
412
413
|
async def run(self):
|
@@ -546,7 +547,7 @@ class SQLiteTransaction(BaseTransaction):
|
|
546
547
|
self._savepoint_id += 1
|
547
548
|
return self._savepoint_id
|
548
549
|
|
549
|
-
async def savepoint(self, name:
|
550
|
+
async def savepoint(self, name: Optional[str] = None) -> Savepoint:
|
550
551
|
name = name or f"savepoint_{self.get_savepoint_id()}"
|
551
552
|
validate_savepoint_name(name)
|
552
553
|
await self.connection.execute(f"SAVEPOINT {name}")
|
@@ -576,7 +577,7 @@ class SQLiteTransaction(BaseTransaction):
|
|
576
577
|
###############################################################################
|
577
578
|
|
578
579
|
|
579
|
-
def dict_factory(cursor, row) ->
|
580
|
+
def dict_factory(cursor, row) -> dict:
|
580
581
|
return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}
|
581
582
|
|
582
583
|
|
@@ -672,7 +673,7 @@ class SQLiteEngine(Engine[SQLiteTransaction]):
|
|
672
673
|
###########################################################################
|
673
674
|
|
674
675
|
async def batch(
|
675
|
-
self, query: Query, batch_size: int = 100, node:
|
676
|
+
self, query: Query, batch_size: int = 100, node: Optional[str] = None
|
676
677
|
) -> AsyncBatch:
|
677
678
|
"""
|
678
679
|
:param query:
|
@@ -698,7 +699,7 @@ class SQLiteEngine(Engine[SQLiteTransaction]):
|
|
698
699
|
|
699
700
|
###########################################################################
|
700
701
|
|
701
|
-
async def _get_inserted_pk(self, cursor, table:
|
702
|
+
async def _get_inserted_pk(self, cursor, table: type[Table]) -> Any:
|
702
703
|
"""
|
703
704
|
If the `pk` column is a non-integer then `ROWID` and `pk` will return
|
704
705
|
different types. Need to query by `lastrowid` to get `pk`s in SQLite
|
@@ -714,9 +715,9 @@ class SQLiteEngine(Engine[SQLiteTransaction]):
|
|
714
715
|
async def _run_in_new_connection(
|
715
716
|
self,
|
716
717
|
query: str,
|
717
|
-
args:
|
718
|
+
args: Optional[list[Any]] = None,
|
718
719
|
query_type: str = "generic",
|
719
|
-
table:
|
720
|
+
table: Optional[type[Table]] = None,
|
720
721
|
):
|
721
722
|
if args is None:
|
722
723
|
args = []
|
@@ -740,9 +741,9 @@ class SQLiteEngine(Engine[SQLiteTransaction]):
|
|
740
741
|
self,
|
741
742
|
connection,
|
742
743
|
query: str,
|
743
|
-
args:
|
744
|
+
args: Optional[list[Any]] = None,
|
744
745
|
query_type: str = "generic",
|
745
|
-
table:
|
746
|
+
table: Optional[type[Table]] = None,
|
746
747
|
):
|
747
748
|
"""
|
748
749
|
This is used when a transaction is currently active.
|
piccolo/query/base.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
3
|
+
from collections.abc import Generator, Sequence
|
4
4
|
from time import time
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, Optional, Union, cast
|
5
6
|
|
6
7
|
from piccolo.columns.column_types import JSON, JSONB
|
7
8
|
from piccolo.custom_types import QueryResponseType, TableInstance
|
@@ -12,7 +13,7 @@ from piccolo.utils.encoding import load_json
|
|
12
13
|
from piccolo.utils.objects import make_nested_object
|
13
14
|
from piccolo.utils.sync import run_sync
|
14
15
|
|
15
|
-
if
|
16
|
+
if TYPE_CHECKING: # pragma: no cover
|
16
17
|
from piccolo.query.mixins import OutputDelegate
|
17
18
|
from piccolo.table import Table # noqa
|
18
19
|
|
@@ -26,13 +27,13 @@ class Timer:
|
|
26
27
|
print(f"Duration: {self.end - self.start}s")
|
27
28
|
|
28
29
|
|
29
|
-
class Query(
|
30
|
+
class Query(Generic[TableInstance, QueryResponseType]):
|
30
31
|
__slots__ = ("table", "_frozen_querystrings")
|
31
32
|
|
32
33
|
def __init__(
|
33
34
|
self,
|
34
|
-
table:
|
35
|
-
frozen_querystrings:
|
35
|
+
table: type[TableInstance],
|
36
|
+
frozen_querystrings: Optional[Sequence[QueryString]] = None,
|
36
37
|
):
|
37
38
|
self.table = table
|
38
39
|
self._frozen_querystrings = frozen_querystrings
|
@@ -55,21 +56,21 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
55
56
|
if hasattr(self, "_raw_response_callback"):
|
56
57
|
self._raw_response_callback(raw)
|
57
58
|
|
58
|
-
output:
|
59
|
+
output: Optional[OutputDelegate] = getattr(
|
59
60
|
self, "output_delegate", None
|
60
61
|
)
|
61
62
|
|
62
63
|
#######################################################################
|
63
64
|
|
64
65
|
if output and output._output.load_json:
|
65
|
-
columns_delegate:
|
66
|
+
columns_delegate: Optional[ColumnsDelegate] = getattr(
|
66
67
|
self, "columns_delegate", None
|
67
68
|
)
|
68
69
|
|
69
|
-
json_column_names:
|
70
|
+
json_column_names: list[str] = []
|
70
71
|
|
71
72
|
if columns_delegate is not None:
|
72
|
-
json_columns:
|
73
|
+
json_columns: list[Union[JSON, JSONB]] = []
|
73
74
|
|
74
75
|
for column in columns_delegate.selected_columns:
|
75
76
|
if isinstance(column, (JSON, JSONB)):
|
@@ -107,12 +108,12 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
107
108
|
if output:
|
108
109
|
if output._output.as_objects:
|
109
110
|
if output._output.nested:
|
110
|
-
return
|
111
|
+
return cast(
|
111
112
|
QueryResponseType,
|
112
113
|
[make_nested_object(row, self.table) for row in raw],
|
113
114
|
)
|
114
115
|
else:
|
115
|
-
return
|
116
|
+
return cast(
|
116
117
|
QueryResponseType,
|
117
118
|
[
|
118
119
|
self.table(**columns, _exists_in_db=True)
|
@@ -120,7 +121,7 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
120
121
|
],
|
121
122
|
)
|
122
123
|
|
123
|
-
return
|
124
|
+
return cast(QueryResponseType, raw)
|
124
125
|
|
125
126
|
def _validate(self):
|
126
127
|
"""
|
@@ -130,7 +131,7 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
130
131
|
"""
|
131
132
|
pass
|
132
133
|
|
133
|
-
def __await__(self) ->
|
134
|
+
def __await__(self) -> Generator[None, None, QueryResponseType]:
|
134
135
|
"""
|
135
136
|
If the user doesn't explicity call .run(), proxy to it as a
|
136
137
|
convenience.
|
@@ -138,7 +139,7 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
138
139
|
return self.run().__await__()
|
139
140
|
|
140
141
|
async def _run(
|
141
|
-
self, node:
|
142
|
+
self, node: Optional[str] = None, in_pool: bool = True
|
142
143
|
) -> QueryResponseType:
|
143
144
|
"""
|
144
145
|
Run the query on the database.
|
@@ -184,16 +185,16 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
184
185
|
processed_results = await self._process_results(results)
|
185
186
|
|
186
187
|
responses.append(processed_results)
|
187
|
-
return
|
188
|
+
return cast(QueryResponseType, responses)
|
188
189
|
|
189
190
|
async def run(
|
190
|
-
self, node:
|
191
|
+
self, node: Optional[str] = None, in_pool: bool = True
|
191
192
|
) -> QueryResponseType:
|
192
193
|
return await self._run(node=node, in_pool=in_pool)
|
193
194
|
|
194
195
|
def run_sync(
|
195
196
|
self,
|
196
|
-
node:
|
197
|
+
node: Optional[str] = None,
|
197
198
|
timed: bool = False,
|
198
199
|
in_pool: bool = False,
|
199
200
|
) -> QueryResponseType:
|
@@ -217,7 +218,7 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
217
218
|
with Timer():
|
218
219
|
return run_sync(coroutine)
|
219
220
|
|
220
|
-
async def response_handler(self, response:
|
221
|
+
async def response_handler(self, response: list) -> Any:
|
221
222
|
"""
|
222
223
|
Subclasses can override this to modify the raw response returned by
|
223
224
|
the database driver.
|
@@ -227,23 +228,23 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
227
228
|
###########################################################################
|
228
229
|
|
229
230
|
@property
|
230
|
-
def sqlite_querystrings(self) ->
|
231
|
+
def sqlite_querystrings(self) -> Sequence[QueryString]:
|
231
232
|
raise NotImplementedError
|
232
233
|
|
233
234
|
@property
|
234
|
-
def postgres_querystrings(self) ->
|
235
|
+
def postgres_querystrings(self) -> Sequence[QueryString]:
|
235
236
|
raise NotImplementedError
|
236
237
|
|
237
238
|
@property
|
238
|
-
def cockroach_querystrings(self) ->
|
239
|
+
def cockroach_querystrings(self) -> Sequence[QueryString]:
|
239
240
|
raise NotImplementedError
|
240
241
|
|
241
242
|
@property
|
242
|
-
def default_querystrings(self) ->
|
243
|
+
def default_querystrings(self) -> Sequence[QueryString]:
|
243
244
|
raise NotImplementedError
|
244
245
|
|
245
246
|
@property
|
246
|
-
def querystrings(self) ->
|
247
|
+
def querystrings(self) -> Sequence[QueryString]:
|
247
248
|
"""
|
248
249
|
Calls the correct underlying method, depending on the current engine.
|
249
250
|
"""
|
@@ -367,7 +368,7 @@ class FrozenQuery:
|
|
367
368
|
class DDL:
|
368
369
|
__slots__ = ("table",)
|
369
370
|
|
370
|
-
def __init__(self, table:
|
371
|
+
def __init__(self, table: type[Table], **kwargs):
|
371
372
|
self.table = table
|
372
373
|
|
373
374
|
@property
|
@@ -379,23 +380,23 @@ class DDL:
|
|
379
380
|
raise ValueError("Engine isn't defined.")
|
380
381
|
|
381
382
|
@property
|
382
|
-
def sqlite_ddl(self) ->
|
383
|
+
def sqlite_ddl(self) -> Sequence[str]:
|
383
384
|
raise NotImplementedError
|
384
385
|
|
385
386
|
@property
|
386
|
-
def postgres_ddl(self) ->
|
387
|
+
def postgres_ddl(self) -> Sequence[str]:
|
387
388
|
raise NotImplementedError
|
388
389
|
|
389
390
|
@property
|
390
|
-
def cockroach_ddl(self) ->
|
391
|
+
def cockroach_ddl(self) -> Sequence[str]:
|
391
392
|
raise NotImplementedError
|
392
393
|
|
393
394
|
@property
|
394
|
-
def default_ddl(self) ->
|
395
|
+
def default_ddl(self) -> Sequence[str]:
|
395
396
|
raise NotImplementedError
|
396
397
|
|
397
398
|
@property
|
398
|
-
def ddl(self) ->
|
399
|
+
def ddl(self) -> Sequence[str]:
|
399
400
|
"""
|
400
401
|
Calls the correct underlying method, depending on the current engine.
|
401
402
|
"""
|
@@ -1,4 +1,11 @@
|
|
1
1
|
from .aggregate import Avg, Count, Max, Min, Sum
|
2
|
+
from .array import (
|
3
|
+
ArrayAppend,
|
4
|
+
ArrayCat,
|
5
|
+
ArrayPrepend,
|
6
|
+
ArrayRemove,
|
7
|
+
ArrayReplace,
|
8
|
+
)
|
2
9
|
from .datetime import Day, Extract, Hour, Month, Second, Strftime, Year
|
3
10
|
from .math import Abs, Ceil, Floor, Round
|
4
11
|
from .string import Concat, Length, Lower, Ltrim, Reverse, Rtrim, Upper
|
@@ -30,4 +37,9 @@ __all__ = (
|
|
30
37
|
"Sum",
|
31
38
|
"Upper",
|
32
39
|
"Year",
|
40
|
+
"ArrayAppend",
|
41
|
+
"ArrayCat",
|
42
|
+
"ArrayPrepend",
|
43
|
+
"ArrayRemove",
|
44
|
+
"ArrayReplace",
|
33
45
|
)
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
from collections.abc import Sequence
|
2
|
+
from typing import Optional
|
2
3
|
|
3
4
|
from piccolo.columns.base import Column
|
4
5
|
from piccolo.querystring import QueryString
|
@@ -51,8 +52,8 @@ class Count(QueryString):
|
|
51
52
|
|
52
53
|
def __init__(
|
53
54
|
self,
|
54
|
-
column:
|
55
|
-
distinct:
|
55
|
+
column: Optional[Column] = None,
|
56
|
+
distinct: Optional[Sequence[Column]] = None,
|
56
57
|
alias: str = "count",
|
57
58
|
):
|
58
59
|
"""
|
@@ -0,0 +1,151 @@
|
|
1
|
+
from typing import Union
|
2
|
+
|
3
|
+
from typing_extensions import TypeAlias
|
4
|
+
|
5
|
+
from piccolo.columns.base import Column
|
6
|
+
from piccolo.querystring import QueryString
|
7
|
+
|
8
|
+
ArrayType: TypeAlias = Union[Column, QueryString, list[object]]
|
9
|
+
ArrayItemType: TypeAlias = Union[Column, QueryString, object]
|
10
|
+
|
11
|
+
|
12
|
+
class ArrayQueryString(QueryString):
|
13
|
+
def __add__(self, array: ArrayType):
|
14
|
+
"""
|
15
|
+
QueryString will use the ``+`` operator by default for addition, but
|
16
|
+
for arrays we want to concatenate them instead.
|
17
|
+
"""
|
18
|
+
return ArrayCat(array_1=self, array_2=array)
|
19
|
+
|
20
|
+
def __radd__(self, array: ArrayType):
|
21
|
+
return ArrayCat(array_1=array, array_2=self)
|
22
|
+
|
23
|
+
|
24
|
+
class ArrayCat(ArrayQueryString):
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
array_1: ArrayType,
|
28
|
+
array_2: ArrayType,
|
29
|
+
):
|
30
|
+
"""
|
31
|
+
Concatenate two arrays.
|
32
|
+
|
33
|
+
:param array_1:
|
34
|
+
These values will be at the start of the new array.
|
35
|
+
:param array_2:
|
36
|
+
These values will be at the end of the new array.
|
37
|
+
|
38
|
+
"""
|
39
|
+
for value in (array_1, array_2):
|
40
|
+
if isinstance(value, Column):
|
41
|
+
engine_type = value._meta.engine_type
|
42
|
+
if engine_type not in ("postgres", "cockroach"):
|
43
|
+
raise ValueError(
|
44
|
+
"Only Postgres and Cockroach support array "
|
45
|
+
"concatenation."
|
46
|
+
)
|
47
|
+
|
48
|
+
super().__init__("array_cat({}, {})", array_1, array_2)
|
49
|
+
|
50
|
+
|
51
|
+
class ArrayAppend(ArrayQueryString):
|
52
|
+
def __init__(self, array: ArrayType, value: ArrayItemType):
|
53
|
+
"""
|
54
|
+
Append an element to the end of an array.
|
55
|
+
|
56
|
+
:param column:
|
57
|
+
Identifies the column.
|
58
|
+
:param value:
|
59
|
+
The value to append.
|
60
|
+
|
61
|
+
"""
|
62
|
+
if isinstance(array, Column):
|
63
|
+
engine_type = array._meta.engine_type
|
64
|
+
if engine_type not in ("postgres", "cockroach"):
|
65
|
+
raise ValueError(
|
66
|
+
"Only Postgres and Cockroach support array appending."
|
67
|
+
)
|
68
|
+
|
69
|
+
super().__init__("array_append({}, {})", array, value)
|
70
|
+
|
71
|
+
|
72
|
+
class ArrayPrepend(ArrayQueryString):
|
73
|
+
def __init__(self, array: ArrayType, value: ArrayItemType):
|
74
|
+
"""
|
75
|
+
Append an element to the beginning of an array.
|
76
|
+
|
77
|
+
:param value:
|
78
|
+
The value to prepend.
|
79
|
+
:param column:
|
80
|
+
Identifies the column.
|
81
|
+
|
82
|
+
"""
|
83
|
+
if isinstance(array, Column):
|
84
|
+
engine_type = array._meta.engine_type
|
85
|
+
if engine_type not in ("postgres", "cockroach"):
|
86
|
+
raise ValueError(
|
87
|
+
"Only Postgres and Cockroach support array prepending."
|
88
|
+
)
|
89
|
+
|
90
|
+
super().__init__("array_prepend({}, {})", value, array)
|
91
|
+
|
92
|
+
|
93
|
+
class ArrayReplace(ArrayQueryString):
|
94
|
+
def __init__(
|
95
|
+
self,
|
96
|
+
array: ArrayType,
|
97
|
+
old_value: ArrayItemType,
|
98
|
+
new_value: ArrayItemType,
|
99
|
+
):
|
100
|
+
"""
|
101
|
+
Replace each array element equal to the given value with a new value.
|
102
|
+
|
103
|
+
:param column:
|
104
|
+
Identifies the column.
|
105
|
+
:param old_value:
|
106
|
+
The old value to be replaced.
|
107
|
+
:param new_value:
|
108
|
+
The new value we are replacing with.
|
109
|
+
|
110
|
+
"""
|
111
|
+
if isinstance(array, Column):
|
112
|
+
engine_type = array._meta.engine_type
|
113
|
+
if engine_type not in ("postgres", "cockroach"):
|
114
|
+
raise ValueError(
|
115
|
+
"Only Postgres and Cockroach support array substitution."
|
116
|
+
)
|
117
|
+
|
118
|
+
super().__init__(
|
119
|
+
"array_replace({}, {}, {})", array, old_value, new_value
|
120
|
+
)
|
121
|
+
|
122
|
+
|
123
|
+
class ArrayRemove(ArrayQueryString):
|
124
|
+
def __init__(self, array: ArrayType, value: ArrayItemType):
|
125
|
+
"""
|
126
|
+
Remove all elements equal to the given value
|
127
|
+
from the array (array must be one-dimensional).
|
128
|
+
|
129
|
+
:param column:
|
130
|
+
Identifies the column.
|
131
|
+
:param value:
|
132
|
+
The value to remove.
|
133
|
+
|
134
|
+
"""
|
135
|
+
if isinstance(array, Column):
|
136
|
+
engine_type = array._meta.engine_type
|
137
|
+
if engine_type not in ("postgres", "cockroach"):
|
138
|
+
raise ValueError(
|
139
|
+
"Only Postgres and Cockroach support array removing."
|
140
|
+
)
|
141
|
+
|
142
|
+
super().__init__("array_remove({}, {})", array, value)
|
143
|
+
|
144
|
+
|
145
|
+
__all__ = (
|
146
|
+
"ArrayCat",
|
147
|
+
"ArrayAppend",
|
148
|
+
"ArrayPrepend",
|
149
|
+
"ArrayReplace",
|
150
|
+
"ArrayRemove",
|
151
|
+
)
|
piccolo/query/functions/base.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
from typing import Optional, Union
|
2
2
|
|
3
3
|
from piccolo.columns.base import Column
|
4
4
|
from piccolo.querystring import QueryString
|
@@ -9,8 +9,8 @@ class Function(QueryString):
|
|
9
9
|
|
10
10
|
def __init__(
|
11
11
|
self,
|
12
|
-
identifier:
|
13
|
-
alias:
|
12
|
+
identifier: Union[Column, QueryString, str],
|
13
|
+
alias: Optional[str] = None,
|
14
14
|
):
|
15
15
|
alias = alias or self.__class__.__name__.lower()
|
16
16
|
|