0.7.2
Bug fixes
- Resource metadata caching no longer fails when the ETS table is missing.
The
:ash_clickhouse_resource_metadatatable 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/2passed the version as a scalar param toINSERT INTO schema_migrations ... VALUES (?), but the client treats params of anyINSERTas bulk rows (each must be a list, and?placeholders are never substituted there) — so applying any migration crashed with aFunctionClauseError(surfacing as aClickhouseError) 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]])withFORMAT JSONCompactEachRow. - Migration discovery finds files another extension already loaded. During
mix ash.migrate, sibling extensions require every.exsunderpriv/repo/migrations, andCode.require_file/1returns 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'sdefmodulename viaCode.ensure_loaded.
0.7.0
Features
containsis now configurable as case-sensitive viaconfig :ash_clickhouse, :case_sensitive_contains, true(usesposition()instead ofpositionCaseInsensitive()), matching the case-sensitivity ofstarts_with/ends_with. Defaults to the historical case-insensitive behaviour.like/ilikefilters are now supported, translating to ClickHouseLIKE/ILIKE.- Ash NewTypes are unwrapped in DDL type mapping. A NewType wrapping e.g.
:uuidnow maps toUUIDinstead of silently becoming aStringcolumn; truly unknown types fall back toStringwith 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/4no longer swallows genuine bugs. Only client error modules (includingArgumentErrorfrom 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.
Connectionnow monitors the client pid and erases the cached struct on:DOWN, soget_conn/1doesn't keep returning a dead pid. Connection.stop/1only 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_infilter 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 todefault_value— wrong numbers in reports are usually worse than a loud error. Opt out withconfig :ash_clickhouse, :raise_on_aggregate_failure, false. -avgover Decimal columns keeps precision. Aggregate decoding now uses the field's attribute type foravgtoo, decoding back toDecimalinstead of losing precision as a float. - Single-row update/destroy default tomutations_sync: 1, giving read-your-writes semantics so the returned record reflects the change immediately. ### Improvements -insert_rows/4drops the unusedtableargument (breaking change):repo.insert_rows(statement, rows, opts)— the table was already embedded in the statement.Repobehaviour andDataLayercall sites updated. - Per-resource metadata caching.uuid_attribute_names/1,atom_attribute_names/1, andattr_type_map/1results 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.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. - TheindexDSL 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, andDecimalstructs. 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 (viaDsl.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 deadModule.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 viaAshClickhouse.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 strictsanitize!/1(table/database names) andquote_name/1(column identifiers), and marked the unusedgroup_byquery field as dead scaffolding. - Split the mono-moduledata_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. - ImplementsAsh.DataLayerwith CRUD, filter, sort, limit/offset, select, distinct, bulk_create, update_query/destroy_query, native aggregates, multitenancy, calculations, and relationship aggregates. - Provides theclickhouseDSL,AshClickhouse.Repo,AshClickhouse.Connection, andmix ash_clickhouse.setup/mix ash_clickhouse.migratetasks.