changed
README.md
|
@@ -1,18 +1,10 @@
|
1
1
|
# Tds - MSSQL Driver for Elixir
|
2
2
|
|
3
3
|
[![Hex.pm](https://img.shields.io/hexpm/v/tds.svg)](https://hex.pm/packages/tds)
|
4
|
- [![Coverage Status](https://coveralls.io/repos/github/livehelpnow/tds/badge.svg?branch=support-1.1)](https://coveralls.io/github/livehelpnow/tds?branch=master)
|
5
|
- ![Elixir TDS CI](https://github.com/livehelpnow/tds/workflows/Elixir%20TDS%20CI/badge.svg)
|
4
|
+ ![Elixir TDS CI](https://github.com/elixir-ecto/tds/workflows/Elixir%20TDS%20CI/badge.svg)
|
6
5
|
|
7
6
|
MSSQL / TDS Database driver for Elixir.
|
8
7
|
|
9
|
- ### NOTE:
|
10
|
- Since TDS version 2.0, `tds_ecto` package is deprecated, this version supports `ecto_sql` since version 3.3.4.
|
11
|
-
|
12
|
- Please check out the issues for a more complete overview. This branch should not be considered stable or ready for production yet.
|
13
|
-
|
14
|
- For stable versions always use [hex.pm](https://hex.pm/packages/tds) as source for your mix.exs!!!
|
15
|
-
|
16
8
|
## Usage
|
17
9
|
|
18
10
|
|
|
@@ -98,6 +90,35 @@ This will skip calling `sp_prepare` and query will be executed using `sp_execute
|
98
90
|
Please note that only one execution mode can be set at a time, and SQL Server will probably
|
99
91
|
use single execution plan (since it is NOT estimated by checking data density!).
|
100
92
|
|
93
|
+ ## SSL / TLS support
|
94
|
+
|
95
|
+ tds `>= 2.3.0` supports encrypted connections to the SQL Server.
|
96
|
+
|
97
|
+ The following encryption behaviours are currently supported:
|
98
|
+
|
99
|
+ - `:required`: Requires the server to use TLS
|
100
|
+ - `:on`: Same as required
|
101
|
+ - `:not_supported`: Indicates to the server that encryption is not supported. If server requires encryption, the connection will not be established.
|
102
|
+
|
103
|
+ Currently not supported:
|
104
|
+
|
105
|
+ - `:off`: This setting allows the server to upgrade the connection (if server encryption is `:on` or `:required`) and only encrypts the LOGIN packet when the server has encryption set to `:off`.
|
106
|
+ - `:client_cert`: This will make the server check the client cerfiticate.
|
107
|
+
|
108
|
+ Setting `ssl: true` or `ssl: false` is also allowed. In that case `true` is mapped to `:required` and `false` to `:not_supported`.
|
109
|
+
|
110
|
+ ```elixir
|
111
|
+ config :your_app, :tds_conn,
|
112
|
+ hostname: "localhost",
|
113
|
+ username: "test_user",
|
114
|
+ password: "test_password",
|
115
|
+ database: "test_db",
|
116
|
+ ssl: :required,
|
117
|
+ port: 1433,
|
118
|
+ execution_mode: :executesql
|
119
|
+
|
120
|
+ ```
|
121
|
+
|
101
122
|
## Connecting to SQL Server Instances
|
102
123
|
|
103
124
|
Tds supports SQL Server instances by passing `instance: "instancename"` to the connection options.
|
|
@@ -171,7 +192,7 @@ To convert a big-endian UUID string to a mixed-endian binary, use
|
171
192
|
Clone and compile Tds with:
|
172
193
|
|
173
194
|
```bash
|
174
|
- git clone https://github.com/livehelpnow/tds.git
|
195
|
+ git clone https://github.com/elixir-ecto/tds.git
|
175
196
|
cd tds
|
176
197
|
mix deps.get
|
177
198
|
```
|
changed
hex_metadata.config
|
@@ -6,15 +6,15 @@
|
6
6
|
{<<"files">>,
|
7
7
|
[<<"lib">>,<<"lib/tds.ex">>,<<"lib/tds">>,<<"lib/tds/protocol.ex">>,
|
8
8
|
<<"lib/tds/types">>,<<"lib/tds/types/uuid.ex">>,<<"lib/tds/types.ex">>,
|
9
|
- <<"lib/tds/tokens.ex">>,<<"lib/tds/perf.ex">>,<<"lib/tds/latin1.ex">>,
|
10
|
- <<"lib/tds/error.ex">>,<<"lib/tds/protocol">>,
|
11
|
- <<"lib/tds/protocol/collation.ex">>,<<"lib/tds/protocol/grammar.ex">>,
|
12
|
- <<"lib/tds/protocol/token.ex">>,<<"lib/tds/protocol/header.ex">>,
|
13
|
- <<"lib/tds/query.ex">>,<<"lib/tds/versions.ex">>,<<"lib/tds/parameter.ex">>,
|
14
|
- <<"lib/tds/result.ex">>,<<"lib/tds/errors.csv">>,<<"lib/tds/utils.ex">>,
|
15
|
- <<"lib/tds/token_descriptors">>,<<"lib/tds/token_descriptors/basic.ex">>,
|
16
|
- <<"lib/tds/binary_utils.ex">>,<<"lib/tds/messages.ex">>,<<"mix.exs">>,
|
17
|
- <<"README.md">>]}.
|
9
|
+ <<"lib/tds/tokens.ex">>,<<"lib/tds/latin1.ex">>,<<"lib/tds/error.ex">>,
|
10
|
+ <<"lib/tds/protocol">>,<<"lib/tds/protocol/collation.ex">>,
|
11
|
+ <<"lib/tds/protocol/grammar.ex">>,<<"lib/tds/protocol/prelogin.ex">>,
|
12
|
+ <<"lib/tds/protocol/token.ex">>,<<"lib/tds/protocol/login7.ex">>,
|
13
|
+ <<"lib/tds/protocol/header.ex">>,<<"lib/tds/query.ex">>,
|
14
|
+ <<"lib/tds/versions.ex">>,<<"lib/tds/parameter.ex">>,<<"lib/tds/ucs2.ex">>,
|
15
|
+ <<"lib/tds/result.ex">>,<<"lib/tds/tls.ex">>,<<"lib/tds/errors.csv">>,
|
16
|
+ <<"lib/tds/utils.ex">>,<<"lib/tds/binary_utils.ex">>,
|
17
|
+ <<"lib/tds/messages.ex">>,<<"mix.exs">>,<<"README.md">>]}.
|
18
18
|
{<<"licenses">>,[<<"Apache 2.0">>]}.
|
19
19
|
{<<"links">>,[{<<"Github">>,<<"https://github.com/livehelpnow/tds">>}]}.
|
20
20
|
{<<"name">>,<<"tds">>}.
|
|
@@ -34,4 +34,4 @@
|
34
34
|
{<<"optional">>,false},
|
35
35
|
{<<"repository">>,<<"hexpm">>},
|
36
36
|
{<<"requirement">>,<<"~> 2.0">>}]]}.
|
37
|
- {<<"version">>,<<"2.2.0">>}.
|
37
|
+ {<<"version">>,<<"2.3.0-rc.0">>}.
|
changed
lib/tds/binary_utils.ex
|
@@ -21,6 +21,11 @@ defmodule Tds.BinaryUtils do
|
21
21
|
"""
|
22
22
|
defmacro ushort(), do: quote(do: little - unsigned - 16)
|
23
23
|
|
24
|
+ @doc """
|
25
|
+ An unsigned 6-byte (48-bit) value. The range is 0 to (2^48)-1
|
26
|
+ """
|
27
|
+ defmacro sixbyte(), do: quote(do: unsigned - 48)
|
28
|
+
|
24
29
|
@doc """
|
25
30
|
A signed 4-byte (32-bit) value. The range is -(2^31) to (2^31)-1.
|
26
31
|
"""
|
|
@@ -162,7 +167,7 @@ defmodule Tds.BinaryUtils do
|
162
167
|
@doc """
|
163
168
|
A 64-bit signed integer
|
164
169
|
"""
|
165
|
- defmacro uint64(), do: quote(do: unsigned - 64)
|
170
|
+ defmacro uint64(), do: quote(do: unsigned - 64)
|
166
171
|
|
167
172
|
@doc """
|
168
173
|
A 64-bit signed float
|
changed
lib/tds/error.ex
|
@@ -20,6 +20,9 @@ defmodule Tds.Error do
|
20
20
|
** (Tds.Error) Line 10 (8): some error
|
21
21
|
"""
|
22
22
|
|
23
|
+ @type error_details :: %{line_number: integer(), number: integer(), msg_text: String.t()}
|
24
|
+ @type t :: %__MODULE__{message: String.t(), mssql: error_details}
|
25
|
+
|
23
26
|
defexception [:message, :mssql]
|
24
27
|
|
25
28
|
def exception(message) when is_binary(message) or is_atom(message) do
|
changed
lib/tds/latin1.ex
|
@@ -1,7 +1,7 @@
|
1
1
|
defmodule Tds.Latin1 do
|
2
2
|
@moduledoc false
|
3
3
|
|
4
|
- def encode(str, "utf-16le") do
|
4
|
+ def encode(str, "utf-16le") when is_binary(str) do
|
5
5
|
case :unicode.characters_to_binary(str, :unicode, {:utf16, :little}) do
|
6
6
|
utf16 when is_bitstring(utf16) ->
|
7
7
|
utf16
|
|
@@ -12,7 +12,7 @@ defmodule Tds.Latin1 do
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
- def encode(str, "windows-1252") do
|
15
|
+ def encode(str, "windows-1252") when is_binary(str) do
|
16
16
|
case :unicode.characters_to_binary(str, :unicode, :latin1) do
|
17
17
|
utf16 when is_bitstring(utf16) ->
|
18
18
|
utf16
|
|
@@ -23,11 +23,11 @@ defmodule Tds.Latin1 do
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
- def decode(binary, "utf-16le") do
|
27
|
- :unicode.characters_to_binary(binary, {:utf16, :little}, :utf8)
|
26
|
+ def decode(binary, "utf-16le") when is_binary(binary) do
|
27
|
+ :unicode.characters_to_binary(binary, {:utf16, :little})
|
28
28
|
end
|
29
29
|
|
30
|
- def decode(binary, _) do
|
30
|
+ def decode(binary, _) when is_binary(binary) do
|
31
31
|
binary
|
32
32
|
end
|
33
33
|
end
|
changed
lib/tds/messages.ex
|
@@ -1,10 +1,11 @@
|
1
1
|
defmodule Tds.Messages do
|
2
2
|
import Record, only: [defrecord: 2]
|
3
|
- import Tds.Utils
|
4
3
|
import Tds.Tokens, only: [decode_tokens: 1]
|
5
4
|
|
6
5
|
alias Tds.Parameter
|
6
|
+ alias Tds.Protocol.{Login7, Prelogin}
|
7
7
|
alias Tds.Types
|
8
|
+ alias Tds.UCS2
|
8
9
|
|
9
10
|
require Bitwise
|
10
11
|
require Logger
|
|
@@ -19,6 +20,7 @@ defmodule Tds.Messages do
|
19
20
|
defrecord :msg_attn, []
|
20
21
|
|
21
22
|
# responses
|
23
|
+ defrecord :msg_preloginack, [:response]
|
22
24
|
defrecord :msg_loginack, [:redirect]
|
23
25
|
defrecord :msg_prepared, [:params]
|
24
26
|
defrecord :msg_sql_result, [:columns, :rows, :row_count]
|
|
@@ -45,8 +47,8 @@ defmodule Tds.Messages do
|
45
47
|
|
46
48
|
## Packet Size
|
47
49
|
@tds_pack_data_size 4088
|
48
|
- @tds_pack_header_size 8
|
49
|
- @tds_pack_size @tds_pack_header_size + @tds_pack_data_size
|
50
|
+ # @tds_pack_header_size 8
|
51
|
+ # @tds_pack_size @tds_pack_header_size + @tds_pack_data_size
|
50
52
|
|
51
53
|
## Packet Types
|
52
54
|
# @tds_pack_sqlbatch 1
|
|
@@ -60,6 +62,10 @@ defmodule Tds.Messages do
|
60
62
|
# @tds_pack_prelogin 18
|
61
63
|
|
62
64
|
## Parsers
|
65
|
+ def parse(:prelogin, packet_data, s) do
|
66
|
+ response = Prelogin.decode(packet_data, s)
|
67
|
+ {msg_preloginack(response: response), s}
|
68
|
+ end
|
63
69
|
|
64
70
|
def parse(:login, packet_data, s) do
|
65
71
|
packet_data
|
|
@@ -160,8 +166,7 @@ defmodule Tds.Messages do
|
160
166
|
c = %{c | rows: [row | c.rows], num_rows: c.num_rows + 1}
|
161
167
|
{m, c, s}
|
162
168
|
|
163
|
- {token, %{status: status, rows: num_rows}},
|
164
|
- {msg_result(set: set) = m, c, s}
|
169
|
+ {token, %{status: status, rows: num_rows}}, {msg_result(set: set) = m, c, s}
|
165
170
|
when token in [:done, :doneinproc, :doneproc] ->
|
166
171
|
cond do
|
167
172
|
status.count? and is_nil(c) ->
|
|
@@ -236,159 +241,14 @@ defmodule Tds.Messages do
|
236
241
|
encode(msg, env)
|
237
242
|
end
|
238
243
|
|
239
|
- defp encode(msg_prelogin(params: _params), _env) do
|
240
|
- version_data = <<11, 0, 12, 56, 0, 0>>
|
241
|
- version_length = byte_size(version_data)
|
242
|
- version_offset = 0x06
|
243
|
- version = <<0x00, version_offset::size(16), version_length::size(16)>>
|
244
|
- terminator = <<0xFF>>
|
245
|
- prelogin_data = version_data
|
246
|
- data = version <> terminator <> prelogin_data
|
247
|
- encode_packets(0x12, data)
|
248
|
- # encode_header(0x12, data) <> data
|
244
|
+ defp encode(msg_prelogin(params: opts), _env) do
|
245
|
+ Prelogin.encode(opts)
|
249
246
|
end
|
250
247
|
|
251
|
- defp encode(msg_login(params: params), _env) do
|
252
|
- {:ok, hostname} = :inet.gethostname()
|
253
|
- hostname = String.Chars.to_string(hostname)
|
254
|
- app_name = Node.self() |> Atom.to_string()
|
255
|
-
|
256
|
- tds_version = <<0x04, 0x00, 0x00, 0x74>>
|
257
|
- message_size = <<@tds_pack_size::little-size(4)-unit(8)>>
|
258
|
- client_prog_ver = <<0x04, 0x00, 0x00, 0x07>>
|
259
|
- client_pid = <<0x00, 0x10, 0x00, 0x00>>
|
260
|
- connection_id = <<0x00::size(32)>>
|
261
|
- option_flags_1 = <<0x00>>
|
262
|
- option_flags_2 = <<0x00>>
|
263
|
- type_flags = <<0x00>>
|
264
|
- option_flags_3 = <<0x00>>
|
265
|
- client_time_zone = <<0xE0, 0x01, 0x00, 0x00>>
|
266
|
- client_lcid = <<0x09, 0x04, 0x00, 0x00>>
|
267
|
-
|
268
|
- login_a =
|
269
|
- tds_version <>
|
270
|
- message_size <>
|
271
|
- client_prog_ver <>
|
272
|
- client_pid <>
|
273
|
- connection_id <>
|
274
|
- option_flags_1 <>
|
275
|
- option_flags_2 <>
|
276
|
- type_flags <> option_flags_3 <> client_time_zone <> client_lcid
|
277
|
-
|
278
|
- offset_start = byte_size(login_a) + 4
|
279
|
- username = params[:username]
|
280
|
- password = params[:password]
|
281
|
- servername = params[:hostname]
|
282
|
-
|
283
|
- username_ucs = to_little_ucs2(username)
|
284
|
- password_ucs = to_little_ucs2(password)
|
285
|
- servername_ucs = to_little_ucs2(servername)
|
286
|
- app_name_ucs = to_little_ucs2(app_name)
|
287
|
- hostname_ucs = to_little_ucs2(hostname)
|
288
|
-
|
289
|
- password_ucs_xor = encode_tdspassword(password_ucs)
|
290
|
- # Before submitting a password from the client to the server,
|
291
|
- # for every byte in the password buffer starting with the position pointed
|
292
|
- # to by IbPassword, the client SHOULD first swap the four high bits with the
|
293
|
- # four low bits and then do a bit-XOR with 0xA5 (10100101).
|
294
|
-
|
295
|
- clt_int_name = "ODBC"
|
296
|
- clt_int_name_ucs = to_little_ucs2(clt_int_name)
|
297
|
- database = params[:database] || ""
|
298
|
- database_ucs = to_little_ucs2(database)
|
299
|
-
|
300
|
- login_data =
|
301
|
- hostname_ucs <>
|
302
|
- username_ucs <>
|
303
|
- password_ucs_xor <>
|
304
|
- app_name_ucs <>
|
305
|
- servername_ucs <>
|
306
|
- clt_int_name_ucs <>
|
307
|
- database_ucs
|
308
|
-
|
309
|
- curr_offset = offset_start + 58
|
310
|
- ibHostName = <<curr_offset::little-size(16)>>
|
311
|
- cchHostName = <<String.length(hostname)::little-size(16)>>
|
312
|
- curr_offset = curr_offset + byte_size(hostname_ucs)
|
313
|
-
|
314
|
- ibUserName = <<curr_offset::little-size(16)>>
|
315
|
- cchUserName = <<String.length(username)::little-size(16)>>
|
316
|
- curr_offset = curr_offset + byte_size(username_ucs)
|
317
|
-
|
318
|
- ibPassword = <<curr_offset::little-size(16)>>
|
319
|
- cchPassword = <<String.length(password)::little-size(16)>>
|
320
|
- curr_offset = curr_offset + byte_size(password_ucs)
|
321
|
-
|
322
|
- ibAppName = <<curr_offset::little-size(16)>>
|
323
|
- cchAppName = <<String.length(app_name)::little-size(16)>>
|
324
|
- curr_offset = curr_offset + byte_size(app_name_ucs)
|
325
|
-
|
326
|
- ibServerName = <<curr_offset::little-size(16)>>
|
327
|
- cchServerName = <<String.length(servername)::little-size(16)>>
|
328
|
- curr_offset = curr_offset + byte_size(servername_ucs)
|
329
|
-
|
330
|
- ibUnused = <<0::size(16)>>
|
331
|
- cbUnused = <<0::size(16)>>
|
332
|
-
|
333
|
- ibCltIntName = <<curr_offset::little-size(16)>>
|
334
|
- cchCltIntName = <<4::little-size(16)>>
|
335
|
- curr_offset = curr_offset + 4 * 2
|
336
|
-
|
337
|
- ibLanguage = <<0::size(16)>>
|
338
|
- cchLanguage = <<0::size(16)>>
|
339
|
-
|
340
|
- ibDatabase = <<curr_offset::little-size(16)>>
|
341
|
-
|
342
|
- cchDatabase =
|
343
|
- if database == "" do
|
344
|
- <<0xAC>>
|
345
|
- else
|
346
|
- <<String.length(database)::little-size(16)>>
|
347
|
- end
|
348
|
-
|
349
|
- clientID = <<0::size(48)>>
|
350
|
-
|
351
|
- ibSSPI = <<0::size(16)>>
|
352
|
- cbSSPI = <<0::size(16)>>
|
353
|
-
|
354
|
- ibAtchDBFile = <<0::size(16)>>
|
355
|
- cchAtchDBFile = <<0::size(16)>>
|
356
|
-
|
357
|
- ibChangePassword = <<0::size(16)>>
|
358
|
- cchChangePassword = <<0::size(16)>>
|
359
|
-
|
360
|
- cbSSPILong = <<0::size(32)>>
|
361
|
-
|
362
|
- offset =
|
363
|
- ibHostName <>
|
364
|
- cchHostName <>
|
365
|
- ibUserName <>
|
366
|
- cchUserName <>
|
367
|
- ibPassword <>
|
368
|
- cchPassword <>
|
369
|
- ibAppName <>
|
370
|
- cchAppName <>
|
371
|
- ibServerName <>
|
372
|
- cchServerName <>
|
373
|
- ibUnused <>
|
374
|
- cbUnused <>
|
375
|
- ibCltIntName <>
|
376
|
- cchCltIntName <>
|
377
|
- ibLanguage <>
|
378
|
- cchLanguage <>
|
379
|
- ibDatabase <>
|
380
|
- cchDatabase <>
|
381
|
- clientID <>
|
382
|
- ibSSPI <>
|
383
|
- cbSSPI <>
|
384
|
- ibAtchDBFile <>
|
385
|
- cchAtchDBFile <> ibChangePassword <> cchChangePassword <> cbSSPILong
|
386
|
-
|
387
|
- login7 = login_a <> offset <> login_data
|
388
|
-
|
389
|
- login7_len = byte_size(login7) + 4
|
390
|
- data = <<login7_len::little-size(32)>> <> login7
|
391
|
- encode_packets(0x10, data)
|
248
|
+ defp encode(msg_login(params: opts), _env) do
|
249
|
+ opts
|
250
|
+ |> Login7.new()
|
251
|
+ |> Login7.encode()
|
392
252
|
end
|
393
253
|
|
394
254
|
defp encode(msg_attn(), _s) do
|
|
@@ -397,7 +257,7 @@ defmodule Tds.Messages do
|
397
257
|
|
398
258
|
defp encode(msg_sql(query: q), %{trans: trans}) do
|
399
259
|
# convert query to unicodestream
|
400
|
- q_ucs = to_little_ucs2(q)
|
260
|
+ q_ucs = UCS2.from_string(q)
|
401
261
|
|
402
262
|
# Transaction Descriptor header
|
403
263
|
header_type = <<2::little-size(2)-unit(8)>>
|
|
@@ -406,8 +266,7 @@ defmodule Tds.Messages do
|
406
266
|
transaction_descriptor = trans <> <<0::size(padding)-unit(8)>>
|
407
267
|
outstanding_request_count = <<1::little-size(4)-unit(8)>>
|
408
268
|
|
409
|
- td_header =
|
410
|
- header_type <> transaction_descriptor <> outstanding_request_count
|
269
|
+ td_header = header_type <> transaction_descriptor <> outstanding_request_count
|
411
270
|
|
412
271
|
td_header_len = byte_size(td_header) + 4
|
413
272
|
td_header = <<td_header_len::little-size(4)-unit(8)>> <> td_header
|
|
@@ -427,8 +286,7 @@ defmodule Tds.Messages do
|
427
286
|
transaction_descriptor = trans <> <<0::size(padding)-unit(8)>>
|
428
287
|
outstanding_request_count = <<1::little-size(4)-unit(8)>>
|
429
288
|
|
430
|
- td_header =
|
431
|
- header_type <> transaction_descriptor <> outstanding_request_count
|
289
|
+ td_header = header_type <> transaction_descriptor <> outstanding_request_count
|
432
290
|
|
433
291
|
td_header_len = byte_size(td_header) + 4
|
434
292
|
td_header = <<td_header_len::little-size(4)-unit(8)>> <> td_header
|
|
@@ -442,7 +300,9 @@ defmodule Tds.Messages do
|
442
300
|
encode_packets(0x03, data)
|
443
301
|
end
|
444
302
|
|
445
|
- defp encode(msg_transmgr(command: "TM_BEGIN_XACT", isolation_level: isolation_level), %{trans: trans}) do
|
303
|
+ defp encode(msg_transmgr(command: "TM_BEGIN_XACT", isolation_level: isolation_level), %{
|
304
|
+ trans: trans
|
305
|
+ }) do
|
446
306
|
isolation = encode_isolation_level(isolation_level)
|
447
307
|
encode_trans(5, trans, <<isolation::size(1)-unit(8), 0x0::size(1)-unit(8)>>)
|
448
308
|
end
|
|
@@ -452,9 +312,10 @@ defmodule Tds.Messages do
|
452
312
|
end
|
453
313
|
|
454
314
|
defp encode(msg_transmgr(command: "TM_ROLLBACK_XACT", name: name), %{trans: trans}) do
|
455
|
- payload = unless name > 0,
|
456
|
- do: <<0x00::size(2)-unit(8)>>,
|
457
|
- else: <<2::unsigned-8, name::little-size(2)-unit(8), 0x0::size(1)-unit(8)>>
|
315
|
+ payload =
|
316
|
+ unless name > 0,
|
317
|
+ do: <<0x00::size(2)-unit(8)>>,
|
318
|
+ else: <<2::unsigned-8, name::little-size(2)-unit(8), 0x0::size(1)-unit(8)>>
|
458
319
|
|
459
320
|
encode_trans(8, trans, payload)
|
460
321
|
end
|
|
@@ -482,8 +343,7 @@ defmodule Tds.Messages do
|
482
343
|
transaction_descriptor = trans <> <<0::size(padding)-unit(8)>>
|
483
344
|
outstanding_request_count = <<1::little-size(4)-unit(8)>>
|
484
345
|
|
485
|
- td_header =
|
486
|
- header_type <> transaction_descriptor <> outstanding_request_count
|
346
|
+ td_header = header_type <> transaction_descriptor <> outstanding_request_count
|
487
347
|
|
488
348
|
td_header_len = byte_size(td_header) + 4
|
489
349
|
td_header = <<td_header_len::little-size(4)-unit(8)>> <> td_header
|
|
@@ -492,8 +352,7 @@ defmodule Tds.Messages do
|
492
352
|
total_length = byte_size(headers) + 4
|
493
353
|
all_headers = <<total_length::little-size(32)>> <> headers
|
494
354
|
|
495
|
- data =
|
496
|
- all_headers <> <<request_type::little-size(2)-unit(8), request_payload::binary>>
|
355
|
+ data = all_headers <> <<request_type::little-size(2)-unit(8), request_payload::binary>>
|
497
356
|
|
498
357
|
encode_packets(0x0E, data)
|
499
358
|
end
|
|
@@ -519,7 +378,7 @@ defmodule Tds.Messages do
|
519
378
|
# for that parameter. Otherwise RPC will fail and we must use ProceName
|
520
379
|
# instead. But we want to avoid execution overhead with named approach
|
521
380
|
# hence ommiting @handle from parameter name
|
522
|
- %{p| name: ""}
|
381
|
+ %{p | name: ""}
|
523
382
|
|
524
383
|
p ->
|
525
384
|
# other paramters should be named
|
|
@@ -549,7 +408,7 @@ defmodule Tds.Messages do
|
549
408
|
end
|
550
409
|
|
551
410
|
defp encode_rpc_param(%Tds.Parameter{name: name} = param) do
|
552
|
- p_name = to_little_ucs2(name)
|
411
|
+ p_name = UCS2.from_string(name)
|
553
412
|
p_flags = param |> Parameter.option_flags()
|
554
413
|
{type_code, type_data, type_attr} = Types.encode_data_type(param)
|
555
414
|
|
|
@@ -585,12 +444,4 @@ defmodule Tds.Messages do
|
585
444
|
header = encode_header(type, data, id, 1)
|
586
445
|
[header <> data]
|
587
446
|
end
|
588
|
-
|
589
|
- defp encode_tdspassword(list) do
|
590
|
- for <<b::4, a::4 <- list>> do
|
591
|
- <<c>> = <<a::size(4), b::size(4)>>
|
592
|
- Bitwise.bxor(c, 0xA5)
|
593
|
- end
|
594
|
- |> Enum.map_join(&<<&1>>)
|
595
|
- end
|
596
447
|
end
|
changed
lib/tds/parameter.ex
|
@@ -57,6 +57,7 @@ defmodule Tds.Parameter do
|
57
57
|
|
58
58
|
def do_name([param | tail], name, acc) do
|
59
59
|
name = name + 1
|
60
|
+
|
60
61
|
param =
|
61
62
|
case param do
|
62
63
|
%Tds.Parameter{name: nil} -> fix_data_type(%{param | name: "@#{name}"})
|
|
@@ -140,9 +141,7 @@ defmodule Tds.Parameter do
|
140
141
|
%{param | type: :datetime}
|
141
142
|
end
|
142
143
|
|
143
|
- def fix_data_type(
|
144
|
- %Tds.Parameter{value: %NaiveDateTime{microsecond: {_, s}}} = param
|
145
|
- ) do
|
144
|
+ def fix_data_type(%Tds.Parameter{value: %NaiveDateTime{microsecond: {_, s}}} = param) do
|
146
145
|
type = if s > 3, do: :datetime2, else: :datetime
|
147
146
|
%{param | type: type}
|
148
147
|
end
|
|
@@ -158,9 +157,7 @@ defmodule Tds.Parameter do
|
158
157
|
%{param | type: :datetimeoffset}
|
159
158
|
end
|
160
159
|
|
161
|
- def fix_data_type(
|
162
|
- %Tds.Parameter{value: {{_y, _m, _d}, _time, _offset}} = param
|
163
|
- ) do
|
160
|
+ def fix_data_type(%Tds.Parameter{value: {{_y, _m, _d}, _time, _offset}} = param) do
|
164
161
|
%{param | type: :datetimeoffset}
|
165
162
|
end
|
removed
lib/tds/perf.ex
|
@@ -1,41 +0,0 @@
|
1
|
- defmodule Tds.Perf do
|
2
|
- @moduledoc false
|
3
|
-
|
4
|
- @millisecond 1000
|
5
|
- @second @millisecond * 1000
|
6
|
- @minute @second * 60
|
7
|
- @hour @minute * 60
|
8
|
-
|
9
|
-
|
10
|
- def to_string(misec) do
|
11
|
- cond do
|
12
|
- 0.9 < misec / @hour ->
|
13
|
- "#{Float.round(misec / @hour, 2)}h"
|
14
|
- 0.9 < misec / @minute ->
|
15
|
- "#{Float.round(misec / @minute, 2)}m"
|
16
|
- 0.9 < misec / @second ->
|
17
|
- "#{Float.round(misec / @second, 2)}s"
|
18
|
- 0.9 < misec / @millisecond ->
|
19
|
- "#{Float.round(misec / @millisecond, 2)}ms"
|
20
|
- :else ->
|
21
|
- "#{misec}μsec"
|
22
|
- end
|
23
|
- end
|
24
|
-
|
25
|
- @kbyte 1024
|
26
|
- @mbyte @kbyte * 1024
|
27
|
- @gbyte @mbyte * 1024
|
28
|
-
|
29
|
- def to_size(len) do
|
30
|
- cond do
|
31
|
- 0.1 < len / @gbyte ->
|
32
|
- "#{Float.round(len / @gbyte, 3)}GiB"
|
33
|
- 0.1 < len / @mbyte ->
|
34
|
- "#{Float.round(len / @mbyte, 3)}MiB"
|
35
|
- 0.1 < len / @kbyte ->
|
36
|
- "#{Float.round(len / @kbyte, 3)}KiB"
|
37
|
- :else ->
|
38
|
- "#{len}bytes"
|
39
|
- end
|
40
|
- end
|
41
|
- end
|
changed
lib/tds/protocol.ex
|
@@ -2,18 +2,12 @@ defmodule Tds.Protocol do
|
2
2
|
@moduledoc """
|
3
3
|
Implements DBConnection behaviour for TDS protocol
|
4
4
|
"""
|
5
|
- import Tds.BinaryUtils
|
6
|
- import Tds.Messages
|
7
|
- import Tds.Utils
|
8
|
-
|
9
|
- alias Tds.Parameter
|
10
|
- alias Tds.Query
|
11
|
-
|
5
|
+ alias Tds.{Parameter, Query}
|
6
|
+ import Tds.{BinaryUtils, Messages, Utils}
|
12
7
|
require Logger
|
8
|
+ use DBConnection
|
13
9
|
|
14
|
- @behaviour DBConnection
|
15
|
-
|
16
|
- @timeout 5000
|
10
|
+ @timeout 5_000
|
17
11
|
@sock_opts [packet: :raw, mode: :binary, active: false]
|
18
12
|
@trans_levels [
|
19
13
|
:read_uncommitted,
|
|
@@ -67,16 +61,11 @@ defmodule Tds.Protocol do
|
67
61
|
packetsize: 4096
|
68
62
|
}
|
69
63
|
|
70
|
- @impl DBConnection
|
71
|
- @spec connect(opts :: Keyword.t()) ::
|
72
|
- {:ok, state :: t()} | {:error, Exception.t()}
|
64
|
+ @spec connect(opts :: Keyword.t()) :: {:ok, state :: t()} | {:error, Exception.t()}
|
73
65
|
def connect(opts) do
|
74
66
|
opts =
|
75
67
|
opts
|
76
|
- |> Keyword.put_new(
|
77
|
- :username,
|
78
|
- System.get_env("MSSQLUSER") || System.get_env("USER")
|
79
|
- )
|
68
|
+ |> Keyword.put_new(:username, System.get_env("MSSQLUSER") || System.get_env("USER"))
|
80
69
|
|> Keyword.put_new(:password, System.get_env("MSSQLPASSWORD"))
|
81
70
|
|> Keyword.put_new(:instance, System.get_env("MSSQLINSTANCE"))
|
82
71
|
|> Keyword.put_new(:hostname, System.get_env("MSSQLHOST") || "localhost")
|
|
@@ -96,9 +85,7 @@ defmodule Tds.Protocol do
|
96
85
|
end
|
97
86
|
end
|
98
87
|
|
99
|
- @impl DBConnection
|
100
|
- @spec disconnect(err :: Exception.t() | String.t(), state :: t()) ::
|
101
|
- :ok
|
88
|
+ @spec disconnect(err :: Exception.t() | String.t(), state :: t()) :: :ok
|
102
89
|
def disconnect(_err, %{sock: {mod, sock}} = s) do
|
103
90
|
# If socket is active we flush any socket messages so the next
|
104
91
|
# socket does not get the messages.
|
|
@@ -106,7 +93,6 @@ defmodule Tds.Protocol do
|
106
93
|
mod.close(sock)
|
107
94
|
end
|
108
95
|
|
109
|
- @impl DBConnection
|
110
96
|
@spec ping(t) :: {:ok, t} | {:disconnect, Exception.t(), t}
|
111
97
|
def ping(state) do
|
112
98
|
case send_query(~s{SELECT 'pong' as [msg]}, state) do
|
|
@@ -131,19 +117,17 @@ defmodule Tds.Protocol do
|
131
117
|
end
|
132
118
|
end
|
133
119
|
|
134
|
- @impl DBConnection
|
135
120
|
@spec checkout(state :: t) ::
|
136
|
- {:ok, new_state :: any}
|
137
|
- | {:disconnect, Exception.t(), new_state :: t}
|
121
|
+ {:ok, new_state :: any} | {:disconnect, Exception.t(), new_state :: t}
|
138
122
|
def checkout(%{transaction: :started} = s) do
|
139
123
|
err = %Tds.Error{message: "Unexpected transaction status `:started`"}
|
140
124
|
{:disconnect, err, s}
|
141
125
|
end
|
142
126
|
|
143
|
- def checkout(%{sock: {mod, sock}} = s) do
|
127
|
+ def checkout(%{sock: {mod, _sock}} = s) do
|
144
128
|
sock_mod = inspect(mod)
|
145
129
|
|
146
|
- case :inet.setopts(sock, active: false) do
|
130
|
+ case setopts(s.sock, active: false) do
|
147
131
|
:ok ->
|
148
132
|
{:ok, s}
|
149
133
|
|
|
@@ -153,19 +137,17 @@ defmodule Tds.Protocol do
|
153
137
|
end
|
154
138
|
end
|
155
139
|
|
156
|
- @impl DBConnection
|
157
140
|
@spec checkin(state :: t) ::
|
158
|
- {:ok, new_state :: t}
|
159
|
- | {:disconnect, Exception.t(), new_state :: t}
|
141
|
+ {:ok, new_state :: t} | {:disconnect, Exception.t(), new_state :: t}
|
160
142
|
def checkin(%{transaction: :started} = s) do
|
161
143
|
err = %Tds.Error{message: "Unexpected transaction status `:started`"}
|
162
144
|
{:disconnect, err, s}
|
163
145
|
end
|
164
146
|
|
165
|
- def checkin(%{sock: {mod, sock}} = s) do
|
147
|
+ def checkin(%{sock: {mod, _sock}} = s) do
|
166
148
|
sock_mod = inspect(mod)
|
167
149
|
|
168
|
- case :inet.setopts(sock, active: :once) do
|
150
|
+ case setopts(s.sock, active: :once) do
|
169
151
|
:ok ->
|
170
152
|
{:ok, s}
|
171
153
|
|
|
@@ -175,7 +157,6 @@ defmodule Tds.Protocol do
|
175
157
|
end
|
176
158
|
end
|
177
159
|
|
178
|
- @impl DBConnection
|
179
160
|
@spec handle_execute(Tds.Query.t(), DBConnection.params(), Keyword.t(), t) ::
|
180
161
|
{:ok, Tds.Query.t(), Tds.Result.t(), new_state :: t}
|
181
162
|
| {:error | :disconnect, Exception.t(), new_state :: t}
|
|
@@ -210,7 +191,6 @@ defmodule Tds.Protocol do
|
210
191
|
end
|
211
192
|
end
|
212
193
|
|
213
|
- @impl DBConnection
|
214
194
|
@spec handle_prepare(Tds.Query.t(), Keyword.t(), t) ::
|
215
195
|
{:ok, Tds.Query.t(), new_state :: t()}
|
216
196
|
| {:error | :disconnect, Exception.t(), new_state :: t}
|
|
@@ -235,7 +215,6 @@ defmodule Tds.Protocol do
|
235
215
|
end
|
236
216
|
end
|
237
217
|
|
238
|
- @impl DBConnection
|
239
218
|
@spec handle_close(Tds.Query.t(), nil | keyword | map, t()) ::
|
240
219
|
{:ok, Tds.Result.t(), new_state :: t()}
|
241
220
|
| {:error | :disconnect, Exception.t(), new_state :: t()}
|
|
@@ -244,7 +223,6 @@ defmodule Tds.Protocol do
|
244
223
|
send_close(query, params, s)
|
245
224
|
end
|
246
225
|
|
247
|
- @impl DBConnection
|
248
226
|
@spec handle_begin(Keyword.t(), t) ::
|
249
227
|
{:ok, Tds.Result.t(), new_state :: t}
|
250
228
|
| {DBConnection.status(), new_state :: t}
|
|
@@ -268,7 +246,6 @@ defmodule Tds.Protocol do
|
268
246
|
end
|
269
247
|
end
|
270
248
|
|
271
|
- @impl DBConnection
|
272
249
|
@spec handle_commit(Keyword.t(), t) ::
|
273
250
|
{:ok, Tds.Result.t(), new_state :: t}
|
274
251
|
| {DBConnection.status(), new_state :: t}
|
|
@@ -286,7 +263,6 @@ defmodule Tds.Protocol do
|
286
263
|
end
|
287
264
|
end
|
288
265
|
|
289
|
- @impl DBConnection
|
290
266
|
@spec handle_rollback(Keyword.t(), t) ::
|
291
267
|
{:ok, Tds.Result.t(), new_state :: t}
|
292
268
|
| {:idle, new_state :: t}
|
|
@@ -312,7 +288,6 @@ defmodule Tds.Protocol do
|
312
288
|
end
|
313
289
|
end
|
314
290
|
|
315
|
- @impl DBConnection
|
316
291
|
@spec handle_status(Keyword.t(), t) ::
|
317
292
|
{:idle | :transaction | :error, t}
|
318
293
|
| {:disconnect, Exception.t(), t}
|
|
@@ -325,7 +300,6 @@ defmodule Tds.Protocol do
|
325
300
|
end
|
326
301
|
end
|
327
302
|
|
328
|
- @impl DBConnection
|
329
303
|
@spec handle_fetch(
|
330
304
|
Query.t(),
|
331
305
|
cursor :: any(),
|
|
@@ -338,7 +312,6 @@ defmodule Tds.Protocol do
|
338
312
|
{:error, Tds.Error.exception("Cursor is not supported by TDS"), state}
|
339
313
|
end
|
340
314
|
|
341
|
- @impl DBConnection
|
342
315
|
@spec handle_deallocate(
|
343
316
|
query :: Query.t(),
|
344
317
|
cursor :: any,
|
|
@@ -348,22 +321,14 @@ defmodule Tds.Protocol do
|
348
321
|
{:ok, Tds.Result.t(), new_state :: t()}
|
349
322
|
| {:error | :disconnect, Exception.t(), new_state :: t()}
|
350
323
|
def handle_deallocate(_query, _cursor, _opts, state) do
|
351
|
- {:error, Tds.Error.exception("Cursor operations are not supported in TDS"),
|
352
|
- state}
|
324
|
+ {:error, Tds.Error.exception("Cursor operations are not supported in TDS"), state}
|
353
325
|
end
|
354
326
|
|
355
|
- @impl DBConnection
|
356
|
- @spec handle_declare(
|
357
|
- Query.t(),
|
358
|
- params :: any,
|
359
|
- opts :: Keyword.t(),
|
360
|
- state :: t
|
361
|
- ) ::
|
327
|
+ @spec handle_declare(Query.t(), params :: any, opts :: Keyword.t(), state :: t) ::
|
362
328
|
{:ok, Query.t(), cursor :: any, new_state :: t}
|
363
329
|
| {:error | :disconnect, Exception.t(), new_state :: t}
|
364
330
|
def handle_declare(_query, _params, _opts, state) do
|
365
|
- {:error, Tds.Error.exception("Cursor operations are not supported in TDS"),
|
366
|
- state}
|
331
|
+ {:error, Tds.Error.exception("Cursor operations are not supported in TDS"), state}
|
367
332
|
end
|
368
333
|
|
369
334
|
# CONNECTION
|
|
@@ -379,43 +344,39 @@ defmodule Tds.Protocol do
|
379
344
|
parse_udp(msg, %{s | opts: opts, usock: sock})
|
380
345
|
|
381
346
|
{:error, error} ->
|
382
|
- error(%Tds.Error{message: "udp connect: #{error}"}, s)
|
347
|
+ {:error, %Tds.Error{message: "udp connect: #{error}"}}
|
383
348
|
end
|
384
349
|
end
|
385
350
|
|
386
351
|
defp connect(opts, s) do
|
387
352
|
host = Keyword.fetch!(opts, :hostname)
|
388
353
|
host = if is_binary(host), do: String.to_charlist(host), else: host
|
354
|
+
|
389
355
|
port = s.itcp || opts[:port] || System.get_env("MSSQLPORT") || 1433
|
390
356
|
{port, _} = if is_binary(port), do: Integer.parse(port), else: {port, nil}
|
357
|
+
|
391
358
|
timeout = opts[:timeout] || @timeout
|
359
|
+
|
392
360
|
sock_opts = @sock_opts ++ (opts[:socket_options] || [])
|
393
361
|
|
394
362
|
s = %{s | opts: opts}
|
395
363
|
|
396
|
- case :gen_tcp.connect(host, port, sock_opts, timeout) do
|
397
|
- {:ok, sock} ->
|
398
|
- {:ok, [sndbuf: sndbuf, recbuf: recbuf, buffer: buffer]} =
|
399
|
- :inet.getopts(sock, [:sndbuf, :recbuf, :buffer])
|
400
|
-
|
401
|
- buffer =
|
402
|
- buffer
|
403
|
- |> max(sndbuf)
|
404
|
- |> max(recbuf)
|
405
|
-
|
406
|
- :ok = :inet.setopts(sock, buffer: buffer)
|
407
|
-
|
408
|
- case login(%{s | sock: {:gen_tcp, sock}}) do
|
409
|
- {:error, error, _state} ->
|
410
|
- :gen_tcp.close(sock)
|
411
|
- {:error, error}
|
412
|
-
|
413
|
- r ->
|
414
|
- r
|
415
|
- end
|
364
|
+ # Initalize TCP connection with the SQL Server
|
365
|
+ with {:ok, sock} <- :gen_tcp.connect(host, port, sock_opts, timeout),
|
366
|
+ {:ok, buffers} <- :inet.getopts(sock, [:sndbuf, :recbuf, :buffer]),
|
367
|
+ :ok <- :inet.setopts(sock, buffer: max_buf_size(buffers)) do
|
368
|
+ # Send Prelogin message to SQL Server
|
369
|
+ case send_prelogin(%{s | sock: {:gen_tcp, sock}}) do
|
370
|
+ {:error, error, _state} ->
|
371
|
+ :gen_tcp.close(sock)
|
372
|
+ {:error, error}
|
416
373
|
|
374
|
+ other ->
|
375
|
+ other
|
376
|
+ end
|
377
|
+ else
|
417
378
|
{:error, error} ->
|
418
|
- error(%Tds.Error{message: "tcp connect: #{error}"}, s)
|
379
|
+ {:error, %Tds.Error{message: "tcp connect: #{error}"}}
|
419
380
|
end
|
420
381
|
end
|
421
382
|
|
|
@@ -451,10 +412,7 @@ defmodule Tds.Protocol do
|
451
412
|
|
452
413
|
case server do
|
453
414
|
nil ->
|
454
|
- error(%Tds.Error{message: "Instance #{opts[:instance]} not found"}, %{
|
455
|
- s
|
456
|
- | usock: nil
|
457
|
- })
|
415
|
+ {:error, %Tds.Error{message: "Instance #{opts[:instance]} not found"}}
|
458
416
|
|
459
417
|
serv ->
|
460
418
|
{port, _} = Integer.parse(serv[:tcp])
|
|
@@ -462,6 +420,25 @@ defmodule Tds.Protocol do
|
462
420
|
end
|
463
421
|
end
|
464
422
|
|
423
|
+ defp ssl_connect(%{sock: {:gen_tcp, sock}, opts: opts} = s) do
|
424
|
+ {:ok, _} = Application.ensure_all_started(:ssl)
|
425
|
+
|
426
|
+ case Tds.Tls.connect(sock, opts[:ssl_opts] || []) do
|
427
|
+ {:ok, ssl_sock} ->
|
428
|
+ state = %{s | sock: {:ssl, ssl_sock}}
|
429
|
+ {:ok, state}
|
430
|
+
|
431
|
+ {:error, reason} ->
|
432
|
+ error =
|
433
|
+ Tds.Error.exception(
|
434
|
+ "Unable to establish secure connection to server due #{inspect(reason)}"
|
435
|
+ )
|
436
|
+
|
437
|
+ :gen_tcp.close(sock)
|
438
|
+ {:error, error, s}
|
439
|
+ end
|
440
|
+ end
|
441
|
+
|
465
442
|
def handle_info({:udp_error, _, :econnreset}, s) do
|
466
443
|
msg =
|
467
444
|
"Tds encountered an error while connecting to the Sql Server " <>
|
|
@@ -474,10 +451,7 @@ defmodule Tds.Protocol do
|
474
451
|
{:tcp, _, _data},
|
475
452
|
%{sock: {mod, sock}, opts: opts, state: :prelogin} = s
|
476
453
|
) do
|
477
|
- case mod do
|
478
|
- :gen_tcp -> :inet.setopts(sock, active: false)
|
479
|
- :ssl -> :ssl.setopts(sock, active: false)
|
480
|
- end
|
454
|
+ setopts(s.sock, active: false)
|
481
455
|
|
482
456
|
login(%{s | opts: opts, sock: {mod, sock}})
|
483
457
|
end
|
|
@@ -541,25 +515,19 @@ defmodule Tds.Protocol do
|
541
515
|
|
542
516
|
# PROTOCOL
|
543
517
|
|
544
|
- def prelogin(%{opts: opts} = s) do
|
518
|
+ def send_prelogin(%{opts: opts} = s) do
|
545
519
|
msg = msg_prelogin(params: opts)
|
546
520
|
|
547
|
- case msg_send(msg, s) do
|
548
|
- {:ok, s} ->
|
549
|
- {:noreply, %{s | state: :prelogin}}
|
550
|
-
|
551
|
- {:error, reason, s} ->
|
552
|
- error(%Tds.Error{message: "tcp send: #{reason}"}, s)
|
553
|
-
|
554
|
- any ->
|
555
|
- any
|
521
|
+ case msg_send(msg, %{s | state: :prelogin}) do
|
522
|
+ {:ok, s} -> login(s)
|
523
|
+ any -> any
|
556
524
|
end
|
557
525
|
end
|
558
526
|
|
559
527
|
def login(%{opts: opts} = s) do
|
560
528
|
msg = msg_login(params: opts)
|
561
529
|
|
562
|
- case login_send(msg, s) do
|
530
|
+ case login_send(msg, %{s | state: :login}) do
|
563
531
|
{:ok, s} ->
|
564
532
|
{:ok, %{s | state: :ready}}
|
565
533
|
|
|
@@ -763,14 +731,19 @@ defmodule Tds.Protocol do
|
763
731
|
end
|
764
732
|
end
|
765
733
|
|
734
|
+ def message(:prelogin, msg_preloginack(response: response), _) do
|
735
|
+ case response do
|
736
|
+ {:login, s} -> {:ok, s}
|
737
|
+ {:encrypt, s} -> ssl_connect(s)
|
738
|
+ other -> other
|
739
|
+ end
|
740
|
+ end
|
741
|
+
|
766
742
|
def message(
|
767
743
|
:login,
|
768
744
|
msg_loginack(redirect: %{hostname: host, port: port}),
|
769
|
- %{opts: opts} = s
|
745
|
+ %{opts: opts}
|
770
746
|
) do
|
771
|
- # we got an ENVCHANGE:redirection token, we need to disconnect and start over with new server
|
772
|
- disconnect("redirected", s)
|
773
|
-
|
774
747
|
new_opts =
|
775
748
|
opts
|
776
749
|
|> Keyword.put(:hostname, host)
|
|
@@ -837,8 +810,9 @@ defmodule Tds.Protocol do
|
837
810
|
end
|
838
811
|
|
839
812
|
# Send Command To Sql Server
|
840
|
- defp login_send(msg, %{sock: {mod, sock}, env: env} = s) do
|
813
|
+ defp login_send(msg, %{sock: {mod, sock}, env: env, opts: opts} = s) do
|
841
814
|
paks = encode_msg(msg, env)
|
815
|
+ s = %{s | opts: clean_opts(opts)}
|
842
816
|
|
843
817
|
Enum.each(paks, fn pak ->
|
844
818
|
mod.send(sock, pak)
|
|
@@ -846,7 +820,7 @@ defmodule Tds.Protocol do
|
846
820
|
|
847
821
|
case msg_recv(s) do
|
848
822
|
{:disconnect, ex, s} ->
|
849
|
- {:error, ex, s}
|
823
|
+ {:disconnect, ex, s}
|
850
824
|
|
851
825
|
buffer ->
|
852
826
|
buffer
|
|
@@ -857,51 +831,33 @@ defmodule Tds.Protocol do
|
857
831
|
|
858
832
|
defp msg_send(
|
859
833
|
msg,
|
860
|
- %{sock: {mod, sock}, env: env, state: state, opts: opts} = s
|
834
|
+ %{sock: {mod, port}, env: env, opts: opts} = s
|
861
835
|
) do
|
862
|
- :inet.setopts(sock, active: false)
|
836
|
+ setopts(s.sock, active: false)
|
863
837
|
|
864
838
|
opts
|
865
839
|
|> Keyword.get(:use_elixir_calendar_types, false)
|
866
840
|
|> use_elixir_calendar_types()
|
867
841
|
|
868
|
- {t_send, _} =
|
869
|
- :timer.tc(fn ->
|
870
|
- msg
|
871
|
- |> encode_msg(env)
|
872
|
- |> Enum.each(&mod.send(sock, &1))
|
873
|
- end)
|
874
|
-
|
875
|
- {t_recv, {t_decode, result}} =
|
876
|
- :timer.tc(fn ->
|
877
|
- case msg_recv(s) do
|
878
|
- {:disconnect, _ex, _s} = res ->
|
879
|
- {0, res}
|
880
|
-
|
881
|
- buffer ->
|
882
|
- :timer.tc(fn ->
|
883
|
- buffer
|
884
|
- |> IO.iodata_to_binary()
|
885
|
- |> decode(s)
|
886
|
- end)
|
842
|
+ send_result =
|
843
|
+ msg
|
844
|
+ |> encode_msg(env)
|
845
|
+ |> Enum.reduce_while(:ok, fn chunk, _ ->
|
846
|
+ case mod.send(port, chunk) do
|
847
|
+ {:error, reason} -> {:halt, {:error, reason}}
|
848
|
+ :ok -> {:cont, :ok}
|
887
849
|
end
|
888
850
|
end)
|
889
851
|
|
890
|
- stm = Map.get(s, :query)
|
891
|
-
|
892
|
- if Keyword.get(s.opts, :trace, false) == true do
|
893
|
- Logger.debug(fn ->
|
894
|
- "[trace] [Tds.Protocod.msg_send/2] " <>
|
895
|
- "state=#{inspect(state)} " <>
|
896
|
- "send=#{Tds.Perf.to_string(t_send)} " <>
|
897
|
- "receive=#{Tds.Perf.to_string(t_recv - t_decode)} " <>
|
898
|
- "decode=#{Tds.Perf.to_string(t_decode)}" <>
|
899
|
- "\n" <>
|
900
|
- "#{inspect(stm)}"
|
901
|
- end)
|
852
|
+ with :ok <- send_result,
|
853
|
+ buffer when is_list(buffer) <- msg_recv(s) do
|
854
|
+ buffer
|
855
|
+ |> IO.iodata_to_binary()
|
856
|
+ |> decode(s)
|
857
|
+ else
|
858
|
+ {:disconnect, _ex, _s} = res -> {0, res}
|
859
|
+ other -> other
|
902
860
|
end
|
903
|
-
|
904
|
- result
|
905
861
|
end
|
906
862
|
|
907
863
|
defp msg_recv(%{sock: {mod, pid}} = s) do
|
|
@@ -1073,7 +1029,7 @@ defmodule Tds.Protocol do
|
1073
1029
|
raise(
|
1074
1030
|
ArgumentError,
|
1075
1031
|
"set_deadlock_priority: #{inspect(val)} is an invalid value, " <>
|
1076
|
- "valid values are #{inspect([:low, :high, :normal | -10..10])}"
|
1032
|
+ "valid values are #{inspect([:low, :high, :normal] ++ [-10..10])}"
|
1077
1033
|
)
|
1078
1034
|
end
|
1079
1035
|
end
|
|
@@ -1175,4 +1131,17 @@ defmodule Tds.Protocol do
|
1175
1131
|
)
|
1176
1132
|
end
|
1177
1133
|
end
|
1134
|
+
|
1135
|
+ defp setopts({mod, sock}, options) do
|
1136
|
+ case mod do
|
1137
|
+ :gen_tcp -> :inet.setopts(sock, options)
|
1138
|
+ :ssl -> :ssl.setopts(sock, options)
|
1139
|
+ end
|
1140
|
+ end
|
1141
|
+
|
1142
|
+ defp max_buf_size(buffers) when is_list(buffers) do
|
1143
|
+ buffers
|
1144
|
+ |> Keyword.values()
|
1145
|
+ |> Enum.max()
|
1146
|
+ end
|
1178
1147
|
end
|
changed
lib/tds/protocol/grammar.ex
|
@@ -135,5 +135,4 @@ defmodule Tds.Protocol.Grammar do
|
135
135
|
defmacro unicodechar(n \\ 1), do: quote(do: size(unquote(n)) - unit(16))
|
136
136
|
|
137
137
|
defmacro bigbinary(n), do: quote(do: binary - size(unquote(n)) - unit(8))
|
138
|
-
|
139
138
|
end
|
changed
lib/tds/protocol/header.ex
|
@@ -29,9 +29,8 @@ defmodule Tds.Protocol.Header do
|
29
29
|
| :sspi
|
30
30
|
| :pre_login
|
31
31
|
|
32
|
-
|
33
32
|
@typedoc """
|
34
|
- Header flag that should tell if pakcage data that header preceding is end of
|
33
|
+ Header flag that should tell if package data that header preceding is end of
|
35
34
|
TDS message or not.
|
36
35
|
|
37
36
|
* `:eom` - End of message (EOM). The packet is the last packet in the whole
|
|
@@ -39,7 +38,7 @@ defmodule Tds.Protocol.Header do
|
39
38
|
* `normal` - Normal message, means that there is more packages comming after
|
40
39
|
the one that header is preceding.
|
41
40
|
"""
|
42
|
- @type msg_sned_status :: :normal | :eom
|
41
|
+ @type msg_send_status :: :normal | :eom
|
43
42
|
@typedoc """
|
44
43
|
(Client to SQL server only) Reset this connection before processing event.
|
45
44
|
Only set for event types Batch, RPC, or Transaction Manager request
|
|
@@ -55,7 +54,7 @@ defmodule Tds.Protocol.Header do
|
55
54
|
Tuple that holds decoded package header status flags.
|
56
55
|
"""
|
57
56
|
@type pkg_header_status ::
|
58
|
- {msg_sned_status, pkg_process, conn_reset}
|
57
|
+ {msg_send_status, pkg_process, conn_reset}
|
59
58
|
|
60
59
|
@typedoc """
|
61
60
|
Decoded TDS package header
|
|
@@ -81,14 +80,9 @@ defmodule Tds.Protocol.Header do
|
81
80
|
]
|
82
81
|
|
83
82
|
@spec decode(<<_::64>>) :: t | {:error, any}
|
84
|
- def decode(<<
|
85
|
- type::int8,
|
86
|
- status::int8,
|
87
|
- length::int16,
|
88
|
- spid::int16,
|
89
|
- package::int8,
|
90
|
- window::int8
|
91
|
- >>) do
|
83
|
+ def decode(
|
84
|
+ <<type::int8, status::int8, length::int16, spid::int16, package::int8, window::int8>>
|
85
|
+ ) do
|
92
86
|
with {^type, pkg_header_type, has_data} <- decode_type(type) do
|
93
87
|
{:ok,
|
94
88
|
struct!(__MODULE__,
|
added
lib/tds/protocol/login7.ex
|
@@ -0,0 +1,212 @@
|
1
|
+ defmodule Tds.Protocol.Login7 do
|
2
|
+ @moduledoc """
|
3
|
+ Login7 message definition
|
4
|
+
|
5
|
+ See: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac
|
6
|
+ """
|
7
|
+ alias Tds.UCS2
|
8
|
+ import Tds.BinaryUtils
|
9
|
+
|
10
|
+ @packet_header 0x10
|
11
|
+ ## Packet Size
|
12
|
+ @tds_pack_header_size 8
|
13
|
+ @tds_pack_data_size 4088
|
14
|
+ @tds_pack_size @tds_pack_header_size + @tds_pack_data_size
|
15
|
+ @max_supported_tds_version <<0x04, 0x00, 0x00, 0x74>>
|
16
|
+ @default_client_version <<0x04, 0x00, 0x00, 0x07>>
|
17
|
+ @client_pid <<0x00, 0x10, 0x00, 0x00>>
|
18
|
+ # SQL_DFLT
|
19
|
+ @sql_type <<0x00>>
|
20
|
+ @options <<0x00>>
|
21
|
+ @clt_int_name "ODBC"
|
22
|
+ @default_app_name "Elixir TDS"
|
23
|
+ # EN-US
|
24
|
+ @language_code_id <<0x09, 0x04, 0x00, 0x00>>
|
25
|
+
|
26
|
+ defstruct [
|
27
|
+ # Highest TDS version used by the client
|
28
|
+ :tds_version,
|
29
|
+ # The packet size being requested by the client
|
30
|
+ :packet_size,
|
31
|
+ # The version of the interface library (for example, ODBC or OLEDB) being used by the client.
|
32
|
+ :client_version,
|
33
|
+ # The process ID of the client application.
|
34
|
+ :client_pid,
|
35
|
+ # The connection ID of the primary Server. Used when connecting to an "Always Up" backup server.
|
36
|
+ :connection_id,
|
37
|
+ # Options (currently not used)
|
38
|
+ :option_flags_1,
|
39
|
+ # More options (also not used)
|
40
|
+ :option_flags_2,
|
41
|
+ # The SQL type sent to the client
|
42
|
+ :type_flags,
|
43
|
+ # More options (also not used)
|
44
|
+ :option_flags_3,
|
45
|
+ # This field is not used and can be set to zero.
|
46
|
+ :client_time_zone,
|
47
|
+ # The language code identifier (LCID) value for the client collation.
|
48
|
+ # If ClientLCID is specified, the specified collation is set as the session collation.
|
49
|
+ :client_language_code_id,
|
50
|
+ # Client username
|
51
|
+ :username,
|
52
|
+ # Client password
|
53
|
+ :password,
|
54
|
+ # Server name
|
55
|
+ :servername,
|
56
|
+ # Application name
|
57
|
+ :app_name,
|
58
|
+ # Hostname of the SQL server
|
59
|
+ :hostname,
|
60
|
+ # Database to use (defaults to user database)
|
61
|
+ :database
|
62
|
+ ]
|
63
|
+
|
64
|
+ def new(opts) do
|
65
|
+ # gethostname/0 always succeeds
|
66
|
+ {:ok, hostname} = :inet.gethostname()
|
67
|
+
|
68
|
+ %__MODULE__{
|
69
|
+ tds_version: @max_supported_tds_version,
|
70
|
+ packet_size: <<@tds_pack_size::little-size(4)-unit(8)>>,
|
71
|
+ hostname: to_string(hostname),
|
72
|
+ app_name: Keyword.get(opts, :app_name, @default_app_name),
|
73
|
+ client_version: @default_client_version,
|
74
|
+ client_pid: pid!(),
|
75
|
+ connection_id: <<0x00::size(32)>>,
|
76
|
+ option_flags_1: @options,
|
77
|
+ option_flags_2: @options,
|
78
|
+ type_flags: @sql_type,
|
79
|
+ option_flags_3: @options,
|
80
|
+ client_time_zone: <<0x0, 0x0, 0x0, 0x0>>,
|
81
|
+ client_language_code_id: @language_code_id,
|
82
|
+ username: opts[:username],
|
83
|
+ password: opts[:password],
|
84
|
+ servername: opts[:hostname],
|
85
|
+ database: Keyword.get(opts, :database, "")
|
86
|
+ }
|
87
|
+ end
|
88
|
+
|
89
|
+ def encode(%__MODULE__{} = login) do
|
90
|
+ # Fixed login configuration
|
91
|
+ fixed_login = fixed_login(login)
|
92
|
+ {variable_login, offsets} = encode_variable_login(login, byte_size(fixed_login) + 62)
|
93
|
+
|
94
|
+ login7 = fixed_login <> offsets <> variable_login
|
95
|
+ login7_len = byte_size(login7) + 4
|
96
|
+ data = <<login7_len::little-size(32)>> <> login7
|
97
|
+
|
98
|
+ Tds.Messages.encode_packets(@packet_header, data)
|
99
|
+ end
|
100
|
+
|
101
|
+ defp fixed_login(login) do
|
102
|
+ login.tds_version <>
|
103
|
+ login.packet_size <>
|
104
|
+ login.client_version <>
|
105
|
+ login.client_pid <>
|
106
|
+ login.connection_id <>
|
107
|
+ login.option_flags_1 <>
|
108
|
+ login.option_flags_2 <>
|
109
|
+ login.type_flags <>
|
110
|
+ login.option_flags_3 <>
|
111
|
+ login.client_time_zone <>
|
112
|
+ login.client_language_code_id
|
113
|
+ end
|
114
|
+
|
115
|
+ defp encode_variable_login(login, start_offset) do
|
116
|
+ current_offset = start_offset
|
117
|
+
|
118
|
+ # Hostname
|
119
|
+ offsets = <<current_offset::ushort, String.length(login.hostname)::ushort>>
|
120
|
+ hostname = UCS2.from_string(login.hostname)
|
121
|
+ variable_login = hostname
|
122
|
+ current_offset = current_offset + byte_size(hostname)
|
123
|
+
|
124
|
+ # Username
|
125
|
+ offsets = offsets <> <<current_offset::ushort, String.length(login.username)::ushort>>
|
126
|
+ username = UCS2.from_string(login.username)
|
127
|
+ variable_login = variable_login <> username
|
128
|
+ current_offset = current_offset + byte_size(username)
|
129
|
+
|
130
|
+ # Password
|
131
|
+ offsets = offsets <> <<current_offset::ushort, String.length(login.password)::ushort>>
|
132
|
+ password = UCS2.from_string(login.password)
|
133
|
+ variable_login = variable_login <> encode_tds_password(password)
|
134
|
+ current_offset = current_offset + byte_size(password)
|
135
|
+
|
136
|
+ # App Name
|
137
|
+ offsets = offsets <> <<current_offset::ushort, String.length(login.app_name)::ushort>>
|
138
|
+ app_name = UCS2.from_string(login.app_name)
|
139
|
+ variable_login = variable_login <> app_name
|
140
|
+ current_offset = current_offset + byte_size(app_name)
|
141
|
+
|
142
|
+ # Servername
|
143
|
+ offsets = offsets <> <<current_offset::ushort, String.length(login.servername)::ushort>>
|
144
|
+ servername = UCS2.from_string(login.servername)
|
145
|
+ variable_login = variable_login <> servername
|
146
|
+ current_offset = current_offset + byte_size(servername)
|
147
|
+
|
148
|
+ # Unused
|
149
|
+ offsets = offsets <> <<0::ushort, 0::ushort>>
|
150
|
+
|
151
|
+ # Client Int Name
|
152
|
+ variable_login = variable_login <> UCS2.from_string(@clt_int_name)
|
153
|
+ offsets = offsets <> <<current_offset::ushort, 4::ushort>>
|
154
|
+ current_offset = current_offset + 8
|
155
|
+
|
156
|
+ # Language
|
157
|
+ offsets = offsets <> <<0::ushort, 0::ushort>>
|
158
|
+
|
159
|
+ # Database
|
160
|
+ variable_login = variable_login <> UCS2.from_string(login.database)
|
161
|
+
|
162
|
+ database =
|
163
|
+ if login.database == "" do
|
164
|
+ 0xAC
|
165
|
+ else
|
166
|
+ String.length(login.database)
|
167
|
+ end
|
168
|
+
|
169
|
+ offsets = offsets <> <<current_offset::ushort, database::ushort>>
|
170
|
+
|
171
|
+ # Client ID
|
172
|
+ offsets = offsets <> <<0::sixbyte>>
|
173
|
+
|
174
|
+ # SSPI
|
175
|
+ offsets = offsets <> <<0::ushort, 0::ushort>>
|
176
|
+
|
177
|
+ # Attach DB File
|
178
|
+ offsets = offsets <> <<0::ushort, 0::ushort>>
|
179
|
+
|
180
|
+ # Change password?
|
181
|
+ offsets = offsets <> <<0::ushort, 0::ushort>>
|
182
|
+
|
183
|
+ # SSPI Long
|
184
|
+ offsets = offsets <> <<0::dword>>
|
185
|
+
|
186
|
+ {variable_login, offsets}
|
187
|
+ end
|
188
|
+
|
189
|
+ defp encode_tds_password(list) do
|
190
|
+ for <<b::4, a::4 <- list>> do
|
191
|
+ <<c>> = <<a::size(4), b::size(4)>>
|
192
|
+ Bitwise.bxor(c, 0xA5)
|
193
|
+ end
|
194
|
+ |> Enum.map_join(&<<&1>>)
|
195
|
+ end
|
196
|
+
|
197
|
+ # Return the current pid
|
198
|
+ # If that fails return a "default" pid
|
199
|
+ defp pid! do
|
200
|
+ value =
|
201
|
+ self()
|
202
|
+ |> :erlang.pid_to_list()
|
203
|
+ |> to_string()
|
204
|
+ |> String.split(".")
|
205
|
+ |> Enum.at(1)
|
206
|
+ |> String.to_integer()
|
207
|
+
|
208
|
+ <<value::dword>>
|
209
|
+ rescue
|
210
|
+ _ -> @client_pid
|
211
|
+ end
|
212
|
+ end
|
added
lib/tds/protocol/prelogin.ex
|
@@ -0,0 +1,332 @@
|
1
|
+ defmodule Tds.Protocol.Prelogin do
|
2
|
+ @moduledoc """
|
3
|
+ Prelogin message definition
|
4
|
+
|
5
|
+ See: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868
|
6
|
+ """
|
7
|
+ import Tds.Protocol.Grammar
|
8
|
+ require Logger
|
9
|
+
|
10
|
+ @type state :: Tds.Protocol.t()
|
11
|
+ @type packet_data :: iodata()
|
12
|
+
|
13
|
+ @type response ::
|
14
|
+ {:ok, state()}
|
15
|
+ | {:error, Exception.t() | atom(), state()}
|
16
|
+
|
17
|
+ defstruct version: nil,
|
18
|
+ encryption: <<0x00>>,
|
19
|
+ instance: true,
|
20
|
+ thread_id: nil,
|
21
|
+ mars: false,
|
22
|
+ trace_id: nil,
|
23
|
+ fed_auth_required: false,
|
24
|
+ nonce_opt: nil
|
25
|
+
|
26
|
+ @type t :: %__MODULE__{
|
27
|
+ version: tuple(),
|
28
|
+ encryption: <<_::8>>,
|
29
|
+ instance: boolean(),
|
30
|
+ mars: boolean()
|
31
|
+ }
|
32
|
+
|
33
|
+ @packet_header 0x12
|
34
|
+
|
35
|
+ # PL Options Tokens
|
36
|
+ @version_token 0x00
|
37
|
+ @encryption_token 0x01
|
38
|
+ @instopt_token 0x02
|
39
|
+ @thread_id_token 0x03
|
40
|
+ @mars_token 0x04
|
41
|
+ # @trace_id_token 0x05
|
42
|
+ @fed_auth_required_token 0x06
|
43
|
+ @nonce_opt_token 0x07
|
44
|
+ @termintator_token 0xFF
|
45
|
+
|
46
|
+ # Encryption flags
|
47
|
+ @encryption_off 0x00
|
48
|
+ @encryption_on 0x01
|
49
|
+ @encryption_not_supported 0x02
|
50
|
+ @encryption_required 0x03
|
51
|
+
|
52
|
+ @version Mix.Project.config()[:version]
|
53
|
+ |> String.split(".")
|
54
|
+ |> Enum.map(&(Integer.parse(&1, 10) |> elem(0)))
|
55
|
+
|
56
|
+ @spec encode(maybe_improper_list()) :: [binary(), ...]
|
57
|
+ def encode(opts) do
|
58
|
+ stream = [
|
59
|
+ {@version_token, get_version()},
|
60
|
+ encode_encryption(opts),
|
61
|
+ # when instance id check is sent, encryption is not negotiated
|
62
|
+ # encode_instance(opts),
|
63
|
+ encode_thread_id(opts),
|
64
|
+ encode_mars(opts),
|
65
|
+ encode_fed_auth_required(opts)
|
66
|
+ ]
|
67
|
+
|
68
|
+ start_offset = 5 * Enum.count(stream) + 1
|
69
|
+
|
70
|
+ {iodata, _} =
|
71
|
+ stream
|
72
|
+ |> Enum.reduce({[[], @termintator_token, []], start_offset}, fn
|
73
|
+ {token, option_data}, {[options, term, data], offset} ->
|
74
|
+ data_length = byte_size(option_data)
|
75
|
+
|
76
|
+ options = [
|
77
|
+ options,
|
78
|
+ <<token, offset::ushort(), data_length::ushort()>>
|
79
|
+ ]
|
80
|
+
|
81
|
+ data = [data, option_data]
|
82
|
+ {[options, term, data], offset + data_length}
|
83
|
+ end)
|
84
|
+
|
85
|
+ data = IO.iodata_to_binary(iodata)
|
86
|
+ Tds.Messages.encode_packets(@packet_header, data)
|
87
|
+ end
|
88
|
+
|
89
|
+ defp get_version do
|
90
|
+ @version
|
91
|
+ |> case do
|
92
|
+ [major, minor, build] ->
|
93
|
+ <<build::little-ushort, minor, major, 0x00, 0x00>>
|
94
|
+
|
95
|
+ [major, minor] ->
|
96
|
+ <<0x00, 0x00, minor, major, 0x00, 0x00>>
|
97
|
+
|
98
|
+ _ ->
|
99
|
+ # probably PRE-release
|
100
|
+ <<0x01, 0x00, 0, 1, 0x00, 0x00>>
|
101
|
+ end
|
102
|
+ end
|
103
|
+
|
104
|
+ # TODO: Add support for client certificates
|
105
|
+ defp encode_encryption(opts) do
|
106
|
+ data =
|
107
|
+ case ssl?(opts) do
|
108
|
+ :on ->
|
109
|
+ <<@encryption_on::byte>>
|
110
|
+
|
111
|
+ :not_supported ->
|
112
|
+ <<@encryption_not_supported::byte>>
|
113
|
+
|
114
|
+ :required ->
|
115
|
+ <<@encryption_required::byte>>
|
116
|
+
|
117
|
+ :off ->
|
118
|
+ # TODO: Support ssl: :off
|
119
|
+ # This requires that the LOGIN7 message is send encrypted, but
|
120
|
+ # the other packages are send unencrypted over the wire.
|
121
|
+ raise ArgumentError, ~s("ssl: :off" is currently not supported)
|
122
|
+
|
123
|
+ # <<@encryption_off::byte>>
|
124
|
+
|
125
|
+ value ->
|
126
|
+ raise ArgumentError, "invalid value for :ssl: #{inspect(value)}"
|
127
|
+ end
|
128
|
+
|
129
|
+ {@encryption_token, data}
|
130
|
+ end
|
131
|
+
|
132
|
+ # defp encode_instance(opts) do
|
133
|
+ # # not working for some reason
|
134
|
+ # instance = Keyword.get(opts, :instance)
|
135
|
+
|
136
|
+ # if is_nil(instance) do
|
137
|
+ # {@instopt_token, <<0x00>>}
|
138
|
+ # else
|
139
|
+ # {@instopt_token, instance <> <<0x00>>}
|
140
|
+ # end
|
141
|
+ # end
|
142
|
+
|
143
|
+ defp encode_thread_id(_opts) do
|
144
|
+ pid_serial =
|
145
|
+ self()
|
146
|
+ |> inspect()
|
147
|
+ |> String.split(".")
|
148
|
+ |> Enum.at(1)
|
149
|
+ |> Integer.parse()
|
150
|
+ |> elem(0)
|
151
|
+
|
152
|
+ {@thread_id_token, <<pid_serial::ulong()>>}
|
153
|
+ end
|
154
|
+
|
155
|
+ defp encode_mars(_opts) do
|
156
|
+ {@mars_token, <<0x00>>}
|
157
|
+ end
|
158
|
+
|
159
|
+ defp encode_fed_auth_required(_opts) do
|
160
|
+ {@fed_auth_required_token, <<0x01>>}
|
161
|
+ end
|
162
|
+
|
163
|
+ # DECODE
|
164
|
+ @spec decode(iodata(), state()) ::
|
165
|
+ {:encrypt, state()}
|
166
|
+ | {:login, state()}
|
167
|
+ | {:disconnect, Tds.Error.t(), state()}
|
168
|
+ def decode(packet_data, %{opts: opts} = s) do
|
169
|
+ {:ok, %{encryption: encryption, instance: instance}} =
|
170
|
+ packet_data
|
171
|
+ |> IO.iodata_to_binary()
|
172
|
+ |> decode_tokens([], s)
|
173
|
+
|
174
|
+ case {ssl?(opts), encryption, instance} do
|
175
|
+ {_, _, false} ->
|
176
|
+ msg = "Connection terminated, connected instance is not '#{instance}'!"
|
177
|
+ disconnect(msg, s)
|
178
|
+
|
179
|
+ # Encryption is off. Allowed server response is :off or :not_supported
|
180
|
+ {:off, enc, _} when enc in [<<@encryption_off>>, <<@encryption_not_supported>>] ->
|
181
|
+ {:login, s}
|
182
|
+
|
183
|
+ # TODO: Encryption is off but server has encryption on. Should upgrade.
|
184
|
+ {:off, <<@encryption_required>>, _} ->
|
185
|
+ disconnect("Server does not allow the requested encryption level.", s)
|
186
|
+
|
187
|
+ # Encryption is not supported. The server needs to respond with :not_supported
|
188
|
+ {:not_supported, <<@encryption_not_supported>>, _} ->
|
189
|
+ {:login, s}
|
190
|
+
|
191
|
+ # Encryption is on. The server needs to respond with :on
|
192
|
+ {:on, <<@encryption_on>>, _} ->
|
193
|
+ {:encrypt, s}
|
194
|
+
|
195
|
+ # Encryption is required. The server needs to respond with :on
|
196
|
+ {:required, <<@encryption_on>>, _} ->
|
197
|
+ {:encrypt, s}
|
198
|
+
|
199
|
+ {_, _, _} ->
|
200
|
+ disconnect("Server does not allow the requested encryption level.", s)
|
201
|
+ end
|
202
|
+ end
|
203
|
+
|
204
|
+ defp decode_tokens(
|
205
|
+ <<@version_token, offset::ushort, length::ushort, tail::binary>>,
|
206
|
+ tokens,
|
207
|
+ s
|
208
|
+ ) do
|
209
|
+ tokens = [{:version, offset, length} | tokens]
|
210
|
+ decode_tokens(tail, tokens, s)
|
211
|
+ end
|
212
|
+
|
213
|
+ defp decode_tokens(
|
214
|
+ <<@encryption_token, offset::ushort, length::ushort, tail::binary>>,
|
215
|
+ tokens,
|
216
|
+ s
|
217
|
+ ) do
|
218
|
+ tokens = [{:encryption, offset, length} | tokens]
|
219
|
+ decode_tokens(tail, tokens, s)
|
220
|
+ end
|
221
|
+
|
222
|
+ defp decode_tokens(
|
223
|
+ <<@instopt_token, offset::ushort, length::ushort, tail::binary>>,
|
224
|
+ tokens,
|
225
|
+ s
|
226
|
+ ) do
|
227
|
+ tokens = [{:encryption, offset, length} | tokens]
|
228
|
+ decode_tokens(tail, tokens, s)
|
229
|
+ end
|
230
|
+
|
231
|
+ defp decode_tokens(
|
232
|
+ <<@thread_id_token, offset::ushort, length::ushort, tail::binary>>,
|
233
|
+ tokens,
|
234
|
+ s
|
235
|
+ ) do
|
236
|
+ tokens = [{:thread_id, offset, length} | tokens]
|
237
|
+ decode_tokens(tail, tokens, s)
|
238
|
+ end
|
239
|
+
|
240
|
+ defp decode_tokens(
|
241
|
+ <<@mars_token, offset::ushort, length::ushort, tail::binary>>,
|
242
|
+ tokens,
|
243
|
+ s
|
244
|
+ ) do
|
245
|
+ tokens = [{:mars, offset, length} | tokens]
|
246
|
+ decode_tokens(tail, tokens, s)
|
247
|
+ end
|
248
|
+
|
249
|
+ defp decode_tokens(
|
250
|
+ <<@fed_auth_required_token, offset::ushort, length::ushort, tail::binary>>,
|
251
|
+ tokens,
|
252
|
+ s
|
253
|
+ ) do
|
254
|
+ tokens = [{:fed_auth_required, offset, length} | tokens]
|
255
|
+ decode_tokens(tail, tokens, s)
|
256
|
+ end
|
257
|
+
|
258
|
+ defp decode_tokens(
|
259
|
+ <<@nonce_opt_token, offset::ushort, length::ushort, tail::binary>>,
|
260
|
+ tokens,
|
261
|
+ s
|
262
|
+ ) do
|
263
|
+ tokens = [{:nonce_opt, offset, length} | tokens]
|
264
|
+ decode_tokens(tail, tokens, s)
|
265
|
+ end
|
266
|
+
|
267
|
+ defp decode_tokens(
|
268
|
+ <<@termintator_token, tail::binary>>,
|
269
|
+ tokens,
|
270
|
+ _s
|
271
|
+ ) do
|
272
|
+ {:ok, decode_data(Enum.reverse(tokens), tail, %__MODULE__{})}
|
273
|
+ end
|
274
|
+
|
275
|
+ defp decode_data([], _, result), do: result
|
276
|
+
|
277
|
+ defp decode_data([{key, _, length} | tokens], bin, m) do
|
278
|
+ <<data::binary-size(length), tail::binary>> = bin
|
279
|
+
|
280
|
+ case key do
|
281
|
+ :version ->
|
282
|
+ <<major, minor, patch, trivial, subbuild, _>> = data
|
283
|
+
|
284
|
+ decode_data(
|
285
|
+ tokens,
|
286
|
+ tail,
|
287
|
+ %{m | version: {major, minor, patch, trivial, subbuild}}
|
288
|
+ )
|
289
|
+
|
290
|
+ :encryption ->
|
291
|
+ decode_data(
|
292
|
+ tokens,
|
293
|
+ tail,
|
294
|
+ %{m | encryption: data}
|
295
|
+ )
|
296
|
+
|
297
|
+ :instance ->
|
298
|
+ decode_data(
|
299
|
+ tokens,
|
300
|
+ tail,
|
301
|
+ %{m | instance: data == <<0x00>>}
|
302
|
+ )
|
303
|
+
|
304
|
+ # :thread_id ->
|
305
|
+ # :mars ->
|
306
|
+ # :fed_auth_required ->
|
307
|
+ # :nonce_opt ->
|
308
|
+ _ ->
|
309
|
+ decode_data(tokens, tail, m)
|
310
|
+ end
|
311
|
+ end
|
312
|
+
|
313
|
+ defp disconnect(message, s) do
|
314
|
+ {:disconnect, Tds.Error.exception(message), s}
|
315
|
+ end
|
316
|
+
|
317
|
+ defp ssl?(opts) do
|
318
|
+ case opts[:ssl] do
|
319
|
+ nil ->
|
320
|
+ :not_supported
|
321
|
+
|
322
|
+ true ->
|
323
|
+ :required
|
324
|
+
|
325
|
+ false ->
|
326
|
+ :not_supported
|
327
|
+
|
328
|
+ other ->
|
329
|
+ other
|
330
|
+ end
|
331
|
+ end
|
332
|
+ end
|
changed
lib/tds/query.ex
|
@@ -1,10 +1,12 @@
|
1
1
|
defmodule Tds.Query do
|
2
|
- @moduledoc false
|
2
|
+ @moduledoc """
|
3
|
+ TDS query encoding and decoding
|
4
|
+ """
|
3
5
|
|
4
6
|
@type t :: %__MODULE__{
|
5
|
- statement: String.t,
|
6
|
- handle: term
|
7
|
- }
|
7
|
+ statement: String.t(),
|
8
|
+ handle: term
|
9
|
+ }
|
8
10
|
|
9
11
|
defstruct [:statement, :handle]
|
10
12
|
end
|
added
lib/tds/tls.ex
|
@@ -0,0 +1,219 @@
|
1
|
+ defmodule Tds.Tls do
|
2
|
+ @moduledoc false
|
3
|
+ use GenServer
|
4
|
+
|
5
|
+ require Logger
|
6
|
+
|
7
|
+ import Kernel, except: [send: 2]
|
8
|
+ import Tds.BinaryUtils
|
9
|
+
|
10
|
+ @default_ssl_opts [active: false, cb_info: {Tds.Tls, :tcp, :tcp_closed, :tcp_error}]
|
11
|
+
|
12
|
+ defstruct [:socket, :ssl_opts, :owner_pid, :handshake?, :buffer]
|
13
|
+
|
14
|
+ def connect(socket, ssl_opts) do
|
15
|
+ ssl_opts = ssl_opts ++ @default_ssl_opts
|
16
|
+ :inet.setopts(socket, active: false)
|
17
|
+
|
18
|
+ with {:ok, pid} <- GenServer.start_link(__MODULE__, {socket, ssl_opts}, []),
|
19
|
+ :ok <- :gen_tcp.controlling_process(socket, pid) do
|
20
|
+ connection_result = :ssl.connect(socket, ssl_opts, :infinity)
|
21
|
+
|
22
|
+ # Check if ssl connection was established successfully
|
23
|
+ if elem(connection_result, 0) == :ok do
|
24
|
+ GenServer.cast(pid, :handshake_complete)
|
25
|
+ end
|
26
|
+
|
27
|
+ connection_result
|
28
|
+ else
|
29
|
+ error -> error
|
30
|
+ end
|
31
|
+ end
|
32
|
+
|
33
|
+ def controlling_process(socket, tls_conn_pid) do
|
34
|
+ socket
|
35
|
+ |> assert_connected!()
|
36
|
+ |> GenServer.call({:controlling_process, tls_conn_pid})
|
37
|
+ end
|
38
|
+
|
39
|
+ def send(socket, payload) do
|
40
|
+ socket
|
41
|
+ |> assert_connected!()
|
42
|
+ |> GenServer.call({:send, payload})
|
43
|
+ end
|
44
|
+
|
45
|
+ def recv(socket, length, timeout \\ :infinity) do
|
46
|
+ socket
|
47
|
+ |> assert_connected!()
|
48
|
+ |> GenServer.call({:recv, length, timeout}, timeout)
|
49
|
+ end
|
50
|
+
|
51
|
+ defdelegate getopts(port, options), to: :inet
|
52
|
+
|
53
|
+ # defdelegate setopts(socket, options), to: :inet
|
54
|
+ def setopts(socket, options) do
|
55
|
+ socket
|
56
|
+ |> assert_connected!()
|
57
|
+ |> GenServer.call({:setopts, options})
|
58
|
+ end
|
59
|
+
|
60
|
+ defdelegate peername(socket), to: :inet
|
61
|
+
|
62
|
+ :exports
|
63
|
+ |> :gen_tcp.module_info()
|
64
|
+ |> Enum.reject(fn {fun, _} ->
|
65
|
+ fun in [:send, :recv, :module_info, :controlling_process]
|
66
|
+ end)
|
67
|
+ |> Enum.each(fn
|
68
|
+ {name, 0} ->
|
69
|
+ defdelegate unquote(name)(), to: :gen_tcp
|
70
|
+
|
71
|
+ {name, 1} ->
|
72
|
+ defdelegate unquote(name)(arg1), to: :gen_tcp
|
73
|
+
|
74
|
+ {name, 2} ->
|
75
|
+ defdelegate unquote(name)(arg1, arg2), to: :gen_tcp
|
76
|
+
|
77
|
+ {name, 3} ->
|
78
|
+ defdelegate unquote(name)(arg1, arg2, arg3), to: :gen_tcp
|
79
|
+
|
80
|
+ {name, 4} ->
|
81
|
+ defdelegate unquote(name)(arg1, arg2, arg3, arg4), to: :gen_tcp
|
82
|
+ end)
|
83
|
+
|
84
|
+ # Asserts that the port / socket is still open and returns its `pid`
|
85
|
+ defp assert_connected!(socket) do
|
86
|
+ {:connected, pid} = Port.info(socket, :connected)
|
87
|
+ pid
|
88
|
+ end
|
89
|
+
|
90
|
+ # SERVER
|
91
|
+ def init({socket, ssl_opts}) do
|
92
|
+ {:ok, %__MODULE__{socket: socket, ssl_opts: ssl_opts, handshake?: true}}
|
93
|
+ end
|
94
|
+
|
95
|
+ def handle_call({:controlling_process, tls_conn_pid}, _from, s) do
|
96
|
+ {:reply, :ok, %{s | owner_pid: tls_conn_pid}}
|
97
|
+ end
|
98
|
+
|
99
|
+ def handle_call({:setopts, options}, _from, %{socket: socket, handshake?: hs} = s) do
|
100
|
+ tds_header_size = if hs == true, do: 8, else: 0
|
101
|
+
|
102
|
+ opts =
|
103
|
+ options
|
104
|
+ |> Enum.map(fn
|
105
|
+ {:active, val} when is_number(val) -> {:active, val + tds_header_size}
|
106
|
+ val -> val
|
107
|
+ end)
|
108
|
+
|
109
|
+ {:reply, :inet.setopts(socket, opts), s}
|
110
|
+ end
|
111
|
+
|
112
|
+ def handle_call({:send, data}, _from, %{socket: socket, handshake?: true} = s) do
|
113
|
+ size = IO.iodata_length(data) + 8
|
114
|
+
|
115
|
+ header = <<0x12, 0x01, size::unsigned-size(2)-unit(8), 0x00, 0x00, 0x00, 0x00>>
|
116
|
+
|
117
|
+ resp = :gen_tcp.send(socket, [header, data])
|
118
|
+ {:reply, resp, s}
|
119
|
+ end
|
120
|
+
|
121
|
+ def handle_call({:send, data}, _from, %{socket: socket, handshake?: false} = s) do
|
122
|
+ resp = :gen_tcp.send(socket, data)
|
123
|
+ {:reply, resp, s}
|
124
|
+ end
|
125
|
+
|
126
|
+ # def handle_call({:recv, length, timeout}, _from, %{socket: socket, handshake?: true} = s) do
|
127
|
+ # res = case :gen_tcp.recv(socket, length, timeout) do
|
128
|
+ # {:ok, data}
|
129
|
+ # end
|
130
|
+ # {:reply, res, s}
|
131
|
+ # end
|
132
|
+
|
133
|
+ def handle_call({:recv, length, timeout}, _from, %{socket: socket} = s) do
|
134
|
+ res = :gen_tcp.recv(socket, length, timeout)
|
135
|
+ {:reply, res, s}
|
136
|
+ end
|
137
|
+
|
138
|
+ def handle_cast(:handshake_complete, s), do: {:noreply, %{s | handshake?: false}}
|
139
|
+
|
140
|
+ def handle_info({:tcp, _, _} = msg, %{owner_pid: pid, handshake?: false, buffer: nil} = s) do
|
141
|
+ Kernel.send(pid, msg)
|
142
|
+ {:noreply, s}
|
143
|
+ end
|
144
|
+
|
145
|
+ def handle_info(
|
146
|
+ {:tcp, port, <<0x12, 0, size::unsigned-16, _::32, tail::binary>>},
|
147
|
+ %{socket: socket, owner_pid: pid, buffer: nil, handshake?: true} = s
|
148
|
+ ) do
|
149
|
+ expecting = size - 8
|
150
|
+
|
151
|
+ case tail do
|
152
|
+ <<ssl_payload::binary(expecting), next_packet::binary>> ->
|
153
|
+ Kernel.send(pid, {:tcp, socket, ssl_payload})
|
154
|
+ handle_info({:tcp, port, next_packet}, %{s | buffer: nil})
|
155
|
+
|
156
|
+ next_slice ->
|
157
|
+ state = %{s | buffer: {next_slice, expecting}}
|
158
|
+ {:noreply, state}
|
159
|
+ end
|
160
|
+ end
|
161
|
+
|
162
|
+ def handle_info(
|
163
|
+ {:tcp, port, <<0x12, 1, size::unsigned-16, _::32, tail::binary>>},
|
164
|
+ %{socket: socket, owner_pid: pid, buffer: nil, handshake?: true} = s
|
165
|
+ ) do
|
166
|
+ expecting = size - 8
|
167
|
+
|
168
|
+ case tail do
|
169
|
+ <<ssl_payload::binary(expecting), next_packet::binary>> ->
|
170
|
+ Kernel.send(pid, {:tcp, socket, ssl_payload})
|
171
|
+ handle_info({:tcp, port, next_packet}, %{s | buffer: nil})
|
172
|
+
|
173
|
+ next_slice ->
|
174
|
+ state = %{s | buffer: {next_slice, expecting}}
|
175
|
+ {:noreply, state}
|
176
|
+ end
|
177
|
+ end
|
178
|
+
|
179
|
+ def handle_info(
|
180
|
+ {:tcp, port, bin},
|
181
|
+ %{socket: socket, owner_pid: pid, buffer: {slice, expecting}, handshake?: true} = s
|
182
|
+ ) do
|
183
|
+ case IO.iodata_to_binary([slice, bin]) do
|
184
|
+ <<ssl_payload::binary(expecting), next_packet::binary>> ->
|
185
|
+ Kernel.send(pid, {:tcp, socket, ssl_payload})
|
186
|
+ handle_info({:tcp, port, next_packet}, %{s | buffer: nil})
|
187
|
+
|
188
|
+ next_slice ->
|
189
|
+ state = %{s | buffer: {next_slice, expecting}}
|
190
|
+ {:noreply, state}
|
191
|
+ end
|
192
|
+ end
|
193
|
+
|
194
|
+ def handle_info({:tcp, _, _} = msg, %{owner_pid: pid, handshake?: true, buffer: nil} = s) do
|
195
|
+ Kernel.send(pid, msg)
|
196
|
+ {:noreply, s}
|
197
|
+ end
|
198
|
+
|
199
|
+ def handle_info(
|
200
|
+ {:tcp_passive, _port} = msg,
|
201
|
+ %{owner_pid: pid, handshake?: false, buffer: nil} = s
|
202
|
+ ) do
|
203
|
+ Kernel.send(pid, msg)
|
204
|
+ {:noreply, s}
|
205
|
+ end
|
206
|
+
|
207
|
+ def handle_info({tag, _} = msg, %{owner_pid: pid} = s) when tag in [:tcp_closed, :ssl_closed] do
|
208
|
+ # todo
|
209
|
+ send(pid, msg)
|
210
|
+ {:stop, tag, s}
|
211
|
+ end
|
212
|
+
|
213
|
+ def handle_info({tag, _, _} = msg, %{owner_pid: pid} = s)
|
214
|
+ when tag in [:tcp_error, :ssl_error] do
|
215
|
+ # todo
|
216
|
+ send(pid, msg)
|
217
|
+ {:stop, tag, s}
|
218
|
+ end
|
219
|
+ end
|
removed
lib/tds/token_descriptors/basic.ex
|
@@ -1,3 +0,0 @@
|
1
|
- defmodule Tds.TokenDescriptors.Basic do
|
2
|
-
|
3
|
- end
|
changed
lib/tds/tokens.ex
|
@@ -1,11 +1,11 @@
|
1
1
|
defmodule Tds.Tokens do
|
2
2
|
import Tds.BinaryUtils
|
3
|
- import Tds.Utils
|
4
3
|
import Bitwise
|
5
4
|
|
6
5
|
require Logger
|
7
6
|
|
8
7
|
alias Tds.Types
|
8
|
+ alias Tds.UCS2
|
9
9
|
|
10
10
|
def retval_typ_size(38) do
|
11
11
|
# 0x26 - SYBINTN - 1
|
|
@@ -86,7 +86,7 @@ defmodule Tds.Tokens do
|
86
86
|
data::binary
|
87
87
|
>> = bin
|
88
88
|
|
89
|
- name = ucs2_to_utf(name)
|
89
|
+ name = UCS2.to_string(name)
|
90
90
|
{type_info, tail} = Tds.Types.decode_info(data)
|
91
91
|
{value, tail} = Tds.Types.decode_data(type_info, tail)
|
92
92
|
param = %Tds.Parameter{name: name, value: value, direction: :output}
|
|
@@ -138,9 +138,9 @@ defmodule Tds.Tokens do
|
138
138
|
number: number,
|
139
139
|
state: state,
|
140
140
|
class: class,
|
141
|
- msg_text: ucs2_to_utf(:binary.copy(msg)),
|
142
|
- server_name: ucs2_to_utf(:binary.copy(server_name)),
|
143
|
- proc_name: ucs2_to_utf(:binary.copy(proc_name)),
|
141
|
+ msg_text: UCS2.to_string(:binary.copy(msg)),
|
142
|
+ server_name: UCS2.to_string(:binary.copy(server_name)),
|
143
|
+ proc_name: UCS2.to_string(:binary.copy(proc_name)),
|
144
144
|
line_number: line_number
|
145
145
|
}
|
146
146
|
|
|
@@ -170,9 +170,9 @@ defmodule Tds.Tokens do
|
170
170
|
number: number,
|
171
171
|
state: state,
|
172
172
|
class: class,
|
173
|
- msg_text: ucs2_to_utf(msg),
|
174
|
- server_name: ucs2_to_utf(server_name),
|
175
|
- proc_name: ucs2_to_utf(proc_name),
|
173
|
+ msg_text: UCS2.to_string(msg),
|
174
|
+ server_name: UCS2.to_string(server_name),
|
175
|
+ proc_name: UCS2.to_string(proc_name),
|
176
176
|
line_number: line_number
|
177
177
|
}
|
178
178
|
|
|
@@ -227,8 +227,8 @@ defmodule Tds.Tokens do
|
227
227
|
rest::binary
|
228
228
|
>> = tail
|
229
229
|
|
230
|
- new_database = ucs2_to_utf(new_value)
|
231
|
- old_database = ucs2_to_utf(old_value)
|
230
|
+ new_database = UCS2.to_string(new_value)
|
231
|
+ old_database = UCS2.to_string(old_value)
|
232
232
|
{{:database, new_database, old_database}, rest}
|
233
233
|
|
234
234
|
0x02 ->
|
|
@@ -240,8 +240,8 @@ defmodule Tds.Tokens do
|
240
240
|
rest::binary
|
241
241
|
>> = tail
|
242
242
|
|
243
|
- new_language = ucs2_to_utf(new_value)
|
244
|
- old_language = ucs2_to_utf(old_value)
|
243
|
+ new_language = UCS2.to_string(new_value)
|
244
|
+ old_language = UCS2.to_string(old_value)
|
245
245
|
{{:language, new_language, old_language}, rest}
|
246
246
|
|
247
247
|
0x03 ->
|
|
@@ -253,8 +253,8 @@ defmodule Tds.Tokens do
|
253
253
|
rest::binary
|
254
254
|
>> = tail
|
255
255
|
|
256
|
- new_charset = ucs2_to_utf(new_value)
|
257
|
- old_charset = ucs2_to_utf(old_value)
|
256
|
+ new_charset = UCS2.to_string(new_value)
|
257
|
+ old_charset = UCS2.to_string(old_value)
|
258
258
|
{{:charset, new_charset, old_charset}, rest}
|
259
259
|
|
260
260
|
0x04 ->
|
|
@@ -268,7 +268,7 @@ defmodule Tds.Tokens do
|
268
268
|
|
269
269
|
new_packetsize =
|
270
270
|
new_value
|
271
|
- |> ucs2_to_utf()
|
271
|
+ |> UCS2.to_string()
|
272
272
|
|> Integer.parse()
|
273
273
|
|> case do
|
274
274
|
:error -> 4096
|
|
@@ -278,7 +278,7 @@ defmodule Tds.Tokens do
|
278
278
|
|
279
279
|
old_packetsize =
|
280
280
|
old_value
|
281
|
- |> ucs2_to_utf()
|
281
|
+ |> UCS2.to_string()
|
282
282
|
|> Integer.parse()
|
283
283
|
|> case do
|
284
284
|
:error -> 4096
|
|
@@ -386,7 +386,7 @@ defmodule Tds.Tokens do
|
386
386
|
rest::binary
|
387
387
|
>> = tail
|
388
388
|
|
389
|
- {{:userinfo, ucs2_to_utf(value), nil}, rest}
|
389
|
+ {{:userinfo, UCS2.to_string(value), nil}, rest}
|
390
390
|
|
391
391
|
0x14 ->
|
392
392
|
<<
|
|
@@ -402,7 +402,7 @@ defmodule Tds.Tokens do
|
402
402
|
>> = tail
|
403
403
|
|
404
404
|
routing = %{
|
405
|
- hostname: ucs2_to_utf(alt_host),
|
405
|
+ hostname: UCS2.to_string(alt_host),
|
406
406
|
port: port
|
407
407
|
}
|
408
408
|
|
|
@@ -414,8 +414,7 @@ defmodule Tds.Tokens do
|
414
414
|
|
415
415
|
## DONE
|
416
416
|
defp decode_done(
|
417
|
- <<status::little-unsigned-size(2)-unit(8),
|
418
|
- cur_cmd::little-unsigned-size(2)-unit(8),
|
417
|
+ <<status::little-unsigned-size(2)-unit(8), cur_cmd::little-unsigned-size(2)-unit(8),
|
419
418
|
row_count::little-size(8)-unit(8), tail::binary>>,
|
420
419
|
collmetadata
|
421
420
|
) do
|
|
@@ -469,7 +468,7 @@ defmodule Tds.Tokens do
|
469
468
|
token = %{
|
470
469
|
t_sql_only: interface == 1,
|
471
470
|
tds_version: tds_version,
|
472
|
- program: ucs2_to_utf(prog_name),
|
471
|
+ program: UCS2.to_string(prog_name),
|
473
472
|
version: "#{major_ver}.#{minor_ver}.#{build_hi}.#{build_low}"
|
474
473
|
}
|
475
474
|
|
|
@@ -493,8 +492,7 @@ defmodule Tds.Tokens do
|
493
492
|
end
|
494
493
|
|
495
494
|
defp bitmap_list(
|
496
|
- <<b8::1, b7::1, b6::1, b5::1, b4::1, b3::1, b2::1, b1::1,
|
497
|
- tail::binary>>,
|
495
|
+ <<b8::1, b7::1, b6::1, b5::1, b4::1, b3::1, b2::1, b1::1, tail::binary>>,
|
498
496
|
n
|
499
497
|
) do
|
500
498
|
{bits, tail} = bitmap_list(tail, n - 1)
|
|
@@ -523,10 +521,8 @@ defmodule Tds.Tokens do
|
523
521
|
{info, tail}
|
524
522
|
end
|
525
523
|
|
526
|
- defp decode_column_name(
|
527
|
- <<length::int8, name::binary-size(length)-unit(16), tail::binary>>
|
528
|
- ) do
|
529
|
- name = ucs2_to_utf(name)
|
524
|
+ defp decode_column_name(<<length::int8, name::binary-size(length)-unit(16), tail::binary>>) do
|
525
|
+ name = UCS2.to_string(name)
|
530
526
|
{name, tail}
|
531
527
|
end
|
changed
lib/tds/types.ex
|
@@ -4,13 +4,18 @@ defmodule Tds.Types do
|
4
4
|
use Bitwise
|
5
5
|
|
6
6
|
alias Tds.Parameter
|
7
|
+ alias Tds.UCS2
|
7
8
|
|
8
9
|
@year_1900_days :calendar.date_to_gregorian_days({1900, 1, 1})
|
9
10
|
@secs_in_min 60
|
10
11
|
@secs_in_hour 60 * @secs_in_min
|
11
12
|
@max_time_scale 7
|
12
13
|
|
14
|
+ # Zero Length Data Types
|
13
15
|
@tds_data_type_null 0x1F
|
16
|
+
|
17
|
+ # Fixed Length Data Types
|
18
|
+ # See: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/859eb3d2-80d3-40f6-a637-414552c9c552
|
14
19
|
@tds_data_type_tinyint 0x30
|
15
20
|
@tds_data_type_bit 0x32
|
16
21
|
@tds_data_type_smallint 0x34
|
|
@@ -23,21 +28,24 @@ defmodule Tds.Types do
|
23
28
|
@tds_data_type_smallmoney 0x7A
|
24
29
|
@tds_data_type_bigint 0x7F
|
25
30
|
|
26
|
- @fixed_data_types [
|
27
|
- @tds_data_type_null,
|
28
|
- @tds_data_type_tinyint,
|
29
|
- @tds_data_type_bit,
|
30
|
- @tds_data_type_smallint,
|
31
|
- @tds_data_type_int,
|
32
|
- @tds_data_type_smalldatetime,
|
33
|
- @tds_data_type_real,
|
34
|
- @tds_data_type_money,
|
35
|
- @tds_data_type_datetime,
|
36
|
- @tds_data_type_float,
|
37
|
- @tds_data_type_smallmoney,
|
38
|
- @tds_data_type_bigint
|
39
|
- ]
|
31
|
+ # Fixed Data Types with their length
|
32
|
+ @fixed_data_types %{
|
33
|
+ @tds_data_type_null => 0,
|
34
|
+ @tds_data_type_tinyint => 1,
|
35
|
+ @tds_data_type_bit => 1,
|
36
|
+ @tds_data_type_smallint => 2,
|
37
|
+ @tds_data_type_int => 4,
|
38
|
+ @tds_data_type_smalldatetime => 4,
|
39
|
+ @tds_data_type_real => 4,
|
40
|
+ @tds_data_type_money => 8,
|
41
|
+ @tds_data_type_datetime => 8,
|
42
|
+ @tds_data_type_float => 8,
|
43
|
+ @tds_data_type_smallmoney => 4,
|
44
|
+ @tds_data_type_bigint => 8
|
45
|
+ }
|
40
46
|
|
47
|
+ # Variable-Length Data Types
|
48
|
+ # See: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de
|
41
49
|
@tds_data_type_uniqueidentifier 0x24
|
42
50
|
@tds_data_type_intn 0x26
|
43
51
|
# legacy
|
|
@@ -160,42 +168,11 @@ defmodule Tds.Types do
|
160
168
|
end
|
161
169
|
|
162
170
|
def decode_info(<<data_type_code::unsigned-8, tail::binary>>)
|
163
|
- when data_type_code in @fixed_data_types do
|
164
|
- length =
|
165
|
- cond do
|
166
|
- data_type_code == @tds_data_type_null ->
|
167
|
- 0
|
168
|
-
|
169
|
- data_type_code in [
|
170
|
- @tds_data_type_tinyint,
|
171
|
- @tds_data_type_bit
|
172
|
- ] ->
|
173
|
- 1
|
174
|
-
|
175
|
- data_type_code == @tds_data_type_smallint ->
|
176
|
- 2
|
177
|
-
|
178
|
- data_type_code in [
|
179
|
- @tds_data_type_int,
|
180
|
- @tds_data_type_smalldatetime,
|
181
|
- @tds_data_type_real,
|
182
|
- @tds_data_type_smallmoney
|
183
|
- ] ->
|
184
|
- 4
|
185
|
-
|
186
|
- data_type_code in [
|
187
|
- @tds_data_type_datetime,
|
188
|
- @tds_data_type_float,
|
189
|
- @tds_data_type_money,
|
190
|
- @tds_data_type_bigint
|
191
|
- ] ->
|
192
|
- 8
|
193
|
- end
|
194
|
-
|
171
|
+ when is_map_key(@fixed_data_types, data_type_code) do
|
195
172
|
{%{
|
196
173
|
data_type: :fixed,
|
197
174
|
data_type_code: data_type_code,
|
198
|
- length: length,
|
175
|
+ length: @fixed_data_types[data_type_code],
|
199
176
|
data_type_name: to_atom(data_type_code)
|
200
177
|
}, tail}
|
201
178
|
end
|
|
@@ -372,8 +349,7 @@ defmodule Tds.Types do
|
372
349
|
1..numparts,
|
373
350
|
rest,
|
374
351
|
fn _,
|
375
|
- <<tsize::little-unsigned-16,
|
376
|
- _table_name::binary-size(tsize)-unit(16),
|
352
|
+ <<tsize::little-unsigned-16, _table_name::binary-size(tsize)-unit(16),
|
377
353
|
next_rest::binary>> ->
|
378
354
|
next_rest
|
379
355
|
end
|
|
@@ -696,9 +672,7 @@ defmodule Tds.Types do
|
696
672
|
# UUID
|
697
673
|
def decode_uuid(<<_::128>> = bin), do: bin
|
698
674
|
|
699
|
- def encode_uuid(
|
700
|
- <<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string
|
701
|
- ) do
|
675
|
+ def encode_uuid(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string) do
|
702
676
|
raise ArgumentError,
|
703
677
|
"trying to load string UUID as Tds.Types.UUID: #{inspect(string)}. " <>
|
704
678
|
"Maybe you wanted to declare :uuid as your database field?"
|
|
@@ -729,11 +703,11 @@ defmodule Tds.Types do
|
729
703
|
end
|
730
704
|
|
731
705
|
def decode_nchar(_data_info, <<data::binary>>) do
|
732
|
- ucs2_to_utf(data)
|
706
|
+ UCS2.to_string(data)
|
733
707
|
end
|
734
708
|
|
735
709
|
def decode_xml(_data_info, <<data::binary>>) do
|
736
|
- ucs2_to_utf(data)
|
710
|
+ UCS2.to_string(data)
|
737
711
|
end
|
738
712
|
|
739
713
|
def decode_udt(%{}, <<data::binary>>) do
|
|
@@ -821,7 +795,7 @@ defmodule Tds.Types do
|
821
795
|
|
822
796
|
length =
|
823
797
|
if value != nil do
|
824
|
- value = value |> to_little_ucs2
|
798
|
+ value = value |> UCS2.from_string()
|
825
799
|
value_size = byte_size(value)
|
826
800
|
|
827
801
|
if value_size == 0 or value_size > 8000 do
|
|
@@ -938,7 +912,7 @@ defmodule Tds.Types do
|
938
912
|
|
939
913
|
def encode_float_type(%Parameter{value: value} = param)
|
940
914
|
when is_float(value) do
|
941
|
- encode_float_type(%{param | value: to_decimal(value)})
|
915
|
+ encode_float_type(%{param | value: Decimal.from_float(value)})
|
942
916
|
end
|
943
917
|
|
944
918
|
def encode_float_type(%Parameter{value: %Decimal{} = value}) do
|
|
@@ -989,9 +963,7 @@ defmodule Tds.Types do
|
989
963
|
@doc """
|
990
964
|
Creates the Parameter Descriptor for the selected type
|
991
965
|
"""
|
992
|
- def encode_param_descriptor(
|
993
|
- %Parameter{name: name, value: value, type: type} = param
|
994
|
- )
|
966
|
+ def encode_param_descriptor(%Parameter{name: name, value: value, type: type} = param)
|
995
967
|
when type != nil do
|
996
968
|
desc =
|
997
969
|
case type do
|
|
@@ -1152,9 +1124,7 @@ defmodule Tds.Types do
|
1152
1124
|
end
|
1153
1125
|
|
1154
1126
|
# Decimal.new/0 is undefined -- modifying params to hopefully fix
|
1155
|
- def encode_decimal_descriptor(
|
1156
|
- %Parameter{type: :decimal, value: value} = param
|
1157
|
- ) do
|
1127
|
+ def encode_decimal_descriptor(%Parameter{type: :decimal, value: value} = param) do
|
1158
1128
|
encode_decimal_descriptor(%{param | value: Decimal.new(value)})
|
1159
1129
|
end
|
1160
1130
|
|
|
@@ -1166,7 +1136,7 @@ defmodule Tds.Types do
|
1166
1136
|
def encode_float_descriptor(%Parameter{value: value} = param)
|
1167
1137
|
when is_float(value) do
|
1168
1138
|
param
|
1169
|
- |> Map.put(:value, to_decimal(value))
|
1139
|
+ |> Map.put(:value, Decimal.from_float(value))
|
1170
1140
|
|> encode_float_descriptor
|
1171
1141
|
end
|
1172
1142
|
|
|
@@ -1216,7 +1186,7 @@ defmodule Tds.Types do
|
1216
1186
|
do: <<@tds_plp_null::little-unsigned-64>>
|
1217
1187
|
|
1218
1188
|
def encode_data(@tds_data_type_nvarchar, value, _) do
|
1219
|
- value = to_little_ucs2(value)
|
1189
|
+ value = UCS2.from_string(value)
|
1220
1190
|
value_size = byte_size(value)
|
1221
1191
|
|
1222
1192
|
cond do
|
|
@@ -1636,8 +1606,7 @@ defmodule Tds.Types do
|
1636
1606
|
# 10^scale fs in 1 sec
|
1637
1607
|
fs_per_sec = trunc(:math.pow(10, scale))
|
1638
1608
|
|
1639
|
- fsec =
|
1640
|
- hour * 3600 * fs_per_sec + min * 60 * fs_per_sec + sec * fs_per_sec + fsec
|
1609
|
+ fsec = hour * 3600 * fs_per_sec + min * 60 * fs_per_sec + sec * fs_per_sec + fsec
|
1641
1610
|
|
1642
1611
|
bin =
|
1643
1612
|
cond do
|
|
@@ -1803,9 +1772,9 @@ defmodule Tds.Types do
|
1803
1772
|
>> = tail
|
1804
1773
|
|
1805
1774
|
schema_info = %{
|
1806
|
- db: ucs2_to_utf(db),
|
1807
|
- prefix: ucs2_to_utf(prefix),
|
1808
|
- schema: ucs2_to_utf(schema)
|
1775
|
+ db: UCS2.to_string(db),
|
1776
|
+ prefix: UCS2.to_string(prefix),
|
1777
|
+ schema: UCS2.to_string(schema)
|
1809
1778
|
}
|
1810
1779
|
|
1811
1780
|
{schema_info, rest}
|
changed
lib/tds/types/uuid.ex
|
@@ -1,19 +1,18 @@
|
1
1
|
defmodule Tds.Types.UUID do
|
2
|
- @moduledoc false
|
2
|
+ @moduledoc """
|
3
|
+ UUID data type
|
4
|
+ """
|
3
5
|
|
4
6
|
@doc """
|
5
7
|
Casts to UUID.
|
6
8
|
"""
|
7
|
- def cast(<< a1, a2, a3, a4, a5, a6, a7, a8, ?-,
|
8
|
- b1, b2, b3, b4, ?-,
|
9
|
- c1, c2, c3, c4, ?-,
|
10
|
- d1, d2, d3, d4, ?-,
|
11
|
- e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >>) do
|
12
|
- << c(a1), c(a2), c(a3), c(a4), c(a5), c(a6), c(a7), c(a8), ?-,
|
13
|
- c(b1), c(b2), c(b3), c(b4), ?-,
|
14
|
- c(c1), c(c2), c(c3), c(c4), ?-,
|
15
|
- c(d1), c(d2), c(d3), c(d4), ?-,
|
16
|
- c(e1), c(e2), c(e3), c(e4), c(e5), c(e6), c(e7), c(e8), c(e9), c(e10), c(e11), c(e12) >>
|
9
|
+ def cast(
|
10
|
+ <<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2, d3,
|
11
|
+ d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
|
12
|
+ ) do
|
13
|
+ <<c(a1), c(a2), c(a3), c(a4), c(a5), c(a6), c(a7), c(a8), ?-, c(b1), c(b2), c(b3), c(b4), ?-,
|
14
|
+ c(c1), c(c2), c(c3), c(c4), ?-, c(d1), c(d2), c(d3), c(d4), ?-, c(e1), c(e2), c(e3), c(e4),
|
15
|
+ c(e5), c(e6), c(e7), c(e8), c(e9), c(e10), c(e11), c(e12)>>
|
17
16
|
catch
|
18
17
|
:error -> :error
|
19
18
|
else
|
|
@@ -58,25 +57,20 @@ defmodule Tds.Types.UUID do
|
58
57
|
defp c(?d), do: ?d
|
59
58
|
defp c(?e), do: ?e
|
60
59
|
defp c(?f), do: ?f
|
61
|
- defp c(_), do: throw(:error)
|
60
|
+ defp c(_), do: throw(:error)
|
62
61
|
|
63
62
|
@doc """
|
64
63
|
Converts a string representing a UUID into a binary.
|
65
64
|
"""
|
66
|
- def dump(<< a1, a2, a3, a4, a5, a6, a7, a8, ?-,
|
67
|
- b1, b2, b3, b4, ?-,
|
68
|
- c1, c2, c3, c4, ?-,
|
69
|
- d1, d2, d3, d4, ?-,
|
70
|
- e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >>) do
|
65
|
+ def dump(
|
66
|
+ <<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2, d3,
|
67
|
+ d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
|
68
|
+ ) do
|
71
69
|
try do
|
72
|
- << d(a7)::4, d(a8)::4, d(a5)::4, d(a6)::4,
|
73
|
- d(a3)::4, d(a4)::4, d(a1)::4, d(a2)::4,
|
74
|
- d(b3)::4, d(b4)::4, d(b1)::4, d(b2)::4,
|
75
|
- d(c3)::4, d(c4)::4, d(c1)::4, d(c2)::4,
|
76
|
- d(d1)::4, d(d2)::4, d(d3)::4, d(d4)::4,
|
77
|
- d(e1)::4, d(e2)::4, d(e3)::4, d(e4)::4,
|
78
|
- d(e5)::4, d(e6)::4, d(e7)::4, d(e8)::4,
|
79
|
- d(e9)::4, d(e10)::4, d(e11)::4, d(e12)::4 >>
|
70
|
+ <<d(a7)::4, d(a8)::4, d(a5)::4, d(a6)::4, d(a3)::4, d(a4)::4, d(a1)::4, d(a2)::4, d(b3)::4,
|
71
|
+ d(b4)::4, d(b1)::4, d(b2)::4, d(c3)::4, d(c4)::4, d(c1)::4, d(c2)::4, d(d1)::4, d(d2)::4,
|
72
|
+ d(d3)::4, d(d4)::4, d(e1)::4, d(e2)::4, d(e3)::4, d(e4)::4, d(e5)::4, d(e6)::4, d(e7)::4,
|
73
|
+ d(e8)::4, d(e9)::4, d(e10)::4, d(e11)::4, d(e12)::4>>
|
80
74
|
catch
|
81
75
|
:error -> :error
|
82
76
|
else
|
|
@@ -118,7 +112,7 @@ defmodule Tds.Types.UUID do
|
118
112
|
defp d(?d), do: 13
|
119
113
|
defp d(?e), do: 14
|
120
114
|
defp d(?f), do: 15
|
121
|
- defp d(_), do: throw(:error)
|
115
|
+ defp d(_), do: throw(:error)
|
122
116
|
|
123
117
|
@doc """
|
124
118
|
Converts a binary UUID into a string.
|
|
@@ -128,8 +122,9 @@ defmodule Tds.Types.UUID do
|
128
122
|
end
|
129
123
|
|
130
124
|
def load(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string) do
|
131
|
- raise ArgumentError, "trying to load string UUID as Tds.Types.UUID: #{inspect string}. " <>
|
132
|
- "Maybe you wanted to declare :uuid as your database field?"
|
125
|
+ raise ArgumentError,
|
126
|
+ "trying to load string UUID as Tds.Types.UUID: #{inspect(string)}. " <>
|
127
|
+ "Maybe you wanted to declare :uuid as your database field?"
|
133
128
|
end
|
134
129
|
|
135
130
|
def load(_), do: :error
|
|
@@ -146,41 +141,27 @@ defmodule Tds.Types.UUID do
|
146
141
|
Generates a version 4 (random) UUID in the binary format.
|
147
142
|
"""
|
148
143
|
def bingenerate do
|
149
|
- << a1::4, a2::4, a3::4, a4::4,
|
150
|
- a5::4, a6::4, a7::4, a8::4,
|
151
|
- b1::4, b2::4, b3::4, b4::4,
|
152
|
- _ ::4, c2::4, c3::4, c4::4,
|
153
|
- d1::4, d2::4, d3::4, d4::4,
|
154
|
- e1::4, e2::4, e3::4, e4::4,
|
155
|
- _ ::4, e6::4, e7::4, e8::4,
|
156
|
- e9::4, e10::4, e11::4, e12::4 >> = :crypto.strong_rand_bytes(16)
|
157
|
- << a7::4, a8::4, a5::4, a6::4,
|
158
|
- a3::4, a4::4, a1::4, a2::4,
|
159
|
- b3::4, b4::4, b1::4, b2::4,
|
160
|
- c3::4, c4::4, 4 ::4, c2::4,
|
161
|
- d1::4, d2::4, d3::4, d4::4,
|
162
|
- e1::4, e2::4, e3::4, e4::4,
|
163
|
- 2 ::4, e6::4, e7::4, e8::4,
|
164
|
- e9::4, e10::4, e11::4, e12::4 >>
|
144
|
+ <<a1::4, a2::4, a3::4, a4::4, a5::4, a6::4, a7::4, a8::4, b1::4, b2::4, b3::4, b4::4, _::4,
|
145
|
+ c2::4, c3::4, c4::4, d1::4, d2::4, d3::4, d4::4, e1::4, e2::4, e3::4, e4::4, _::4, e6::4,
|
146
|
+ e7::4, e8::4, e9::4, e10::4, e11::4, e12::4>> = :crypto.strong_rand_bytes(16)
|
147
|
+
|
148
|
+ <<a7::4, a8::4, a5::4, a6::4, a3::4, a4::4, a1::4, a2::4, b3::4, b4::4, b1::4, b2::4, c3::4,
|
149
|
+ c4::4, 4::4, c2::4, d1::4, d2::4, d3::4, d4::4, e1::4, e2::4, e3::4, e4::4, 2::4, e6::4,
|
150
|
+ e7::4, e8::4, e9::4, e10::4, e11::4, e12::4>>
|
165
151
|
end
|
166
152
|
|
167
153
|
# Callback invoked by autogenerate fields.
|
168
154
|
@doc false
|
169
155
|
def autogenerate, do: generate()
|
170
156
|
|
171
|
- defp encode(<< a1::4, a2::4, a3::4, a4::4,
|
172
|
- a5::4, a6::4, a7::4, a8::4,
|
173
|
- b1::4, b2::4, b3::4, b4::4,
|
174
|
- c1::4, c2::4, c3::4, c4::4,
|
175
|
- d1::4, d2::4, d3::4, d4::4,
|
176
|
- e1::4, e2::4, e3::4, e4::4,
|
177
|
- e5::4, e6::4, e7::4, e8::4,
|
178
|
- e9::4, e10::4, e11::4, e12::4 >>) do
|
179
|
- << e(a7), e(a8), e(a5), e(a6), e(a3), e(a4), e(a1), e(a2), ?-,
|
180
|
- e(b3), e(b4), e(b1), e(b2), ?-,
|
181
|
- e(c3), e(c4), e(c1), e(c2), ?-,
|
182
|
- e(d1), e(d2), e(d3), e(d4), ?-,
|
183
|
- e(e1), e(e2), e(e3), e(e4), e(e5), e(e6), e(e7), e(e8), e(e9), e(e10), e(e11), e(e12) >>
|
157
|
+ defp encode(
|
158
|
+ <<a1::4, a2::4, a3::4, a4::4, a5::4, a6::4, a7::4, a8::4, b1::4, b2::4, b3::4, b4::4,
|
159
|
+ c1::4, c2::4, c3::4, c4::4, d1::4, d2::4, d3::4, d4::4, e1::4, e2::4, e3::4, e4::4,
|
160
|
+ e5::4, e6::4, e7::4, e8::4, e9::4, e10::4, e11::4, e12::4>>
|
161
|
+ ) do
|
162
|
+ <<e(a7), e(a8), e(a5), e(a6), e(a3), e(a4), e(a1), e(a2), ?-, e(b3), e(b4), e(b1), e(b2), ?-,
|
163
|
+ e(c3), e(c4), e(c1), e(c2), ?-, e(d1), e(d2), e(d3), e(d4), ?-, e(e1), e(e2), e(e3), e(e4),
|
164
|
+ e(e5), e(e6), e(e7), e(e8), e(e9), e(e10), e(e11), e(e12)>>
|
184
165
|
catch
|
185
166
|
:error -> :error
|
186
167
|
else
|
|
@@ -189,16 +170,16 @@ defmodule Tds.Types.UUID do
|
189
170
|
|
190
171
|
@compile {:inline, e: 1}
|
191
172
|
|
192
|
- defp e(0), do: ?0
|
193
|
- defp e(1), do: ?1
|
194
|
- defp e(2), do: ?2
|
195
|
- defp e(3), do: ?3
|
196
|
- defp e(4), do: ?4
|
197
|
- defp e(5), do: ?5
|
198
|
- defp e(6), do: ?6
|
199
|
- defp e(7), do: ?7
|
200
|
- defp e(8), do: ?8
|
201
|
- defp e(9), do: ?9
|
173
|
+ defp e(0), do: ?0
|
174
|
+ defp e(1), do: ?1
|
175
|
+ defp e(2), do: ?2
|
176
|
+ defp e(3), do: ?3
|
177
|
+ defp e(4), do: ?4
|
178
|
+ defp e(5), do: ?5
|
179
|
+ defp e(6), do: ?6
|
180
|
+ defp e(7), do: ?7
|
181
|
+ defp e(8), do: ?8
|
182
|
+ defp e(9), do: ?9
|
202
183
|
defp e(10), do: ?a
|
203
184
|
defp e(11), do: ?b
|
204
185
|
defp e(12), do: ?c
|
added
lib/tds/ucs2.ex
|
@@ -0,0 +1,25 @@
|
1
|
+ defmodule Tds.UCS2 do
|
2
|
+ @moduledoc """
|
3
|
+ Converting UTF-8 strings into UCS-2 (UTF16 Little Endian) strings
|
4
|
+ used by the MSSQL Database
|
5
|
+ """
|
6
|
+ alias Tds.Utils
|
7
|
+
|
8
|
+ @ucs2_charset "utf-16le"
|
9
|
+
|
10
|
+ @doc """
|
11
|
+ Converts a UTF-8 string into UCS-2
|
12
|
+ """
|
13
|
+ @spec from_string(binary) :: binary
|
14
|
+ def from_string(string) when is_binary(string) do
|
15
|
+ Utils.encode_chars(string, @ucs2_charset)
|
16
|
+ end
|
17
|
+
|
18
|
+ @doc """
|
19
|
+ Converts a UCS-2 string into UTF-8
|
20
|
+ """
|
21
|
+ @spec to_string(binary) :: binary
|
22
|
+ def to_string(string) when is_binary(string) do
|
23
|
+ Utils.decode_chars(string, @ucs2_charset)
|
24
|
+ end
|
25
|
+ end
|
changed
lib/tds/utils.ex
|
@@ -1,65 +1,15 @@
|
1
1
|
defmodule Tds.Utils do
|
2
2
|
@moduledoc false
|
3
|
- require Logger
|
4
|
-
|
5
|
- def to_hex_list(x) when is_list(x) do
|
6
|
- Enum.map(x, &Base.encode16(<<&1>>))
|
7
|
- end
|
8
|
-
|
9
|
- def to_hex_list(x) when is_binary(x) do
|
10
|
- x
|
11
|
- |> :erlang.binary_to_list()
|
12
|
- |> to_hex_list()
|
13
|
- end
|
14
|
-
|
15
|
- def to_hex_string(x) when is_binary(x) do
|
16
|
- x
|
17
|
- |> to_hex_list()
|
18
|
- |> to_hex_string()
|
19
|
- end
|
20
|
-
|
21
|
- def to_hex_string(x) when is_list(x) do
|
22
|
- Enum.join(x, " ")
|
23
|
- end
|
24
|
-
|
25
|
- def to_little_ucs2(str) when is_list(str) do
|
26
|
- str
|
27
|
- |> IO.iodata_to_binary()
|
28
|
- |> to_little_ucs2()
|
29
|
- end
|
30
|
-
|
31
|
- def to_little_ucs2(str) do
|
32
|
- encode_chars(str, "utf-16le")
|
33
|
- end
|
34
|
-
|
35
|
- def ucs2_to_utf(s) do
|
36
|
- decode_chars(s, "utf-16le")
|
37
|
- end
|
38
|
-
|
39
3
|
def encode_chars(string, to_codepage) do
|
40
4
|
Application.get_env(:tds, :text_encoder, Tds.Latin1)
|
41
5
|
|> apply(:encode, [string, to_codepage])
|
42
6
|
end
|
43
7
|
|
44
|
- def decode_chars(binary, from_codepage) do
|
8
|
+ def decode_chars(binary, from_codepage) when is_binary(binary) do
|
45
9
|
Application.get_env(:tds, :text_encoder, Tds.Latin1)
|
46
10
|
|> apply(:decode, [binary, from_codepage])
|
47
11
|
end
|
48
12
|
|
49
|
- def to_boolean(<<1>>) do
|
50
|
- true
|
51
|
- end
|
52
|
-
|
53
|
- def to_boolean(<<0>>) do
|
54
|
- false
|
55
|
- end
|
56
|
-
|
57
|
- def error(error, _s) do
|
58
|
- {:error, error}
|
59
|
- end
|
60
|
-
|
61
|
- def to_decimal(float), do: Decimal.from_float(float)
|
62
|
-
|
63
13
|
@doc false
|
64
14
|
def use_elixir_calendar_types(value),
|
65
15
|
do: Process.put(:use_elixir_calendar_types, value)
|
changed
lib/tds/versions.ex
|
@@ -1,26 +1,23 @@
|
1
1
|
defmodule Tds.Version do
|
2
2
|
import Tds.Protocol.Grammar
|
3
3
|
|
4
|
- defstruct version: 0x74000004, str_version: "7.4"
|
4
|
+ @default_version :v7_4
|
5
|
+ @default_code 0x74000004
|
5
6
|
|
6
7
|
@versions [
|
7
|
- {0x71000001, "7.1"},
|
8
|
- {0x72090002, "7.2"},
|
9
|
- {0x730A0003, "7.3.A"},
|
10
|
- {0x730B0003, "7.3.B"},
|
11
|
- {0x74000004, "7.4"}
|
8
|
+ {0x71000001, :v7_1},
|
9
|
+ {0x72090002, :v7_2},
|
10
|
+ {0x730A0003, :v7_3_a},
|
11
|
+ {0x730B0003, :v7_3_b},
|
12
|
+ {0x74000004, :v7_4}
|
12
13
|
]
|
13
14
|
|
14
15
|
def decode(<<key::little-dword>>) do
|
15
|
- @versions
|
16
|
- |> List.keyfind(key, 0, "7.4")
|
16
|
+ List.keyfind(@versions, key, 0, @default_version)
|
17
17
|
end
|
18
18
|
|
19
19
|
def encode(ver) do
|
20
|
- val =
|
21
|
- @versions
|
22
|
- |> List.keyfind(ver, 1, 0x74000004)
|
23
|
-
|
20
|
+ val = List.keyfind(@versions, ver, 1, @default_code)
|
24
21
|
<<val::little-dword>>
|
25
22
|
end
|
26
23
|
end
|
changed
mix.exs
|
@@ -2,7 +2,7 @@ defmodule Tds.Mixfile do
|
2
2
|
@moduledoc false
|
3
3
|
use Mix.Project
|
4
4
|
|
5
|
- @version "2.2.0"
|
5
|
+ @version "2.3.0-rc.0"
|
6
6
|
def project do
|
7
7
|
[
|
8
8
|
app: :tds,
|
|
@@ -21,12 +21,7 @@ defmodule Tds.Mixfile do
|
21
21
|
# Docs
|
22
22
|
name: "Tds",
|
23
23
|
source_url: "https://github.com/livehelpnow/tds",
|
24
|
- docs: [
|
25
|
- main: "readme",
|
26
|
- extras: ["README.md", "CHANGELOG.md"],
|
27
|
- source_ref: "v#{@version}",
|
28
|
- source_url: "https://github.com/livehelpnow/tds"
|
29
|
- ]
|
24
|
+ docs: docs()
|
30
25
|
]
|
31
26
|
end
|
32
27
|
|
|
@@ -45,7 +40,7 @@ defmodule Tds.Mixfile do
|
45
40
|
{:decimal, "~> 1.9 or ~> 2.0"},
|
46
41
|
{:jason, "~> 1.0", optional: true},
|
47
42
|
{:db_connection, "~> 2.0"},
|
48
|
- {:ex_doc, "~> 0.19", only: :dev},
|
43
|
+ {:ex_doc, "~> 0.19", only: :docs},
|
49
44
|
{:tds_encoding, "~> 1.1", optional: true, only: :test},
|
50
45
|
{:tzdata, "~> 1.0", optional: true, only: :test}
|
51
46
|
]
|
|
@@ -57,6 +52,19 @@ defmodule Tds.Mixfile do
|
57
52
|
"""
|
58
53
|
end
|
59
54
|
|
55
|
+ defp docs do
|
56
|
+ [
|
57
|
+ extras: [
|
58
|
+ "CHANGELOG.md": [title: "Changelog"],
|
59
|
+ "README.md": [title: "Overview"]
|
60
|
+ ],
|
61
|
+ main: "readme",
|
62
|
+ source_ref: "v#{@version}",
|
63
|
+ source_url: "https://github.com/livehelpnow/tds",
|
64
|
+ formatters: ["html"]
|
65
|
+ ]
|
66
|
+ end
|
67
|
+
|
60
68
|
defp package do
|
61
69
|
[
|
62
70
|
name: "tds",
|