changed README.md
 
@@ -67,7 67,7 @@ Example configuration
67
67
```elixir
68
68
import Mix.Config
69
69
70
- config :your_app, tds_conn,
70
config :your_app, :tds_conn,
71
71
hostname: "localhost",
72
72
username: "test_user",
73
73
password: "test_password",
 
@@ -86,7 86,7 @@ you can try switching how tds executes queries as below:
86
86
```elixir
87
87
import Mix.Config
88
88
89
- config :your_app, tds_conn,
89
config :your_app, :tds_conn,
90
90
hostname: "localhost",
91
91
username: "test_user",
92
92
password: "test_password",
changed hex_metadata.config
 
@@ -34,4 34,4 @@
34
34
{<<"optional">>,false},
35
35
{<<"repository">>,<<"hexpm">>},
36
36
{<<"requirement">>,<<"~> 2.0">>}]]}.
37
- {<<"version">>,<<"2.1.0">>}.
37
{<<"version">>,<<"2.1.1">>}.
changed lib/tds.ex
 
@@ -1,90 1,179 @@
1
1
defmodule Tds do
2
@moduledoc """
3
Microsoft SQL Server driver for Elixir.
4
5
Tds is partial implementation of the Micorosoft SQL Server
6
[MS-TDS](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds)
7
Tabular Data Stream Protocol.
8
9
A Tds query is performed in separate server-side prepare and execute stages.
10
At the moment query handle is not reused, but there is plan to cahce handles in
11
near feature. It uses [RPC](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/619c43b6-9495-4a58-9e49-a4950db245b3)
12
requests by default to `Sp_Prepare` (ProcId=11) and `Sp_Execute` (ProcId=12)
13
query, but it is possible to configure driver to use only Sp_ExecuteSql.
14
Please consult with [configuration](readme.html#configuration) how to do this.
15
"""
2
16
alias Tds.Query
3
17
4
18
@timeout 5000
5
19
@execution_mode :prepare_execute
6
20
21
@type start_option ::
22
{:hostname, String.t()}
23
| {:port, :inet.port_number()}
24
| {:database, String.t()}
25
| {:username, String.t()}
26
| {:password, String.t()}
27
| {:timeout, timeout}
28
| {:connect_timeout, timeout}
29
| DBConnection.start_option()
30
31
@type isolation_level ::
32
:read_uncommitted
33
| :read_committed
34
| :repeatable_read
35
| :serializable
36
| :snapshot
37
| :no_change
38
39
@type conn :: DBConnection.conn()
40
41
@type resultset :: list(Tds.Result.t())
42
43
@type option :: DBConnection.option()
44
45
@type transaction_option ::
46
{:mode, :transaction | :savepoint}
47
| {:isolation_level, isolation_level()}
48
| option()
49
50
@type execute_option ::
51
{:decode_mapper, (list -> term)}
52
| {:resultset, boolean()}
53
| option
54
55
@spec start_link([start_option]) ::
56
{:ok, conn} | {:error, Tds.Error.t() | term}
7
57
def start_link(opts \\ []) do
8
58
DBConnection.start_link(Tds.Protocol, default(opts))
9
59
end
10
60
11
- def query(pid, statement, params, opts \\ []) do
61
@spec query(conn, iodata, list, [execute_option]) ::
62
{:ok, Tds.Result.t()} | {:error, Exception.t()}
63
def query(conn, statement, params, opts \\ []) do
12
64
query = %Query{statement: statement}
13
65
opts = Keyword.put_new(opts, :parameters, params)
14
66
15
- case DBConnection.prepare_execute(pid, query, params, opts) do
67
case DBConnection.prepare_execute(conn, query, params, opts) do
16
68
{:ok, _query, result} -> {:ok, result}
17
69
{:error, err} -> {:error, err}
18
70
end
19
71
end
20
72
21
- def query!(pid, statement, params, opts \\ []) do
73
@spec query!(conn, iodata, list, [execute_option]) ::
74
Tds.Result.t() | no_return()
75
def query!(conn, statement, params, opts \\ []) do
22
76
query = %Query{statement: statement}
23
77
opts = Keyword.put_new(opts, :parameters, params)
24
78
25
- case DBConnection.prepare_execute(pid, query, params, opts) do
79
case DBConnection.prepare_execute(conn, query, params, opts) do
26
80
{:ok, _query, result} -> result
27
81
{:error, %{mssql: %{msg_text: msg}}} -> raise Tds.Error, msg
28
82
{:error, err} -> raise err
29
83
end
30
84
end
31
85
32
- def prepare(pid, statement, opts \\ []) do
86
@doc """
87
Executes statement that can contain multiple sql batches, result will contain
88
all results that server yield for each batch.
89
"""
90
@spec query_multi(conn(), iodata(), option(), [execute_option]) ::
91
{:ok, resultset()}
92
| {:error, Exception.t()}
93
def query_multi(conn, statemnt, params, opts \\ []) do
94
query = %Query{statement: statemnt}
95
96
opts =
97
opts
98
|> Keyword.put_new(:parameters, params)
99
|> Keyword.put_new(:resultset, true)
100
101
case DBConnection.prepare_execute(conn, query, params, opts) do
102
{:ok, _query, resultset} -> {:ok, resultset}
103
{:error, err} -> {:error, err}
104
end
105
end
106
107
@spec prepare(conn, iodata, [option]) ::
108
{:ok, Tds.Query.t()} | {:error, Exception.t()}
109
def prepare(conn, statement, opts \\ []) do
33
110
query = %Query{statement: statement}
34
111
35
- case DBConnection.prepare(pid, query, opts) do
112
case DBConnection.prepare(conn, query, opts) do
36
113
{:ok, query} -> {:ok, query}
37
114
{:error, err} -> {:error, err}
38
115
end
39
116
end
40
117
41
- def prepare!(pid, statement, opts \\ []) do
118
@spec prepare!(conn, iodata, [option]) :: Tds.Query.t() | no_return()
119
def prepare!(conn, statement, opts \\ []) do
42
120
query = %Query{statement: statement}
43
121
44
- case DBConnection.prepare(pid, query, opts) do
122
case DBConnection.prepare(conn, query, opts) do
45
123
{:ok, query} -> query
46
124
{:error, %{mssql: %{msg_text: msg}}} -> raise Tds.Error, msg
47
125
{:error, err} -> raise err
48
126
end
49
127
end
50
128
51
- def execute(pid, query, params, opts \\ []) do
52
- case DBConnection.execute(pid, query, params, opts) do
129
@spec execute(conn, Tds.Query.t(), list, [execute_option]) ::
130
{:ok, Tds.Query.t(), Tds.Result.t()}
131
| {:error, Tds.Error.t()}
132
def execute(conn, query, params, opts \\ []) do
133
case DBConnection.execute(conn, query, params, opts) do
53
134
{:ok, q, result} -> {:ok, q, result}
54
135
{:error, err} -> {:error, err}
55
136
end
56
137
end
57
138
58
- def execute!(pid, query, params, opts \\ []) do
59
- case DBConnection.execute(pid, query, params, opts) do
139
@spec execute!(conn, Tds.Query.t(), list, [execute_option]) ::
140
Tds.Result.t()
141
def execute!(conn, query, params, opts \\ []) do
142
case DBConnection.execute(conn, query, params, opts) do
60
143
{:ok, _q, result} -> result
61
144
{:error, %{mssql: %{msg_text: msg}}} -> raise Tds.Error, msg
62
145
{:error, err} -> raise err
63
146
end
64
147
end
65
148
66
- def close(pid, query, opts \\ []) do
67
- case DBConnection.close(pid, query, opts) do
149
@spec close(conn, Tds.Query.t(), [option]) :: :ok | {:error, Exception.t()}
150
def close(conn, query, opts \\ []) do
151
case DBConnection.close(conn, query, opts) do
68
152
{:ok, result} -> {:ok, result}
69
153
{:error, err} -> {:error, err}
70
154
end
71
155
end
72
156
73
- def close!(pid, query, opts \\ []) do
74
- case DBConnection.close(pid, query, opts) do
157
@spec close!(conn, Tds.Query.t(), [option]) :: :ok
158
def close!(conn, query, opts \\ []) do
159
case DBConnection.close(conn, query, opts) do
75
160
{:ok, result} -> result
76
161
{:error, %{mssql: %{msg_text: msg}}} -> raise Tds.Error, msg
77
162
{:error, err} -> raise err
78
163
end
79
164
end
80
165
81
- def transaction(pid, fun, opts \\ []) do
82
- DBConnection.transaction(pid, fun, opts)
166
@spec transaction(conn, (DBConnection.t() -> result), [transaction_option()]) ::
167
{:ok, result} | {:error, any}
168
when result: var
169
def transaction(conn, fun, opts \\ []) do
170
DBConnection.transaction(conn, fun, opts)
83
171
end
84
172
85
173
@spec rollback(DBConnection.t(), reason :: any) :: no_return
86
174
defdelegate rollback(conn, any), to: DBConnection
87
175
176
@spec child_spec([start_option]) :: Supervisor.Spec.spec()
88
177
def child_spec(opts) do
89
178
DBConnection.child_spec(Tds.Protocol, default(opts))
90
179
end
 
