plain.models 0.49.2__py3-none-any.whl → 0.50.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.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +13 -0
  2. plain/models/aggregates.py +42 -19
  3. plain/models/backends/base/base.py +125 -105
  4. plain/models/backends/base/client.py +11 -3
  5. plain/models/backends/base/creation.py +22 -12
  6. plain/models/backends/base/features.py +10 -4
  7. plain/models/backends/base/introspection.py +29 -16
  8. plain/models/backends/base/operations.py +187 -91
  9. plain/models/backends/base/schema.py +267 -165
  10. plain/models/backends/base/validation.py +12 -3
  11. plain/models/backends/ddl_references.py +85 -43
  12. plain/models/backends/mysql/base.py +29 -26
  13. plain/models/backends/mysql/client.py +7 -2
  14. plain/models/backends/mysql/compiler.py +12 -3
  15. plain/models/backends/mysql/creation.py +5 -2
  16. plain/models/backends/mysql/features.py +24 -22
  17. plain/models/backends/mysql/introspection.py +22 -13
  18. plain/models/backends/mysql/operations.py +106 -39
  19. plain/models/backends/mysql/schema.py +48 -24
  20. plain/models/backends/mysql/validation.py +13 -6
  21. plain/models/backends/postgresql/base.py +41 -34
  22. plain/models/backends/postgresql/client.py +7 -2
  23. plain/models/backends/postgresql/creation.py +10 -5
  24. plain/models/backends/postgresql/introspection.py +15 -8
  25. plain/models/backends/postgresql/operations.py +109 -42
  26. plain/models/backends/postgresql/schema.py +85 -46
  27. plain/models/backends/sqlite3/_functions.py +151 -115
  28. plain/models/backends/sqlite3/base.py +37 -23
  29. plain/models/backends/sqlite3/client.py +7 -1
  30. plain/models/backends/sqlite3/creation.py +9 -5
  31. plain/models/backends/sqlite3/features.py +5 -3
  32. plain/models/backends/sqlite3/introspection.py +32 -16
  33. plain/models/backends/sqlite3/operations.py +125 -42
  34. plain/models/backends/sqlite3/schema.py +82 -58
  35. plain/models/backends/utils.py +52 -29
  36. plain/models/backups/cli.py +8 -6
  37. plain/models/backups/clients.py +16 -7
  38. plain/models/backups/core.py +24 -13
  39. plain/models/base.py +113 -74
  40. plain/models/cli.py +94 -63
  41. plain/models/config.py +1 -1
  42. plain/models/connections.py +23 -7
  43. plain/models/constraints.py +65 -47
  44. plain/models/database_url.py +1 -1
  45. plain/models/db.py +6 -2
  46. plain/models/deletion.py +66 -43
  47. plain/models/entrypoints.py +1 -1
  48. plain/models/enums.py +22 -11
  49. plain/models/exceptions.py +23 -8
  50. plain/models/expressions.py +440 -257
  51. plain/models/fields/__init__.py +253 -202
  52. plain/models/fields/json.py +120 -54
  53. plain/models/fields/mixins.py +12 -8
  54. plain/models/fields/related.py +284 -252
  55. plain/models/fields/related_descriptors.py +31 -22
  56. plain/models/fields/related_lookups.py +23 -11
  57. plain/models/fields/related_managers.py +81 -47
  58. plain/models/fields/reverse_related.py +58 -55
  59. plain/models/forms.py +89 -63
  60. plain/models/functions/comparison.py +71 -18
  61. plain/models/functions/datetime.py +79 -29
  62. plain/models/functions/math.py +43 -10
  63. plain/models/functions/mixins.py +24 -7
  64. plain/models/functions/text.py +104 -25
  65. plain/models/functions/window.py +12 -6
  66. plain/models/indexes.py +52 -28
  67. plain/models/lookups.py +228 -153
  68. plain/models/migrations/autodetector.py +86 -43
  69. plain/models/migrations/exceptions.py +7 -3
  70. plain/models/migrations/executor.py +33 -7
  71. plain/models/migrations/graph.py +79 -50
  72. plain/models/migrations/loader.py +45 -22
  73. plain/models/migrations/migration.py +23 -18
  74. plain/models/migrations/operations/base.py +37 -19
  75. plain/models/migrations/operations/fields.py +89 -42
  76. plain/models/migrations/operations/models.py +245 -143
  77. plain/models/migrations/operations/special.py +82 -25
  78. plain/models/migrations/optimizer.py +7 -2
  79. plain/models/migrations/questioner.py +58 -31
  80. plain/models/migrations/recorder.py +18 -11
  81. plain/models/migrations/serializer.py +50 -39
  82. plain/models/migrations/state.py +220 -133
  83. plain/models/migrations/utils.py +29 -13
  84. plain/models/migrations/writer.py +17 -14
  85. plain/models/options.py +63 -56
  86. plain/models/otel.py +16 -6
  87. plain/models/preflight.py +35 -12
  88. plain/models/query.py +323 -228
  89. plain/models/query_utils.py +93 -58
  90. plain/models/registry.py +34 -16
  91. plain/models/sql/compiler.py +146 -97
  92. plain/models/sql/datastructures.py +38 -25
  93. plain/models/sql/query.py +255 -169
  94. plain/models/sql/subqueries.py +32 -21
  95. plain/models/sql/where.py +54 -29
  96. plain/models/test/pytest.py +15 -11
  97. plain/models/test/utils.py +4 -2
  98. plain/models/transaction.py +20 -7
  99. plain/models/utils.py +13 -5
  100. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
  101. plain_models-0.50.0.dist-info/RECORD +122 -0
  102. plain_models-0.49.2.dist-info/RECORD +0 -122
  103. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import decimal
