Skip to content

Commit bca7a05

Browse files
authored
Support django 4.2
1 parent 2cc2515 commit bca7a05

File tree

11 files changed

+87
-166
lines changed

11 files changed

+87
-166
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- Fix str(queryset.query) when default database is not clickhouse.
55
- Fix [bug when save django model instance](https://github.com/jayvynl/django-clickhouse-backend/issues/9).
66
- Support [clickhouse-driver 0.2.6](https://github.com/mymarilyn/clickhouse-driver), drop support for python3.6.
7+
- Support [Django 4.2](https://docs.djangoproject.com).
78

89
### 1.0.2 (2023-02-28)
910
- Fix test db name when NAME not provided in DATABASES setting.

clickhouse_backend/backend/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ def init_connection_state(self):
203203
def create_cursor(self, name=None):
204204
return self.connection.cursor()
205205

206+
def _savepoint(self, sid):
207+
pass
208+
209+
def _savepoint_rollback(self, sid):
210+
pass
211+
212+
def _savepoint_commit(self, sid):
213+
pass
214+
206215
def _set_autocommit(self, autocommit):
207216
pass
208217

clickhouse_backend/backend/operations.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -344,21 +344,6 @@ def last_insert_id(self, cursor, table_name, pk_name):
344344
cursor.execute(query % params)
345345
return cursor.fetchone()[0]
346346

347-
def savepoint_create_sql(self, sid):
348-
if self.fake_transaction:
349-
return "SELECT 1"
350-
return super().savepoint_create_sql(sid)
351-
352-
def savepoint_commit_sql(self, sid):
353-
if self.fake_transaction:
354-
return "SELECT 1"
355-
return super().savepoint_commit_sql(sid)
356-
357-
def savepoint_rollback_sql(self, sid):
358-
if self.fake_transaction:
359-
return "SELECT 1"
360-
return super().savepoint_rollback_sql(sid)
361-
362347
def last_executed_query(self, cursor, sql, params):
363348
if params:
364349
if insert_pattern.match(sql):

clickhouse_backend/backend/schema.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.db.models.expressions import ExpressionList
1111
from django.db.models.indexes import IndexExpression
1212

13+
from clickhouse_backend import compat
1314
from clickhouse_backend.driver.escape import escape_param
1415

1516

@@ -364,6 +365,15 @@ def _field_should_be_altered(self, old_field, new_field):
364365
(old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs)
365366
)
366367

368+
def _alter_column_type_sql(
369+
self, model, old_field, new_field, new_type, old_collation=None, new_collation=None
370+
):
371+
"""Django4.2 add old_collation, new_collation"""
372+
if compat.dj_ge42:
373+
return super()._alter_column_type_sql(model, old_field, new_field, new_type, old_collation, new_collation)
374+
else:
375+
return super()._alter_column_type_sql(model, old_field, new_field, new_type)
376+
367377
def _alter_field(self, model, old_field, new_field, old_type, new_type,
368378
old_db_params, new_db_params, strict=False):
369379
# Change check constraints?

clickhouse_backend/compat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
dj4 = (4, ) <= django.VERSION < (5, )
55
dj_ge4 = django.VERSION >= (4, )
66
dj_ge41 = django.VERSION >= (4, 1)
7+
dj_ge42 = django.VERSION >= (4, 2)

clickhouse_backend/models/sql/compiler.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from django.db.models.fields import BigAutoField
22
from django.db.models.sql import compiler
33

4+
from clickhouse_backend import compat
45
from clickhouse_backend.idworker import id_worker
56

7+
if compat.dj_ge42:
8+
from django.core.exceptions import FullResultSet
9+
610

711
class ClickhouseMixin:
8-
def as_sql(self, *args, **kwargs):
9-
sql, params = super().as_sql(*args, **kwargs)
12+
def _add_explain_sql(self, sql, params):
1013
# Backward compatible for django 3.2
1114
explain_info = getattr(self.query, "explain_info", None)
1215
if explain_info:
@@ -18,6 +21,9 @@ def as_sql(self, *args, **kwargs):
1821
sql = "%s %s" % (prefix, sql.lstrip())
1922
if suffix:
2023
sql = "%s %s" % (sql, suffix)
24+
return sql, params
25+
26+
def _add_settings_sql(self, sql, params):
2127
if getattr(self.query, "setting_info", None):
2228
setting_sql, setting_params = self.connection.ops.settings_sql(
2329
**self.query.setting_info
@@ -26,12 +32,30 @@ def as_sql(self, *args, **kwargs):
2632
params = (*params, *setting_params)
2733
return sql, params
2834

35+
def _compile_where(self, table):
36+
if compat.dj_ge42:
37+
try:
38+
where, params = self.compile(self.query.where)
39+
except FullResultSet:
40+
where, params = "", ()
41+
else:
42+
where, params = self.compile(self.query.where)
43+
if where:
44+
where = where.replace(table + ".", "")
45+
else:
46+
where = "1"
47+
return where, params
48+
2949

3050
class SQLCompiler(ClickhouseMixin, compiler.SQLCompiler):
31-
pass
51+
def as_sql(self, *args, **kwargs):
52+
sql, params = super().as_sql(*args, **kwargs)
53+
sql, params = self._add_settings_sql(sql, params)
54+
sql, params = self._add_explain_sql(sql, params)
55+
return sql, params
3256

3357

34-
class SQLInsertCompiler(ClickhouseMixin, compiler.SQLInsertCompiler):
58+
class SQLInsertCompiler(compiler.SQLInsertCompiler):
3559
def as_sql(self):
3660
# We don't need quote_name_unless_alias() here, since these are all
3761
# going to be column names (so we can avoid the extra overhead).
@@ -62,11 +86,16 @@ def as_sql(self):
6286
]
6387

6488
placeholder_rows, param_rows = self.assemble_as_sql(fields, value_rows)
89+
# https://clickhouse.com/docs/en/sql-reference/statements/insert-into
90+
# If you want to specify SETTINGS for INSERT query then you have to do it before FORMAT clause
91+
# since everything after FORMAT format_name is treated as data.
92+
if getattr(self.query, "setting_info", None):
93+
setting_sql, setting_params = self.connection.ops.settings_sql(
94+
**self.query.setting_info
95+
)
96+
result.append(setting_sql % setting_params)
97+
6598
result.append(self.connection.ops.bulk_insert_sql(fields, placeholder_rows))
66-
if hasattr(self.query, "get_settings"):
67-
settings_string = self.query.get_settings()
68-
if settings_string:
69-
result.append(settings_string)
7099
return [(" ".join(result), param_rows)]
71100

72101
def execute_sql(self, returning_fields=None):
@@ -84,16 +113,14 @@ def _as_sql(self, query):
84113
"table"."column" in WHERE clause.
85114
"""
86115
table = self.quote_name_unless_alias(query.base_table)
87-
result = [
88-
"ALTER TABLE %s DELETE" % table
89-
]
90-
where, params = self.compile(query.where)
91-
where = where.replace(table + ".", "")
92-
if where:
93-
result.append("WHERE %s" % where)
94-
else:
95-
result.append("WHERE 1")
96-
return " ".join(result), tuple(params)
116+
delete = "ALTER TABLE %s DELETE" % table
117+
where, params = self._compile_where(table)
118+
return f"{delete} WHERE {where}", tuple(params)
119+
120+
def as_sql(self):
121+
sql, params = super().as_sql()
122+
sql, params = self._add_settings_sql(sql, params)
123+
return sql, params
97124

98125

99126
class SQLUpdateCompiler(ClickhouseMixin, compiler.SQLUpdateCompiler):
@@ -159,23 +186,15 @@ def as_sql(self):
159186
"ALTER TABLE %s UPDATE" % table,
160187
", ".join(values).replace(table + ".", ""),
161188
]
162-
where, params = self.compile(self.query.where)
163-
where = where.replace(table + ".", "")
164-
165-
if where:
166-
result.append("WHERE %s" % where)
167-
else:
168-
result.append("WHERE 1")
169-
189+
where, params = self._compile_where(table)
190+
result.append(f"WHERE {where}")
170191
params = (*update_params, *params)
171-
if getattr(self.query, "setting_info", None):
172-
setting_sql, setting_params = self.connection.ops.settings_sql(
173-
**self.query.setting_info
174-
)
175-
result.append(setting_sql)
176-
params = (*params, *setting_params)
177-
return " ".join(result), params
192+
return self._add_settings_sql(" ".join(result), params)
178193

179194

180195
class SQLAggregateCompiler(ClickhouseMixin, compiler.SQLAggregateCompiler):
181-
pass
196+
def as_sql(self):
197+
sql, params = super().as_sql()
198+
sql, params = self._add_settings_sql(sql, params)
199+
sql, params = self._add_explain_sql(sql, params)
200+
return sql, params

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def read(name):
3333
"Framework :: Django :: 4",
3434
"Framework :: Django :: 4.0",
3535
"Framework :: Django :: 4.1",
36+
"Framework :: Django :: 4.2",
3637
"Intended Audience :: Developers",
3738
"License :: OSI Approved :: MIT License",
3839
"Programming Language :: Python",

tests/aggregation/tests.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,14 +1106,6 @@ def test_annotate_over_annotate(self):
11061106

11071107
self.assertEqual(author.sum_age, other_author.sum_age)
11081108

1109-
def test_aggregate_over_aggregate(self):
1110-
msg = "Cannot compute Avg('age'): 'age' is an aggregate"
1111-
with self.assertRaisesMessage(FieldError, msg):
1112-
Author.objects.annotate(age_alias=F("age"),).aggregate(
1113-
age=Sum(F("age")),
1114-
avg_age=Avg(F("age")),
1115-
)
1116-
11171109
def test_annotated_aggregate_over_annotated_aggregate(self):
11181110
with self.assertRaisesMessage(
11191111
FieldError, "Cannot compute Sum('id__max'): 'id__max' is an aggregate"

tests/basic/tests.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
)
1313
from django.utils.translation import gettext_lazy
1414

15+
from clickhouse_backend import compat
1516
from .models import (
1617
Article,
1718
ArticleSelectOnSave,
@@ -148,7 +149,15 @@ def test_save_primary_with_default(self):
148149
def test_save_parent_primary_with_default(self):
149150
# An UPDATE attempt is skipped when an inherited primary key has
150151
# default.
151-
with self.assertNumQueries(2):
152+
#
153+
if compat.dj_ge42:
154+
# Django 4.2 add BEGIN and COMMIT debug queries.
155+
# https://github.com/django/django/blob/a18e0f44d5692b656bd8ea178e830ebdc80a000d/django/db/backends/base/base.py#L496
156+
# https://github.com/django/django/blob/a18e0f44d5692b656bd8ea178e830ebdc80a000d/django/db/backends/base/base.py#L312
157+
num_queries = 4
158+
else:
159+
num_queries = 2
160+
with self.assertNumQueries(num_queries):
152161
ChildPrimaryKeyWithDefault().save()
153162

154163

tests/migrations/test_autodetector.py

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2107,25 +2107,6 @@ def test_same_app_circular_fk_dependency(self):
21072107
self.assertOperationAttributes(changes, "testapp", 0, 2, name="publisher")
21082108
self.assertMigrationDependencies(changes, "testapp", 0, [])
21092109

2110-
def test_same_app_circular_fk_dependency_with_unique_together_and_indexes(self):
2111-
"""
2112-
#22275 - A migration with circular FK dependency does not try
2113-
to create unique together constraint and indexes before creating all
2114-
required fields first.
2115-
"""
2116-
changes = self.get_changes([], [self.knight, self.rabbit])
2117-
# Right number/type of migrations?
2118-
self.assertNumberMigrations(changes, "eggs", 1)
2119-
self.assertOperationTypes(
2120-
changes,
2121-
"eggs",
2122-
0,
2123-
["CreateModel", "CreateModel", "AddIndex", "AlterUniqueTogether"],
2124-
)
2125-
self.assertNotIn("unique_together", changes["eggs"][0].operations[0].options)
2126-
self.assertNotIn("unique_together", changes["eggs"][0].operations[1].options)
2127-
self.assertMigrationDependencies(changes, "eggs", 0, [])
2128-
21292110
def test_alter_db_table_add(self):
21302111
"""Tests detection for adding db_table in model's options."""
21312112
changes = self.get_changes(
@@ -2366,37 +2347,6 @@ def test(from_state, to_state, msg):
23662347
for t in tests:
23672348
test(*t)
23682349

2369-
def test_create_model_with_indexes(self):
2370-
"""Test creation of new model with indexes already defined."""
2371-
author = ModelState(
2372-
"otherapp",
2373-
"Author",
2374-
[
2375-
("id", models.BigAutoField(primary_key=True)),
2376-
("name", models.CharField(max_length=200)),
2377-
],
2378-
{
2379-
"indexes": [
2380-
models.Index(fields=["name"], name="create_model_with_indexes_idx")
2381-
]
2382-
},
2383-
)
2384-
changes = self.get_changes([], [author])
2385-
added_index = models.Index(
2386-
fields=["name"], name="create_model_with_indexes_idx"
2387-
)
2388-
# Right number of migrations?
2389-
self.assertEqual(len(changes["otherapp"]), 1)
2390-
# Right number of actions?
2391-
migration = changes["otherapp"][0]
2392-
self.assertEqual(len(migration.operations), 2)
2393-
# Right actions order?
2394-
self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel", "AddIndex"])
2395-
self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Author")
2396-
self.assertOperationAttributes(
2397-
changes, "otherapp", 0, 1, model_name="author", index=added_index
2398-
)
2399-
24002350
def test_add_indexes(self):
24012351
"""Test change detection of new indexes."""
24022352
changes = self.get_changes(
@@ -3617,63 +3567,6 @@ def test_add_model_order_with_respect_to_unique_together(self):
36173567
},
36183568
)
36193569

3620-
def test_add_model_order_with_respect_to_index_constraint(self):
3621-
tests = [
3622-
(
3623-
"AddIndex",
3624-
{
3625-
"indexes": [
3626-
models.Index(fields=["_order"], name="book_order_idx"),
3627-
]
3628-
},
3629-
),
3630-
(
3631-
"AddConstraint",
3632-
{
3633-
"constraints": [
3634-
models.CheckConstraint(
3635-
check=models.Q(_order__gt=1),
3636-
name="book_order_gt_1",
3637-
),
3638-
]
3639-
},
3640-
),
3641-
]
3642-
for operation, extra_option in tests:
3643-
with self.subTest(operation=operation):
3644-
after = ModelState(
3645-
"testapp",
3646-
"Author",
3647-
[
3648-
("id", models.BigAutoField(primary_key=True)),
3649-
("name", models.CharField(max_length=200)),
3650-
("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
3651-
],
3652-
options={
3653-
"order_with_respect_to": "book",
3654-
**extra_option,
3655-
},
3656-
)
3657-
changes = self.get_changes([], [self.book, after])
3658-
self.assertNumberMigrations(changes, "testapp", 1)
3659-
self.assertOperationTypes(
3660-
changes,
3661-
"testapp",
3662-
0,
3663-
[
3664-
"CreateModel",
3665-
operation,
3666-
],
3667-
)
3668-
self.assertOperationAttributes(
3669-
changes,
3670-
"testapp",
3671-
0,
3672-
0,
3673-
name="Author",
3674-
options={"order_with_respect_to": "book"},
3675-
)
3676-
36773570
def test_set_alter_order_with_respect_to_index_constraint_unique_together(self):
36783571
tests = [
36793572
(

0 commit comments

Comments
 (0)