@@ -100,7 189,7 @@ defmodule Tds do
100
189
101
190
To customize the JSON library, include the following in your `config/config.exs`:
102
191
103
- config :myxql, json_library: SomeJSONModule
192
config :tds, json_library: SomeJSONModule
104
193
105
194
Defaults to `Jason`.
106
195
"""
 
@@ -125,7 214,9 @@ defmodule Tds do
125
214
"""
126
215
def decode_uuid!(uuid) do
127
216
case Tds.Types.UUID.load(uuid) do
128
- {:ok, value} -> value
217
{:ok, value} ->
218
value
219
129
220
:error ->
130
221
raise ArgumentError, "Invalid uuid binary #{inspect(uuid)}"
131
222
end
changed lib/tds/protocol.ex
 
@@ -43,7 43,7 @@ defmodule Tds.Protocol do
43
43
sock: nil | sock,
44
44
usock: nil | pid,
45
45
itcp: term,
46
- opts: Keyword.t(),
46
opts: nil | Keyword.t(),
47
47
state: state,
48
48
result: nil | list(),
49
49
query: nil | String.t(),
 
@@ -186,6 186,7 @@ defmodule Tds.Protocol do
186
186
%{sock: _sock} = s
187
187
) do
188
188
params = opts[:parameters] || params
189
Process.put(:resultset, Keyword.get(opts, :resultset, false))
189
190
190
191
if params != [] do
191
192
send_param_query(query, params, s)
 
