tiny_tds 2.1.0-x64-mingw32 → 2.1.4.pre2-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,9 +5,9 @@
5
5
  void init_tinytds_client();
6
6
 
7
7
  #define ERROR_MSG_SIZE 1024
8
+ #define ERRORS_STACK_INIT_SIZE 2
8
9
 
9
10
  typedef struct {
10
- short int is_set;
11
11
  int is_message;
12
12
  int cancel;
13
13
  char error[ERROR_MSG_SIZE];
@@ -25,7 +25,10 @@ typedef struct {
25
25
  RETCODE dbsqlok_retcode;
26
26
  short int dbcancel_sent;
27
27
  short int nonblocking;
28
- tinytds_errordata nonblocking_error;
28
+ short int nonblocking_errors_length;
29
+ short int nonblocking_errors_size;
30
+ tinytds_errordata *nonblocking_errors;
31
+ VALUE message_handler;
29
32
  } tinytds_client_userdata;
30
33
 
31
34
  typedef struct {
@@ -39,7 +42,7 @@ typedef struct {
39
42
  rb_encoding *encoding;
40
43
  } tinytds_client_wrapper;
41
44
 
42
- VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int is_message, int cancel, const char *error, const char *source, int severity, int dberr, int oserr);
45
+ VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error);
43
46
 
44
47
  // Lib Macros
45
48
 
@@ -22,35 +22,39 @@ do_help if arg_config('--help')
22
22
 
23
23
  # Make sure to check the ports path for the configured host
24
24
  host = RbConfig::CONFIG['host']
25
- project_dir = File.join(['..']*4)
25
+ project_dir = File.expand_path("../../..", __FILE__)
26
26
  freetds_ports_dir = File.join(project_dir, 'ports', host, 'freetds', FREETDS_VERSION)
27
27
  freetds_ports_dir = File.expand_path(freetds_ports_dir)
28
28
 
29
29
  # Add all the special path searching from the original tiny_tds build
30
- # order is important here! First in, last searched.
30
+ # order is important here! First in, first searched.
31
31
  DIRS = %w(
32
- /usr/local
33
32
  /opt/local
33
+ /usr/local
34
34
  )
35
35
 
36
+ # Add the ports directory if it exists for local developer builds
37
+ DIRS.unshift(freetds_ports_dir) if File.directory?(freetds_ports_dir)
38
+
36
39
  # Grab freetds environment variable for use by people on services like
37
40
  # Heroku who they can't easily use bundler config to set directories
38
- DIRS.push(ENV['FREETDS_DIR']) if ENV.has_key?('FREETDS_DIR')
39
-
40
- # Add the ports directory if it exists for local developer builds
41
- DIRS.push(freetds_ports_dir) if File.directory?(freetds_ports_dir)
41
+ DIRS.unshift(ENV['FREETDS_DIR']) if ENV.has_key?('FREETDS_DIR')
42
42
 
43
43
  # Add the search paths for freetds configured above
44
- DIRS.each do |path|
45
- idir = "#{path}/include"
44
+ ldirs = DIRS.flat_map do |path|
46
45
  ldir = "#{path}/lib"
46
+ [ldir, "#{ldir}/freetds"]
47
+ end
47
48
 
48
- dir_config('freetds',
49
- [idir, "#{idir}/freetds"],
50
- [ldir, "#{ldir}/freetds"]
51
- )
49
+ idirs = DIRS.flat_map do |path|
50
+ idir = "#{path}/include"
51
+ [idir, "#{idir}/freetds"]
52
52
  end
53
53
 
