Packages
tds
2.3.2
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/tokens.ex
defmodule Tds.Tokens do
@moduledoc false
import Tds.BinaryUtils
import Bitwise
require Logger
alias Tds.Encoding.UCS2
alias Tds.Types
def retval_typ_size(38) do
# 0x26 - SYBINTN - 1
8
end
def retval_typ_size(dec) do
# Undefined
raise Tds.Error,
"Unknown datatype parsed when decoding return value: #{dec}"
end
@type token ::
:colmetadata
| :done
| :doneinproc
| :doneproc
| :envchange
| :error
| :info
| :loginack
| :order
| :parameters
| :returnstatus
| :row
# | :eof # end of message marker
## Decode Token Stream
@spec decode_tokens(any, any) :: [{token, any}]
def decode_tokens(binary, colmetadata \\ nil)
def decode_tokens(tail, _) when tail == "" or tail == nil do
[]
end
def decode_tokens(<<token::unsigned-size(8), tail::binary>>, collmetadata) do
{token_data, tail, collmetadata} =
case token do
0x81 -> decode_colmetadata(tail, collmetadata)
# 0xA5 -> decode_colinfo(tail, collmetadata)
0xFD -> decode_done(tail, collmetadata)
0xFE -> decode_doneproc(tail, collmetadata)
0xFF -> decode_doneinproc(tail, collmetadata)
0xE3 -> decode_envchange(tail, collmetadata)
0xAA -> decode_error(tail, collmetadata)
# 0xAE -> decode_featureextack(tail, collmetadata)
# 0xEE -> decode_fedauthinfo(tail, collmetadata)
0xAB -> decode_info(tail, collmetadata)
0xAD -> decode_loginack(tail, collmetadata)
0xD2 -> decode_nbcrow(tail, collmetadata)
# 0x78 -> decode_offset(tail, collmetadata)
0xA9 -> decode_order(tail, collmetadata)
0x79 -> decode_returnstatus(tail, collmetadata)
0xAC -> decode_returnvalue(tail, collmetadata)
0xD1 -> decode_row(tail, collmetadata)
# 0xE4 -> decode_sessionstate(tail, collmetadata)
# 0xED -> decode_sspi(tail, collmetadata)
# 0xA4 -> decode_tablename(tail, collmetadata)
t -> raise_unsupported_token(t, collmetadata)
end
[token_data | decode_tokens(tail, collmetadata)]
end
defp raise_unsupported_token(token, _) do
raise RuntimeError,
"Unsupported Token code #{inspect(token, base: :hex)} in Token Stream"
end
defp decode_returnvalue(bin, collmetadata) do
<<
_ord::little-unsigned-16,
length::size(8),
name::binary-size(length)-unit(16),
_status::size(8),
_usertype::size(32),
_flags::size(16),
data::binary
>> = bin
name = UCS2.to_string(name)
{type_info, tail} = Tds.Types.decode_info(data)
{value, tail} = Tds.Types.decode_data(type_info, tail)
param = %Tds.Parameter{name: name, value: value, direction: :output}
{{:returnvalue, param}, tail, collmetadata}
end
defp decode_returnstatus(
<<value::little-size(32), tail::binary>>,
collmetadata
) do
{{:returnstatus, value}, tail, collmetadata}
end
# COLMETADATA
defp decode_colmetadata(
<<column_count::little-size(2)-unit(8), tail::binary>>,
_
) do
{colmetadata, tail} = decode_columns(tail, column_count)
{{:colmetadata, colmetadata}, tail, colmetadata}
end
# ORDER
defp decode_order(<<length::little-unsigned-16, tail::binary>>, collmetadata) do
length = trunc(length / 2)
{columns, tail} = decode_column_order(tail, length)
{{:order, columns}, tail, collmetadata}
end
# ERROR
defp decode_error(
<<l::little-size(16), data::binary-size(l), tail::binary>>,
collmetadata
) do
<<
number::little-size(32),
state,
class,
msg_len::little-size(16),
msg::binary-size(msg_len)-unit(16),
sn_len,
server_name::binary-size(sn_len)-unit(16),
pn_len,
proc_name::binary-size(pn_len)-unit(16),
line_number::little-size(32)
>> = data
e = %{
number: number,
state: state,
class: class,
msg_text: UCS2.to_string(:binary.copy(msg)),
server_name: UCS2.to_string(:binary.copy(server_name)),
proc_name: UCS2.to_string(:binary.copy(proc_name)),
line_number: line_number
}
# TODO Need to concat errors for delivery
# Logger.debug "SQL Error: #{inspect e}"
{{:error, e}, tail, collmetadata}
end
defp decode_info(
<<l::little-size(16), data::binary-size(l), tail::binary>>,
collmetadata
) do
<<
number::little-size(32),
state,
class,
msg_len::little-size(16),
msg::binary-size(msg_len)-unit(16),
sn_len,
server_name::binary-size(sn_len)-unit(16),
pn_len,
proc_name::binary-size(pn_len)-unit(16),
line_number::little-size(32)
>> = data
info = %{
number: number,
state: state,
class: class,
msg_text: UCS2.to_string(msg),
server_name: UCS2.to_string(server_name),
proc_name: UCS2.to_string(proc_name),
line_number: line_number
}
Logger.debug(fn ->
[
"(Tds.Info)",
"Line",
to_string(info.line_number),
"(Class #{info.class})",
info.msg_text
]
|> Enum.intersperse(" ")
|> IO.iodata_to_binary()
end)
# tokens = Keyword.update(tokens, :info, [i], &[i | &1])
{{:info, info}, tail, collmetadata}
end
## ROW
defp decode_row(<<tail::binary>>, collmetadata) do
{row, tail} = decode_row_columns(tail, collmetadata)
{{:row, row}, tail, collmetadata}
end
## NBC ROW
defp decode_nbcrow(<<tail::binary>>, collmetadata) do
column_count = Enum.count(collmetadata)
bitmap_bytes = round(Float.ceil(column_count / 8))
{bitmap, tail} = bitmap_list(tail, bitmap_bytes)
{row, tail} = decode_nbcrow_columns(tail, collmetadata, bitmap)
{{:row, row}, tail, collmetadata}
end
defp decode_envchange(
<<
_length::little-unsigned-16,
env_type::unsigned-8,
tail::binary
>>,
colmetadata
) do
{token, tail} =
case env_type do
0x01 ->
<<
new_value_size::unsigned-8,
new_value::binary(new_value_size, 16),
old_value_size::unsigned-8,
old_value::binary(old_value_size, 16),
rest::binary
>> = tail
new_database = UCS2.to_string(new_value)
old_database = UCS2.to_string(old_value)
{{:database, new_database, old_database}, rest}
0x02 ->
<<
new_value_size::unsigned-8,
new_value::binary(new_value_size, 16),
old_value_size::unsigned-8,
old_value::binary(old_value_size, 16),
rest::binary
>> = tail
new_language = UCS2.to_string(new_value)
old_language = UCS2.to_string(old_value)
{{:language, new_language, old_language}, rest}
0x03 ->
<<
new_value_size::unsigned-8,
new_value::binary(new_value_size, 16),
old_value_size::unsigned-8,
old_value::binary(old_value_size, 16),
rest::binary
>> = tail
new_charset = UCS2.to_string(new_value)
old_charset = UCS2.to_string(old_value)
{{:charset, new_charset, old_charset}, rest}
0x04 ->
<<
new_value_size::unsigned-8,
new_value::binary(new_value_size, 16),
old_value_size::unsigned-8,
old_value::binary(old_value_size, 16),
rest::binary
>> = tail
new_packetsize =
new_value
|> UCS2.to_string()
|> Integer.parse()
|> case do
:error -> 4096
{value, ""} -> value
{value, _maybe_unit} -> value
end
old_packetsize =
old_value
|> UCS2.to_string()
|> Integer.parse()
|> case do
:error -> 4096
{value, ""} -> value
{value, _maybe_unit} -> value
end
{{:packetsize, new_packetsize, old_packetsize}, rest}
# 0x05
# @tds_envtype_unicode_data_storing_local_id ->
# 0x06
# @tds_envtype_uncode_data_string_comparison_flag ->
0x07 ->
<<
new_value_size::unsigned-8,
collation::binary(new_value_size, 8),
old_value_size::unsigned-8,
_old_value::binary(old_value_size, 8),
rest::binary
>> = tail
{:ok, collation} = Tds.Protocol.Collation.decode(collation)
{{:collation, collation, nil}, rest}
0x08 ->
<<
value_size::unsigned-8,
new_value::binary-little-size(value_size)-unit(8),
0x00,
rest::binary
>> = tail
new_trans = :binary.copy(new_value)
{{:transaction_begin, new_trans, <<0x00>>}, rest}
0x09 ->
<<
0x00,
value_size::unsigned-8,
old_value::binary-little-size(value_size)-unit(8),
rest::binary
>> = tail
old_trans = :binary.copy(old_value)
{{:transaction_commit, <<0x00>>, old_trans}, rest}
0x0A ->
<<
0x00,
value_size::unsigned-8,
old_value::binary-little-size(value_size)-unit(8),
rest::binary
>> = tail
trans = :binary.copy(old_value)
{{:transaction_rollback, <<0x00>>, trans}, rest}
# 0x0B
# @tds_envtype_enlist_dtc_transaction ->
0x0C ->
<<
value_size::unsigned-8,
new_value::binary-little-size(value_size)-unit(8),
0x00,
rest::binary
>> = tail
tran = :binary.copy(new_value)
{{:transaction_defect, tran, <<0x00>>}, rest}
0x0D ->
<<
new_value_size::unsigned-8,
_new_value::binary(new_value_size, 16),
0x00,
rest::binary
>> = tail
{{:mirroring_partner, :ignore_me, :ignore_me}, rest}
0x11 ->
<<
0x00,
value_size::unsigned-8,
old_value::binary-little-size(value_size)-unit(8),
rest::binary
>> = tail
old = :binary.copy(old_value)
{{:transaction_ended, <<0x00>>, old}, rest}
0x12 ->
<<0x00, 0x00, rest::binary>> = tail
{{:resetconnection_ack, 0x00, 0x00}, rest}
0x13 ->
<<
size::little-uint16(),
value::binary(size, 16),
0x00,
rest::binary
>> = tail
{{:userinfo, UCS2.to_string(value), nil}, rest}
0x14 ->
<<
_routing_data_len::little-uint16(),
# Protocol MUST be 0, specifying TCP-IP protocol
0x00,
port::little-uint16(),
alt_host_len::little-uint16(),
alt_host::binary(alt_host_len, 16),
0x00,
0x00,
rest::binary
>> = tail
{hostname, instance} =
UCS2.to_string(alt_host)
|> String.split("\\")
|> case do
[host, instance] -> {host, instance}
[host] -> {host, nil}
end
routing = %{
hostname: hostname,
instance: instance,
port: port
}
{{:routing, routing, nil}, rest}
end
{{:envchange, token}, tail, colmetadata}
end
## DONE
defp decode_done(
<<status::little-unsigned-size(2)-unit(8), cur_cmd::little-unsigned-size(2)-unit(8),
row_count::little-size(8)-unit(8), tail::binary>>,
collmetadata
) do
status = %{
final?: band(status, 0x0001) == 0x0,
more?: band(status, 0x0001) == 0x1,
error?: band(status, 0x0002) == 0x2,
inxact?: band(status, 0x0004) == 0x4,
count?: band(status, 0x0010) == 0x10,
atnn?: band(status, 0x0020) == 0x20,
rpc_in_batch?: band(status, 0x0080) == 0x80,
srverror?: band(status, 0x0100) == 0x100
}
done = %{
status: status,
cmd: cur_cmd,
rows: row_count
}
{{:done, done}, tail, collmetadata}
end
## DONEPROC
defp decode_doneproc(<<tail::binary>>, collmetadata) do
{{_, done}, tail, _} = decode_done(tail, collmetadata)
{{:doneproc, done}, tail, collmetadata}
end
## DONEINPROC
defp decode_doneinproc(<<tail::binary>>, collmetadata) do
{{_, done}, tail, _} = decode_done(tail, collmetadata)
{{:doneinproc, done}, tail, collmetadata}
end
defp decode_loginack(
<<
_length::little-uint16(),
interface::size(8),
tds_version::unsigned-32,
prog_name_len::size(8),
prog_name::binary(prog_name_len, 16),
major_ver::size(8),
minor_ver::size(8),
build_hi::size(8),
build_low::size(8),
tail::binary
>>,
collmetadata
) do
token = %{
t_sql_only: interface == 1,
tds_version: tds_version,
program: UCS2.to_string(prog_name),
version: "#{major_ver}.#{minor_ver}.#{build_hi}.#{build_low}"
}
{{:loginack, token}, tail, collmetadata}
end
defp decode_column_order(tail, n, acc \\ [])
defp decode_column_order(tail, n, acc) when n < 1 do
{Enum.reverse(acc), tail}
end
defp decode_column_order(<<col_id::little-unsigned-16, tail::binary>>, n, acc) do
decode_column_order(tail, n - 1, [col_id | acc])
end
## Row and Column Decoders
defp bitmap_list(tail, n) when n <= 0 do
{[], tail}
end
defp bitmap_list(
<<b8::1, b7::1, b6::1, b5::1, b4::1, b3::1, b2::1, b1::1, tail::binary>>,
n
) do
{bits, tail} = bitmap_list(tail, n - 1)
{[b1, b2, b3, b4, b5, b6, b7, b8 | bits], tail}
end
defp decode_columns(data, n, acc \\ [])
defp decode_columns(tail, n, acc) when n < 1 do
{Enum.reverse(acc), tail}
end
defp decode_columns(data, n, acc) do
{column, tail} = decode_column(data)
decode_columns(tail, n - 1, [column | acc])
end
defp decode_column(<<_usertype::int32(), _flags::int16(), tail::binary>>) do
{info, tail} = Types.decode_info(tail)
{name, tail} = decode_column_name(tail)
info =
info
|> Map.put(:name, name)
{info, tail}
end
defp decode_column_name(<<length::int8(), name::binary-size(length)-unit(16), tail::binary>>) do
name = UCS2.to_string(name)
{name, tail}
end
defp decode_row_columns(binary, colmetadata, acc \\ [])
defp decode_row_columns(<<tail::binary>>, [], acc) do
{Enum.reverse(acc), tail}
end
defp decode_row_columns(<<data::binary>>, [column_meta | colmetadata], acc) do
{column, tail} = decode_row_column(data, column_meta)
decode_row_columns(tail, colmetadata, [column | acc])
end
defp decode_nbcrow_columns(binary, colmetadata, bitmap, acc \\ [])
defp decode_nbcrow_columns(<<tail::binary>>, [], _bitmap, acc) do
{Enum.reverse(acc), tail}
end
defp decode_nbcrow_columns(<<tail::binary>>, colmetadata, bitmap, acc) do
[column_meta | colmetadata] = colmetadata
[bit | bitmap] = bitmap
{column, tail} =
case bit do
0 -> decode_row_column(tail, column_meta)
_ -> {nil, tail}
end
decode_nbcrow_columns(tail, colmetadata, bitmap, [column | acc])
end
defp decode_row_column(<<tail::binary>>, column_meta) do
Types.decode_data(column_meta, tail)
end
end