@@ -202,6 203,8 @@ defmodule Tds.Protocol do
202
203
other ->
203
204
other
204
205
after
206
Process.delete(:resultset)
207
205
208
unless is_nil(handle) do
206
209
handle_close(query, opts, %{s | state: :executing})
207
210
end
 
@@ -565,7 568,7 @@ defmodule Tds.Protocol do
565
568
end
566
569
end
567
570
568
- def send_query(statement, s) do
571
defp send_query(statement, s) do
569
572
msg = msg_sql(query: statement)
570
573
571
574
case msg_send(msg, %{s | state: :executing}) do
 
@@ -632,11 635,11 @@ defmodule Tds.Protocol do
632
635
| {:disconnect, any(), %{env: any(), sock: {any(), any()}}}
633
636
| {:error, Tds.Error.t(), %{pak_header: <<>>, tail: <<>>}}
634
637
| {:ok, any(), %{result: any(), state: :ready}}
635
- def send_param_query(
636
- %Query{handle: handle, statement: statement} = _,
637
- params,
638
- %{transaction: :started} = s
639
- ) do
638
defp send_param_query(
639
%Query{handle: handle, statement: statement} = _,
640
params,
641
%{transaction: :started} = s
642
) do
640
643
msg =
641
644
case handle do
642
645
nil ->
 
@@ -684,11 687,11 @@ defmodule Tds.Protocol do
684
687
end
685
688
end
686
689
687
- def send_param_query(
688
- %Query{handle: handle, statement: statement} = _,
689
- params,
690
- s
691
- ) do
690
defp send_param_query(
691
%Query{handle: handle, statement: statement} = _,
692
params,
693
s
694
) do
692
695
msg =
693
696
case handle do
694
697
nil ->
 
@@ -785,15 788,14 @@ defmodule Tds.Protocol do
785
788
|> send_query(state)
786
789
end
787
790
788
- def message(
789
- :executing,
790
- msg_result(set: set, status: _status, params: _params),
791
- %{} = s
792
- ) do
791
def message(:executing, msg_result(set: set), s) do
792
resultset? = Process.get(:resultset, false)
793
793
794
result =
794
- case set do
795
- [] -> %Tds.Result{rows: nil}
796
- [h | _t] -> h
795
case {set, resultset?} do
796
{r, true} -> r
797
{r, false} -> List.first(r) || %Tds.Result{rows: nil}
798
{[h | _t], _false} -> h
797
799
end
798
800
799
801
{:ok, mark_ready(%{s | result: result})}
 
