Packages
exosql
0.2.1
0.2.88
0.2.87
0.2.86
0.2.85
0.2.84
0.2.83
0.2.82
0.2.81
0.2.80
0.2.79
0.2.78
0.2.77
0.2.76
0.2.75
0.2.74
0.2.73
0.2.72
0.2.71
0.2.70
0.2.69
0.2.68
0.2.67
0.2.65
0.2.64
0.2.63
0.2.62
0.2.60
0.2.59
0.2.58
0.2.57
0.2.56
0.2.55
0.2.54
0.2.53
0.2.51
0.2.50
0.2.49
0.2.48
0.2.47
0.2.46
0.2.45
0.2.44
0.2.43
0.2.42
0.2.41
0.2.40
0.2.39
0.2.38
0.2.37
0.2.36
0.2.35
0.2.33
0.2.32
0.2.31
0.2.30
0.2.29
0.2.28
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
Universal SQL engine for Elixir. This library implements the SQL logic to perform queries on user provided databases using a simple interface based on Foreign Data Wrappers from PostgreSQL.
Current section
Files
Jump to
Current section
Files
lib/executor.ex
require Logger
defmodule ExoSQL.Executor do
@doc ~S"""
Executes the AST for the query.
Always returns a ExoSQL.Result and work over them.
"""
def execute({:select, from, columns}, context) do
{:ok, %{ columns: rcolumns, rows: rows}} = execute(from, context)
# Logger.debug("Get #{inspect columns} from #{inspect rcolumns}")
exprs = Enum.map(columns, &simplify_expr_columns(&1, rcolumns))
# Logger.debug("From #{inspect {rcolumns, rows}} get #{inspect exprs} / #{inspect columns}")
rows = Enum.map(rows, fn row ->
Enum.map(exprs, &ExoSQL.Expr.run_expr(&1, row) )
end)
columns = resolve_column_names(columns)
{:ok, %ExoSQL.Result{ rows: rows, columns: columns}}
end
def execute({:execute, {db, table}, quals, columns}, context) do
{dbmod, context} = context[db]
case apply(dbmod, :execute, [context, table, quals, columns]) do
{:ok, %{ columns: ^columns, rows: rows}} ->
{:ok, %ExoSQL.Result{
columns: Enum.map(columns, fn c -> {db, table, c} end),
rows: rows
}}
{:ok, %{ columns: rcolumns, rows: rows}} ->
result = %ExoSQL.Result{
columns: Enum.map(rcolumns, fn c -> {db, table, c} end),
rows: rows
}
columns = Enum.map(columns, &({:column, &1}))
execute({:select, result, columns}, context)
other -> other
end
end
def execute({:filter, from, expr}, context) do
{:ok, %{ columns: columns, rows: rows }} = execute(from, context)
expr = simplify_expr_columns(expr, columns)
rows = Enum.filter(rows, fn row ->
ExoSQL.Expr.run_expr(expr, row)
end)
{:ok, %ExoSQL.Result{ columns: columns, rows: rows}}
end
def execute({:cross_join, table1, table2}, context) do
{:ok, res1} = execute(table1, context)
{:ok, res2} = execute(table2, context)
rows = Enum.flat_map(res1.rows, fn r1 ->
Enum.map(res2.rows, fn r2 ->
r1 ++ r2
end)
end)
{:ok, %ExoSQL.Result{
columns: res1.columns ++ res2.columns,
rows: rows
}}
end
def execute({:inner_join, table1, table2, expr}, context) do
{:ok, table1} = execute(table1, context)
{:ok, table2} = execute(table2, context)
# Logger.debug("Inner join of\n\n#{inspect table1, pretty: true}\n\n#{inspect table2, pretty: true}\n\n#{inspect expr}")
columns = table1.columns ++ table2.columns
# Logger.debug("Columns #{inspect columns}")
rexpr = simplify_expr_columns(expr, columns)
rows = Enum.reduce( table1.rows, [], fn row1, acc ->
nrows = Enum.map( table2.rows, fn row2 ->
row = row1 ++ row2
if ExoSQL.Expr.run_expr(rexpr, row) do
row
else
nil
end
end) |> Enum.filter(&(&1 != nil))
# Logger.debug("Test row #{inspect nrow} #{inspect rexpr}")
nrows ++ acc
end)
# Logger.debug("Result #{inspect rows, pretty: true}")
{:ok, %ExoSQL.Result{
columns: columns,
rows: rows
}}
end
def execute({:group_by, from, groups}, context) do
{:ok, data} = execute(from, context)
sgroups = Enum.map(groups, &simplify_expr_columns(&1, data.columns))
rows = Enum.reduce(data.rows, %{}, fn row, acc ->
set = Enum.map(sgroups, &ExoSQL.Expr.run_expr( &1, row ))
# Logger.debug("Which set for #{inspect row} by #{inspect sgroups}/#{inspect groups} (#{inspect data.columns}): #{inspect set}")
Map.put( acc, set, [row] ++ Map.get(acc, set, []))
end) |> Enum.map(fn {group,row} ->
table = %ExoSQL.Result{
columns: data.columns,
rows: row
}
group ++ [table]
end)
columns = resolve_column_names(groups) ++ [{"group_by"}]
# Logger.debug("Grouped rows: #{inspect columns}\n #{inspect rows, pretty: true}")
{:ok, %ExoSQL.Result{
columns: columns,
rows: rows
} }
end
def execute({:table_to_row, from}, context) do
{:ok, data} = execute(from, context)
{:ok, %ExoSQL.Result{
columns: ["group_by"],
rows: [[data]]
}}
end
def execute(%ExoSQL.Result{} = res, _context), do: {:ok, res}
def execute(%{ rows: rows, columns: columns}, _context), do: {:ok, %ExoSQL.Result{ rows: rows, columns: columns }}
## Simplify the column ids to positions on the list of columns, to ease operations.
def simplify_expr_columns({:column, cn}, _names) when is_number(cn) do
{:column, cn}
end
def simplify_expr_columns({:column, cn}, names) do
i = Enum.find_index(names, &(&1 == cn))
if i == nil do
throw {:error, {:not_found, cn, :in, names}}
end
{:column, i}
end
def simplify_expr_columns({:op, {op, op1, op2}}, names) do
op1 = simplify_expr_columns(op1, names)
op2 = simplify_expr_columns(op2, names)
{:op, {op, op1, op2}}
end
def simplify_expr_columns({:fn, {f, params}}, names) do
params = Enum.map(params, &simplify_expr_columns(&1, names))
{:fn, {f, params}}
end
def simplify_expr_columns(other, _names), do: other
def simplify_expr_columns_nofn({:column, cn}, names) do
i = Enum.find_index(names, &(&1 == cn))
{:column, i}
end
def simplify_expr_columns_nofn({:op, {op, op1, op2}}, names) do
op1 = simplify_expr_columns(op1, names)
op2 = simplify_expr_columns(op2, names)
{:op, {op, op1, op2}}
end
def simplify_expr_columns_nofn(other, _names), do: other
def resolve_column_names(columns) do
Enum.map(columns, fn
{:column, col} -> col
_other -> "?NONAME"
end)
end
end