3
5
  import uuid
6
+ from collections.abc import Callable
4
7
  from functools import cached_property, lru_cache
8
+ from typing import TYPE_CHECKING, Any
5
9
 
6
10
  from plain import models
7
11
  from plain.models.backends.base.operations import BaseDatabaseOperations
@@ -12,6 +16,9 @@ from plain.models.expressions import Col
12
16
  from plain.utils import timezone
13
17
  from plain.utils.dateparse import parse_date, parse_datetime, parse_time
14
18
 
19
+ if TYPE_CHECKING:
20
+ from plain.models.backends.base.base import BaseDatabaseWrapper
21
+
15
22
 
16
23
  class DatabaseOperations(BaseDatabaseOperations):
17
24
  cast_char_field_without_max_length = "text"
@@ -24,7 +31,7 @@ class DatabaseOperations(BaseDatabaseOperations):
24
31
  # SQLite. Use JSON_TYPE() instead.
25
32
  jsonfield_datatype_values = frozenset(["null", "false", "true"])
26
33
 
27
- def bulk_batch_size(self, fields, objs):
34
+ def bulk_batch_size(self, fields: list[Any], objs: list[Any]) -> int:
28
35
  """
29
36
  SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of
30
37
  999 variables per query.
@@ -39,7 +46,7 @@ class DatabaseOperations(BaseDatabaseOperations):
39
46
  else:
40
47
  return len(objs)
41
48
 
42
- def check_expression_support(self, expression):
49
+ def check_expression_support(self, expression: Any) -> None:
43
50
  bad_fields = (models.DateField, models.DateTimeField, models.TimeField)
44
51
  bad_aggregates = (models.Sum, models.Avg, models.Variance, models.StdDev)
45
52
  if isinstance(expression, bad_aggregates):
@@ -67,7 +74,9 @@ class DatabaseOperations(BaseDatabaseOperations):
67
74
  "accepting multiple arguments."
68
75
  )
69
76
 
70
- def date_extract_sql(self, lookup_type, sql, params):
77
+ def date_extract_sql(
78
+ self, lookup_type: str, sql: str, params: list[Any] | tuple[Any, ...]
79
+ ) -> tuple[str, tuple[Any, ...]]:
71
80
  """
72
81
  Support EXTRACT with a user-defined function plain_date_extract()
73
82
  that's registered in connect(). Use single quotes because this is a
