defmodule Selecto do @derive {Inspect, only: [:repo, :set]} defstruct [:repo, :domain, :config, :set] import Ecto.Query import Selecto.Helpers @moduledoc """ Documentation for `Selecto,` a query writer and report generator for Elixir/Ecto """ @doc """ Generate a selecto structure from this Repo following the instructions in Domain map """ def configure(repo, domain) do %Selecto{ repo: repo, domain: domain, config: configure_domain(domain), set: %{ selected: Map.get(domain, :required_selected, []), filtered: [], order_by: Map.get(domain, :required_order_by, []), group_by: Map.get(domain, :required_group_by, []) } } end # generate the selecto configuration defp configure_domain(%{source: source} = domain) do primary_key = source.__schema__(:primary_key) fields = Selecto.Schema.Column.configure_columns( :selecto_root, ## Add in keys from domain.columns ... source.__schema__(:fields) -- source.__schema__(:redact_fields), source, domain ) joins = Selecto.Schema.Join.recurse_joins(source, domain) ## Combine fields from Joins into fields list fields = List.flatten([fields | Enum.map(Map.values(joins), fn e -> e.fields end)]) |> Enum.reduce(%{}, fn m, acc -> Map.merge(m, acc) end) ### Extra filters (all normal fields can be a filter) These are custom, which is really passed into Selecto Components to deal with filters = Map.get(domain, :filters, %{}) filters = Enum.reduce( Map.values(joins), filters, fn e, acc -> Map.merge(Map.get(e, :filters, %{}), acc) end ) |> Enum.map(fn {f, v} -> {f, Map.put(v, :id, f)} end) |> Enum.into(%{}) %{ source: source, source_table: source.__schema__(:source), primary_key: primary_key, columns: fields, joins: joins, filters: filters, domain_data: Map.get(domain, :domain_data) } end @doc """ add a field to the Select list. Send in one or a list of field names or selectable tuples TODO allow to send single, and special forms.. """ def select(selecto, fields) when is_list(fields) do put_in(selecto.set.selected, Enum.uniq(selecto.set.selected ++ fields)) end def select(selecto, field) do Selecto.select(selecto, [field]) end @doc """ add a filter to selecto. Send in a tuple with field name and filter value """ def filter(selecto, filters) when is_list(filters) do put_in(selecto.set.filtered, selecto.set.filtered ++ filters) end def filter(selecto, filters) do put_in(selecto.set.filtered, selecto.set.filtered ++ [filters]) end @doc """ Add to the Order By """ def order_by(selecto, orders) when is_list(orders) do put_in(selecto.set.order_by, selecto.set.order_by ++ orders) end def order_by(selecto, orders) do put_in(selecto.set.order_by, selecto.set.order_by ++ [orders]) end @doc """ Add to the Group By """ def group_by(selecto, groups) when is_list(groups) do put_in(selecto.set.group_by, selecto.set.group_by ++ groups) end def group_by(selecto, groups) do put_in(selecto.set.group_by, selecto.set.group_by ++ [groups]) end def gen_sql(selecto, opts) do Selecto.Builder.Sql.build(selecto, opts) end @doc """ Generate and run the query, returning list of lists, db produces column headers, and provides aliases """ def execute(selecto, opts \\ []) do # IO.puts("Execute Query") {query, aliases, params} = gen_sql(selecto, opts) # IO.inspect(query, label: "Exe") {:ok, result} = Ecto.Adapters.SQL.query(selecto.repo, query, params) # |> IO.inspect(label: "Results") {result.rows, result.columns, aliases} end end