changed CHANGELOG.md
 
@@ -1,3 1,10 @@
1
# v0.2.1
2
* Bug Fixes
3
* Fixed: Packets sent to the server which exceed the negotiated packet size would cause the connection to close
4
* Enhancements
5
* Added support for decoding Time(n) and DateTime2
6
* Added support for SQL Named Instances, pass instance: "instance_name" in connection options
7
1
8
# v0.2.0
2
9
* Enhancements
3
10
* Added SET defaults upon connection of
changed hex_metadata.config
 
@@ -21,4 21,4 @@
21
21
[{<<"app">>,<<"timex">>},
22
22
{<<"optional">>,nil},
23
23
{<<"requirement">>,<<"~> 0.12.9">>}]}]}.
24
- {<<"version">>,<<"0.2.0">>}.
24
{<<"version">>,<<"0.2.1">>}.
changed lib/tds/connection.ex
 
@@ -14,14 14,29 @@ defmodule Tds.Connection do
14
14
opts = opts
15
15
|> Keyword.put_new(:username, System.get_env("MSSQLUSER") || System.get_env("USER"))
16
16
|> Keyword.put_new(:password, System.get_env("MSSQLPASSWORD"))
17
|> Keyword.put_new(:instance, System.get_env("MSSQLINSTANCE"))
17
18
|> Keyword.put_new(:hostname, System.get_env("MSSQLHOST") || "localhost")
18
19
|> Enum.reject(fn {_k,v} -> is_nil(v) end)
20
19
21
case GenServer.start_link(__MODULE__, []) do
20
22
{:ok, pid} ->
21
23
timeout = opts[:timeout] || @timeout
22
- case GenServer.call(pid, {:connect, opts}, timeout) do
23
- :ok -> {:ok, pid}
24
- err -> {:error, err}
24
case opts[:instance] do
25
nil ->
26
case GenServer.call(pid, {:connect, opts}, timeout) do
27
:ok -> {:ok, pid}
28
err -> {:error, err}
29
end
30
instance ->
31
case GenServer.call(pid, {:instance, opts}, timeout) do
32
:ok ->
33
case GenServer.call(pid, {:connect, opts}, timeout) do
34
:ok -> {:ok, pid}
35
err -> {:error, err}
36
end
37
err ->
38
{:error, err}
39
end
25
40
end
26
41
err -> err
27
42
end
 
