Skip to content

Commit ed4e0b6

Browse files
authored
Remove support for non-maintained CH versions. Add again support for 25.8 (latest). Optimize some tests. (#517)
* Remove support for non-maintained CH versions. Add again support for 25.8 (latest) * Ensure system tables are available right after cluster is created. * Retry queries instead of waiting a fixed amount of time.
1 parent d29fcf5 commit ed4e0b6

File tree

12 files changed

+136
-121
lines changed

12 files changed

+136
-121
lines changed

.github/workflows/test_matrix.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ jobs:
2222
- '3.10'
2323
- '3.11'
2424
- '3.12'
25-
clickhouse-version:
26-
- '23.8'
27-
- '24.3'
28-
- '24.8'
25+
clickhouse-version: # Testing ClickHouse versions with active security support https://github.com/ClickHouse/ClickHouse/blob/master/SECURITY.md
2926
- '25.3'
30-
- '25.7' # Skipping 25.8+ until https://github.com/ClickHouse/ClickHouse/issues/86434 is fixed.
27+
- '25.6'
28+
- '25.7'
29+
- 'latest'
3130

3231
steps:
3332
- name: Checkout

tests/integration/adapter/clickhouse/test_clickhouse_table_ttl.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,7 @@
77
from dbt.tests.adapter.basic.test_base import BaseSimpleMaterializations
88
from dbt.tests.util import relation_from_name, run_dbt
99

10-
from tests.integration.adapter.helpers import below_version
1110

12-
13-
@pytest.mark.skipif(
14-
below_version(24, 3),
15-
reason='Pending to fix. Syntax error in 23.8: Code: 62. DB::Exception: Syntax error: failed at position 501 (\'SETTINGS\') (line 20, col 21): SETTINGS allow_nullable_key=1, replicated_deduplication_window=0',
16-
)
1711
class TestTableTTL(BaseSimpleMaterializations):
1812
@pytest.fixture(scope="class")
1913
def models(self):
@@ -88,8 +82,8 @@ def test_base(self, project):
8882

8983

9084
@pytest.mark.skipif(
91-
os.environ.get('DBT_CH_TEST_CLUSTER', '').strip() == '' or below_version(24, 3),
92-
reason='Not on a cluster. Also generated syntax not supported in 23.8: Code: 62. DB::Exception: Syntax error: failed at position 530 (end of query) (line 31, col 3): . Expected end of query. (SYNTAX_ERROR) (version 23.8.16.16 (official build))',
85+
os.environ.get('DBT_CH_TEST_CLUSTER', '').strip() == '',
86+
reason='Not on a cluster.',
9387
)
9488
class TestDistributedTableTTL:
9589
@pytest.fixture(scope="class")

tests/integration/adapter/dictionary/test_dictionary.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
import json
66
import os
7-
import time
87

98
import pytest
109
from dbt.tests.util import run_dbt
1110

11+
from tests.integration.adapter.helpers import DEFAULT_RETRY_CONFIG, retry_until_assertion_passes
12+
1213
testing_s3 = os.environ.get('DBT_CH_TEST_INCLUDE_S3', '').lower() in ('1', 'true', 'yes')
1314

1415

@@ -177,10 +178,17 @@ def test_create_and_update(self, project):
177178
# force the dictionary to be rebuilt to include the new records in `people`
178179
project.run_sql("system reload dictionary hackers")
179180

180-
if os.environ.get('DBT_CH_TEST_CLOUD', '').lower() in ('1', 'true', 'yes'):
181-
time.sleep(30)
182-
result = project.run_sql("select count(distinct id) from hackers", fetch="all")
183-
assert result[0][0] == 5
181+
retry_config = (
182+
{'max_retries': 30, 'delay': 1}
183+
if os.environ.get('DBT_CH_TEST_CLOUD', '').lower() in ('1', 'true', 'yes')
184+
else DEFAULT_RETRY_CONFIG
185+
)
186+
187+
def check_count():
188+
result = project.run_sql("select count(distinct id) from hackers", fetch="all")
189+
assert result[0][0] == 5
190+
191+
retry_until_assertion_passes(check_count, **retry_config)
184192

185193
# re-run dbt but this time with the new MV SQL
186194
run_vars = {"run_type": "extended_schema"}

tests/integration/adapter/helpers.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
2-
from typing import Optional
2+
import time
3+
from typing import Any, Callable, Optional, TypedDict
34

45

56
def below_version(major: int, minor: int = 0, _ch_test_version_value: Optional[str] = None) -> bool:
@@ -20,3 +21,30 @@ def below_version(major: int, minor: int = 0, _ch_test_version_value: Optional[s
2021
)
2122
actual_major, actual_minor = current_version.split('.')
2223
return int(actual_major) < major or (int(actual_major) == major and int(actual_minor) < minor)
24+
25+
26+
retry_config = TypedDict('retry_config', {'max_retries': int, 'delay': float})
27+
DEFAULT_RETRY_CONFIG: retry_config = {
28+
"max_retries": 20,
29+
"delay": 0.5,
30+
}
31+
32+
33+
def retry_until_assertion_passes(
34+
func: Callable[[], Any],
35+
max_retries: int = DEFAULT_RETRY_CONFIG["max_retries"],
36+
delay: float = DEFAULT_RETRY_CONFIG["delay"],
37+
) -> Any:
38+
last_error: Optional[AssertionError] = None
39+
for attempt in range(max_retries + 1): # +1 to include the initial attempt
40+
try:
41+
return func()
42+
except AssertionError as e:
43+
last_error = e
44+
if attempt < max_retries: # Don't sleep after the last attempt
45+
time.sleep(delay)
46+
continue
47+
# If we get here, all retries failed
48+
if last_error:
49+
raise last_error
50+
return None

tests/integration/adapter/materialized_view/test_materialized_view.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from dbt.adapters.clickhouse.query import quote_identifier
1010
from dbt.tests.util import check_relation_types, run_dbt
1111

12-
from tests.integration.adapter.helpers import below_version
13-
1412
PEOPLE_SEED_CSV = """
1513
id,name,age,department
1614
1231,Dade,33,engineering
@@ -175,10 +173,6 @@ def test_disabled_catchup(self, project):
175173
assert result[0][0] == 1
176174

177175

178-
@pytest.mark.skipif(
179-
below_version(24, 3),
180-
reason='Pending to fix. Syntax error in 23.8: Code: 53. DB::Exception: Cannot convert string VIEW to type UInt8: while executing...',
181-
)
182176
class TestUpdateMV:
183177
@pytest.fixture(scope="class")
184178
def seeds(self):

tests/integration/adapter/materialized_view/test_multiple_materialized_views.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from dbt.adapters.clickhouse.query import quote_identifier
1010
from dbt.tests.util import check_relation_types, run_dbt
1111

12-
from tests.integration.adapter.helpers import below_version
13-
1412
PEOPLE_SEED_CSV = """
1513
id,name,age,department
1614
1231,Dade,33,engineering
@@ -178,10 +176,6 @@ def test_create(self, project):
178176
]
179177

180178

181-
@pytest.mark.skipif(
182-
below_version(24, 3),
183-
reason='Pending to fix. Syntax error in 23.8: Code: 53. DB::Exception: Cannot convert string VIEW to type UInt8: while executing...',
184-
)
185179
class TestUpdateMultipleMV:
186180
@pytest.fixture(scope="class")
187181
def seeds(self):

tests/integration/adapter/materialized_view/test_refreshable_materialized_view.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import pytest
1010
from dbt.tests.util import check_relation_types, run_dbt
1111

12-
from tests.integration.adapter.helpers import below_version
13-
1412
PEOPLE_SEED_CSV = """
1513
id,name,age,department
1614
1231,Dade,33,engineering
@@ -58,10 +56,6 @@
5856
"""
5957

6058

61-
@pytest.mark.skipif(
62-
below_version(25),
63-
reason='Refreshable MVs are not supported in the tested 24 versions https://github.com/ClickHouse/ClickHouse/issues/59369#issuecomment-2047926705',
64-
)
6559
class TestBasicRefreshableMV:
6660
@pytest.fixture(scope="class")
6761
def seeds(self):

tests/integration/adapter/projections/test_projections.py

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
2-
import time
32
import uuid
43

54
import pytest
65
from dbt.tests.util import relation_from_name, run_dbt
76

7+
from tests.integration.adapter.helpers import DEFAULT_RETRY_CONFIG, retry_until_assertion_passes
8+
89
PEOPLE_SEED_CSV = """
910
id,name,age,department
1011
1231,Dade,33,engineering
@@ -66,7 +67,11 @@
6667
- name: people
6768
"""
6869

69-
SLEEP_TIME = 30 if os.environ.get('DBT_CH_TEST_CLOUD', '').lower() in ('1', 'true', 'yes') else 10
70+
RETRY_CONFIG = (
71+
{'max_retries': 30, 'delay': 1}
72+
if os.environ.get('DBT_CH_TEST_CLOUD', '').lower() in ('1', 'true', 'yes')
73+
else DEFAULT_RETRY_CONFIG
74+
)
7075

7176

7277
class TestProjections:
@@ -87,6 +92,18 @@ def models(self):
8792
% "table",
8893
}
8994

