Current section
Files
Jump to
Current section
Files
ash_clickhouse
CHANGELOG.md
CHANGELOG.md
# Changelog
## 0.7.2
### Bug fixes
- **Resource metadata caching no longer fails when the ETS table is missing.**
The `:ash_clickhouse_resource_metadata` table is now initialized at
application startup and owned by the long-lived application process, so it
is not removed when the request process that first reads resource metadata
exits.
## 0.7.1
### Bug fixes
- **Recording applied migrations no longer fails.** `record_applied/2` passed
the version as a scalar param to `INSERT INTO schema_migrations ... VALUES (?)`,
but the client treats params of any `INSERT` as bulk rows (each must be a
list, and `?` placeholders are never substituted there) — so applying any
migration crashed with a `FunctionClauseError` (surfacing as a
`ClickhouseError`) even though the DDL itself had succeeded. The version is
now written through the same row-encoding path as data-layer inserts:
`repo.insert_rows(statement, [[version]])` with `FORMAT JSONCompactEachRow`.
- **Migration discovery finds files another extension already loaded.** During
`mix ash.migrate`, sibling extensions require every `.exs` under
`priv/repo/migrations`, and `Code.require_file/1` returns nothing for files
already required in the VM — so AshClickhouse discovered zero modules and
reported "No pending migrations" against an empty tracking table. Discovery
now falls back to resolving the already-loaded module from the file's
`defmodule` name via `Code.ensure_loaded`.
## 0.7.0
### Features
- **`contains` is now configurable as case-sensitive** via
`config :ash_clickhouse, :case_sensitive_contains, true` (uses `position()`
instead of `positionCaseInsensitive()`), matching the case-sensitivity of
`starts_with`/`ends_with`. Defaults to the historical case-insensitive
behaviour.
- **`like` / `ilike` filters are now supported**, translating to ClickHouse
`LIKE` / `ILIKE`.
- **Ash NewTypes are unwrapped in DDL type mapping.** A NewType wrapping e.g.
`:uuid` now maps to `UUID` instead of silently becoming a `String` column;
truly unknown types fall back to `String` with a warning.
- **Typed array attributes encode element values natively.** For arrays like
`{:array, :integer}` elements are passed through so the client emits native
JSON numbers; only string-ish element types are stringified.
- **Added GitHub Actions CI workflow** (`.github/`).
### Bug fixes
- **`Connection.query/4` no longer swallows genuine bugs.** Only client error
modules (including `ArgumentError` from the client) are converted to a
normalized error tuple; any other exception propagates with its original
stacktrace instead of being mislabelled as a ClickHouse error.
- **Stale connection cache entries are cleaned up when the client process
dies.** `Connection` now monitors the client pid and erases the cached struct
on `:DOWN`, so `get_conn/1` doesn't keep returning a dead pid.
- **`Connection.stop/1` only erases the cache entry after the client process
has been stopped** (or was already gone), so a failed stop leaves the cache
intact for retry, and always returns `:ok`.
- **Empty `in` / `not_in` filter lists no longer produce invalid SQL.**
`col IN ()` would be rejected by ClickHouse; these now emit semantically
equivalent always-false / always-true literals.
- **DISTINCT queries with sorts on unselected columns are now valid SQL.** Sort
columns missing from the SELECT list are appended automatically (ClickHouse
requires every ORDER BY expression in the SELECT list for DISTINCT).
- **Rollback version deletion uses parameterized queries** instead of manual
string escaping (`delete_applied/2`).
- **`Rollback stop comparison handles mixed-width numeric versions.** Versions
are compared numerically when both parse as integers, falling back to
lexicographic order for timestamp-style versions.
- **Batched relationship-aggregate failures now raise by default**
(`QueryError`) instead of silently falling back to `default_value` — wrong
numbers in reports are usually worse than a loud error. Opt out with
`config :ash_clickhouse, :raise_on_aggregate_failure, false`.
- **`avg` over Decimal columns keeps precision.** Aggregate decoding now uses
the field's attribute type for `avg` too, decoding back to `Decimal` instead
of losing precision as a float.
- **Single-row update/destroy default to `mutations_sync: 1`**, giving
read-your-writes semantics so the returned record reflects the change
immediately.
### Improvements
- **`insert_rows/4` drops the unused `table` argument** (breaking change):
`repo.insert_rows(statement, rows, opts)` — the table was already embedded in
the statement. `Repo` behaviour and `DataLayer` call sites updated.
- **Per-resource metadata caching.** `uuid_attribute_names/1`,
`atom_attribute_names/1`, and `attr_type_map/1` results are cached per
resource in an ETS table, so hot paths like per-row record decoding and bulk
value encoding don't re-scan resource attributes on every call.
- **Standard Ash mix tasks.** `AshClickhouse.DataLayer.Extension` now implements
`Spark.Dsl.Extension` with `codegen/1` and `migrate/1`, so `mix ash.codegen`
(print pending DDL, with `--dry-run` / `--check` support) and `mix ash.migrate`
(apply DDL) work out of the box. `mix ash_clickhouse.migrate` now delegates to
the same `migrate/1`, keeping both commands in sync.
- **Data-skipping indexes.** Declare ClickHouse data-skipping indexes
(`minmax`, `set`, `bloom_filter`, `ngrambf_v1`, `tokenbf_v1`) inside the
`clickhouse` block via the repeatable `index` macro. They are emitted in the
`CREATE TABLE` DDL and added to existing tables with `ALTER TABLE ... ADD
INDEX IF NOT EXISTS` by `mix ash_clickhouse.migrate`. The index `type` is
validated against a whitelist at compile time. See `guides/migrations.md` and
`guides/resources.md`.
### Bug fixes
- **`create_database/1` / `drop_database/1` no longer target a database named
`nil`.** When a repo has no `:database` configured, these now fall back to
ClickHouse's `default` database (matching `alter_table_cql/2`),
so `mix ash_clickhouse.setup` no longer creates a literal `nil` database.
- **`Repo.child_spec/1` now honors its `opts` argument.** Options passed via
the supervision tree (e.g. `{MyApp.Repo, url: "...", pool_size: 7}`) are
merged into the connection options instead of being silently dropped.
- **`run_query/2` now guards against a missing repo.** A resource whose
`clickhouse` block forgets `repo` raises a clear `ConfigurationError` instead
of failing with `UndefinedFunctionError: function nil.query/3 is undefined`.
- **`distinct` + explicit `select` no longer silently drops columns.**
`build_optimized_query/1` now emits the merged select list under `DISTINCT`
(ClickHouse dedupes on the full row) rather than only the distinct columns.
- **Sort building now supports Ash's nulls-ordering directions**
(`:asc_nils_first`, `:asc_nils_last`, `:desc_nils_first`, `:desc_nils_last`),
emitting `NULLS FIRST` / `NULLS LAST`. Previously these raised
`FunctionClauseError`.
- **The `index` DSL macro is now robust to key order.** It matches any keyword
list and pulls `name`/`expression`/`type` with `Keyword.get`, raising a clear
error if any required key is missing (instead of a cryptic "undefined
function index/1" when keys were reordered). Duplicate index names now raise
a compile-time `ArgumentError`.
- **`mix ash_clickhouse.migrate` is more resilient.** A single resource that
raises during DDL generation no longer aborts the whole run, and a resource
that forgets `repo` is skipped with an error instead of being migrated into
every configured database.
- **Migration defaults support booleans, dates, datetimes, and `Decimal`
structs.** These now emit correct literals instead of a misleading
"Non-numeric default" error. Other unsupported default shapes raise a clearer
"Unsupported default" message.
- **`stream/3` now wraps raw client exceptions** in
`AshClickhouse.Error.ClickhouseError`, matching every other read path.
- **`qualified_table/1` (writes) now backtick-quotes the table name** like the
read path, so a reserved-word table name behaves consistently across reads
and writes.
- **`can?/2` now has a single source of truth.** The 26 redundant per-atom
clauses that duplicated the `@supported_features` MapSet were removed; only
the genuinely special cases (aggregates, joins, `:filter_expr`, and the
explicit `false` clauses) remain.
- **`Dsl.get_config/3` no longer rescues an unreachable `FunctionClauseError`.**
- **`Identifier.valid_identifier?/1` is now just the regex** (the manual
first-character check was redundant).
- **`collect_columns/1` now handles `:not` expressions** (both
`%Ash.Query.BooleanExpression{op: :not}` and `%Ash.Query.Not{}`), matching
`build_predicate/1`.
### Improvements
- **UUID heuristic no longer corrupts non-UUID string data.** Parameters are
now converted to the 16-byte UUID binary form only when the column is
provably UUID-typed (via `Dsl.uuid_attribute_names/1`), instead of whenever a
36-character string merely *looked* like a UUID. This prevents legitimate
`:string` business identifiers (order numbers, etc.) from being mangled on
insert/update and in WHERE filters.
- **Removed dead `Module.get_attribute/2` fallbacks** in `resolve_table_name/1`
and `repo/1`, which always raised (and were silently swallowed) at runtime.
They now use `Dsl.table/1` / `Dsl.repo/1` directly.
- **Relationship aggregates are now batched.** `attach_aggregates/5` issues one
grouped query per aggregate across the whole result set (instead of one query
per record × aggregate), turning an N×M round-trip pattern into M round-trips.
`has_many` / `has_one` relationship aggregates are now supported (previously
only `belongs_to` worked; all others silently returned `default_value`).
- **Consistent, type-aware aggregate decoding.** `decode_aggregate/2` became
`decode_aggregate/4`, which resolves the actual Ash attribute type of the
aggregated field instead of sniffing the string's shape. This fixes
`Decimal` columns being silently downgraded to `float` and mishandled
scientific notation, and makes query-level and relationship aggregates return
the same decoded type.
- **`raise_on_untranslatable_filter` now defaults to `true` (fail-closed).** An
untranslatable filter on a `base_filter` (tenant scoping, soft-delete) is now
raised rather than silently dropped, avoiding queries that are less
restrictive than intended. Opt back into the old warning-only behaviour with
`config :ash_clickhouse, :raise_on_untranslatable_filter, false`.
- **Repo cache can now be cleared** via `AshClickhouse.DataLayer.clear_repo_cache!/0`
for test suites that redefine resources between tests.
- **Connection errors now log the message and stacktrace** before being
swallowed as `{:error, _}`, and `Connection.stop/1` logs a debug message on
shutdown instead of silently masking failures.
- **`truncate_integer/1` renamed to `validate_integer!/1`** to reflect that it
validates/parses rather than truncates.
- **Documented** the intentional difference between strict `sanitize!/1`
(table/database names) and `quote_name/1` (column identifiers), and marked
the unused `group_by` query field as dead scaffolding.
- **Split the mono-module `data_layer.ex` into focused modules** under
`AshClickhouse.DataLayer.*`: `Insert` (value encoding + insert/update SQL),
`Record` (row → Ash record decoding), `Aggregate` (native + batched
relationship aggregates), and `Calculations` (in-memory calculation
application). The public API is unchanged.
- **`Connection.query!/4` now reraises a wrapped `ClickhouseError`** (instead of
re-raising a fresh one), preserving the original stacktrace while still
normalizing client errors.
- **`QueryBuilder.build_where_clause/2` no longer grows params quadratically.**
The `params ++ mapped` append inside the reduce was replaced with prepend +
single `Enum.reverse()`/`Enum.concat()`, so building WHERE clauses is now
linear in the number of filters.
- **Shared mix-task helpers.** `find_repos/0` / `find_resources/0` moved into
`Mix.Tasks.AshClickhouse.Helpers`, removing the duplication between
`mix ash_clickhouse.setup` and `mix ash_clickhouse.migrate`.
## 0.1.0
- Initial release of AshClickhouse, an Ash data layer for ClickHouse.
- Implements `Ash.DataLayer` with CRUD, filter, sort, limit/offset, select,
distinct, bulk_create, update_query/destroy_query, native aggregates,
multitenancy, calculations, and relationship aggregates.
- Provides the `clickhouse` DSL, `AshClickhouse.Repo`, `AshClickhouse.Connection`,
and `mix ash_clickhouse.setup` / `mix ash_clickhouse.migrate` tasks.