@@ -65,7 80,9 @@ defmodule Tds.Connection do
65
80
66
81
def init([]) do
67
82
{:ok, %{
68
- sock: nil,
83
sock: nil,
84
usock: nil,
85
ireq: nil,
69
86
opts: nil,
70
87
state: :ready,
71
88
tail: "",
 
@@ -82,15 99,28 @@ defmodule Tds.Connection do
82
99
{:stop, :normal, s}
83
100
end
84
101
102
def handle_call({:instance, opts}, from, s) do
103
host = Keyword.fetch!(opts, :hostname)
104
host = if is_binary(host), do: String.to_char_list(host), else: host
105
timeout = opts[:timeout] || @timeout
106
107
case :gen_udp.open(0, [:binary, {:active, true}, {:reuseaddr, true}]) do
108
{:ok, sock} ->
109
:gen_udp.send(sock, host, 1434, <<3>>)
110
{:noreply, %{s | opts: opts, ireq: from, usock: sock}}
111
{:error, error} ->
112
error(%Tds.Error{message: "udp connect: #{error}"}, s)
113
end
114
end
115
85
116
def handle_call({:connect, opts}, from, %{queue: queue} = s) do
117
opts = s[:opts] || opts
86
118
host = Keyword.fetch!(opts, :hostname)
87
119
host = if is_binary(host), do: String.to_char_list(host), else: host
88
120
port = opts[:port] || System.get_env("MSSQL_PORT") || 1433
89
121
if is_binary(port), do: {port, _} = Integer.parse(port)
90
122
timeout = opts[:timeout] || @timeout
91
123
sock_opts = [{:active, :once}, :binary, {:packet, :raw}, {:delay_send, false}]
92
-
93
- #Logger.debug "Connect Timeout: #{inspect timeout}"
94
124
95
125
{caller, _} = from
96
126
ref = Process.monitor(caller)
 
@@ -102,8 132,8 @@ defmodule Tds.Connection do
102
132
{:ok, sock} ->
103
133
s = put_in s.sock, {:gen_tcp, sock}
104
134
Protocol.login(%{s | opts: opts, sock: {:gen_tcp, sock}})
105
- {:error, reason} ->
106
- error(%Tds.Error{message: "tcp connect: #{reason}"}, s)
135
{:error, error} ->
136
error(%Tds.Error{message: "tcp connect: #{error}"}, s)
107
137
end
108
138
end
109
139
 
@@ -123,9 153,6 @@ defmodule Tds.Connection do
123
153
{:error, error, s} -> error(error, s)
124
154
end
125
155
_ ->
126
- #Logger.error "TDS-State: #{inspect s}"
127
- #Logger.error "TDS-PID: #{inspect self}"
128
- #Logger.error "Query Queued: #{inspect command}"
129
156
{:noreply, s}
130
157
end
131
158
end
 
@@ -133,7 160,6 @@ defmodule Tds.Connection do
133
160
134
161
135
162
def handle_info({:DOWN, ref, :process, _, _}, s) do
136
- #Logger.error "Caller Down"
137
163
case :queue.out(s.queue) do
138
164
{{:value, {_,_,^ref}}, _queue} ->
139
165
{_, s} = command(:attn, s)
 
@@ -148,6 174,39 @@ defmodule Tds.Connection do
148
174
{:noreply, s}
149
175
end
150
176
177
def handle_info({:udp, _, _, _, <<head::binary-3, data::binary>>}, %{opts: opts, ireq: pid, usock: sock} = s) do
178
:gen_udp.close(sock)
179
server = String.split(data, ";;")
180
|> Enum.slice(0..-2)
181
|> Enum.reduce([], fn(str, acc) ->
182
server = String.split(str, ";")
183
|> Enum.chunk(2)
184
|> Enum.reduce([], fn ([k,v], acc) ->
185
k = k
186
|> String.downcase
187
|> String.to_atom
188
Keyword.put_new(acc, k, v)
189
end)
190
[server | acc]
191
end)
192
|> Enum.find(fn(s) ->
193
String.downcase(s[:instancename]) == String.downcase(opts[:instance])
194
end)
195
case server do
196
nil ->
197
error(%Tds.Error{message: "Instance #{opts.instance} not found"}, s)
198
serv ->
199
timeout = opts[:timeout] || @timeout
200
{port, _} = Integer.parse(serv[:tcp])
201
opts = Keyword.put(opts, :port, port)
202
GenServer.reply(pid, :ok)
203
204
{:noreply, %{s | opts: opts}}
205
#GenServer.call(self, {:connect, %{port: s[:tcp], instance: nil}}, timeout)
206
end
207
208
end
209
151
210
def handle_info({:tcp, _, _data}, %{sock: {mod, sock}, opts: opts, state: :prelogin} = s) do
152
211
case mod do
153
212
:gen_tcp -> :inet.setopts(sock, active: :once)
changed lib/tds/messages.ex
 
@@ -50,6 50,11 @@ defmodule Tds.Messages do
50
50
@fByRefValue 1
51
51
@fDefaultValue 2
52
52
53
## Packet Size
54
@tds_pack_data_size 4088
55
@tds_pack_header_size 8
56
@tds_pack_size (@tds_pack_header_size @tds_pack_data_size)
57
53
58
## Packet Types
54
59
@tds_pack_sqlbatch 1
55
60
@tds_pack_rpcRequest 3
 
@@ -111,12 116,13 @@ defmodule Tds.Messages do
111
116
terminator = <<0xFF>>
112
117
prelogin_data = version_data
113
118
data = version <> terminator <> prelogin_data
114
- encode_header(0x12, data)<>data
119
encode_packets(0x12, data, [])
120
# encode_header(0x12, data)<>data
115
121
end
116
122
117
123
defp encode(msg_login(params: params), _env) do
118
124
tds_version = <<0x04, 0x00, 0x00, 0x74>>
119
- message_size = <<0x00, 0x10, 0x00, 0x00>>
125
message_size = <<@tds_pack_size::little-size(4)-unit(8)>>
120
126
client_prog_ver = <<0x04, 0x00, 0x00, 0x07>>
121
127
client_pid = <<0x00, 0x10, 0x00, 0x00>>
122
128
connection_id = <<0x00::size(32)>>
 
@@ -208,9 214,10 @@ defmodule Tds.Messages do
208
214
209
215
login7_len = byte_size(login7) 4
210
216
data = <<login7_len::little-size(32)>> <> login7
211
- header = encode_header(0x10, data)
217
encode_packets(0x10, data, [])
218
# header = encode_header(0x10, data)
212
219
213
- header <> data
220
# header <> data
214
221
end
215
222
216
223
defp encode(msg_attn(), _s) do
 
@@ -235,8 242,9 @@ defmodule Tds.Messages do
235
242
total_length = byte_size(headers) 4
236
243
all_headers = <<total_length::little-size(32)>> <> headers
237
244
data = all_headers <> q_ucs
238
- header = encode_header(0x01, data)
239
- header <> data
245
encode_packets(0x01, data, [])
246
# header = encode_header(0x01, data)
247
# header <> data
240
248
end
241
249
242
250
defp encode(msg_rpc(proc: proc, params: params), %{trans: trans}) do
 
@@ -256,10 264,10 @@ defmodule Tds.Messages do
256
264
257
265
data = all_headers <> encode_rpc(proc, params)
258
266
#layout Data
259
-
260
- header = encode_header(0x03, data)
261
- pak = header <> data
262
- pak
267
encode_packets(0x03, data, [])
268
# header = encode_header(0x03, data)
269
# pak = header <> data
270
# pak
263
271
end
264
272
265
273
defp encode_rpc(:sp_executesql, params) do
 
@@ -282,20 290,36 @@ defmodule Tds.Messages do
282
290
p_meta_data <> Types.encode_data(type_code, param.value, type_attr)
283
291
end
284
292
285
- defp encode_header(type, data) do
286
- status = 0x01
293
defp encode_header(type, data, opts \\ []) do
294
status = opts[:status] || 1
295
296
id = opts[:id] || 1
297
287
298
length = byte_size(data) 8
288
- message = 0x01
289
299
<<
290
300
type,
291
301
status,
292
302
length::size(16),
293
303
0::size(16),
294
- message,
304
id,
295
305
0
296
306
>>
297
307
end
298
308
309
defp encode_packets(type, <<>>, paks) do
310
Enum.reverse paks
311
end
312
defp encode_packets(type, <<data::binary-size(@tds_pack_data_size)-unit(8), tail::binary>>, paks) do
313
status =
314
if byte_size(tail) > 0, do: 0, else: 1
315
header = encode_header(type, data, id: length(paks) 1, status: status)
316
encode_packets(type, tail, [header <> data | paks])
317
end
318
defp encode_packets(type, <<data::binary>>, paks) do
319
header = encode_header(type, data, id: length(paks) 1, status: 1)
320
encode_packets(type, <<>>, [header <> data | paks])
321
end
322
299
323
defp encode_tdspassword(list) do
300
324
for <<b::size(8) <- list>> do
301
325
<<x::size(4), y::size(4)>> = <<b>> #swap 4 bits
changed lib/tds/protocol.ex
 
@@ -134,8 134,10 @@ defmodule Tds.Protocol do
134
134
end
135
135
136
136
defp msg_send(msg, %{sock: {mod, sock}, env: env}) do
137
- data = encode_msg(msg, env)
138
- mod.send(sock, data)
137
paks = encode_msg(msg, env)
138
Enum.each(paks, fn(pak) ->
139
mod.send(sock, pak)
140
end)
139
141
end
140
142
141
143
defp send_to_result(msg, s) do
changed lib/tds/tokens.ex
 
@@ -68,7 68,6 @@ defmodule Tds.Tokens do
68
68
line_number: line_number,
69
69
}
70
70
# TODO Need to concat errors for delivery
71
- #Logger.debug "TDS Error: #{inspect e}"
72
71
{[error: e], nil}
73
72
end
changed lib/tds/types.ex
 
@@ -228,7 228,7 @@ defmodule Tds.Types do
228
228
@tds_data_type_nvarchar,
229
229
@tds_data_type_nchar
230
230
] do
231
- <<collation::binary-size(5), tail::binary>> = tail
231
<<collation::binary-5, tail::binary>> = tail
232
232
col_info = col_info
233
233
|> Map.put(:collation, collation)
234
234
end
 