54
+ puts "looking for freetds headers in the following directories:\n#{idirs.map{|a| " - #{a}\n"}.join}"
55
+ puts "looking for freetds library in the following directories:\n#{ldirs.map{|a| " - #{a}\n"}.join}"
56
+ dir_config('freetds', idirs, ldirs)
57
+
54
58
  have_dependencies = [
55
59
  find_header('sybfront.h'),
56
60
  find_header('sybdb.h'),
@@ -2,10 +2,10 @@
2
2
  ICONV_VERSION = ENV['TINYTDS_ICONV_VERSION'] || "1.15"
3
3
  ICONV_SOURCE_URI = "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-#{ICONV_VERSION}.tar.gz"
4
4
 
5
- OPENSSL_VERSION = ENV['TINYTDS_OPENSSL_VERSION'] || '1.1.0e'
5
+ OPENSSL_VERSION = ENV['TINYTDS_OPENSSL_VERSION'] || '1.1.1d'
6
6
  OPENSSL_SOURCE_URI = "https://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz"
7
7
 
8
- FREETDS_VERSION = ENV['TINYTDS_FREETDS_VERSION'] || "1.00.27"
8
+ FREETDS_VERSION = ENV['TINYTDS_FREETDS_VERSION'] || "1.1.24"
9
9
  FREETDS_VERSION_INFO = Hash.new { |h,k|
10
10
  h[k] = {files: "http://www.freetds.org/files/stable/freetds-#{k}.tar.bz2"}
11
11
  }
@@ -6,10 +6,10 @@
6
6
 
7
7
  VALUE cTinyTdsResult;
8
8
  extern VALUE mTinyTds, cTinyTdsClient, cTinyTdsError;
9
- VALUE cBigDecimal, cDate;
9
+ VALUE cKernel, cDate;
10
10
  VALUE opt_decimal_zero, opt_float_zero, opt_one, opt_zero, opt_four, opt_19hdr, opt_onek, opt_tenk, opt_onemil, opt_onebil;
11
11
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge,
12
- intern_civil, intern_new_offset, intern_plus, intern_divide;
12
+ intern_civil, intern_new_offset, intern_plus, intern_divide, intern_bigd;
13
13
  static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc, sym_empty_sets;
14
14
 
15
15
 
@@ -86,26 +86,38 @@ static void dbcancel_ubf(DBPROCESS *client) {
86
86
  static void nogvl_setup(DBPROCESS *client) {
87
87
  GET_CLIENT_USERDATA(client);
88
88
  userdata->nonblocking = 1;
89
+ userdata->nonblocking_errors_length = 0;
90
+ userdata->nonblocking_errors = malloc(ERRORS_STACK_INIT_SIZE * sizeof(tinytds_errordata));
91
+ userdata->nonblocking_errors_size = ERRORS_STACK_INIT_SIZE;
89
92
  }
90
93
 
91
94
  static void nogvl_cleanup(DBPROCESS *client) {
92
95
  GET_CLIENT_USERDATA(client);
93
96
  userdata->nonblocking = 0;
97
+ userdata->timing_out = 0;
94
98
  /*
95
99
  Now that the blocking operation is done, we can finally throw any
96
100
  exceptions based on errors from SQL Server.
97
101
  */
98
- if (userdata->nonblocking_error.is_set) {
99
- userdata->nonblocking_error.is_set = 0;
100
- rb_tinytds_raise_error(client,
101
- userdata->nonblocking_error.is_message,
102
- userdata->nonblocking_error.cancel,
103
- userdata->nonblocking_error.error,
104
- userdata->nonblocking_error.source,
105
- userdata->nonblocking_error.severity,
106
- userdata->nonblocking_error.dberr,
107
- userdata->nonblocking_error.oserr);
102
+ for (short int i = 0; i < userdata->nonblocking_errors_length; i++) {
103
+ tinytds_errordata error = userdata->nonblocking_errors[i];
104
+
105
+ // lookahead to drain any info messages ahead of raising error
106
+ if (!error.is_message) {
107
+ for (short int j = i; j < userdata->nonblocking_errors_length; j++) {
108
+ tinytds_errordata msg_error = userdata->nonblocking_errors[j];
109
+ if (msg_error.is_message) {
110
+ rb_tinytds_raise_error(client, msg_error);
111
+ }
112
+ }
113
+ }
114
+
115
+ rb_tinytds_raise_error(client, error);
108
116
  }
117
+
118
+ free(userdata->nonblocking_errors);
119
+ userdata->nonblocking_errors_length = 0;
120
+ userdata->nonblocking_errors_size = 0;
109
121
  }
110
122
 
111
123
  static RETCODE nogvl_dbsqlok(DBPROCESS *client) {
@@ -228,7 +240,7 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
228
240
  int data_slength = (int)data_info->precision + (int)data_info->scale + 1;
229
241
  char converted_decimal[data_slength];
230
242
  dbconvert(rwrap->client, coltype, data, data_len, SYBVARCHAR, (BYTE *)converted_decimal, -1);
231
- val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2((char *)converted_decimal));
243
+ val = rb_funcall(cKernel, intern_bigd, 1, rb_str_new2((char *)converted_decimal));
232
244
  break;
233
245
  }
234
246
  case SYBFLT8: {
@@ -246,7 +258,7 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
246
258
  char converted_money[25];
247
259
  long long money_value = ((long long)money->mnyhigh << 32) | money->mnylow;
248
260
  sprintf(converted_money, "%" LONG_LONG_FORMAT, money_value);
249
- val = rb_funcall(cBigDecimal, intern_new, 2, rb_str_new2(converted_money), opt_four);
261
+ val = rb_funcall(cKernel, intern_bigd, 2, rb_str_new2(converted_money), opt_four);
250
262
  val = rb_funcall(val, intern_divide, 1, opt_tenk);
251
263
  break;
252
264
  }
@@ -254,7 +266,7 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
254
266
  DBMONEY4 *money = (DBMONEY4 *)data;
255
267
  char converted_money[20];
256
268
  sprintf(converted_money, "%f", money->mny4 / 10000.0);
257
- val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2(converted_money));
269
+ val = rb_funcall(cKernel, intern_bigd, 1, rb_str_new2(converted_money));
258
270
  break;
259
271
  }
