Skip to content

Commit 478060d

Browse files
improve tests and refactor
1 parent 6c85c24 commit 478060d

File tree

2 files changed

+71
-44
lines changed

2 files changed

+71
-44
lines changed

clickhouse_connect/driver/httpclient.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,6 @@ def __init__(self,
197197
self._setting_status('http_headers_progress_interval_ms').is_writable:
198198
self._progress_interval = str(min(120000, max(10000, (send_receive_timeout - 5) * 1000)))
199199

200-
def _ensure_query_id(self, settings: Optional[dict]) -> Optional[dict]:
201-
if not self._autogenerate_query_id:
202-
return settings
203-
204-
if settings is None:
205-
settings = {}
206-
207-
if "query_id" not in settings:
208-
settings["query_id"] = str(uuid.uuid4())
209-
210-
return settings
211-
212200
def set_client_setting(self, key, value):
213201
str_value = self._validate_setting(key, value, common.get_setting('invalid_setting_action'))
214202
if str_value is not None:
@@ -233,8 +221,6 @@ def _prep_query(self, context: QueryContext):
233221
return final_query + fmt
234222

235223
def _query_with_context(self, context: QueryContext) -> QueryResult:
236-
context.settings = self._ensure_query_id(context.settings)
237-
238224
headers = {}
239225
params = {}
240226
if self.database:
@@ -327,8 +313,6 @@ def data_insert(self, context: InsertContext) -> QuerySummary:
327313
logger.debug('No data included in insert, skipping')
328314
return QuerySummary()
329315

330-
context.settings = self._ensure_query_id(context.settings)
331-
332316
def error_handler(resp: HTTPResponse):
333317
# If we actually had a local exception when building the insert, throw that instead
334318
if context.insert_exception:
@@ -364,8 +348,6 @@ def raw_insert(self, table: str = None,
364348
"""
365349
See BaseClient doc_string for this method
366350
"""
367-
settings = self._ensure_query_id(settings)
368-
369351
params = {}
370352
headers = {'Content-Type': 'application/octet-stream'}
371353
if compression:
@@ -409,8 +391,6 @@ def command(self,
409391
"""
410392
See BaseClient doc_string for this method
411393
"""
412-
settings = self._ensure_query_id(settings)
413-
414394
cmd, params = bind_query(cmd, parameters, self.server_tz)
415395
headers = {}
416396
payload = None
@@ -521,6 +501,10 @@ def _raw_request(self,
521501
final_params['http_headers_progress_interval_ms'] = self._progress_interval
522502
final_params = dict_copy(self.params, final_params)
523503
final_params = dict_copy(final_params, params)
504+
505+
if self._autogenerate_query_id and "query_id" not in final_params:
506+
final_params["query_id"] = str(uuid.uuid4())
507+
524508
url = f'{self.url}?{urlencode(final_params)}'
525509
kwargs = {
526510
'headers': headers,

tests/integration_tests/test_client.py

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
klm,1,"""
2121

2222

23+
def _is_valid_uuid_v4(id_string: str) -> bool:
24+
"""Helper function to validate that a string is a valid UUID v4"""
25+
try:
26+
parsed_uuid = uuid.UUID(id_string)
27+
return parsed_uuid.version == 4
28+
except (ValueError, AttributeError):
29+
return False
30+
31+
2332
def test_ping(test_client: Client):
2433
assert test_client.ping() is True
2534

@@ -388,41 +397,75 @@ def test_role_setting_works(test_client: Client, test_config: TestConfig):
388397
assert res.result_rows == [([role_limited],)]
389398

390399

391-
# pylint: disable=protected-access
392-
def test_autogenerate_query_id(test_client: Client, test_table_engine: str, test_config: TestConfig):
393-
def _is_valid_uuid_v4_string(id_string: str) -> bool:
394-
parsed_uuid = uuid.UUID(id_string)
395-
return parsed_uuid.version == 4
396-
400+
def test_query_id_autogeneration(test_client: Client, test_table_engine: str):
401+
"""Test that query_id is auto-generated for query(), command(), and insert() methods"""
397402
result = test_client.query("SELECT 1")
398-
assert _is_valid_uuid_v4_string(result.query_id)
403+
assert _is_valid_uuid_v4(result.query_id)
399404

400-
manual_query_id = "test_manual_query_id_12345"
401-
result = test_client.query("SELECT 2", settings={"query_id": manual_query_id})
402-
assert result.query_id == manual_query_id
405+
summary = test_client.command("DROP TABLE IF EXISTS test_query_id_nonexistent")
406+
assert _is_valid_uuid_v4(summary.query_id())
403407

404-
summary = test_client.command("DROP TABLE IF EXISTS does_not_exist")
405-
assert _is_valid_uuid_v4_string(summary.query_id())
408+
test_client.command("DROP TABLE IF EXISTS test_query_id_insert")
409+
test_client.command(f"CREATE TABLE test_query_id_insert (id UInt32) ENGINE {test_table_engine} ORDER BY id")
410+
summary = test_client.insert("test_query_id_insert", [[1], [2], [3]], column_names=["id"])
411+
assert _is_valid_uuid_v4(summary.query_id())
412+
test_client.command("DROP TABLE test_query_id_insert")
413+
414+
415+
def test_query_id_manual_override(test_client: Client):
416+
"""Test that manually specified query_id is respected and not overwritten"""
417+
manual_query_id = "test_manual_query_id_override"
418+
result = test_client.query("SELECT 1", settings={"query_id": manual_query_id})
419+
assert result.query_id == manual_query_id
406420

407-
test_client.command("DROP TABLE IF EXISTS test_autogen_query_id")
408-
test_client.command(f"CREATE TABLE test_autogen_query_id (id UInt32) ENGINE {test_table_engine} ORDER BY id")
409-
summary = test_client.insert("test_autogen_query_id", [[1], [2], [3]], column_names=["id"])
410-
assert _is_valid_uuid_v4_string(summary.query_id())
411-
test_client.command("DROP TABLE test_autogen_query_id")
412421

413-
# Create client with autogenerate_query_id disabled
422+
# pylint: disable=protected-access
423+
def test_query_id_disabled(test_config: TestConfig):
424+
"""Test that autogenerate_query_id=False works correctly"""
414425
client_no_autogen = create_client(
415426
host=test_config.host,
416427
port=test_config.port,
417428
username=test_config.username,
418429
password=test_config.password,
419430
autogenerate_query_id=False,
420431
)
421-
result = client_no_autogen.query("SELECT 4")
422-
# Even with autogen disabled the server generates a query_id
423-
# so we still expect a query_id in the result, it's just generated by the server...
424-
# not sure how to verify that specifically though.
425-
assert _is_valid_uuid_v4_string(result.query_id)
426432

427-
# We can verify that the setting on the client is False though
428433
assert client_no_autogen._autogenerate_query_id is False
434+
435+
# Even with autogen disabled, server generates a query_id
436+
result = client_no_autogen.query("SELECT 1")
437+
assert _is_valid_uuid_v4(result.query_id)
438+
439+
client_no_autogen.close()
440+
441+
442+
def test_query_id_in_query_logs(test_client: Client, test_config: TestConfig):
443+
"""Test that query_id appears in ClickHouse's system.query_log for observability"""
444+
if test_config.cloud:
445+
pytest.skip("Skipping query_log test in cloud environment")
446+
447+
def check_in_logs(test_query_id):
448+
max_retries = 30
449+
for _ in range(max_retries):
450+
log_result = test_client.query(
451+
f"SELECT query_id FROM system.query_log WHERE query_id = '{test_query_id}' AND " "event_time > now() - 30 LIMIT 1"
452+
)
453+
454+
if len(log_result.result_set) > 0:
455+
assert log_result.result_set[0][0] == test_query_id
456+
return
457+
458+
sleep(0.1)
459+
460+
# If we get here, query_id never appeared in logs
461+
pytest.fail(f"query_id '{test_query_id}' did not appear in system.query_log after {max_retries * 0.1}s")
462+
463+
# Manual override check
464+
test_query_id_manual = "test_query_id_in_logs"
465+
test_client.query("SELECT 1 as num", settings={"query_id": test_query_id_manual})
466+
check_in_logs(test_query_id_manual)
467+
468+
# Autogen check
469+
result = test_client.query("SELECT 2 as num")
470+
test_query_id_auto = result.query_id
471+
check_in_logs(test_query_id_auto)

0 commit comments

Comments
 (0)