defmodule Tds.Parameter do @moduledoc false alias Tds.Types @type t :: %__MODULE__{ name: String.t() | nil, direction: :input | :output, value: String.t() | nil, type: atom() | nil, length: nil | integer } defstruct name: "", direction: :input, value: "", type: nil, length: nil def option_flags(%__MODULE__{direction: direction, value: value}) do fByRefValue = case direction do :output -> 1 _ -> 0 end fDefaultValue = case value do :default -> 1 _ -> 0 end <<0::size(6), fDefaultValue::size(1), fByRefValue::size(1)>> end def prepared_params(params) do params |> List.wrap() |> name(0) |> Enum.map_join(", ", fn param -> param |> fix_data_type() |> Types.encode_param_descriptor() end) end @doc """ Prepares parameters by giving them names, define missing type, encoding value if necessary. """ def prepare_params(params) do params |> List.wrap() |> name(0) |> Enum.map(&fix_data_type/1) end def name(params, name) do do_name(params, name, []) end def do_name([param | tail], name, acc) do name = name + 1 param = case param do %__MODULE__{name: nil} -> fix_data_type(%{param | name: "@#{name}"}) %__MODULE__{} -> fix_data_type(param) raw_param -> fix_data_type(raw_param, name) end do_name(tail, name, [param | acc]) end def do_name([], _, acc) do acc end def fix_data_type(%__MODULE__{type: type, value: _value} = param) when not is_nil(type) do param end def fix_data_type(%__MODULE__{type: nil, value: nil} = param) do # should fix Ecto has_one, on_change :nullify issue where type is not known when Ecto # builds query/statement for on_change callback %{param | type: :binary} end def fix_data_type(%__MODULE__{value: value} = param) when is_boolean(value) do %{param | type: :boolean} end def fix_data_type(%__MODULE__{value: value} = param) when is_binary(value) and value == "" do %{param | type: :string} end def fix_data_type(%__MODULE__{value: value} = param) when is_binary(value) do if String.valid?(value) do %{param | type: :string} else %{param | type: :binary} end end def fix_data_type(%__MODULE__{value: value} = param) when is_integer(value) do # if -2_147_483_648 >= value and value <= 2_147_483_647 do # %{param | type: :integer} # else # %{param | type: :bigint} # end %{param | type: :integer} end def fix_data_type(%__MODULE__{value: value} = param) when is_float(value) do %{param | type: :float} end def fix_data_type(%__MODULE__{value: %Decimal{}} = param) do %{param | type: :decimal} end def fix_data_type(%__MODULE__{value: {{_, _, _}}} = param) do %{param | type: :date} end def fix_data_type(%__MODULE__{value: %Date{}} = param) do %{param | type: :date} end def fix_data_type(%__MODULE__{value: {{_, _, _, _}}} = param) do %{param | type: :time} end def fix_data_type(%__MODULE__{value: %Time{}} = param) do %{param | type: :time} end def fix_data_type(%__MODULE__{value: {{_, _, _}, {_, _, _}}} = param) do %{param | type: :datetime} end def fix_data_type(%__MODULE__{value: %NaiveDateTime{microsecond: {_, s}}} = param) do type = if s > 3, do: :datetime2, else: :datetime %{param | type: type} end def fix_data_type(%__MODULE__{value: {{_, _, _}, {_, _, _, fsec}}} = param) do # todo: enable warning and introduce Tds.Types.DateTime2 and Tds.Types.DateTime # Logger.warn(fn -> "Datetime as tuple is obsolete, please use NaiveDateTime." end) type = if rem(fsec, 1000) > 0, do: :datetime2, else: :datetime %{param | type: type} end def fix_data_type(%__MODULE__{value: %DateTime{}} = param) do %{param | type: :datetimeoffset} end def fix_data_type(%__MODULE__{value: {{_y, _m, _d}, _time, _offset}} = param) do %{param | type: :datetimeoffset} end def fix_data_type(%__MODULE__{} = raw_param, acc) do param = if is_nil(raw_param.name) do %{raw_param | name: "@#{acc}"} else raw_param end fix_data_type(param) end def fix_data_type(raw_param, acc) do fix_data_type(%__MODULE__{name: "@#{acc}", value: raw_param}) end end