@@ -853,11 855,16 @@ defmodule Tds.Protocol do
853
855
end
854
856
end
855
857
856
- defp msg_send(msg, %{sock: {mod, sock}, env: env, state: state, opts: opts} = s) do
858
defp msg_send(
859
msg,
860
%{sock: {mod, sock}, env: env, state: state, opts: opts} = s
861
) do
857
862
:inet.setopts(sock, active: false)
863
858
864
opts
859
865
|> Keyword.get(:use_elixir_calendar_types, false)
860
866
|> use_elixir_calendar_types()
867
861
868
{t_send, _} =
862
869
:timer.tc(fn ->
863
870
msg
changed lib/tds/query.ex
 
@@ -35,7 35,14 @@ defimpl DBConnection.Query, for: Tds.Query do
35
35
] params
36
36
end
37
37
38
def decode(_query, [], _opts), do: []
39
40
def decode(query, [h | t], opts) do
41
[decode(query, h, opts) | decode(query, t, opts)]
42
end
43
38
44
def decode(_query, %Result{rows: nil} = result, _opts), do: result
45
39
46
def decode(_query, %Result{rows: rows} = result, opts) do
40
47
mapper = opts[:decode_mapper] || fn x -> x end
41
48
rows = do_decode(rows, mapper, [])
changed lib/tds/types.ex
 
@@ -1108,9 1108,6 @@ defmodule Tds.Types do
1108
1108
"#{name} #{desc}"
1109
1109
end
1110
1110
1111
- @doc """
1112
- Implictly Selected Types
1113
- """
1114
1111
# nil
1115
1112
def encode_param_descriptor(param),
1116
1113
do: param |> Parameter.fix_data_type() |> encode_param_descriptor()
 
@@ -1195,8 1192,10 @@ defmodule Tds.Types do
1195
1192
# def encode_binary_descriptor(value), do: "varbinary(#{byte_size(value)})"
1196
1193
1197
1194
@doc """
1198
- Data Encoding Binary Types
1195
Data encoding
1199
1196
"""
1197
1198
# binary
1200
1199
def encode_data(@tds_data_type_bigvarbinary, value, attr)
1201
1200
when is_integer(value),
1202
1201
do: encode_data(@tds_data_type_bigvarbinary, <<value>>, attr)
 
@@ -1212,9 1211,7 @@ defmodule Tds.Types do
1212
1211
end
1213
1212
end
1214
1213
1215
- @doc """
1216
- Data Encoding String Types
1217
- """
1214
# string
1218
1215
def encode_data(@tds_data_type_nvarchar, nil, _),
1219
1216
do: <<@tds_plp_null::little-unsigned-64>>
1220
1217
 
@@ -1234,9 1231,7 @@ defmodule Tds.Types do
1234
1231
end
1235
1232
end
1236
1233
1237
- @doc """
1238
- Data Encoding Positive Integers Types
1239
- """
1234
# integers
1240
1235
def encode_data(_, value, _) when is_integer(value) do
1241
1236
size = int_type_size(value)
1242
1237
<<size>> <> <<value::little-signed-size(size)-unit(8)>>
 
@@ -1250,9 1245,7 @@ defmodule Tds.Types do
1250
1245
<<0>>
1251
1246
end
1252
1247
1253
- @doc """
1254
- Data Encoding Float Types
1255
- """
1248
# float
1256
1249
def encode_data(@tds_data_type_floatn, nil, _) do
1257
1250
<<0>>
1258
1251
end
 
@@ -1287,9 1280,7 @@ defmodule Tds.Types do
1287
1280
# end
1288
1281
end
1289
1282
1290
- @doc """
1291
- Data Encoding Decimal Types
1292
- """
1283
# decimal
1293
1284
def encode_data(@tds_data_type_decimaln, �cimal{} = value, attr) do
1294
1285
d_ctx = Decimal.get_context()
1295
1286
d_ctx = %{d_ctx | precision: 38}
 
@@ -1339,9 1330,7 @@ defmodule Tds.Types do
1339
1330
encode_data(data_type, Decimal.new(value), attr)
1340
1331
end
1341
1332
1342
- @doc """
1343
- Data Encoding UUID Types
1344
- """
1333
# uuid
1345
1334
def encode_data(@tds_data_type_uniqueidentifier, value, _) do
1346
1335
if value != nil do
1347
1336
<<0x10>> <> encode_uuid(value)
 
@@ -1350,9 1339,7 @@ defmodule Tds.Types do
1350
1339
end
1351
1340
end
1352
1341
1353
- @doc """
1354
- Data Encoding DateTime Types
1355
- """
1342
# datetime
1356
1343
def encode_data(@tds_data_type_daten, value, _attr) do
1357
1344
data = encode_date(value)
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.1.0"
5
@version "2.1.1"
6
6
def project do
7
7
[
8
8
app: :tds,
 
@@ -18,6 18,7 @@ defmodule Tds.Mixfile do
18
18
],
19
19
description: description(),
20
20
package: package(),
21
xref: [exclude: [:ssl]],
21
22
rustler_crates: [
22
23
tds_encoding: [
23
24
mode: (if Mix.env() == :prod, do: :release, else: :debug)