plain.models 0.49.2__py3-none-any.whl → 0.51.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.
- plain/models/CHANGELOG.md +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +13 -4
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,28 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import ipaddress
|
2
4
|
import json
|
5
|
+
from collections.abc import Callable
|
3
6
|
from functools import lru_cache, partial
|
7
|
+
from typing import TYPE_CHECKING, Any
|
4
8
|
|
5
|
-
from psycopg import ClientCursor, errors
|
6
|
-
from psycopg.types import numeric
|
7
|
-
from psycopg.types.json import Jsonb
|
9
|
+
from psycopg import ClientCursor, errors # type: ignore[import-untyped]
|
10
|
+
from psycopg.types import numeric # type: ignore[import-untyped]
|
11
|
+
from psycopg.types.json import Jsonb # type: ignore[import-untyped]
|
8
12
|
|
9
13
|
from plain.models.backends.base.operations import BaseDatabaseOperations
|
10
14
|
from plain.models.backends.utils import split_tzname_delta
|
11
15
|
from plain.models.constants import OnConflict
|
12
16
|
from plain.utils.regex_helper import _lazy_re_compile
|
13
17
|
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from plain.models.fields import Field
|
20
|
+
|
14
21
|
|
15
22
|
@lru_cache
|
16
|
-
def get_json_dumps(
|
23
|
+
def get_json_dumps(
|
24
|
+
encoder: type[json.JSONEncoder] | None,
|
25
|
+
) -> Callable[..., str]:
|
17
26
|
if encoder is None:
|
18
27
|
return json.dumps
|
19
28
|
return partial(json.dumps, cls=encoder)
|
@@ -47,7 +56,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
47
56
|
"PositiveBigIntegerField": numeric.Int8,
|
48
57
|
}
|
49
58
|
|
50
|
-
def unification_cast_sql(self, output_field):
|
59
|
+
def unification_cast_sql(self, output_field: Field) -> str:
|
51
60
|
internal_type = output_field.get_internal_type()
|
52
61
|
if internal_type in (
|
53
62
|
"GenericIPAddressField",
|
@@ -61,15 +70,17 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
61
70
|
# PostgreSQL configuration so we need to explicitly cast them.
|
62
71
|
# We must also remove components of the type within brackets:
|
63
72
|
# varchar(255) -> varchar.
|
64
|
-
|
65
|
-
|
66
|
-
|
73
|
+
db_type = output_field.db_type(self.connection)
|
74
|
+
if db_type:
|
75
|
+
return "CAST(%s AS {})".format(db_type.split("(")[0])
|
67
76
|
return "%s"
|
68
77
|
|
69
78
|
# EXTRACT format cannot be passed in parameters.
|
70
79
|
_extract_format_re = _lazy_re_compile(r"[A-Z_]+")
|
71
80
|
|
72
|
-
def date_extract_sql(
|
81
|
+
def date_extract_sql(
|
82
|
+
self, lookup_type: str, sql: str, params: list[Any] | tuple[Any, ...]
|
83
|
+
) -> tuple[str, list[Any] | tuple[Any, ...]]:
|
73
84
|
# https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
|
74
85
|
if lookup_type == "week_day":
|
75
86
|
# For consistency across backends, we return Sunday=1, Saturday=7.
|
@@ -84,65 +95,97 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
84
95
|
raise ValueError(f"Invalid lookup type: {lookup_type!r}")
|
85
96
|
return f"EXTRACT({lookup_type} FROM {sql})", params
|
86
97
|
|
87
|
-
def date_trunc_sql(
|
98
|
+
def date_trunc_sql(
|
99
|
+
self,
|
100
|
+
lookup_type: str,
|
101
|
+
sql: str,
|
102
|
+
params: list[Any] | tuple[Any, ...],
|
103
|
+
tzname: str | None = None,
|
104
|
+
) -> tuple[str, tuple[Any, ...]]:
|
88
105
|
sql, params = self._convert_sql_to_tz(sql, params, tzname)
|
89
106
|
# https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
90
107
|
return f"DATE_TRUNC(%s, {sql})", (lookup_type, *params)
|
91
108
|
|
92
|
-
def _prepare_tzname_delta(self, tzname):
|
109
|
+
def _prepare_tzname_delta(self, tzname: str) -> str:
|
93
110
|
tzname, sign, offset = split_tzname_delta(tzname)
|
94
111
|
if offset:
|
95
112
|
sign = "-" if sign == "+" else "+"
|
96
113
|
return f"{tzname}{sign}{offset}"
|
97
114
|
return tzname
|
98
115
|
|
99
|
-
def _convert_sql_to_tz(
|
116
|
+
def _convert_sql_to_tz(
|
117
|
+
self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
|
118
|
+
) -> tuple[str, list[Any] | tuple[Any, ...]]:
|
100
119
|
if tzname:
|
101
120
|
tzname_param = self._prepare_tzname_delta(tzname)
|
102
121
|
return f"{sql} AT TIME ZONE %s", (*params, tzname_param)
|
103
122
|
return sql, params
|
104
123
|
|
105
|
-
def datetime_cast_date_sql(
|
124
|
+
def datetime_cast_date_sql(
|
125
|
+
self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
|
126
|
+
) -> tuple[str, list[Any] | tuple[Any, ...]]:
|
106
127
|
sql, params = self._convert_sql_to_tz(sql, params, tzname)
|
107
128
|
return f"({sql})::date", params
|
108
129
|
|
109
|
-
def datetime_cast_time_sql(
|
130
|
+
def datetime_cast_time_sql(
|
131
|
+
self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
|
132
|
+
) -> tuple[str, list[Any] | tuple[Any, ...]]:
|
110
133
|
sql, params = self._convert_sql_to_tz(sql, params, tzname)
|
111
134
|
return f"({sql})::time", params
|
112
135
|
|
113
|
-
def datetime_extract_sql(
|
136
|
+
def datetime_extract_sql(
|
137
|
+
self,
|
138
|
+
lookup_type: str,
|
139
|
+
sql: str,
|
140
|
+
params: list[Any] | tuple[Any, ...],
|
141
|
+
tzname: str | None,
|
142
|
+
) -> tuple[str, list[Any] | tuple[Any, ...]]:
|
114
143
|
sql, params = self._convert_sql_to_tz(sql, params, tzname)
|
115
144
|
if lookup_type == "second":
|
116
145
|
# Truncate fractional seconds.
|
117
146
|
return f"EXTRACT(SECOND FROM DATE_TRUNC(%s, {sql}))", ("second", *params)
|
118
147
|
return self.date_extract_sql(lookup_type, sql, params)
|
119
148
|
|
120
|
-
def datetime_trunc_sql(
|
149
|
+
def datetime_trunc_sql(
|
150
|
+
self,
|
151
|
+
lookup_type: str,
|
152
|
+
sql: str,
|
153
|
+
params: list[Any] | tuple[Any, ...],
|
154
|
+
tzname: str | None,
|
155
|
+
) -> tuple[str, tuple[Any, ...]]:
|
121
156
|
sql, params = self._convert_sql_to_tz(sql, params, tzname)
|
122
157
|
# https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
123
158
|
return f"DATE_TRUNC(%s, {sql})", (lookup_type, *params)
|
124
159
|
|
125
|
-
def time_extract_sql(
|
160
|
+
def time_extract_sql(
|
161
|
+
self, lookup_type: str, sql: str, params: list[Any] | tuple[Any, ...]
|
162
|
+
) -> tuple[str, list[Any] | tuple[Any, ...]]:
|
126
163
|
if lookup_type == "second":
|
127
164
|
# Truncate fractional seconds.
|
128
165
|
return f"EXTRACT(SECOND FROM DATE_TRUNC(%s, {sql}))", ("second", *params)
|
129
166
|
return self.date_extract_sql(lookup_type, sql, params)
|
130
167
|
|
131
|
-
def time_trunc_sql(
|
168
|
+
def time_trunc_sql(
|
169
|
+
self,
|
170
|
+
lookup_type: str,
|
171
|
+
sql: str,
|
172
|
+
params: list[Any] | tuple[Any, ...],
|
173
|
+
tzname: str | None = None,
|
174
|
+
) -> tuple[str, tuple[Any, ...]]:
|
132
175
|
sql, params = self._convert_sql_to_tz(sql, params, tzname)
|
133
176
|
return f"DATE_TRUNC(%s, {sql})::time", (lookup_type, *params)
|
134
177
|
|
135
|
-
def deferrable_sql(self):
|
178
|
+
def deferrable_sql(self) -> str:
|
136
179
|
return " DEFERRABLE INITIALLY DEFERRED"
|
137
180
|
|
138
|
-
def fetch_returned_insert_rows(self, cursor):
|
181
|
+
def fetch_returned_insert_rows(self, cursor: Any) -> list[Any]:
|
139
182
|
"""
|
140
183
|
Given a cursor object that has just performed an INSERT...RETURNING
|
141
184
|
statement into a table, return the tuple of returned data.
|
142
185
|
"""
|
143
186
|
return cursor.fetchall()
|
144
187
|
|
145
|
-
def lookup_cast(self, lookup_type, internal_type=None):
|
188
|
+
def lookup_cast(self, lookup_type: str, internal_type: str | None = None) -> str:
|
146
189
|
lookup = "%s"
|
147
190
|
|
148
191
|
if lookup_type == "isnull" and internal_type in (
|
@@ -175,27 +218,27 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
175
218
|
|
176
219
|
return lookup
|
177
220
|
|
178
|
-
def no_limit_value(self):
|
221
|
+
def no_limit_value(self) -> None:
|
179
222
|
return None
|
180
223
|
|
181
|
-
def prepare_sql_script(self, sql):
|
224
|
+
def prepare_sql_script(self, sql: str) -> list[str]:
|
182
225
|
return [sql]
|
183
226
|
|
184
|
-
def quote_name(self, name):
|
227
|
+
def quote_name(self, name: str) -> str:
|
185
228
|
if name.startswith('"') and name.endswith('"'):
|
186
229
|
return name # Quoting once is enough.
|
187
230
|
return f'"{name}"'
|
188
231
|
|
189
|
-
def compose_sql(self, sql, params):
|
232
|
+
def compose_sql(self, sql: str, params: Any) -> bytes:
|
190
233
|
return ClientCursor(self.connection.connection).mogrify(sql, params)
|
191
234
|
|
192
|
-
def set_time_zone_sql(self):
|
235
|
+
def set_time_zone_sql(self) -> str:
|
193
236
|
return "SELECT set_config('TimeZone', %s, false)"
|
194
237
|
|
195
|
-
def prep_for_iexact_query(self, x):
|
238
|
+
def prep_for_iexact_query(self, x: str) -> str:
|
196
239
|
return x
|
197
240
|
|
198
|
-
def max_name_length(self):
|
241
|
+
def max_name_length(self) -> int:
|
199
242
|
"""
|
200
243
|
Return the maximum length of an identifier.
|
201
244
|
|
@@ -208,59 +251,77 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
208
251
|
"""
|
209
252
|
return 63
|
210
253
|
|
211
|
-
def distinct_sql(
|
254
|
+
def distinct_sql(
|
255
|
+
self, fields: list[str], params: list[Any] | tuple[Any, ...]
|
256
|
+
) -> tuple[list[str], list[Any]]:
|
212
257
|
if fields:
|
213
258
|
params = [param for param_list in params for param in param_list]
|
214
259
|
return (["DISTINCT ON ({})".format(", ".join(fields))], params)
|
215
260
|
else:
|
216
261
|
return ["DISTINCT"], []
|
217
262
|
|
218
|
-
def last_executed_query(self, cursor, sql, params):
|
263
|
+
def last_executed_query(self, cursor: Any, sql: str, params: Any) -> bytes | None:
|
219
264
|
try:
|
220
265
|
return self.compose_sql(sql, params)
|
221
266
|
except errors.DataError:
|
222
267
|
return None
|
223
268
|
|
224
|
-
def return_insert_columns(self, fields):
|
269
|
+
def return_insert_columns(self, fields: list[Field]) -> tuple[str, tuple[Any, ...]]:
|
225
270
|
if not fields:
|
226
271
|
return "", ()
|
227
272
|
columns = [
|
228
|
-
f"{self.quote_name(field.model.
|
273
|
+
f"{self.quote_name(field.model.model_options.db_table)}.{self.quote_name(field.column)}"
|
229
274
|
for field in fields
|
230
275
|
]
|
231
276
|
return "RETURNING {}".format(", ".join(columns)), ()
|
232
277
|
|
233
|
-
def bulk_insert_sql(
|
278
|
+
def bulk_insert_sql(
|
279
|
+
self, fields: list[Field], placeholder_rows: list[list[str]]
|
280
|
+
) -> str:
|
234
281
|
placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
|
235
282
|
values_sql = ", ".join(f"({sql})" for sql in placeholder_rows_sql)
|
236
283
|
return "VALUES " + values_sql
|
237
284
|
|
238
|
-
def adapt_integerfield_value(
|
285
|
+
def adapt_integerfield_value(
|
286
|
+
self, value: int | Any | None, internal_type: str
|
287
|
+
) -> int | Any | None:
|
239
288
|
if value is None or hasattr(value, "resolve_expression"):
|
240
289
|
return value
|
241
290
|
return self.integerfield_type_map[internal_type](value)
|
242
291
|
|
243
|
-
def adapt_datefield_value(self, value):
|
292
|
+
def adapt_datefield_value(self, value: Any) -> Any:
|
244
293
|
return value
|
245
294
|
|
246
|
-
def adapt_datetimefield_value(self, value):
|
295
|
+
def adapt_datetimefield_value(self, value: Any) -> Any:
|
247
296
|
return value
|
248
297
|
|
249
|
-
def adapt_timefield_value(self, value):
|
298
|
+
def adapt_timefield_value(self, value: Any) -> Any:
|
250
299
|
return value
|
251
300
|
|
252
|
-
def adapt_decimalfield_value(
|
301
|
+
def adapt_decimalfield_value(
|
302
|
+
self,
|
303
|
+
value: Any,
|
304
|
+
max_digits: int | None = None,
|
305
|
+
decimal_places: int | None = None,
|
306
|
+
) -> Any:
|
253
307
|
return value
|
254
308
|
|
255
|
-
def adapt_ipaddressfield_value(
|
309
|
+
def adapt_ipaddressfield_value(
|
310
|
+
self, value: str | None
|
311
|
+
) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
|
256
312
|
if value:
|
257
313
|
return ipaddress.ip_address(value)
|
258
314
|
return None
|
259
315
|
|
260
|
-
def adapt_json_value(self, value, encoder):
|
316
|
+
def adapt_json_value(self, value: Any, encoder: type[json.JSONEncoder]) -> Jsonb:
|
261
317
|
return Jsonb(value, dumps=get_json_dumps(encoder))
|
262
318
|
|
263
|
-
def subtract_temporals(
|
319
|
+
def subtract_temporals(
|
320
|
+
self,
|
321
|
+
internal_type: str,
|
322
|
+
lhs: tuple[str, list[Any] | tuple[Any, ...]],
|
323
|
+
rhs: tuple[str, list[Any] | tuple[Any, ...]],
|
324
|
+
) -> tuple[str, tuple[Any, ...]]:
|
264
325
|
if internal_type == "DateField":
|
265
326
|
lhs_sql, lhs_params = lhs
|
266
327
|
rhs_sql, rhs_params = rhs
|
@@ -268,7 +329,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
268
329
|
return f"(interval '1 day' * ({lhs_sql} - {rhs_sql}))", params
|
269
330
|
return super().subtract_temporals(internal_type, lhs, rhs)
|
270
331
|
|
271
|
-
def explain_query_prefix(self, format=None, **options):
|
332
|
+
def explain_query_prefix(self, format: str | None = None, **options: Any) -> str:
|
272
333
|
extra = {}
|
273
334
|
# Normalize options.
|
274
335
|
if options:
|
@@ -289,7 +350,13 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|
289
350
|
)
|
290
351
|
return prefix
|
291
352
|
|
292
|
-
def on_conflict_suffix_sql(
|
353
|
+
def on_conflict_suffix_sql(
|
354
|
+
self,
|
355
|
+
fields: list[Field],
|
356
|
+
on_conflict: OnConflict | None,
|
357
|
+
update_fields: list[Field],
|
358
|
+
unique_fields: list[Field],
|
359
|
+
) -> str:
|
293
360
|
if on_conflict == OnConflict.IGNORE:
|
294
361
|
return "ON CONFLICT DO NOTHING"
|
295
362
|
if on_conflict == OnConflict.UPDATE:
|
@@ -1,9 +1,20 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
5
|
+
from psycopg import sql # type: ignore[import-untyped]
|
2
6
|
|
3
7
|
from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
|
4
|
-
from plain.models.backends.ddl_references import IndexColumns
|
8
|
+
from plain.models.backends.ddl_references import Columns, IndexColumns, Statement
|
5
9
|
from plain.models.backends.utils import strip_quotes
|
6
10
|
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from collections.abc import Generator
|
13
|
+
|
14
|
+
from plain.models.base import Model
|
15
|
+
from plain.models.fields import Field
|
16
|
+
from plain.models.indexes import Index
|
17
|
+
|
7
18
|
|
8
19
|
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
9
20
|
# Setting all constraints to IMMEDIATE to allow changing data in the same
|
@@ -39,7 +50,9 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
39
50
|
"ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
40
51
|
)
|
41
52
|
|
42
|
-
def execute(
|
53
|
+
def execute(
|
54
|
+
self, sql: str | Statement, params: tuple[Any, ...] | list[Any] | None = ()
|
55
|
+
) -> None:
|
43
56
|
# Merge the query client-side, as PostgreSQL won't do it server-side.
|
44
57
|
if params is None:
|
45
58
|
return super().execute(sql, params)
|
@@ -55,19 +68,19 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
55
68
|
"ALTER TABLE %(table)s ALTER COLUMN %(column)s DROP IDENTITY IF EXISTS"
|
56
69
|
)
|
57
70
|
|
58
|
-
def quote_value(self, value):
|
71
|
+
def quote_value(self, value: Any) -> str:
|
59
72
|
if isinstance(value, str):
|
60
73
|
value = value.replace("%", "%%")
|
61
74
|
return sql.quote(value, self.connection.connection)
|
62
75
|
|
63
|
-
def _field_indexes_sql(self, model, field):
|
76
|
+
def _field_indexes_sql(self, model: type[Model], field: Field) -> list[Statement]:
|
64
77
|
output = super()._field_indexes_sql(model, field)
|
65
78
|
like_index_statement = self._create_like_index_sql(model, field)
|
66
79
|
if like_index_statement is not None:
|
67
80
|
output.append(like_index_statement)
|
68
81
|
return output
|
69
82
|
|
70
|
-
def _field_data_type(self, field):
|
83
|
+
def _field_data_type(self, field: Field) -> str | None:
|
71
84
|
if field.is_relation:
|
72
85
|
return field.rel_db_type(self.connection)
|
73
86
|
return self.connection.data_types.get(
|
@@ -75,14 +88,16 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
75
88
|
field.db_type(self.connection),
|
76
89
|
)
|
77
90
|
|
78
|
-
def _field_base_data_types(self, field):
|
91
|
+
def _field_base_data_types(self, field: Field) -> Generator[str | None, None, None]:
|
79
92
|
# Yield base data types for array fields.
|
80
|
-
if field.base_field.get_internal_type() == "ArrayField":
|
81
|
-
yield from self._field_base_data_types(field.base_field)
|
93
|
+
if field.base_field.get_internal_type() == "ArrayField": # type: ignore[attr-defined]
|
94
|
+
yield from self._field_base_data_types(field.base_field) # type: ignore[attr-defined]
|
82
95
|
else:
|
83
|
-
yield self._field_data_type(field.base_field)
|
96
|
+
yield self._field_data_type(field.base_field) # type: ignore[attr-defined]
|
84
97
|
|
85
|
-
def _create_like_index_sql(
|
98
|
+
def _create_like_index_sql(
|
99
|
+
self, model: type[Model], field: Field
|
100
|
+
) -> Statement | None:
|
86
101
|
"""
|
87
102
|
Return the statement to create an index with varchar operator pattern
|
88
103
|
when the column type is 'varchar' or 'text', otherwise return None.
|
@@ -107,18 +122,18 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
107
122
|
model,
|
108
123
|
fields=[field],
|
109
124
|
suffix="_like",
|
110
|
-
opclasses=
|
125
|
+
opclasses=("varchar_pattern_ops",),
|
111
126
|
)
|
112
127
|
elif db_type.startswith("text"):
|
113
128
|
return self._create_index_sql(
|
114
129
|
model,
|
115
130
|
fields=[field],
|
116
131
|
suffix="_like",
|
117
|
-
opclasses=
|
132
|
+
opclasses=("text_pattern_ops",),
|
118
133
|
)
|
119
134
|
return None
|
120
135
|
|
121
|
-
def _using_sql(self, new_field, old_field):
|
136
|
+
def _using_sql(self, new_field: Field, old_field: Field) -> str:
|
122
137
|
using_sql = " USING %(column)s::%(type)s"
|
123
138
|
new_internal_type = new_field.get_internal_type()
|
124
139
|
old_internal_type = old_field.get_internal_type()
|
@@ -132,7 +147,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
132
147
|
return using_sql
|
133
148
|
return ""
|
134
149
|
|
135
|
-
def _get_sequence_name(self, table, column):
|
150
|
+
def _get_sequence_name(self, table: str, column: str) -> str | None:
|
136
151
|
with self.connection.cursor() as cursor:
|
137
152
|
for sequence in self.connection.introspection.get_sequences(cursor, table):
|
138
153
|
if sequence["column"] == column:
|
@@ -140,8 +155,14 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
140
155
|
return None
|
141
156
|
|
142
157
|
def _alter_column_type_sql(
|
143
|
-
self,
|
144
|
-
|
158
|
+
self,
|
159
|
+
model: type[Model],
|
160
|
+
old_field: Field,
|
161
|
+
new_field: Field,
|
162
|
+
new_type: str,
|
163
|
+
old_collation: str | None,
|
164
|
+
new_collation: str | None,
|
165
|
+
) -> tuple[tuple[str, list[Any]], list[tuple[str, list[Any]]]]:
|
145
166
|
# Drop indexes on varchar/text/citext columns that are changing to a
|
146
167
|
# different type.
|
147
168
|
old_db_params = old_field.db_parameters(connection=self.connection)
|
@@ -152,7 +173,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
152
173
|
or (old_type.startswith("citext") and not new_type.startswith("citext"))
|
153
174
|
):
|
154
175
|
index_name = self._create_index_name(
|
155
|
-
model.
|
176
|
+
model.model_options.db_table, [old_field.column], suffix="_like"
|
156
177
|
)
|
157
178
|
self.execute(self._delete_index_sql(model, index_name))
|
158
179
|
|
@@ -165,7 +186,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
165
186
|
new_internal_type = new_field.get_internal_type()
|
166
187
|
old_internal_type = old_field.get_internal_type()
|
167
188
|
# Make ALTER TYPE with IDENTITY make sense.
|
168
|
-
table = strip_quotes(model.
|
189
|
+
table = strip_quotes(model.model_options.db_table)
|
169
190
|
auto_field_types = {"PrimaryKeyField"}
|
170
191
|
old_is_auto = old_internal_type in auto_field_types
|
171
192
|
new_is_auto = new_internal_type in auto_field_types
|
@@ -248,15 +269,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
248
269
|
|
249
270
|
def _alter_field(
|
250
271
|
self,
|
251
|
-
model,
|
252
|
-
old_field,
|
253
|
-
new_field,
|
254
|
-
old_type,
|
255
|
-
new_type,
|
256
|
-
old_db_params,
|
257
|
-
new_db_params,
|
258
|
-
strict=False,
|
259
|
-
):
|
272
|
+
model: type[Model],
|
273
|
+
old_field: Field,
|
274
|
+
new_field: Field,
|
275
|
+
old_type: str,
|
276
|
+
new_type: str,
|
277
|
+
old_db_params: dict[str, Any],
|
278
|
+
new_db_params: dict[str, Any],
|
279
|
+
strict: bool = False,
|
280
|
+
) -> None:
|
260
281
|
super()._alter_field(
|
261
282
|
model,
|
262
283
|
old_field,
|
@@ -270,9 +291,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
270
291
|
# Added an index? Create any PostgreSQL-specific indexes.
|
271
292
|
if (
|
272
293
|
not (
|
273
|
-
(old_field.remote_field and old_field.db_index)
|
294
|
+
(old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
|
295
|
+
or old_field.primary_key
|
274
296
|
)
|
275
|
-
and (new_field.remote_field and new_field.db_index)
|
297
|
+
and (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
|
276
298
|
) or (not old_field.primary_key and new_field.primary_key):
|
277
299
|
like_index_statement = self._create_like_index_sql(model, new_field)
|
278
300
|
if like_index_statement is not None:
|
@@ -280,14 +302,21 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
280
302
|
|
281
303
|
# Removed an index? Drop any PostgreSQL-specific indexes.
|
282
304
|
if old_field.primary_key and not (
|
283
|
-
(new_field.remote_field and new_field.db_index)
|
305
|
+
(new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
|
306
|
+
or new_field.primary_key
|
284
307
|
):
|
285
308
|
index_to_remove = self._create_index_name(
|
286
|
-
model.
|
309
|
+
model.model_options.db_table, [old_field.column], suffix="_like"
|
287
310
|
)
|
288
311
|
self.execute(self._delete_index_sql(model, index_to_remove))
|
289
312
|
|
290
|
-
def _index_columns(
|
313
|
+
def _index_columns(
|
314
|
+
self,
|
315
|
+
table: str,
|
316
|
+
columns: list[str],
|
317
|
+
col_suffixes: tuple[str, ...],
|
318
|
+
opclasses: tuple[str, ...],
|
319
|
+
) -> Columns | IndexColumns:
|
291
320
|
if opclasses:
|
292
321
|
return IndexColumns(
|
293
322
|
table,
|
@@ -298,15 +327,25 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
298
327
|
)
|
299
328
|
return super()._index_columns(table, columns, col_suffixes, opclasses)
|
300
329
|
|
301
|
-
def add_index(
|
330
|
+
def add_index(
|
331
|
+
self, model: type[Model], index: Index, concurrently: bool = False
|
332
|
+
) -> None:
|
302
333
|
self.execute(
|
303
334
|
index.create_sql(model, self, concurrently=concurrently), params=None
|
304
335
|
)
|
305
336
|
|
306
|
-
def remove_index(
|
337
|
+
def remove_index(
|
338
|
+
self, model: type[Model], index: Index, concurrently: bool = False
|
339
|
+
) -> None:
|
307
340
|
self.execute(index.remove_sql(model, self, concurrently=concurrently))
|
308
341
|
|
309
|
-
def _delete_index_sql(
|
342
|
+
def _delete_index_sql(
|
343
|
+
self,
|
344
|
+
model: type[Model],
|
345
|
+
name: str,
|
346
|
+
sql: str | None = None,
|
347
|
+
concurrently: bool = False,
|
348
|
+
) -> Statement:
|
310
349
|
sql = (
|
311
350
|
self.sql_delete_index_concurrently
|
312
351
|
if concurrently
|
@@ -316,20 +355,20 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|
316
355
|
|
317
356
|
def _create_index_sql(
|
318
357
|
self,
|
319
|
-
model,
|
358
|
+
model: type[Model],
|
320
359
|
*,
|
321
|
-
fields=None,
|
322
|
-
name=None,
|
323
|
-
suffix="",
|
324
|
-
using="",
|
325
|
-
col_suffixes=(),
|
326
|
-
sql=None,
|
327
|
-
opclasses=(),
|
328
|
-
condition=None,
|
329
|
-
concurrently=False,
|
330
|
-
include=None,
|
331
|
-
expressions=None,
|
332
|
-
):
|
360
|
+
fields: list[Field] | None = None,
|
361
|
+
name: str | None = None,
|
362
|
+
suffix: str = "",
|
363
|
+
using: str = "",
|
364
|
+
col_suffixes: tuple[str, ...] = (),
|
365
|
+
sql: str | None = None,
|
366
|
+
opclasses: tuple[str, ...] = (),
|
367
|
+
condition: str | None = None,
|
368
|
+
concurrently: bool = False,
|
369
|
+
include: list[str] | None = None,
|
370
|
+
expressions: Any = None,
|
371
|
+
) -> Statement:
|
333
372
|
sql = sql or (
|
334
373
|
self.sql_create_index
|
335
374
|
if not concurrently
|