diff --git a/CHANGELOG.md b/CHANGELOG.md index 8791ec46..7236166b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ In any case, this should not affect the basic usage of Superset with ClickHouse. your Superset installation, the ClickHouse datasource will be available with either the enhanced connection dialog or a standard SqlAlchemy DSN in the form of `clickhousedb://{username}:{password}@{host}:{port}`. +## 0.6.14, TBD +### Bug Fixes +- Fixed insert error when inserting a zero length string into a FixedString column. Closes https://github.com/ClickHouse/clickhouse-connect/issues/244 +- Removed unnecessary validate_entrypoints import from top level package __init__ that was breaking Python 3.7. Note that Python 3.7 is EOL +and will no longer be supported as of January 1, 2024. + ## 0.6.13, 2023-09-20 ### Bug Fix - Fixed an issue with the automatic retry of "connection reset errors". This should prevent exceptions when the @@ -32,7 +38,7 @@ to [Ashton Hudson](https://github.com/CaptainCuddleCube) for the report and the ### Bug fixes - Inserts using Pandas 2.1 would fail due to a removed method in the Pandas library. There is now a workaround/fix for this. Closes https://github.com/ClickHouse/clickhouse-connect/issues/234 -- Inserts into a FixedString column that were not the expected size could cause corrupt insert blocksd and mysterious errors +- Inserts into a FixedString column that were not the expected size could cause corrupt insert blocks and mysterious errors from the ClickHouse server. Validation has been added so that more meaningful error messages are generated if a fixed string value is an invalid size. A reminder that strings which are "too short" for a FixedString column will be padded with 0 bytes, while strings that are "too long" will generate an exception during the insert. diff --git a/clickhouse_connect/__init__.py b/clickhouse_connect/__init__.py index 6db9a9f4..9bd95e85 100644 --- a/clickhouse_connect/__init__.py +++ b/clickhouse_connect/__init__.py @@ -1,12 +1,8 @@ from clickhouse_connect.driver import create_client -from clickhouse_connect.entry_points import validate_entrypoints + driver_name = 'clickhousedb' def get_client(**kwargs): return create_client(**kwargs) - - -def check_ep(): - assert validate_entrypoints() == 0 diff --git a/clickhouse_connect/__version__.py b/clickhouse_connect/__version__.py index 2216bf56..10123bef 100644 --- a/clickhouse_connect/__version__.py +++ b/clickhouse_connect/__version__.py @@ -1 +1 @@ -version = '0.6.13' +version = '0.6.14' diff --git a/clickhouse_connect/datatypes/string.py b/clickhouse_connect/datatypes/string.py index 82bf5a70..7f6b7a5a 100644 --- a/clickhouse_connect/datatypes/string.py +++ b/clickhouse_connect/datatypes/string.py @@ -101,8 +101,7 @@ def _write_column_binary(self, column: Union[Sequence, MutableSequence], dest: b if len(b) > sz: raise DataError(f'UTF-8 encoded FixedString value {b.hex(" ")} exceeds column size {sz}') ext(b) - if len(b) < sz: - ext(empty[:-len(b)]) + ext(empty[:sz - len(b)]) else: for x in column: try: @@ -112,8 +111,7 @@ def _write_column_binary(self, column: Union[Sequence, MutableSequence], dest: b if len(b) > sz: raise DataError(f'UTF-8 encoded FixedString value {b.hex(" ")} exceeds column size {sz}') ext(b) - if len(b) < sz: - ext(empty[:-len(b)]) + ext(empty[:sz - len(b)]) elif self.nullable: for b in column: if not b: diff --git a/examples/write_perf.py b/examples/write_perf.py index e044f82a..71155a9f 100755 --- a/examples/write_perf.py +++ b/examples/write_perf.py @@ -1,8 +1,9 @@ #!/usr/bin/env python -u +# pylint: disable=import-error import time import random -import clickhouse_driver # pylint: disable=import-error +import clickhouse_driver import clickhouse_connect from clickhouse_connect.tools.testing import TableContext diff --git a/tests/integration_tests/test_native.py b/tests/integration_tests/test_native.py index 5e97087b..81a0f73d 100644 --- a/tests/integration_tests/test_native.py +++ b/tests/integration_tests/test_native.py @@ -187,3 +187,13 @@ def test_decimal_rounding(test_client: Client, table_context: Callable): def test_empty_maps(test_client: Client): result = test_client.query("select Cast(([],[]), 'Map(String, Map(String, String))')") assert result.first_row[0] == {} + + +def test_fixed_str_padding(test_client: Client, table_context: Callable): + table = 'test_fixed_str_padding' + with table_context(table, 'key Int32, value FixedString(3)'): + test_client.insert(table, [[1, 'abc']]) + test_client.insert(table, [[2, 'a']]) + test_client.insert(table, [[3, '']]) + result = test_client.query(f'select * from {table} ORDER BY key') + assert result.result_columns[1] == [b'abc', b'a\x00\x00', b'\x00\x00\x00']