A ClickHouse backend implementation for rhosocial-activerecord — the ActiveRecord pattern with type safety and rich features, adapted to ClickHouse's columnar, OLTP-deviant world view.
ClickHouse is not a row-store OLTP engine, and this backend does not pretend
it is. Relational guarantees that ClickHouse does not provide are declared
through capability switches and fail fast with
UnsupportedFeatureError instead of being silently emulated:
| Feature | Behaviour in this backend |
|---|---|
| ACID transactions | transaction() degrades to a no-op context manager; rollback semantics raise |
| AUTO_INCREMENT | Client-side snowflake Int64 ids generated by the backend |
| FOREIGN KEY / UNIQUE | Not emitted; DDL stays ClickHouse-native (ENGINE, ORDER BY, skip indexes) |
| UPSERT / ON CONFLICT / REPLACE | Raises UnsupportedFeatureError |
| JSON access | Native functions: JSONExtractString / JSONExtractRaw / JSON_VALUE |
| UPDATE / DELETE | Lightweight mutations executed with mutations_sync=1; affected_rows = pre-counted matched rows |
| Feature | This Backend | Generic Solutions |
|---|---|---|
| Columnar types | Int/UInt, Decimal, Bool, UUID, IPv4/6, Enum8/16, Array, Map, Tuple, Nullable, LowCardinality, JSON | Lossy VARCHAR mapping |
| DDL control | ENGINE, ORDER BY, PARTITION BY, TTL clauses |
One-size-fits-all DDL |
| Introspection | system.* tables (settings, metrics, replicas, processes) |
information_schema guesses |
| Parameterized SQL | DB-API placeholders via clickhouse-connect HTTP | String concatenation |
- Connection pooling support through the shared backend pool
- Automatic version adaptation — dialect and type adapters re-tune to the actual server version on connect
- Error mapping from ClickHouse error codes to Python exceptions
- Synchronous by design — clickhouse-connect is a sync-only driver; there is deliberately no half-working async layer
- Python >= 3.10, < 3.15 (per clickhouse-connect)
- ClickHouse server: any release line currently under maintenance (25.8 LTS, 26.3 LTS, 26.7+ tested; older lines may work but are untested)
- rhosocial-activerecord >= 1.0.0.dev30
pip install rhosocial-activerecord-clickhousefrom typing import Optional, ClassVar
from pydantic import Field
from rhosocial.activerecord import ActiveRecord, FieldProxy
from rhosocial.activerecord.api.connection import ConnectionConfig
from rhosocial.activerecord.backend.impl.clickhouse import ClickHouseBackend
class User(ActiveRecord):
__table_name__ = "users"
__primary_key__ = "id"
c: ClassVar[FieldProxy] = FieldProxy()
id: Optional[int] = None # snowflake id, assigned on save()
username: str = Field(min_length=3, max_length=50)
email: str
age: Optional[int] = None # -> Nullable(Int32) column
config = ConnectionConfig(
host="localhost", port=8123,
database="test_db", username="root", password="password",
)
User.configure(config, ClickHouseBackend)
# Create — id is generated client-side and returned
user = User(username="alice", email="alice@example.com")
user.save()
# Query
young = User.query().where(User.c.age < 30).order_by(User.c.username).all()
# Update
user.age = 31
user.save() # lightweight mutation, synchronous completion
# Delete
user.delete()Schema note: model tables should be created as MergeTree tables whose optional fields use
Nullable(T); writing NULL into a non-nullable String column stores an empty string. Primary keys map to theORDER BYsorting key, which cannot be updated.
- No cross-statement transactions (ClickHouse mutations are per-part atomic; the context manager exists only so generic code paths keep working)
- No uniqueness enforcement — deduplication is your engine's job
(
ReplacingMergeTree,CollapsingMergeTree) - No async API (the driver is sync-only)
- No SQL-standard FULLTEXT index /
MATCH ... AGAINST/JSON_TABLE(raisesUnsupportedFeatureError; use skip indexes andJSONExtract*) - No MySQL-specific feature surface: spatial types /
ST_*, the 9.0VECTORtype,SET, stored procedures /CALL,LOAD DATA/LOAD XML, admin commands (FLUSH/RESET/KILL/GRANT...),TABLE/VALUESconstructors,ANALYZE/CHECK/REPAIR TABLEmaintenance,/*+ */optimizer hints, or JSON Duality Views — all fail fast
The full shared testsuite
is bridged under tests/rhosocial/activerecord_clickhouse_test/feature/
(basic, events, interface, mixins, query, relation):
export PYTHONPATH=src:tests
python -m pytest tests/rhosocial/activerecord_clickhouse_test -p no:logging -p no:cacheproviderA local ClickHouse is required; connection defaults live in
tests/config/clickhouse_scenarios.yaml (override path via
CLICKHOUSE_SCENARIOS_CONFIG_PATH).
CI exercises the matrix across Python 3.10–3.14 and the maintained ClickHouse
release lines (25.8 LTS, 26.3 LTS, 26.7) — see .github/workflows/test.yml.
- AGENTS.md — repository conventions for humans and AI agents
- CLAUDE.md — same overview, Claude Code entry point
- .claude/ — backend knowledge base and development guides
Issues and pull requests are welcome at the issue tracker. Please follow the core project's coding and changelog standards.