@@ -75,69 +84,103 @@ class DatabaseOperations(BaseDatabaseOperations):
75
84
  """
76
85
  return f"plain_date_extract(%s, {sql})", (lookup_type.lower(), *params)
77
86
 
78
- def fetch_returned_insert_rows(self, cursor):
87
+ def fetch_returned_insert_rows(self, cursor: Any) -> list[Any]:
79
88
  """
80
89
  Given a cursor object that has just performed an INSERT...RETURNING
81
90
  statement into a table, return the list of returned data.
82
91
  """
83
92
  return cursor.fetchall()
84
93
 
85
- def format_for_duration_arithmetic(self, sql):
94
+ def format_for_duration_arithmetic(self, sql: str) -> str:
86
95
  """Do nothing since formatting is handled in the custom function."""
87
96
  return sql
88
97
 
89
- def date_trunc_sql(self, lookup_type, sql, params, tzname=None):
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, ...]]:
90
105
  return f"plain_date_trunc(%s, {sql}, %s, %s)", (
91
106
  lookup_type.lower(),
92
107
  *params,
93
108
  *self._convert_tznames_to_sql(tzname),
94
109
  )
95
110
 
96
- def time_trunc_sql(self, lookup_type, sql, params, tzname=None):
111
+ def time_trunc_sql(
112
+ self,
113
+ lookup_type: str,
114
+ sql: str,
115
+ params: list[Any] | tuple[Any, ...],
116
+ tzname: str | None = None,
117
+ ) -> tuple[str, tuple[Any, ...]]:
97
118
  return f"plain_time_trunc(%s, {sql}, %s, %s)", (
98
119
  lookup_type.lower(),
99
120
  *params,
100
121
  *self._convert_tznames_to_sql(tzname),
101
122
  )
102
123
 
103
- def _convert_tznames_to_sql(self, tzname):
124
+ def _convert_tznames_to_sql(
125
+ self, tzname: str | None
126
+ ) -> tuple[str | None, str | None]:
104
127
  if tzname:
105
128
  return tzname, self.connection.timezone_name
106
129
  return None, None
107
130
 
108
- def datetime_cast_date_sql(self, sql, params, tzname):
131
+ def datetime_cast_date_sql(
132
+ self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
133
+ ) -> tuple[str, tuple[Any, ...]]:
109
134
  return f"plain_datetime_cast_date({sql}, %s, %s)", (
110
135
  *params,
111
136
  *self._convert_tznames_to_sql(tzname),
112
137
  )
113
138
 
114
- def datetime_cast_time_sql(self, sql, params, tzname):
139
+ def datetime_cast_time_sql(
140
+ self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
141
+ ) -> tuple[str, tuple[Any, ...]]:
115
142
  return f"plain_datetime_cast_time({sql}, %s, %s)", (
116
143
  *params,
117
144
  *self._convert_tznames_to_sql(tzname),
118
145
  )
119
146
 
120
- def datetime_extract_sql(self, lookup_type, sql, params, tzname):
147
+ def datetime_extract_sql(
148
+ self,
149
+ lookup_type: str,
150
+ sql: str,
151
+ params: list[Any] | tuple[Any, ...],
152
+ tzname: str | None,
153
+ ) -> tuple[str, tuple[Any, ...]]:
121
154
  return f"plain_datetime_extract(%s, {sql}, %s, %s)", (
122
155
  lookup_type.lower(),
123
156
  *params,
124
157
  *self._convert_tznames_to_sql(tzname),
125
158
  )
126
159
 
127
- def datetime_trunc_sql(self, lookup_type, sql, params, tzname):
160
+ def datetime_trunc_sql(
161
+ self,
162
+ lookup_type: str,
163
+ sql: str,
164
+ params: list[Any] | tuple[Any, ...],
165
+ tzname: str | None,
166
+ ) -> tuple[str, tuple[Any, ...]]:
128
167
  return f"plain_datetime_trunc(%s, {sql}, %s, %s)", (
129
168
  lookup_type.lower(),
130
169
  *params,
131
170
  *self._convert_tznames_to_sql(tzname),
132
171
  )
133
172
 
134
- def time_extract_sql(self, lookup_type, sql, params):
173
+ def time_extract_sql(
174
+ self, lookup_type: str, sql: str, params: list[Any] | tuple[Any, ...]
175
+ ) -> tuple[str, tuple[Any, ...]]:
135
176
  return f"plain_time_extract(%s, {sql})", (lookup_type.lower(), *params)
136
177
 
137
- def pk_default_value(self):
178
+ def pk_default_value(self) -> str:
138
179
  return "NULL"
139
180
 
140
- def _quote_params_for_last_executed_query(self, params):
181
+ def _quote_params_for_last_executed_query(
182
+ self, params: list[Any] | tuple[Any, ...]
183
+ ) -> tuple[Any, ...]:
141
184
  """
