Skip to content

Commit

Permalink
boolean expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
mjaric committed Mar 1, 2017
1 parent a64c0e9 commit 17b9d97
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 33 deletions.
59 changes: 40 additions & 19 deletions lib/tds_ecto/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ if Code.ensure_loaded?(Tds) do
@behaviour Ecto.Adapters.SQL.Connection
# @behaviour Ecto.Adapters.SQL.Query

@unsafe_query_strings ["'\\"]

def connect(opts) do
opts = opts
|> Keyword.put_new(:port, @default_port)
Expand Down Expand Up @@ -163,9 +165,7 @@ if Code.ensure_loaded?(Tds) do
## Query

alias Ecto.Query
alias Ecto.Query.SelectExpr
alias Ecto.Query.QueryExpr
alias Ecto.Query.JoinExpr
alias Ecto.Query.{SelectExpr, QueryExpr, JoinExpr, BooleanExpr}

def all(query) do
sources = create_names(query)
Expand Down Expand Up @@ -434,23 +434,39 @@ if Code.ensure_loaded?(Tds) do
defp lock(nil), do: ""
defp lock(lock_clause), do: " #{lock_clause} "

defp boolean(_name, [], _sources, _query), do: nil
defp boolean(name, query_exprs, sources, query) do
defp boolean(_name, [], _sources, _query), do: []
defp boolean(name, [%{expr: expr, op: op} | query_exprs], sources, query) do
name <> " " <>
Enum.map_join(query_exprs, " AND ", fn
%QueryExpr{expr: expr} ->
case expr do
true -> "(1 = 1)"
false -> "(0 = 1)"
_ -> "(" <> expr(expr, sources, query) <> ")"
end
end)
end
Enum.reduce(query_exprs, {op, paren_expr(expr, sources, query)}, fn
%BooleanExpr{expr: expr, op: op}, {op, acc} ->
{op, acc <> operator_to_boolean(op) <> paren_expr(expr, sources, query) }
%BooleanExpr{expr: expr, op: op}, {_, acc} ->
{op, "(" <> acc <> ")" <> operator_to_boolean(op) <> paren_expr(expr, sources, query)}
end) |> elem(1)
end
# defp boolean(name, query_exprs, sources, query) do
# name <> " " <>
# Enum.map_join(query_exprs, " AND ", fn
# %QueryExpr{expr: true, op: op} ->
# {op, "(1 = 1)"}
# %QueryExpr{expr: false, op: op} ->
# {op, "(0 = 1)"}
# %QueryExpr{expr: expr, op: op} ->
# {op, paren_expr(expr, sources, query)}
# end)
# end

defp operator_to_boolean(:and), do: " AND "
defp operator_to_boolean(:or), do: " OR "

defp paren_expr(expr, sources, query) do
"(" <> expr(expr, sources, query) <> ")"
end
# :^ - represents parameter ix is index number
defp expr({:^, [], [ix]}, _sources, _query) do
"@#{ix+1}"
end

# :. - attribure, table alias name can be get from sources by passing index
defp expr({{:., _, [{:&, _, [idx]}, field]}, _, []}, sources, _query) when is_atom(field) do
{_, name, _} = elem(sources, idx)
"#{name}.#{quote_name(field)}"
Expand Down Expand Up @@ -540,10 +556,15 @@ if Code.ensure_loaded?(Tds) do
end

defp expr(string, _sources, _query) when is_binary(string) do
hex = string
|> :unicode.characters_to_binary(:utf8, {:utf16, :little})
|> Base.encode16(case: :lower)
"CONVERT(nvarchar(max), 0x#{hex})"
if String.contains?(string, @unsafe_query_strings) do
len = String.length(string)
hex = string
|> :unicode.characters_to_binary(:utf8, {:utf16, :little})
|> Base.encode16(case: :lower)
"CONVERT(nvarchar(#{len}), 0x#{hex})"
else
"'#{escape_string(string)}'"
end
end

defp expr(%Decimal{} = decimal, _sources, _query) do
Expand Down
26 changes: 12 additions & 14 deletions test/tds_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,13 @@ defmodule Tds.Ecto.TdsTest do
assert SQL.all(query) == ~s{SELECT 0 FROM [model] AS m0 WITH(NOLOCK)}
end

# # TODO
# # These need to be updated
# test "string escape" do
# query = Model |> select([], "'\\ ") |> normalize
# assert SQL.all(query) == ~s{SELECT '''\\\\ ' FROM [model] AS m0}
test "string escape" do
query = Model |> select([], "'\\ ") |> normalize
assert SQL.all(query) == ~s{SELECT CONVERT(nvarchar(4), 0x27005c0020002000) FROM [model] AS m0}

# query = Model |> select([], "'") |> normalize
# assert SQL.all(query) == ~s{SELECT '''' FROM [model] AS m0}
# end
query = Model |> select([], "'") |> normalize
assert SQL.all(query) == ~s{SELECT '''' FROM [model] AS m0}
end

test "binary ops" do
query = Model |> select([r], r.x == 2) |> normalize
Expand Down Expand Up @@ -208,10 +206,10 @@ defmodule Tds.Ecto.TdsTest do
query = Model |> select([r], fragment("lower(?)", ^value)) |> normalize
assert SQL.all(query) == ~s{SELECT lower(@1) FROM [model] AS m0}

# query = Model |> select([], fragment(title: 2)) |> normalize
# assert_raise ArgumentError, fn ->
# SQL.all(query)
# end
query = Model |> select([], fragment(title: 2)) |> normalize
assert_raise Ecto.QueryError, ~r"TDS adapter does not support keyword or interpolated fragments", fn ->
SQL.all(query)
end
end

test "literals" do
Expand All @@ -224,8 +222,8 @@ defmodule Tds.Ecto.TdsTest do
query = Model |> select([], false) |> normalize
assert SQL.all(query) == ~s{SELECT 0 FROM [model] AS m0}

# query = Model |> select([], "abc") |> normalize
# assert SQL.all(query) == ~s{SELECT 'abc' FROM [model] AS m0}
query = Model |> select([], "abc") |> normalize
assert SQL.all(query) == ~s{SELECT 'abc' FROM [model] AS m0}

query = Model |> select([], 123) |> normalize
assert SQL.all(query) == ~s{SELECT 123 FROM [model] AS m0}
Expand Down

0 comments on commit 17b9d97

Please sign in to comment.