tinybird-toolset 2.2.2__tar.gz → 2.4.0__tar.gz

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 (29) hide show
  1. {tinybird_toolset-2.2.2/src/tinybird_toolset.egg-info → tinybird_toolset-2.4.0}/PKG-INFO +1 -1
  2. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/README.md +37 -8
  3. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/setup.py +1 -1
  4. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/src/chtoolset/query.py +1 -0
  5. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0/src/tinybird_toolset.egg-info}/PKG-INFO +1 -1
  6. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/src/tinybird_toolset.egg-info/SOURCES.txt +1 -0
  7. tinybird_toolset-2.4.0/tests/test_normalize_table_column.py +431 -0
  8. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_replace_tables.py +19 -0
  9. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_tables.py +17 -0
  10. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/LICENSE +0 -0
  11. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/MANIFEST.in +0 -0
  12. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/conf.py +0 -0
  13. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/setup.cfg +0 -0
  14. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/src/chtoolset/__init__.py +0 -0
  15. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/src/tinybird_toolset.egg-info/dependency_links.txt +0 -0
  16. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/src/tinybird_toolset.egg-info/requires.txt +0 -0
  17. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/src/tinybird_toolset.egg-info/top_level.txt +0 -0
  18. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_check_compatible_types.py +0 -0
  19. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_check_write_query.py +0 -0
  20. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_chquery.py +0 -0
  21. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_convert_to_row_binary.py +0 -0
  22. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_explain_ast.py +0 -0
  23. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_get_columns_from_create_query.py +0 -0
  24. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_get_left_table.py +0 -0
  25. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_internal_cache.py +0 -0
  26. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_normalize_query_keep_names.py +0 -0
  27. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_parse_create_materialized_view_target_table.py +0 -0
  28. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_replace_tables_backward_compat.py +0 -0
  29. {tinybird_toolset-2.2.2 → tinybird_toolset-2.4.0}/tests/test_rewrite_aggregation_states.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinybird-toolset
3
- Version: 2.2.2
3
+ Version: 2.4.0
4
4
  Home-page: https://gitlab.com/tinybird/clickhouse-toolset
5
5
  Author: Tinybird.co
6
6
  Author-email: support@tinybird.co
@@ -60,6 +60,15 @@ The build is a multi-stage process orchestrated by `conf.py`:
60
60
  2. **Toolset Build** - Compiles `functions/*.cpp` into `libCHToolset.a`
61
61
  3. **Extension Build** - Compiles `src/query.cpp`, links everything into `_query.so`
62
62
 
63
+ On MacOS, you'll need the gnu version of multiple tools. You can get them with:
64
+ ```
65
+ brew install grep findutils gpatch
66
+ ```
67
+ And you'll have to add their locations to your PATH. Homebrew will output which command to run for each.
68
+
69
+ You'll also need `objcopy` or `llvm-objcopy` in your path. The latter is more practical and will be
70
+ in the bin path of your homebrew llvm installation, so you'll have to do something like: `export PATH="/opt/homebrew/opt/llvm@19/bin:$PATH"`
71
+
63
72
  ```bash
64
73
  make build-3.11 # Build for Python 3.11
65
74
  make test-3.11 # Build and test
@@ -148,9 +157,10 @@ Then, you will compile the dependencies and the module itself. You need a modern
148
157
  If your system Clang is not the correct version, set the CC and CXX environment variables before building:
149
158
 
150
159
  ```bash