142
185
  Only for last_executed_query! Don't use this to execute SQL queries!
143
186
  """
@@ -164,7 +207,12 @@ class DatabaseOperations(BaseDatabaseOperations):
164
207
  finally:
165
208
  cursor.close()
166
209
 
167
- def last_executed_query(self, cursor, sql, params):
210
+ def last_executed_query(
211
+ self,
212
+ cursor: Any,
213
+ sql: str,
214
+ params: list[Any] | tuple[Any, ...] | dict[str, Any] | None,
215
+ ) -> str:
168
216
  # Python substitutes parameters in Modules/_sqlite/cursor.c with:
169
217
  # bind_parameters(state, self->statement, parameters);
170
218
  # Unfortunately there is no way to reach self->statement from Python,
@@ -173,7 +221,7 @@ class DatabaseOperations(BaseDatabaseOperations):
173
221
  if isinstance(params, list | tuple):
174
222
  params = self._quote_params_for_last_executed_query(params)
175
223
  else:
176
- values = tuple(params.values())
224
+ values = tuple(params.values()) # type: ignore[union-attr]
177
225
  values = self._quote_params_for_last_executed_query(values)
178
226
  params = dict(zip(params, values))
179
227
  return sql % params
@@ -182,15 +230,15 @@ class DatabaseOperations(BaseDatabaseOperations):
182
230
  else:
183
231
  return sql
184
232
 
185
- def quote_name(self, name):
233
+ def quote_name(self, name: str) -> str:
186
234
  if name.startswith('"') and name.endswith('"'):
187
235
  return name # Quoting once is enough.
188
236
  return f'"{name}"'
189
237
 
190
- def no_limit_value(self):
238
+ def no_limit_value(self) -> int:
191
239
  return -1
192
240
 
193
- def __references_graph(self, table_name):
241
+ def __references_graph(self, table_name: str) -> list[str]:
194
242
  query = """
