diff --git a/.gitignore b/.gitignore index 0b7f6bc..69e5915 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +.claude/ src/build/ +src/generated/ target/ .doit.db validation-report.xml diff --git a/src/docs/clickhouse.md b/src/docs/clickhouse.md new file mode 100644 index 0000000..bd2afdb --- /dev/null +++ b/src/docs/clickhouse.md @@ -0,0 +1,60 @@ +--- +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{} +--- + +{{ cross_reference|safe }} +# ClickHouse Driver {{ version }} + +{{ heading|safe }} + +This driver provides access to [ClickHouse][clickhouse], an open-source data warehouse and analytical database. + +## Installation + +The ClickHouse driver can be installed with [dbc](https://docs.columnar.tech/dbc): + +```bash +dbc install clickhouse +``` + +## Connecting + +To use the driver, provide the URI of a ClickHouse database as the `uri` option. + +```python +from adbc_driver_manager import dbapi + +conn = dbapi.connect( + driver="clickhouse", + db_kwargs={ + "uri": "http://localhost:8123/", + } +) +``` + +Note: The example above is for Python using the [adbc-driver-manager](https://pypi.org/project/adbc-driver-manager) package but the process will be similar for other driver managers. + +## Feature & Type Support + +{{ features|safe }} + +### Types + +{{ types|safe }} + +{{ footnotes|safe }} + +[clickhouse]: diff --git a/src/validation/CLAUDE.md b/src/validation/CLAUDE.md new file mode 100644 index 0000000..11c0b13 --- /dev/null +++ b/src/validation/CLAUDE.md @@ -0,0 +1,107 @@ + + +# Validation Tests Guide + +## Task + +Get validation tests passing using `pixi run validate`. Use `-k` to run a single test or pattern, e.g., `pixi run validate -k type/select/string`. + +## Handling Test Failures + +### Query-based tests (type/select, type/literal, type/bind, ingest) + +1. **Override queries**: If ClickHouse requires different SQL syntax, create override query files in `./validation/queries/` using the `.txtcase` format. + +2. **Mark as broken**: If a test cannot pass due to driver or vendor limitations, add to the metadata section: + - `[tags] broken-driver = "reason"` - for driver limitations + - `[tags] broken-vendor = "reason"` - for ClickHouse limitations + +3. **Hide tests**: Use `hide = true` in metadata to completely hide irrelevant tests. + +### Non-query tests (connection, statement) + +Subclass the test class and add `@pytest.mark.xfail(reason="...")` decorator to the method. + +## Override Query Format (.txtcase) + +Override files go in `./validation/queries/` mirroring the structure in `adbc-drivers-validation`. Only include the parts you need to override. + +``` +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// ... + +// part: metadata + +[setup] +drop = "test_tablename" + +[tags] +sql-type-name = "TYPE" + +// part: setup_query + +CREATE TABLE ... + +// part: query + +SELECT ... + +// part: expected_schema + +{ JSON schema } + +// part: expected + +{ JSON data rows } +``` + +Parts map to file extensions: +- `metadata` -> .toml +- `setup_query` -> .setup.sql +- `query` -> .sql +- `expected_schema` -> .schema.json +- `expected` -> .json +- `bind_query` -> .bind.sql +- `bind_schema` -> .bind.schema.json +- `bind` -> .bind.json + +## ClickHouse-Specific SQL Requirements + +1. **Nullable columns**: Use `Nullable(Type)` wrapper, e.g., `Nullable(Int32)` +2. **Table engine**: Add `ENGINE = MergeTree() ORDER BY idx` +3. **Types**: Use ClickHouse type names (Int32, Int64, Bool, etc.) + +## Workflow + +1. Run the specific test to see the failure: `pixi run validate -k "type/select/int32"` +2. Check expected data in `~/Code/adbc-drivers-validation/adbc_drivers_validation/queries/` +3. Create override .txtcase file with correct data matching expected output +4. Test the query after creating each file +5. Move to next failing test + +## Copyright Header + +All new files must include the Apache 2.0 license header with copyright year 2026: + +``` +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// ... +``` diff --git a/src/validation/pytest.ini b/src/validation/pytest.ini new file mode 100644 index 0000000..b958766 --- /dev/null +++ b/src/validation/pytest.ini @@ -0,0 +1,25 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[pytest] +junit_suite_name = validation +junit_duration_report = call +xfail_strict = true + +markers = + feature: test for a driver-specific feature + +filterwarnings = + error + ignore:record_property is incompatible with junit_family:pytest.PytestWarning diff --git a/src/validation/queries/type/literal/binary.txtcase b/src/validation/queries/type/literal/binary.txtcase new file mode 100644 index 0000000..fda44b3 --- /dev/null +++ b/src/validation/queries/type/literal/binary.txtcase @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +# Ignore this test: we need a SET statement and ClickHouse doesn't support +# multi-statement queries. Rely on type/select/binary instead. +hide = true diff --git a/src/validation/queries/type/literal/date.txtcase b/src/validation/queries/type/literal/date.txtcase new file mode 100644 index 0000000..2752d25 --- /dev/null +++ b/src/validation/queries/type/literal/date.txtcase @@ -0,0 +1,17 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: query + +SELECT CAST('2023-05-15' AS Date32) AS res diff --git a/src/validation/queries/type/literal/time.txtcase b/src/validation/queries/type/literal/time.txtcase new file mode 100644 index 0000000..4cafc5a --- /dev/null +++ b/src/validation/queries/type/literal/time.txtcase @@ -0,0 +1,22 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: query + +SELECT CAST('13:45:31' AS TIME) AS res + +// part: metadata + +[tags] +broken-vendor = "ClickHouse Time type is not supported in Arrow format export" diff --git a/src/validation/queries/type/literal/timestamp.txtcase b/src/validation/queries/type/literal/timestamp.txtcase new file mode 100644 index 0000000..ec2188f --- /dev/null +++ b/src/validation/queries/type/literal/timestamp.txtcase @@ -0,0 +1,39 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +caveats = ["ClickHouse datetime without timezone is interpreted in **server** timezone"] + +// part: query + +SELECT CAST('2023-05-15 13:45:30' AS DateTime64(6)) AS res + +// part: expected_schema + +{ + "format": "+s", + "children": [ + { + "name": "res", + "format": "tsu:UTC", + "flags": [] + } + ] +} + +// part: expected + +{"res": 1684158330000000} diff --git a/src/validation/queries/type/literal/timestamptz.txtcase b/src/validation/queries/type/literal/timestamptz.txtcase new file mode 100644 index 0000000..1f341f6 --- /dev/null +++ b/src/validation/queries/type/literal/timestamptz.txtcase @@ -0,0 +1,17 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: query + +SELECT CAST('2023-05-15 13:45:30' AS DateTime64(6, 'UTC')) AS res diff --git a/src/validation/queries/type/select/binary.txtcase b/src/validation/queries/type/select/binary.txtcase new file mode 100644 index 0000000..6049706 --- /dev/null +++ b/src/validation/queries/type/select/binary.txtcase @@ -0,0 +1,31 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +broken-vendor = "ClickHouse has no dedicated bytestring type; it is the same as the string type, and by default all strings are treated as Arrow strings, meaning binary data in ClickHouse results in invalid string data in Arrow" + +// part: setup_query + +CREATE TABLE test_binary ( + idx Int32, + res Nullable(String) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_binary (idx, res) VALUES (1, unhex('e38193e38293e381abe381a1e381afe38081e4b896e7958cefbc81')); +INSERT INTO test_binary (idx, res) VALUES (2, unhex('00')); +INSERT INTO test_binary (idx, res) VALUES (3, unhex('deadbeef')); +INSERT INTO test_binary (idx, res) VALUES (4, unhex('')); +INSERT INTO test_binary (idx, res) VALUES (5, NULL); diff --git a/src/validation/queries/type/select/boolean.txtcase b/src/validation/queries/type/select/boolean.txtcase new file mode 100644 index 0000000..d5f7300 --- /dev/null +++ b/src/validation/queries/type/select/boolean.txtcase @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_boolean ( + idx Int32, + res Nullable(Bool) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_boolean (idx, res) VALUES (1, TRUE); +INSERT INTO test_boolean (idx, res) VALUES (2, FALSE); +INSERT INTO test_boolean (idx, res) VALUES (3, NULL); diff --git a/src/validation/queries/type/select/date.txtcase b/src/validation/queries/type/select/date.txtcase new file mode 100644 index 0000000..9706f64 --- /dev/null +++ b/src/validation/queries/type/select/date.txtcase @@ -0,0 +1,39 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +caveats = ["ClickHouse Date32 has limited range (1900-01-01 to 2299-12-31)"] + +// part: setup_query + +CREATE TABLE test_date ( + idx Int32, + res Nullable(Date32) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_date (idx, res) VALUES (1, '2023-05-15'); +INSERT INTO test_date (idx, res) VALUES (2, '1900-01-01'); +INSERT INTO test_date (idx, res) VALUES (3, '1969-07-20'); +INSERT INTO test_date (idx, res) VALUES (4, '2299-12-31'); +INSERT INTO test_date (idx, res) VALUES (5, NULL); + +// part: expected + +{"res": 19492} +{"res": -25567} +{"res": -165} +{"res": 120529} +{"res": null} diff --git a/src/validation/queries/type/select/decimal.txtcase b/src/validation/queries/type/select/decimal.txtcase new file mode 100644 index 0000000..f23785f --- /dev/null +++ b/src/validation/queries/type/select/decimal.txtcase @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_decimal ( + idx Int32, + res Nullable(Decimal(10,2)) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_decimal (idx, res) VALUES (1, 123.45); +INSERT INTO test_decimal (idx, res) VALUES (2, 0.00); +INSERT INTO test_decimal (idx, res) VALUES (3, -999.99); +INSERT INTO test_decimal (idx, res) VALUES (4, 9999999.99); +INSERT INTO test_decimal (idx, res) VALUES (5, NULL); diff --git a/src/validation/queries/type/select/float32.txtcase b/src/validation/queries/type/select/float32.txtcase new file mode 100644 index 0000000..513d56d --- /dev/null +++ b/src/validation/queries/type/select/float32.txtcase @@ -0,0 +1,36 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_float32 ( + idx Int32, + res Nullable(Float32) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_float32 (idx, res) VALUES (1, 3.14); +INSERT INTO test_float32 (idx, res) VALUES (2, 0.0); +INSERT INTO test_float32 (idx, res) VALUES (3, -3.4e38); +INSERT INTO test_float32 (idx, res) VALUES (4, 3.4e38); +INSERT INTO test_float32 (idx, res) VALUES (5, 1.175494351e-38); +INSERT INTO test_float32 (idx, res) VALUES (6, NULL); + +// part: expected + +{"res": 3.14} +{"res": 0.0} +{"res": -3.4e38} +{"res": 3.4e38} +{"res": 1.1754942106924411e-38} +{"res": null} diff --git a/src/validation/queries/type/select/float64.txtcase b/src/validation/queries/type/select/float64.txtcase new file mode 100644 index 0000000..35763f9 --- /dev/null +++ b/src/validation/queries/type/select/float64.txtcase @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_float64 ( + idx Int32, + res Nullable(Float64) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_float64 (idx, res) VALUES (1, 3.14159265358979); +INSERT INTO test_float64 (idx, res) VALUES (2, 0.0); +INSERT INTO test_float64 (idx, res) VALUES (3, -1.7976931348623157e308); +INSERT INTO test_float64 (idx, res) VALUES (4, 1.7976931348623157e308); +INSERT INTO test_float64 (idx, res) VALUES (5, 2.2250738585072014e-308); +INSERT INTO test_float64 (idx, res) VALUES (6, NULL); diff --git a/src/validation/queries/type/select/int16.txtcase b/src/validation/queries/type/select/int16.txtcase new file mode 100644 index 0000000..669b226 --- /dev/null +++ b/src/validation/queries/type/select/int16.txtcase @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_int16 ( + idx Int32, + res Nullable(Int16) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_int16 (idx, res) VALUES (1, 16384); +INSERT INTO test_int16 (idx, res) VALUES (2, 32767); +INSERT INTO test_int16 (idx, res) VALUES (3, -32768); +INSERT INTO test_int16 (idx, res) VALUES (4, 0); +INSERT INTO test_int16 (idx, res) VALUES (5, NULL); diff --git a/src/validation/queries/type/select/int32.txtcase b/src/validation/queries/type/select/int32.txtcase new file mode 100644 index 0000000..c1cc5c8 --- /dev/null +++ b/src/validation/queries/type/select/int32.txtcase @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_int32 ( + idx Int32, + res Nullable(Int32) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_int32 (idx, res) VALUES (1, 131072); +INSERT INTO test_int32 (idx, res) VALUES (2, 2147483647); +INSERT INTO test_int32 (idx, res) VALUES (3, -2147483648); +INSERT INTO test_int32 (idx, res) VALUES (4, 0); +INSERT INTO test_int32 (idx, res) VALUES (5, NULL); diff --git a/src/validation/queries/type/select/int64.txtcase b/src/validation/queries/type/select/int64.txtcase new file mode 100644 index 0000000..0cb06d4 --- /dev/null +++ b/src/validation/queries/type/select/int64.txtcase @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: setup_query + +CREATE TABLE test_int64 ( + idx Int32, + res Nullable(Int64) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_int64 (idx, res) VALUES (1, 4294967296); +INSERT INTO test_int64 (idx, res) VALUES (2, 9223372036854775807); +INSERT INTO test_int64 (idx, res) VALUES (3, -9223372036854775808); +INSERT INTO test_int64 (idx, res) VALUES (4, 0); +INSERT INTO test_int64 (idx, res) VALUES (5, NULL); diff --git a/src/validation/queries/type/select/string.txtcase b/src/validation/queries/type/select/string.txtcase new file mode 100644 index 0000000..fda2db8 --- /dev/null +++ b/src/validation/queries/type/select/string.txtcase @@ -0,0 +1,31 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +caveats = ["Special characters like {} are not supported due to clickhouse crate parameter parsing"] + +// part: setup_query + +CREATE TABLE test_string ( + idx Int32, + res Nullable(String) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_string (idx, res) VALUES (1, 'hello'); +INSERT INTO test_string (idx, res) VALUES (2, ''); +INSERT INTO test_string (idx, res) VALUES (3, 'Special chars: !@#$%^&*()_+{}|:"<>??~`-=[]\;'',./'); +INSERT INTO test_string (idx, res) VALUES (4, 'Unicode: 你好, Привет, こんにちは, สวัสดี'); +INSERT INTO test_string (idx, res) VALUES (5, NULL); diff --git a/src/validation/queries/type/select/time.txtcase b/src/validation/queries/type/select/time.txtcase new file mode 100644 index 0000000..8ba88a5 --- /dev/null +++ b/src/validation/queries/type/select/time.txtcase @@ -0,0 +1,18 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +broken-vendor = "ClickHouse Time type is not supported in Arrow format export" diff --git a/src/validation/queries/type/select/timestamp.txtcase b/src/validation/queries/type/select/timestamp.txtcase new file mode 100644 index 0000000..7791b0d --- /dev/null +++ b/src/validation/queries/type/select/timestamp.txtcase @@ -0,0 +1,52 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +caveats = ["ClickHouse DateTime64(6) has limited range (1900 to 2299)"] + +// part: setup_query + +CREATE TABLE test_timestamp ( + idx Int32, + res Nullable(DateTime64(6)) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_timestamp (idx, res) VALUES (1, '2023-05-15 13:45:30'); +INSERT INTO test_timestamp (idx, res) VALUES (2, '2000-01-01 00:00:00'); +INSERT INTO test_timestamp (idx, res) VALUES (3, '1969-07-20 20:17:40'); +INSERT INTO test_timestamp (idx, res) VALUES (4, '2299-12-31 23:59:59'); +INSERT INTO test_timestamp (idx, res) VALUES (5, NULL); + +// part: expected_schema + +{ + "format": "+s", + "children": [ + { + "name": "res", + "format": "tsu:UTC", + "flags": ["nullable"] + } + ] +} + +// part: expected + +{"res": 1684158330000000} +{"res": 946684800000000} +{"res": -14182940000000} +{"res": 10413791999000000} +{"res": null} diff --git a/src/validation/queries/type/select/timestamptz.txtcase b/src/validation/queries/type/select/timestamptz.txtcase new file mode 100644 index 0000000..eadce74 --- /dev/null +++ b/src/validation/queries/type/select/timestamptz.txtcase @@ -0,0 +1,39 @@ +// Copyright (c) 2026 ADBC Drivers Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// part: metadata + +[tags] +caveats = ["ClickHouse DateTime64(6, 'UTC') has limited range (1900 to 2262)"] + +// part: setup_query + +CREATE TABLE test_timestamptz ( + idx Int32, + res Nullable(DateTime64(6, 'UTC')) +) ENGINE = MergeTree() ORDER BY idx; + +INSERT INTO test_timestamptz (idx, res) VALUES (1, '2023-05-15 13:45:30'); +INSERT INTO test_timestamptz (idx, res) VALUES (2, '2000-01-01 00:00:00'); +INSERT INTO test_timestamptz (idx, res) VALUES (3, '1969-07-20 20:17:40'); +INSERT INTO test_timestamptz (idx, res) VALUES (4, '2299-12-31 23:59:59'); +INSERT INTO test_timestamptz (idx, res) VALUES (5, NULL); + +// part: expected + +{"res": 1684158330000000} +{"res": 946684800000000} +{"res": -14182940000000} +{"res": 10413791999000000} +{"res": null} diff --git a/src/validation/tests/__init__.py b/src/validation/tests/__init__.py new file mode 100644 index 0000000..4afba9f --- /dev/null +++ b/src/validation/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/validation/tests/clickhouse.py b/src/validation/tests/clickhouse.py new file mode 100644 index 0000000..807a218 --- /dev/null +++ b/src/validation/tests/clickhouse.py @@ -0,0 +1,69 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +from adbc_drivers_validation import model, quirks + + +class ClickHouseQuirks(model.DriverQuirks): + name = "clickhouse" + driver = "adbc_driver_clickhouse" + driver_name = "ADBC Driver Foundry Driver for ClickHouse" + vendor_name = "ClickHouse" + vendor_version = "25.12" + short_version = "25.12" + features = model.DriverFeatures( + statement_bind=False, + current_catalog="", + current_schema="db", + supported_xdbc_fields=[], + ) + setup = model.DriverSetup( + database={"uri": "http://localhost:8123/"}, + connection={}, + statement={}, + ) + + @property + def queries_paths(self) -> tuple[Path]: + return (Path(__file__).parent.parent / "queries",) + + def is_table_not_found(self, table_name: str, error: Exception) -> bool: + return "Not found: Table" in str(error) and table_name in str(error) + + def quote_one_identifier(self, identifier: str) -> str: + return f"`{identifier}`" + + # @property + # def sample_ddl_constraints(self) -> list[str]: + # return [ + # "CREATE TABLE constraint_primary (z INT, a INT, b STRING, PRIMARY KEY (a) NOT ENFORCED)", + # "CREATE TABLE constraint_primary_multi (z INT, a INT, b STRING, PRIMARY KEY (b, a) NOT ENFORCED)", + # "CREATE TABLE constraint_primary_multi2 (z INT, a STRING, b INT, PRIMARY KEY (a, b) NOT ENFORCED)", + # "CREATE TABLE constraint_foreign (z INT, a INT, b INT, FOREIGN KEY (b) REFERENCES constraint_primary(a) NOT ENFORCED)", + # "CREATE TABLE constraint_foreign_multi (z INT, a INT, b INT, c STRING, FOREIGN KEY (c, b) REFERENCES constraint_primary_multi2(a, b) NOT ENFORCED)", + # # Ensure the driver doesn't misinterpret column IDs as indices + # "ALTER TABLE constraint_primary DROP COLUMN z", + # "ALTER TABLE constraint_primary_multi DROP COLUMN z", + # "ALTER TABLE constraint_primary_multi2 DROP COLUMN z", + # "ALTER TABLE constraint_foreign DROP COLUMN z", + # "ALTER TABLE constraint_foreign_multi DROP COLUMN z", + # ] + + def split_statement(self, statement: str) -> list[str]: + return quirks.split_statement(statement) + + +QUIRKS = [ClickHouseQuirks()] diff --git a/src/validation/tests/conftest.py b/src/validation/tests/conftest.py new file mode 100644 index 0000000..7fc327d --- /dev/null +++ b/src/validation/tests/conftest.py @@ -0,0 +1,47 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from pathlib import Path + +import adbc_drivers_validation.model +import pytest +from adbc_drivers_validation.tests.conftest import ( # noqa: F401 + conn, + conn_factory, + manual_test, + pytest_addoption, + pytest_collection_modifyitems, +) + +from .clickhouse import ClickHouseQuirks + + +@pytest.fixture(scope="session") +def driver(request) -> adbc_drivers_validation.model.DriverQuirks: + driver = request.param + assert driver.startswith("clickhouse") + return ClickHouseQuirks() + + +@pytest.fixture(scope="session") +def driver_path(driver: adbc_drivers_validation.model.DriverQuirks) -> str: + ext = { + "win32": "dll", + "darwin": "dylib", + }.get(sys.platform, "so") + return str( + Path(__file__).parent.parent.parent + / f"build/libadbc_driver_{driver.name}.{ext}" + ) diff --git a/src/validation/tests/generate_documentation.py b/src/validation/tests/generate_documentation.py new file mode 100644 index 0000000..bd0ab03 --- /dev/null +++ b/src/validation/tests/generate_documentation.py @@ -0,0 +1,34 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +from pathlib import Path + +import adbc_drivers_validation.generate_documentation as generate_documentation + +from . import clickhouse + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + template = Path(__file__).parent.parent.parent / "docs/clickhouse.md" + template = template.resolve() + + generate_documentation.generate( + clickhouse.QUIRKS, + Path("validation-report.xml").resolve(), + template, + args.output.resolve(), + ) diff --git a/src/validation/tests/test_connection.py b/src/validation/tests/test_connection.py new file mode 100644 index 0000000..9b5769b --- /dev/null +++ b/src/validation/tests/test_connection.py @@ -0,0 +1,133 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import adbc_drivers_validation.tests.connection as connection_tests +import pytest + +from . import clickhouse + + +def pytest_generate_tests(metafunc) -> None: + return connection_tests.generate_tests(clickhouse.QUIRKS, metafunc) + + +class TestConnection(connection_tests.TestConnection): + @pytest.mark.xfail(reason="get_option_string(CurrentCatalog) not implemented") + def test_current_catalog(self, driver, conn) -> None: + super().test_current_catalog(driver, conn) + + @pytest.mark.xfail(reason="get_option_string(CurrentSchema) not implemented") + def test_current_db_schema(self, driver, conn) -> None: + super().test_current_db_schema(driver, conn) + + @pytest.mark.xfail(reason="get_info() not implemented") + def test_get_info(self, driver, conn, record_property) -> None: + # TODO: un-hardcode this + record_property("driver_version", "v0.1.0-alpha") + with conn.cursor() as cursor: + version = cursor.execute("SELECT version()").fetchone()[0] + record_property("vendor_version", version) + record_property("short_version", driver.short_version) + + assert version == driver.vendor_version + super().test_get_info(driver, conn, record_property) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_catalog(self, conn, driver) -> None: + super().test_get_objects_catalog(conn, driver) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_schema(self, conn, driver) -> None: + super().test_get_objects_schema(conn, driver) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_table_not_exist(self, conn, driver) -> None: + super().test_get_objects_table_not_exist(conn, driver) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_table_present(self, conn, driver, get_objects_table) -> None: + super().test_get_objects_table_present(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_table_invalid_catalog( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_table_invalid_catalog(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_table_invalid_schema( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_table_invalid_schema(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_table_invalid_table( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_table_invalid_table(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_table_exact_table( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_table_exact_table(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_not_exist( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_column_not_exist(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_present(self, conn, driver, get_objects_table) -> None: + super().test_get_objects_column_present(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_filter_column_name( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_column_filter_column_name( + conn, driver, get_objects_table + ) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_filter_table_name( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_column_filter_table_name( + conn, driver, get_objects_table + ) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_filter_catalog( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_column_filter_catalog(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_filter_schema( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_column_filter_schema(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_filter_table( + self, conn, driver, get_objects_table + ) -> None: + super().test_get_objects_column_filter_table(conn, driver, get_objects_table) + + @pytest.mark.xfail(reason="get_objects() not implemented") + def test_get_objects_column_xdbc(self, conn, driver, get_objects_table) -> None: + super().test_get_objects_column_xdbc(conn, driver, get_objects_table) diff --git a/src/validation/tests/test_ingest.py b/src/validation/tests/test_ingest.py new file mode 100644 index 0000000..456499b --- /dev/null +++ b/src/validation/tests/test_ingest.py @@ -0,0 +1,27 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import adbc_drivers_validation.tests.ingest + +from . import clickhouse + + +def pytest_generate_tests(metafunc) -> None: + return adbc_drivers_validation.tests.ingest.generate_tests( + clickhouse.QUIRKS, metafunc + ) + + +class TestIngest(adbc_drivers_validation.tests.ingest.TestIngest): + pass diff --git a/src/validation/tests/test_query.py b/src/validation/tests/test_query.py new file mode 100644 index 0000000..d91fc7c --- /dev/null +++ b/src/validation/tests/test_query.py @@ -0,0 +1,25 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import adbc_drivers_validation.tests.query as query_tests + +from . import clickhouse + + +def pytest_generate_tests(metafunc) -> None: + return query_tests.generate_tests(clickhouse.QUIRKS, metafunc) + + +class TestQuery(query_tests.TestQuery): + pass diff --git a/src/validation/tests/test_statement.py b/src/validation/tests/test_statement.py new file mode 100644 index 0000000..91d981e --- /dev/null +++ b/src/validation/tests/test_statement.py @@ -0,0 +1,38 @@ +# Copyright (c) 2026 ADBC Drivers Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import adbc_drivers_validation.tests.statement as statement_tests +import pytest + +from . import clickhouse + + +def pytest_generate_tests(metafunc) -> None: + return statement_tests.generate_tests(clickhouse.QUIRKS, metafunc) + + +class TestStatement(statement_tests.TestStatement): + @pytest.mark.xfail(reason="prepare() not implemented") + def test_prepare(self, driver, conn) -> None: + super().test_prepare(driver, conn) + + @pytest.mark.xfail(reason="prepare() not implemented") + def test_parameter_execute(self, driver, conn) -> None: + super().test_parameter_execute(driver, conn) + + @pytest.mark.xfail( + reason="ClickHouse lightweight updates require special table settings" + ) + def test_rows_affected(self, driver, conn) -> None: + super().test_rows_affected(driver, conn)