Skip to content

Commit 31cc7ba

Browse files
committed
feat(clickhouse): properly support native boolean types
1 parent d44978c commit 31cc7ba

File tree

7 files changed

+36
-11
lines changed

7 files changed

+36
-11
lines changed

ibis/backends/clickhouse/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ class Options(ibis.config.Config):
8181
----------
8282
temp_db : str
8383
Database to use for temporary objects.
84+
bool_type : str
85+
Type to use for boolean columns
8486
"""
8587

8688
temp_db: str = "__ibis_tmp"
89+
bool_type: str = "Boolean"
8790

8891
def __init__(self, *args, external_tables=None, **kwargs):
8992
super().__init__(*args, **kwargs)

ibis/backends/clickhouse/datatypes.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import parsy
66

7+
import ibis
78
import ibis.expr.datatypes as dt
89
from ibis.common.parsing import (
910
COMMA,
@@ -19,6 +20,10 @@
1920
)
2021

2122

23+
def _bool_type():
24+
return getattr(getattr(ibis.options, "clickhouse", None), "bool_type", "Boolean")
25+
26+
2227
def parse(text: str) -> dt.DataType:
2328
@parsy.generate
2429
def datetime():
@@ -35,8 +40,9 @@ def datetime():
3540
| spaceless_string("smallint", "int16", "int2").result(dt.Int16(nullable=False))
3641
| spaceless_string("date32", "date").result(dt.Date(nullable=False))
3742
| spaceless_string("time").result(dt.Time(nullable=False))
38-
| spaceless_string("tinyint", "int8", "int1", "boolean", "bool").result(
39-
dt.Int8(nullable=False)
43+
| spaceless_string("tinyint", "int8", "int1").result(dt.Int8(nullable=False))
44+
| spaceless_string("boolean", "bool").result(
45+
getattr(dt, _bool_type())(nullable=False)
4046
)
4147
| spaceless_string("integer", "int32", "int4", "int").result(
4248
dt.Int32(nullable=False)
@@ -223,6 +229,11 @@ def _(ty: dt.DataType) -> str:
223229
return type(ty).__name__.capitalize()
224230

225231

232+
@serialize_raw.register(dt.Boolean)
233+
def _(_: dt.Boolean) -> str:
234+
return _bool_type()
235+
236+
226237
@serialize_raw.register(dt.Array)
227238
def _(ty: dt.Array) -> str:
228239
return f"Array({serialize(ty.value_type)})"

ibis/backends/clickhouse/tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ class TestConf(UnorderedComparator, BackendTest, RoundHalfToEven):
2323
returned_timestamp_unit = 's'
2424
supported_to_timestamp_units = {'s'}
2525
supports_floating_modulus = False
26-
bool_is_int = True
2726
supports_json = False
2827

28+
@property
29+
def native_bool(self) -> bool:
30+
[(value,)] = self.connection._client.execute("SELECT true")
31+
return isinstance(value, bool)
32+
2933
@staticmethod
3034
def _load_data(
3135
data_dir: Path,

ibis/backends/clickhouse/tests/test_operators.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pandas as pd
66
import pandas.testing as tm
77
import pytest
8+
from pytest import param
89

910
import ibis
1011
import ibis.expr.datatypes as dt
@@ -156,11 +157,17 @@ def test_field_in_literals(con, alltypes, translate, container):
156157
assert len(con.execute(expr))
157158

158159

159-
@pytest.mark.parametrize('column', ['int_col', 'float_col', 'bool_col'])
160-
def test_negate(con, alltypes, translate, column):
161-
# clickhouse represent boolean as UInt8
160+
@pytest.mark.parametrize(
161+
("column", "operator"),
162+
[
163+
param("int_col", "-", id="int_col"),
164+
param("float_col", "-", id="float_col"),
165+
param("bool_col", "NOT ", id="bool_col"),
166+
],
167+
)
168+
def test_negate(con, alltypes, translate, column, operator):
162169
expr = -alltypes[column]
163-
assert translate(expr.op()) == f'-{column}'
170+
assert translate(expr.op()) == f"{operator}{column}"
164171
assert len(con.execute(expr))
165172

166173

ibis/backends/datafusion/tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class TestConf(BackendTest, RoundAwayFromZero):
1616
# additional_skipped_operations = frozenset({ops.StringSQLLike})
1717
# supports_divide_by_zero = True
1818
# returned_timestamp_unit = 'ns'
19-
bool_is_int = True
19+
native_bool = False
2020
supports_structs = False
2121
supports_json = False
2222

ibis/backends/mysql/tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class TestConf(BackendTest, RoundHalfToEven):
2626
returned_timestamp_unit = 's'
2727
supports_arrays = False
2828
supports_arrays_outside_of_select = supports_arrays
29-
bool_is_int = True
29+
native_bool = False
3030
supports_structs = False
3131

3232
def __init__(self, data_directory: Path) -> None:

ibis/backends/tests/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class BackendTest(abc.ABC):
7272
returned_timestamp_unit = 'us'
7373
supported_to_timestamp_units = {'s', 'ms', 'us'}
7474
supports_floating_modulus = True
75-
bool_is_int = False
75+
native_bool = True
7676
supports_structs = True
7777
supports_json = True
7878
reduction_tolerance = 1e-7
@@ -163,7 +163,7 @@ def least(f: Callable[..., ir.Value], *args: ir.Value) -> ir.Value:
163163
@property
164164
def functional_alltypes(self) -> ir.Table:
165165
t = self.connection.table('functional_alltypes')
166-
if self.bool_is_int:
166+
if not self.native_bool:
167167
return t.mutate(bool_col=t.bool_col == 1)
168168
return t
169169

0 commit comments

Comments
 (0)