260
272
  case SYBBINARY:
@@ -566,7 +578,7 @@ static VALUE rb_tinytds_result_insert(VALUE self) {
566
578
 
567
579
  void init_tinytds_result() {
568
580
  /* Data Classes */
569
- cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
581
+ cKernel = rb_const_get(rb_cObject, rb_intern("Kernel"));
570
582
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
571
583
  /* Define TinyTds::Result */
572
584
  cTinyTdsResult = rb_define_class_under(mTinyTds, "Result", rb_cObject);
@@ -588,6 +600,7 @@ void init_tinytds_result() {
588
600
  intern_new_offset = rb_intern("new_offset");
589
601
  intern_plus = rb_intern("+");
590
602
  intern_divide = rb_intern("/");
603
+ intern_bigd = rb_intern("BigDecimal");
591
604
  /* Symbol Helpers */
592
605
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
593
606
  sym_as = ID2SYM(rb_intern("as"));
@@ -8,7 +8,7 @@ task 'gem:windows' => ['ports:cross'] do
8
8
  build = ['bundle']
9
9
 
10
10
  # and finally build the native gem
11
- build << 'rake cross native gem RUBY_CC_VERSION=2.4.0:2.3.0:2.2.2:2.1.6:2.0.0 CFLAGS="-Wall"'
11
+ build << 'rake cross native gem RUBY_CC_VERSION=2.7.0:2.6.0:2.5.0:2.4.0 CFLAGS="-Wall" MAKE="make -j`nproc`"'
12
12
 
13
13
  RakeCompilerDock.sh build.join(' && ')
14
14
  end
data/tasks/ports.rake CHANGED
@@ -6,14 +6,14 @@ require_relative 'ports/openssl'
6
6
  require_relative 'ports/freetds'
7
7
  require_relative '../ext/tiny_tds/extconsts'
8
8
 
9
- OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE if defined? OpenSSL
10
-
11
9
  namespace :ports do
12
10
  openssl = Ports::Openssl.new(OPENSSL_VERSION)
13
11
  libiconv = Ports::Libiconv.new(ICONV_VERSION)
14
12
  freetds = Ports::Freetds.new(FREETDS_VERSION)
15
13
 
16
14
  directory "ports"
15
+ CLEAN.include "ports/*mingw32*"
16
+ CLEAN.include "ports/*.installed"
17
17
 
18
18
  task :openssl, [:host] do |task, args|
19
19
  args.with_defaults(host: RbConfig::CONFIG['host'])
@@ -71,15 +71,13 @@ namespace :ports do
71
71
  task 'cross' do
72
72
  require 'rake_compiler_dock'
73
73
 
74
- # make sure to install our bundle
75
- build = ['bundle']
76
-
77
74
  # build the ports for all our cross compile hosts
78
75
  GEM_PLATFORM_HOSTS.each do |gem_platform, host|
79
- build << "rake ports:compile[#{host}]"
76
+ # make sure to install our bundle
77
+ build = ['bundle']
78
+ build << "rake ports:compile[#{host}] MAKE='make -j`nproc`'"
79
+ RakeCompilerDock.sh build.join(' && '), platform: gem_platform
80
80
  end
81
-
82
- RakeCompilerDock.sh build.join(' && ')
83
81
  end
84
82
  end
85
83
 
@@ -27,27 +27,11 @@ module Ports
27
27
 
28
28
  private
29
29
 
30
- def execute(action, command, options={})
31
- # OpenSSL Requires Perl >= 5.10, while the Ruby devkit uses MSYS1 with Perl 5.8.8.
32
- # To overcome this, prepend Git's usr/bin to the PATH.
33
- # It has MSYS2 with a recent version of perl.
34
- prev_path = ENV['PATH']
35
- if host =~ /mingw/ && IO.popen(["perl", "-e", "print($])"], &:read).to_f < 5.010
36
- git_perl = 'C:/Program Files/Git/usr/bin'
37
- if File.directory?(git_perl)
38
- ENV['PATH'] = "#{git_perl}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
39
- ENV['PERL'] = 'perl'
40
- end
41
- end
42
-
43
- super
44
- ENV['PATH'] = prev_path
45
- end
46
-
47
30
  def configure_defaults
48
31
  opts = [
49
32
  'shared',
50
- target_arch
33
+ target_arch,
34
+ "--openssldir=#{path}",
51
35
  ]
52
36
 
53
37
  if cross_build?
data/test/bin/setup.sh CHANGED
@@ -3,7 +3,7 @@
3
3
  set -x
4
4
  set -e
5
5
 
6
- tag=2.0
6
+ tag=2017-GA
7
7
 
8
8
  docker pull metaskills/mssql-server-linux-tinytds:$tag
9
9
 
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
@@ -67,10 +65,12 @@ class ClientTest < TinyTds::TestCase
67
65
  client.close if client
68
66
  end
69
67
  end unless sqlserver_azure?
70
-
71
68
  end
72
69
 
73
70
  describe 'With in-valid options' do
71
+ before(:all) do
72
+ init_toxiproxy
73
+ end
74
74
 
75
75
  it 'raises an argument error when no :host given and :dataserver is blank' do
76
76
  assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
@@ -132,30 +132,46 @@ class ClientTest < TinyTds::TestCase
132
132
  end
133
133
  end
134
134
 
135
- it 'must run this test to prove we account for dropped connections' do
136
- 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
137
  begin
138
- client = new_connection :login_timeout => 2, :timeout => 2
138
+ client = new_connection timeout: 2, port: 1234
139
139
  assert_client_works(client)
140
- STDOUT.puts "Disconnect network!"
141
- sleep 10
142
- STDOUT.puts "This should not get stuck past 6 seconds!"
143
- action = lambda { client.execute('SELECT 1 as [one]').each }
144
- assert_raise_tinytds_error(action) do |e|
145
- assert_equal 20003, e.db_error_number
146
- assert_equal 6, e.severity
147
- assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
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
148
151
  end
149
152
  ensure
150
- STDOUT.puts "Reconnect network!"
151
- sleep 10
152
- action = lambda { client.execute('SELECT 1 as [one]').each }
153
- assert_raise_tinytds_error(action) do |e|
154
- assert_equal 20047, e.db_error_number
155
- assert_equal 1, e.severity
156
- assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
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
159
+ begin
160
+ client = new_connection timeout: 2, port: 1234
161
+ assert_client_works(client)
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
157
173
  end
158
- close_client(client)
174
+ ensure
159
175
  assert_new_connections_work
160
176
  end
161
177
  end
@@ -174,57 +190,68 @@ class ClientTest < TinyTds::TestCase
174
190
 
175
191
  end
176
192
 
177
- describe 'Private methods' do
178
-
193
+ describe '#parse_username' do
179
194
  let(:client) { @client = new_connection }
180
195
 
181
- it '#parse_username returns username if azure is not true' do
182
- username = 'user@abc123.database.windows.net'
183
- client.send(:parse_username, username: username).must_equal username
196
+ it 'returns username if azure is not true' do
197
+ _(
198
+ client.send(:parse_username, username: 'user@abc123.database.windows.net')
199
+ ).must_equal 'user@abc123.database.windows.net'
184
200
  end
185
201
 
186
- it '#parse_username returns short username if azure is true' do
187
- client.send(:parse_username,
188
- username: 'user@abc123.database.windows.net',
189
- host: 'abc123.database.windows.net',
190
- azure: true
202
+ it 'returns short username if azure is true' do
203
+ _(
204
+ client.send(
205
+ :parse_username,
206
+ username: 'user@abc123.database.windows.net',
207
+ host: 'abc123.database.windows.net',
208
+ azure: true
209
+ )
191
210
  ).must_equal 'user@abc123'
192
211
  end
193
212
 
194
- it '#parse_username returns full username if azure is false' do
195
- client.send(:parse_username,
196
- username: 'user@abc123.database.windows.net',
197
- host: 'abc123.database.windows.net',
198
- azure: false
213
+ it 'returns full username if azure is false' do
214
+ _(
215
+ client.send(
216
+ :parse_username,
217
+ username: 'user@abc123.database.windows.net',
218
+ host: 'abc123.database.windows.net',
219
+ azure: false
220
+ )
199
221
  ).must_equal 'user@abc123.database.windows.net'
200
222
  end
201
223
 
202
- it '#parse_username returns short username if passed and azure is true' do
203
- client.send(:parse_username,
204
- username: 'user@abc123',
205
- host: 'abc123.database.windows.net',
206
- azure: true
224
+ it 'returns short username if passed and azure is true' do
225
+ _(
226
+ client.send(
227
+ :parse_username,
228
+ username: 'user@abc123',
229
+ host: 'abc123.database.windows.net',
230
+ azure: true
231
+ )
207
232
  ).must_equal 'user@abc123'
208
233
  end
209
234
 
210
- it '#parse_username returns username with servername if passed and azure is true' do
211
- client.send(:parse_username,
212
- username: 'user',
213
- host: 'abc123.database.windows.net',
214
- azure: true
235
+ it 'returns username with servername if passed and azure is true' do
236
+ _(
237
+ client.send(
238
+ :parse_username,
239
+ username: 'user',
240
+ host: 'abc123.database.windows.net',
241
+ azure: true
242
+ )
215
243
  ).must_equal 'user@abc123'
216
244
  end
217
245
 
218
- it '#parse_username returns username with servername if passed and azure is false' do
219
- client.send(:parse_username,
220
- username: 'user',
221
- host: 'abc123.database.windows.net',
222
- azure: false
246
+ it 'returns username with servername if passed and azure is false' do
247
+ _(
248
+ client.send(
249
+ :parse_username,
250
+ username: 'user',
251
+ host: 'abc123.database.windows.net',
252
+ azure: false
253
+ )
223
254
  ).must_equal 'user'
224
255
  end
225
-
226
256
  end
227
-
228
-
229
257
  end
230
-