Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

.elixir_ls
39 changes: 27 additions & 12 deletions lib/triton/cql/delete.ex
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions lib/triton/cql/helper.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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)

timestamp
|> DateTime.to_unix(:millisecond)
|> to_string()
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: to_string(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() |> binary_value()
end
31 changes: 11 additions & 20 deletions lib/triton/cql/insert.ex
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
defmodule Triton.CQL.Insert do
import Triton.CQL.Helper

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 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 insert(fields, table, schema) when is_list(fields),
do:
"INSERT INTO #{table} (" <>
field_keys(fields) <> ") VALUES (" <> field_values(fields, schema) <> ")"

defp if_not_exists(flag) when flag == true, do: " IF NOT EXISTS"
defp if_not_exists(_), do: ""
defp field_keys(fields) when is_list(fields),
do: fields |> Keyword.keys() |> Enum.join(", ")

defp binary_value(v) do
cond do
String.valid?(v) && String.contains?(v, ["'", "\""]) -> "$$#{v}$$"
true -> "'#{v}'"
end
end
defp field_values(fields, schema) when is_list(fields),
do: Enum.map_join(fields, ", ", fn {k, v} -> field_value(v, schema[k][:type]) end)
end
53 changes: 35 additions & 18 deletions lib/triton/cql/select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,45 @@ 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}"
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(&where_fragment/1)
|> Enum.join(" AND "))
end

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: ""
Expand All @@ -35,8 +52,8 @@ 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_boolean(v), do: to_string(v)
defp value(v) when is_atom(v), do: ":#{v}"
defp value(v), do: v
defp value(v), do: to_string(v)
end
58 changes: 30 additions & 28 deletions lib/triton/cql/update.ex
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
defmodule Triton.CQL.Update do
import Triton.CQL.Helper

def build(query) 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 " <> Enum.map_join(assignments, ", ", &key_eq_field_value(&1, schema))

defp where(fragments, schema) when is_list(fragments),
do:
" WHERE " <>
(fragments
|> Enum.flat_map(& where_fragment(&1, schema))
|> 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), 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, 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 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} = " <> field_value(v, schema[k][:type])]

defp where_fragment({k, :in, v}, schema),
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} " <> field_value(v, schema[k][:type])

defp constrain(constraints, schema) when is_list(constraints),
do: " IF " <> Enum.map_join(constraints, " AND ", &key_eq_field_value(&1, schema))

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
defp key_eq_field_value({k, v}, schema) do
"#{k} = " <> field_value(v, schema[k][:type])
end
end
5 changes: 5 additions & 0 deletions lib/triton/validate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ 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

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)
Expand Down