95+
def _get_table_reference(self, table: str) -> str:
96+
return (
97+
table
98+
if os.environ.get('DBT_CH_TEST_CLUSTER', '').strip() == ''
99+
else f"clusterAllReplicas({os.environ.get('DBT_CH_TEST_CLUSTER')}, {table})"
100+
)
101+
102+
def _flush_system_logs(self, project) -> None:
103+
cluster = os.environ.get('DBT_CH_TEST_CLUSTER', '').strip()
104+
cluster_clause = f'ON CLUSTER "{cluster}"' if cluster else ''
105+
project.run_sql(f"SYSTEM FLUSH LOGS {cluster_clause}", fetch="all")
106+
90107
def test_create_and_verify_projection(self, project):
91108
run_dbt(["seed"])
92109
run_dbt(["run", "--select", "people_with_projection"])
@@ -102,20 +119,23 @@ def test_create_and_verify_projection(self, project):
102119
assert len(result) == 3 # We expect 3 departments in the result
103120
assert result == [('engineering', 43.666666666666664), ('malware', 40.0), ('sales', 25.0)]
104121

105-
# waiting for system.log table to be created/populated
106-
time.sleep(SLEEP_TIME)
107-
108122
# check that the latest query used the projection
109-
result = project.run_sql(
110-
f"SELECT query, projections FROM clusterAllReplicas(default, 'system.query_log') "
111-
f"WHERE query like '%{unique_query_identifier}%' "
112-
f"and query not like '%clusterAllReplicas%' and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
113-
fetch="all",
123+
def check_that_the_latest_query_used_the_projection():
124+
self._flush_system_logs(project)
125+
result = project.run_sql(
126+
f"SELECT query, projections FROM {self._get_table_reference('system.query_log')} "
127+
f"WHERE query like '%{unique_query_identifier}%' "
128+
f"and query not like '%clusterAllReplicas%' and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
129+
fetch="all",
130+
)
131+
assert len(result) > 0
132+
assert query in result[0][0]
133+
134+
assert result[0][1] == [f'{project.test_schema}.{relation.name}.projection_avg_age']
135+
136+
retry_until_assertion_passes(
137+
check_that_the_latest_query_used_the_projection, **RETRY_CONFIG
114138
)
115-
assert len(result) > 0
116-
assert query in result[0][0]
117-
118-
assert result[0][1] == [f'{project.test_schema}.{relation.name}.projection_avg_age']
119139

120140
def test_create_and_verify_multiple_projections(self, project):
121141
run_dbt(["seed"])
@@ -134,20 +154,23 @@ def test_create_and_verify_multiple_projections(self, project):
134154
assert len(result) == 3 # We expect 3 departments in the result
135155
assert result == [('engineering', 43.666666666666664), ('malware', 40.0), ('sales', 25.0)]
136156

137-
# waiting for system.log table to be created/populated
138-
time.sleep(SLEEP_TIME)
139-
140157
# check that the latest query used the projection
141-
result = project.run_sql(
142-
f"SELECT query, projections FROM clusterAllReplicas(default, 'system.query_log') "
143-
f"WHERE query like '%{unique_query_identifier}%' "
144-
f"and query not like '%clusterAllReplicas%' and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
145-
fetch="all",
158+
def check_that_the_latest_query_used_the_projection():
159+
self._flush_system_logs(project)
160+
result = project.run_sql(
161+
f"SELECT query, projections FROM {self._get_table_reference('system.query_log')} "
162+
f"WHERE query like '%{unique_query_identifier}%' "
163+
f"and query not like '%clusterAllReplicas%' and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
164+
fetch="all",
165+
)
166+
assert len(result) > 0
167+
assert query in result[0][0]
168+
169+
assert result[0][1] == [f'{project.test_schema}.{relation.name}.projection_avg_age']
170+
171+
retry_until_assertion_passes(
172+
check_that_the_latest_query_used_the_projection, **RETRY_CONFIG
146173
)
147-
assert len(result) > 0
148-
assert query in result[0][0]
149-
150-
assert result[0][1] == [f'{project.test_schema}.{relation.name}.projection_avg_age']
151174

152175
# test the second projection
153176
unique_query_identifier = str(uuid.uuid4())
@@ -160,20 +183,22 @@ def test_create_and_verify_multiple_projections(self, project):
160183
assert len(result) == 3 # We expect 3 departments in the result
161184
assert result == [('engineering', 131), ('malware', 40), ('sales', 25)]
162185

163-
# waiting for system.log table to be created/populated
164-
time.sleep(SLEEP_TIME)
165-
166-
# check that the latest query used the projection
167-
result = project.run_sql(
168-
f"SELECT query, projections FROM clusterAllReplicas(default, 'system.query_log') "
169-
f"WHERE query like '%{unique_query_identifier}%' "
170-
f"and query not like '%clusterAllReplicas%' and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
171-
fetch="all",
186+
def check_that_the_latest_query_used_the_projection():
187+
self._flush_system_logs(project)
188+
result = project.run_sql(
189+
f"SELECT query, projections FROM {self._get_table_reference('system.query_log')} "
190+
f"WHERE query like '%{unique_query_identifier}%' "
191+
f"and query not like '%clusterAllReplicas%' and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
192+
fetch="all",
193+
)
194+
assert len(result) > 0
195+
assert query in result[0][0]
196+
197+
assert result[0][1] == [f'{project.test_schema}.{relation.name}.projection_sum_age']
198+
199+
retry_until_assertion_passes(
200+
check_that_the_latest_query_used_the_projection, **RETRY_CONFIG
172201
)
173-
assert len(result) > 0
174-
assert query in result[0][0]
175-
176-
assert result[0][1] == [f'{project.test_schema}.{relation.name}.projection_sum_age']
177202

178203
@pytest.mark.xfail
179204
@pytest.mark.skipif(
@@ -193,17 +218,21 @@ def test_create_and_verify_distributed_projection(self, project):
193218
assert len(result) == 3 # We expect 3 departments in the result
194219
assert result == [('engineering', 43.666666666666664), ('malware', 40.0), ('sales', 25.0)]
195220

196-
# waiting for system.log table to be created/populated
197-
time.sleep(SLEEP_TIME)
198-
199-
# check that the latest query used the projection
200-
result = project.run_sql(
201-
f"SELECT query, projections FROM clusterAllReplicas(default, 'system.query_log') "
202-
f"WHERE query like '%{unique_query_identifier}%' "
203-
f"and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
204-
fetch="all",
221+
def check_that_the_latest_query_used_the_projection():
222+
self._flush_system_logs(project)
223+
result = project.run_sql(
224+
f"SELECT query, projections FROM {self._get_table_reference('system.query_log')} "
225+
f"WHERE query like '%{unique_query_identifier}%' "
226+
f"and query not like '%system.query_log%' and read_rows > 0 ORDER BY query_start_time DESC",
227+
fetch="all",
228+
)
229+
assert len(result) > 0
230+
assert query in result[0][0]
231+
232+
assert result[0][1] == [
233+
f'{project.test_schema}.{relation.name}_local.projection_avg_age'
234+
]
235+
236+
retry_until_assertion_passes(
237+
check_that_the_latest_query_used_the_projection, **RETRY_CONFIG
205238
)
206-
assert len(result) > 0
207-
assert query in result[0][0]
208-
209-
assert result[0][1] == [f'{project.test_schema}.{relation.name}_local.projection_avg_age']

0 commit comments

Comments
 (0)