151
- # Example for macOS with Homebrew llvm@19
152
- export CC=/opt/homebrew/opt/llvm@19/bin/clang
153
- export CXX=/opt/homebrew/opt/llvm@19/bin/clang++
160
+ # Example for macOS with Homebrew llvm@21
161
+ export PATH=/opt/homebrew/opt/llvm@21/bin:$PATH
162
+ export CC=/opt/homebrew/opt/llvm@21/bin/clang
163
+ export CXX=/opt/homebrew/opt/llvm@21/bin/clang++
154
164
  ```
155
165
 
156
166
  The best option is to use the Makefile targets which will use virtualenv to install dependencies, build the packages, install them too and run tests:
@@ -171,13 +181,30 @@ You need to be able to compile ClickHouse for MacOS so we follow [their guide](h
171
181
  * Install Xcode and Command Line Tools
172
182
  * Install the necessary tools (cmake ninja libtool gettext llvm gcc ccache findutils grep). For the LLVM version, check the [ClickHouse build documentation](https://clickhouse.com/docs/en/development/build) for the minimum required Clang version.
173
183
  * Make sure your local clang is pointing to the llvm installation and not the default from OSX / Xcode. You can check it by running `clang --version` - it should show "Homebrew clang" and the correct version.
174
- * Set the CC and CXX environment variables before building:
184
+ * Add Homebrew LLVM and the GNU userland tools to your `PATH` before building. ClickHouse's build on macOS expects tools such as `llvm-objcopy`, `llvm-strip`, `find`, and `grep` to resolve to the Homebrew/GNU versions instead of the default BSD/macOS tools.
175
185
  ```bash
176
- # Example for llvm@19
177
- export CC=/opt/homebrew/opt/llvm@19/bin/clang
178
- export CXX=/opt/homebrew/opt/llvm@19/bin/clang++
186
+ export PATH="/opt/homebrew/opt/llvm@21/bin:/opt/homebrew/opt/findutils/libexec/gnubin:/opt/homebrew/opt/grep/libexec/gnubin:$PATH"
187
+ export CC=/opt/homebrew/opt/llvm@21/bin/clang
188
+ export CXX=/opt/homebrew/opt/llvm@21/bin/clang++
189
+ ```
190
+ * You can persist those settings in `~/.zshrc`:
191
+ ```bash
192
+ echo '' >> ~/.zshrc
193
+ echo '# tinybird/clickhouse-toolset build environment' >> ~/.zshrc
194
+ echo 'export PATH="/opt/homebrew/opt/llvm@21/bin:/opt/homebrew/opt/findutils/libexec/gnubin:/opt/homebrew/opt/grep/libexec/gnubin:$PATH"' >> ~/.zshrc
195
+ echo 'export CC="/opt/homebrew/opt/llvm@21/bin/clang"' >> ~/.zshrc
196
+ echo 'export CXX="/opt/homebrew/opt/llvm@21/bin/clang++"' >> ~/.zshrc
197
+ source ~/.zshrc
198
+ ```
199
+
200
+ * Follow the normal build. When validating a new machine or toolchain, prefer building a single Python version first:
201
+ ```bash
202
+ make build-3.11
203
+ ```
204
+ Then run the full build only after that succeeds:
205
+ ```bash
206
+ make build
179
207
  ```
180
- * Follow the normal build (`make build`)
181
208
 
182
209
  #### Clean environment if you already did some compilation before
183
210
  To clean your environment you need to make sure that the repository is clean and updated with the expected values. To do that you can use:
@@ -187,6 +214,8 @@ make distclean
187
214
  git clean -fdx
188
215
  ```
189
216
 
217
+ If you manually configured `ch_build` or `ts_build` with a different CMake generator earlier, remove them before rebuilding. The project configures the toolset with `Ninja`, and stale caches created with `Unix Makefiles` will fail with a generator mismatch.
218
+
190
219
  #### Make sure you have the expeted version from the submodules
