Packages
exosql
0.2.82
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 {:ok, ExoSQL.Result}.
"""
def execute({:select, from, columns}, context) do
{:ok, %{columns: rcolumns, rows: rows}} = execute(from, context)
ocontext = context
# Logger.debug("Get #{inspect columns} from #{inspect rcolumns}. Context: #{inspect context}")
# Logger.debug("Rows: #{inspect {rcolumns, rows}, pretty: true}")
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:select, columns})}")
end
{rows, columns} =
case Enum.count(rows) do
0 ->
columns = resolve_column_names(columns, rcolumns)
{rows, columns}
_ ->
context = Map.put(context, :columns, rcolumns)
exprs = Enum.map(columns, &ExoSQL.Expr.simplify(&1, context))
# Logger.debug("From #{inspect rcolumns}\n get #{inspect exprs, pretty: true} /\n #{inspect columns, pretty: true}")
rows =
Enum.map(rows, fn row ->
Enum.map(exprs, &ExoSQL.Expr.run_expr(&1, Map.put(context, :row, row)))
end)
columns = resolve_column_names(columns, rcolumns)
{rows, columns}
end
{:ok, %ExoSQL.Result{rows: rows, columns: columns}}
end
def execute({:distinct, what, from}, context) do
{:ok, %{columns: columns, rows: rows}} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:distinct, what})}")
end
rows =
case what do
:all_columns ->
Enum.map(rows, fn row ->
{row, row}
end)
what ->
context = Map.put(context, :columns, columns)
expr = ExoSQL.Expr.simplify(what, context)
Enum.map(rows, fn row ->
{ExoSQL.Expr.run_expr(expr, Map.put(context, :row, row)), row}
end)
end
rows =
Enum.sort(rows)
|> Enum.uniq_by(fn {key, _} -> key end)
|> Enum.map(fn {_, row} -> row end)
# Logger.debug("Get distinct from #{inspect rows}")
{:ok,
%{
columns: columns,
rows: rows
}}
end
def execute({:execute, {:table, {"self", "tables"}}, _quals, _columns}, context) do
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:table, "self", "tables"})}")
end
rows =
Enum.flat_map(context, fn {db, _conf} ->
{:ok, tables} = ExoSQL.schema(db, context)
Enum.flat_map(tables, fn table ->
{:ok, %{columns: columns}} = ExoSQL.schema(db, table, context)
Enum.map(columns, fn column ->
[db, table, column]
end)
end)
end)
# Logger.debug("Rows: #{inspect rows}")
{:ok,
%{
columns: [
{"self", "tables", "db"},
{"self", "tables", "table"},
{"self", "tables", "column"}
],
rows: rows
}}
end
def execute({:fn, {function, params}}, context) do
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:fn, {function, params}})}")
end
params =
params
|> Enum.map(&ExoSQL.Expr.simplify(&1, context))
res = ExoSQL.Expr.run_expr({:fn, {function, params}}, context)
res = %ExoSQL.Result{
columns: [{:tmp, function, function}],
rows: res.rows
}
{:ok, res}
end
# alias select needs to rename quals and columns to the final, and back to aliased
def execute({:execute, {:alias, {table, alias_}}, quals, columns}, context) do
{:ok, res} = execute({:execute, table, quals, columns}, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:alias, alias_})}")
end
columns =
Enum.map(res.columns, fn {_db, _table, column} ->
{:tmp, alias_, column}
end)
{:ok,
%ExoSQL.Result{
columns: columns,
rows: res.rows
}}
end
def execute({:execute, {:table, {:with, table}}, _quals, columns}, context) do
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:table, :with, table})}")
end
data = context[:with][table]
column_reselect(data, columns, context)
end
def execute({:execute, {:table, {db, table}}, quals, columns}, context) do
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({{:table, {db, table}}, quals, columns})}")
end
# Logger.debug("Execute table #{inspect {db, table}}")
{dbmod, ctx} = context[db]
quals = quals_with_vars(quals, Map.get(context, "__vars__", %{}))
scolumns =
Enum.map(columns, fn {^db, ^table, column} ->
column
end)
executor_res = apply(dbmod, :execute, [ctx, table, quals, scolumns])
case executor_res do
{:ok, data} ->
data = %ExoSQL.Result{
columns: Enum.map(data.columns, &{db, table, &1}),
rows: data.rows
}
column_reselect(data, columns, context)
{:error, other} ->
{:error, {:extractor, {db, table}, other}}
end
end
def execute({:project, from}, context) do
{:ok, from} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:project})}")
end
if Enum.count(from.rows) == 0 do
# warning, the projection fails and the number of columns is undefined.
{:ok, from}
else
# Logger.debug("Orig #{inspect from}")
columns = project_columns(from.columns, hd(from.rows))
rows = Enum.flat_map(from.rows, &project_row(&1))
# Logger.debug("Final #{inspect {columns, rows}}")
{:ok,
%ExoSQL.Result{
columns: columns,
rows: rows
}}
end
end
def execute({:filter, from, expr}, context) do
# Can not use the same simplification as an inner query may require a
# change from a column to a literal (See "Complex nested SELECT" example)
# So first one simplification to check if the expr is false, and then the
# real simplification.
expr_ = ExoSQL.Expr.simplify(expr, context)
case expr_ do
{:lit, false} ->
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:filter, false})}")
end
{:ok, %ExoSQL.Result{columns: [], rows: []}}
_ ->
{:ok, %{columns: columns, rows: rows}} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:filter, expr})}")
end
context = Map.put(context, :columns, columns)
expr = ExoSQL.Expr.simplify(expr, context)
rows =
Enum.filter(rows, fn row ->
context = Map.put(context, :columns, columns)
ExoSQL.Expr.run_expr(expr, Map.put(context, :row, row))
end)
{:ok, %ExoSQL.Result{columns: columns, rows: rows}}
end
end
def execute({:cross_join, table1, table2}, context) do
{:ok, res1} = execute(table1, context)
{:ok, res2} = execute(table2, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:cross_join})}")
end
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
# An inner join does a first loop to get the quals for a single query on the
# second table with all the join ids (a {:in, "id", [1,2,3,4]} or similar)
# and then does the full join and filter
def execute({:inner_join, table1, table2, expr}, context) do
execute_join(table1, table2, expr, context, :empty)
end
# An left outer join is as the inner join, but instead of
# collapsing the non matching rows, generates a null one for
# the right side
def execute({:left_join, table1, table2, expr}, context) do
execute_join(table1, table2, expr, context, :left)
end
def execute({:right_join, table1, table2, expr}, context) do
execute_join(table2, table1, expr, context, :left)
end
def execute({:cross_join_lateral, from, expr}, context) do
{:ok, data} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:cross_join_lateral})}")
end
# Logger.debug("Cross join lateral #{inspect from, pretty: true}")
# Logger.debug("Cross join lateral #{inspect expr, pretty: true}")
ncolumns =
case expr do
{:fn, {"unnest", [_from]}} ->
[{:tmp, "unnest", "unnest"}]
{:fn, {"unnest", [_from | columns]}} ->
Enum.map(columns, fn {:lit, col} -> {:tmp, "unnest", col} end)
{:fn, {func, _args}} ->
[{:tmp, func, func}]
{:alias, {:fn, {"unnest", [_from]}}, alias_} ->
[{:tmp, alias_, alias_}]
{:alias, {:fn, {"unnest", [_from | columns]}}, alias_} ->
Enum.map(columns, fn {:lit, col} -> {:tmp, alias_, col} end)
{:alias, {:fn, {_func, _args}}, alias_} ->
[{:tmp, alias_, alias_}]
{:select, _expr, cols} ->
resolve_column_names(cols, Map.get(context, :columns, []))
_ ->
data.columns
end
# It accumulative to allow nested cross
parent_columns = Map.get(context, :parent_columns, []) ++ data.columns
context = Map.put(context, :parent_columns, parent_columns)
nrows =
Enum.flat_map(data.rows, fn row ->
parent_row = Map.get(context, :parent_row, []) ++ row
context = Map.put(context, :parent_row, parent_row)
sexpr = ExoSQL.Expr.simplify(expr, context)
res =
case sexpr do
{:select, _, _} = select ->
{:ok, data} = execute(select, context)
data.rows
sexpr ->
ExoSQL.Expr.run_expr(sexpr, context)
end
# Logger.debug("Expression is #{inspect expr, pretty: true}:\n---\n#{inspect res, pretty: true}")
case res do
res when is_list(res) ->
res
|> Enum.map(fn
nrow when is_list(nrow) ->
nrow ++ row
other ->
[other] ++ row
end)
%{columns: _rcolumns, rows: rrows} ->
rrows |> Enum.map(&(&1 ++ row))
other ->
[other] ++ row
end
end)
result = %ExoSQL.Result{
columns: ncolumns ++ data.columns,
rows: nrows
}
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:cross_join_lateral_end})}")
end
# Logger.debug("Got result #{inspect result, pretty: true}")
{:ok, result}
end
def execute({:group_by, from, groups}, context) do
{:ok, data} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:group_by, groups})}")
end
context = Map.put(context, :columns, data.columns)
sgroups = Enum.map(groups, &ExoSQL.Expr.simplify(&1, context))
rows =
Enum.reduce(data.rows, %{}, fn row, acc ->
set = Enum.map(sgroups, &ExoSQL.Expr.run_expr(&1, Map.put(context, :row, 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, data.columns) ++ ["group_by"]
# Logger.debug("Grouped rows: #{inspect columns}\n #{inspect rows, pretty: true}")
{:ok,
%ExoSQL.Result{
columns: columns,
rows: rows
}}
end
def execute({:order_by, type, expr, from}, context) do
{:ok, data} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:order_by, type, expr})}")
end
context2 = Map.put(context, :columns, data.columns)
expr =
case expr do
{:lit, n} ->
{:column, n}
_other ->
ExoSQL.Expr.simplify(expr, context2)
end
rows =
if type == :asc do
Enum.sort_by(data.rows, &ExoSQL.Expr.run_expr(expr, Map.put(context, :row, &1)))
else
Enum.sort_by(data.rows, &ExoSQL.Expr.run_expr(expr, Map.put(context, :row, &1)), &>=/2)
end
{:ok,
%ExoSQL.Result{
columns: data.columns,
rows: rows
}}
end
def execute({:table_to_row, from}, context) do
{:ok, data} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:table_to_row})}")
end
{:ok,
%ExoSQL.Result{
columns: ["group_by"],
rows: [[data]]
}}
end
# alias of fn renames the table and the column inside
def execute({:alias, {:fn, {function, params}}, alias_}, context) do
params =
params
|> Enum.map(&ExoSQL.Expr.simplify(&1, context))
res = ExoSQL.Expr.run_expr({:fn, {function, params}}, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:alias, {:fn, {function, params}}, alias_})}")
end
columns =
Enum.map(res.columns, fn _column ->
{:tmp, alias_, alias_}
end)
{:ok,
%ExoSQL.Result{
columns: columns,
rows: res.rows
}}
end
def execute({:alias, from, alias_}, context) do
{:ok, data} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:alias, alias_})}")
end
columns =
Enum.map(data.columns, fn {_db, _table, column} ->
{:tmp, alias_, column}
end)
# Logger.debug("Set alias for #{inspect alias_} #{inspect data} -> #{inspect columns}")
{:ok,
%ExoSQL.Result{
columns: columns,
rows: data.rows
}}
end
def execute({:offset, offset, from}, context) do
{:ok, data} = execute(from, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:offset, offset})}")
end
rows = Enum.drop(data.rows, offset)
{:ok,
%ExoSQL.Result{
columns: data.columns,
rows: rows
}}
end
def execute({:limit, limit, from}, context) do
{:ok, data} = execute(from, context)
rows = Enum.take(data.rows, limit)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:limit, limit})}")
end
{:ok,
%ExoSQL.Result{
columns: data.columns,
rows: rows
}}
end
def execute({:union, froma, fromb}, context) do
taska = Task.async(fn -> execute(froma, context) end)
taskb = Task.async(fn -> execute(fromb, context) end)
{:ok, dataa} = Task.await(taska)
{:ok, datab} = Task.await(taskb)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:union})}")
end
if Enum.count(dataa.columns) != Enum.count(datab.columns) do
{:error, {:union_column_count_mismatch}}
else
{:ok,
%ExoSQL.Result{
columns: dataa.columns,
rows: dataa.rows ++ datab.rows
}}
end
end
def execute({:with, {name, plan}, next}, context) do
{:ok, data} = execute(plan, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:with, name})}")
end
data = %{data | columns: Enum.map(data.columns, fn {_, _, col} -> {:with, name, col} end)}
newwith = Map.put(Map.get(context, :with, %{}), name, data)
context = Map.put(context, :with, newwith)
execute(next, context)
end
def execute(%ExoSQL.Result{} = res, context), do: {:ok, res}
def execute({:crosstab, ctcolumns, query}, context) do
{:ok, data} = execute(query, context)
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:crosstab, ctcolumns, query})}")
end
first_column = hd(data.columns)
columns =
case ctcolumns do
:all_columns ->
Enum.reduce(data.rows, MapSet.new(), fn [_, name, _], acc ->
MapSet.put(acc, name)
end)
|> MapSet.to_list()
|> Enum.sort()
other ->
other
end
columns_ri = Enum.with_index(columns) |> Map.new()
empty_row = Enum.map(columns, fn _ -> nil end)
rows =
Enum.reduce(data.rows, %{}, fn [row, column, value], acc ->
current = Map.get(acc, row, empty_row)
ncurrent =
case Map.get(columns_ri, column, nil) do
nil ->
current
coln ->
List.replace_at(current, coln, value)
end
Map.put(acc, row, ncurrent)
end)
columns = [first_column | columns |> Enum.map(&{:tmp, :tmp, &1})]
rows = rows |> Enum.map(fn {k, v} -> [k | v] end)
{:ok,
%{
columns: columns,
rows: rows
}}
end
def project_row([head]) do
cond do
is_list(head) ->
[head]
is_map(head) ->
%{columns: _columns, rows: rows} = head
rows
true ->
[head]
end
end
def project_row([head | rest]) do
rest_rows = project_row(rest)
cond do
is_list(head) ->
Enum.flat_map(head, fn h -> Enum.map(rest_rows, fn r -> h ++ [r] end) end)
is_map(head) ->
%{columns: _columns, rows: rows} = head
Enum.flat_map(rows, fn h -> Enum.map(rest_rows, fn r -> h ++ [r] end) end)
true ->
for r <- rest_rows do
[head | r]
end
end
end
def project_columns([chead], [rhead]) do
# Logger.debug("Project columns #{inspect {chead, rhead}}")
cond do
is_list(rhead) ->
[chead]
is_map(rhead) ->
%{columns: columns, rows: _rows} = rhead
# special case, I inherit the column name from alias or 1. If more columns at inner table, sorry
column_change? = columns in [[{:tmp, :tmp, "generate_series"}], [{:tmp, :tmp, "unnest"}]]
# Logger.debug("Change column names #{inspect chead} #{inspect columns} #{inspect column_change?}")
if column_change? do
[chead]
else
columns
end
true ->
[chead]
end
end
def project_columns([chead | crest], [rhead | rrest]) do
rest_columns = project_columns(crest, rrest)
me_column =
cond do
is_list(chead) ->
[chead]
is_map(rhead) ->
%{columns: columns, rows: _rows} = rhead
columns
true ->
[chead]
end
me_column ++ rest_columns
end
def execute_join(table1, table2, expr, context, no_match_strategy) do
{:ok, res1} = execute(table1, context)
# calculate extraquals with the {:in, "id", [...]} form, or none and
# do a full query
# if it follows the canonical form, then all OK
table2 =
case table2 do
{:execute, from2, quals2, columns2} ->
extraquals = get_extra_quals(res1, expr, context)
{:execute, from2, quals2 ++ extraquals, columns2}
# sorry no qual optimization yet TODO
other ->
other
end
# Now we get the final table2. As always if the quals are ignored it is just
# less efficient.
{:ok, res2} = execute(table2, context)
# Logger.debug("Left join of\n\n#{inspect res1, pretty: true}\n\n#{inspect res2, pretty: true}\n\n#{inspect expr}")
# Use hashmap or loop strategy depending on size of second table.
# The size limit is a very arbitrary number that balances the
# cost of creating an M the map and looking into it N times (N*log M+M*logM),
# vs looping and checking N*M. N is out of control, so we focus on M.
if Enum.count(res2.rows) > 50 do
execute_join_hashmap(res1, res2, expr, context, no_match_strategy)
else
execute_join_loop(res1, res2, expr, context, no_match_strategy)
end
end
def execute_join_loop(res1, res2, expr, context, no_match_strategy) do
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:join_loop, expr, no_match_strategy})}")
end
columns = res1.columns ++ res2.columns
context = Map.put(context, :columns, columns)
rexpr = ExoSQL.Expr.simplify(expr, context)
empty_row2 = Enum.map(res2.columns, fn _ -> nil end)
left_match = no_match_strategy == :left
# Logger.debug("Columns #{inspect columns}")
rows =
Enum.reduce(res1.rows, [], fn row1, acc ->
nrows =
Enum.map(res2.rows, fn row2 ->
row = row1 ++ row2
if ExoSQL.Expr.run_expr(rexpr, Map.put(context, :row, row)) do
row
else
nil
end
end)
|> Enum.filter(&(&1 != nil))
nrows =
if nrows == [] and left_match do
[row1 ++ empty_row2]
else
nrows
end
# Logger.debug("Test row #{inspect nrow} #{inspect rexpr}")
nrows ++ acc
end)
# Logger.debug("Result #{inspect no_match_strategy} #{inspect rows, pretty: true}")
{:ok,
%ExoSQL.Result{
columns: columns,
rows: rows
}}
end
def execute_join_hashmap(res1, res2, expr, context, no_match_strategy) do
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:join_hashmap, expr, no_match_strategy})}")
end
left_match = no_match_strategy == :left
case hashmap_decompose_expr(res1.columns, expr) do
{expra, exprb} ->
expra = ExoSQL.Expr.simplify(expra, Map.put(context, :columns, res1.columns))
exprb = ExoSQL.Expr.simplify(exprb, Map.put(context, :columns, res2.columns))
mapb =
Enum.reduce(res2.rows, %{}, fn row, acc ->
{_, map} =
Map.get_and_update(acc, ExoSQL.Expr.run_expr(exprb, Map.put(context, :row, row)), fn
nil -> {nil, [row]}
list -> {nil, [row | list]}
end)
map
end)
empty_row2 = Enum.map(res2.columns, fn _ -> nil end)
rows =
Enum.reduce(res1.rows, [], fn row1, acc ->
vala = ExoSQL.Expr.run_expr(expra, Map.put(context, :row, row1))
rows2 = Map.get(mapb, vala, [])
nrows =
if rows2 == [] and left_match do
[row1 ++ empty_row2]
else
Enum.map(rows2, fn row2 ->
row1 ++ row2
end)
end
nrows ++ acc
end)
columns = res1.columns ++ res2.columns
{:ok,
%ExoSQL.Result{
columns: columns,
rows: rows
}}
_ ->
Logger.debug("Cant decompose expr, use loop strategy (#{inspect(expr)})")
execute_join_loop(res1, res2, expr, context, no_match_strategy)
end
end
def hashmap_decompose_expr(columns, {:op, {"=", {:column, a}, {:column, b}}}) do
at_a = Enum.any?(columns, &(&1 == a))
if at_a do
{{:column, a}, {:column, b}}
else
{{:column, b}, {:column, a}}
end
end
def hashmap_decompose_expr(columns, {:op, {"==", {:column, a}, {:column, b}}}) do
at_a = Enum.any?(columns, &(&1 == a))
if at_a do
{{:column, a}, {:column, b}}
else
{{:column, b}, {:column, a}}
end
end
def hashmap_decompose_expr(_, _), do: nil
def quals_with_vars(quals, vars) do
Enum.map(quals, fn
[op1, op, {:var, variable}] ->
[op1, op, vars[variable]]
other ->
other
end)
end
# common data that given a result, reorders the columns as required
def column_reselect(data, columns, context) do
case data do
%{columns: ^columns, rows: _rows} ->
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:column_reselect, :equal})}")
end
{:ok, data}
%{columns: rcolumns, rows: rows} ->
if ExoSQL.debug_mode(context) do
Logger.debug("ExoSQL Executor #{inspect({:column_reselect, rcolumns, columns})}")
end
new_order = Enum.map(columns, fn col -> Enum.find_index(rcolumns, &(&1 == col)) end)
rows =
Enum.map(rows, fn row ->
Enum.map(new_order, &Enum.at(row, &1))
end)
{:ok,
%ExoSQL.Result{
columns: columns,
rows: rows
}}
end
end
def get_extra_quals(res1, expr, context) do
case expr do
{:op, {"=", {:column, a}, {:column, b}}} ->
get_extra_quals_from_eq(res1, a, b, context)
{:op, {"==", {:column, a}, {:column, b}}} ->
get_extra_quals_from_eq(res1, a, b, context)
_expr ->
[]
end
end
def get_extra_quals_from_eq(res1, a, b, context) do
res1_contains_a =
Enum.find(res1.columns, fn
^a -> true
_ -> false
end)
{idf, idt} =
if res1_contains_a do
{a, b}
else
{b, a}
end
ids = ExoSQL.Expr.simplify({:column, idf}, Map.put(context, :columns, res1.columns))
# Logger.debug("From ltable get #{inspect idf} #{inspect ids}")
inids =
Enum.reduce(res1.rows, [], fn row, acc ->
[ExoSQL.Expr.run_expr(ids, Map.put(context, :row, row)) | acc]
end)
|> Enum.uniq()
# Logger.debug("inids #{inspect inids}")
{_db, _table, columnname} = idt
[{columnname, "IN", inids}]
end
defp resolve_column_names(columns, pcolumns), do: resolve_column_names(columns, pcolumns, 1)
defp resolve_column_names([{:column, col} | rest], pcolumns, count) when is_tuple(col) do
[col | resolve_column_names(rest, pcolumns, count + 1)]
end
defp resolve_column_names([{:column, col} | rest], pcolumns, count) when is_number(col) do
col = Enum.at(pcolumns, col)
[col | resolve_column_names(rest, pcolumns, count + 1)]
end
defp resolve_column_names([{:alias, {oldcol, name}} | rest], pcolumns, count) do
# to keep the table name
[{_db, table, _column}] = resolve_column_names([oldcol], pcolumns, 1)
[{:tmp, table, name} | resolve_column_names(rest, pcolumns, count + 1)]
end
defp resolve_column_names([_other | rest], pcolumns, count) do
[{:tmp, :tmp, "col_#{count}"} | resolve_column_names(rest, pcolumns, count + 1)]
end
defp resolve_column_names([], _pcolumns, _count), do: []
end