Skip to content

Commit

Permalink
refactor Ch type
Browse files Browse the repository at this point in the history
  • Loading branch information
ruslandoga committed Jul 25, 2023
1 parent c88e888 commit 19d5d28
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 170 deletions.
19 changes: 19 additions & 0 deletions bench/cast.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
string = Ecto.ParameterizedType.init(Ch, type: "String")
int64 = Ecto.ParameterizedType.init(Ch, type: "Int64")
nullable_string = Ecto.ParameterizedType.init(Ch, type: "Nullable(String)")
low_cardinality_string = Ecto.ParameterizedType.init(Ch, type: "LowCardinality(String)")
tuple = Ecto.ParameterizedType.init(Ch, type: "Tuple(String, Int64)")
map = Ecto.ParameterizedType.init(Ch, type: "Map(String, UInt64)")

Benchee.run(
%{
"String" => fn -> Ecto.Type.cast(string, "value") end,
"Int64" => fn -> Ecto.Type.cast(int64, 10) end,
"Nullable(String)" => fn -> Ecto.Type.cast(nullable_string, "value") end,
"LowCardinality(String)" => fn -> Ecto.Type.cast(low_cardinality_string, "value") end,
"Tuple(String, Int64)" => fn -> Ecto.Type.cast(tuple, {"value", 10}) end,
"Map(String, UInt64)" => fn -> Ecto.Type.cast(map, %{"value" => 10}) end
},
measure_function_call_overhead: true
# profile_after: :eprof
)
216 changes: 95 additions & 121 deletions lib/ch.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,184 +93,158 @@ defmodule Ch do
end

@impl true
def load(value, _loader, {:tuple, _types}), do: {:ok, value}
def load(value, _loader, {:map, _key_type, _value_type}), do: {:ok, value}
def load(value, _loader, _params), do: {:ok, value}

for type <- [:ipv4, :ipv6, :point, :ring, :polygon, :multipolygon] do
def load(value, _loader, unquote(type)), do: {:ok, value}
end

def load(value, _loader, params), do: Ecto.Type.load(base_type(params), value)
@impl true
def dump(value, _dumper, _params), do: {:ok, value}

@impl true
def dump(value, _dumper, {:tuple, types}) do
process_tuple(types, value, &Ecto.Type.dump/2)
end
def cast(value, :string = type), do: Ecto.Type.cast(type, value)
def cast(value, :boolean = type), do: Ecto.Type.cast(type, value)
def cast(value, :uuid), do: Ecto.Type.cast(Ecto.UUID, value)
def cast(value, :date = type), do: Ecto.Type.cast(type, value)
def cast(value, :date32), do: Ecto.Type.cast(:date, value)
def cast(value, :datetime), do: Ecto.Type.cast(:naive_datetime, value)
def cast(value, {:datetime, "UTC"}), do: Ecto.Type.cast(:utc_datetime, value)
def cast(value, {:datetime64, _p}), do: Ecto.Type.cast(:naive_datetime_usec, value)
def cast(value, {:datetime64, _p, "UTC"}), do: Ecto.Type.cast(:utc_datetime_usec, value)
def cast(value, {:fixed_string, _s}), do: Ecto.Type.cast(:string, value)

def dump(value, _dumper, {:map, key_type, value_type}) do
process_map(value, key_type, value_type, &Ecto.Type.dump/2)
for size <- [8, 16, 32, 64, 128, 256] do
def cast(value, unquote(:"i#{size}")), do: Ecto.Type.cast(:integer, value)
def cast(value, unquote(:"u#{size}")), do: Ecto.Type.cast(:integer, value)
end

def dump(value, _dumper, :ipv4) do
case value do
{_, _, _, _} -> {:ok, value}
nil -> {:ok, value}
_other -> :error
end
for size <- [32, 64] do
def cast(value, unquote(:"f#{size}")), do: Ecto.Type.cast(:float, value)
end

def dump(value, _loader, :ipv6) do
case value do
{_, _, _, _, _, _, _, _} -> {:ok, value}
nil -> {:ok, value}
_other -> :error
end
end
def cast(value, {:decimal = type, _p, _s}), do: Ecto.Type.cast(type, value)

