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,6 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import logging
|
2
4
|
import operator
|
5
|
+
from collections.abc import Generator
|
3
6
|
from datetime import datetime
|
7
|
+
from typing import TYPE_CHECKING, Any
|
4
8
|
|
5
9
|
from plain.models.backends.ddl_references import (
|
6
10
|
Columns,
|
@@ -17,10 +21,20 @@ from plain.models.sql import Query
|
|
17
21
|
from plain.models.transaction import TransactionManagementError, atomic
|
18
22
|
from plain.utils import timezone
|
19
23
|
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
from collections.abc import Iterable
|
26
|
+
|
27
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
28
|
+
from plain.models.base import Model
|
29
|
+
from plain.models.constraints import BaseConstraint
|
30
|
+
from plain.models.fields import Field
|
31
|
+
from plain.models.fields.related import ForeignKey, ManyToManyField
|
32
|
+
from plain.models.fields.reverse_related import ManyToManyRel
|
33
|
+
|
20
34
|
logger = logging.getLogger("plain.models.backends.schema")
|
21
35
|
|
22
36
|
|
23
|
-
def _is_relevant_relation(relation, altered_field):
|
37
|
+
def _is_relevant_relation(relation: Any, altered_field: Field) -> bool:
|
24
38
|
"""
|
25
39
|
When altering the given field, must constraints on its model from the given
|
26
40
|
relation be temporarily dropped?
|
@@ -36,10 +50,10 @@ def _is_relevant_relation(relation, altered_field):
|
|
36
50
|
return altered_field.name == "id"
|
37
51
|
|
38
52
|
|
39
|
-
def _all_related_fields(model):
|
53
|
+
def _all_related_fields(model: type[Model]) -> list[Any]:
|
40
54
|
# Related fields must be returned in a deterministic order.
|
41
55
|
return sorted(
|
42
|
-
model.
|
56
|
+
model._model_meta._get_fields(
|
43
57
|
forward=False,
|
44
58
|
reverse=True,
|
45
59
|
include_hidden=True,
|
@@ -48,7 +62,9 @@ def _all_related_fields(model):
|
|
48
62
|
)
|
49
63
|
|
50
64
|
|
51
|
-
def _related_non_m2m_objects(
|
65
|
+
def _related_non_m2m_objects(
|
66
|
+
old_field: Field, new_field: Field
|
67
|
+
) -> Generator[tuple[Any, Any], None, None]:
|
52
68
|
# Filter out m2m objects from reverse relations.
|
53
69
|
# Return (old_relation, new_relation) tuples.
|
54
70
|
related_fields = zip(
|
@@ -140,23 +156,28 @@ class BaseDatabaseSchemaEditor:
|
|
140
156
|
sql_alter_table_comment = "COMMENT ON TABLE %(table)s IS %(comment)s"
|
141
157
|
sql_alter_column_comment = "COMMENT ON COLUMN %(table)s.%(column)s IS %(comment)s"
|
142
158
|
|
143
|
-
def __init__(
|
159
|
+
def __init__(
|
160
|
+
self,
|
161
|
+
connection: BaseDatabaseWrapper,
|
162
|
+
collect_sql: bool = False,
|
163
|
+
atomic: bool = True,
|
164
|
+
):
|
144
165
|
self.connection = connection
|
145
166
|
self.collect_sql = collect_sql
|
146
167
|
if self.collect_sql:
|
147
|
-
self.collected_sql = []
|
168
|
+
self.collected_sql: list[str] = []
|
148
169
|
self.atomic_migration = self.connection.features.can_rollback_ddl and atomic
|
149
170
|
|
150
171
|
# State-managing methods
|
151
172
|
|
152
|
-
def __enter__(self):
|
153
|
-
self.deferred_sql = []
|
173
|
+
def __enter__(self) -> BaseDatabaseSchemaEditor:
|
174
|
+
self.deferred_sql: list[Any] = []
|
154
175
|
if self.atomic_migration:
|
155
176
|
self.atomic = atomic()
|
156
177
|
self.atomic.__enter__()
|
157
178
|
return self
|
158
179
|
|
159
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
180
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
160
181
|
if exc_type is None:
|
161
182
|
for sql in self.deferred_sql:
|
162
183
|
self.execute(sql)
|
@@ -165,7 +186,9 @@ class BaseDatabaseSchemaEditor:
|
|
165
186
|
|
166
187
|
# Core utility functions
|
167
188
|
|
168
|
-
def execute(
|
189
|
+
def execute(
|
190
|
+
self, sql: str | Statement, params: tuple[Any, ...] | list[Any] | None = ()
|
191
|
+
) -> None:
|
169
192
|
"""Execute the given SQL statement, with optional parameters."""
|
170
193
|
# Don't perform the transactional DDL check if SQL is being collected
|
171
194
|
# as it's not going to be executed anyway.
|
@@ -196,15 +219,15 @@ class BaseDatabaseSchemaEditor:
|
|
196
219
|
with self.connection.cursor() as cursor:
|
197
220
|
cursor.execute(sql, params)
|
198
221
|
|
199
|
-
def quote_name(self, name):
|
222
|
+
def quote_name(self, name: str) -> str:
|
200
223
|
return self.connection.ops.quote_name(name)
|
201
224
|
|
202
|
-
def table_sql(self, model):
|
225
|
+
def table_sql(self, model: type[Model]) -> tuple[str, list[Any]]:
|
203
226
|
"""Take a model and return its table definition."""
|
204
227
|
# Create column SQL, add FK deferreds if needed.
|
205
228
|
column_sqls = []
|
206
229
|
params = []
|
207
|
-
for field in model.
|
230
|
+
for field in model._model_meta.local_fields:
|
208
231
|
# SQL.
|
209
232
|
definition, extra_params = self.column_sql(model, field)
|
210
233
|
if definition is None:
|
@@ -219,9 +242,9 @@ class BaseDatabaseSchemaEditor:
|
|
219
242
|
definition += f" {col_type_suffix}"
|
220
243
|
params.extend(extra_params)
|
221
244
|
# FK.
|
222
|
-
if field.remote_field and field.db_constraint:
|
223
|
-
to_table = field.remote_field.model.
|
224
|
-
to_column = field.remote_field.model.
|
245
|
+
if field.remote_field and field.db_constraint: # type: ignore[attr-defined]
|
246
|
+
to_table = field.remote_field.model.model_options.db_table
|
247
|
+
to_column = field.remote_field.model._model_meta.get_field(
|
225
248
|
field.remote_field.field_name
|
226
249
|
).column
|
227
250
|
if self.sql_create_inline_fk:
|
@@ -241,16 +264,16 @@ class BaseDatabaseSchemaEditor:
|
|
241
264
|
# variant).
|
242
265
|
if field.get_internal_type() in ("PrimaryKeyField",):
|
243
266
|
autoinc_sql = self.connection.ops.autoinc_sql(
|
244
|
-
model.
|
267
|
+
model.model_options.db_table, field.column
|
245
268
|
)
|
246
269
|
if autoinc_sql:
|
247
270
|
self.deferred_sql.extend(autoinc_sql)
|
248
271
|
constraints = [
|
249
272
|
constraint.constraint_sql(model, self)
|
250
|
-
for constraint in model.
|
273
|
+
for constraint in model.model_options.constraints
|
251
274
|
]
|
252
275
|
sql = self.sql_create_table % {
|
253
|
-
"table": self.quote_name(model.
|
276
|
+
"table": self.quote_name(model.model_options.db_table),
|
254
277
|
"definition": ", ".join(
|
255
278
|
str(constraint)
|
256
279
|
for constraint in (*column_sqls, *constraints)
|
@@ -262,8 +285,14 @@ class BaseDatabaseSchemaEditor:
|
|
262
285
|
# Field <-> database mapping functions
|
263
286
|
|
264
287
|
def _iter_column_sql(
|
265
|
-
self,
|
266
|
-
|
288
|
+
self,
|
289
|
+
column_db_type: str,
|
290
|
+
params: list[Any],
|
291
|
+
model: type[Model],
|
292
|
+
field: Field,
|
293
|
+
field_db_params: dict[str, Any],
|
294
|
+
include_default: bool,
|
295
|
+
) -> Generator[str, None, None]:
|
267
296
|
yield column_db_type
|
268
297
|
if collation := field_db_params.get("collation"):
|
269
298
|
yield self._collate_sql(collation)
|
@@ -302,7 +331,9 @@ class BaseDatabaseSchemaEditor:
|
|
302
331
|
if field.primary_key:
|
303
332
|
yield "PRIMARY KEY"
|
304
333
|
|
305
|
-
def column_sql(
|
334
|
+
def column_sql(
|
335
|
+
self, model: type[Model], field: Field, include_default: bool = False
|
336
|
+
) -> tuple[str | None, list[Any] | None]:
|
306
337
|
"""
|
307
338
|
Return the column definition for a field. The field must already have
|
308
339
|
had set_attributes_from_name() called.
|
@@ -313,7 +344,7 @@ class BaseDatabaseSchemaEditor:
|
|
313
344
|
# Check for fields that aren't actually columns (e.g. M2M).
|
314
345
|
if column_db_type is None:
|
315
346
|
return None, None
|
316
|
-
params = []
|
347
|
+
params: list[Any] = []
|
317
348
|
return (
|
318
349
|
" ".join(
|
319
350
|
# This appends to the params being returned.
|
@@ -329,21 +360,21 @@ class BaseDatabaseSchemaEditor:
|
|
329
360
|
params,
|
330
361
|
)
|
331
362
|
|
332
|
-
def skip_default(self, field):
|
363
|
+
def skip_default(self, field: Field) -> bool:
|
333
364
|
"""
|
334
365
|
Some backends don't accept default values for certain columns types
|
335
366
|
(i.e. MySQL longtext and longblob).
|
336
367
|
"""
|
337
368
|
return False
|
338
369
|
|
339
|
-
def skip_default_on_alter(self, field):
|
370
|
+
def skip_default_on_alter(self, field: Field) -> bool:
|
340
371
|
"""
|
341
372
|
Some backends don't accept default values for certain columns types
|
342
373
|
(i.e. MySQL longtext and longblob) in the ALTER COLUMN statement.
|
343
374
|
"""
|
344
375
|
return False
|
345
376
|
|
346
|
-
def prepare_default(self, value):
|
377
|
+
def prepare_default(self, value: Any) -> str:
|
347
378
|
"""
|
348
379
|
Only used for backends which have requires_literal_defaults feature
|
349
380
|
"""
|
@@ -352,7 +383,7 @@ class BaseDatabaseSchemaEditor:
|
|
352
383
|
"requires_literal_defaults must provide a prepare_default() method"
|
353
384
|
)
|
354
385
|
|
355
|
-
def _column_default_sql(self, field):
|
386
|
+
def _column_default_sql(self, field: Field) -> str:
|
356
387
|
"""
|
357
388
|
Return the SQL to use in a DEFAULT clause. The resulting string should
|
358
389
|
contain a '%s' placeholder for a default value.
|
@@ -360,7 +391,7 @@ class BaseDatabaseSchemaEditor:
|
|
360
391
|
return "%s"
|
361
392
|
|
362
393
|
@staticmethod
|
363
|
-
def _effective_default(field):
|
394
|
+
def _effective_default(field: Field) -> Any:
|
364
395
|
# This method allows testing its logic without a connection.
|
365
396
|
if field.has_default():
|
366
397
|
default = field.get_default()
|
@@ -385,11 +416,11 @@ class BaseDatabaseSchemaEditor:
|
|
385
416
|
default = None
|
386
417
|
return default
|
387
418
|
|
388
|
-
def effective_default(self, field):
|
419
|
+
def effective_default(self, field: Field) -> Any:
|
389
420
|
"""Return a field's effective database default value."""
|
390
421
|
return field.get_db_prep_save(self._effective_default(field), self.connection)
|
391
422
|
|
392
|
-
def quote_value(self, value):
|
423
|
+
def quote_value(self, value: Any) -> str:
|
393
424
|
"""
|
394
425
|
Return a quoted version of the value so it's safe to use in an SQL
|
395
426
|
string. This is not safe against injection from user code; it is
|
@@ -401,7 +432,7 @@ class BaseDatabaseSchemaEditor:
|
|
401
432
|
|
402
433
|
# Actions
|
403
434
|
|
404
|
-
def create_model(self, model):
|
435
|
+
def create_model(self, model: type[Model]) -> None:
|
405
436
|
"""
|
406
437
|
Create a table and any accompanying indexes or unique constraints for
|
407
438
|
the given `model`.
|
@@ -413,11 +444,13 @@ class BaseDatabaseSchemaEditor:
|
|
413
444
|
|
414
445
|
if self.connection.features.supports_comments:
|
415
446
|
# Add table comment.
|
416
|
-
if model.
|
417
|
-
self.alter_db_table_comment(
|
447
|
+
if model.model_options.db_table_comment:
|
448
|
+
self.alter_db_table_comment(
|
449
|
+
model, None, model.model_options.db_table_comment
|
450
|
+
)
|
418
451
|
# Add column comments.
|
419
452
|
if not self.connection.features.supports_comments_inline:
|
420
|
-
for field in model.
|
453
|
+
for field in model._model_meta.local_fields:
|
421
454
|
if field.db_comment:
|
422
455
|
field_db_params = field.db_parameters(
|
423
456
|
connection=self.connection
|
@@ -431,24 +464,24 @@ class BaseDatabaseSchemaEditor:
|
|
431
464
|
# Add any field index (deferred as SQLite _remake_table needs it).
|
432
465
|
self.deferred_sql.extend(self._model_indexes_sql(model))
|
433
466
|
|
434
|
-
def delete_model(self, model):
|
467
|
+
def delete_model(self, model: type[Model]) -> None:
|
435
468
|
"""Delete a model from the database."""
|
436
469
|
|
437
470
|
# Delete the table
|
438
471
|
self.execute(
|
439
472
|
self.sql_delete_table
|
440
473
|
% {
|
441
|
-
"table": self.quote_name(model.
|
474
|
+
"table": self.quote_name(model.model_options.db_table),
|
442
475
|
}
|
443
476
|
)
|
444
477
|
# Remove all deferred statements referencing the deleted table.
|
445
478
|
for sql in list(self.deferred_sql):
|
446
479
|
if isinstance(sql, Statement) and sql.references_table(
|
447
|
-
model.
|
480
|
+
model.model_options.db_table
|
448
481
|
):
|
449
482
|
self.deferred_sql.remove(sql)
|
450
483
|
|
451
|
-
def add_index(self, model, index):
|
484
|
+
def add_index(self, model: type[Model], index: Index) -> None:
|
452
485
|
"""Add an index on a model."""
|
453
486
|
if (
|
454
487
|
index.contains_expressions
|
@@ -459,7 +492,7 @@ class BaseDatabaseSchemaEditor:
|
|
459
492
|
# necessity to avoid escaping attempts on execution.
|
460
493
|
self.execute(index.create_sql(model, self), params=None)
|
461
494
|
|
462
|
-
def remove_index(self, model, index):
|
495
|
+
def remove_index(self, model: type[Model], index: Index) -> None:
|
463
496
|
"""Remove an index from a model."""
|
464
497
|
if (
|
465
498
|
index.contains_expressions
|
@@ -468,7 +501,9 @@ class BaseDatabaseSchemaEditor:
|
|
468
501
|
return None
|
469
502
|
self.execute(index.remove_sql(model, self))
|
470
503
|
|
471
|
-
def rename_index(
|
504
|
+
def rename_index(
|
505
|
+
self, model: type[Model], old_index: Index, new_index: Index
|
506
|
+
) -> None:
|
472
507
|
if self.connection.features.can_rename_index:
|
473
508
|
self.execute(
|
474
509
|
self._rename_index_sql(model, old_index.name, new_index.name),
|
@@ -478,7 +513,7 @@ class BaseDatabaseSchemaEditor:
|
|
478
513
|
self.remove_index(model, old_index)
|
479
514
|
self.add_index(model, new_index)
|
480
515
|
|
481
|
-
def add_constraint(self, model, constraint):
|
516
|
+
def add_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
|
482
517
|
"""Add a constraint to a model."""
|
483
518
|
sql = constraint.create_sql(model, self)
|
484
519
|
if sql:
|
@@ -486,13 +521,15 @@ class BaseDatabaseSchemaEditor:
|
|
486
521
|
# params=None a necessity to avoid escaping attempts on execution.
|
487
522
|
self.execute(sql, params=None)
|
488
523
|
|
489
|
-
def remove_constraint(self, model, constraint):
|
524
|
+
def remove_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
|
490
525
|
"""Remove a constraint from a model."""
|
491
526
|
sql = constraint.remove_sql(model, self)
|
492
527
|
if sql:
|
493
528
|
self.execute(sql)
|
494
529
|
|
495
|
-
def alter_db_table(
|
530
|
+
def alter_db_table(
|
531
|
+
self, model: type[Model], old_db_table: str, new_db_table: str
|
532
|
+
) -> None:
|
496
533
|
"""Rename the table a model points to."""
|
497
534
|
if old_db_table == new_db_table or (
|
498
535
|
self.connection.features.ignores_table_name_case
|
@@ -511,16 +548,21 @@ class BaseDatabaseSchemaEditor:
|
|
511
548
|
if isinstance(sql, Statement):
|
512
549
|
sql.rename_table_references(old_db_table, new_db_table)
|
513
550
|
|
514
|
-
def alter_db_table_comment(
|
551
|
+
def alter_db_table_comment(
|
552
|
+
self,
|
553
|
+
model: type[Model],
|
554
|
+
old_db_table_comment: str | None,
|
555
|
+
new_db_table_comment: str | None,
|
556
|
+
) -> None:
|
515
557
|
self.execute(
|
516
558
|
self.sql_alter_table_comment
|
517
559
|
% {
|
518
|
-
"table": self.quote_name(model.
|
560
|
+
"table": self.quote_name(model.model_options.db_table),
|
519
561
|
"comment": self.quote_value(new_db_table_comment or ""),
|
520
562
|
}
|
521
563
|
)
|
522
564
|
|
523
|
-
def add_field(self, model, field):
|
565
|
+
def add_field(self, model: type[Model], field: Field) -> None:
|
524
566
|
"""
|
525
567
|
Create a field on a model. Usually involves adding a column, but may
|
526
568
|
involve adding a table instead (for M2M fields).
|
@@ -539,16 +581,16 @@ class BaseDatabaseSchemaEditor:
|
|
539
581
|
if (
|
540
582
|
field.remote_field
|
541
583
|
and self.connection.features.supports_foreign_keys
|
542
|
-
and field.db_constraint
|
584
|
+
and field.db_constraint # type: ignore[attr-defined]
|
543
585
|
):
|
544
586
|
constraint_suffix = "_fk_%(to_table)s_%(to_column)s"
|
545
587
|
# Add FK constraint inline, if supported.
|
546
588
|
if self.sql_create_column_inline_fk:
|
547
|
-
to_table = field.remote_field.model.
|
548
|
-
to_column = field.remote_field.model.
|
589
|
+
to_table = field.remote_field.model.model_options.db_table
|
590
|
+
to_column = field.remote_field.model._model_meta.get_field(
|
549
591
|
field.remote_field.field_name
|
550
592
|
).column
|
551
|
-
namespace, _ = split_identifier(model.
|
593
|
+
namespace, _ = split_identifier(model.model_options.db_table)
|
552
594
|
definition += " " + self.sql_create_column_inline_fk % {
|
553
595
|
"name": self._fk_constraint_name(model, field, constraint_suffix),
|
554
596
|
"namespace": f"{self.quote_name(namespace)}." if namespace else "",
|
@@ -564,7 +606,7 @@ class BaseDatabaseSchemaEditor:
|
|
564
606
|
)
|
565
607
|
# Build the SQL and run it
|
566
608
|
sql = self.sql_create_column % {
|
567
|
-
"table": self.quote_name(model.
|
609
|
+
"table": self.quote_name(model.model_options.db_table),
|
568
610
|
"column": self.quote_name(field.column),
|
569
611
|
"definition": definition,
|
570
612
|
}
|
@@ -579,7 +621,7 @@ class BaseDatabaseSchemaEditor:
|
|
579
621
|
model, None, field, drop=True
|
580
622
|
)
|
581
623
|
sql = self.sql_alter_column % {
|
582
|
-
"table": self.quote_name(model.
|
624
|
+
"table": self.quote_name(model.model_options.db_table),
|
583
625
|
"changes": changes_sql,
|
584
626
|
}
|
585
627
|
self.execute(sql, params)
|
@@ -601,7 +643,7 @@ class BaseDatabaseSchemaEditor:
|
|
601
643
|
if self.connection.features.connection_persists_old_columns:
|
602
644
|
self.connection.close()
|
603
645
|
|
604
|
-
def remove_field(self, model, field):
|
646
|
+
def remove_field(self, model: type[Model], field: Field) -> None:
|
605
647
|
"""
|
606
648
|
Remove a field from a model. Usually involves deleting a column,
|
607
649
|
but for M2Ms may involve deleting a table.
|
@@ -616,7 +658,7 @@ class BaseDatabaseSchemaEditor:
|
|
616
658
|
self.execute(self._delete_fk_sql(model, fk_name))
|
617
659
|
# Delete the column
|
618
660
|
sql = self.sql_delete_column % {
|
619
|
-
"table": self.quote_name(model.
|
661
|
+
"table": self.quote_name(model.model_options.db_table),
|
620
662
|
"column": self.quote_name(field.column),
|
621
663
|
}
|
622
664
|
self.execute(sql)
|
@@ -626,11 +668,17 @@ class BaseDatabaseSchemaEditor:
|
|
626
668
|
# Remove all deferred statements referencing the deleted column.
|
627
669
|
for sql in list(self.deferred_sql):
|
628
670
|
if isinstance(sql, Statement) and sql.references_column(
|
629
|
-
model.
|
671
|
+
model.model_options.db_table, field.column
|
630
672
|
):
|
631
673
|
self.deferred_sql.remove(sql)
|
632
674
|
|
633
|
-
def alter_field(
|
675
|
+
def alter_field(
|
676
|
+
self,
|
677
|
+
model: type[Model],
|
678
|
+
old_field: Field,
|
679
|
+
new_field: Field,
|
680
|
+
strict: bool = False,
|
681
|
+
) -> None:
|
634
682
|
"""
|
635
683
|
Allow a field's type, uniqueness, nullability, default, column,
|
636
684
|
constraints, etc. to be modified.
|
@@ -655,7 +703,7 @@ class BaseDatabaseSchemaEditor:
|
|
655
703
|
elif (
|
656
704
|
old_type is None
|
657
705
|
and new_type is None
|
658
|
-
and (old_field.remote_field.through and new_field.remote_field.through)
|
706
|
+
and (old_field.remote_field.through and new_field.remote_field.through) # type: ignore[attr-defined]
|
659
707
|
):
|
660
708
|
# Both sides have through models; this is a no-op.
|
661
709
|
return
|
@@ -677,7 +725,9 @@ class BaseDatabaseSchemaEditor:
|
|
677
725
|
strict,
|
678
726
|
)
|
679
727
|
|
680
|
-
def _field_db_check(
|
728
|
+
def _field_db_check(
|
729
|
+
self, field: Field, field_db_params: dict[str, Any]
|
730
|
+
) -> str | None:
|
681
731
|
# Always check constraints with the same mocked column name to avoid
|
682
732
|
# recreating constrains when the column is renamed.
|
683
733
|
check_constraints = self.connection.data_type_check_constraints
|
@@ -690,22 +740,22 @@ class BaseDatabaseSchemaEditor:
|
|
690
740
|
|
691
741
|
def _alter_field(
|
692
742
|
self,
|
693
|
-
model,
|
694
|
-
old_field,
|
695
|
-
new_field,
|
696
|
-
old_type,
|
697
|
-
new_type,
|
698
|
-
old_db_params,
|
699
|
-
new_db_params,
|
700
|
-
strict=False,
|
701
|
-
):
|
743
|
+
model: type[Model],
|
744
|
+
old_field: Field,
|
745
|
+
new_field: Field,
|
746
|
+
old_type: str,
|
747
|
+
new_type: str,
|
748
|
+
old_db_params: dict[str, Any],
|
749
|
+
new_db_params: dict[str, Any],
|
750
|
+
strict: bool = False,
|
751
|
+
) -> None:
|
702
752
|
"""Perform a "physical" (non-ManyToMany) field update."""
|
703
753
|
# Drop any FK constraints, we'll remake them later
|
704
754
|
fks_dropped = set()
|
705
755
|
if (
|
706
756
|
self.connection.features.supports_foreign_keys
|
707
757
|
and old_field.remote_field
|
708
|
-
and old_field.db_constraint
|
758
|
+
and old_field.db_constraint # type: ignore[attr-defined]
|
709
759
|
and self._field_should_be_altered(
|
710
760
|
old_field,
|
711
761
|
new_field,
|
@@ -717,7 +767,7 @@ class BaseDatabaseSchemaEditor:
|
|
717
767
|
)
|
718
768
|
if strict and len(fk_names) != 1:
|
719
769
|
raise ValueError(
|
720
|
-
f"Found wrong number ({len(fk_names)}) of foreign key constraints for {model.
|
770
|
+
f"Found wrong number ({len(fk_names)}) of foreign key constraints for {model.model_options.db_table}.{old_field.column}"
|
721
771
|
)
|
722
772
|
for fk_name in fk_names:
|
723
773
|
fks_dropped.add((old_field.column,))
|
@@ -729,7 +779,7 @@ class BaseDatabaseSchemaEditor:
|
|
729
779
|
):
|
730
780
|
# Find the unique constraint for this field
|
731
781
|
meta_constraint_names = {
|
732
|
-
constraint.name for constraint in model.
|
782
|
+
constraint.name for constraint in model.model_options.constraints
|
733
783
|
}
|
734
784
|
constraint_names = self._constraint_names(
|
735
785
|
model,
|
@@ -740,7 +790,7 @@ class BaseDatabaseSchemaEditor:
|
|
740
790
|
)
|
741
791
|
if strict and len(constraint_names) != 1:
|
742
792
|
raise ValueError(
|
743
|
-
f"Found wrong number ({len(constraint_names)}) of unique constraints for {model.
|
793
|
+
f"Found wrong number ({len(constraint_names)}) of unique constraints for {model.model_options.db_table}.{old_field.column}"
|
744
794
|
)
|
745
795
|
for constraint_name in constraint_names:
|
746
796
|
self.execute(self._delete_unique_sql(model, constraint_name))
|
@@ -754,7 +804,7 @@ class BaseDatabaseSchemaEditor:
|
|
754
804
|
and ((old_type != new_type) or (old_collation != new_collation))
|
755
805
|
)
|
756
806
|
if drop_foreign_keys:
|
757
|
-
# '
|
807
|
+
# '_model_meta.related_field' also contains M2M reverse fields, these
|
758
808
|
# will be filtered out
|
759
809
|
for _old_rel, new_rel in _related_non_m2m_objects(old_field, new_field):
|
760
810
|
rel_fk_names = self._constraint_names(
|
@@ -773,15 +823,15 @@ class BaseDatabaseSchemaEditor:
|
|
773
823
|
# True | False | False | True
|
774
824
|
# True | False | True | True
|
775
825
|
if (
|
776
|
-
(old_field.remote_field and old_field.db_index)
|
826
|
+
(old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
|
777
827
|
and not old_field.primary_key
|
778
828
|
and (
|
779
|
-
not (new_field.remote_field and new_field.db_index)
|
829
|
+
not (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
|
780
830
|
or new_field.primary_key
|
781
831
|
)
|
782
832
|
):
|
783
833
|
# Find the index for this field
|
784
|
-
meta_index_names = {index.name for index in model.
|
834
|
+
meta_index_names = {index.name for index in model.model_options.indexes}
|
785
835
|
# Retrieve only BTREE indexes since this is what's created with
|
786
836
|
# db_index=True.
|
787
837
|
index_names = self._constraint_names(
|
@@ -801,7 +851,7 @@ class BaseDatabaseSchemaEditor:
|
|
801
851
|
new_db_check = self._field_db_check(new_field, new_db_params)
|
802
852
|
if old_db_check != new_db_check and old_db_check:
|
803
853
|
meta_constraint_names = {
|
804
|
-
constraint.name for constraint in model.
|
854
|
+
constraint.name for constraint in model.model_options.constraints
|
805
855
|
}
|
806
856
|
constraint_names = self._constraint_names(
|
807
857
|
model,
|
@@ -811,7 +861,7 @@ class BaseDatabaseSchemaEditor:
|
|
811
861
|
)
|
812
862
|
if strict and len(constraint_names) != 1:
|
813
863
|
raise ValueError(
|
814
|
-
f"Found wrong number ({len(constraint_names)}) of check constraints for {model.
|
864
|
+
f"Found wrong number ({len(constraint_names)}) of check constraints for {model.model_options.db_table}.{old_field.column}"
|
815
865
|
)
|
816
866
|
for constraint_name in constraint_names:
|
817
867
|
self.execute(self._delete_check_sql(model, constraint_name))
|
@@ -819,14 +869,14 @@ class BaseDatabaseSchemaEditor:
|
|
819
869
|
if old_field.column != new_field.column:
|
820
870
|
self.execute(
|
821
871
|
self._rename_field_sql(
|
822
|
-
model.
|
872
|
+
model.model_options.db_table, old_field, new_field, new_type
|
823
873
|
)
|
824
874
|
)
|
825
875
|
# Rename all references to the renamed column.
|
826
876
|
for sql in self.deferred_sql:
|
827
877
|
if isinstance(sql, Statement):
|
828
878
|
sql.rename_column_references(
|
829
|
-
model.
|
879
|
+
model.model_options.db_table, old_field.column, new_field.column
|
830
880
|
)
|
831
881
|
# Next, start accumulating actions to do
|
832
882
|
actions = []
|
@@ -893,7 +943,7 @@ class BaseDatabaseSchemaEditor:
|
|
893
943
|
self.execute(
|
894
944
|
self.sql_alter_column
|
895
945
|
% {
|
896
|
-
"table": self.quote_name(model.
|
946
|
+
"table": self.quote_name(model.model_options.db_table),
|
897
947
|
"changes": sql,
|
898
948
|
},
|
899
949
|
params,
|
@@ -903,7 +953,7 @@ class BaseDatabaseSchemaEditor:
|
|
903
953
|
self.execute(
|
904
954
|
self.sql_update_with_default
|
905
955
|
% {
|
906
|
-
"table": self.quote_name(model.
|
956
|
+
"table": self.quote_name(model.model_options.db_table),
|
907
957
|
"column": self.quote_name(new_field.column),
|
908
958
|
"default": "%s",
|
909
959
|
},
|
@@ -915,7 +965,7 @@ class BaseDatabaseSchemaEditor:
|
|
915
965
|
self.execute(
|
916
966
|
self.sql_alter_column
|
917
967
|
% {
|
918
|
-
"table": self.quote_name(model.
|
968
|
+
"table": self.quote_name(model.model_options.db_table),
|
919
969
|
"changes": sql,
|
920
970
|
},
|
921
971
|
params,
|
@@ -938,10 +988,10 @@ class BaseDatabaseSchemaEditor:
|
|
938
988
|
# True | True | True | False
|
939
989
|
if (
|
940
990
|
(
|
941
|
-
not (old_field.remote_field and old_field.db_index)
|
991
|
+
not (old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
|
942
992
|
or old_field.primary_key
|
943
993
|
)
|
944
|
-
and (new_field.remote_field and new_field.db_index)
|
994
|
+
and (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
|
945
995
|
and not new_field.primary_key
|
946
996
|
):
|
947
997
|
self.execute(self._create_index_sql(model, fields=[new_field]))
|
@@ -974,7 +1024,9 @@ class BaseDatabaseSchemaEditor:
|
|
974
1024
|
self.execute(
|
975
1025
|
self.sql_alter_column
|
976
1026
|
% {
|
977
|
-
"table": self.quote_name(
|
1027
|
+
"table": self.quote_name(
|
1028
|
+
new_rel.related_model.model_options.db_table
|
1029
|
+
),
|
978
1030
|
"changes": fragment[0],
|
979
1031
|
},
|
980
1032
|
fragment[1],
|
@@ -986,9 +1038,9 @@ class BaseDatabaseSchemaEditor:
|
|
986
1038
|
self.connection.features.supports_foreign_keys
|
987
1039
|
and new_field.remote_field
|
988
1040
|
and (
|
989
|
-
fks_dropped or not old_field.remote_field or not old_field.db_constraint
|
1041
|
+
fks_dropped or not old_field.remote_field or not old_field.db_constraint # type: ignore[attr-defined]
|
990
1042
|
)
|
991
|
-
and new_field.db_constraint
|
1043
|
+
and new_field.db_constraint # type: ignore[attr-defined]
|
992
1044
|
):
|
993
1045
|
self.execute(
|
994
1046
|
self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s")
|
@@ -1003,7 +1055,7 @@ class BaseDatabaseSchemaEditor:
|
|
1003
1055
|
# Does it have check constraints we need to add?
|
1004
1056
|
if old_db_check != new_db_check and new_db_check:
|
1005
1057
|
constraint_name = self._create_index_name(
|
1006
|
-
model.
|
1058
|
+
model.model_options.db_table, [new_field.column], suffix="_check"
|
1007
1059
|
)
|
1008
1060
|
self.execute(
|
1009
1061
|
self._create_check_sql(model, constraint_name, new_db_params["check"])
|
@@ -1015,7 +1067,7 @@ class BaseDatabaseSchemaEditor:
|
|
1015
1067
|
model, old_field, new_field, drop=True
|
1016
1068
|
)
|
1017
1069
|
sql = self.sql_alter_column % {
|
1018
|
-
"table": self.quote_name(model.
|
1070
|
+
"table": self.quote_name(model.model_options.db_table),
|
1019
1071
|
"changes": changes_sql,
|
1020
1072
|
}
|
1021
1073
|
self.execute(sql, params)
|
@@ -1023,7 +1075,9 @@ class BaseDatabaseSchemaEditor:
|
|
1023
1075
|
if self.connection.features.connection_persists_old_columns:
|
1024
1076
|
self.connection.close()
|
1025
1077
|
|
1026
|
-
def _alter_column_null_sql(
|
1078
|
+
def _alter_column_null_sql(
|
1079
|
+
self, model: type[Model], old_field: Field, new_field: Field
|
1080
|
+
) -> tuple[str, list[Any]]:
|
1027
1081
|
"""
|
1028
1082
|
Hook to specialize column null alteration.
|
1029
1083
|
|
@@ -1045,7 +1099,13 @@ class BaseDatabaseSchemaEditor:
|
|
1045
1099
|
[],
|
1046
1100
|
)
|
1047
1101
|
|
1048
|
-
def _alter_column_default_sql(
|
1102
|
+
def _alter_column_default_sql(
|
1103
|
+
self,
|
1104
|
+
model: type[Model],
|
1105
|
+
old_field: Field | None,
|
1106
|
+
new_field: Field,
|
1107
|
+
drop: bool = False,
|
1108
|
+
) -> tuple[str, list[Any]]:
|
1049
1109
|
"""
|
1050
1110
|
Hook to specialize column default alteration.
|
1051
1111
|
|
@@ -1084,8 +1144,14 @@ class BaseDatabaseSchemaEditor:
|
|
1084
1144
|
)
|
1085
1145
|
|
1086
1146
|
def _alter_column_type_sql(
|
1087
|
-
self,
|
1088
|
-
|
1147
|
+
self,
|
1148
|
+
model: type[Model],
|
1149
|
+
old_field: Field,
|
1150
|
+
new_field: Field,
|
1151
|
+
new_type: str,
|
1152
|
+
old_collation: str | None,
|
1153
|
+
new_collation: str | None,
|
1154
|
+
) -> tuple[tuple[str, list[Any]], list[tuple[str, list[Any]]]]:
|
1089
1155
|
"""
|
1090
1156
|
Hook to specialize column type alteration for different backends,
|
1091
1157
|
for cases when a creation type is different to an alteration type
|
@@ -1097,7 +1163,7 @@ class BaseDatabaseSchemaEditor:
|
|
1097
1163
|
"""
|
1098
1164
|
other_actions = []
|
1099
1165
|
if collate_sql := self._collate_sql(
|
1100
|
-
new_collation, old_collation, model.
|
1166
|
+
new_collation, old_collation, model.model_options.db_table
|
1101
1167
|
):
|
1102
1168
|
collate_sql = f" {collate_sql}"
|
1103
1169
|
else:
|
@@ -1129,53 +1195,71 @@ class BaseDatabaseSchemaEditor:
|
|
1129
1195
|
other_actions,
|
1130
1196
|
)
|
1131
1197
|
|
1132
|
-
def _alter_column_comment_sql(
|
1198
|
+
def _alter_column_comment_sql(
|
1199
|
+
self,
|
1200
|
+
model: type[Model],
|
1201
|
+
new_field: Field,
|
1202
|
+
new_type: str,
|
1203
|
+
new_db_comment: str | None,
|
1204
|
+
) -> tuple[str, list[Any]]:
|
1133
1205
|
return (
|
1134
1206
|
self.sql_alter_column_comment
|
1135
1207
|
% {
|
1136
|
-
"table": self.quote_name(model.
|
1208
|
+
"table": self.quote_name(model.model_options.db_table),
|
1137
1209
|
"column": self.quote_name(new_field.column),
|
1138
1210
|
"comment": self._comment_sql(new_db_comment),
|
1139
1211
|
},
|
1140
1212
|
[],
|
1141
1213
|
)
|
1142
1214
|
|
1143
|
-
def _comment_sql(self, comment):
|
1215
|
+
def _comment_sql(self, comment: str | None) -> str:
|
1144
1216
|
return self.quote_value(comment or "")
|
1145
1217
|
|
1146
|
-
def _alter_many_to_many(
|
1218
|
+
def _alter_many_to_many(
|
1219
|
+
self,
|
1220
|
+
model: type[Model],
|
1221
|
+
old_field: ManyToManyField,
|
1222
|
+
new_field: ManyToManyField,
|
1223
|
+
strict: bool,
|
1224
|
+
) -> None:
|
1147
1225
|
"""Alter M2Ms to repoint their to= endpoints."""
|
1226
|
+
# Type narrow for ManyToManyField.remote_field
|
1227
|
+
old_rel: ManyToManyRel = old_field.remote_field # type: ignore[assignment]
|
1228
|
+
new_rel: ManyToManyRel = new_field.remote_field # type: ignore[assignment]
|
1229
|
+
|
1148
1230
|
# Rename the through table
|
1149
1231
|
if (
|
1150
|
-
|
1151
|
-
!=
|
1232
|
+
old_rel.through.model_options.db_table
|
1233
|
+
!= new_rel.through.model_options.db_table
|
1152
1234
|
):
|
1153
1235
|
self.alter_db_table(
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1236
|
+
old_rel.through,
|
1237
|
+
old_rel.through.model_options.db_table,
|
1238
|
+
new_rel.through.model_options.db_table,
|
1157
1239
|
)
|
1158
1240
|
# Repoint the FK to the other side
|
1159
1241
|
self.alter_field(
|
1160
|
-
|
1242
|
+
new_rel.through,
|
1161
1243
|
# The field that points to the target model is needed, so we can
|
1162
1244
|
# tell alter_field to change it - this is m2m_reverse_field_name()
|
1163
1245
|
# (as opposed to m2m_field_name(), which points to our model).
|
1164
|
-
|
1165
|
-
old_field.m2m_reverse_field_name()
|
1246
|
+
old_rel.through._model_meta.get_field(
|
1247
|
+
old_field.m2m_reverse_field_name() # type: ignore[attr-defined]
|
1166
1248
|
),
|
1167
|
-
|
1168
|
-
new_field.m2m_reverse_field_name()
|
1249
|
+
new_rel.through._model_meta.get_field(
|
1250
|
+
new_field.m2m_reverse_field_name() # type: ignore[attr-defined]
|
1169
1251
|
),
|
1170
1252
|
)
|
1171
1253
|
self.alter_field(
|
1172
|
-
|
1254
|
+
new_rel.through,
|
1173
1255
|
# for self-referential models we need to alter field from the other end too
|
1174
|
-
|
1175
|
-
|
1256
|
+
old_rel.through._model_meta.get_field(old_field.m2m_field_name()),
|
1257
|
+
new_rel.through._model_meta.get_field(new_field.m2m_field_name()),
|
1176
1258
|
)
|
1177
1259
|
|
1178
|
-
def _create_index_name(
|
1260
|
+
def _create_index_name(
|
1261
|
+
self, table_name: str, column_names: list[str], suffix: str = ""
|
1262
|
+
) -> str:
|
1179
1263
|
"""
|
1180
1264
|
Generate a unique name for an index/unique constraint.
|
1181
1265
|
|
@@ -1208,34 +1292,36 @@ class BaseDatabaseSchemaEditor:
|
|
1208
1292
|
index_name = f"D{index_name[:-1]}"
|
1209
1293
|
return index_name
|
1210
1294
|
|
1211
|
-
def _index_condition_sql(self, condition):
|
1295
|
+
def _index_condition_sql(self, condition: str | None) -> str:
|
1212
1296
|
if condition:
|
1213
1297
|
return " WHERE " + condition
|
1214
1298
|
return ""
|
1215
1299
|
|
1216
|
-
def _index_include_sql(
|
1300
|
+
def _index_include_sql(
|
1301
|
+
self, model: type[Model], columns: list[str] | None
|
1302
|
+
) -> str | Statement:
|
1217
1303
|
if not columns or not self.connection.features.supports_covering_indexes:
|
1218
1304
|
return ""
|
1219
1305
|
return Statement(
|
1220
1306
|
" INCLUDE (%(columns)s)",
|
1221
|
-
columns=Columns(model.
|
1307
|
+
columns=Columns(model.model_options.db_table, columns, self.quote_name),
|
1222
1308
|
)
|
1223
1309
|
|
1224
1310
|
def _create_index_sql(
|
1225
1311
|
self,
|
1226
|
-
model,
|
1312
|
+
model: type[Model],
|
1227
1313
|
*,
|
1228
|
-
fields=None,
|
1229
|
-
name=None,
|
1230
|
-
suffix="",
|
1231
|
-
using="",
|
1232
|
-
col_suffixes=(),
|
1233
|
-
sql=None,
|
1234
|
-
opclasses=(),
|
1235
|
-
condition=None,
|
1236
|
-
include=None,
|
1237
|
-
expressions=None,
|
1238
|
-
):
|
1314
|
+
fields: list[Field] | None = None,
|
1315
|
+
name: str | None = None,
|
1316
|
+
suffix: str = "",
|
1317
|
+
using: str = "",
|
1318
|
+
col_suffixes: tuple[str, ...] = (),
|
1319
|
+
sql: str | None = None,
|
1320
|
+
opclasses: tuple[str, ...] = (),
|
1321
|
+
condition: str | None = None,
|
1322
|
+
include: list[str] | None = None,
|
1323
|
+
expressions: Any = None,
|
1324
|
+
) -> Statement:
|
1239
1325
|
"""
|
1240
1326
|
Return the SQL statement to create the index for one or several fields
|
1241
1327
|
or expressions. `sql` can be specified if the syntax differs from the
|
@@ -1246,9 +1332,9 @@ class BaseDatabaseSchemaEditor:
|
|
1246
1332
|
compiler = Query(model, alias_cols=False).get_compiler()
|
1247
1333
|
columns = [field.column for field in fields]
|
1248
1334
|
sql_create_index = sql or self.sql_create_index
|
1249
|
-
table = model.
|
1335
|
+
table = model.model_options.db_table
|
1250
1336
|
|
1251
|
-
def create_index_name(*args, **kwargs):
|
1337
|
+
def create_index_name(*args: Any, **kwargs: Any) -> str:
|
1252
1338
|
nonlocal name
|
1253
1339
|
if name is None:
|
1254
1340
|
name = self._create_index_name(*args, **kwargs)
|
@@ -1269,33 +1355,43 @@ class BaseDatabaseSchemaEditor:
|
|
1269
1355
|
include=self._index_include_sql(model, include),
|
1270
1356
|
)
|
1271
1357
|
|
1272
|
-
def _delete_index_sql(
|
1358
|
+
def _delete_index_sql(
|
1359
|
+
self, model: type[Model], name: str, sql: str | None = None
|
1360
|
+
) -> Statement:
|
1273
1361
|
return Statement(
|
1274
1362
|
sql or self.sql_delete_index,
|
1275
|
-
table=Table(model.
|
1363
|
+
table=Table(model.model_options.db_table, self.quote_name),
|
1276
1364
|
name=self.quote_name(name),
|
1277
1365
|
)
|
1278
1366
|
|
1279
|
-
def _rename_index_sql(
|
1367
|
+
def _rename_index_sql(
|
1368
|
+
self, model: type[Model], old_name: str, new_name: str
|
1369
|
+
) -> Statement:
|
1280
1370
|
return Statement(
|
1281
1371
|
self.sql_rename_index,
|
1282
|
-
table=Table(model.
|
1372
|
+
table=Table(model.model_options.db_table, self.quote_name),
|
1283
1373
|
old_name=self.quote_name(old_name),
|
1284
1374
|
new_name=self.quote_name(new_name),
|
1285
1375
|
)
|
1286
1376
|
|
1287
|
-
def _index_columns(
|
1377
|
+
def _index_columns(
|
1378
|
+
self,
|
1379
|
+
table: str,
|
1380
|
+
columns: list[str],
|
1381
|
+
col_suffixes: tuple[str, ...],
|
1382
|
+
opclasses: tuple[str, ...],
|
1383
|
+
) -> Columns:
|
1288
1384
|
return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes)
|
1289
1385
|
|
1290
|
-
def _model_indexes_sql(self, model):
|
1386
|
+
def _model_indexes_sql(self, model: type[Model]) -> list[Statement | None]:
|
1291
1387
|
"""
|
1292
1388
|
Return a list of all index SQL statements (field indexes, Meta.indexes) for the specified model.
|
1293
1389
|
"""
|
1294
|
-
output = []
|
1295
|
-
for field in model.
|
1390
|
+
output: list[Statement | None] = []
|
1391
|
+
for field in model._model_meta.local_fields:
|
1296
1392
|
output.extend(self._field_indexes_sql(model, field))
|
1297
1393
|
|
1298
|
-
for index in model.
|
1394
|
+
for index in model.model_options.indexes:
|
1299
1395
|
if (
|
1300
1396
|
not index.contains_expressions
|
1301
1397
|
or self.connection.features.supports_expression_indexes
|
@@ -1303,16 +1399,18 @@ class BaseDatabaseSchemaEditor:
|
|
1303
1399
|
output.append(index.create_sql(model, self))
|
1304
1400
|
return output
|
1305
1401
|
|
1306
|
-
def _field_indexes_sql(self, model, field):
|
1402
|
+
def _field_indexes_sql(self, model: type[Model], field: Field) -> list[Statement]:
|
1307
1403
|
"""
|
1308
1404
|
Return a list of all index SQL statements for the specified field.
|
1309
1405
|
"""
|
1310
|
-
output = []
|
1406
|
+
output: list[Statement] = []
|
1311
1407
|
if self._field_should_be_indexed(model, field):
|
1312
1408
|
output.append(self._create_index_sql(model, fields=[field]))
|
1313
1409
|
return output
|
1314
1410
|
|
1315
|
-
def _field_should_be_altered(
|
1411
|
+
def _field_should_be_altered(
|
1412
|
+
self, old_field: Field, new_field: Field, ignore: set[str] | None = None
|
1413
|
+
) -> bool:
|
1316
1414
|
ignore = ignore or set()
|
1317
1415
|
_, old_path, old_args, old_kwargs = old_field.deconstruct()
|
1318
1416
|
_, new_path, new_args, new_kwargs = new_field.deconstruct()
|
@@ -1321,21 +1419,23 @@ class BaseDatabaseSchemaEditor:
|
|
1321
1419
|
# - changing an attribute that doesn't affect the schema
|
1322
1420
|
# - changing an attribute in the provided set of ignored attributes
|
1323
1421
|
# - adding only a db_column and the column name is not changed
|
1324
|
-
for attr in ignore.union(old_field.non_db_attrs):
|
1422
|
+
for attr in ignore.union(old_field.non_db_attrs): # type: ignore[attr-defined]
|
1325
1423
|
old_kwargs.pop(attr, None)
|
1326
|
-
for attr in ignore.union(new_field.non_db_attrs):
|
1424
|
+
for attr in ignore.union(new_field.non_db_attrs): # type: ignore[attr-defined]
|
1327
1425
|
new_kwargs.pop(attr, None)
|
1328
1426
|
return self.quote_name(old_field.column) != self.quote_name(
|
1329
1427
|
new_field.column
|
1330
1428
|
) or (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs)
|
1331
1429
|
|
1332
|
-
def _field_should_be_indexed(self, model, field):
|
1333
|
-
return (field.remote_field and field.db_index) and not field.primary_key
|
1430
|
+
def _field_should_be_indexed(self, model: type[Model], field: Field) -> bool:
|
1431
|
+
return (field.remote_field and field.db_index) and not field.primary_key # type: ignore[attr-defined]
|
1334
1432
|
|
1335
|
-
def _field_became_primary_key(self, old_field, new_field):
|
1433
|
+
def _field_became_primary_key(self, old_field: Field, new_field: Field) -> bool:
|
1336
1434
|
return not old_field.primary_key and new_field.primary_key
|
1337
1435
|
|
1338
|
-
def _rename_field_sql(
|
1436
|
+
def _rename_field_sql(
|
1437
|
+
self, table: str, old_field: Field, new_field: Field, new_type: str
|
1438
|
+
) -> str:
|
1339
1439
|
return self.sql_rename_column % {
|
1340
1440
|
"table": self.quote_name(table),
|
1341
1441
|
"old_column": self.quote_name(old_field.column),
|
@@ -1343,14 +1443,18 @@ class BaseDatabaseSchemaEditor:
|
|
1343
1443
|
"type": new_type,
|
1344
1444
|
}
|
1345
1445
|
|
1346
|
-
def _create_fk_sql(
|
1347
|
-
|
1446
|
+
def _create_fk_sql(
|
1447
|
+
self, model: type[Model], field: ForeignKey, suffix: str
|
1448
|
+
) -> Statement:
|
1449
|
+
table = Table(model.model_options.db_table, self.quote_name)
|
1348
1450
|
name = self._fk_constraint_name(model, field, suffix)
|
1349
|
-
column = Columns(model.
|
1350
|
-
to_table = Table(
|
1451
|
+
column = Columns(model.model_options.db_table, [field.column], self.quote_name)
|
1452
|
+
to_table = Table(
|
1453
|
+
field.target_field.model.model_options.db_table, self.quote_name
|
1454
|
+
)
|
1351
1455
|
to_column = Columns(
|
1352
|
-
field.target_field.model.
|
1353
|
-
[field.target_field.column],
|
1456
|
+
field.target_field.model.model_options.db_table,
|
1457
|
+
[field.target_field.column], # type: ignore[attr-defined]
|
1354
1458
|
self.quote_name,
|
1355
1459
|
)
|
1356
1460
|
deferrable = self.connection.ops.deferrable_sql()
|
@@ -1364,41 +1468,44 @@ class BaseDatabaseSchemaEditor:
|
|
1364
1468
|
deferrable=deferrable,
|
1365
1469
|
)
|
1366
1470
|
|
1367
|
-
def _fk_constraint_name(
|
1368
|
-
|
1471
|
+
def _fk_constraint_name(
|
1472
|
+
self, model: type[Model], field: ForeignKey, suffix: str
|
1473
|
+
) -> ForeignKeyName:
|
1474
|
+
def create_fk_name(*args: Any, **kwargs: Any) -> str:
|
1369
1475
|
return self.quote_name(self._create_index_name(*args, **kwargs))
|
1370
1476
|
|
1371
1477
|
return ForeignKeyName(
|
1372
|
-
model.
|
1478
|
+
model.model_options.db_table,
|
1373
1479
|
[field.column],
|
1374
|
-
split_identifier(field.target_field.model.
|
1375
|
-
[field.target_field.column],
|
1480
|
+
split_identifier(field.target_field.model.model_options.db_table)[1],
|
1481
|
+
[field.target_field.column], # type: ignore[attr-defined]
|
1376
1482
|
suffix,
|
1377
1483
|
create_fk_name,
|
1378
1484
|
)
|
1379
1485
|
|
1380
|
-
def _delete_fk_sql(self, model, name):
|
1486
|
+
def _delete_fk_sql(self, model: type[Model], name: str) -> Statement:
|
1381
1487
|
return self._delete_constraint_sql(self.sql_delete_fk, model, name)
|
1382
1488
|
|
1383
|
-
def _deferrable_constraint_sql(self, deferrable):
|
1489
|
+
def _deferrable_constraint_sql(self, deferrable: Deferrable | None) -> str:
|
1384
1490
|
if deferrable is None:
|
1385
1491
|
return ""
|
1386
1492
|
if deferrable == Deferrable.DEFERRED:
|
1387
1493
|
return " DEFERRABLE INITIALLY DEFERRED"
|
1388
1494
|
if deferrable == Deferrable.IMMEDIATE:
|
1389
1495
|
return " DEFERRABLE INITIALLY IMMEDIATE"
|
1496
|
+
return ""
|
1390
1497
|
|
1391
1498
|
def _unique_sql(
|
1392
1499
|
self,
|
1393
|
-
model,
|
1394
|
-
fields,
|
1395
|
-
name,
|
1396
|
-
condition=None,
|
1397
|
-
deferrable=None,
|
1398
|
-
include=None,
|
1399
|
-
opclasses=None,
|
1400
|
-
expressions=None,
|
1401
|
-
):
|
1500
|
+
model: type[Model],
|
1501
|
+
fields: Iterable[Field],
|
1502
|
+
name: str,
|
1503
|
+
condition: str | None = None,
|
1504
|
+
deferrable: Deferrable | None = None,
|
1505
|
+
include: list[str] | None = None,
|
1506
|
+
opclasses: tuple[str, ...] | None = None,
|
1507
|
+
expressions: Any = None,
|
1508
|
+
) -> str | None:
|
1402
1509
|
if (
|
1403
1510
|
deferrable
|
1404
1511
|
and not self.connection.features.supports_deferrable_unique_constraints
|
@@ -1430,15 +1537,15 @@ class BaseDatabaseSchemaEditor:
|
|
1430
1537
|
|
1431
1538
|
def _create_unique_sql(
|
1432
1539
|
self,
|
1433
|
-
model,
|
1434
|
-
fields,
|
1435
|
-
name=None,
|
1436
|
-
condition=None,
|
1437
|
-
deferrable=None,
|
1438
|
-
include=None,
|
1439
|
-
opclasses=None,
|
1440
|
-
expressions=None,
|
1441
|
-
):
|
1540
|
+
model: type[Model],
|
1541
|
+
fields: Iterable[Field],
|
1542
|
+
name: str | None = None,
|
1543
|
+
condition: str | None = None,
|
1544
|
+
deferrable: Deferrable | None = None,
|
1545
|
+
include: list[str] | None = None,
|
1546
|
+
opclasses: tuple[str, ...] | None = None,
|
1547
|
+
expressions: Any = None,
|
1548
|
+
) -> Statement | None:
|
1442
1549
|
if (
|
1443
1550
|
(
|
1444
1551
|
deferrable
|
@@ -1453,7 +1560,7 @@ class BaseDatabaseSchemaEditor:
|
|
1453
1560
|
return None
|
1454
1561
|
|
1455
1562
|
compiler = Query(model, alias_cols=False).get_compiler()
|
1456
|
-
table = model.
|
1563
|
+
table = model.model_options.db_table
|
1457
1564
|
columns = [field.column for field in fields]
|
1458
1565
|
if name is None:
|
1459
1566
|
name = self._unique_constraint_name(table, columns, quote=True)
|
@@ -1464,25 +1571,27 @@ class BaseDatabaseSchemaEditor:
|
|
1464
1571
|
else:
|
1465
1572
|
sql = self.sql_create_unique
|
1466
1573
|
if columns:
|
1467
|
-
|
1574
|
+
columns_obj: Columns | Expressions = self._index_columns(
|
1468
1575
|
table, columns, col_suffixes=(), opclasses=opclasses
|
1469
1576
|
)
|
1470
1577
|
else:
|
1471
|
-
|
1578
|
+
columns_obj = Expressions(table, expressions, compiler, self.quote_value)
|
1472
1579
|
return Statement(
|
1473
1580
|
sql,
|
1474
1581
|
table=Table(table, self.quote_name),
|
1475
1582
|
name=name,
|
1476
|
-
columns=
|
1583
|
+
columns=columns_obj,
|
1477
1584
|
condition=self._index_condition_sql(condition),
|
1478
1585
|
deferrable=self._deferrable_constraint_sql(deferrable),
|
1479
1586
|
include=self._index_include_sql(model, include),
|
1480
1587
|
)
|
1481
1588
|
|
1482
|
-
def _unique_constraint_name(
|
1589
|
+
def _unique_constraint_name(
|
1590
|
+
self, table: str, columns: list[str], quote: bool = True
|
1591
|
+
) -> IndexName | str:
|
1483
1592
|
if quote:
|
1484
1593
|
|
1485
|
-
def create_unique_name(*args, **kwargs):
|
1594
|
+
def create_unique_name(*args: Any, **kwargs: Any) -> str:
|
1486
1595
|
return self.quote_name(self._create_index_name(*args, **kwargs))
|
1487
1596
|
|
1488
1597
|
else:
|
@@ -1492,14 +1601,14 @@ class BaseDatabaseSchemaEditor:
|
|
1492
1601
|
|
1493
1602
|
def _delete_unique_sql(
|
1494
1603
|
self,
|
1495
|
-
model,
|
1496
|
-
name,
|
1497
|
-
condition=None,
|
1498
|
-
deferrable=None,
|
1499
|
-
include=None,
|
1500
|
-
opclasses=None,
|
1501
|
-
expressions=None,
|
1502
|
-
):
|
1604
|
+
model: type[Model],
|
1605
|
+
name: str,
|
1606
|
+
condition: str | None = None,
|
1607
|
+
deferrable: Deferrable | None = None,
|
1608
|
+
include: list[str] | None = None,
|
1609
|
+
opclasses: tuple[str, ...] | None = None,
|
1610
|
+
expressions: Any = None,
|
1611
|
+
) -> Statement | None:
|
1503
1612
|
if (
|
1504
1613
|
(
|
1505
1614
|
deferrable
|
@@ -1518,46 +1627,50 @@ class BaseDatabaseSchemaEditor:
|
|
1518
1627
|
sql = self.sql_delete_unique
|
1519
1628
|
return self._delete_constraint_sql(sql, model, name)
|
1520
1629
|
|
1521
|
-
def _check_sql(self, name, check):
|
1630
|
+
def _check_sql(self, name: str, check: str) -> str:
|
1522
1631
|
return self.sql_constraint % {
|
1523
1632
|
"name": self.quote_name(name),
|
1524
1633
|
"constraint": self.sql_check_constraint % {"check": check},
|
1525
1634
|
}
|
1526
1635
|
|
1527
|
-
def _create_check_sql(
|
1636
|
+
def _create_check_sql(
|
1637
|
+
self, model: type[Model], name: str, check: str
|
1638
|
+
) -> Statement | None:
|
1528
1639
|
if not self.connection.features.supports_table_check_constraints:
|
1529
1640
|
return None
|
1530
1641
|
return Statement(
|
1531
1642
|
self.sql_create_check,
|
1532
|
-
table=Table(model.
|
1643
|
+
table=Table(model.model_options.db_table, self.quote_name),
|
1533
1644
|
name=self.quote_name(name),
|
1534
1645
|
check=check,
|
1535
1646
|
)
|
1536
1647
|
|
1537
|
-
def _delete_check_sql(self, model, name):
|
1648
|
+
def _delete_check_sql(self, model: type[Model], name: str) -> Statement | None:
|
1538
1649
|
if not self.connection.features.supports_table_check_constraints:
|
1539
1650
|
return None
|
1540
1651
|
return self._delete_constraint_sql(self.sql_delete_check, model, name)
|
1541
1652
|
|
1542
|
-
def _delete_constraint_sql(
|
1653
|
+
def _delete_constraint_sql(
|
1654
|
+
self, template: str, model: type[Model], name: str
|
1655
|
+
) -> Statement:
|
1543
1656
|
return Statement(
|
1544
1657
|
template,
|
1545
|
-
table=Table(model.
|
1658
|
+
table=Table(model.model_options.db_table, self.quote_name),
|
1546
1659
|
name=self.quote_name(name),
|
1547
1660
|
)
|
1548
1661
|
|
1549
1662
|
def _constraint_names(
|
1550
1663
|
self,
|
1551
|
-
model,
|
1552
|
-
column_names=None,
|
1553
|
-
unique=None,
|
1554
|
-
primary_key=None,
|
1555
|
-
index=None,
|
1556
|
-
foreign_key=None,
|
1557
|
-
check=None,
|
1558
|
-
type_=None,
|
1559
|
-
exclude=None,
|
1560
|
-
):
|
1664
|
+
model: type[Model],
|
1665
|
+
column_names: list[str] | None = None,
|
1666
|
+
unique: bool | None = None,
|
1667
|
+
primary_key: bool | None = None,
|
1668
|
+
index: bool | None = None,
|
1669
|
+
foreign_key: bool | None = None,
|
1670
|
+
check: bool | None = None,
|
1671
|
+
type_: str | None = None,
|
1672
|
+
exclude: set[str] | None = None,
|
1673
|
+
) -> list[str]:
|
1561
1674
|
"""Return all constraint names matching the columns and conditions."""
|
1562
1675
|
if column_names is not None:
|
1563
1676
|
column_names = [
|
@@ -1570,9 +1683,9 @@ class BaseDatabaseSchemaEditor:
|
|
1570
1683
|
]
|
1571
1684
|
with self.connection.cursor() as cursor:
|
1572
1685
|
constraints = self.connection.introspection.get_constraints(
|
1573
|
-
cursor, model.
|
1686
|
+
cursor, model.model_options.db_table
|
1574
1687
|
)
|
1575
|
-
result = []
|
1688
|
+
result: list[str] = []
|
1576
1689
|
for name, infodict in constraints.items():
|
1577
1690
|
if column_names is None or column_names == infodict["columns"]:
|
1578
1691
|
if unique is not None and infodict["unique"] != unique:
|
@@ -1591,29 +1704,36 @@ class BaseDatabaseSchemaEditor:
|
|
1591
1704
|
result.append(name)
|
1592
1705
|
return result
|
1593
1706
|
|
1594
|
-
def _delete_primary_key(self, model, strict=False):
|
1707
|
+
def _delete_primary_key(self, model: type[Model], strict: bool = False) -> None:
|
1595
1708
|
constraint_names = self._constraint_names(model, primary_key=True)
|
1596
1709
|
if strict and len(constraint_names) != 1:
|
1597
1710
|
raise ValueError(
|
1598
|
-
f"Found wrong number ({len(constraint_names)}) of PK constraints for {model.
|
1711
|
+
f"Found wrong number ({len(constraint_names)}) of PK constraints for {model.model_options.db_table}"
|
1599
1712
|
)
|
1600
1713
|
for constraint_name in constraint_names:
|
1601
1714
|
self.execute(self._delete_primary_key_sql(model, constraint_name))
|
1602
1715
|
|
1603
|
-
def _create_primary_key_sql(self, model, field):
|
1716
|
+
def _create_primary_key_sql(self, model: type[Model], field: Field) -> Statement:
|
1604
1717
|
return Statement(
|
1605
1718
|
self.sql_create_pk,
|
1606
|
-
table=Table(model.
|
1719
|
+
table=Table(model.model_options.db_table, self.quote_name),
|
1607
1720
|
name=self.quote_name(
|
1608
1721
|
self._create_index_name(
|
1609
|
-
model.
|
1722
|
+
model.model_options.db_table, [field.column], suffix="_pk"
|
1610
1723
|
)
|
1611
1724
|
),
|
1612
|
-
columns=Columns(
|
1725
|
+
columns=Columns(
|
1726
|
+
model.model_options.db_table, [field.column], self.quote_name
|
1727
|
+
),
|
1613
1728
|
)
|
1614
1729
|
|
1615
|
-
def _delete_primary_key_sql(self, model, name):
|
1730
|
+
def _delete_primary_key_sql(self, model: type[Model], name: str) -> Statement:
|
1616
1731
|
return self._delete_constraint_sql(self.sql_delete_pk, model, name)
|
1617
1732
|
|
1618
|
-
def _collate_sql(
|
1733
|
+
def _collate_sql(
|
1734
|
+
self,
|
1735
|
+
collation: str | None,
|
1736
|
+
old_collation: str | None = None,
|
1737
|
+
table_name: str | None = None,
|
1738
|
+
) -> str:
|
1619
1739
|
return "COLLATE " + self.quote_name(collation) if collation else ""
|