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",
|