Files
driver/tests/test_blocks.py
Kim GustyrandGitHub 4a8514c68e test: Fix flaky coverage (#506)
* Make columnar block-extension test deterministic

test_columnar_block_extend relied on its two single-row inserts
reaching the client as two blocks. A background merge between the
inserts and the select lets the server send one block, silently
skipping the column-extension path in QueryResult.store. Force one
row per block with max_block_size so the path is always exercised.

beep boop

* Cover sparse serialization without relying on the server

Whether a block arrives in sparse form is the server's choice and
varies by version and run, so the integration tests cover
SparseSerialization only when the server cooperates. Feed read_sparse
and apply_sparse crafted buffers directly so every line is exercised
deterministically on every job.

beep boop
2026-07-14 19:34:31 +01:00

260 lines
9.6 KiB
Python

import types
from unittest.mock import patch
from clickhouse_driver.errors import ServerException
from tests.testcase import BaseTestCase, file_config
from tests.util import capture_logging
from clickhouse_driver.util.helpers import chunks
class BlocksTestCase(BaseTestCase):
def test_return_totals_extremes(self):
rv = self.client.execute(
'SELECT a, sum(b + a) FROM ('
'SELECT arrayJoin(range(3)) - 1 AS a,'
'arrayJoin(range(4)) AS b'
') AS t '
'GROUP BY a WITH TOTALS '
'ORDER BY a',
settings={'extremes': 1}
)
self.assertEqual(rv, [
(-1, 2),
(0, 6),
(1, 10),
# TOTALS
(0, 18),
# EXTREMES
(-1, 2),
(1, 10)
])
def test_columnar_result(self):
rv = self.client.execute(
'SELECT a, sum(b + a) FROM ('
'SELECT arrayJoin(range(3)) - 1 AS a,'
'arrayJoin(range(4)) AS b'
') AS t '
'GROUP BY a '
'ORDER BY a',
columnar=True
)
self.assertEqual(rv, [
(-1, 0, 1),
(2, 6, 10)
])
def test_columnar_block_extend(self):
with self.create_table('a Int32'):
self.client.execute('INSERT INTO test (a) VALUES', [(1, )])
self.client.execute('INSERT INTO test (a) VALUES', [(2, )])
query = 'SELECT * FROM test ORDER BY a'
# One row per block even if the two parts get merged.
inserted = self.client.execute(
query, columnar=True, settings={'max_block_size': 1}
)
self.assertEqual(inserted, [(1, 2)])
def test_select_with_column_types(self):
rv = self.client.execute(
'SELECT CAST(1 AS Int32) AS x', with_column_types=True
)
self.assertEqual(rv, ([(1,)], [('x', 'Int32')]))
def test_select_with_columnar_with_column_types(self):
progress = self.client.execute_with_progress(
'SELECT arrayJoin(A) -1 as j,'
'arrayJoin(A)+1 as k FROM('
'SELECT range(3) as A)',
columnar=True, with_column_types=True)
rv = ([(-1, 0, 1), (1, 2, 3)], [('j', 'Int16'), ('k', 'UInt16')])
self.assertEqual(progress.get_result(), rv)
def test_close_connection_on_keyboard_interrupt(self):
connection = self.client.connection
with self.assertRaises(KeyboardInterrupt):
with patch.object(connection, 'send_query') as mocked_send_query:
mocked_send_query.side_effect = KeyboardInterrupt
self.client.execute('SELECT 1')
self.assertFalse(self.client.connection.connected)
class ProgressTestCase(BaseTestCase):
def test_select_with_progress(self):
progress = self.client.execute_with_progress('SELECT 2')
# CH 25 reports 1 byte per progress chunk for trivial selects;
# earlier versions reported 0.
if self.server_version >= (25,):
expected = [(1, 1), (1, 1)]
elif self.server_version > (20,):
expected = [(1, 0), (1, 0)]
else:
expected = [(1, 0)]
self.assertEqual(list(progress), expected)
self.assertEqual(progress.get_result(), [(2,)])
self.assertTrue(self.client.connection.connected)
def test_progress_totals(self):
progress = self.client.execute_with_progress('SELECT 2')
self.assertEqual(progress.progress_totals.rows, 0)
self.assertEqual(progress.progress_totals.bytes, 0)
self.assertEqual(progress.progress_totals.total_rows, 0)
self.assertEqual(progress.get_result(), [(2,)])
self.assertEqual(progress.progress_totals.rows, 1)
self.assertEqual(progress.progress_totals.bytes, 1)
# CH 25 sets total_rows for trivial selects to 1; earlier
# versions left it at 0.
expected_total_rows = 1 if self.server_version >= (25,) else 0
self.assertEqual(
progress.progress_totals.total_rows, expected_total_rows)
def test_select_with_progress_error(self):
with self.assertRaises(ServerException):
progress = self.client.execute_with_progress('SELECT error')
list(progress)
self.assertFalse(self.client.connection.connected)
def test_select_with_progress_no_progress_unwind(self):
progress = self.client.execute_with_progress('SELECT 2')
self.assertEqual(progress.get_result(), [(2,)])
self.assertTrue(self.client.connection.connected)
def test_select_with_progress_cancel(self):
self.client.execute_with_progress('SELECT 2')
rv = self.client.cancel()
self.assertEqual(rv, [(2,)])
self.assertTrue(self.client.connection.connected)
def test_select_with_progress_cancel_with_column_types(self):
self.client.execute_with_progress('SELECT CAST(2 AS Int32) as x')
rv = self.client.cancel(with_column_types=True)
self.assertEqual(rv, ([(2,)], [('x', 'Int32')]))
self.assertTrue(self.client.connection.connected)
def test_select_with_progress_with_params(self):
progress = self.client.execute_with_progress(
'SELECT %(x)s', params={'x': 2}
)
self.assertEqual(progress.get_result(), [(2,)])
self.assertTrue(self.client.connection.connected)
def test_close_connection_on_keyboard_interrupt(self):
connection = self.client.connection
with self.assertRaises(KeyboardInterrupt):
with patch.object(connection, 'send_query') as mocked_send_query:
mocked_send_query.side_effect = KeyboardInterrupt
self.client.execute_with_progress('SELECT 1')
self.assertFalse(self.client.connection.connected)
class IteratorTestCase(BaseTestCase):
def test_select_with_iter(self):
result = self.client.execute_iter(
'SELECT number FROM system.numbers LIMIT 10'
)
self.assertIsInstance(result, types.GeneratorType)
self.assertEqual(list(result), list(zip(range(10))))
self.assertEqual(list(result), [])
def test_select_with_chunk_one_iter(self):
result = self.client.execute_iter(
'SELECT number FROM system.numbers LIMIT 10',
chunk_size=1
)
self.assertIsInstance(result, types.GeneratorType)
self.assertEqual(list(result), list(zip(range(10))))
self.assertEqual(list(result), [])
def test_select_with_chunk_some_iter(self):
result = self.client.execute_iter(
'SELECT number FROM system.numbers LIMIT 10',
chunk_size=3
)
self.assertIsInstance(result, types.GeneratorType)
self.assertEqual(list(result), list(chunks(zip(range(10)), 3)))
self.assertEqual(list(result), [])
def test_select_with_iter_with_column_types(self):
result = self.client.execute_iter(
'SELECT CAST(number AS UInt32) as number '
'FROM system.numbers LIMIT 10',
with_column_types=True
)
self.assertIsInstance(result, types.GeneratorType)
self.assertEqual(
list(result),
[[('number', 'UInt32')]] + list(zip(range(10)))
)
self.assertEqual(list(result), [])
def test_select_with_iter_error(self):
with self.assertRaises(ServerException):
result = self.client.execute_iter('SELECT error')
self.assertIsInstance(result, types.GeneratorType)
list(result)
self.assertFalse(self.client.connection.connected)
def test_close_connection_on_keyboard_interrupt(self):
connection = self.client.connection
with self.assertRaises(KeyboardInterrupt):
with patch.object(connection, 'send_query') as mocked_send_query:
mocked_send_query.side_effect = KeyboardInterrupt
self.client.execute_iter('SELECT 1')
self.assertFalse(self.client.connection.connected)
class LogTestCase(BaseTestCase):
required_server_version = (18, 12, 13)
def test_logs(self):
with capture_logging('clickhouse_driver.log', 'INFO') as buffer:
settings = {'send_logs_level': 'debug'}
query = 'SELECT 1'
self.client.execute(query, settings=settings)
self.assertIn(query, buffer.getvalue())
def test_logs_insert(self):
with capture_logging('clickhouse_driver.log', 'INFO') as buffer:
with self.create_table('a Int32'):
settings = {'send_logs_level': 'debug'}
query = 'INSERT INTO test (a) VALUES'
self.client.execute(query, [(1, )], settings=settings)
logs = buffer.getvalue()
self.assertIn(query, logs)
# Test all packets of INSERT query are consumed.
rv = self.client.execute('SELECT 1', settings=settings)
self.assertEqual(rv, [(1, )])
def test_logs_with_compression(self):
compression = 'lz4'
supported_compressions = (
file_config.get('db', 'compression').split(',')
)
if compression not in supported_compressions:
self.skipTest(
'Compression {} is not supported'.format(compression)
)
with self.created_client(compression='lz4') as client:
with capture_logging('clickhouse_driver.log', 'INFO') as buffer:
settings = {'send_logs_level': 'debug'}
query = 'SELECT 1'
client.execute(query, settings=settings)
self.assertIn(query, buffer.getvalue())