def dump(value, _loader, :point) do
case value do
{x, y} when is_number(x) and is_number(y) -> {:ok, value}
nil -> {:ok, value}
_other -> :error
for size <- [32, 64, 128, 256] do
def cast(value, {unquote(:"decimal#{size}"), _s}) do
Ecto.Type.cast(:decimal, value)
end
end

def dump(value, _dumper, params), do: Ecto.Type.dump(base_type(params), value)
def cast(value, {:array, _type} = array), do: Ecto.Type.cast(array, value)
def cast(value, {:nullable, type}), do: cast(value, type)
def cast(value, {:low_cardinality, type}), do: cast(value, type)
def cast(value, {:simple_aggregate_function, _name, type}), do: cast(value, type)

@impl true
def cast(value, {:tuple, types}) do
with {:ok, value} <- process_tuple(types, value, &Ecto.Type.cast/2) do
{:ok, List.to_tuple(value)}
def cast(value, :ring), do: Ecto.Type.cast({:array, type(:point)}, value)
def cast(value, :polygon), do: Ecto.Type.cast({:array, type(:ring)}, value)
def cast(value, :multipolygon), do: Ecto.Type.cast({:array, type(:polygon)}, value)

def cast(nil, _params), do: {:ok, nil}

def cast(value, {enum, mappings}) when enum in [:enum8, :enum16] do
result =
case value do
_ when is_integer(value) -> List.keyfind(mappings, value, 1, :error)
_ when is_binary(value) -> List.keyfind(mappings, value, 0, :error)
_ -> :error
end

case result do
{_, _} -> {:ok, value}
:error = e -> e
end
end

def cast(value, :ipv4) do
case value do
{_, _, _, _} -> {:ok, value}
_ when is_binary(value) -> :inet.parse_ipv4_address(to_charlist(value))
_ when is_list(value) -> :inet.parse_ipv4_address(value)
nil -> {:ok, value}
_ -> :error
{a, b, c, d} when is_number(a) and is_number(b) and is_number(c) and is_number(d) ->
{:ok, value}

_ when is_binary(value) ->
with {:error = e, _reason} <- :inet.parse_ipv4_address(to_charlist(value)), do: e

_ when is_list(value) ->
with {:error = e, _reason} <- :inet.parse_ipv4_address(value), do: e

_ ->
:error
end
end

def cast(value, :ipv6) do
case value do
{_, _, _, _, _, _, _, _} -> {:ok, value}
_ when is_binary(value) -> :inet.parse_ipv6_address(to_charlist(value))
_ when is_list(value) -> :inet.parse_ipv6_address(value)
nil -> {:ok, value}
_ -> :error
{a, s, d, f, g, h, j, k}
when is_number(a) and is_number(s) and is_number(d) and is_number(f) and
is_number(g) and is_number(h) and is_number(j) and is_number(k) ->
{:ok, value}

_ when is_binary(value) ->
with {:error = e, _reason} <- :inet.parse_ipv6_address(to_charlist(value)), do: e

_ when is_list(value) ->
with {:error = e, _reason} <- :inet.parse_ipv6_address(value), do: e

_ ->
:error
end
end

def cast(value, :point) do
case value do
{x, y} when is_number(x) and is_number(y) -> {:ok, value}
nil -> {:ok, value}
_ -> :error
end
end

def cast(value, {:map, key_type, value_type}) do
with {:ok, value} <- process_map(value, key_type, value_type, &Ecto.Type.cast/2) do
{:ok, Map.new(value)}
end
end

def cast(value, params), do: Ecto.Type.cast(base_type(params), value)

@doc false
def base_type(type)

def base_type(t) when t in [:string, :boolean, :date], do: t
def base_type(:date32), do: :date
def base_type(:datetime), do: :naive_datetime
def base_type(:uuid), do: Ecto.UUID
def cast(value, {:tuple, types}), do: cast_tuple(types, value)
def cast(value, {:map, key_type, value_type}), do: cast_map(value, key_type, value_type)

# TODO
def base_type({:enum8, _mappings}), do: :string
def base_type({:enum16, _mappings}), do: :string

for size <- [8, 16, 32, 64, 128, 256] do
def base_type(unquote(:"i#{size}")), do: :integer
def base_type(unquote(:"u#{size}")), do: :integer
defp cast_tuple(types, values) when is_tuple(values) do
cast_tuple(types, Tuple.to_list(values), [])
end

