lib/clickhouse/data_type/encodable.ex

defprotocol ClickHouse.DataType.Encodable do
  @moduledoc """
  A protocol to encode Elixir data types into ClickHouse data types.
  """

  @spec encode(any()) :: iodata()
  def encode(data)
end

defimpl ClickHouse.DataType.Encodable, for: Atom do
  @moduledoc false

  import ClickHouse.Utils, only: [escape: 1]

  @spec encode(atom()) :: iodata()
  def encode(false), do: "0"
  def encode(true), do: "1"
  def encode(nil), do: "NULL"

  def encode(atom) do
    ["'", atom |> to_string() |> escape(), "'"]
  end
end

defimpl ClickHouse.DataType.Encodable, for: Date do
  @moduledoc false

  @spec encode(Date.t()) :: iodata()
  def encode(date) do
    ["'", to_string(date), "'"]
  end
end

defimpl ClickHouse.DataType.Encodable, for: DateTime do
  @moduledoc false

  @spec encode(DateTime.t()) :: iodata()
  def encode(datetime) do
    datetime =
      datetime
      |> DateTime.truncate(:second)
      |> DateTime.to_iso8601()
      |> String.replace("Z", "")

    ["'", datetime, "'"]
  end
end

defimpl ClickHouse.DataType.Encodable, for: Float do
  @moduledoc false

  @spec encode(float()) :: iodata()
  def encode(float), do: to_string(float)
end

defimpl ClickHouse.DataType.Encodable, for: Integer do
  @moduledoc false

  @spec encode(integer()) :: iodata()
  def encode(integer), do: to_string(integer)
end

defimpl ClickHouse.DataType.Encodable, for: List do
  @moduledoc false

  import ClickHouse.Utils, only: [intersperse_map: 3]

  alias ClickHouse.DataType

  @spec encode(list()) :: iodata()
  def encode(list) do
    ["[", intersperse_map(list, ",", &DataType.encode/1), "]"]
  end
end

defimpl ClickHouse.DataType.Encodable, for: BitString do
  @moduledoc false

  import ClickHouse.Utils, only: [escape: 1]

  @spec encode(binary()) :: iodata()
  def encode(string) do
    ["'", escape(string), "'"]
  end
end