Packages
tds
0.2.8
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.2
2.3.1
2.3.0
2.3.0-rc.1
2.3.0-rc.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
2.0.4
2.0.3
2.0.2
2.0.1
2.0.1-rc2
2.0.1-rc1
2.0.0
retired
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.0
1.0.19
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Microsoft SQL Server client (Elixir implementation of the MS TDS protocol)
Current section
Files
Jump to
Current section
Files
lib/tds/types.ex
defmodule Tds.Types do
import Tds.BinaryUtils
import Tds.Utils
alias Timex.Date
alias Tds.Parameter
alias Tds.DateTime
alias Tds.DateTime2
@gd_epoch :calendar.date_to_gregorian_days({2000, 1, 1})
@gs_epoch :calendar.datetime_to_gregorian_seconds({{2000, 1, 1}, {0, 0, 0}})
@datetime Date.from {{1900, 1, 1}, {0, 0, 0}}
@days_in_month 30
@secs_in_day 24 * 60 * 60
@numeric_base 10_000
@tds_data_type_null 0x1F
@tds_data_type_tinyint 0x30
@tds_data_type_bit 0x32
@tds_data_type_smallint 0x34
@tds_data_type_int 0x38
@tds_data_type_smalldatetime 0x3A
@tds_data_type_real 0x3B
@tds_data_type_money 0x3C
@tds_data_type_datetime 0x3D
@tds_data_type_float 0x3E
@tds_data_type_smallmoney 0x7A
@tds_data_type_bigint 0x7F
@fixed_data_types [
@tds_data_type_null,
@tds_data_type_tinyint,
@tds_data_type_bit,
@tds_data_type_smallint,
@tds_data_type_int,
@tds_data_type_smalldatetime,
@tds_data_type_real,
@tds_data_type_money,
@tds_data_type_datetime,
@tds_data_type_float,
@tds_data_type_smallmoney,
@tds_data_type_bigint
]
@tds_data_type_uniqueidentifier 0x24
@tds_data_type_intn 0x26
@tds_data_type_decimal 0x37
@tds_data_type_numeric 0x3F
@tds_data_type_bitn 0x68
@tds_data_type_decimaln 0x6A
@tds_data_type_numericn 0x6C
@tds_data_type_floatn 0x6D
@tds_data_type_moneyn 0x6E
@tds_data_type_datetimen 0x6F
@tds_data_type_daten 0x28
@tds_data_type_timen 0x29
@tds_data_type_datetime2n 0x2A
@tds_data_type_datetimeoffsetn 0x2B
@tds_data_type_char 0x2F
@tds_data_type_varchar 0x27
@tds_data_type_binary 0x2D
@tds_data_type_varbinary 0x25
@tds_data_type_bigvarbinary 0xA5
@tds_data_type_bigvarchar 0xA7
@tds_data_type_bigbinary 0xAD
@tds_data_type_bigchar 0xAF
@tds_data_type_nvarchar 0xE7
@tds_data_type_nchar 0xEF
@tds_data_type_xml 0xF1
@tds_data_type_udt 0xF0
@tds_data_type_text 0x23
@tds_data_type_image 0x22
@tds_data_type_ntext 0x63
@tds_data_type_variant 0x62
@variable_data_types [
@tds_data_type_uniqueidentifier,
@tds_data_type_intn,
@tds_data_type_decimal,
@tds_data_type_numeric,
@tds_data_type_bitn,
@tds_data_type_decimaln,
@tds_data_type_numericn,
@tds_data_type_floatn,
@tds_data_type_moneyn,
@tds_data_type_datetimen,
@tds_data_type_daten,
@tds_data_type_timen,
@tds_data_type_datetime2n,
@tds_data_type_datetimeoffsetn,
@tds_data_type_char,
@tds_data_type_varchar,
@tds_data_type_binary,
@tds_data_type_varbinary,
@tds_data_type_bigvarbinary,
@tds_data_type_bigvarchar,
@tds_data_type_bigbinary,
@tds_data_type_bigchar,
@tds_data_type_nvarchar,
@tds_data_type_nchar,
@tds_data_type_xml,
@tds_data_type_udt,
@tds_data_type_text,
@tds_data_type_image,
@tds_data_type_ntext,
@tds_data_type_variant
]
@tds_plp_marker 0xffff
@tds_plp_null 0xffffffffffffffff
@tds_plp_unknown 0xfffffffffffffffe
#
# Data Type Decoders
#
def decode_info(<<data_type_code::unsigned-8, tail::binary>>) when data_type_code in @fixed_data_types do
cond do
data_type_code == @tds_data_type_null -> length = 0
data_type_code in [
@tds_data_type_tinyint,
@tds_data_type_bit
] -> length = 1
data_type_code == @tds_data_type_smallint -> length = 2
data_type_code in [
@tds_data_type_int,
@tds_data_type_smalldatetime,
@tds_data_type_real,
@tds_data_type_smallmoney
] -> length = 4
data_type_code in [
@tds_data_type_datetime,
@tds_data_type_float,
@tds_data_type_money,
@tds_data_type_bigint
] -> length = 8
end
{%{data_type: :fixed, data_type_code: data_type_code, length: length},tail}
end
def decode_info(<<data_type_code::unsigned-8, tail::binary>>) when data_type_code in @variable_data_types do
col_info = %{data_type: :variable, data_type_code: data_type_code}
cond do
data_type_code == @tds_data_type_daten ->
length = 3
col_info = col_info
|> Map.put(:length, length)
|> Map.put(:data_reader, :bytelen)
data_type_code in [
@tds_data_type_timen,
@tds_data_type_datetime2n,
@tds_data_type_datetimeoffsetn
] ->
<<scale::unsigned-8, tail::binary>> = tail
cond do
scale in [1, 2] ->
length = 3
scale in [3, 4] ->
length = 4
scale in [5, 6, 7] ->
length = 5
true -> nil
end
col_info = col_info
|> Map.put(:scale, scale)
case data_type_code do
@tds_data_type_datetime2n -> length = length + 3
@tds_data_type_datetimeoffsetn -> length = length + 5
_ -> length
end
col_info = col_info
|> Map.put(:length, length)
|> Map.put(:data_reader, :bytelen)
data_type_code in [
@tds_data_type_uniqueidentifier,
@tds_data_type_intn,
@tds_data_type_decimal,
@tds_data_type_decimaln,
@tds_data_type_numeric,
@tds_data_type_numericn,
@tds_data_type_bitn,
@tds_data_type_floatn,
@tds_data_type_moneyn,
@tds_data_type_datetimen,
@tds_data_type_char,
@tds_data_type_varchar,
@tds_data_type_binary,
@tds_data_type_varbinary
] ->
<<length::little-unsigned-8, tail::binary>> = tail
if data_type_code in [
@tds_data_type_numericn,
@tds_data_type_decimaln
] do
<<precision::unsigned-8, scale::unsigned-8, tail::binary>> = tail
col_info = col_info
|> Map.put(:precision, precision)
|> Map.put(:scale, scale)
end
col_info = col_info
|> Map.put(:length, length)
|> Map.put(:data_reader, :bytelen)
data_type_code == @tds_data_type_xml ->
<<schema::unsigned-8, tail::binary>> = tail
if schema == 1 do
# TODO
# BVarChar dbname
# BVarChar owning schema
# USVarChar xml schema collection
end
col_info = col_info
|> Map.put(:data_reader, :plp)
data_type_code in [
@tds_data_type_bigvarbinary,
@tds_data_type_bigvarchar,
@tds_data_type_bigbinary,
@tds_data_type_bigchar,
@tds_data_type_nvarchar,
@tds_data_type_nchar,
@tds_data_type_udt
] ->
<<length::little-unsigned-16, tail::binary>> = tail
if data_type_code in [
@tds_data_type_bigvarchar,
@tds_data_type_bigchar,
@tds_data_type_nvarchar,
@tds_data_type_nchar
] do
<<collation::binary-5, tail::binary>> = tail
col_info = col_info
|> Map.put(:collation, collation)
end
if length == 0xFFFF do
col_info = col_info
|> Map.put(:data_reader, :plp)
else
col_info = col_info
|> Map.put(:data_reader, :shortlen)
end
col_info = col_info
|> Map.put(:length, length)
data_type_code in [
@tds_data_type_text,
@tds_data_type_image,
@tds_data_type_ntext,
@tds_data_type_variant
] ->
<<length::signed-32, tail::binary>> = tail
col_info = col_info
|> Map.put(:length, length)
cond do
data_type_code in [@tds_data_type_text, @tds_data_type_ntext] ->
<<collation::binary-5, tail::binary>> = tail
col_info = col_info
|> Map.put(:collation, collation)
|> Map.put(:data_reader, :longlen)
<<numparts::signed-8, tail::binary>> = tail
tail =
Enum.reduce([1..numparts], tail, fn(_, acc) ->
<<tsize::little-unsigned-16, _table_name::binary-size(tsize)-unit(16), tail::binary>> = acc
tail
end)
data_type_code == @tds_data_type_image ->
# TODO NumBarts Reader
<<numparts::signed-8, tail::binary>> = tail
tail =
Enum.reduce([1..numparts], tail, fn(_, acc) ->
<<size::unsigned-16, _str::size(size)-unit(16), tail::binary>> = acc
tail
end)
col_info = col_info
|> Map.put(:data_reader, :bytelen)
data_type_code == @tds_data_type_variant ->
col_info = col_info
|> Map.put(:data_reader, :variant)
true -> nil
end
end
{col_info,tail}
end
#
# Data Decoders
#
def decode_data(%{data_type: :fixed, data_type_code: data_type_code, length: length}, <<tail::binary>>) do
<<value_binary::binary-size(length)-unit(8), tail::binary>> = tail
value = case data_type_code do
@tds_data_type_null ->
nil
@tds_data_type_bit ->
value_binary != <<0x00>>
@tds_data_type_smalldatetime -> decode_smalldatetime(value_binary)
@tds_data_type_smallmoney -> decode_smallmoney(value_binary)
@tds_data_type_real ->
<<value::little-float-32>> = value_binary
Float.round value, 4
@tds_data_type_datetime -> decode_datetime(value_binary)
@tds_data_type_float ->
<<value::little-float-64>> = value_binary
Float.round value, 8
@tds_data_type_money -> decode_money(value_binary)
_ -> <<value::little-signed-size(length)-unit(8)>> = value_binary
value
end
{value, tail}
end
# ByteLength Types
def decode_data(%{data_reader: :bytelen}, <<0x00, tail::binary>>), do: {nil, tail}
def decode_data(%{data_type_code: data_type_code, data_reader: :bytelen, length: length} = data_info, <<size::unsigned-8, data::binary-size(size), tail::binary>>) do
value = cond do
data_type_code == @tds_data_type_daten -> decode_date(data)
data_type_code == @tds_data_type_timen -> decode_time(data_info[:scale], data)
data_type_code == @tds_data_type_datetime2n -> decode_datetime2(data_info[:scale], data)
data_type_code == @tds_data_type_datetimeoffsetn -> decode_datetimeoffset(data_info[:scale], data)
data_type_code == @tds_data_type_uniqueidentifier -> decode_uuid(data)
data_type_code == @tds_data_type_intn ->
data = data <> tail
case length do
1 -> <<value::unsigned-8, tail::binary>> = data
2 -> <<value::little-signed-16, tail::binary>> = data
4 -> <<value::little-signed-32, tail::binary>> = data
8 -> <<value::little-signed-64, tail::binary>> = data
end
value
data_type_code in [
@tds_data_type_decimal,
@tds_data_type_numeric,
@tds_data_type_decimaln,
@tds_data_type_numericn
] ->
decode_decimal(data_info[:precision], data_info[:scale], data)
data_type_code == @tds_data_type_bitn ->
data != <<0x00>>
data_type_code == @tds_data_type_floatn ->
data = data <> tail
case length do
4 ->
<<value::little-float-32, tail::binary>> = data
8 ->
<<value::little-float-64, tail::binary>> = data
end
value
data_type_code == @tds_data_type_moneyn ->
case length do
4 -> decode_smallmoney(data)
8 -> decode_money(data)
end
data_type_code == @tds_data_type_datetimen ->
case length do
4 -> decode_smalldatetime(data)
8 -> decode_datetime(data)
end
data_type_code in [
@tds_data_type_char,
@tds_data_type_varchar
] -> decode_char(data_info[:collation], data)
data_type_code in [
@tds_data_type_binary,
@tds_data_type_varbinary
] -> data
end
{value, tail}
end
# ShortLength Types
def decode_data(%{data_reader: :shortlen}, <<0xFF, 0xFF, tail::binary>>), do: {nil, tail}
def decode_data(%{data_type_code: data_type_code, data_reader: :shortlen} = data_info, <<size::little-unsigned-16, data::binary-size(size), tail::binary>>) do
value = cond do
data_type_code in [
@tds_data_type_bigvarchar,
@tds_data_type_bigchar
] ->
decode_char(data_info[:collation], data)
data_type_code in [
@tds_data_type_bigvarbinary,
@tds_data_type_bigbinary
] ->
data
data_type_code in [
@tds_data_type_nvarchar,
@tds_data_type_nchar
] ->
decode_nchar(data)
data_type_code == @tds_data_type_udt ->
decode_udt(data_info, data)
end
{value, tail}
end
def decode_data(%{data_reader: :longlen}, <<0x00, tail::binary>>), do: {nil, tail}
def decode_data(%{data_type_code: data_type_code, data_reader: :longlen} = data_info, <<text_ptr_size::unsigned-8, _text_ptr::size(text_ptr_size)-unit(8), _timestamp::unsigned-64, size::little-signed-32, data::binary-size(size)-unit(8), tail::binary>>) do
value =
case data_type_code do
@tds_data_type_text -> decode_char(data_info[:collation], data)
@tds_data_type_ntext -> decode_nchar(data)
@tds_data_type_image -> data
_ -> nil
end
{value, tail}
end
# TODO Variant Types
def decode_data(%{data_reader: :plp}, <<@tds_plp_null, tail::binary>>), do: {nil, tail}
def decode_data(%{data_type_code: data_type_code, data_reader: :plp} = data_info, <<_size::little-unsigned-64, tail::binary>>) do
{data, tail} = decode_plp_chunk(tail, <<>>)
value = cond do
data_type_code == @tds_data_type_xml ->
decode_xml(data_info, data)
data_type_code in [
@tds_data_type_bigvarchar,
@tds_data_type_bigchar,
@tds_data_type_text
] ->
decode_char(data_info[:collation], data)
data_type_code in [
@tds_data_type_bigvarbinary,
@tds_data_type_bigbinary,
@tds_data_type_image
] ->
data
data_type_code in [
@tds_data_type_nvarchar,
@tds_data_type_nchar,
@tds_data_type_ntext
] ->
decode_nchar(data)
data_type_code == @tds_data_type_udt ->
decode_udt(data_info, data)
end
{value, tail}
end
def decode_plp_chunk(<<chunksize::little-unsigned-32, tail::binary>>, buf) when chunksize == 0, do: {buf, tail}
def decode_plp_chunk(<<chunksize::little-unsigned-32, chunk::binary-size(chunksize)-unit(8), tail::binary>>, buf) do
decode_plp_chunk(tail, buf <> chunk)
end
def decode_smalldatetime(<<days::little-unsigned-16, mins::little-unsigned-16>>) do
date = Date.shift(@datetime, days: days)
|> Date.shift(mins: mins)
{{date.year, date.month, date.day},{date.hour, date.minute, date.second, 0}}
end
def decode_smallmoney(<<money::little-signed-32>>) do
money = pow10(money,(4 * -1))
Float.round money, 4
end
def decode_datetime(<<days::little-signed-32, sec::little-unsigned-32>>) do
date = Date.shift(@datetime, days: days)
date = Date.shift(date, secs: (sec/300))
{{date.year, date.month, date.day}, {date.hour, date.minute, date.second, 0}}
end
def encode_datetime({{y,m,d},{h,mm,s,0}}), do: encode_datetime(Date.from {{y,m,d},{h,mm,s}})
def encode_datetime({{_,_,_},{_,_,_}} = date), do: encode_datetime(Date.from date)
def encode_datetime(%Timex.DateTime{} = date) do
days = Date.diff @datetime, date, :days
d = Date.shift(@datetime, days: days)
sec = Date.diff d, date, :secs
sec = sec*300
<<days::little-signed-32, sec::little-unsigned-32>>
end
def encode_datetime(nil) do
end
def decode_money(<<_money_m::little-signed-32, money_l::little-signed-32>>) do
money = pow10(money_l,(4 * -1))
Float.round money, 4
end
def decode_time(scale, <<time::binary>>) do
cond do
scale in [1, 2] ->
<<time::little-unsigned-24>> = time
scale in [3, 4] ->
<<time::little-unsigned-32>> = time
scale in [5, 6, 7] ->
<<time::little-unsigned-40>> = time
end
usec = time
|> pow10(7-scale)
hour = Float.floor(usec / 10000000 / 60 / 60)
|> trunc
usec = usec - (hour * 60 * 60 * 10000000)
min = Float.floor(usec / 10000000 / 60)
|> trunc
usec = usec - (min * 60 * 10000000)
sec = Float.floor(usec / 10000000)
|> trunc
usec = usec - (sec * 10000000)
{hour, min, sec, usec}
end
def decode_datetime2(scale, <<data::binary>>) do
cond do
scale in [1, 2] -> <<time::binary-3, date::binary-3>> = data
scale in [3, 4] -> <<time::binary-4, date::binary-3>> = data
scale in [5, 6, 7] -> <<time::binary-5, date::binary-3>> = data
true -> raise "DateTime Scale Unknown"
end
{decode_date(date), decode_time(scale, time)}
end
def decode_datetimeoffset(scale, <<data::binary>>) do
cond do
scale in [1, 2] -> <<datetime::binary-6, offset_min::little-signed-16>> = data
scale in [3, 4] -> <<datetime::binary-7, offset_min::little-signed-16>> = data
scale in [5, 6, 7] -> <<datetime::binary-8, offset_min::little-signed-16>> = data
true -> raise "DateTimeOffset Scale Unknown"
end
d = decode_datetime2(scale, datetime)
|> Date.from
|> Date.shift(mins: offset_min)
{{d.year,d.month,d.day},{d.hour,d.minute,d.second,d.ms}}
end
def decode_date(<<days::little-size(3)-unit(8)>>) do
date = Date.from {{1, 1, 1}, {0, 0, 0}}
date = Date.shift(date, days: days)
{date.year, date.month, date.day}
end
def decode_uuid(<<v1::little-signed-32, v2::little-signed-16, v3::little-signed-16, v4::signed-16, v5::signed-48>>) do
<<v1::integer-32 , v2::integer-16, v3::integer-16, v4::integer-16, v5::integer-48>>
end
def decode_decimal(precision, scale, <<sign::int8, value::binary>>) do
size = byte_size(value)
<<value::little-size(size)-unit(8)>> = value
d_ctx = Decimal.get_context
d_ctx = %{d_ctx | precision: precision}
Decimal.set_context d_ctx
d = Decimal.new pow10(value,(scale * -1))
value = pow10(d.coef, d.exp)
value =
case sign do
0 -> value * -1
_ -> value
end
Decimal.new value
end
def decode_char(_collation, <<data::binary>>) do
data
end
def decode_nchar(<<data::binary>>) do
data |> :unicode.characters_to_binary({:utf16, :little}, :utf8)
end
def decode_xml(_data_info, <<_data::binary>>) do
# TODO: Decode XML Data
nil
end
def decode_udt(%{}, <<_data::binary>>) do
# TODO: Decode UDT Data
nil
end
@doc """
Data Type Encoders
Encodes the COLMETADATA for the data type
"""
def encode_data_type(%Parameter{type: type} = param) when type != nil do
case type do
:boolean -> encode_binary_type(param)
:binary -> encode_binary_type(param)
:string -> encode_string_type(param)
:integer -> encode_integer_type(param)
:decimal -> encode_decimal_type(param)
:float -> encode_float_type(param)
:datetime -> encode_datetime_type(param)
:uuid -> encode_uuid_type(param)
_ -> encode_string_type(param)
end
end
def encode_data_type(%Parameter{value: value} = param)
when value == true or value == false do
encode_data_type(%{param | type: :boolean})
end
def encode_data_type(%Parameter{value: value} = param)
when is_binary(value) and value == "" do
encode_data_type(%{param | type: :string})
end
def encode_data_type(%Parameter{value: value} = param)
when is_binary(value) do
encode_data_type(%{param | type: :binary})
end
def encode_data_type(%Parameter{value: value} = param)
when is_integer(value) and value >= 0 do
encode_data_type(%{param | type: :integer})
end
def encode_data_type(%Parameter{value: value} = param)
when is_float(value) do
encode_data_type(%{param | type: :float})
end
def encode_data_type(%Parameter{value: value} = param)
when (is_integer(value) and value < 0) do
encode_data_type(%{param | value: Decimal.new(value), type: :decimal})
end
def encode_data_type(%Parameter{value: %Decimal{}} = param) do
encode_data_type(%{param | type: :decimal})
end
def encode_data_type(%Parameter{value: %DateTime{}} = param) do
encode_data_type(%{param | type: :datetime})
end
def encode_data_type(%Parameter{value: %DateTime2{}} = param) do
encode_data_type(%{param | type: :datetime2})
end
def encode_data_type(%Parameter{value: {{_,_,_},{_,_,_}}} = param) do
encode_data_type(%{param | type: :datetime})
end
def encode_data_type(%Parameter{value: {{_,_,_},{_,_,_,0}}} = param) do
encode_data_type(%{param | type: :datetime})
end
def encode_data_type(%Parameter{value: {{_,_,_},{_,_,_,_}}} = param) do
encode_data_type(%{param | type: :datetime2})
end
def encode_binary_type(%Parameter{value: value} = param)
when value == "" do
encode_string_type(param)
end
def encode_binary_type(%Parameter{value: value} = param)
when is_integer(value) do
%{param | value: <<value>>} |> encode_binary_type
end
def encode_binary_type(%Parameter{value: value}) do
if value == nil do
length = <<0xFF, 0xFF>>
else
length = <<byte_size(value)::little-unsigned-16>>
end
type = @tds_data_type_bigvarbinary
data = <<type>> <> length
{type, data, []}
end
def encode_bit_type(%Parameter{}) do
type = @tds_data_type_bigvarbinary
data = <<type, 0x01>>
{type, data, []}
end
def encode_uuid_type(%Parameter{value: value}) do
if value == nil do
length = 0x00
else
length = 0x10
end
type = @tds_data_type_uniqueidentifier
data = <<type, length>>
{type, data, []}
end
def encode_string_type(%Parameter{value: value}) do
collation = <<0x00, 0x00, 0x00, 0x00, 0x00>>
length =
if value != nil do
value = value |> to_little_ucs2
value_size = byte_size(value)
cond do
value_size == 0 or value_size > 8000 ->
<<0xFF, 0xFF>>
true ->
<<value_size::little-2*8>>
end
else
<<0xFF, 0xFF>>
end
type = @tds_data_type_nvarchar
data = <<type>> <> length <> collation
{type, data, [collation: collation]}
end
def encode_integer_type(%Parameter{value: value} = param)
when value < 0 do
encode_decimal_type(param)
end
def encode_integer_type(%Parameter{value: value})
when value >= 0 do
attributes = []
type = @tds_data_type_intn
length =
if value == nil do
attributes = attributes
|> Keyword.put(:length, 1)
<<0x01::int8>>
else
value_size = int_type_size(value)
# cond do
# value_size == 1 ->
# data_type_code = @tds_data_type_tinyint #Enum.find(data_types, fn(x) -> x[:name] == :tinyint end)
# value_size == 2 ->
# data_type_code = @tds_data_type_smallint #Enum.find(data_types, fn(x) -> x[:name] == :smallint end)
# value_size > 2 and value_size <= 4 ->
# data_type_code = @tds_data_type_int #Enum.find(data_types, fn(x) -> x[:name] == :int end)
# value_size > 4 and value_size <= 8 ->
# data_type_code = @tds_data_type_bigint #Enum.find(data_types, fn(x) -> x[:name] == :bigint end)
# end
attributes = attributes
|> Keyword.put(:length, value_size)
<<value_size>>
end
data = <<type>> <> length
{type, data, attributes}
end
def encode_decimal_type(%Parameter{value: nil} = param) do
encode_binary_type(param)
end
def encode_decimal_type(%Parameter{value: value}) do
d_ctx = Decimal.get_context
d_ctx = %{d_ctx | precision: 38}
Decimal.set_context d_ctx
value_list = value
|> Decimal.abs
|> Decimal.to_string(:normal)
|> String.split(".")
case value_list do
[p,s] ->
precision = String.length(p) + String.length(s); scale = String.length(s)
[p] ->
precision = String.length(p); scale = 0
end
dec_abs = value
|> Decimal.abs
value = dec_abs.coef
|> :binary.encode_unsigned(:little)
value_size = byte_size(value)
padding = cond do
precision <= 9 ->
byte_len = 4
byte_len - value_size
precision <= 19 ->
byte_len = 8
byte_len - value_size
precision <= 28 ->
byte_len = 12
byte_len - value_size
precision <= 38 ->
byte_len = 16
byte_len - value_size
end
value_size = value_size + padding + 1
type = @tds_data_type_decimaln
data = <<type, value_size, precision, scale>>
{type, data, precision: precision, scale: scale}
end
def encode_float_type(%Parameter{value: nil} = param) do
encode_decimal_type(param)
end
def encode_float_type(%Parameter{value: value} = param) when is_float(value) do
value = value |> Decimal.new
encode_float_type(%{param | value: value})
end
def encode_float_type(%Parameter{value: %Decimal{} = value}) do
d_ctx = Decimal.get_context
d_ctx = %{d_ctx | precision: 38}
Decimal.set_context d_ctx
value_list = value
|> Decimal.abs
|> Decimal.to_string(:normal)
|> String.split(".")
case value_list do
[p,s] ->
precision = String.length(p) + String.length(s); scale = String.length(s)
[p] ->
precision = String.length(p); scale = 0
end
dec_abs = value
|> Decimal.abs
value = dec_abs.coef
|> :binary.encode_unsigned(:little)
value_size = byte_size(value)
padding = cond do
precision <= 9 ->
byte_len = 4
byte_len - value_size
precision <= 19 ->
byte_len = 8
byte_len - value_size
end
value_size = value_size + padding
type = @tds_data_type_floatn
data = <<type, value_size>>
{type, data, precision: precision, scale: scale}
end
def encode_datetime_type(%Parameter{}) do
type = @tds_data_type_datetimen
data = <<type, 0x08>>
{type, data, []}
end
def encode_datetime2_type(%Parameter{}) do
type = @tds_data_type_datetime2n
data = <<type, 0x08>>
{type, data, []}
end
@doc """
Creates the Parameter Descriptor for the selected type
"""
def encode_param_descriptor(%Parameter{name: name, value: value, type: type} = param) when type != nil do
desc = case type do
:uuid -> "uniqueidentifier"
:datetime -> "datetime"
:datetime2 -> "datetime2"
:binary -> encode_binary_descriptor(value)
:string ->
if value == nil do
length = 0
else
length = String.length(value)
end
if length <= 0, do: length = 1
if length > 4000, do: length = "max"
"nvarchar(#{length})"
:integer ->
if value >= 0 do
"bigint"
else
precision = value
|> Integer.to_string
|> String.length
"decimal(#{precision-1}, 0)"
end
:decimal -> encode_decimal_descriptor(param)
:float -> encode_float_descriptor(param)
:boolean -> "bit"
_ ->
if value == nil do
length = 0
else
length = String.length(value)
end
if length <= 0, do: length = 1
if length > 4000, do: length = "max"
"nvarchar(#{length})"
end
"#{name} #{desc}"
end
@doc """
Implictally Selected Types
"""
# nil
def encode_param_descriptor(%Parameter{value: nil} = param) do
#Logger.debug "Param Descriptor nil"
param = %{param | type: :boolean}
encode_param_descriptor(param)
end
# Boolean
def encode_param_descriptor(%Parameter{value: value} = param)
when value == true or value == false do
param = %{param | type: :boolean}
encode_param_descriptor(param)
end
# DateTime
def encode_param_descriptor(%Parameter{value: %Tds.DateTime{}} = param) do
param = %{param | type: :datetime}
encode_param_descriptor(param)
end
def encode_param_descriptor(%Parameter{value: {{_,_,_},{_,_,_}}} = param) do
param = %{param | type: :datetime}
encode_param_descriptor(param)
end
#DateTime2
def encode_param_descriptor(%Parameter{value: %Tds.DateTime2{}} = param) do
param = %{param | type: :datetime2}
encode_param_descriptor(param)
end
def encode_param_descriptor(%Parameter{value: {{_,_,_},{_,_,_,_}}} = param) do
#Logger.debug "Param Descriptor datetime2"
param = %{param | type: :datetime2}
encode_param_descriptor(param)
end
# Positive Integers
def encode_param_descriptor(%Parameter{value: value} = param)
when is_integer(value) and value >= 0 do
param = %{param | type: :integer}
encode_param_descriptor(param)
end
# Float
def encode_param_descriptor(%Parameter{value: value} = param)
when is_float(value) do
param = %{param | type: :float}
encode_param_descriptor(param)
end
# Negative Integers
def encode_param_descriptor(%Parameter{value: value} = param)
when is_integer(value) and value < 0 do
param = %{param | type: :decimal, value: Decimal.new(value)}
encode_param_descriptor(param)
end
# Decimal
def encode_param_descriptor(%Parameter{value: %Decimal{}} = param) do
param = %{param | type: :decimal}
encode_param_descriptor(param)
end
# Binary
def encode_param_descriptor(%Parameter{value: value} = param)
when is_binary(value) and value == "" do
#Logger.debug "Param Descriptor String"
param = %{param | type: :string}
encode_param_descriptor(param)
end
def encode_param_descriptor(%Parameter{value: value} = param)
when is_binary(value) do
param = %{param | type: :binary}
encode_param_descriptor(param)
end
@doc """
Decimal Type Parameter Descriptor
"""
def encode_decimal_descriptor(%Parameter{value: nil}), do: encode_binary_descriptor(nil)
def encode_decimal_descriptor(%Parameter{value: value} = param) when is_float(value) do
param = param
|> Map.put(:value, Decimal.new(value))
encode_decimal_descriptor(param)
end
def encode_decimal_descriptor(%Parameter{value: %Decimal{} = dec}) do
d_ctx = Decimal.get_context
d_ctx = %{d_ctx | precision: 38}
Decimal.set_context d_ctx
value_list = dec
|> Decimal.abs
|> Decimal.to_string(:normal)
|> String.split(".")
case value_list do
[p,s] ->
precision = String.length(p) + String.length(s); scale = String.length(s)
[p] ->
precision = String.length(p); scale = 0
end
"decimal(#{precision}, #{scale})"
end
def encode_decimal_descriptor(%Parameter{type: :decimal} = param) do
encode_decimal_descriptor(%{param | value: Decimal.new()})
end
@doc """
Float Type Parameter Descriptor
"""
def encode_float_descriptor(%Parameter{value: nil}), do: "decimal(1,0)"
def encode_float_descriptor(%Parameter{value: value} = param) when is_float(value) do
param = param
|> Map.put(:value, Decimal.new(value))
encode_float_descriptor(param)
end
def encode_float_descriptor(%Parameter{value: %Decimal{} = dec}) do
d_ctx = Decimal.get_context
d_ctx = %{d_ctx | precision: 38}
Decimal.set_context d_ctx
value_list = dec
|> Decimal.abs
|> Decimal.to_string(:normal)
|> String.split(".")
case value_list do
[p,s] ->
precision = String.length(p) + String.length(s); scale = String.length(s)
[p] ->
precision = String.length(p)
end
"float(#{precision})"
end
@doc """
Binary Type Parameter Descriptor
"""
def encode_binary_descriptor(value) when is_integer(value), do: encode_binary_descriptor(<<value>>)
def encode_binary_descriptor(value) do
if value == nil do
size = 1
else
size = byte_size(value)
end
"varbinary(#{size})"
end
@doc """
Data Encoding Binary Types
"""
def encode_data(@tds_data_type_bigvarbinary = data_type, value, attr)
when is_integer(value),
do: encode_data(data_type, <<value>>, attr)
def encode_data(@tds_data_type_bigvarbinary, value, _) do
if value != nil do
<<byte_size(value)::little-unsigned-16>> <> value
else
<<@tds_plp_null::little-unsigned-64>>
end
end
@doc """
Data Encoding String Types
"""
def encode_data(@tds_data_type_nvarchar, value, _) do
if value == nil do
<<@tds_plp_null::little-unsigned-64>>
else
value = value |> to_little_ucs2
value_size = byte_size(value)
cond do
value_size == 0 ->
<<0x00::unsigned-64, 0x00::unsigned-32>>
value_size > 8000 ->
encode_plp(value)
true ->
<<value_size::little-size(2)-unit(8)>> <> value
end
end
end
@doc """
Data Encoding Positive Integers Types
"""
def encode_data(_, value, _) when is_integer(value) and value >= 0 do
size = int_type_size(value)
<<size>> <> <<value::little-signed-size(size)-unit(8)>>
end
def encode_data(@tds_data_type_intn, value, _) when value == nil do
<<0>>
end
def encode_data(@tds_data_type_tinyint, value, _) when value == nil do
<<0>>
end
@doc """
Data Encoding Float Types
"""
def encode_data(@tds_data_type_floatn, nil, _) do
<<0>>
end
def encode_data(@tds_data_type_floatn, value, _) do
<<0x04, value::little-float-size(32)>>
end
@doc """
Data Encoding Decimal Types
"""
def encode_data(@tds_data_type_decimaln, %Decimal{} = value, attr) do
d_ctx = Decimal.get_context
d_ctx = %{d_ctx | precision: 38}
Decimal.set_context d_ctx
precision = attr[:precision]
d = value
|> Decimal.to_string
|> Decimal.new
sign = case d.sign do 1 -> 1; -1 -> 0 end
d_abs = Decimal.abs d
value = d_abs.coef
value_binary = value
|> :binary.encode_unsigned(:little)
value_size = byte_size(value_binary)
padding = cond do
precision <= 9 ->
byte_len = 4
byte_len - value_size
precision <= 19 ->
byte_len = 8
byte_len - value_size
precision <= 28 ->
byte_len = 12
byte_len - value_size
precision <= 38 ->
byte_len = 16
byte_len - value_size
end
byte_len = byte_len + 1
value_binary = value_binary <> <<0::size(padding)-unit(8)>>
<<byte_len>> <> <<sign>> <> value_binary
end
def encode_data(@tds_data_type_decimaln, nil, _) do
<<0x00, 0x00, 0x00, 0x00>>
end
def encode_data(@tds_data_type_decimaln = data_type, value, attr) do
encode_data(data_type, Decimal.new(value), attr)
end
def encode_data(@tds_data_type_datetimen, value, _attr) do
data = encode_datetime(value)
if data == nil do
<<0x00>>
else
<<0x08>> <> data
end
end
@doc """
Data Encoding UUID Types
"""
def encode_data(@tds_data_type_uniqueidentifier, value, _) do
if value != nil do
<<
p1::binary-size(1),
p2::binary-size(1),
p3::binary-size(1),
p4::binary-size(1),
p5::binary-size(1),
p6::binary-size(1),
p7::binary-size(1),
p8::binary-size(1),
p9::binary-size(1),
p10::binary-size(1),
p11::binary-size(1),
p12::binary-size(1),
p13::binary-size(1),
p14::binary-size(1),
p15::binary-size(1),
p16::binary-size(1)>> = value
# <<v1::little-signed-32>> = p4 <> p3 <> p2 <>p1
# <<v2::little-signed-16>> = p6 <> p5
# <<v3::little-signed-16>> = p8 <> p7
# <<v4::signed-16>> = p10 <> p9
# <<v5::signed-48>> = p11 <> p12 <> p13 <> p14 <> p15 <> p16
# v1 <> v2 <> v3 <> v4 <> v5
<<0x10>> <> p4 <> p3 <> p2 <>p1 <> p6 <> p5 <> p8 <> p7 <> p9 <> p10 <> p11 <> p12 <> p13 <> p14 <> p15 <> p16
else
<<0x00>>
end
end
def encode_plp(data) do
size = byte_size(data)
<<size::little-unsigned-64>> <> encode_plp_chunk(size, data, <<>>) <> <<0x00::32>>
end
def encode_plp_chunk(0, _, buf), do: buf
def encode_plp_chunk(size, data, buf) do
<<_t::unsigned-32, chunk_size::unsigned-32>> = <<size::unsigned-64>>
<<chunk::binary-size(chunk_size), data::binary>> = data
plp = <<chunk_size::little-unsigned-32>> <> chunk
encode_plp_chunk(size-chunk_size, data, buf <> plp)
end
defp int_type_size(int) when int in 0..255, do: 1
defp int_type_size(int) when int in -32768..32767, do: 2
defp int_type_size(int) when int in -2147483648..2147483647, do: 4
defp int_type_size(int) when int in -9223372036854775808..9223372036854775807, do: 8
end