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