1
+ from itertools import zip_longest
2
+
1
3
from django .db .models .expressions import Func , Value
4
+ from django .utils .itercompat import is_iterable
2
5
3
6
__all__ = [
4
7
"Engine" ,
20
23
]
21
24
22
25
26
+ def _check_positive (value , name ):
27
+ if not isinstance (value , int ) and value <= 0 :
28
+ raise ValueError (f"{ name } must be positive integer." )
29
+ return value
30
+
31
+
32
+ def _check_not_negative (value , name ):
33
+ if not isinstance (value , int ) and value < 0 :
34
+ raise ValueError (f"{ name } must not be negative." )
35
+ return value
36
+
37
+
38
+ def _check_bool (value , name ):
39
+ if value not in (0 , 1 ):
40
+ raise ValueError (f"{ name } must be one of (0, 1, True, False)" )
41
+ return int (value )
42
+
43
+
44
+ def _check_str (value , name ):
45
+ if not isinstance (value , str ):
46
+ raise ValueError (f"{ name } must be string" )
47
+ return value
48
+
49
+
23
50
class Engine (Func ):
51
+ @property
52
+ def function (self ):
53
+ return self .__class__ .__name__
54
+
24
55
def deconstruct (self ):
25
56
path , args , kwargs = super ().deconstruct ()
26
57
if path .startswith ("clickhouse_backend.models.engines" ):
@@ -31,99 +62,197 @@ def deconstruct(self):
31
62
32
63
33
64
class BaseMergeTree (Engine ):
34
- def __init__ (self , * expressions , output_field = None , ** extra ):
35
- self .order_by = extra .pop ("order_by" , None )
36
- assert self .order_by is not None , "order_by is required by MergeTree family."
37
- self .partition_by = extra .pop ("partition_by" , None )
38
- self .primary_key = extra .pop ("primary_key" , None )
39
- super ().__init__ (* expressions , output_field = output_field , ** extra )
65
+ max_arity = None # The max number of arguments the function accepts.
66
+ # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#settings
67
+ setting_types = {
68
+ _check_positive : [
69
+ "index_granularity" ,
70
+ "min_index_granularity_bytes" ,
71
+ "merge_max_block_size" ,
72
+ "min_bytes_for_wide_part" ,
73
+ "min_rows_for_wide_part" ,
74
+ "max_parts_in_total" ,
75
+ "max_compress_block_size" ,
76
+ "min_compress_block_size" ,
77
+ "max_partitions_to_read" ,
78
+ ],
79
+ _check_not_negative : [
80
+ "index_granularity_bytes" ,
81
+ "min_merge_bytes_to_use_direct_io" ,
82
+ "merge_with_ttl_timeout" ,
83
+ "merge_with_recompression_ttl_timeout" ,
84
+ "try_fetch_recompressed_part_timeout" ,
85
+ ],
86
+ _check_bool : [
87
+ "enable_mixed_granularity_parts" ,
88
+ "use_minimalistic_part_header_in_zookeeper" ,
89
+ "write_final_mark" ,
90
+ ],
91
+ _check_str : ["storage_policy" ],
92
+ }
93
+
94
+ def __init__ (
95
+ self ,
96
+ * expressions ,
97
+ order_by = None ,
98
+ partition_by = None ,
99
+ primary_key = None ,
100
+ ** settings ,
101
+ ):
102
+ if self .max_arity is not None and len (expressions ) > self .max_arity :
103
+ raise TypeError (
104
+ "'%s' takes at most %s %s (%s given)"
105
+ % (
106
+ self .__class__ .__name__ ,
107
+ self .max_arity ,
108
+ "argument" if self .max_arity == 1 else "arguments" ,
109
+ len (expressions ),
110
+ )
111
+ )
112
+
113
+ assert (
114
+ order_by is not None or primary_key is not None
115
+ ), "order_by or primary_key is missing."
116
+ self .order_by = order_by
117
+ self .primary_key = primary_key
118
+ self .partition_by = partition_by
119
+
120
+ if order_by is not None :
121
+ if isinstance (order_by , str ) or not is_iterable (order_by ):
122
+ self .order_by = [order_by ]
123
+ if any (o is None for o in self .order_by ):
124
+ raise ValueError ("None is not allowed in order_by" )
125
+
126
+ if primary_key is not None :
127
+ if isinstance (primary_key , str ) or not is_iterable (primary_key ):
128
+ self .primary_key = [primary_key ]
129
+ if any (o is None for o in self .primary_key ):
130
+ raise ValueError ("None is not allowed in primary_key" )
131
+
132
+ # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#choosing-a-primary-key-that-differs-from-the-sorting-key
133
+ # primary key expression tuple must be a prefix of the sorting key expression tuple.
134
+ if self .order_by is not None and self .primary_key is not None :
135
+ for o , p in zip_longest (self .order_by , self .primary_key ):
136
+ if p is None :
137
+ break
138
+ if p != o :
139
+ raise ValueError ("primary_key must be a prefix of order_by" )
140
+
141
+ normalized_settings = {}
142
+ for setting , value in settings .items ():
143
+ for validate , keys in self .setting_types .items ():
144
+ if setting in keys :
145
+ normalized_settings [setting ] = validate (value , setting )
146
+ break
147
+ else :
148
+ raise TypeError (f"{ setting } is not a valid setting." )
149
+ self .settings = normalized_settings
150
+ super ().__init__ (* expressions )
40
151
41
152
42
153
class MergeTree (BaseMergeTree ):
43
- function = "MergeTree"
44
154
arity = 0
45
155
46
156
47
157
class ReplacingMergeTree (BaseMergeTree ):
48
- function = "ReplacingMergeTree"
158
+ max_arity = 2
49
159
50
160
51
161
class SummingMergeTree (BaseMergeTree ):
52
- function = "SummingMergeTree"
162
+ pass
53
163
54
164
55
165
class AggregatingMergeTree (BaseMergeTree ):
56
- function = "AggregatingMergeTree"
57
166
arity = 0
58
167
59
168
60
169
class CollapsingMergeTree (BaseMergeTree ):
61
- function = "CollapsingMergeTree"
62
170
arity = 1
63
171
64
172
65
173
class VersionedCollapsingMergeTree (BaseMergeTree ):
66
- function = "CollapsingMergeTree"
67
174
arity = 2
68
175
69
176
70
177
class GraphiteMergeTree (BaseMergeTree ):
71
- function = "GraphiteMergeTree"
72
178
arity = 1
73
179
180
+ def __init__ (self , * expressions , ** extra ):
181
+ if expressions :
182
+ expressions = (Value (expressions [0 ]), * expressions [1 :])
183
+ super ().__init__ (* expressions , ** extra )
184
+
74
185
75
186
class ReplicatedMixin :
76
187
def __init__ (self , * expressions , ** extra ):
77
188
if self .arity is not None and len (expressions ) != self .arity + 2 :
78
189
raise TypeError (
79
- "'%s' takes exactly %s %s (%s given)"
190
+ "'%s' takes exactly %s arguments (%s given)"
80
191
% (
81
192
self .__class__ .__name__ ,
82
193
self .arity + 2 ,
83
- "arguments" ,
84
194
len (expressions ),
85
195
)
86
196
)
87
197
if self .arity is None and len (expressions ) < 2 :
88
198
raise TypeError (
89
- "'%s' takes at least %s %s (%s given)"
199
+ "'%s' takes at least 2 arguments (%s given)"
90
200
% (
91
201
self .__class__ .__name__ ,
92
- 2 ,
93
- "arguments" ,
94
202
len (expressions ),
95
203
)
96
204
)
97
- replicated_params = tuple (Value (arg ) for arg in self .expressions [:2 ])
205
+ if self .max_arity is not None and len (expressions ) > self .max_arity + 2 :
206
+ raise TypeError (
207
+ "'%s' takes at most %s arguments (%s given)"
208
+ % (
209
+ self .__class__ .__name__ ,
210
+ self .max_arity + 2 ,
211
+ len (expressions ),
212
+ )
213
+ )
214
+ replicated_params = map (Value , self .expressions [:2 ])
98
215
super ().__init__ (* expressions [2 :], ** extra )
99
- self .expressions = replicated_params + self .expressions
216
+ self .expressions = ( * replicated_params , * self .expressions )
100
217
101
218
102
- class ReplicatedMergeTree (ReplicatedMixin , MergeTree ):
103
- function = "ReplicatedMergeTree"
219
+ class ReplicatedMergeTree (MergeTree ):
220
+ def __init__ (self , * expressions , ** extra ):
221
+ # https://github.com/ClickHouse/ClickHouse/issues/8675
222
+ # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication#creating-replicated-tables
223
+ # You can specify default arguments for Replicated table engine in the server configuration file.
224
+ # In this case, you can omit arguments when creating tables.
225
+ if expressions :
226
+ if len (expressions ) != 2 :
227
+ raise TypeError (
228
+ "'ReplicatedMergeTree' takes at 0 or 2 arguments (%s given)"
229
+ % len (expressions )
230
+ )
231
+ expressions = map (Value , expressions )
232
+ super ().__init__ (* expressions , ** extra )
104
233
105
234
106
235
class ReplicatedReplacingMergeTree (ReplicatedMixin , ReplacingMergeTree ):
107
- function = "ReplicatedReplacingMergeTree"
236
+ pass
108
237
109
238
110
239
class ReplicatedSummingMergeTree (ReplicatedMixin , SummingMergeTree ):
111
- function = "ReplicatedSummingMergeTree"
240
+ pass
112
241
113
242
114
243
class ReplicatedAggregatingMergeTree (ReplicatedMixin , AggregatingMergeTree ):
115
- function = "ReplicatedAggregatingMergeTree"
244
+ pass
116
245
117
246
118
247
class ReplicatedCollapsingMergeTree (ReplicatedMixin , CollapsingMergeTree ):
119
- function = "ReplicatedCollapsingMergeTree"
248
+ pass
120
249
121
250
122
251
class ReplicatedVersionedCollapsingMergeTree (
123
252
ReplicatedMixin , VersionedCollapsingMergeTree
124
253
):
125
- function = "ReplicatedCollapsingMergeTree"
254
+ pass
126
255
127
256
128
257
class ReplicatedGraphiteMergeTree (ReplicatedMixin , GraphiteMergeTree ):
129
- function = "ReplicatedGraphiteMergeTree"
258
+ pass
0 commit comments