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)
|