tinybird-toolset 2.5.5__tar.gz → 2.6.1__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.
- {tinybird_toolset-2.5.5/src/tinybird_toolset.egg-info → tinybird_toolset-2.6.1}/PKG-INFO +1 -1
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/setup.py +1 -1
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/chtoolset/query.py +2 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1/src/tinybird_toolset.egg-info}/PKG-INFO +1 -1
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/SOURCES.txt +2 -0
- tinybird_toolset-2.6.1/tests/test_check_ttl_partition_compatibility.py +175 -0
- tinybird_toolset-2.6.1/tests/test_validate_partition_key.py +150 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_validate_ttl.py +248 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/LICENSE +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/MANIFEST.in +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/README.md +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/conf.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/setup.cfg +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/chtoolset/__init__.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/dependency_links.txt +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/requires.txt +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/top_level.txt +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_check_compatible_types.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_check_write_query.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_chquery.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_convert_to_row_binary.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_explain_ast.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_get_columns_from_create_query.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_get_left_table.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_internal_cache.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_normalize_query_keep_names.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_normalize_table_column.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_parse_create_materialized_view_target_table.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_replace_tables.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_replace_tables_backward_compat.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_rewrite_aggregation_states.py +0 -0
- {tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_tables.py +0 -0
|
@@ -21,6 +21,8 @@ from chtoolset._query import replace_tables, \
|
|
|
21
21
|
get_columns_from_create_query, \
|
|
22
22
|
normalize_table_column, \
|
|
23
23
|
validate_ttl, \
|
|
24
|
+
validate_partition_key, \
|
|
25
|
+
check_ttl_partition_compatibility, \
|
|
24
26
|
parse_create_materialized_view_target_table as _parse_create_materialized_view_target_table
|
|
25
27
|
|
|
26
28
|
from typing import Optional, TypedDict
|
|
@@ -11,6 +11,7 @@ src/tinybird_toolset.egg-info/dependency_links.txt
|
|
|
11
11
|
src/tinybird_toolset.egg-info/requires.txt
|
|
12
12
|
src/tinybird_toolset.egg-info/top_level.txt
|
|
13
13
|
tests/test_check_compatible_types.py
|
|
14
|
+
tests/test_check_ttl_partition_compatibility.py
|
|
14
15
|
tests/test_check_write_query.py
|
|
15
16
|
tests/test_chquery.py
|
|
16
17
|
tests/test_convert_to_row_binary.py
|
|
@@ -25,4 +26,5 @@ tests/test_replace_tables.py
|
|
|
25
26
|
tests/test_replace_tables_backward_compat.py
|
|
26
27
|
tests/test_rewrite_aggregation_states.py
|
|
27
28
|
tests/test_tables.py
|
|
29
|
+
tests/test_validate_partition_key.py
|
|
28
30
|
tests/test_validate_ttl.py
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from chtoolset import query as chquery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestCheckTTLPartitionCompatibility(unittest.TestCase):
|
|
6
|
+
def test_checked_and_compatible(self):
|
|
7
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
8
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
9
|
+
ttl="ts + INTERVAL 1 MONTH",
|
|
10
|
+
sorting_key="ts",
|
|
11
|
+
primary_key="ts",
|
|
12
|
+
partition_key="toYYYYMM(ts)",
|
|
13
|
+
)
|
|
14
|
+
self.assertTrue(result["checked"])
|
|
15
|
+
self.assertIsNone(result["error"])
|
|
16
|
+
|
|
17
|
+
def test_checked_and_compatible_uses_interval_count(self):
|
|
18
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
19
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
20
|
+
ttl="ts + INTERVAL 90 DAY",
|
|
21
|
+
sorting_key="ts",
|
|
22
|
+
primary_key="ts",
|
|
23
|
+
partition_key="toYYYYMM(ts)",
|
|
24
|
+
)
|
|
25
|
+
self.assertTrue(result["checked"])
|
|
26
|
+
self.assertIsNone(result["error"])
|
|
27
|
+
|
|
28
|
+
def test_checked_and_incompatible(self):
|
|
29
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
30
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
31
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
32
|
+
sorting_key="ts",
|
|
33
|
+
primary_key="ts",
|
|
34
|
+
partition_key="toYYYYMM(ts)",
|
|
35
|
+
)
|
|
36
|
+
self.assertTrue(result["checked"])
|
|
37
|
+
self.assertIsNotNone(result["error"])
|
|
38
|
+
self.assertIn("shorter than the partition key period", result["error"])
|
|
39
|
+
|
|
40
|
+
def test_mixed_where_clause_ttl_is_not_checked(self):
|
|
41
|
+
# A WHERE clause makes the whole statement ineligible for ttl_only_drop_parts,
|
|
42
|
+
# including when it also contains an unconditional TTL.
|
|
43
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
44
|
+
columns=[
|
|
45
|
+
{"name": "ts", "type": "DateTime"},
|
|
46
|
+
{"name": "status", "type": "String"},
|
|
47
|
+
],
|
|
48
|
+
ttl="ts + INTERVAL 1 MONTH, ts + INTERVAL 1 DAY WHERE status = 'expired'",
|
|
49
|
+
sorting_key="ts",
|
|
50
|
+
primary_key="ts",
|
|
51
|
+
partition_key="toYYYYMM(ts)",
|
|
52
|
+
)
|
|
53
|
+
self.assertFalse(result["checked"])
|
|
54
|
+
self.assertIsNone(result["error"])
|
|
55
|
+
|
|
56
|
+
def test_not_checked_when_a_ttl_clause_is_unrecognized(self):
|
|
57
|
+
# An unrecognized unconditional TTL remains unchecked; the WHERE clause also makes the
|
|
58
|
+
# whole statement ineligible for the optimization.
|
|
59
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
60
|
+
columns=[
|
|
61
|
+
{"name": "ts", "type": "DateTime"},
|
|
62
|
+
{"name": "other_col", "type": "DateTime"},
|
|
63
|
+
{"name": "flag", "type": "UInt8"},
|
|
64
|
+
],
|
|
65
|
+
ttl="toDateTime(other_col) + INTERVAL 1 DAY, ts + INTERVAL 1 MONTH WHERE flag = 1",
|
|
66
|
+
sorting_key="ts",
|
|
67
|
+
primary_key="ts",
|
|
68
|
+
partition_key="toYYYYMM(ts)",
|
|
69
|
+
)
|
|
70
|
+
self.assertFalse(result["checked"])
|
|
71
|
+
self.assertIsNone(result["error"])
|
|
72
|
+
|
|
73
|
+
# validate_ttl, by contrast, silently accepts it - confirming the gap this function closes.
|
|
74
|
+
self.assertIsNone(
|
|
75
|
+
chquery.validate_ttl(
|
|
76
|
+
columns=[
|
|
77
|
+
{"name": "ts", "type": "DateTime"},
|
|
78
|
+
{"name": "other_col", "type": "DateTime"},
|
|
79
|
+
{"name": "flag", "type": "UInt8"},
|
|
80
|
+
],
|
|
81
|
+
ttl="toDateTime(other_col) + INTERVAL 1 DAY, ts + INTERVAL 1 MONTH WHERE flag = 1",
|
|
82
|
+
sorting_key="ts",
|
|
83
|
+
primary_key="ts",
|
|
84
|
+
partition_key="toYYYYMM(ts)",
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def test_not_checked_when_only_where_ttl_exists(self):
|
|
89
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
90
|
+
columns=[
|
|
91
|
+
{"name": "ts", "type": "DateTime"},
|
|
92
|
+
{"name": "flag", "type": "UInt8"},
|
|
93
|
+
],
|
|
94
|
+
ttl="ts + INTERVAL 1 DAY WHERE flag = 1",
|
|
95
|
+
sorting_key="ts",
|
|
96
|
+
primary_key="ts",
|
|
97
|
+
partition_key="toYYYYMM(ts)",
|
|
98
|
+
)
|
|
99
|
+
self.assertFalse(result["checked"])
|
|
100
|
+
self.assertIsNone(result["error"])
|
|
101
|
+
|
|
102
|
+
def test_not_checked_when_ttl_column_differs_from_partition_column(self):
|
|
103
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
104
|
+
columns=[
|
|
105
|
+
{"name": "ts", "type": "DateTime"},
|
|
106
|
+
{"name": "updated_at", "type": "DateTime"},
|
|
107
|
+
],
|
|
108
|
+
ttl="updated_at + INTERVAL 7 DAY",
|
|
109
|
+
sorting_key="ts",
|
|
110
|
+
primary_key="ts",
|
|
111
|
+
partition_key="toYYYYMM(ts)",
|
|
112
|
+
)
|
|
113
|
+
self.assertFalse(result["checked"])
|
|
114
|
+
self.assertIsNone(result["error"])
|
|
115
|
+
|
|
116
|
+
def test_not_checked_for_composite_partition_key(self):
|
|
117
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
118
|
+
columns=[
|
|
119
|
+
{"name": "ts", "type": "DateTime"},
|
|
120
|
+
{"name": "k", "type": "String"},
|
|
121
|
+
],
|
|
122
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
123
|
+
sorting_key="ts",
|
|
124
|
+
primary_key="ts",
|
|
125
|
+
partition_key="toYYYYMM(ts), k",
|
|
126
|
+
)
|
|
127
|
+
self.assertFalse(result["checked"])
|
|
128
|
+
self.assertIsNone(result["error"])
|
|
129
|
+
|
|
130
|
+
def test_not_checked_for_unrecognized_partition_function(self):
|
|
131
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
132
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
133
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
134
|
+
sorting_key="ts",
|
|
135
|
+
primary_key="ts",
|
|
136
|
+
partition_key="intDiv(toUInt32(ts), 86400)",
|
|
137
|
+
)
|
|
138
|
+
self.assertFalse(result["checked"])
|
|
139
|
+
self.assertIsNone(result["error"])
|
|
140
|
+
|
|
141
|
+
def test_not_checked_when_no_partition_key(self):
|
|
142
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
143
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
144
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
145
|
+
sorting_key="ts",
|
|
146
|
+
primary_key="ts",
|
|
147
|
+
)
|
|
148
|
+
self.assertFalse(result["checked"])
|
|
149
|
+
self.assertIsNone(result["error"])
|
|
150
|
+
|
|
151
|
+
def test_not_checked_when_no_ttl(self):
|
|
152
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
153
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
154
|
+
ttl="",
|
|
155
|
+
sorting_key="ts",
|
|
156
|
+
primary_key="ts",
|
|
157
|
+
partition_key="toYYYYMM(ts)",
|
|
158
|
+
)
|
|
159
|
+
self.assertFalse(result["checked"])
|
|
160
|
+
self.assertIsNone(result["error"])
|
|
161
|
+
|
|
162
|
+
def test_not_checked_on_malformed_ttl(self):
|
|
163
|
+
result = chquery.check_ttl_partition_compatibility(
|
|
164
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
165
|
+
ttl="this is not a valid ttl !!!",
|
|
166
|
+
sorting_key="ts",
|
|
167
|
+
primary_key="ts",
|
|
168
|
+
partition_key="toYYYYMM(ts)",
|
|
169
|
+
)
|
|
170
|
+
self.assertFalse(result["checked"])
|
|
171
|
+
self.assertIsNone(result["error"])
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
unittest.main()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from chtoolset import query as chquery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestValidatePartitionKey(unittest.TestCase):
|
|
6
|
+
def test_valid_empty_partition_key(self):
|
|
7
|
+
self.assertIsNone(
|
|
8
|
+
chquery.validate_partition_key(
|
|
9
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
10
|
+
partition_key="",
|
|
11
|
+
)
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def test_valid_hourly_partition_key(self):
|
|
15
|
+
# Hour is the floor - allowed.
|
|
16
|
+
self.assertIsNone(
|
|
17
|
+
chquery.validate_partition_key(
|
|
18
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
19
|
+
partition_key="toStartOfHour(ts)",
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def test_valid_daily_partition_key(self):
|
|
24
|
+
self.assertIsNone(
|
|
25
|
+
chquery.validate_partition_key(
|
|
26
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
27
|
+
partition_key="toDate(ts)",
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def test_valid_monthly_partition_key(self):
|
|
32
|
+
self.assertIsNone(
|
|
33
|
+
chquery.validate_partition_key(
|
|
34
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
35
|
+
partition_key="toYYYYMM(ts)",
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def test_invalid_minute_partition_key(self):
|
|
40
|
+
err = chquery.validate_partition_key(
|
|
41
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
42
|
+
partition_key="toStartOfMinute(ts)",
|
|
43
|
+
)
|
|
44
|
+
self.assertIsNotNone(err)
|
|
45
|
+
self.assertIn("finer than hourly", err)
|
|
46
|
+
|
|
47
|
+
def test_invalid_second_partition_key(self):
|
|
48
|
+
err = chquery.validate_partition_key(
|
|
49
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
50
|
+
partition_key="toStartOfSecond(ts)",
|
|
51
|
+
)
|
|
52
|
+
self.assertIsNotNone(err)
|
|
53
|
+
self.assertIn("finer than hourly", err)
|
|
54
|
+
|
|
55
|
+
def test_invalid_five_minute_partition_key(self):
|
|
56
|
+
err = chquery.validate_partition_key(
|
|
57
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
58
|
+
partition_key="toStartOfFiveMinutes(ts)",
|
|
59
|
+
)
|
|
60
|
+
self.assertIsNotNone(err)
|
|
61
|
+
self.assertIn("finer than hourly", err)
|
|
62
|
+
|
|
63
|
+
def test_invalid_ten_minute_partition_key(self):
|
|
64
|
+
err = chquery.validate_partition_key(
|
|
65
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
66
|
+
partition_key="toStartOfTenMinutes(ts)",
|
|
67
|
+
)
|
|
68
|
+
self.assertIsNotNone(err)
|
|
69
|
+
self.assertIn("finer than hourly", err)
|
|
70
|
+
|
|
71
|
+
def test_invalid_fifteen_minute_partition_key(self):
|
|
72
|
+
err = chquery.validate_partition_key(
|
|
73
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
74
|
+
partition_key="toStartOfFifteenMinutes(ts)",
|
|
75
|
+
)
|
|
76
|
+
self.assertIsNotNone(err)
|
|
77
|
+
self.assertIn("finer than hourly", err)
|
|
78
|
+
|
|
79
|
+
def test_invalid_five_minute_singular_alias_partition_key(self):
|
|
80
|
+
# toStartOfFiveMinute (singular) is a registered alias for toStartOfFiveMinutes.
|
|
81
|
+
err = chquery.validate_partition_key(
|
|
82
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
83
|
+
partition_key="toStartOfFiveMinute(ts)",
|
|
84
|
+
)
|
|
85
|
+
self.assertIsNotNone(err)
|
|
86
|
+
self.assertIn("finer than hourly", err)
|
|
87
|
+
|
|
88
|
+
def test_invalid_millisecond_partition_key(self):
|
|
89
|
+
err = chquery.validate_partition_key(
|
|
90
|
+
columns=[{"name": "ts", "type": "DateTime64(3)"}],
|
|
91
|
+
partition_key="toStartOfMillisecond(ts)",
|
|
92
|
+
)
|
|
93
|
+
self.assertIsNotNone(err)
|
|
94
|
+
self.assertIn("finer than hourly", err)
|
|
95
|
+
|
|
96
|
+
def test_invalid_microsecond_partition_key(self):
|
|
97
|
+
err = chquery.validate_partition_key(
|
|
98
|
+
columns=[{"name": "ts", "type": "DateTime64(6)"}],
|
|
99
|
+
partition_key="toStartOfMicrosecond(ts)",
|
|
100
|
+
)
|
|
101
|
+
self.assertIsNotNone(err)
|
|
102
|
+
self.assertIn("finer than hourly", err)
|
|
103
|
+
|
|
104
|
+
def test_invalid_nanosecond_partition_key(self):
|
|
105
|
+
err = chquery.validate_partition_key(
|
|
106
|
+
columns=[{"name": "ts", "type": "DateTime64(9)"}],
|
|
107
|
+
partition_key="toStartOfNanosecond(ts)",
|
|
108
|
+
)
|
|
109
|
+
self.assertIsNotNone(err)
|
|
110
|
+
self.assertIn("finer than hourly", err)
|
|
111
|
+
|
|
112
|
+
def test_invalid_yyyymmddhhmmss_partition_key(self):
|
|
113
|
+
# Packs the full date+time down to the second - no coarsening at all.
|
|
114
|
+
err = chquery.validate_partition_key(
|
|
115
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
116
|
+
partition_key="toYYYYMMDDhhmmss(ts)",
|
|
117
|
+
)
|
|
118
|
+
self.assertIsNotNone(err)
|
|
119
|
+
self.assertIn("finer than hourly", err)
|
|
120
|
+
|
|
121
|
+
def test_valid_skipped_for_composite_partition_key(self):
|
|
122
|
+
self.assertIsNone(
|
|
123
|
+
chquery.validate_partition_key(
|
|
124
|
+
columns=[
|
|
125
|
+
{"name": "ts", "type": "DateTime"},
|
|
126
|
+
{"name": "k", "type": "String"},
|
|
127
|
+
],
|
|
128
|
+
partition_key="toStartOfMinute(ts), k",
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def test_valid_skipped_for_unrecognized_function(self):
|
|
133
|
+
self.assertIsNone(
|
|
134
|
+
chquery.validate_partition_key(
|
|
135
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
136
|
+
partition_key="intDiv(toUInt32(ts), 60)",
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def test_valid_skipped_for_bare_column(self):
|
|
141
|
+
self.assertIsNone(
|
|
142
|
+
chquery.validate_partition_key(
|
|
143
|
+
columns=[{"name": "k", "type": "String"}],
|
|
144
|
+
partition_key="k",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
unittest.main()
|
|
@@ -389,6 +389,254 @@ class TestValidateTTL(unittest.TestCase):
|
|
|
389
389
|
primary_key="ts",
|
|
390
390
|
)
|
|
391
391
|
|
|
392
|
+
# --- Partition key / TTL granularity ---
|
|
393
|
+
def test_invalid_ttl_finer_than_partition_month_vs_day(self):
|
|
394
|
+
err = chquery.validate_ttl(
|
|
395
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
396
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
397
|
+
sorting_key="ts",
|
|
398
|
+
primary_key="ts",
|
|
399
|
+
partition_key="toYYYYMM(ts)",
|
|
400
|
+
)
|
|
401
|
+
self.assertIsNotNone(err)
|
|
402
|
+
self.assertIn("shorter than the partition key period", err)
|
|
403
|
+
|
|
404
|
+
def test_invalid_ttl_finer_than_partition_month_vs_week(self):
|
|
405
|
+
err = chquery.validate_ttl(
|
|
406
|
+
ttl="ts + INTERVAL 1 WEEK",
|
|
407
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
408
|
+
sorting_key="ts",
|
|
409
|
+
primary_key="ts",
|
|
410
|
+
partition_key="toYYYYMM(ts)",
|
|
411
|
+
)
|
|
412
|
+
self.assertIsNotNone(err)
|
|
413
|
+
self.assertIn("shorter than the partition key period", err)
|
|
414
|
+
|
|
415
|
+
def test_valid_ttl_interval_count_is_included_in_duration(self):
|
|
416
|
+
# 90 DAY is comparable to 3 MONTH for a monthly partition; the DAY unit alone
|
|
417
|
+
# must not cause a rejection.
|
|
418
|
+
self.assertIsNone(
|
|
419
|
+
chquery.validate_ttl(
|
|
420
|
+
ttl="ts + INTERVAL 90 DAY",
|
|
421
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
422
|
+
sorting_key="ts",
|
|
423
|
+
primary_key="ts",
|
|
424
|
+
partition_key="toYYYYMM(ts)",
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def test_invalid_ttl_interval_does_not_cover_longest_partition_period(self):
|
|
429
|
+
err = chquery.validate_ttl(
|
|
430
|
+
ttl="ts + INTERVAL 30 DAY",
|
|
431
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
432
|
+
sorting_key="ts",
|
|
433
|
+
primary_key="ts",
|
|
434
|
+
partition_key="toYYYYMM(ts)",
|
|
435
|
+
)
|
|
436
|
+
self.assertIsNotNone(err)
|
|
437
|
+
self.assertIn("shorter than the partition key period", err)
|
|
438
|
+
|
|
439
|
+
def test_valid_ttl_interval_covers_longest_partition_period(self):
|
|
440
|
+
self.assertIsNone(
|
|
441
|
+
chquery.validate_ttl(
|
|
442
|
+
ttl="ts + INTERVAL 31 DAY",
|
|
443
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
444
|
+
sorting_key="ts",
|
|
445
|
+
primary_key="ts",
|
|
446
|
+
partition_key="toYYYYMM(ts)",
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
def test_valid_ttl_coarser_than_partition_month_vs_day(self):
|
|
451
|
+
# TTL is monthly, partition is daily: whole day-partitions still expire together.
|
|
452
|
+
self.assertIsNone(
|
|
453
|
+
chquery.validate_ttl(
|
|
454
|
+
ttl="ts + INTERVAL 3 MONTH",
|
|
455
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
456
|
+
sorting_key="ts",
|
|
457
|
+
primary_key="ts",
|
|
458
|
+
partition_key="toDate(ts)",
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
def test_valid_ttl_matches_partition_granularity(self):
|
|
463
|
+
self.assertIsNone(
|
|
464
|
+
chquery.validate_ttl(
|
|
465
|
+
ttl="ts + INTERVAL 1 MONTH",
|
|
466
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
467
|
+
sorting_key="ts",
|
|
468
|
+
primary_key="ts",
|
|
469
|
+
partition_key="toYYYYMM(ts)",
|
|
470
|
+
)
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
def test_valid_no_partition_key_skips_granularity_check(self):
|
|
474
|
+
self.assertIsNone(
|
|
475
|
+
chquery.validate_ttl(
|
|
476
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
477
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
478
|
+
sorting_key="ts",
|
|
479
|
+
primary_key="ts",
|
|
480
|
+
)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
def test_valid_granularity_check_skipped_for_different_column(self):
|
|
484
|
+
# TTL and partition key are on different columns; the relationship is unknown, so no check is applied.
|
|
485
|
+
self.assertIsNone(
|
|
486
|
+
chquery.validate_ttl(
|
|
487
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
488
|
+
columns=[
|
|
489
|
+
{"name": "ts", "type": "DateTime"},
|
|
490
|
+
{"name": "inserted_at", "type": "DateTime"},
|
|
491
|
+
],
|
|
492
|
+
sorting_key="ts",
|
|
493
|
+
primary_key="ts",
|
|
494
|
+
partition_key="toYYYYMM(inserted_at)",
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def test_valid_granularity_check_skipped_for_composite_partition_key(self):
|
|
499
|
+
self.assertIsNone(
|
|
500
|
+
chquery.validate_ttl(
|
|
501
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
502
|
+
columns=[
|
|
503
|
+
{"name": "ts", "type": "DateTime"},
|
|
504
|
+
{"name": "k", "type": "String"},
|
|
505
|
+
],
|
|
506
|
+
sorting_key="ts",
|
|
507
|
+
primary_key="ts",
|
|
508
|
+
partition_key="toYYYYMM(ts), k",
|
|
509
|
+
)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def test_valid_granularity_check_skipped_for_unrecognized_partition_function(self):
|
|
513
|
+
self.assertIsNone(
|
|
514
|
+
chquery.validate_ttl(
|
|
515
|
+
ttl="ts + INTERVAL 7 DAY",
|
|
516
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
517
|
+
sorting_key="ts",
|
|
518
|
+
primary_key="ts",
|
|
519
|
+
partition_key="intDiv(toUInt32(ts), 86400)",
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
def test_invalid_bare_column_ttl_with_partition_key(self):
|
|
524
|
+
# No INTERVAL at all: the TTL fires the instant the raw column value is reached,
|
|
525
|
+
# which is finer than any partition granularity.
|
|
526
|
+
err = chquery.validate_ttl(
|
|
527
|
+
ttl="ts",
|
|
528
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
529
|
+
sorting_key="ts",
|
|
530
|
+
primary_key="ts",
|
|
531
|
+
partition_key="toYYYYMM(ts)",
|
|
532
|
+
)
|
|
533
|
+
self.assertIsNotNone(err)
|
|
534
|
+
self.assertIn("shorter than the partition key period", err)
|
|
535
|
+
|
|
536
|
+
def test_valid_mixed_where_clause_ttl_skips_part_drop_check(self):
|
|
537
|
+
self.assertIsNone(
|
|
538
|
+
chquery.validate_ttl(
|
|
539
|
+
ttl="ts + INTERVAL 1 DAY, ts + INTERVAL 1 DAY WHERE status = 'expired'",
|
|
540
|
+
columns=[
|
|
541
|
+
{"name": "ts", "type": "DateTime"},
|
|
542
|
+
{"name": "status", "type": "String"},
|
|
543
|
+
],
|
|
544
|
+
sorting_key="ts",
|
|
545
|
+
primary_key="ts",
|
|
546
|
+
partition_key="toYYYYMM(ts)",
|
|
547
|
+
)
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
def test_invalid_fixed_interval_can_be_shorter_than_calendar_truncation(self):
|
|
551
|
+
# A month can contain 31 days, so late rows in that month can be born expired.
|
|
552
|
+
err = chquery.validate_ttl(
|
|
553
|
+
ttl="toStartOfMonth(ts) + INTERVAL 30 DAY",
|
|
554
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
555
|
+
sorting_key="ts",
|
|
556
|
+
primary_key="ts",
|
|
557
|
+
partition_key="toYYYYMM(ts)",
|
|
558
|
+
)
|
|
559
|
+
self.assertIsNotNone(err)
|
|
560
|
+
self.assertIn("shorter than its truncation period", err)
|
|
561
|
+
|
|
562
|
+
def test_valid_matching_calendar_interval_and_truncation(self):
|
|
563
|
+
self.assertIsNone(
|
|
564
|
+
chquery.validate_ttl(
|
|
565
|
+
ttl="toStartOfMonth(ts) + INTERVAL 1 MONTH",
|
|
566
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
567
|
+
sorting_key="ts",
|
|
568
|
+
primary_key="ts",
|
|
569
|
+
partition_key="toYYYYMM(ts)",
|
|
570
|
+
)
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
def test_valid_equivalent_calendar_interval_and_truncation(self):
|
|
574
|
+
self.assertIsNone(
|
|
575
|
+
chquery.validate_ttl(
|
|
576
|
+
ttl="toStartOfQuarter(ts) + INTERVAL 3 MONTH",
|
|
577
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
578
|
+
sorting_key="ts",
|
|
579
|
+
primary_key="ts",
|
|
580
|
+
partition_key="toStartOfQuarter(ts)",
|
|
581
|
+
)
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
def test_valid_long_interval_with_truncated_base_finer_than_partition(self):
|
|
585
|
+
self.assertIsNone(
|
|
586
|
+
chquery.validate_ttl(
|
|
587
|
+
ttl="toStartOfDay(ts) + INTERVAL 1 YEAR",
|
|
588
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
589
|
+
sorting_key="ts",
|
|
590
|
+
primary_key="ts",
|
|
591
|
+
partition_key="toYYYYMM(ts)",
|
|
592
|
+
)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def test_invalid_interval_shorter_than_ttl_truncation(self):
|
|
596
|
+
err = chquery.validate_ttl(
|
|
597
|
+
ttl="toStartOfMonth(ts) + INTERVAL 5 MINUTE",
|
|
598
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
599
|
+
sorting_key="ts",
|
|
600
|
+
primary_key="ts",
|
|
601
|
+
partition_key="toYYYYMM(ts)",
|
|
602
|
+
)
|
|
603
|
+
self.assertIsNotNone(err)
|
|
604
|
+
self.assertIn("shorter than its truncation period", err)
|
|
605
|
+
self.assertIn("expire as soon as they are inserted", err)
|
|
606
|
+
|
|
607
|
+
def test_invalid_interval_shorter_than_truncation_without_partition_key(self):
|
|
608
|
+
err = chquery.validate_ttl(
|
|
609
|
+
ttl="toStartOfHour(ts) + INTERVAL 5 MINUTE",
|
|
610
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
611
|
+
sorting_key="ts",
|
|
612
|
+
primary_key="ts",
|
|
613
|
+
)
|
|
614
|
+
self.assertIsNotNone(err)
|
|
615
|
+
self.assertIn("shorter than its truncation period", err)
|
|
616
|
+
|
|
617
|
+
def test_valid_granularity_check_skipped_for_multi_minute_partition(self):
|
|
618
|
+
# toStartOfFiveMinutes isn't a recognized partition granularity (IntervalKind has no
|
|
619
|
+
# level between Minute and Hour), so the check is skipped rather than risk a false accept.
|
|
620
|
+
self.assertIsNone(
|
|
621
|
+
chquery.validate_ttl(
|
|
622
|
+
ttl="ts + INTERVAL 1 MINUTE",
|
|
623
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
624
|
+
sorting_key="ts",
|
|
625
|
+
primary_key="ts",
|
|
626
|
+
partition_key="toStartOfFiveMinutes(ts)",
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
def test_valid_granularity_check_skipped_for_multi_minute_ttl_truncation(self):
|
|
631
|
+
self.assertIsNone(
|
|
632
|
+
chquery.validate_ttl(
|
|
633
|
+
ttl="toStartOfFiveMinutes(ts) + INTERVAL 1 HOUR",
|
|
634
|
+
columns=[{"name": "ts", "type": "DateTime"}],
|
|
635
|
+
sorting_key="ts",
|
|
636
|
+
primary_key="ts",
|
|
637
|
+
partition_key="toStartOfTenMinutes(ts)",
|
|
638
|
+
)
|
|
639
|
+
)
|
|
392
640
|
|
|
393
641
|
# --- Regression guard: sequential throwing calls must not crash ---
|
|
394
642
|
def test_sequential_throwing_calls_do_not_crash(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/requires.txt
RENAMED
|
File without changes
|
{tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/src/tinybird_toolset.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_get_columns_from_create_query.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_toolset-2.5.5 → tinybird_toolset-2.6.1}/tests/test_replace_tables_backward_compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|