for size <- [32, 64] do
def base_type(unquote(:"f#{size}")), do: :float
defp cast_tuple(types, values) when is_list(values) do
cast_tuple(types, values, [])
end

def base_type({:array = a, type}), do: {a, base_type(type)}
def base_type({:nullable, type}), do: base_type(type)
def base_type({:low_cardinality, type}), do: base_type(type)
def base_type({:simple_aggregate_function, _name, type}), do: base_type(type)
def base_type({:fixed_string, _size}), do: :string
def base_type({:datetime, "UTC"}), do: :utc_datetime
def base_type({:datetime64, _precision}), do: :naive_datetime_usec
def base_type({:datetime64, _precision, "UTC"}), do: :utc_datetime_usec
def base_type({:decimal = d, _precision, _scale}), do: d

for size <- [32, 64, 128, 256] do
def base_type({unquote(:"decimal#{size}"), _scale}), do: :decimal
end
defp cast_tuple(_types, _values), do: :error

def base_type(:point = p), do: {:parameterized, Ch, p}
def base_type(:ring), do: {:array, base_type(:point)}
def base_type(:polygon), do: {:array, base_type(:ring)}
def base_type(:multipolygon), do: {:array, base_type(:polygon)}

def base_type({:parameterized, Ch, params}), do: base_type(params)

defp process_tuple(types, values, mapper) when is_tuple(values) do
process_tuple(types, Tuple.to_list(values), mapper, [])
end

defp process_tuple(types, values, mapper) when is_list(values) do
process_tuple(types, values, mapper, [])
end

defp process_tuple(_types, nil = value, _mapper), do: {:ok, value}
defp process_tuple(_types, _values, _napper), do: :error

defp process_tuple([t | types], [v | values], mapper, acc) do
case mapper.(base_type(t), v) do
{:ok, v} -> process_tuple(types, values, mapper, [v | acc])
defp cast_tuple([type | types], [value | values], acc) do
case cast(value, type) do
{:ok, value} -> cast_tuple(types, values, [value | acc])
:error = e -> e
end
end

defp process_tuple([], [], _mapper, acc), do: {:ok, :lists.reverse(acc)}
defp process_tuple(_types, _values, _mapper, _acc), do: :error
defp cast_tuple([], [], acc), do: {:ok, List.to_tuple(:lists.reverse(acc))}
defp cast_tuple(_types, _values, _acc), do: :error

defp process_map(value, key_type, value_type, mapper) when is_map(value) do
process_map(Map.to_list(value), key_type, value_type, mapper)
defp cast_map(value, key_type, value_type) when is_map(value) do
cast_map(Map.to_list(value), key_type, value_type)
end

defp process_map(value, key_type, value_type, mapper) when is_list(value) do
process_map(value, base_type(key_type), base_type(value_type), mapper, [])
defp cast_map(value, key_type, value_type) when is_list(value) do
cast_map(value, key_type, value_type, [])
end

defp process_map(nil = value, _key_type, _value_type, _mapper), do: {:ok, value}

defp process_map(_value, _key_type, _value_type, _mapper), do: :error
defp cast_map(_value, _key_type, _value_type), do: :error

defp process_map([{k, v} | kvs], key_type, value_type, mapper, acc) do
with {:ok, k} <- mapper.(key_type, k),
{:ok, v} <- mapper.(value_type, v) do
process_map(kvs, key_type, value_type, mapper, [{k, v} | acc])
else
:error = e -> e
defp cast_map([{key, value} | kvs], key_type, value_type, acc) do
with {:ok, key} <- cast(key, key_type),
{:ok, value} <- cast(value, value_type) do
cast_map(kvs, key_type, value_type, [{key, value} | acc])
end
end

defp process_map([], _key_type, _value_type, _mapper, acc), do: {:ok, :lists.reverse(acc)}
defp process_map(_kvs, _key_type, _value_type, _mapper, _acc), do: :error
defp cast_map([], _key_type, _value_type, acc), do: {:ok, Map.new(acc)}
defp cast_map(_kvs, _key_type, _value_type, _acc), do: :error

@impl true
def embed_as(_, _), do: :self

@impl true
def equal?(a, b, _), do: a == b

@impl true
def format(params) do
"#Ch<#{Ch.Types.encode(params)}>"
end
end
end
Loading

0 comments on commit 19d5d28

Please sign in to comment.