Selecto

A Query Writing System

Selecto allows you to create queries within a configured domain. The domain has a main table that will always be included in queries, required filters that will always be applied, and a tree of available tables for joins.

This is very young software and might spill milk in your computer.

Better documentation is planned once the API is finalized.

For now, see selecto_test.

Selecto is configured by passing in a 'domain' which tells it which table to start at, which tables it can join (assocs are supported now, but I will be adding support for non-assoc & parameterized joins), what columns are available (currently it's the columns from the schema, but guess what), and what filters are available (currently its generated from the list of columns, but that will also be expanded to custom filters).

selecto = Selecto.configure( YourApp.Repo, domain )

The domain is a map, and contains:

domain = %{
name: "Solar System",
source: SelectoTest.Test.SolarSystem,
joins: [ ### Joins require a Name currently-- may change and allow a format similar to the list from preload
planets: %{
name: "Planet",
joins: [
satellites: %{
name: "Satellites"
}
],
}
],
### These filters will always be applied to a query in the domain. Note due to a bug/feature, if no filters are provided
### you will not get any rows returned
required_filters: [{"id", [1, 2, 3, 4, 5, 6]}]
}

Selecto will walk through the configuration and configure columns from the ecto schema. From the source table, they will be named as a string of the column name. Columns in associated tables will be given name as association atom and the column name in brackets, eg "assoctable[id]".

To select data, use Selecto.select:

Selecto.select(selecto, ["id", "name", "assoctable[id]" ])

To filter data, use Selecto.filter:

Selecto.filter(selecto, [ {"id", 1} ])

Selecto will add the joins needed to build the query for the requested selections. It currently uses left joins only, but I will add support to specify join type.

To get results, use Selecto.execute

Selecto.execute(selecto)

Selections in Detail

When func is referenced below, it is referring to a SQL function

"field" # - plain old field from one of the tables
{:field, field } #- same as above disamg for predicate second+ position
{:literal, "value"} #- for literal values
{:literal, 1.0}
{:literal, 1}
{:literal, datetime} etc
{:func, SELECTOR}
{:count, *} (for count(*))
{:func, SELECTOR, SELECTOR}
{:func, SELECTOR, SELECTOR, SELECTOR} #...
{:extract, part, SELECTOR}
{:case, [PREDICATE, SELECTOR, ..., :else, SELECTOR]}
{:coalese, [SELECTOR, SELECTOR, ...]}
{:greatest, [SELECTOR, SELECTOR, ...]}
{:least, [SELECTOR, SELECTOR, ...]}
{:nullif, [SELECTOR, LITERAL_SELECTOR]} #LITERAL_SELECTOR means naked value treated as lit not field
{:subquery, ...}
#TODO
{:operator, OP, SELECTOR}
{:operator, OP, SELECTOR, SELECTOR}

Filters in Detail

A filter is given as a tuple with the following forms allowed:

Planned Features:

Planned new format :

#standardize predicate format FUTURE NOT AVAILABLE YET!
{SELECTOR} # for boolean fields
{SELECTOR, nil} #is null
{SELECTOR, :not_nil} #is not null
{SELECTOR, SELECTOR} #=
{SELECTOR, [SELECTOR2, ...]}# in ()
{SELECTOR, {comp, SELECTOR2}} #<= etc
{SELECTOR, {:between, SELECTOR2, SELECTOR2}
{:not, PREDICATE}
{:and, [PREDICATES]}
{:or, [PREDICATES]}
{SELECTOR, :in, SUBQUERY}
{SELECTOR, comp, :any, SUBQUERY}
{SELECTOR, comp, :all, SUBQUERY}
{:exists, SUBQUERY}

Installation

If available in Hex, the package can be installed by adding selecto to your list of dependencies in mix.exs:

def deps do
[
{:selecto, "~> 0.2.3"}
]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/selecto.