tiny_tds 1.0.4 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +20 -0
  3. data/.gitattributes +1 -0
  4. data/.rubocop.yml +31 -0
  5. data/.travis.yml +25 -0
  6. data/{CHANGELOG → CHANGELOG.md} +102 -26
  7. data/Gemfile +4 -1
  8. data/ISSUE_TEMPLATE.md +35 -2
  9. data/README.md +131 -56
  10. data/Rakefile +31 -88
  11. data/VERSION +1 -1
  12. data/appveyor.yml +38 -17
  13. data/docker-compose.yml +22 -0
  14. data/ext/tiny_tds/client.c +147 -60
  15. data/ext/tiny_tds/client.h +11 -5
  16. data/ext/tiny_tds/extconf.rb +41 -297
  17. data/ext/tiny_tds/extconsts.rb +7 -7
  18. data/ext/tiny_tds/result.c +40 -15
  19. data/lib/tiny_tds/bin.rb +45 -27
  20. data/lib/tiny_tds/client.rb +46 -34
  21. data/lib/tiny_tds/error.rb +0 -1
  22. data/lib/tiny_tds/gem.rb +32 -0
  23. data/lib/tiny_tds/result.rb +2 -3
  24. data/lib/tiny_tds/version.rb +1 -1
  25. data/lib/tiny_tds.rb +38 -14
  26. data/{ports/patches/freetds/1.00 → patches/freetds/1.00.27}/0001-mingw_missing_inet_pton.diff +4 -4
  27. data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +28 -0
  28. data/patches/libiconv/1.14/1-avoid-gets-error.patch +17 -0
  29. data/tasks/native_gem.rake +14 -0
  30. data/tasks/package.rake +8 -0
  31. data/tasks/ports/freetds.rb +37 -0
  32. data/tasks/ports/libiconv.rb +43 -0
  33. data/tasks/ports/openssl.rb +62 -0
  34. data/tasks/ports/recipe.rb +52 -0
  35. data/tasks/ports.rake +85 -0
  36. data/tasks/test.rake +9 -0
  37. data/test/appveyor/dbsetup.ps1 +1 -1
  38. data/test/bin/install-freetds.sh +20 -0
  39. data/test/bin/install-openssl.sh +18 -0
  40. data/test/bin/setup.sh +19 -0
  41. data/test/client_test.rb +124 -66
  42. data/test/gem_test.rb +179 -0
  43. data/test/result_test.rb +128 -42
  44. data/test/schema/sqlserver_2016.sql +140 -0
  45. data/test/schema_test.rb +23 -23
  46. data/test/test_helper.rb +65 -7
  47. data/test/thread_test.rb +1 -1
  48. data/tiny_tds.gemspec +9 -7
  49. metadata +60 -20
  50. /data/bin/{defncopy → defncopy-ttds} +0 -0
  51. /data/bin/{tsql → tsql-ttds} +0 -0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -x
4
+ set -e
5
+
6
+ if [ -z "$FREETDS_VERSION" ]; then
7
+ FREETDS_VERSION=$(ruby -r "./ext/tiny_tds/extconsts.rb" -e "puts FREETDS_VERSION")
8
+ fi
9
+
10
+ wget http://www.freetds.org/files/stable/freetds-$FREETDS_VERSION.tar.gz
11
+ tar -xzf freetds-$FREETDS_VERSION.tar.gz
12
+ cd freetds-$FREETDS_VERSION
13
+ ./configure --prefix=/opt/local \
14
+ --with-openssl=/opt/local \
15
+ --with-tdsver=7.3
16
+ make
17
+ make install
18
+ cd ..
19
+ rm -rf freetds-$FREETDS_VERSION
20
+ rm freetds-$FREETDS_VERSION.tar.gz
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -x
4
+ set -e
5
+
6
+ if [ -z "$OPENSSL_VERSION" ]; then
7
+ OPENSSL_VERSION=$(ruby -r "./ext/tiny_tds/extconsts.rb" -e "puts OPENSSL_VERSION")
8
+ fi
9
+
10
+ wget https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz
11
+ tar -xzf openssl-$OPENSSL_VERSION.tar.gz
12
+ cd openssl-$OPENSSL_VERSION
13
+ ./config --prefix=/opt/local --openssldir=/opt/local
14
+ make
15
+ make install_sw install_ssldirs
16
+ cd ..
17
+ rm -rf openssl-$OPENSSL_VERSION
18
+ rm openssl-$OPENSSL_VERSION.tar.gz
data/test/bin/setup.sh ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -x
4
+ set -e
5
+
6
+ tag=2017-GA
7
+
8
+ docker pull metaskills/mssql-server-linux-tinytds:$tag
9
+
10
+ container=$(docker ps -a -q --filter ancestor=metaskills/mssql-server-linux-tinytds:$tag)
11
+ if [[ -z $container ]]; then
12
+ docker run -p 1433:1433 -d metaskills/mssql-server-linux-tinytds:$tag && sleep 10
13
+ exit
14
+ fi
15
+
16
+ container=$(docker ps -q --filter ancestor=metaskills/mssql-server-linux-tinytds:$tag)
17
+ if [[ -z $container ]]; then
18
+ docker start $container && sleep 10
19
+ fi
data/test/client_test.rb CHANGED
@@ -2,9 +2,7 @@
2
2
  require 'test_helper'