@@ -252,7 252,7 @@ defmodule Tds.Types do
252
252
|> Map.put(:length, length)
253
253
cond do
254
254
data_type_code in [@tds_data_type_text, @tds_data_type_ntext] ->
255
- <<collation::binary-size(5), tail::binary>> = tail
255
<<collation::binary-5, tail::binary>> = tail
256
256
col_info = col_info
257
257
|> Map.put(:collation, collation)
258
258
|> Map.put(:data_reader, :longlen)
 
@@ -296,11 296,11 @@ defmodule Tds.Types do
296
296
@tds_data_type_smalldatetime -> decode_smalldatetime(value_binary)
297
297
@tds_data_type_smallmoney -> decode_smallmoney(value_binary)
298
298
@tds_data_type_real ->
299
- <<value::little-float-size(32)>> = value_binary
299
<<value::little-float-32>> = value_binary
300
300
Float.round value, 4
301
301
@tds_data_type_datetime -> decode_datetime(value_binary)
302
302
@tds_data_type_float ->
303
- <<value::little-float-size(64)>> = value_binary
303
<<value::little-float-64>> = value_binary
304
304
Float.round value, 8
305
305
@tds_data_type_money -> decode_money(value_binary)
306
306
_ -> <<value::little-signed-size(length)-unit(8)>> = value_binary
 