191
220
  ```bash
192
221
  git submodule sync && git submodule update --init --recursive
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, Extension
2
2
 
3
3
  NAME = 'tinybird-toolset'
4
- VERSION = '2.2.2'
4
+ VERSION = '2.4.0'
5
5
 
6
6
  try:
7
7
  from conf import *
@@ -19,6 +19,7 @@ from chtoolset._query import replace_tables, \
19
19
  set_row_binary_encoder_buffer_config, \
20
20
  set_row_binary_encoder_thread_buffer_size, \
21
21
  get_columns_from_create_query, \
22
+ normalize_table_column, \
22
23
  parse_create_materialized_view_target_table as _parse_create_materialized_view_target_table
23
24
 
24
25
  from typing import Optional, TypedDict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinybird-toolset
3
- Version: 2.2.2
3
+ Version: 2.4.0
4
4
  Home-page: https://gitlab.com/tinybird/clickhouse-toolset
5
5
  Author: Tinybird.co
6
6
  Author-email: support@tinybird.co
@@ -19,6 +19,7 @@ tests/test_get_columns_from_create_query.py
19
19
  tests/test_get_left_table.py
20
20
  tests/test_internal_cache.py
21
21
  tests/test_normalize_query_keep_names.py
22
+ tests/test_normalize_table_column.py
22
23
  tests/test_parse_create_materialized_view_target_table.py
23
24
  tests/test_replace_tables.py
24
25
  tests/test_replace_tables_backward_compat.py
@@ -0,0 +1,431 @@
1
+ import unittest
2
+
3
+ from chtoolset import query as chquery
4
+
5
+
6
+ class TestNormalizeTableColumn(unittest.TestCase):
7
+ def test_normalizes_low_cardinality_with_nullable_flag(self):
8
+ column = chquery.normalize_table_column(
9
+ column={
10
+ "name": "status",
11
+ "type": "LowCardinality(String)",
12
+ "nullable": True,
13
+ },
14
+ )
15
+
16
+ self.assertEqual(column["name"], "status")
17
+ self.assertEqual(column["type"], "LowCardinality(String)")
18
+ self.assertTrue(column["nullable"])
19
+
20
+ def test_uses_normalized_name_alias(self):
21
+ column = chquery.normalize_table_column(
22
+ column={"name": "Name", "normalized_name": "name", "type": "String"},
23
+ )
24
+
25
+ self.assertEqual(column["name"], "name")
26
+ self.assertEqual(column["type"], "String")
27
+ self.assertFalse(column["nullable"])
28
+
29
+ def test_detects_nullable_simple_aggregate_function(self):
30
+ column = chquery.normalize_table_column(
31
+ column={
32
+ "name": "metric",
33
+ "type": "SimpleAggregateFunction(sum, Nullable(Int64))",
34
+ },
35
+ )
36
+
37
+ self.assertEqual(
38
+ column["type"], "SimpleAggregateFunction(sum, Nullable(Int64))"
39
+ )
40
+ self.assertTrue(column["nullable"])
41
+
42
+ def test_plain_nullable_wrapper_does_not_force_nullable_flag(self):
43
+ column = chquery.normalize_table_column(
44
+ column={"name": "discount", "type": "Nullable(Decimal64(4))"},
45
+ )
46
+
47
+ self.assertEqual(column["type"], "Nullable(Decimal64(4))")
48
+ self.assertTrue(column["nullable"])
49
+
50
+ def test_normalizes_decimal_spacing(self):
51
+ column = chquery.normalize_table_column(
52
+ column={
53
+ "name": "amount",
54
+ "normalized_name": "amount",
55
+ "type": "Decimal(18,4)",
56
+ "nullable": False,
57
+ },
58
+ )
59
+
60
+ self.assertEqual(column["type"], "Decimal(18, 4)")
61
+ self.assertFalse(column["nullable"])
62
+
63
+ def test_normalizes_decimal_spacing_with_nullable_flag(self):
64
+ column = chquery.normalize_table_column(
65
+ column={
66
+ "name": "tax",
67
+ "normalized_name": "tax",
68
+ "type": "Decimal(10,2)",
69
+ "nullable": True,
70
+ },
71
+ )
72
+
73
+ self.assertEqual(column["type"], "Decimal(10, 2)")
74
+ self.assertTrue(column["nullable"])
75
+
76
+ def test_preserves_decimal64_alias(self):
77
+ column = chquery.normalize_table_column(
78
+ column={
79
+ "name": "discount",
80
+ "normalized_name": "discount",
81
+ "type": "Nullable(Decimal64(4))",
82
+ "nullable": False,
83
+ },
84
+ )
85
+
86
+ self.assertEqual(column["type"], "Nullable(Decimal64(4))")
87
+ self.assertTrue(column["nullable"])
88
+
89
+ def test_preserves_datetime64_precision(self):
90
+ column = chquery.normalize_table_column(
91
+ column={
92
+ "name": "created_at",
93
+ "normalized_name": "created_at",
94
+ "type": "DateTime64(3)",
95
+ "nullable": False,
96
+ },
97
+ )
98
+
99
+ self.assertEqual(column["type"], "DateTime64(3)")
100
+ self.assertFalse(column["nullable"])
101
+
102
+ def test_normalizes_json_dynamic_param_spacing(self):
103
+ column = chquery.normalize_table_column(
104
+ column={
105
+ "name": "json",
106
+ "normalized_name": "json",
107
+ "type": "JSON(max_dynamic_types=2, max_dynamic_paths=16)",
108
+ "nullable": False,
109
+ },
110
+ )
111
+
112
+ self.assertEqual(
113
+ column["type"], "JSON(max_dynamic_types = 2, max_dynamic_paths = 16)"
114
+ )
115
+ self.assertFalse(column["nullable"])
116
+
117
+ def test_normalizes_nested_decimal_spacing(self):
118
+ column = chquery.normalize_table_column(
119
+ column={
120
+ "name": "agg",
121
+ "normalized_name": "agg",
122
+ "type": "AggregateFunction(sum, Decimal(10,2))",
123
+ "nullable": False,
124
+ },
125
+ )
126
+
127
+ self.assertEqual(column["type"], "AggregateFunction(sum, Decimal(10, 2))")
128
+ self.assertFalse(column["nullable"])
129
+
130
+ def test_normalizes_multiline_complex_type_spacing(self):
131
+ column = chquery.normalize_table_column(
132
+ column={
133
+ "name": "agg",
134
+ "normalized_name": "agg",
135
+ "type": """AggregateFunction(
136
+ argMax, Nullable(UUID), Tuple(UInt8, DateTime64(3), DateTime64(3), DateTime64(3))
137
+ )""",
138
+ "nullable": False,
139
+ },
140
+ )
141
+
142
+ self.assertEqual(
143
+ column["type"],
144
+ "AggregateFunction(argMax, Nullable(UUID), Tuple(UInt8, DateTime64(3), DateTime64(3), DateTime64(3)))",
145
+ )
146
+ self.assertFalse(column["nullable"])
147
+
148
+ def test_preserves_space_before_timezone_string(self):
149
+ column = chquery.normalize_table_column(
150
+ column={
151
+ "name": "ts",
152
+ "normalized_name": "ts",
153
+ "type": "DateTime64(3, 'Europe/Vienna')",
154
+ "nullable": False,
155
+ },
156
+ )
157
+
158
+ self.assertEqual(column["type"], "DateTime64(3, 'Europe/Vienna')")
159
+ self.assertFalse(column["nullable"])
160
+
161
+ def test_nullable_flag_preserves_raw_type(self):
162
+ column = chquery.normalize_table_column(
163
+ column={
164
+ "name": "reason",
165
+ "normalized_name": "reason",
166
+ "type": "String",
167
+ "nullable": True,
168
+ },
169
+ )
170
+
171
+ self.assertEqual(column["type"], "String")
172
+ self.assertTrue(column["nullable"])
173
+
174
+ def test_nullable_flag_preserves_low_cardinality_type(self):
175
+ column = chquery.normalize_table_column(
176
+ column={
177
+ "name": "status",
178
+ "normalized_name": "status",
179
+ "type": "LowCardinality(String)",
180
+ "nullable": True,
181
+ },
182
+ )
183
+
184
+ self.assertEqual(column["type"], "LowCardinality(String)")
185
+ self.assertTrue(column["nullable"])
186
+
187
+ def test_preserves_existing_nullable_low_cardinality_wrapper(self):
188
+ column = chquery.normalize_table_column(
189
+ column={
190
+ "name": "status",
191
+ "normalized_name": "status",
192
+ "type": "LowCardinality(Nullable(String))",
193
+ "nullable": False,
194
+ },
195
+ )
196
+
197
+ self.assertEqual(column["type"], "LowCardinality(Nullable(String))")
198
+ self.assertTrue(column["nullable"])
199
+
200
+ def test_normalizes_default_value_into_specifier_and_expression(self):
201
+ column = chquery.normalize_table_column(
202
+ column={
203
+ "name": "created_at",
204
+ "normalized_name": "created_at",
205
+ "type": "DateTime64(3)",
206
+ "nullable": False,
207
+ "default_value": "DEFAULT now()",
208
+ },
209
+ )
210
+
211
+ self.assertEqual(column["type"], "DateTime64(3)")
212
+ self.assertEqual(column["default_specifier"], "DEFAULT")
213
+ self.assertEqual(column["default_expression"], "now()")
214
+
215
+ def test_preserves_split_default_specifier_and_expression(self):
216
+ column = chquery.normalize_table_column(
217
+ column={
218
+ "name": "country",
219
+ "normalized_name": "country",
220
+ "type": "String",
221
+ "nullable": False,
222
+ "default_value": "DEFAULT 'Unknown'",
223
+ },
224
+ )
225
+
226
+ self.assertEqual(column["default_specifier"], "DEFAULT")
227
+ self.assertEqual(column["default_expression"], "'Unknown'")
228
+
229
+ def test_normalizes_comment_codec_ttl_and_primary_key(self):
230
+ column = chquery.normalize_table_column(
231
+ column={
232
+ "name": "ts",
233
+ "normalized_name": "ts",
234
+ "type": "DateTime64(3)",
235
+ "nullable": False,
236
+ "comment": "'event time'",
237
+ "codec": "CODEC(ZSTD(1))",
238
+ "ttl": "ts + toIntervalDay(30)",
239
+ "is_primary_key": True,
240
+ },
241
+ )
242
+
243
+ self.assertEqual(column["type"], "DateTime64(3)")
244
+ self.assertEqual(column["comment"], "'event time'")
245
+ self.assertEqual(column["codec"], "CODEC(ZSTD(1))")
246
+ self.assertEqual(column["ttl"], "ts + toIntervalDay(30)")
247
+ self.assertTrue(column["is_primary_key"])
248
+
249
+ def test_preserves_normalized_name_for_output_name(self):
250
+ column = chquery.normalize_table_column(
251
+ column={
252
+ "name": "CamelCase",
253
+ "normalized_name": "camel_case",
254
+ "type": "String",
255
+ },
256
+ )
257
+
258
+ self.assertEqual(column["name"], "camel_case")
259
+ self.assertEqual(column["type"], "String")
260
+
261
+ def test_normalizes_string_scalar(self):
262
+ column = chquery.normalize_table_column(
263
+ column={
264
+ "name": "id",
265
+ "normalized_name": "id",
266
+ "type": "String",
267
+ "nullable": False,
268
+ },
269
+ )
270
+
271
+ self.assertEqual(column["type"], "String")
272
+
273
+ def test_normalizes_bool_scalar(self):
274
+ column = chquery.normalize_table_column(
275
+ column={
276
+ "name": "is_active",
277
+ "normalized_name": "is_active",
278
+ "type": "Bool",
279
+ "nullable": False,
280
+ },
281
+ )
282
+
283
+ self.assertEqual(column["type"], "Bool")
284
+
285
+ def test_normalizes_uint64_scalar(self):
286
+ column = chquery.normalize_table_column(
287
+ column={
288
+ "name": "count",
289
+ "normalized_name": "count",
290
+ "type": "UInt64",
291
+ "nullable": False,
292
+ },
293
+ )
294
+
295
+ self.assertEqual(column["type"], "UInt64")
296
+
297
+ def test_normalizes_array_payload(self):
298
+ column = chquery.normalize_table_column(
299
+ column={
300
+ "name": "tags",
301
+ "normalized_name": "tags",
302
+ "type": "Array(String)",
303
+ "nullable": False,
304
+ },
305
+ )
306
+
307
+ self.assertEqual(column["type"], "Array(String)")
308
+
309
+ def test_normalizes_map_payload(self):
310
+ column = chquery.normalize_table_column(
311
+ column={
312
+ "name": "metadata",
313
+ "normalized_name": "metadata",
314
+ "type": "Map(String, String)",
315
+ "nullable": False,
316
+ },
317
+ )
318
+
319
+ self.assertEqual(column["type"], "Map(String, String)")
320
+
321
+ def test_normalizes_low_cardinality_payload(self):
322
+ column = chquery.normalize_table_column(
323
+ column={
324
+ "name": "category",
325
+ "normalized_name": "category",
326
+ "type": "LowCardinality(String)",
327
+ "nullable": False,
328
+ },
329
+ )
330
+
331
+ self.assertEqual(column["type"], "LowCardinality(String)")
332
+
333
+ def test_normalizes_nullable_string_field(self):
334
+ column = chquery.normalize_table_column(
335
+ column={
336
+ "name": "phone",
337
+ "normalized_name": "phone",
338
+ "type": "Nullable(String)",
339
+ "nullable": False,
340
+ },
341
+ )
342
+
343
+ self.assertEqual(column["type"], "Nullable(String)")
344
+
345
+ def test_normalizes_nullable_datetime64_field(self):
346
+ column = chquery.normalize_table_column(
347
+ column={
348
+ "name": "deleted_at",
349
+ "normalized_name": "deleted_at",
350
+ "type": "Nullable(DateTime64(3))",
351
+ "nullable": False,
352
+ },
353
+ )
354
+
355
+ self.assertEqual(column["type"], "Nullable(DateTime64(3))")
356
+
357
+ def test_normalizes_decimal_business_field(self):
358
+ column = chquery.normalize_table_column(
359
+ column={
360
+ "name": "price",
361
+ "normalized_name": "price",
362
+ "type": "Decimal(12,2)",
363
+ "nullable": False,
364
+ },
365
+ )
366
+
367
+ self.assertEqual(column["type"], "Decimal(12, 2)")
368
+
369
+ def test_preserves_decimal64_business_field(self):
370
+ column = chquery.normalize_table_column(
371
+ column={
372
+ "name": "amount",
373
+ "normalized_name": "amount",
374
+ "type": "Decimal64(4)",
375
+ "nullable": False,
376
+ },
377
+ )
378
+
379
+ self.assertEqual(column["type"], "Decimal64(4)")
380
+
381
+ def test_normalizes_float64_business_field(self):
382
+ column = chquery.normalize_table_column(
383
+ column={
384
+ "name": "score",
385
+ "normalized_name": "score",
386
+ "type": "Float64",
387
+ "nullable": False,
388
+ },
389
+ )
390
+
391
+ self.assertEqual(column["type"], "Float64")
392
+
393
+ def test_default_now_for_created_at(self):
394
+ column = chquery.normalize_table_column(
395
+ column={
396
+ "name": "created_at",
397
+ "normalized_name": "created_at",
398
+ "type": "DateTime64(3)",
399
+ "nullable": False,
400
+ "default_value": "DEFAULT now()",
401
+ },
402
+ )
403
+
404
+ self.assertEqual(column["default_specifier"], "DEFAULT")
405
+ self.assertEqual(column["default_expression"], "now()")
406
+
407
+ def test_default_true_for_bool(self):
408
+ column = chquery.normalize_table_column(
409
+ column={
410
+ "name": "is_active",
411
+ "normalized_name": "is_active",
412
+ "type": "Bool",
413
+ "nullable": False,
414
+ "default_value": "DEFAULT true",
415
+ },
416
+ )
417
+
418
+ self.assertEqual(column["default_expression"], "true")
419
+
420
+ def test_default_zero_for_retry_count(self):
421
+ column = chquery.normalize_table_column(
422
+ column={
423
+ "name": "retry_count",
424
+ "normalized_name": "retry_count",
425
+ "type": "UInt32",
426
+ "nullable": False,
427
+ "default_value": "DEFAULT 0",
428
+ },
429
+ )
430
+
431
+ self.assertEqual(column["default_expression"], "0")
@@ -1144,6 +1144,25 @@ class TestDisabledFunctions(unittest.TestCase):
1144
1144
  expected = "SELECT * FROM (SELECT count() from system.numbers LIMIT 2) AS a, (SELECT count() from system.numbers LIMIT 2) AS a SETTINGS join_use_nulls = 1"
1145
1145
  self.assertEqual(chquery.format(expected), chquery.format(replaced))
1146
1146
 
1147
+ def test_allow_analytics_query_settings(self):
1148
+ sql = """SELECT * FROM a SETTINGS
1149
+ max_threads = 1,
1150
+ max_memory_usage = 1000000,
1151
+ max_execution_time = 60,
1152
+ use_query_condition_cache = 0
1153
+ """
1154
+ replacement = {("default", "a"): ("", "(SELECT count() FROM system.numbers LIMIT 1)")}
1155
+ replaced = chquery.replace_tables(sql, replacement, default_database="default")
1156
+ out = chquery.format(replaced)
1157
+ self.assertIn("system.numbers", out)
1158
+ for key in (
1159
+ "max_threads",
1160
+ "max_memory_usage",
1161
+ "max_execution_time",
1162
+ "use_query_condition_cache",
1163
+ ):
1164
+ self.assertIn(key, out)
1165
+
1147
1166
  def test_disallow_enabled_settings(self):
1148
1167
  sql = "SELECT * FROM a, a SETTINGS join_use_nulls=1"
1149
1168
  replacement = {
@@ -2025,6 +2025,23 @@ SELECT *
2025
2025
  self.assertEqual(chquery.tables("SELECT count() from system.numbers SETTINGS aggregate_functions_null_for_empty=1"),
2026
2026
  [('system', 'numbers', '')])
2027
2027
 
2028
+ def test_allow_analytics_query_settings(self):
2029
+ sql = """SELECT count() FROM system.numbers LIMIT 1 SETTINGS
2030
+ max_threads = 1,
2031
+ max_memory_usage = 1000000,
2032
+ max_execution_time = 60,
2033
+ optimize_aggregation_in_order = 1,
2034
+ use_skip_indexes = 1,
2035
+ use_skip_indexes_if_final = 0,
2036
+ optimize_move_to_prewhere = 1,
2037
+ query_plan_optimize_lazy_materialization = 0,
2038
+ min_bytes_to_use_direct_io = 0,
2039
+ enable_filesystem_cache = 1,
2040
+ use_query_cache = 0,
2041
+ use_query_condition_cache = 0
2042
+ """
2043
+ self.assertEqual(chquery.tables(sql), [('system', 'numbers', '')])
2044
+
2028
2045
  def test_disallow_blocked_settings(self):
2029
2046
  with self.assertRaisesRegex(ValueError, """DB::Exception: Usage of setting 'aggregate_functions_null_for_empty' is restricted. Contact support@tinybird.co if you require access to this feature"""):
2030
2047
  chquery.tables("SELECT count() from system.numbers SETTINGS aggregate_functions_null_for_empty=1",