3
3
 
4
4
  class ClientTest < TinyTds::TestCase
5
-
6
- describe 'With valid credentials' do
7
-
5
+ describe 'with valid credentials' do
8
6
  before do
9
7
  @client = new_connection
10
8
  end
@@ -46,25 +44,33 @@ class ClientTest < TinyTds::TestCase
46
44
  assert_equal "''hello''", @client.escape("'hello'")
47
45
  end
48
46
 
49
- it 'allows valid iconv character set' do
50
- ['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
51
- client = new_connection(:encoding => encoding)
52
- assert_equal encoding, client.charset
53
- assert_equal Encoding.find(encoding), client.encoding
54
- client.close
47
+ ['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
48
+ it "allows valid iconv character set - #{encoding}" do
49
+ begin
50
+ client = new_connection(:encoding => encoding)
51
+ assert_equal encoding, client.charset
52
+ assert_equal Encoding.find(encoding), client.encoding
53
+ ensure
54
+ client.close if client
55
+ end
55
56
  end
56
57
  end
57
58
 
58
59
  it 'must be able to use :host/:port connection' do
59
- host = ENV['TINYTDS_UNIT_HOST_TEST'] || ENV['TINYTDS_UNIT_HOST']
60
+ host = ENV['TINYTDS_UNIT_HOST_TEST'] || ENV['TINYTDS_UNIT_HOST'] || 'localhost'
60
61
  port = ENV['TINYTDS_UNIT_PORT_TEST'] || ENV['TINYTDS_UNIT_PORT'] || 1433
61
- client = new_connection dataserver: nil, host: host, port: port
62
- client.close
62
+ begin
63
+ client = new_connection dataserver: nil, host: host, port: port
64
+ ensure
65
+ client.close if client
66
+ end
63
67
  end unless sqlserver_azure?
64
-
65
68
  end
66
69
 
67
70
  describe 'With in-valid options' do
71
+ before(:all) do
72
+ init_toxiproxy
73
+ end
68
74
 
69
75
  it 'raises an argument error when no :host given and :dataserver is blank' do
70
76
  assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
@@ -78,9 +84,16 @@ class ClientTest < TinyTds::TestCase
78
84
  options = connection_options :login_timeout => 1, :dataserver => 'DOESNOTEXIST'
79
85
  action = lambda { new_connection(options) }
80
86
  assert_raise_tinytds_error(action) do |e|
81
- assert_equal 20012, e.db_error_number
82
- assert_equal 2, e.severity
83
- assert_match %r{server name not found in configuration files}i, e.message, 'ignore if non-english test run'
87
+ # Not sure why tese are different.
88
+ if ruby_darwin?
89
+ assert_equal 20009, e.db_error_number
90
+ assert_equal 9, e.severity
91
+ assert_match %r{is unavailable or does not exist}i, e.message, 'ignore if non-english test run'
92
+ else
93
+ assert_equal 20012, e.db_error_number
94
+ assert_equal 2, e.severity
95
+ assert_match %r{server name not found in configuration files}i, e.message, 'ignore if non-english test run'
96
+ end
84
97
  end
85
98
  assert_new_connections_work
86
99
  end
@@ -119,30 +132,64 @@ class ClientTest < TinyTds::TestCase
119
132
  end
120
133
  end
121
134
 
122
- it 'must run this test to prove we account for dropped connections' do
123
- skip
135
+ it 'raises TinyTds exception with tcp socket network failure' do
136
+ skip if ENV['CI'] && ENV['APPVEYOR_BUILD_FOLDER'] # only CI using docker
137
+ begin
138
+ client = new_connection timeout: 2, port: 1234
139
+ assert_client_works(client)
140
+ action = lambda { client.execute("waitfor delay '00:00:05'").do }
141
+
142
+ # Use toxiproxy to close the TCP socket after 1 second.
143
+ # We want TinyTds to execute the statement, hit the timeout configured above, and then not be able to use the network to cancel
144
+ # the network connection needs to close after the sql batch is sent and before the timeout above is hit
145
+ Toxiproxy[:sqlserver_test].toxic(:slow_close, delay: 1000).apply do
146
+ assert_raise_tinytds_error(action) do |e|
147
+ assert_equal 20003, e.db_error_number
148
+ assert_equal 6, e.severity
149
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
150
+ end
151
+ end
152
+ ensure
153
+ assert_new_connections_work
154
+ end
155
+ end
156
+
157
+ it 'raises TinyTds exception with dead connection network failure' do
158
+ skip if ENV['CI'] && ENV['APPVEYOR_BUILD_FOLDER'] # only CI using docker
124
159
  begin
125
- client = new_connection :login_timeout => 2, :timeout => 2
160
+ client = new_connection timeout: 2, port: 1234
126
161
  assert_client_works(client)
127
- STDOUT.puts "Disconnect network!"
128
- sleep 10
129
- STDOUT.puts "This should not get stuck past 6 seconds!"
130
- action = lambda { client.execute('SELECT 1 as [one]').each }
162
+ action = lambda { client.execute("waitfor delay '00:00:05'").do }
163
+
164
+ # Use toxiproxy to close the network connection after 1 second.
165
+ # We want TinyTds to execute the statement, hit the timeout configured above, and then not be able to use the network to cancel
166
+ # the network connection needs to close after the sql batch is sent and before the timeout above is hit
167
+ Toxiproxy[:sqlserver_test].toxic(:timeout, timeout: 1000).apply do
168
+ assert_raise_tinytds_error(action) do |e|
169
+ assert_equal 20047, e.db_error_number
170
+ assert_includes [1,9], e.severity
171
+ assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
172
+ end
173
+ end
174
+ ensure
175
+ assert_new_connections_work
176
+ end
177
+ end
178
+
179
+ it 'raises TinyTds exception with login timeout' do
180
+ skip if ENV['CI'] && ENV['APPVEYOR_BUILD_FOLDER'] # only CI using docker
181
+ begin
182
+ action = lambda do
183
+ Toxiproxy[:sqlserver_test].toxic(:timeout, timeout: 0).apply do
184
+ new_connection login_timeout: 1, port: 1234
185
+ end
186
+ end
131
187
  assert_raise_tinytds_error(action) do |e|
132
188
  assert_equal 20003, e.db_error_number
133
189
  assert_equal 6, e.severity
134
190
  assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
135
191
  end
136
192
  ensure
137
- STDOUT.puts "Reconnect network!"
138
- sleep 10
139
- action = lambda { client.execute('SELECT 1 as [one]').each }
140
- assert_raise_tinytds_error(action) do |e|
141
- assert_equal 20047, e.db_error_number
142
- assert_equal 1, e.severity
143
- assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
144
- end
145
- close_client(client)
146
193
  assert_new_connections_work
147
194
  end
148
195
  end
@@ -161,57 +208,68 @@ class ClientTest < TinyTds::TestCase
161
208
 
162
209
  end
163
210
 
164
- describe 'Private methods' do
165
-
211
+ describe '#parse_username' do
166
212
  let(:client) { @client = new_connection }
167
213
 
168
- it '#parse_username returns username if azure is not true' do
169
- username = 'user@abc123.database.windows.net'
170
- client.send(:parse_username, username: username).must_equal username
214
+ it 'returns username if azure is not true' do
215
+ _(
216
+ client.send(:parse_username, username: 'user@abc123.database.windows.net')
217
+ ).must_equal 'user@abc123.database.windows.net'
171
218
  end
172
219
 
173
- it '#parse_username returns short username if azure is true' do
174
- client.send(:parse_username,
175
- username: 'user@abc123.database.windows.net',
176
- host: 'abc123.database.windows.net',
177
- azure: true
220
+ it 'returns short username if azure is true' do
221
+ _(
222
+ client.send(
223
+ :parse_username,
224
+ username: 'user@abc123.database.windows.net',
225
+ host: 'abc123.database.windows.net',
226
+ azure: true
227
+ )
178
228
  ).must_equal 'user@abc123'
179
229
  end
180
230
 
181
- it '#parse_username returns full username if azure is false' do
182
- client.send(:parse_username,
183
- username: 'user@abc123.database.windows.net',
184
- host: 'abc123.database.windows.net',
185
- azure: false
231
+ it 'returns full username if azure is false' do
232
+ _(
233
+ client.send(
234
+ :parse_username,
235
+ username: 'user@abc123.database.windows.net',
236
+ host: 'abc123.database.windows.net',
237
+ azure: false
238
+ )
186
239
  ).must_equal 'user@abc123.database.windows.net'
187
240
  end
188
241
 
189
- it '#parse_username returns short username if passed and azure is true' do
190
- client.send(:parse_username,
191
- username: 'user@abc123',
192
- host: 'abc123.database.windows.net',
193
- azure: true
242
+ it 'returns short username if passed and azure is true' do
243
+ _(
244
+ client.send(
245
+ :parse_username,
246
+ username: 'user@abc123',
247
+ host: 'abc123.database.windows.net',
248
+ azure: true
249
+ )
194
250
  ).must_equal 'user@abc123'
195
251
  end
196
252
 
197
- it '#parse_username returns username with servername if passed and azure is true' do
198
- client.send(:parse_username,
199
- username: 'user',
200
- host: 'abc123.database.windows.net',
201
- azure: true
253
+ it 'returns username with servername if passed and azure is true' do
254
+ _(
255
+ client.send(
256
+ :parse_username,
257
+ username: 'user',
258
+ host: 'abc123.database.windows.net',
259
+ azure: true
260
+ )
202
261
  ).must_equal 'user@abc123'
203
262
  end
204
263
 
205
- it '#parse_username returns username with servername if passed and azure is false' do
206
- client.send(:parse_username,
207
- username: 'user',
208
- host: 'abc123.database.windows.net',
209
- azure: false
264
+ it 'returns username with servername if passed and azure is false' do
265
+ _(
266
+ client.send(
267
+ :parse_username,
268
+ username: 'user',
269
+ host: 'abc123.database.windows.net',
270
+ azure: false
271
+ )
210
272
  ).must_equal 'user'
211
273
  end
212
-
213
274
  end
214
-
215
-
216
275
  end
217
-
data/test/gem_test.rb ADDED
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+ require 'tiny_tds/gem'
4
+
5
+ class GemTest < MiniTest::Spec
6
+ gem_root ||= File.expand_path '../..', __FILE__
7
+
8
+ describe TinyTds::Gem do
9
+
10
+ # We're going to muck with some system globals so lets make sure
11
+ # they get set back later
12
+ original_host = RbConfig::CONFIG['host']
13
+ original_pwd = Dir.pwd
14
+
15
+ after do
16
+ RbConfig::CONFIG['host'] = original_host
17
+ Dir.chdir original_pwd
18
+ end
19
+
20
+ describe '#root_path' do
21
+ let(:root_path) { TinyTds::Gem.root_path }
22
+
23
+ it 'should be the root path' do
24
+ _(root_path).must_equal gem_root
25
+ end
26
+
27
+ it 'should be the root path no matter the cwd' do
28
+ Dir.chdir '/'
29
+
30
+ _(root_path).must_equal gem_root
31
+ end
32
+ end
33
+
34
+ describe '#ports_root_path' do
35
+ let(:ports_root_path) { TinyTds::Gem.ports_root_path }
36
+
37
+ it 'should be the ports path' do
38
+ _(ports_root_path).must_equal File.join(gem_root,'ports')
39
+ end
40
+
41
+ it 'should be the ports path no matter the cwd' do
42
+ Dir.chdir '/'
43
+
44
+ _(ports_root_path).must_equal File.join(gem_root,'ports')
45
+ end
46
+ end
47
+
48
+ describe '#ports_bin_paths' do
49
+ let(:ports_bin_paths) { TinyTds::Gem.ports_bin_paths }
50
+
51
+ describe 'when the ports directories exist' do
52
+ let(:fake_bin_paths) do
53
+ ports_host_root = File.join(gem_root, 'ports', 'fake-host-with-dirs')
54
+ [
55
+ File.join('a','bin'),
56
+ File.join('a','inner','bin'),
57
+ File.join('b','bin')
58
+ ].map do |p|
59
+ File.join(ports_host_root, p)
60
+ end
61
+ end
62
+
63
+ before do
64
+ RbConfig::CONFIG['host'] = 'fake-host-with-dirs'
65
+ fake_bin_paths.each do |path|
66
+ FileUtils.mkdir_p(path)
67
+ end
68
+ end
69
+
70
+ after do
71
+ FileUtils.remove_entry_secure(
72
+ File.join(gem_root, 'ports', 'fake-host-with-dirs'), true
73
+ )
74
+ end
75
+
76
+ it 'should return all the bin directories' do
77
+ _(ports_bin_paths.sort).must_equal fake_bin_paths.sort
78
+ end
79
+
80
+ it 'should return all the bin directories regardless of cwd' do
81
+ Dir.chdir '/'
82
+ _(ports_bin_paths.sort).must_equal fake_bin_paths.sort
83
+ end
84
+ end
85
+
86
+ describe 'when the ports directories are missing' do
87
+ before do
88
+ RbConfig::CONFIG['host'] = 'fake-host-without-dirs'
89
+ end
90
+
91
+ it 'should return no directories' do
92
+ _(ports_bin_paths).must_be_empty
93
+ end
94
+
95
+ it 'should return no directories regardless of cwd' do
96
+ Dir.chdir '/'
97
+ _(ports_bin_paths).must_be_empty
98
+ end
99
+ end
100
+ end
101
+
102
+ describe '#ports_lib_paths' do
103
+ let(:ports_lib_paths) { TinyTds::Gem.ports_lib_paths }
104
+
105
+ describe 'when the ports directories exist' do
106
+ let(:fake_lib_paths) do
107
+ ports_host_root = File.join(gem_root, 'ports', 'fake-host-with-dirs')
108
+ [
109
+ File.join('a','lib'),
110
+ File.join('a','inner','lib'),
111
+ File.join('b','lib')
112
+ ].map do |p|
113
+ File.join(ports_host_root, p)
114
+ end
115
+ end
116
+
117
+ before do
118
+ RbConfig::CONFIG['host'] = 'fake-host-with-dirs'
119
+ fake_lib_paths.each do |path|
120
+ FileUtils.mkdir_p(path)
121
+ end
122
+ end
123
+
124
+ after do
125
+ FileUtils.remove_entry_secure(
126
+ File.join(gem_root, 'ports', 'fake-host-with-dirs'), true
127
+ )
128
+ end
129
+
130
+ it 'should return all the lib directories' do
131
+ _(ports_lib_paths.sort).must_equal fake_lib_paths.sort
132
+ end
133
+
134
+ it 'should return all the lib directories regardless of cwd' do
135
+ Dir.chdir '/'
136
+ _(ports_lib_paths.sort).must_equal fake_lib_paths.sort
137
+ end
138
+ end
139
+
140
+ describe 'when the ports directories are missing' do
141
+ before do
142
+ RbConfig::CONFIG['host'] = 'fake-host-without-dirs'
143
+ end
144
+
145
+
146
+ it 'should return no directories' do
147
+ _(ports_lib_paths).must_be_empty
148
+ end
149
+
150
+ it 'should return no directories regardless of cwd' do
151
+ Dir.chdir '/'
152
+ _(ports_lib_paths).must_be_empty
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#ports_host' do
158
+ {
159
+ 'i686-pc-linux-gnu' => 'i686-pc-linux-gnu',
160
+ 'x86_64-pc-linux-gnu' => 'x86_64-pc-linux-gnu',
161
+ 'i686-w64-mingw32' => 'i686-w64-mingw32',
162
+ 'x86_64-w64-mingw32' => 'x86_64-w64-mingw32',
163
+ # consolidate this host to our build w64-mingw32 arch
164
+ 'i686-pc-mingw32' => 'i686-w64-mingw32'
165
+ }.each do |host,expected|
166
+ describe "on a #{host} architecture" do
167
+ before do
168
+ RbConfig::CONFIG['host'] = host
169
+ end
170
+
171
+ it "should return a #{expected} ports host" do
172
+ _(TinyTds::Gem.ports_host).must_equal expected
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+