Unreleased
Features
- Standard Ash mix tasks.
AshClickhouse.DataLayer.Extensionnow implementsSpark.Dsl.Extensionwithcodegen/1andmigrate/1, somix ash.codegen(print pending DDL, with--dry-run/--checksupport) andmix ash.migrate(apply DDL) work out of the box.mix ash_clickhouse.migratenow delegates to the samemigrate/1, keeping both commands in sync. - Data-skipping indexes. Declare ClickHouse data-skipping indexes
(
minmax,set,bloom_filter,ngrambf_v1,tokenbf_v1) inside theclickhouseblock via the repeatableindexmacro. They are emitted in theCREATE TABLEDDL and added to existing tables withALTER TABLE ... ADD INDEX IF NOT EXISTSbymix ash_clickhouse.migrate. The indextypeis validated against a whitelist at compile time. Seeguides/migrations.mdandguides/resources.md.
Bug fixes
create_database/1/drop_database/1no longer target a database namednil. When a repo has no:databaseconfigured, these now fall back to ClickHouse'sdefaultdatabase (matchingalter_table_cql/2), somix ash_clickhouse.setupno longer creates a literalnildatabase.Repo.child_spec/1now honors itsoptsargument. 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/2now guards against a missing repo. A resource whoseclickhouseblock forgetsreporaises a clearConfigurationErrorinstead of failing withUndefinedFunctionError: function nil.query/3 is undefined.distinct+ explicitselectno longer silently drops columns.build_optimized_query/1now emits the merged select list underDISTINCT(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), emittingNULLS FIRST/NULLS LAST. Previously these raisedFunctionClauseError. - The
indexDSL macro is now robust to key order. It matches any keyword list and pullsname/expression/typewithKeyword.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-timeArgumentError. mix ash_clickhouse.migrateis more resilient. A single resource that raises during DDL generation no longer aborts the whole run, and a resource that forgetsrepois skipped with an error instead of being migrated into every configured database.- Migration defaults support booleans, dates, datetimes, and
Decimalstructs. These now emit correct literals instead of a misleading "Non-numeric default" error. Other unsupported default shapes raise a clearer "Unsupported default" message. stream/3now wraps raw client exceptions inAshClickhouse.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?/2now has a single source of truth. The 26 redundant per-atom clauses that duplicated the@supported_featuresMapSet were removed; only the genuinely special cases (aggregates, joins,:filter_expr, and the explicitfalseclauses) remain.Dsl.get_config/3no longer rescues an unreachableFunctionClauseError.Identifier.valid_identifier?/1is now just the regex (the manual first-character check was redundant).collect_columns/1now handles:notexpressions (both%Ash.Query.BooleanExpression{op: :not}and%Ash.Query.Not{}), matchingbuild_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:stringbusiness identifiers (order numbers, etc.) from being mangled on insert/update and in WHERE filters.Removed dead
Module.get_attribute/2fallbacks inresolve_table_name/1andrepo/1, which always raised (and were silently swallowed) at runtime. They now useDsl.table/1/Dsl.repo/1directly.Relationship aggregates are now batched.
attach_aggregates/5issues 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_onerelationship aggregates are now supported (previously onlybelongs_toworked; all others silently returneddefault_value).Consistent, type-aware aggregate decoding.
decode_aggregate/2becamedecode_aggregate/4, which resolves the actual Ash attribute type of the aggregated field instead of sniffing the string's shape. This fixesDecimalcolumns being silently downgraded tofloatand mishandled scientific notation, and makes query-level and relationship aggregates return the same decoded type.raise_on_untranslatable_filternow defaults totrue(fail-closed). An untranslatable filter on abase_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 withconfig :ash_clickhouse, :raise_on_untranslatable_filter, false.Repo cache can now be cleared via
AshClickhouse.DataLayer.clear_repo_cache!/0for test suites that redefine resources between tests.Connection errors now log the message and stacktrace before being swallowed as
{:error, _}, andConnection.stop/1logs a debug message on shutdown instead of silently masking failures.truncate_integer/1renamed tovalidate_integer!/1to reflect that it validates/parses rather than truncates.Documented the intentional difference between strict
sanitize!/1(table/database names) andquote_name/1(column identifiers), and marked the unusedgroup_byquery field as dead scaffolding.Split the mono-module
data_layer.exinto focused modules underAshClickhouse.DataLayer.*:Insert(value encoding + insert/update SQL),Record(row → Ash record decoding),Aggregate(native + batched relationship aggregates), andCalculations(in-memory calculation application). The public API is unchanged.Connection.query!/4now reraises a wrappedClickhouseError(instead of re-raising a fresh one), preserving the original stacktrace while still normalizing client errors.QueryBuilder.build_where_clause/2no longer grows params quadratically. Theparams ++ mappedappend inside the reduce was replaced with prepend + singleEnum.reverse()/Enum.concat(), so building WHERE clauses is now linear in the number of filters.Shared mix-task helpers.
find_repos/0/find_resources/0moved intoMix.Tasks.AshClickhouse.Helpers, removing the duplication betweenmix ash_clickhouse.setupandmix ash_clickhouse.migrate.
0.1.0
- Initial release of AshClickhouse, an Ash data layer for ClickHouse.
- Implements
Ash.DataLayerwith CRUD, filter, sort, limit/offset, select, distinct, bulk_create, update_query/destroy_query, native aggregates, multitenancy, calculations, and relationship aggregates. - Provides the
clickhouseDSL,AshClickhouse.Repo,AshClickhouse.Connection, andmix ash_clickhouse.setup/mix ash_clickhouse.migratetasks.