QuackDB's Ecto adapter covers analytical reads plus common write and setup workflows. DuckDB's SQL surface is larger than Ecto's native AST, so coverage is tracked in three buckets:

This file is a roadmap, not a claim of complete DuckDB support.

Coverage matrix

AreaFeaturePathSQL generationReal serverStatus
Basic readsselect/where/order/limit/offsetEcto-nativeyesyescovered
Schema readsfull schema select / Repo.get!/2Ecto-nativeyesyescovered
Parameterspinned params and raw paramsEcto-native/rawyesyescovered
Joinsinner/left/right/full/crossEcto-nativeyesyescovered
Aggregatescount/sum/avg/min/max/count distinct/coalesceEcto-nativeyesyescovered
Analytical aggregatesmedian/quantile/list/string_agg/arg_max/arg_min/mode/weighted_avg/favg/fsum/product/statistics/histogramsHelperyesyescovered
Aggregate filterFILTER (WHERE ...)Ecto-nativeyesyescovered
Groupinggroup by/havingEcto-nativeyesyescovered
CTEsnon-recursive CTEsEcto-nativeyesyescovered
Windowsrow_number/rank/dense_rank/percent_rank/cume_distEcto-nativeyespartialpartial
Windowslag/lead/first_value/last_value/nth_valueEcto-native/helperyespartialpartial
Windowsfragment-backed frame clausesEcto-native/fragmentyesyescovered
SourcesCSV/Parquet helpersSource helpersyesyescovered
SourcesJSON/XLSX helpersSource helpers/rawpartialpartialpartial
Source analyticssource helper + aggregate/windowEcto-native/sourceyespartialpartial
Nested analyticslist/struct/map functionsFragment/rawpartialnomissing
JSON analyticsjson_extract/path queriesHelper/source/rawyesyespartial
Regular expressionsregexp_extract/matches/replace/split helpersHelperyesyescovered
Text helperscontains/starts_with/split_part/string_splitHelperyesyescovered
Time seriesdate_trunc/time_bucket/generate_seriesHelper/rawyesyespartial
Grouping extensionsgrouping sets/rollup/cubeRaw SQLnoyespartial
QUALIFYwindow filteringEcto subquery or raw SQLyesyespartial
Pivotingpivot/unpivotRaw SQLnoyespartial
Samplingusing sampleRaw SQLnoyespartial
Set operationsunion/intersect/exceptEcto combinationsyesyescovered
InsertsRepo.insert/2, Repo.insert_all/3Ecto-nativeyesyescovered
UpsertsDO NOTHING, set, inc, replacement fieldsEcto-nativeyesyescovered
Native appendinsert_all(..., insert_method: :append)Adapter optionyesyescovered
Mutationsupdate_all / delete_allEcto-nativeyesyescovered
Schema lifecycleRepo.update/2 / Repo.delete/2Ecto-nativeyesyescovered
Migrationscreate/drop/alter table, rename table/column, indexes, referencesEcto migration DDLyesyescovered
ExplainEcto.Adapters.SQL.explain/4Ecto SQLyesyescovered
Full-text searchBM25 ranking and stemmingEcto helper fragmentsyesyescovered
Advanced joinssemi/anti/asof/positionalRaw SQLnonomissing
DuckDB select extensions* EXCLUDE, * REPLACE, COLUMNS(*)Raw SQLnonomissing
Introspectionsummarize/describe/pragmaDirect SQL helper/rawpartialpartialpartial

Migration boundaries

Basic migration DDL is generated for table creation/drop, column add/drop/modify, table and column renames, references, primary keys, composite primary keys, and ordinary/unique indexes. DuckDB-incompatible index options such as concurrent indexes, covering indexes, raw index options, index comments, custom USING, and nulls_distinct raise explicit QuackDB errors instead of being ignored.

Advanced constraints and comments should be added only where DuckDB can enforce the same semantics. Until then, prefer raw SQL for DuckDB-specific DDL.

Test organization

Coverage should stay split by expression path:

test/quack_db/ecto/sql_generation/
  analytical_test.exs
  aggregates_test.exs
  fragments_test.exs
  migration_test.exs
  source_analytics_test.exs
  sources_test.exs
  update_delete_test.exs
  window_functions_test.exs

test/quack_db/integration/ecto/
  migration_test.exs
  query_test.exs

Use SQL generation tests to pin what QuackDB emits for Ecto-native queries. Use real-server tests for DuckDB-native semantics and raw SQL pass-through.

QUALIFY-style filters

Ecto does not have a QUALIFY clause, but the common top-N-per-group pattern is expressible with a subquery:

ranked =
  from event in "events",
    windows: [by_category: [partition_by: event.category, order_by: [desc: event.score]]],
    select: %{
      id: event.id,
      category: event.category,
      rank: over(row_number(), :by_category)
    }

from event in subquery(ranked),
  where: event.rank <= 3,
  order_by: [event.category, event.rank],
  select: %{id: event.id, rank: event.rank}

Use raw SQL when DuckDB-specific QUALIFY syntax is clearer than the Ecto subquery shape.

Query style

Prefer ordinary Ecto syntax when it maps cleanly to DuckDB SQL:

  • comparisons and boolean logic with Elixir/Ecto operators;
  • conditional aggregates with filter(count(...), predicate) instead of adapter-specific *_if helpers;
  • selected_as/2 for grouped aliases;
  • type/2 for JSON/text casts.

Use QuackDB.Ecto.Analytics for DuckDB analytical functions that are established SQL vocabulary, such as median/1, quantile_cont/2, list/1,2, weighted_avg/2, fsum/1, time_bucket/2,3, JSON path helpers, and query-level profiling with summarize/3 or summarize!/3.

Use QuackDB.Ecto.Regex for DuckDB's regexp_* expression functions. DuckDB uses RE2 while Elixir Regex uses Erlang/OTP :re, so literal ~r/.../ patterns are convenient only for the shared syntax subset. QuackDB translates compatible ~r modifiers (i, m, and s) into DuckDB option strings, ignores Elixir's Unicode modifier, and rejects modifiers DuckDB cannot represent.

Use QuackDB.Ecto.Text for common text predicates and splitting functions (contains/2, starts_with/2, ends_with/2, split_part/3, string_split/2) when they read better than fragments. With use QuackDB.Ecto, shared contains/2 routes obvious text calls to DuckDB contains and spatial helper expressions to ST_Contains; ambiguous calls raise so use contains_text/2 or st_contains/2 when you want to be explicit.

Keep raw SQL for syntax Ecto cannot represent well, including PIVOT, UNPIVOT, QUALIFY, GROUPING SETS, ROLLUP, and CUBE. Window frames should use fragment(...) until QuackDB depends on an Ecto release that includes macro-expanded frame helper support.

Boundaries

QuackDB should not try to reimplement all DuckDB syntax as Ecto macros. For DuckDB-specific syntax that Ecto cannot represent cleanly, prefer:

  1. raw SQL through Repo.query/3 or QuackDB.query/4;
  2. QuackDB.Ecto.Analytics for common DuckDB analytical expressions;
  3. QuackDB.Ecto.Spatial for common spatial expressions;
  4. source helpers for table functions such as CSV/Parquet/JSON;
  5. fragment/1 for expressions inside otherwise-normal Ecto queries;
  6. explicit unsupported errors for Ecto AST shapes that would generate misleading SQL.

Future adapter-specific helpers may make sense for repeated patterns such as QUALIFY, lakehouse sources, or Arrow handoff, but those should be added only after the protocol and result semantics are stable.