195
243
  WITH tables AS (
196
244
  SELECT %s name
@@ -210,12 +258,14 @@ class DatabaseOperations(BaseDatabaseOperations):
210
258
  return [row[0] for row in results.fetchall()]
211
259
 
212
260
  @cached_property
213
- def _references_graph(self):
261
+ def _references_graph(self) -> Callable[[str], list[str]]:
214
262
  # 512 is large enough to fit the ~330 tables (as of this writing) in
215
263
  # Plain's test suite.
216
264
  return lru_cache(maxsize=512)(self.__references_graph)
217
265
 
218
- def adapt_datetimefield_value(self, value):
266
+ def adapt_datetimefield_value(
267
+ self, value: datetime.datetime | Any | None
268
+ ) -> str | Any | None:
219
269
  if value is None:
220
270
  return None
221
271
 
@@ -229,7 +279,9 @@ class DatabaseOperations(BaseDatabaseOperations):
229
279
 
230
280
  return str(value)
231
281
 
232
- def adapt_timefield_value(self, value):
282
+ def adapt_timefield_value(
283
+ self, value: datetime.time | Any | None
284
+ ) -> str | Any | None:
233
285
  if value is None:
234
286
  return None
235
287
 
@@ -238,12 +290,12 @@ class DatabaseOperations(BaseDatabaseOperations):
238
290
  return value
239
291
 
240
292
  # SQLite doesn't support tz-aware datetimes
241
- if timezone.is_aware(value):
293
+ if timezone.is_aware(value): # type: ignore[arg-type]
242
294
  raise ValueError("SQLite backend does not support timezone-aware times.")
243
295
 
244
296
  return str(value)
245
297
 
246
- def get_db_converters(self, expression):
298
+ def get_db_converters(self, expression: Any) -> list[Any]:
247
299
  converters = super().get_db_converters(expression)
248
300
  internal_type = expression.output_field.get_internal_type()
249
301
  if internal_type == "DateTimeField":
@@ -260,27 +312,33 @@ class DatabaseOperations(BaseDatabaseOperations):
260
312
  converters.append(self.convert_booleanfield_value)
261
313
  return converters
262
314
 
263
- def convert_datetimefield_value(self, value, expression, connection):
315
+ def convert_datetimefield_value(
316
+ self, value: Any, expression: Any, connection: BaseDatabaseWrapper
317
+ ) -> datetime.datetime | None:
264
318
  if value is not None:
265
319
  if not isinstance(value, datetime.datetime):
266
320
  value = parse_datetime(value)
267
- if not timezone.is_aware(value):
321
+ if value is not None and not timezone.is_aware(value):
268
322
  value = timezone.make_aware(value, self.connection.timezone)
269
323
  return value
270
324
 
271
- def convert_datefield_value(self, value, expression, connection):
325
+ def convert_datefield_value(
326
+ self, value: Any, expression: Any, connection: BaseDatabaseWrapper
327
+ ) -> datetime.date | None:
272
328
  if value is not None:
273
329
  if not isinstance(value, datetime.date):
274
330
  value = parse_date(value)
275
331
  return value
276
332
 
277
- def convert_timefield_value(self, value, expression, connection):
333
+ def convert_timefield_value(
334
+ self, value: Any, expression: Any, connection: BaseDatabaseWrapper
335
+ ) -> datetime.time | None:
278
336
  if value is not None:
279
337
  if not isinstance(value, datetime.time):
280
338
  value = parse_time(value)
281
339
  return value
282
340
 
283
- def get_decimalfield_converter(self, expression):
341
+ def get_decimalfield_converter(self, expression: Any) -> Callable[..., Any]:
284
342
  # SQLite stores only 15 significant digits. Digits coming from
285
343
  # float inaccuracy must be removed.
286
344
  create_decimal = decimal.Context(prec=15).create_decimal_from_float
@@ -289,34 +347,46 @@ class DatabaseOperations(BaseDatabaseOperations):
289
347
  -expression.output_field.decimal_places
290
348
  )
291
349
 
292
- def converter(value, expression, connection):
350
+ def converter(
351
+ value: Any, expression: Any, connection: BaseDatabaseWrapper
352
+ ) -> decimal.Decimal | None:
293
353
  if value is not None:
294
354
  return create_decimal(value).quantize(
295
355
  quantize_value, context=expression.output_field.context
296
356
  )
357
+ return None
297
358
 
298
359
  else:
299
360
 
300
- def converter(value, expression, connection):
361
+ def converter(
362
+ value: Any, expression: Any, connection: BaseDatabaseWrapper
363
+ ) -> decimal.Decimal | None:
301
364
  if value is not None:
302
365
  return create_decimal(value)
366
+ return None
303
367
 
304
368
  return converter
305
369
 
306
- def convert_uuidfield_value(self, value, expression, connection):
370
+ def convert_uuidfield_value(
371
+ self, value: Any, expression: Any, connection: BaseDatabaseWrapper
372
+ ) -> uuid.UUID | None:
307
373
  if value is not None:
308
374
  value = uuid.UUID(value)
309
375
  return value
310
376
 
311
- def convert_booleanfield_value(self, value, expression, connection):
377
+ def convert_booleanfield_value(
378
+ self, value: Any, expression: Any, connection: BaseDatabaseWrapper
379
+ ) -> bool | Any:
312
380
  return bool(value) if value in (1, 0) else value
313
381
 
314
- def bulk_insert_sql(self, fields, placeholder_rows):
382
+ def bulk_insert_sql(
383
+ self, fields: list[Any], placeholder_rows: list[list[str]]
384
+ ) -> str:
315
385
  placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
