From a7644843ad2aea494eb6244f7e66809ef5f68464 Mon Sep 17 00:00:00 2001
From: David Li
Date: Wed, 4 Feb 2026 10:26:47 +0900
Subject: [PATCH] test: add validation suite
---
.gitignore | 2 +
src/docs/clickhouse.md | 60 ++++++++
src/validation/CLAUDE.md | 107 ++++++++++++++
src/validation/pytest.ini | 25 ++++
.../queries/type/literal/binary.txtcase | 19 +++
.../queries/type/literal/date.txtcase | 17 +++
.../queries/type/literal/time.txtcase | 22 +++
.../queries/type/literal/timestamp.txtcase | 39 +++++
.../queries/type/literal/timestamptz.txtcase | 17 +++
.../queries/type/select/binary.txtcase | 31 ++++
.../queries/type/select/boolean.txtcase | 24 ++++
.../queries/type/select/date.txtcase | 39 +++++
.../queries/type/select/decimal.txtcase | 26 ++++
.../queries/type/select/float32.txtcase | 36 +++++
.../queries/type/select/float64.txtcase | 27 ++++
.../queries/type/select/int16.txtcase | 26 ++++
.../queries/type/select/int32.txtcase | 26 ++++
.../queries/type/select/int64.txtcase | 26 ++++
.../queries/type/select/string.txtcase | 31 ++++
.../queries/type/select/time.txtcase | 18 +++
.../queries/type/select/timestamp.txtcase | 52 +++++++
.../queries/type/select/timestamptz.txtcase | 39 +++++
src/validation/tests/__init__.py | 13 ++
src/validation/tests/clickhouse.py | 69 +++++++++
src/validation/tests/conftest.py | 47 +++++++
.../tests/generate_documentation.py | 34 +++++
src/validation/tests/test_connection.py | 133 ++++++++++++++++++
src/validation/tests/test_ingest.py | 27 ++++
src/validation/tests/test_query.py | 25 ++++
src/validation/tests/test_statement.py | 38 +++++
30 files changed, 1095 insertions(+)
create mode 100644 src/docs/clickhouse.md
create mode 100644 src/validation/CLAUDE.md
create mode 100644 src/validation/pytest.ini
create mode 100644 src/validation/queries/type/literal/binary.txtcase
create mode 100644 src/validation/queries/type/literal/date.txtcase
create mode 100644 src/validation/queries/type/literal/time.txtcase
create mode 100644 src/validation/queries/type/literal/timestamp.txtcase
create mode 100644 src/validation/queries/type/literal/timestamptz.txtcase
create mode 100644 src/validation/queries/type/select/binary.txtcase
create mode 100644 src/validation/queries/type/select/boolean.txtcase
create mode 100644 src/validation/queries/type/select/date.txtcase
create mode 100644 src/validation/queries/type/select/decimal.txtcase
create mode 100644 src/validation/queries/type/select/float32.txtcase
create mode 100644 src/validation/queries/type/select/float64.txtcase
create mode 100644 src/validation/queries/type/select/int16.txtcase
create mode 100644 src/validation/queries/type/select/int32.txtcase
create mode 100644 src/validation/queries/type/select/int64.txtcase
create mode 100644 src/validation/queries/type/select/string.txtcase
create mode 100644 src/validation/queries/type/select/time.txtcase
create mode 100644 src/validation/queries/type/select/timestamp.txtcase
create mode 100644 src/validation/queries/type/select/timestamptz.txtcase
create mode 100644 src/validation/tests/__init__.py
create mode 100644 src/validation/tests/clickhouse.py
create mode 100644 src/validation/tests/conftest.py
create mode 100644 src/validation/tests/generate_documentation.py
create mode 100644 src/validation/tests/test_connection.py
create mode 100644 src/validation/tests/test_ingest.py
create mode 100644 src/validation/tests/test_query.py
create mode 100644 src/validation/tests/test_statement.py
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)