Skip to content

Commit 2065bed

Browse files
committed
fix: engin bug
1. migrate ReplacingMergeTree with [`ver`](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree#ver) raising `AttributeError: 'F' object has no attribute 'get_source_expressions'`. 2. unable to omit [`zoo_path`](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#zoo_path) and [`replica_name`](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#replica_name) in replicated engines other than `ReplicatedMergeTree`.
1 parent a946b21 commit 2065bed

File tree

6 files changed

+93
-63
lines changed

6 files changed

+93
-63
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### 1.1.7
2+
3+
- fix: #76 `AttributeError: 'ReplicatedReplacingMergeTree' object has no attribute 'expressions'`.
4+
- fix: migrate ReplacingMergeTree with [`ver`](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree#ver) raising `AttributeError: 'F' object has no attribute 'get_source_expressions'`.
5+
- fix: unable to omit [`zoo_path`](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#zoo_path) and [`replica_name`](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#replica_name) in replicated engines other than `ReplicatedMergeTree`.
6+
17
### 1.1.6
28

39
- add `CLICKHOUSE_ENABLE_UPDATE_ROWCOUNT` django setting.

clickhouse_backend/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from clickhouse_backend.utils.version import get_version
22

3-
VERSION = (1, 1, 6, "final", 0)
3+
VERSION = (1, 1, 7, "final", 0)
44

55
__version__ = get_version(VERSION)

clickhouse_backend/backend/schema.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
Table,
1313
)
1414
from django.db.models.constraints import CheckConstraint, UniqueConstraint
15-
from django.db.models.expressions import ExpressionList
15+
from django.db.models.expressions import Col, ExpressionList, F
1616
from django.db.models.indexes import IndexExpression
1717

1818
from clickhouse_backend import compat
@@ -166,6 +166,10 @@ def _get_engine_expression(self, model, engine):
166166
compiler = Query(model, alias_cols=False).get_compiler(
167167
connection=self.connection
168168
)
169+
engine.source_expressions = tuple(
170+
Col("", model._meta.get_field(e.name)) if isinstance(e, F) else e
171+
for e in engine.source_expressions
172+
)
169173
return Expressions(model._meta.db_table, engine, compiler, self.quote_value)
170174

171175
def column_sql(self, model, field, include_default=False):

clickhouse_backend/models/engines.py

+23-51
Original file line numberDiff line numberDiff line change
@@ -185,63 +185,35 @@ class VersionedCollapsingMergeTree(BaseMergeTree):
185185

186186

187187
class GraphiteMergeTree(BaseMergeTree):
188-
arity = 1
189-
190-
def __init__(self, *expressions, **extra):
191-
if expressions:
192-
expressions = (value_if_string(expressions[0]), *expressions[1:])
193-
super().__init__(*expressions, **extra)
188+
def __init__(self, config_section, **extra):
189+
super().__init__(value_if_string(config_section), **extra)
194190

195191

196192
class ReplicatedMixin:
197-
def __init__(self, *expressions, **extra):
198-
if self.arity is not None and len(expressions) != self.arity + 2:
199-
raise TypeError(
200-
"'%s' takes exactly %s arguments (%s given)"
201-
% (
202-
self.__class__.__name__,
203-
self.arity + 2,
204-
len(expressions),
205-
)
206-
)
207-
if self.arity is None and len(expressions) < 2:
208-
raise TypeError(
209-
"'%s' takes at least 2 arguments (%s given)"
210-
% (
211-
self.__class__.__name__,
212-
len(expressions),
213-
)
214-
)
215-
if self.max_arity is not None and len(expressions) > self.max_arity + 2:
216-
raise TypeError(
217-
"'%s' takes at most %s arguments (%s given)"
218-
% (
219-
self.__class__.__name__,
220-
self.max_arity + 2,
221-
len(expressions),
193+
def __init__(self, *replicated_parameters, other_parameters=(), **extra):
194+
"""
195+
https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#replicatedmergetree-parameters
196+
https://github.com/ClickHouse/ClickHouse/issues/8675
197+
https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#creating-replicated-tables
198+
199+
replicated_parameters: zoo_path and replica_name. Their default values can be specified in the server
200+
configuration file, in this case, they both can be omitted.
201+
other_parameters: Parameters of an engine which is used for creating the replicated version,
202+
for example, version in ReplacingMergeTree
203+
"""
204+
if replicated_parameters:
205+
if len(replicated_parameters) != 2:
206+
raise TypeError(
207+
"'ReplicatedMergeTree' takes 0 or 2 arguments (%s given)"
208+
% len(replicated_parameters)
222209
)
223-
)
224-
replicated_params = map(value_if_string, expressions[:2])
225-
super().__init__(*expressions[2:], **extra)
226-
self.source_expressions = (*replicated_params, *self.source_expressions)
227-
210+
replicated_parameters = map(value_if_string, replicated_parameters)
211+
super().__init__(*other_parameters, **extra)
212+
self.source_expressions = (*replicated_parameters, *self.source_expressions)
228213

229-
class ReplicatedMergeTree(MergeTree):
230-
arity = None
231214

232-
def __init__(self, *expressions, **extra):
233-
# https://github.com/ClickHouse/ClickHouse/issues/8675
234-
# https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#creating-replicated-tables
235-
# You can specify default arguments for Replicated table engine in the server configuration file.
236-
# In this case, you can omit arguments when creating tables.
237-
if expressions:
238-
if len(expressions) != 2:
239-
raise TypeError(
240-
"'ReplicatedMergeTree' takes at 0 or 2 arguments (%s given)"
241-
% len(expressions)
242-
)
243-
expressions = map(value_if_string, expressions)
244-
super().__init__(*expressions, **extra)
215+
class ReplicatedMergeTree(ReplicatedMixin, MergeTree):
216+
pass
245217

246218

247219
class ReplicatedReplacingMergeTree(ReplicatedMixin, ReplacingMergeTree):

tests/clickhouse_table_engine/models.py

+32
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,35 @@ class Meta:
4848
"cluster", models.currentDatabase(), Student._meta.db_table, models.Rand()
4949
)
5050
cluster = "cluster"
51+
52+
53+
class ReplacingMergeTree(models.ClickhouseModel):
54+
ver = models.UInt64Field()
55+
is_deleted = models.UInt8Field()
56+
57+
class Meta:
58+
engine = models.ReplacingMergeTree("ver", "is_deleted", order_by="id")
59+
60+
61+
class ReplicatedReplacingMergeTree(models.ClickhouseModel):
62+
ver = models.UInt64Field()
63+
is_deleted = models.UInt8Field()
64+
65+
class Meta:
66+
engine = models.ReplicatedReplacingMergeTree(
67+
other_parameters=("ver", "is_deleted"), order_by="id"
68+
)
69+
cluster = "cluster"
70+
71+
72+
class ReplicatedReplacingMergeTreeWithZooReplica(models.ClickhouseModel):
73+
ver = models.UInt64Field()
74+
is_deleted = models.UInt8Field()
75+
76+
class Meta:
77+
engine = models.ReplicatedReplacingMergeTree(
78+
"/clickhouse/tables/{shard}/table_name",
79+
"{replica}",
80+
order_by="id",
81+
)
82+
cluster = "cluster"

tests/clickhouse_table_engine/tests.py

+26-10
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,61 @@
11
from django.db import connection
22
from django.test import TestCase
33

4-
from clickhouse_backend import models
4+
from clickhouse_backend.models import MergeTree
55
from clickhouse_backend.utils.timezone import get_timezone
66

7-
from .models import EngineWithSettings, Event
7+
from . import models
88

99

1010
class TestMergeTree(TestCase):
11-
def test_table(self):
12-
opts = Event._meta
11+
def assertEngineEquals(self, model, engine):
1312
with connection.cursor() as cursor:
1413
cursor.execute(
15-
f"select engine_full from system.tables where table='{opts.db_table}'"
14+
f"select engine_full from system.tables where table='{model._meta.db_table}'"
1615
)
1716
engine_full = cursor.fetchone()[0]
1817
self.assertEqual(
1918
engine_full.partition(" SETTINGS ")[0],
19+
engine,
20+
)
21+
22+
def test_table(self):
23+
self.assertEngineEquals(
24+
models.Event,
2025
f"MergeTree PARTITION BY toYYYYMMDD(timestamp, '{get_timezone()}') PRIMARY KEY timestamp ORDER BY (timestamp, id)",
2126
)
27+
self.assertEngineEquals(
28+
models.ReplacingMergeTree, "ReplacingMergeTree(ver, is_deleted) ORDER BY id"
29+
)
30+
self.assertEngineEquals(
31+
models.ReplicatedReplacingMergeTree,
32+
"ReplicatedReplacingMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}', ver, is_deleted) ORDER BY id",
33+
)
34+
self.assertEngineEquals(
35+
models.ReplicatedReplacingMergeTreeWithZooReplica,
36+
"ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/table_name', '{replica}') ORDER BY id",
37+
)
2238

2339
def test_mergetree_init_exception(self):
2440
with self.assertRaisesMessage(
2541
AssertionError, "At least one of order_by or primary_key must be provided"
2642
):
27-
models.MergeTree()
43+
MergeTree()
2844
with self.assertRaisesMessage(ValueError, "None is not allowed in order_by"):
29-
models.MergeTree(order_by=(None, "a"))
45+
MergeTree(order_by=(None, "a"))
3046
with self.assertRaisesMessage(
3147
ValueError, "primary_key must be a prefix of order_by"
3248
):
33-
models.MergeTree(order_by=("a", "b"), primary_key=["b"])
49+
MergeTree(order_by=("a", "b"), primary_key=["b"])
3450
with self.assertRaisesMessage(
3551
ValueError, "primary_key must be a prefix of order_by"
3652
):
37-
models.MergeTree(order_by=("a", "b"), primary_key=["a", "b", "c"])
53+
MergeTree(order_by=("a", "b"), primary_key=["a", "b", "c"])
3854

3955

4056
class TestEngineSettings(TestCase):
4157
def test(self):
42-
opts = EngineWithSettings._meta
58+
opts = models.EngineWithSettings._meta
4359
with connection.cursor() as cursor:
4460
cursor.execute(
4561
f"select engine_full from system.tables where table='{opts.db_table}'"

0 commit comments

Comments
 (0)