@@ -339,8 339,10 @@ defmodule Tds.Types do
339
339
data_type_code == @tds_data_type_floatn ->
340
340
data = data <> tail
341
341
case length do
342
- 4 -> <<value::little-float-size(32), tail::binary>> = data
343
- 8 -> <<value::little-float-size(64), tail::binary>> = data
342
4 ->
343
<<value::little-float-32, tail::binary>> = data
344
8 ->
345
<<value::little-float-64, tail::binary>> = data
344
346
end
345
347
value
346
348
data_type_code == @tds_data_type_moneyn ->
 
@@ -467,16 469,42 @@ defmodule Tds.Types do
467
469
Float.round money, 4
468
470
end
469
471
470
- def decode_time(_scale, <<_time::binary>>) do
471
- # TODO
472
def decode_time(scale, <<time::binary>>) do
473
cond do
474
scale in [1, 2] ->
475
<<time::little-unsigned-24>> = time
476
scale in [3, 4] ->
477
<<time::little-unsigned-32>> = time
478
scale in [5, 6, 7] ->
479
<<time::little-unsigned-40>> = time
480
end
481
482
usec = time
483
|> pow10(7-scale)
484
hour = Float.floor(usec / 10000000 / 60 / 60)
485
|> trunc
486
usec = usec - (hour * 60 * 60 * 10000000)
487
min = Float.floor(usec / 10000000 / 60)
488
|> trunc
489
usec = usec - (min * 60 * 10000000)
490
sec = Float.floor(usec / 10000000)
491
|> trunc
492
usec = usec - (sec * 10000000)
493
494
{hour, min, sec, usec}
472
495
end
473
496
474
- def decode_time_int() do
475
- # TODO
497
def decode_datetime2(scale, <<time::binary-3, date::binary-3>>)
498
when scale in [1, 2] do
499
{decode_date(date), decode_time(scale, time)}
476
500
end
477
-
478
- def decode_datetime2(_scale, <<_data::binary>>) do
479
- # TODO
501
def decode_datetime2(scale, <<time::binary-4, date::binary-3>>)
502
when scale in [3, 4] do
503
{decode_date(date), decode_time(scale, time)}
504
end
505
def decode_datetime2(scale, <<time::binary-5, date::binary-3>>)
506
when scale in [5, 6, 7] do
507
{decode_date(date), decode_time(scale, time)}
480
508
end
481
509
482
510
def decode_datetimeoffset(_scale, <<_data::binary>>) do
 
@@ -598,7 626,7 @@ defmodule Tds.Types do
598
626
0 ->
599
627
<<0xFF, 0xFF>>
600
628
_ ->
601
- <<value_size::little-size(2)-unit(8)>>
629
<<value_size::little-2*8>>
602
630
end
603
631
else
604
632
<<0xFF, 0xFF>>
 
@@ -739,7 767,6 @@ defmodule Tds.Types do
739
767
end
740
768
741
769
def encode_datetime2_type(%Parameter{} = param) do
742
- # TODO
743
770
type = @tds_data_type_datetime2n
744
771
data = <<type, 0x08>>
745
772
{type, data, []}
changed lib/tds/utils.ex
 
@@ -36,10 36,6 @@ defmodule Tds.Utils do
36
36
false
37
37
end
38
38
39
- def collation_to_charset(<<_collation::binary>>) do
40
-
41
- end
42
-
43
39
def error(error, s) do
44
40
reply(error, s)
45
41
{:stop, error, s}
changed mix.exs
 
@@ -3,7 3,7 @@ defmodule Tds.Mixfile do
3
3
4
4
def project do
5
5
[app: :tds,
6
- version: "0.2.0",
6
version: "0.2.1",
7
7
elixir: "~> 1.0.0",
8
8
deps: deps,
9
9
source_url: "https://github.com/livehelpnow/tds",