Current section

Files

Jump to
ash_clickhouse CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
## Unreleased
### Features
- **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.