From 12940ca372318bff9bcc99cad5a027bd8b6931ab Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Fri, 2 Nov 2018 11:19:55 -0400 Subject: [PATCH 1/8] format cql insert and cql update --- lib/triton/cql/insert.ex | 15 +++++++++----- lib/triton/cql/update.ex | 43 +++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/triton/cql/insert.ex b/lib/triton/cql/insert.ex index 7b30abb..e3cdecc 100644 --- a/lib/triton/cql/insert.ex +++ b/lib/triton/cql/insert.ex @@ -2,13 +2,18 @@ defmodule Triton.CQL.Insert do def build(query) do schema = query[:__schema__].__fields__ - insert(query[:insert], query[:__table__], schema) <> - if_not_exists(query[:if_not_exists]) + insert(query[:insert], query[:__table__], schema) <> if_not_exists(query[:if_not_exists]) end - defp insert(fields, table, schema) when is_list(fields), do: "INSERT INTO #{table} (#{field_keys(fields)}) VALUES (#{field_values(fields, schema)})" - defp field_keys(fields) when is_list(fields), do: fields |> Enum.map(fn {k, _} -> k end) |> Enum.join(", ") - defp field_values(fields, schema) when is_list(fields), do: fields |> Enum.map(fn {k, v} -> field_value(v, schema[k][:type]) end) |> Enum.join(", ") + defp insert(fields, table, schema) when is_list(fields), + do: "INSERT INTO #{table} (#{field_keys(fields)}) VALUES (#{field_values(fields, schema)})" + + defp field_keys(fields) when is_list(fields), + do: fields |> Enum.map(fn {k, _} -> k end) |> Enum.join(", ") + + defp field_values(fields, schema) when is_list(fields), + do: fields |> Enum.map(fn {k, v} -> field_value(v, schema[k][:type]) end) |> Enum.join(", ") + defp field_value(nil, _), do: "NULL" defp field_value(field, {_, _}), do: field defp field_value(field, _) when is_boolean(field), do: "#{field}" diff --git a/lib/triton/cql/update.ex b/lib/triton/cql/update.ex index 109b927..9250dd2 100644 --- a/lib/triton/cql/update.ex +++ b/lib/triton/cql/update.ex @@ -3,23 +3,48 @@ defmodule Triton.CQL.Update do schema = query[:__schema__].__fields__ update(query[:__table__]) <> - set(query[:update], schema) <> - where(query[:where], schema) <> - constrain(query[:constrain], schema) <> - if_exists(query[:if_exists]) + set(query[:update], schema) <> + where(query[:where], schema) <> + constrain(query[:constrain], schema) <> if_exists(query[:if_exists]) end defp update(table), do: "UPDATE #{table}" - defp set(assignments, schema) when is_list(assignments), do: " SET #{assignments |> Enum.map(fn {k, v} -> "#{k} = #{value(v, schema[k][:type])}" end) |> Enum.join(", ")}" - defp where(fragments, schema) when is_list(fragments), do: " WHERE " <> (fragments |> Enum.flat_map(fn fragment -> where_fragment(fragment, schema) end) |> Enum.join(" AND ")) + defp set(assignments, schema) when is_list(assignments), + do: + " SET #{ + assignments + |> Enum.map(fn {k, v} -> "#{k} = #{value(v, schema[k][:type])}" end) + |> Enum.join(", ") + }" + + defp where(fragments, schema) when is_list(fragments), + do: + " WHERE " <> + (fragments + |> Enum.flat_map(fn fragment -> where_fragment(fragment, schema) end) + |> Enum.join(" AND ")) + defp where(_, _), do: "" - defp where_fragment({k, v}, schema) when is_list(v), do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}, schema) end) + + defp where_fragment({k, v}, schema) when is_list(v), + do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}, schema) end) + defp where_fragment({k, v}, schema), do: ["#{k} = #{value(v, schema[k][:type])}"] - defp where_fragment({k, :in, v}, schema), do: "#{k} IN (#{v |> Enum.map(fn v -> value(v, schema[k][:type]) end) |> Enum.join(", ")})" + + defp where_fragment({k, :in, v}, schema), + do: "#{k} IN (#{v |> Enum.map(fn v -> value(v, schema[k][:type]) end) |> Enum.join(", ")})" + defp where_fragment({k, c, v}, schema), do: "#{k} #{c} #{value(v, schema[k][:type])}" - defp constrain(constraints, schema) when is_list(constraints), do: " IF #{constraints |> Enum.map(fn {k, v} -> "#{k} = #{value(v, schema[k][:type])}" end) |> Enum.join(" AND ")}" + defp constrain(constraints, schema) when is_list(constraints), + do: + " IF #{ + constraints + |> Enum.map(fn {k, v} -> "#{k} = #{value(v, schema[k][:type])}" end) + |> Enum.join(" AND ") + }" + defp constrain(_, _), do: "" defp if_exists(flag) when flag == true, do: " IF EXISTS" From c2a5298a5c8a48836ba92dc2a82061de430d1e0e Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Fri, 2 Nov 2018 12:09:17 -0400 Subject: [PATCH 2/8] Refactor CQL.Delete & CQL.Insert to spend less time coercing strings --- lib/triton/cql/delete.ex | 39 +++++++++++++++++++++++---------- lib/triton/cql/select.ex | 47 +++++++++++++++++++++++++--------------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/lib/triton/cql/delete.ex b/lib/triton/cql/delete.ex index 84b0560..945fb02 100644 --- a/lib/triton/cql/delete.ex +++ b/lib/triton/cql/delete.ex @@ -1,28 +1,43 @@ defmodule Triton.CQL.Delete do def build(query) do delete(query[:delete], query[:where], query[:__table__]) <> - constrain(query[:constrain]) <> - if_exists(query[:if_exists]) + constrain(query[:constrain]) <> if_exists(query[:if_exists]) end - defp delete(fields, where, table) when is_list(fields) and is_list(where), do: "DELETE #{fields |> Enum.join(", ")} FROM #{table}#{where(where)}" - defp delete(:all, where, table) when is_list(where), do: "DELETE FROM #{table}#{where(where)}" + defp delete(fields, where, table) when is_list(fields) and is_list(where), + do: "DELETE " <> Enum.join(fields, ", ") <> " FROM #{table}" <> where(where) + + defp delete(:all, where, table) when is_list(where), do: "DELETE FROM #{table}" <> where(where) defp delete(_, _, _), do: "" - defp where(fragments) when is_list(fragments), do: " WHERE " <> (fragments |> Enum.flat_map(fn fragment -> where_fragment(fragment) end) |> Enum.join(" AND ")) + defp where(fragments) when is_list(fragments), + do: + " WHERE " <> + (fragments + |> Enum.flat_map(fn fragment -> where_fragment(fragment) end) + |> Enum.join(" AND ")) + defp where(_), do: "" - defp where_fragment({k, v}) when is_list(v), do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}) end) - defp where_fragment({k, v}), do: ["#{k} = #{value(v)}"] - defp where_fragment({k, :in, v}), do: "#{k} IN (#{v |> Enum.map(fn v -> value(v) end) |> Enum.join(", ")})" - defp where_fragment({k, c, v}), do: "#{k} #{c} #{value(v)}" - defp constrain(constraints) when is_list(constraints), do: " IF #{constraints |> Enum.map(fn {k, v} -> "#{k} = #{value(v)}" end) |> Enum.join(" AND ")}" + defp where_fragment({k, v}) when is_list(v), + do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}) end) + + defp where_fragment({k, v}), do: ["#{k} = " <> value(v)] + + defp where_fragment({k, :in, v}), + do: "#{k} IN (" <> Enum.map_join(v, ", ", fn v -> value(v) end) <> ")" + + defp where_fragment({k, c, v}), do: "#{k} #{c} " <> value(v) + + defp constrain(constraints) when is_list(constraints), + do: " IF " <> Enum.map_join(constraints, ", ", fn {k, v} -> "#{k} = " <> value(v) end) + defp constrain(_), do: "" defp if_exists(flag) when flag == true, do: " IF EXISTS" defp if_exists(_), do: "" - defp value(v) when is_binary(v), do: "'#{v}'" + defp value(v) when is_binary(v), do: "'" <> v <> "'" defp value(v) when is_atom(v), do: ":#{v}" - defp value(v), do: v + defp value(v), do: to_string(v) end diff --git a/lib/triton/cql/select.ex b/lib/triton/cql/select.ex index ef0a35b..5975a82 100644 --- a/lib/triton/cql/select.ex +++ b/lib/triton/cql/select.ex @@ -3,27 +3,41 @@ defmodule Triton.CQL.Select do schema = query[:__schema__].__fields__ select(query[:select], query[:count], query[:__table__], schema) <> - where(query[:where]) <> - order_by(query[:order_by] && List.first(query[:order_by])) <> - limit(query[:limit]) <> - allow_filtering(query[:allow_filtering]) + where(query[:where]) <> + order_by(query[:order_by] && List.first(query[:order_by])) <> + limit(query[:limit]) <> allow_filtering(query[:allow_filtering]) end - defp select(_, count, table, _) when count === true, do: "SELECT COUNT(*) FROM #{table}" + defp select(_, count, table, _) when count == true, do: "SELECT COUNT(*) FROM #{table}" + defp select(fields, _, table, schema) when is_list(fields) do - schema_fields = schema |> Enum.map(fn {k, _} -> "#{k}" end) - req_fields = fields |> Enum.map(fn k -> "#{k}" end) - filtered_fields = MapSet.intersection(MapSet.new(req_fields), MapSet.new(schema_fields)) |> Enum.into([]) - "SELECT #{Enum.join(filtered_fields, ", ")} FROM #{table}" + schema_fields = schema |> Enum.into(MapSet.new(), fn {k, _} -> to_string(k) end) + req_fields = fields |> Enum.into(MapSet.new(), &to_string/1) + filtered_fields = MapSet.intersection(req_fields, schema_fields) |> Enum.join(", ") + + "SELECT " <> filtered_fields <> " FROM #{table}" end + defp select(_, _, table, _), do: "SELECT * FROM #{table}" - defp where(fragments) when is_list(fragments), do: " WHERE " <> (fragments |> Enum.flat_map(fn fragment -> where_fragment(fragment) end) |> Enum.join(" AND ")) + defp where(fragments) when is_list(fragments), + do: + " WHERE " <> + (fragments + |> Enum.flat_map(fn fragment -> where_fragment(fragment) end) + |> Enum.join(" AND ")) + defp where(_), do: "" - defp where_fragment({k, v}) when is_list(v), do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}) end) - defp where_fragment({k, v}), do: ["#{k} = #{value(v)}"] - defp where_fragment({k, :in, v}), do: "#{k} IN (#{v |> Enum.map(fn v -> value(v) end) |> Enum.join(", ")})" - defp where_fragment({k, c, v}), do: "#{k} #{c} #{value(v)}" + + defp where_fragment({k, v}) when is_list(v), + do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}) end) + + defp where_fragment({k, v}), do: ["#{k} = " <> value(v)] + + defp where_fragment({k, :in, v}), + do: "#{k} IN ( " <> Enum.map_join(v, ", ", fn v -> value(v) end) <> ")" + + defp where_fragment({k, c, v}), do: "#{k} #{c} " <> value(v) defp order_by({field, direction}), do: " ORDER BY #{field} #{direction}" defp order_by(_), do: "" @@ -35,8 +49,7 @@ defmodule Triton.CQL.Select do defp allow_filtering(true), do: " ALLOW FILTERING" defp allow_filtering(_), do: "" - defp value(v) when is_binary(v), do: "'#{v}'" - defp value(v) when is_boolean(v), do: "#{v}" + defp value(v) when is_binary(v), do: "'" <> v <> "'" defp value(v) when is_atom(v), do: ":#{v}" - defp value(v), do: v + defp value(v), do: to_string(v) end From 4ddcc387465f2893b2ef5528123401a6f61b8ae7 Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Fri, 2 Nov 2018 12:11:38 -0400 Subject: [PATCH 3/8] Convert Elixir Maps to Cassandra maps, refactor insert & update --- lib/triton/cql/helper.ex | 39 ++++++++++++++++++++++++++++++++++ lib/triton/cql/insert.ex | 26 ++++++----------------- lib/triton/cql/update.ex | 45 ++++++++++++---------------------------- 3 files changed, 58 insertions(+), 52 deletions(-) create mode 100644 lib/triton/cql/helper.ex diff --git a/lib/triton/cql/helper.ex b/lib/triton/cql/helper.ex new file mode 100644 index 0000000..335ad45 --- /dev/null +++ b/lib/triton/cql/helper.ex @@ -0,0 +1,39 @@ +defmodule Triton.CQL.Helper do + def field_value(nil, _), do: "NULL" + def field_value(field, {:map, _}) when is_map(field), do: binary_value(field) + + def field_value(field, :timestamp) when is_binary(field) do + {:ok, timestamp, _} = DateTime.from_iso8601(field) + DateTime.to_unix(timestamp, :millisecond) + end + + def field_value(v, :counter), do: v + + def field_value(field, {_, _}), do: field + def field_value(field, _) when is_boolean(field), do: to_string(field) + def field_value(field, _) when is_binary(field), do: binary_value(field) + def field_value(field, _) when is_atom(field), do: ":#{field}" + def field_value(%DateTime{} = d, _), do: DateTime.to_unix(d, :millisecond) + def field_value(field, _), do: field + + def if_not_exists(flag) when flag == true, do: " IF NOT EXISTS" + def if_not_exists(_), do: "" + + def if_exists(flag) when flag == true, do: " IF EXISTS" + def if_exists(_), do: "" + + def binary_value(v) when is_binary(v) do + if String.valid?(v) && String.contains?(v, ["'", "\""]) do + "$$" <> v <> "$$" + else + "'" <> v <> "'" + end + end + + def binary_value(v) when is_map(v), do: "{" <> Enum.map_join(v, ",", &binary_value/1) <> "}" + + # This will fail if v `is_map`, nested maps are generally not OK + def binary_value({k, v}), do: binary_value(k) <> ": " <> binary_value(v) + + def binary_value(v), do: v |> to_string() +end diff --git a/lib/triton/cql/insert.ex b/lib/triton/cql/insert.ex index e3cdecc..df6f07b 100644 --- a/lib/triton/cql/insert.ex +++ b/lib/triton/cql/insert.ex @@ -1,4 +1,6 @@ defmodule Triton.CQL.Insert do + import Triton.CQL.Helper + def build(query) do schema = query[:__schema__].__fields__ @@ -6,29 +8,13 @@ defmodule Triton.CQL.Insert do end defp insert(fields, table, schema) when is_list(fields), - do: "INSERT INTO #{table} (#{field_keys(fields)}) VALUES (#{field_values(fields, schema)})" + do: + "INSERT INTO #{table} (" <> + field_keys(fields) <> ") VALUES (" <> field_values(fields, schema) <> ")" defp field_keys(fields) when is_list(fields), - do: fields |> Enum.map(fn {k, _} -> k end) |> Enum.join(", ") + do: fields |> Keyword.keys() |> Enum.join(", ") defp field_values(fields, schema) when is_list(fields), do: fields |> Enum.map(fn {k, v} -> field_value(v, schema[k][:type]) end) |> Enum.join(", ") - - defp field_value(nil, _), do: "NULL" - defp field_value(field, {_, _}), do: field - defp field_value(field, _) when is_boolean(field), do: "#{field}" - defp field_value(field, _) when is_binary(field), do: binary_value(field) - defp field_value(field, _) when is_atom(field), do: ":#{field}" - defp field_value(%DateTime{} = d, _), do: DateTime.to_unix(d, :millisecond) - defp field_value(field, _), do: field - - defp if_not_exists(flag) when flag == true, do: " IF NOT EXISTS" - defp if_not_exists(_), do: "" - - defp binary_value(v) do - cond do - String.valid?(v) && String.contains?(v, ["'", "\""]) -> "$$#{v}$$" - true -> "'#{v}'" - end - end end diff --git a/lib/triton/cql/update.ex b/lib/triton/cql/update.ex index 9250dd2..8ab7251 100644 --- a/lib/triton/cql/update.ex +++ b/lib/triton/cql/update.ex @@ -1,4 +1,6 @@ defmodule Triton.CQL.Update do + import Triton.CQL.Helper + def build(query) do schema = query[:__schema__].__fields__ @@ -12,11 +14,10 @@ defmodule Triton.CQL.Update do defp set(assignments, schema) when is_list(assignments), do: - " SET #{ - assignments - |> Enum.map(fn {k, v} -> "#{k} = #{value(v, schema[k][:type])}" end) - |> Enum.join(", ") - }" + " SET " <> + Enum.map_join(assignments, ", ", fn {k, v} -> + "#{k} = " <> field_value(v, schema[k][:type]) + end) defp where(fragments, schema) when is_list(fragments), do: @@ -30,39 +31,19 @@ defmodule Triton.CQL.Update do defp where_fragment({k, v}, schema) when is_list(v), do: v |> Enum.map(fn {c, v} -> where_fragment({k, c, v}, schema) end) - defp where_fragment({k, v}, schema), do: ["#{k} = #{value(v, schema[k][:type])}"] + defp where_fragment({k, v}, schema), do: ["#{k} = " <> field_value(v, schema[k][:type])] defp where_fragment({k, :in, v}, schema), - do: "#{k} IN (#{v |> Enum.map(fn v -> value(v, schema[k][:type]) end) |> Enum.join(", ")})" + do: "#{k} IN (" <> Enum.map_join(v, ", ", fn v -> field_value(v, schema[k][:type]) end) <> ")" - defp where_fragment({k, c, v}, schema), do: "#{k} #{c} #{value(v, schema[k][:type])}" + defp where_fragment({k, c, v}, schema), do: "#{k} #{c} " <> field_value(v, schema[k][:type]) defp constrain(constraints, schema) when is_list(constraints), do: - " IF #{ - constraints - |> Enum.map(fn {k, v} -> "#{k} = #{value(v, schema[k][:type])}" end) - |> Enum.join(" AND ") - }" + " IF " <> + Enum.map_join(constraints, " AND ", fn {k, v} -> + "#{k} = " <> field_value(v, schema[k][:type]) + end) defp constrain(_, _), do: "" - - defp if_exists(flag) when flag == true, do: " IF EXISTS" - defp if_exists(_), do: "" - - defp value(nil, _), do: "NULL" - defp value(v, :counter), do: v - defp value(v, {_, _}), do: v - defp value(v, _) when is_boolean(v), do: "#{v}" - defp value(v, _) when is_binary(v), do: binary_value(v) - defp value(v, _) when is_atom(v), do: ":#{v}" - defp value(%DateTime{} = d, _), do: DateTime.to_unix(d, :millisecond) - defp value(v, _), do: v - - defp binary_value(v) do - cond do - String.valid?(v) && String.contains?(v, ["'", "\""]) -> "$$#{v}$$" - true -> "'#{v}'" - end - end end From 83a71315f7ead6691ddaf41b4896043f3f336a62 Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Fri, 2 Nov 2018 12:12:01 -0400 Subject: [PATCH 4/8] ignore elixir_ls --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b6012c7..c60243c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez + +.elixir_ls \ No newline at end of file From f68db2dbad7ff78fbdcc952c5421cb492f9c803a Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Fri, 2 Nov 2018 14:18:47 -0400 Subject: [PATCH 5/8] Coerce most 'binary values' to sting --- lib/triton/cql/helper.ex | 9 ++++++--- lib/triton/cql/insert.ex | 2 +- lib/triton/cql/update.ex | 18 +++++++----------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/triton/cql/helper.ex b/lib/triton/cql/helper.ex index 335ad45..20f4c23 100644 --- a/lib/triton/cql/helper.ex +++ b/lib/triton/cql/helper.ex @@ -4,7 +4,10 @@ defmodule Triton.CQL.Helper do def field_value(field, :timestamp) when is_binary(field) do {:ok, timestamp, _} = DateTime.from_iso8601(field) - DateTime.to_unix(timestamp, :millisecond) + + timestamp + |> DateTime.to_unix(:millisecond) + |> to_string() end def field_value(v, :counter), do: v @@ -14,7 +17,7 @@ defmodule Triton.CQL.Helper do def field_value(field, _) when is_binary(field), do: binary_value(field) def field_value(field, _) when is_atom(field), do: ":#{field}" def field_value(%DateTime{} = d, _), do: DateTime.to_unix(d, :millisecond) - def field_value(field, _), do: field + def field_value(field, _), do: to_string(field) def if_not_exists(flag) when flag == true, do: " IF NOT EXISTS" def if_not_exists(_), do: "" @@ -35,5 +38,5 @@ defmodule Triton.CQL.Helper do # This will fail if v `is_map`, nested maps are generally not OK def binary_value({k, v}), do: binary_value(k) <> ": " <> binary_value(v) - def binary_value(v), do: v |> to_string() + def binary_value(v), do: v |> to_string() |> binary_value() end diff --git a/lib/triton/cql/insert.ex b/lib/triton/cql/insert.ex index df6f07b..6980b26 100644 --- a/lib/triton/cql/insert.ex +++ b/lib/triton/cql/insert.ex @@ -16,5 +16,5 @@ defmodule Triton.CQL.Insert do do: fields |> Keyword.keys() |> Enum.join(", ") defp field_values(fields, schema) when is_list(fields), - do: fields |> Enum.map(fn {k, v} -> field_value(v, schema[k][:type]) end) |> Enum.join(", ") + do: Enum.map_join(fields, ", ", fn {k, v} -> field_value(v, schema[k][:type]) end) end diff --git a/lib/triton/cql/update.ex b/lib/triton/cql/update.ex index 8ab7251..2508696 100644 --- a/lib/triton/cql/update.ex +++ b/lib/triton/cql/update.ex @@ -13,17 +13,13 @@ defmodule Triton.CQL.Update do defp update(table), do: "UPDATE #{table}" defp set(assignments, schema) when is_list(assignments), - do: - " SET " <> - Enum.map_join(assignments, ", ", fn {k, v} -> - "#{k} = " <> field_value(v, schema[k][:type]) - end) + do: " SET " <> Enum.map_join(assignments, ", ", &key_eq_field_value(&1, schema)) defp where(fragments, schema) when is_list(fragments), do: " WHERE " <> (fragments - |> Enum.flat_map(fn fragment -> where_fragment(fragment, schema) end) + |> Enum.flat_map(& where_fragment(&1, schema)) |> Enum.join(" AND ")) defp where(_, _), do: "" @@ -39,11 +35,11 @@ defmodule Triton.CQL.Update do defp where_fragment({k, c, v}, schema), do: "#{k} #{c} " <> field_value(v, schema[k][:type]) defp constrain(constraints, schema) when is_list(constraints), - do: - " IF " <> - Enum.map_join(constraints, " AND ", fn {k, v} -> - "#{k} = " <> field_value(v, schema[k][:type]) - end) + do: " IF " <> Enum.map_join(constraints, " AND ", &key_eq_field_value(&1, schema)) defp constrain(_, _), do: "" + + defp key_eq_field_value({k, v}, schema) do + "#{k} = " <> field_value(v, schema[k][:type]) + end end From fe31941ebc37b6b9314986552879fd6608bada89 Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Mon, 7 Jan 2019 17:19:51 -0500 Subject: [PATCH 6/8] Fix regression in select statement construction --- lib/triton/cql/select.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/triton/cql/select.ex b/lib/triton/cql/select.ex index 5975a82..5884b77 100644 --- a/lib/triton/cql/select.ex +++ b/lib/triton/cql/select.ex @@ -50,6 +50,7 @@ defmodule Triton.CQL.Select do defp allow_filtering(_), do: "" defp value(v) when is_binary(v), do: "'" <> v <> "'" + defp value(v) when is_boolean(v), do: to_string(v) defp value(v) when is_atom(v), do: ":#{v}" defp value(v), do: to_string(v) end From bfe6d2233fec87389f348242d41bd4dc9c886872 Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Tue, 8 Jan 2019 18:05:58 -0500 Subject: [PATCH 7/8] Add support for LIKE queries with tuple {:name, :like, "%perry%"} --- lib/triton/cql/select.ex | 21 ++++++++++++--------- lib/triton/validate.ex | 2 ++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/triton/cql/select.ex b/lib/triton/cql/select.ex index 5884b77..d4d6d45 100644 --- a/lib/triton/cql/select.ex +++ b/lib/triton/cql/select.ex @@ -2,10 +2,13 @@ defmodule Triton.CQL.Select do def build(query) do schema = query[:__schema__].__fields__ - select(query[:select], query[:count], query[:__table__], schema) <> - where(query[:where]) <> - order_by(query[:order_by] && List.first(query[:order_by])) <> - limit(query[:limit]) <> allow_filtering(query[:allow_filtering]) + select_s = select(query[:select], query[:count], query[:__table__], schema) + where_s = where(query[:where]) + order_by_s = order_by(query[:order_by] && List.first(query[:order_by])) + limit_s = limit(query[:limit]) + filter_s = allow_filtering(query[:allow_filtering]) + + (select_s <> where_s <> order_by_s <> limit_s <> filter_s) end defp select(_, count, table, _) when count == true, do: "SELECT COUNT(*) FROM #{table}" @@ -20,12 +23,12 @@ defmodule Triton.CQL.Select do defp select(_, _, table, _), do: "SELECT * FROM #{table}" - defp where(fragments) when is_list(fragments), - do: + defp where(fragments) when is_list(fragments) do " WHERE " <> (fragments - |> Enum.flat_map(fn fragment -> where_fragment(fragment) end) + |> Enum.flat_map(&where_fragment/1) |> Enum.join(" AND ")) + end defp where(_), do: "" @@ -35,9 +38,9 @@ defmodule Triton.CQL.Select do defp where_fragment({k, v}), do: ["#{k} = " <> value(v)] defp where_fragment({k, :in, v}), - do: "#{k} IN ( " <> Enum.map_join(v, ", ", fn v -> value(v) end) <> ")" + do: ["#{k} IN ( " <> Enum.map_join(v, ", ", fn v -> value(v) end) <> ")"] - defp where_fragment({k, c, v}), do: "#{k} #{c} " <> value(v) + defp where_fragment({k, c, v}), do: ["#{k} #{c} " <> value(v)] defp order_by({field, direction}), do: " ORDER BY #{field} #{direction}" defp order_by(_), do: "" diff --git a/lib/triton/validate.ex b/lib/triton/validate.ex index a3ef755..b87f309 100644 --- a/lib/triton/validate.ex +++ b/lib/triton/validate.ex @@ -41,6 +41,8 @@ defmodule Triton.Validate do defp coerce_fragment({k, v}, fields) when is_list(v), do: {k, v |> Enum.map(fn {c, v} -> coerce_fragment({k, c, v}, fields) end)} defp coerce_fragment({k, v}, fields), do: {k, coerced_value(v, fields[k][:type])} + defp coerce_fragment({k, "like", v}, _fields), do: {k, :like, v} + defp coerce_fragment({k, :like, v}, _fields), do: {k, :like, v} defp coerce_fragment({k, c, v}, fields), do: {c, coerced_value(v, fields[k][:type])} defp coerce_fragment(x, _), do: x From cb25b4b1146feee0a70bbc8c25d1b3295d27c068 Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Thu, 28 Feb 2019 15:25:42 -0500 Subject: [PATCH 8/8] Fix case where query [in: ["a", "b"]] is coerced into [in: "ab"] A string is a list of codepoints, to_string(["a", "b"]) coerces the value to a binary value --- lib/triton/validate.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/triton/validate.ex b/lib/triton/validate.ex index b87f309..0a8e018 100644 --- a/lib/triton/validate.ex +++ b/lib/triton/validate.ex @@ -46,6 +46,9 @@ defmodule Triton.Validate do defp coerce_fragment({k, c, v}, fields), do: {c, coerced_value(v, fields[k][:type])} defp coerce_fragment(x, _), do: x + defp coerced_value(value, type) when is_list(value), + do: Enum.map(value, &coerced_value(&1, type)) + defp coerced_value(value, _) when is_atom(value), do: value defp coerced_value(value, :text) when not is_binary(value), do: to_string(value) defp coerced_value(value, :bigint) when is_binary(value), do: String.to_integer(value)