316
386
  values_sql = ", ".join(f"({sql})" for sql in placeholder_rows_sql)
317
387
  return f"VALUES {values_sql}"
318
388
 
319
- def combine_expression(self, connector, sub_expressions):
389
+ def combine_expression(self, connector: str, sub_expressions: list[str]) -> str:
320
390
  # SQLite doesn't have a ^ operator, so use the user-defined POWER
321
391
  # function that's registered in connect().
322
392
  if connector == "^":
@@ -325,7 +395,9 @@ class DatabaseOperations(BaseDatabaseOperations):
325
395
  return "BITXOR({})".format(",".join(sub_expressions))
326
396
  return super().combine_expression(connector, sub_expressions)
327
397
 
328
- def combine_duration_expression(self, connector, sub_expressions):
398
+ def combine_duration_expression(
399
+ self, connector: str, sub_expressions: list[str]
400
+ ) -> str:
329
401
  if connector not in ["+", "-", "*", "/"]:
330
402
  raise DatabaseError(f"Invalid connector for timedelta: {connector}.")
331
403
  fn_params = [f"'{connector}'"] + sub_expressions
@@ -333,7 +405,7 @@ class DatabaseOperations(BaseDatabaseOperations):
333
405
  raise ValueError("Too many params for timedelta operations.")
334
406
  return "plain_format_dtdelta({})".format(", ".join(fn_params))
335
407
 
336
- def integer_field_range(self, internal_type):
408
+ def integer_field_range(self, internal_type: str) -> tuple[int, int]:
337
409
  # SQLite doesn't enforce any integer constraints, but sqlite3 supports
338
410
  # integers up to 64 bits.
339
411
  if internal_type in [
@@ -344,7 +416,12 @@ class DatabaseOperations(BaseDatabaseOperations):
344
416
  return (0, 9223372036854775807)
345
417
  return (-9223372036854775808, 9223372036854775807)
346
418
 
347
- def subtract_temporals(self, internal_type, lhs, rhs):
419
+ def subtract_temporals(
420
+ self,
421
+ internal_type: str,
422
+ lhs: tuple[str, list[Any] | tuple[Any, ...]],
423
+ rhs: tuple[str, list[Any] | tuple[Any, ...]],
424
+ ) -> tuple[str, tuple[Any, ...]]:
348
425
  lhs_sql, lhs_params = lhs
349
426
  rhs_sql, rhs_params = rhs
350
427
  params = (*lhs_params, *rhs_params)
@@ -352,12 +429,12 @@ class DatabaseOperations(BaseDatabaseOperations):
352
429
  return f"plain_time_diff({lhs_sql}, {rhs_sql})", params
353
430
  return f"plain_timestamp_diff({lhs_sql}, {rhs_sql})", params
354
431
 
355
- def insert_statement(self, on_conflict=None):
432
+ def insert_statement(self, on_conflict: Any = None) -> str:
356
433
  if on_conflict == OnConflict.IGNORE:
357
434
  return "INSERT OR IGNORE INTO"
358
435
  return super().insert_statement(on_conflict=on_conflict)
359
436
 
360
- def return_insert_columns(self, fields):
437
+ def return_insert_columns(self, fields: list[Any]) -> tuple[str, tuple[Any, ...]]:
361
438
  # SQLite < 3.35 doesn't support an INSERT...RETURNING statement.
362
439
  if not fields:
363
440
  return "", ()
@@ -367,7 +444,13 @@ class DatabaseOperations(BaseDatabaseOperations):
367
444
  ]
368
445
  return "RETURNING {}".format(", ".join(columns)), ()
369
446
 
370
- def on_conflict_suffix_sql(self, fields, on_conflict, update_fields, unique_fields):
447
+ def on_conflict_suffix_sql(
448
+ self,
449
+ fields: list[Any],
450
+ on_conflict: Any,
451
+ update_fields: list[Any],
452
+ unique_fields: list[Any],
453
+ ) -> str:
371
454
  if (
372
455
  on_conflict == OnConflict.UPDATE
373
456
  and self.connection.features.supports_update_conflicts_with_target