changed
CHANGELOG.md
|
@@ -1,3 +1,9 @@
|
1
|
+ # v0.2.3
|
2
|
+ * Bug Fixes
|
3
|
+ * Added Long Length decoder support for text, ntext and image
|
4
|
+ * Fixed PLP decode / Encode for sending and receiving large test
|
5
|
+ * Fixed issue where selecting from NTEXT, TEXT, NVARCHAR(MAX), VARCHAR(MAX) would trunc to 4kb
|
6
|
+
|
1
7
|
# v0.2.2
|
2
8
|
* Bug Fixes
|
3
9
|
* Fixed udp port scope for instances
|
changed
README.md
|
@@ -2,8 +2,6 @@
|
2
2
|
|
3
3
|
MSSQL / TDS Database driver for Elixir.
|
4
4
|
|
5
|
- Work in Progress...
|
6
|
-
|
7
5
|
## Usage
|
8
6
|
|
9
7
|
Add Tds as a dependency in your `mix.exs` file.
|
|
@@ -31,8 +29,7 @@ iex> Tds.Connection.query!(pid, "INSERT INTO MyTable (MyColumn) VALUES (@my_valu
|
31
29
|
* Supports TDS Version 7.3, 7.4
|
32
30
|
|
33
31
|
## Connecting to SQL Instances
|
34
|
-
|
35
|
- In order to connect to a SQL instance, you must assign a static port to the TCP adaptor of the instance in the SQL Configuration Manager. You can then use this port as part of the connection options.
|
32
|
+ Tds Supports sql instances by passing ```instance: "instancename"``` to the connection options.
|
36
33
|
|
37
34
|
|
38
35
|
|
|
@@ -67,9 +64,9 @@ $ mix test
|
67
64
|
|
68
65
|
The tests require an addition to your hosts file to connect to your sql server database.
|
69
66
|
|
70
|
- <IP OF SQL SERVER> sqlserverl.local
|
67
|
+ <IP OF SQL SERVER> sqlserver.local
|
71
68
|
|
72
|
- Additionally SQL authentication needs to be used for connecting and testing. Add the user test_user with access to the database test_db. See one of the test files for the connection information and port number.
|
69
|
+ Additionally SQL authentication needs to be used for connecting and testing. Add the user test_user as owner to the database test_db. See one of the test files for the connection information and port number.
|
73
70
|
|
74
71
|
##Special Thanks
|
75
72
|
|
|
@@ -94,4 +91,4 @@ Also thanks to everyone in the Elixir Gogle group and on the Elixir IRC Channel
|
94
91
|
distributed under the License is distributed on an "AS IS" BASIS,
|
95
92
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
96
93
|
See the License for the specific language governing permissions and
|
97
|
- limitations under the License.
|
|
\ No newline at end of file
|
94
|
+ limitations under the License.
|
changed
hex_metadata.config
|
@@ -21,4 +21,4 @@
|
21
21
|
[{<<"app">>,<<"timex">>},
|
22
22
|
{<<"optional">>,nil},
|
23
23
|
{<<"requirement">>,<<"~> 0.12.9">>}]}]}.
|
24
|
- {<<"version">>,<<"0.2.2">>}.
|
24
|
+ {<<"version">>,<<"0.2.3">>}.
|
changed
lib/tds/connection.ex
|
@@ -67,7 +67,6 @@ defmodule Tds.Connection do
|
67
67
|
end
|
68
68
|
|
69
69
|
def attn(pid, opts \\ []) do
|
70
|
- #Logger.debug "ATTN PID: #{inspect pid}"
|
71
70
|
timeout = opts[:timeout] || @timeout
|
72
71
|
case GenServer.call(pid, :attn, timeout) do
|
73
72
|
%Tds.Result{} = res -> {:ok, res}
|
|
@@ -82,6 +81,7 @@ defmodule Tds.Connection do
|
82
81
|
{:ok, %{
|
83
82
|
sock: nil,
|
84
83
|
usock: nil,
|
84
|
+ itcp: nil,
|
85
85
|
ireq: nil,
|
86
86
|
opts: nil,
|
87
87
|
state: :ready,
|
|
@@ -114,10 +114,9 @@ defmodule Tds.Connection do
|
114
114
|
end
|
115
115
|
|
116
116
|
def handle_call({:connect, opts}, from, %{queue: queue} = s) do
|
117
|
- opts = s[:opts] || opts
|
118
117
|
host = Keyword.fetch!(opts, :hostname)
|
119
118
|
host = if is_binary(host), do: String.to_char_list(host), else: host
|
120
|
- port = opts[:port] || System.get_env("MSSQL_PORT") || 1433
|
119
|
+ port = s[:itcp] || opts[:port] || System.get_env("MSSQLPORT") || 1433
|
121
120
|
if is_binary(port), do: {port, _} = Integer.parse(port)
|
122
121
|
timeout = opts[:timeout] || @timeout
|
123
122
|
sock_opts = [{:active, :once}, :binary, {:packet, :raw}, {:delay_send, false}]
|
|
@@ -198,10 +197,10 @@ defmodule Tds.Connection do
|
198
197
|
serv ->
|
199
198
|
timeout = opts[:timeout] || @timeout
|
200
199
|
{port, _} = Integer.parse(serv[:tcp])
|
201
|
- opts = Keyword.put(opts, :port, port)
|
200
|
+ #opts = Keyword.put(opts, :port, port)
|
202
201
|
GenServer.reply(pid, :ok)
|
203
202
|
|
204
|
- {:noreply, %{s | opts: opts}}
|
203
|
+ {:noreply, %{s | opts: opts, itcp: port}}
|
205
204
|
#GenServer.call(self, {:connect, %{port: s[:tcp], instance: nil}}, timeout)
|
206
205
|
end
|
207
206
|
|
|
@@ -259,7 +258,6 @@ defmodule Tds.Connection do
|
259
258
|
end
|
260
259
|
|
261
260
|
defp command(:attn, s) do
|
262
|
- #Logger.error "Command Attn"
|
263
261
|
timeout = s.opts[:timeout] || @timeout
|
264
262
|
attn_timer_ref = :erlang.start_timer(timeout, self(), :command)
|
265
263
|
Protocol.send_attn(%{s |attn_timer: attn_timer_ref, pak_header: "", pak_data: "", tail: "", state: :attn})
|
changed
lib/tds/messages.ex
|
@@ -82,7 +82,7 @@ defmodule Tds.Messages do
|
82
82
|
|
83
83
|
## Parsers
|
84
84
|
|
85
|
- def parse(:login, @tds_pack_reply, _header, tail) do
|
85
|
+ def parse(:login, @tds_pack_reply, header, tail) do
|
86
86
|
msg_login_ack(type: 4, data: tail)
|
87
87
|
end
|
88
88
|
|
|
@@ -284,7 +284,6 @@ defmodule Tds.Messages do
|
284
284
|
defp encode_rpc_param(%Tds.Parameter{name: name, value: value, direction: _direction, type: type} = param) do
|
285
285
|
p_name = to_little_ucs2(name)
|
286
286
|
p_flags = param |> Tds.Parameter.option_flags
|
287
|
- #Logger.debug "Param: #{inspect param}"
|
288
287
|
{type_code, type_data, type_attr} = Types.encode_data_type(param)
|
289
288
|
p_meta_data = <<byte_size(p_name)>> <> to_little_ucs2(p_name) <> p_flags <> type_data
|
290
289
|
p_meta_data <> Types.encode_data(type_code, param.value, type_attr)
|
changed
lib/tds/protocol.ex
|
@@ -27,7 +27,6 @@ defmodule Tds.Protocol do
|
27
27
|
end
|
28
28
|
|
29
29
|
def send_query(statement, s) do
|
30
|
- #Logger.debug "SQL: #{inspect statement}"
|
31
30
|
msg = msg_sql(query: statement)
|
32
31
|
|
33
32
|
case send_to_result(msg, s) do
|
|
@@ -80,7 +79,8 @@ defmodule Tds.Protocol do
|
80
79
|
|
81
80
|
end
|
82
81
|
|
83
|
- def message(:login, msg_login_ack(), %{opts: opts, tail: _tail, opts: opts} = s) do
|
82
|
+ def message(:login, msg_login_ack(), %{opts: opts, tail: tail, opts: opts} = s) do
|
83
|
+
|
84
84
|
s = %{s | opts: clean_opts(opts)}
|
85
85
|
reply(:ok, s)
|
86
86
|
send_query("""
|
|
@@ -92,6 +92,7 @@ defmodule Tds.Protocol do
|
92
92
|
SET ANSI_PADDING ON;
|
93
93
|
SET ANSI_WARNINGS ON;
|
94
94
|
SET CONCAT_NULL_YIELDS_NULL ON;
|
95
|
+ SET TEXTSIZE 2147483647;
|
95
96
|
""", s)
|
96
97
|
end
|
changed
lib/tds/tokens.ex
|
@@ -133,15 +133,12 @@ defmodule Tds.Tokens do
|
133
133
|
tail::binary>> = tail
|
134
134
|
@tds_envtype_begintrans ->
|
135
135
|
<<value_size::unsigned-8, new_value::binary-little-size(value_size)-unit(8), 0x00, tail::binary>> = tail
|
136
|
- #Logger.info "Begin Transaction: #{Tds.Utils.to_hex_string new_value}"
|
137
136
|
[trans: new_value]
|
138
137
|
@tds_envtype_committrans ->
|
139
138
|
<<0x00, value_size::unsigned-8, _old_value::binary-little-size(value_size)-unit(8), tail::binary>> = tail
|
140
|
- #Logger.info "Commit Transaction"
|
141
139
|
[trans: <<0x00>>]
|
142
140
|
@tds_envtype_rollbacktrans ->
|
143
141
|
<<0x00, value_size::unsigned-8, _old_value::binary-little-size(value_size)-unit(8), tail::binary>> = tail
|
144
|
- #Logger.info "Rollback Transaction"
|
145
142
|
[trans: <<0x00>>]
|
146
143
|
end
|
147
144
|
{token ++ tokens, tail}
|
|
@@ -213,7 +210,7 @@ defmodule Tds.Tokens do
|
213
210
|
decode_columns(tail, [column | columns], n - 1)
|
214
211
|
end
|
215
212
|
|
216
|
- defp decode_column(<<_usertype::int32, _flags::int16, tail::binary>>) do
|
213
|
+ defp decode_column(<<_usertype::int32, _flags::int16, tail::binary>> = data) do
|
217
214
|
{info, tail} = Types.decode_info(tail)
|
218
215
|
{name, tail} = decode_column_name(tail)
|
219
216
|
info = info
|
changed
lib/tds/types.ex
|
@@ -252,16 +252,17 @@ defmodule Tds.Types do
|
252
252
|
|> Map.put(:length, length)
|
253
253
|
cond do
|
254
254
|
data_type_code in [@tds_data_type_text, @tds_data_type_ntext] ->
|
255
|
+
|
255
256
|
<<collation::binary-5, tail::binary>> = tail
|
256
257
|
col_info = col_info
|
257
258
|
|> Map.put(:collation, collation)
|
258
259
|
|> Map.put(:data_reader, :longlen)
|
259
|
- # TODO NumParts Reader
|
260
260
|
<<numparts::signed-8, tail::binary>> = tail
|
261
|
- for _n <- 1..numparts do
|
262
|
- <<tsize::little-unsigned-16, _table_name::binary-size(tsize)-unit(16), tail::binary>> = tail
|
263
|
- <<csize::unsigned-8, _column_name::binary-size(csize)-unit(16), tail::binary>> = tail
|
264
|
- end
|
261
|
+ tail =
|
262
|
+ Enum.reduce([1..numparts], tail, fn(_, acc) ->
|
263
|
+ <<tsize::little-unsigned-16, _table_name::binary-size(tsize)-unit(16), tail::binary>> = acc
|
264
|
+ tail
|
265
|
+ end)
|
265
266
|
data_type_code == @tds_data_type_image ->
|
266
267
|
# TODO NumBarts Reader
|
267
268
|
<<numparts::signed-8, tail::binary>> = tail
|
|
@@ -392,16 +393,23 @@ defmodule Tds.Types do
|
392
393
|
{value, tail}
|
393
394
|
end
|
394
395
|
|
395
|
- # TODO LongLen Types
|
396
|
+ def decode_data(%{data_reader: :longlen}, <<0x00, tail::binary>>), do: {nil, tail}
|
397
|
+ def decode_data(%{data_type_code: data_type_code, data_reader: :longlen} = data_info, <<text_ptr_size::unsigned-8, text_ptr::size(text_ptr_size)-unit(8), timestamp::unsigned-64, size::little-signed-32, data::binary-size(size)-unit(8), tail::binary>>) do
|
398
|
+ value =
|
399
|
+ case data_type_code do
|
400
|
+ @tds_data_type_text -> decode_char(data_info[:collation], data)
|
401
|
+ @tds_data_type_ntext -> decode_nchar(data)
|
402
|
+ @tds_data_type_image -> data
|
403
|
+ _ -> nil
|
404
|
+ end
|
405
|
+ {value, tail}
|
406
|
+ end
|
396
407
|
|
397
408
|
# TODO Variant Types
|
398
409
|
|
399
|
- # TODO PLP TYpes
|
400
|
- # ShortLength Types
|
401
410
|
def decode_data(%{data_reader: :plp}, <<@tds_plp_null, tail::binary>>), do: {nil, tail}
|
402
|
- def decode_data(%{data_type_code: data_type_code, data_reader: :plp} = data_info, <<_size::little-unsigned-64, tail::binary>>) do
|
411
|
+ def decode_data(%{data_type_code: data_type_code, data_reader: :plp} = data_info, <<size::little-unsigned-64, tail::binary>>) do
|
403
412
|
{data, tail} = decode_plp_chunk(tail, <<>>)
|
404
|
-
|
405
413
|
value = cond do
|
406
414
|
data_type_code == @tds_data_type_xml ->
|
407
415
|
decode_xml(data_info, data)
|
|
@@ -621,11 +629,10 @@ defmodule Tds.Types do
|
621
629
|
if value != nil do
|
622
630
|
value = value |> to_little_ucs2
|
623
631
|
value_size = byte_size(value)
|
624
|
-
|
625
|
- case value_size do
|
626
|
- 0 ->
|
632
|
+ cond do
|
633
|
+ value_size == 0 or value_size > 8000 ->
|
627
634
|
<<0xFF, 0xFF>>
|
628
|
- _ ->
|
635
|
+ true ->
|
629
636
|
<<value_size::little-2*8>>
|
630
637
|
end
|
631
638
|
else
|
|
@@ -842,6 +849,7 @@ defmodule Tds.Types do
|
842
849
|
length = String.length(value)
|
843
850
|
end
|
844
851
|
if length <= 0, do: length = 1
|
852
|
+ if length > 4000, do: length = "max"
|
845
853
|
"nvarchar(#{length})"
|
846
854
|
:integer ->
|
847
855
|
if value >= 0 do
|
|
@@ -862,6 +870,7 @@ defmodule Tds.Types do
|
862
870
|
length = String.length(value)
|
863
871
|
end
|
864
872
|
if length <= 0, do: length = 1
|
873
|
+ if length > 4000, do: length = "max"
|
865
874
|
"nvarchar(#{length})"
|
866
875
|
end
|
867
876
|
|
|
@@ -1037,10 +1046,12 @@ defmodule Tds.Types do
|
1037
1046
|
value = value |> to_little_ucs2
|
1038
1047
|
value_size = byte_size(value)
|
1039
1048
|
|
1040
|
- case value_size do
|
1041
|
- 0 ->
|
1049
|
+ cond do
|
1050
|
+ value_size == 0 ->
|
1042
1051
|
<<0x00::unsigned-64, 0x00::unsigned-32>>
|
1043
|
- _ ->
|
1052
|
+ value_size > 8000 ->
|
1053
|
+ encode_plp(value)
|
1054
|
+ true ->
|
1044
1055
|
<<value_size::little-size(2)-unit(8)>> <> value
|
1045
1056
|
end
|
1046
1057
|
end
|
|
@@ -1165,6 +1176,19 @@ defmodule Tds.Types do
|
1165
1176
|
|
1166
1177
|
end
|
1167
1178
|
|
1179
|
+ def encode_plp(data) do
|
1180
|
+ size = byte_size(data)
|
1181
|
+ <<size::little-unsigned-64>> <> encode_plp_chunk(size, data, <<>>) <> <<0x00::32>>
|
1182
|
+ end
|
1183
|
+
|
1184
|
+ def encode_plp_chunk(0, _, buf), do: buf
|
1185
|
+ def encode_plp_chunk(size, data, buf) do
|
1186
|
+ <<_t::unsigned-32, chunk_size::unsigned-32>> = <<size::unsigned-64>>
|
1187
|
+ <<chunk::binary-size(chunk_size), data::binary>> = data
|
1188
|
+ plp = <<chunk_size::little-unsigned-32>> <> chunk
|
1189
|
+ encode_plp_chunk(size-chunk_size, data, buf <> plp)
|
1190
|
+ end
|
1191
|
+
|
1168
1192
|
defp int_type_size(int) when int in 0..255, do: 1
|
1169
1193
|
defp int_type_size(int) when int in -32768..32767, do: 2
|
1170
1194
|
defp int_type_size(int) when int in -2147483648..2147483647, do: 4
|
changed
mix.exs
|
@@ -3,7 +3,7 @@ defmodule Tds.Mixfile do
|
3
3
|
|
4
4
|
def project do
|
5
5
|
[app: :tds,
|
6
|
- version: "0.2.2",
|
6
|
+ version: "0.2.3",
|
7
7
|
elixir: "~> 1.0.0",
|
8
8
|
deps: deps,
|
9
9
|
source_url: "https://github.com/livehelpnow/tds",
|