tiny_tds 2.1.3.pre → 2.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +0 -0
  3. data/.gitattributes +0 -0
  4. data/.gitignore +0 -0
  5. data/.rubocop.yml +0 -0
  6. data/.travis.yml +1 -1
  7. data/CHANGELOG.md +12 -1
  8. data/CODE_OF_CONDUCT.md +0 -0
  9. data/Gemfile +0 -0
  10. data/ISSUE_TEMPLATE.md +0 -0
  11. data/MIT-LICENSE +0 -0
  12. data/README.md +13 -7
  13. data/Rakefile +0 -0
  14. data/VERSION +1 -1
  15. data/appveyor.yml +15 -1
  16. data/docker-compose.yml +22 -0
  17. data/ext/tiny_tds/client.c +90 -44
  18. data/ext/tiny_tds/client.h +5 -3
  19. data/ext/tiny_tds/extconf.rb +0 -0
  20. data/ext/tiny_tds/extconsts.rb +1 -1
  21. data/ext/tiny_tds/result.c +24 -10
  22. data/ext/tiny_tds/result.h +0 -0
  23. data/ext/tiny_tds/tiny_tds_ext.c +0 -0
  24. data/ext/tiny_tds/tiny_tds_ext.h +0 -0
  25. data/lib/tiny_tds.rb +0 -0
  26. data/lib/tiny_tds/bin.rb +0 -0
  27. data/lib/tiny_tds/client.rb +0 -0
  28. data/lib/tiny_tds/error.rb +0 -0
  29. data/lib/tiny_tds/gem.rb +0 -0
  30. data/lib/tiny_tds/result.rb +0 -0
  31. data/lib/tiny_tds/version.rb +0 -0
  32. data/patches/freetds/1.00.27/0001-mingw_missing_inet_pton.diff +0 -0
  33. data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +0 -0
  34. data/patches/libiconv/1.14/1-avoid-gets-error.patch +0 -0
  35. data/tasks/native_gem.rake +0 -0
  36. data/tasks/package.rake +0 -0
  37. data/tasks/ports.rake +0 -0
  38. data/tasks/ports/freetds.rb +0 -0
  39. data/tasks/ports/libiconv.rb +0 -0
  40. data/tasks/ports/openssl.rb +0 -0
  41. data/tasks/ports/recipe.rb +0 -0
  42. data/tasks/test.rake +0 -0
  43. data/test/appveyor/dbsetup.ps1 +0 -0
  44. data/test/appveyor/dbsetup.sql +0 -0
  45. data/test/benchmark/query.rb +0 -0
  46. data/test/benchmark/query_odbc.rb +0 -0
  47. data/test/benchmark/query_tinytds.rb +0 -0
  48. data/test/client_test.rb +99 -54
  49. data/test/gem_test.rb +15 -15
  50. data/test/result_test.rb +83 -42
  51. data/test/schema/sqlserver_2000.sql +0 -0
  52. data/test/schema/sqlserver_2005.sql +0 -0
  53. data/test/schema/sqlserver_2008.sql +0 -0
  54. data/test/schema/sqlserver_2014.sql +0 -0
  55. data/test/schema/sqlserver_2016.sql +0 -0
  56. data/test/schema/sqlserver_azure.sql +0 -0
  57. data/test/schema/sybase_ase.sql +0 -0
  58. data/test/schema_test.rb +12 -12
  59. data/test/test_helper.rb +58 -4
  60. data/test/thread_test.rb +1 -1
  61. data/tiny_tds.gemspec +1 -0
  62. metadata +45 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6803c21378a241d1d114baffcf8f2419a19a4af3bea6654233e64706cea10a4
4
- data.tar.gz: dfe45388f4e3e5ffac255d3ce3fdb2dbe7c726ac226b8d634b274b111391447a
3
+ metadata.gz: 68f96f7fe4be98e8165b7a9716e116dc3989a42511e0d9a5115bcf8e69fe6582
4
+ data.tar.gz: 96cbb636206ff15c987dba8759abe644e62168ed9afa5286acf57b18b95bf6ce
5
5
  SHA512:
6
- metadata.gz: 42ec8401eafb73c3d80a634d038dcd6abec3f76dcb669f077f4be4ec82d71d47c8f2623e52b460af2b3bfce91209c06e03d8395a4946d713f2219fdf6026e29b
7
- data.tar.gz: 1a4c88d4422b3df6e3597206694a40c86e97da57697df479781bc073100e5a2f404e715b5349a497d132a013926ffc91b992dc8ff2424d8edce624700a7ebe18
6
+ metadata.gz: 8679ca4bc997fab7528f8ec2bce4f754b92319835157d08c017a20cf6561bae6638da077300cdd361302935b384a1b5ec00c05e014e7c12700e31bc5fd160d06
7
+ data.tar.gz: d35162380fec76c36ddff7f2407a1715105aa264be7c18ee2b078acfab6aab9a22a24854e815c9da389c295a4d6348668900752623f49fa222d31fd6e5b647a7
data/.codeclimate.yml CHANGED
File without changes
data/.gitattributes CHANGED
File without changes
data/.gitignore CHANGED
File without changes
data/.rubocop.yml CHANGED
File without changes
data/.travis.yml CHANGED
@@ -14,9 +14,9 @@ rvm:
14
14
  - 2.7.0
15
15
  before_install:
16
16
  - docker info
17
+ - docker-compose up -d
17
18
  - sudo ./test/bin/install-openssl.sh
18
19
  - sudo ./test/bin/install-freetds.sh
19
- - sudo ./test/bin/setup.sh
20
20
  install:
21
21
  - gem install bundler
22
22
  - bundle --version
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## (unreleased)
2
2
 
3
+ ## 2.1.5
4
+
5
+ * Fix compilation errors for Amazon Linux 1. Fixes #495.
6
+ * Fix segfault for login timeouts
7
+
8
+ ## 2.1.4
9
+
10
+ * Improve handling of network related timeouts
11
+ * Fix error reporting when preceded by info message
12
+
3
13
  ## 2.1.3
4
14
 
5
15
  * Removed old/unused appveyor config
@@ -215,6 +225,8 @@ Use both dbsetversion() vs. dbsetlversion. Partially reverts #62.
215
225
  state of the client and the need to use Result#cancel to stop processing active results. It is also
216
226
  safe to call Result#cancel over and over again.
217
227
  * Look for the syb headers only.
228
+ * Fix minitest global matchers warnings.
229
+ * Fix test warnings.
218
230
 
219
231
 
220
232
  ## 0.3.2
@@ -260,4 +272,3 @@ Use both dbsetversion() vs. dbsetlversion. Partially reverts #62.
260
272
 
261
273
 
262
274
  ## 0.1.0 Initial release!
263
-
data/CODE_OF_CONDUCT.md CHANGED
File without changes
data/Gemfile CHANGED
File without changes
data/ISSUE_TEMPLATE.md CHANGED
File without changes
data/MIT-LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  * [![TravisCI](https://travis-ci.org/rails-sqlserver/tiny_tds.svg?branch=master)](https://travis-ci.org/rails-sqlserver/tiny_tds) - TravisCI
4
4
  * [![Build Status](https://ci.appveyor.com/api/projects/status/g2bhhbsdkx0mal55/branch/master?svg=true)](https://ci.appveyor.com/project/rails-sqlserver/tiny-tds/branch/master) - Appveyor
5
5
  * [![Gem Version](https://img.shields.io/gem/v/tiny_tds.svg)](https://rubygems.org/gems/tiny_tds) - Gem Version
6
- * [![Dependency Status](https://dependencyci.com/github/rails-sqlserver/tiny_tds/badge)](https://dependencyci.com/github/rails-sqlserver/tiny_tds) - Dependency Status
7
6
  * [![Gitter chat](https://img.shields.io/badge/%E2%8A%AA%20GITTER%20-JOIN%20CHAT%20%E2%86%92-brightgreen.svg?style=flat)](https://gitter.im/rails-sqlserver/activerecord-sqlserver-adapter) - Community
8
7
 
9
8
 
@@ -111,7 +110,7 @@ Creating a new client takes a hash of options. For valid iconv encoding options,
111
110
  * :appname - Short string seen in SQL Servers process/activity window.
112
111
  * :tds_version - TDS version. Defaults to "7.3".
113
112
  * :login_timeout - Seconds to wait for login. Default to 60 seconds.
114
- * :timeout - Seconds to wait for a response to a SQL command. Default 5 seconds. Prior to 1.0rc5, FreeTDS was unable to set the timeout on a per-client basis, permitting only a global timeout value. This means that if you're using an older version, the timeout values for all clients will be overwritten each time you instantiate a new `TinyTds::Client` object. If you are using 1.0rc5 or later, all clients will have an independent timeout setting as you'd expect.
113
+ * :timeout - Seconds to wait for a response to a SQL command. Default 5 seconds. Prior to 1.0rc5, FreeTDS was unable to set the timeout on a per-client basis, permitting only a global timeout value. This means that if you're using an older version, the timeout values for all clients will be overwritten each time you instantiate a new `TinyTds::Client` object. If you are using 1.0rc5 or later, all clients will have an independent timeout setting as you'd expect. Timeouts caused by network failure will raise a timeout error 1 second after the configured timeout limit is hit (see [#481](https://github.com/rails-sqlserver/tiny_tds/pull/481) for details).
115
114
  * :encoding - Any valid iconv value like CP1251 or ISO-8859-1. Default UTF-8.
116
115
  * :azure - Pass true to signal that you are connecting to azure.
117
116
  * :contained - Pass true to signal that you are connecting with a contained database user.
@@ -323,9 +322,13 @@ By default row caching is turned on because the SQL Server adapter for ActiveRec
323
322
  TinyTDS takes an opinionated stance on how we handle encoding errors. First, we treat errors differently on reads vs. writes. Our opinion is that if you are reading bad data due to your client's encoding option, you would rather just find `?` marks in your strings vs being blocked with exceptions. This is how things wold work via ODBC or SMS. On the other hand, writes will raise an exception. In this case we raise the SYBEICONVO/2402 error message which has a description of `Error converting characters into server's character set. Some character(s) could not be converted.`. Even though the severity of this message is only a `4` and TinyTDS will automatically strip/ignore unknown characters, we feel you should know that you are inserting bad encodings. In this way, a transaction can be rolled back, etc. Remember, any database write that has bad characters due to the client encoding will still be written to the database, but it is up to you rollback said write if needed. Most ORMs like ActiveRecord handle this scenario just fine.
324
323
 
325
324
 
325
+ ## Timeout Error Handling
326
+
327
+ TinyTDS will raise a `TinyTDS::Error` when a timeout is reached based on the options supplied to the client. Depending on the reason for the timeout, the connection could be dead or alive. When db processing is the cause for the timeout, the connection should still be usable after the error is raised. When network failure is the cause of the timeout, the connection will be dead. If you attempt to execute another command batch on a dead connection you will see a `DBPROCESS is dead or not enabled` error. Therefore, it is recommended to check for a `dead?` connection before trying to execute another command batch.
328
+
326
329
  ## Binstubs
327
330
 
328
- The TinyTDS gem uses binstub wrappers which mirror compiled [FreeTDS Utilities](http://www.freetds.org/userguide/usefreetds.htm) binaries. These native executables are usually installed at the system level when installing FreeTDS. However, when using MiniPortile to install TinyTDS as we do with Windows binaries, these binstubs will find and prefer local gem `exe` directory executables. These are the following binstubs we wrap.
331
+ The TinyTDS gem uses binstub wrappers which mirror compiled [FreeTDS Utilities](https://www.freetds.org/userguide/usefreetds.html) binaries. These native executables are usually installed at the system level when installing FreeTDS. However, when using MiniPortile to install TinyTDS as we do with Windows binaries, these binstubs will find and prefer local gem `exe` directory executables. These are the following binstubs we wrap.
329
332
 
330
333
  * tsql - Used to test connections and debug compile time settings.
331
334
  * defncopy - Used to dump schema structures.
@@ -420,17 +423,20 @@ First, clone the repo using the command line or your Git GUI of choice.
420
423
  $ git clone git@github.com:rails-sqlserver/tiny_tds.git
421
424
  ```
422
425
 
423
- After that, the quickest way to get setup for development is to use [Docker](https://www.docker.com/). Assuming you have [downloaded docker](https://www.docker.com/products/docker) for your platform and you have , you can run our test setup script.
426
+ After that, the quickest way to get setup for development is to use [Docker](https://www.docker.com/). Assuming you have [downloaded docker](https://www.docker.com/products/docker) for your platform, you can use [docker-compose](https://docs.docker.com/compose/install/) to run the necessary containers for testing.
424
427
 
425
428
  ```shell
426
- $ ./test/bin/setup.sh
429
+ $ docker-compose up -d
427
430
  ```
428
431
 
429
- This will download our SQL Server for Linux Docker image based from [microsoft/mssql-server-linux/](https://hub.docker.com/r/microsoft/mssql-server-linux/). Our image already has the `[tinytdstest]` DB and `tinytds` users created. Basically, it does the following.
432
+ This will download our SQL Server for Linux Docker image based from [microsoft/mssql-server-linux/](https://hub.docker.com/r/microsoft/mssql-server-linux/). Our image already has the `[tinytdstest]` DB and `tinytds` users created. This will also download a [toxiproxy](https://github.com/shopify/toxiproxy) Docker image which we can use to simulate network failures for tests. Basically, it does the following.
430
433
 
431
434
  ```shell
435
+ $ docker network create main-network
432
436
  $ docker pull metaskills/mssql-server-linux-tinytds
433
- $ docker run -p 1433:1433 -d metaskills/mssql-server-linux-tinytds
437
+ $ docker run -p 1433:1433 -d --name sqlserver --network main-network metaskills/mssql-server-linux-tinytds
438
+ $ docker pull shopify/toxiproxy
439
+ $ docker run -p 8474:8474 -p 1234:1234 -d --name toxiproxy --network main-network shopify/toxiproxy
434
440
  ```
435
441
 
436
442
  If you are using your own database. Make sure to run these SQL commands as SA to get the test database and user installed.
data/Rakefile CHANGED
File without changes
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.3.pre
1
+ 2.1.5
data/appveyor.yml CHANGED
@@ -19,7 +19,21 @@ install:
19
19
  - perl --version
20
20
  - ruby --version
21
21
  - gem --version
22
-
22
+ # Update keyring according to https://www.msys2.org/news/#2020-06-29-new-packagers
23
+ - C:\msys64\usr\bin\curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz
24
+ - C:\msys64\usr\bin\curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig
25
+ - ridk exec bash -c "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
26
+ - ridk exec bash -c "pacman -U --noconfirm --config <(echo) msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
27
+ # Update zstd and pacman first https://github.com/msys2/MSYS2-packages/issues/2300
28
+ - C:\msys64\usr\bin\pacman --noconfirm --upgrade https://repo.msys2.org/msys/x86_64/zstd-1.4.7-1-x86_64.pkg.tar.xz # Must come First, or else pacman will install 1.4.8
29
+ - C:\msys64\usr\bin\pacman --noconfirm --upgrade https://repo.msys2.org/msys/x86_64/pacman-5.2.2-5-x86_64.pkg.tar.xz
30
+ # update packages
31
+ - C:\msys64\usr\bin\pacman --noconfirm --ask 20 --sync --refresh --refresh --sysupgrade --sysupgrade
32
+ # Kill all running msys2 binaries to avoid error "size of shared memory region changed".
33
+ # See https://github.com/msys2/MSYS2-packages/issues/258
34
+ - powershell -Command "Get-Process | Where-Object {$_.path -like 'C:\msys64*'} | Stop-Process"
35
+ # refresh the package list to avoid "error: failed to prepare transaction (could not find database)"
36
+ - ridk exec bash -c "pacman --noconfirm -Sy"
23
37
  # prevent freetds to link to wrong ws2_32 lib on i686-w64-mingw32
24
38
  - c:/msys64/usr/bin/rm /usr/lib/w32api/libws2_32.a
25
39
  # Set up project prerequisits
@@ -0,0 +1,22 @@
1
+ version: '3'
2
+
3
+ networks:
4
+ main-network:
5
+
6
+ services:
7
+ mssql:
8
+ image: metaskills/mssql-server-linux-tinytds:2017-GA
9
+ container_name: sqlserver
10
+ ports:
11
+ - "1433:1433"
12
+ networks:
13
+ - main-network
14
+
15
+ toxiproxy:
16
+ image: shopify/toxiproxy
17
+ container_name: toxiproxy
18
+ ports:
19
+ - "8474:8474"
20
+ - "1234:1234"
21
+ networks:
22
+ - main-network
@@ -24,25 +24,25 @@ VALUE opt_escape_regex, opt_escape_dblquote;
24
24
 
25
25
  // Lib Backend (Helpers)
26
26
 
27
- VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int is_message, int cancel, const char *error, const char *source, int severity, int dberr, int oserr) {
27
+ VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error) {
28
28
  VALUE e;
29
29
  GET_CLIENT_USERDATA(dbproc);
30
- if (cancel && !dbdead(dbproc) && userdata && !userdata->closed) {
30
+ if (error.cancel && !dbdead(dbproc) && userdata && !userdata->closed) {
31
31
  userdata->dbsqlok_sent = 1;
32
32
  dbsqlok(dbproc);
33
33
  userdata->dbcancel_sent = 1;
34
34
  dbcancel(dbproc);
35
35
  }
36
- e = rb_exc_new2(cTinyTdsError, error);
37
- rb_funcall(e, intern_source_eql, 1, rb_str_new2(source));
38
- if (severity)
39
- rb_funcall(e, intern_severity_eql, 1, INT2FIX(severity));
40
- if (dberr)
41
- rb_funcall(e, intern_db_error_number_eql, 1, INT2FIX(dberr));
42
- if (oserr)
43
- rb_funcall(e, intern_os_error_number_eql, 1, INT2FIX(oserr));
44
-
45
- if (severity <= 10 && is_message) {
36
+ e = rb_exc_new2(cTinyTdsError, error.error);
37
+ rb_funcall(e, intern_source_eql, 1, rb_str_new2(error.source));
38
+ if (error.severity)
39
+ rb_funcall(e, intern_severity_eql, 1, INT2FIX(error.severity));
40
+ if (error.dberr)
41
+ rb_funcall(e, intern_db_error_number_eql, 1, INT2FIX(error.dberr));
42
+ if (error.oserr)
43
+ rb_funcall(e, intern_os_error_number_eql, 1, INT2FIX(error.oserr));
44
+
45
+ if (error.severity <= 10 && error.is_message) {
46
46
  VALUE message_handler = userdata && userdata->message_handler ? userdata->message_handler : Qnil;
47
47
  if (message_handler && message_handler != Qnil && rb_respond_to(message_handler, intern_call) != 0) {
48
48
  rb_funcall(message_handler, intern_call, 1, e);
@@ -57,6 +57,16 @@ VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int is_message, int cancel, cons
57
57
 
58
58
 
59
59
  // Lib Backend (Memory Management & Handlers)
60
+ static void push_userdata_error(tinytds_client_userdata *userdata, tinytds_errordata error) {
61
+ // reallocate memory for the array as needed
62
+ if (userdata->nonblocking_errors_size == userdata->nonblocking_errors_length) {
63
+ userdata->nonblocking_errors_size *= 2;
64
+ userdata->nonblocking_errors = realloc(userdata->nonblocking_errors, userdata->nonblocking_errors_size * sizeof(tinytds_errordata));
65
+ }
66
+
67
+ userdata->nonblocking_errors[userdata->nonblocking_errors_length] = error;
68
+ userdata->nonblocking_errors_length++;
69
+ }
60
70
 
61
71
  int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) {
62
72
  static const char *source = "error";
@@ -86,6 +96,13 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
86
96
  but we don't ever want to automatically retry. Instead have the app
87
97
  decide what to do.
88
98
  */
99
+ if (userdata && userdata->timing_out) {
100
+ return INT_CANCEL;
101
+ }
102
+ // userdata will not be set if hitting timeout during login so check for it first
103
+ if (userdata) {
104
+ userdata->timing_out = 1;
105
+ }
89
106
  return_value = INT_TIMEOUT;
90
107
  cancel = 1;
91
108
  break;
@@ -99,6 +116,16 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
99
116
  break;
100
117
  }
101
118
 
119
+ tinytds_errordata error_data = {
120
+ .is_message = 0,
121
+ .cancel = cancel,
122
+ .severity = severity,
123
+ .dberr = dberr,
124
+ .oserr = oserr
125
+ };
126
+ strncpy(error_data.error, dberrstr, ERROR_MSG_SIZE);
127
+ strncpy(error_data.source, source, ERROR_MSG_SIZE);
128
+
102
129
  /*
103
130
  When in non-blocking mode we need to store the exception data to throw it
104
131
  once the blocking call returns, otherwise we will segfault ruby since part
@@ -110,27 +137,9 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
110
137
  dbcancel(dbproc);
111
138
  userdata->dbcancel_sent = 1;
112
139
  }
113
-
114
- /*
115
- If we've already captured an error message, don't overwrite it. This is
116
- here because FreeTDS sends a generic "General SQL Server error" message
117
- that will overwrite the real message. This is not normally a problem
118
- because a ruby exception is normally thrown and we bail before the
119
- generic message can be sent.
120
- */
121
- if (!userdata->nonblocking_error.is_set) {
122
- userdata->nonblocking_error.is_message = 0;
123
- userdata->nonblocking_error.cancel = cancel;
124
- strncpy(userdata->nonblocking_error.error, dberrstr, ERROR_MSG_SIZE);
125
- strncpy(userdata->nonblocking_error.source, source, ERROR_MSG_SIZE);
126
- userdata->nonblocking_error.severity = severity;
127
- userdata->nonblocking_error.dberr = dberr;
128
- userdata->nonblocking_error.oserr = oserr;
129
- userdata->nonblocking_error.is_set = 1;
130
- }
131
-
140
+ push_userdata_error(userdata, error_data);
132
141
  } else {
133
- rb_tinytds_raise_error(dbproc, 0, cancel, dberrstr, source, severity, dberr, oserr);
142
+ rb_tinytds_raise_error(dbproc, error_data);
134
143
  }
135
144
 
136
145
  return return_value;
@@ -142,36 +151,72 @@ int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severi
142
151
 
143
152
  int is_message_an_error = severity > 10 ? 1 : 0;
144
153
 
154
+ tinytds_errordata error_data = {
155
+ .is_message = !is_message_an_error,
156
+ .cancel = is_message_an_error,
157
+ .severity = severity,
158
+ .dberr = msgno,
159
+ .oserr = msgstate
160
+ };
161
+ strncpy(error_data.error, msgtext, ERROR_MSG_SIZE);
162
+ strncpy(error_data.source, source, ERROR_MSG_SIZE);
163
+
145
164
  // See tinytds_err_handler() for info about why we do this
146
165
  if (userdata && userdata->nonblocking) {
147
- if (!userdata->nonblocking_error.is_set) {
148
- userdata->nonblocking_error.is_message = !is_message_an_error;
149
- userdata->nonblocking_error.cancel = is_message_an_error;
150
- strncpy(userdata->nonblocking_error.error, msgtext, ERROR_MSG_SIZE);
151
- strncpy(userdata->nonblocking_error.source, source, ERROR_MSG_SIZE);
152
- userdata->nonblocking_error.severity = severity;
153
- userdata->nonblocking_error.dberr = msgno;
154
- userdata->nonblocking_error.oserr = msgstate;
155
- userdata->nonblocking_error.is_set = 1;
156
- }
166
+ /*
167
+ In the case of non-blocking command batch execution we can receive multiple messages
168
+ (including errors). We keep track of those here so they can be processed once the
169
+ non-blocking call returns.
170
+ */
171
+ push_userdata_error(userdata, error_data);
157
172
 
158
173
  if (is_message_an_error && !dbdead(dbproc) && !userdata->closed) {
159
174
  dbcancel(dbproc);
160
175
  userdata->dbcancel_sent = 1;
161
176
  }
162
177
  } else {
163
- rb_tinytds_raise_error(dbproc, !is_message_an_error, is_message_an_error, msgtext, source, severity, msgno, msgstate);
178
+ rb_tinytds_raise_error(dbproc, error_data);
164
179
  }
165
180
  return 0;
166
181
  }
167
182
 
183
+ /*
184
+ Used by dbsetinterrupt -
185
+ This gets called periodically while waiting on a read from the server
186
+ Right now, we only care about cases where a read from the server is
187
+ taking longer than the specified timeout and dbcancel is not working.
188
+ In these cases we decide that we actually want to handle the interrupt
189
+ */
190
+ static int check_interrupt(void *ptr) {
191
+ GET_CLIENT_USERDATA((DBPROCESS *)ptr);
192
+ return userdata->timing_out;
193
+ }
194
+
195
+ /*
196
+ Used by dbsetinterrupt -
197
+ This gets called if check_interrupt returns TRUE.
198
+ Right now, this is only used in cases where a read from the server is
199
+ taking longer than the specified timeout and dbcancel is not working.
200
+ Return INT_CANCEL to abort the current command batch.
201
+ */
202
+ static int handle_interrupt(void *ptr) {
203
+ GET_CLIENT_USERDATA((DBPROCESS *)ptr);
204
+ if (userdata->timing_out) {
205
+ return INT_CANCEL;
206
+ }
207
+ return INT_CONTINUE;
208
+ }
209
+
168
210
  static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata) {
169
211
  userdata->timing_out = 0;
170
212
  userdata->dbsql_sent = 0;
171
213
  userdata->dbsqlok_sent = 0;
172
214
  userdata->dbcancel_sent = 0;
173
215
  userdata->nonblocking = 0;
174
- userdata->nonblocking_error.is_set = 0;
216
+ // the following is mainly done for consistency since the values are reset accordingly in nogvl_setup/cleanup.
217
+ // the nonblocking_errors array does not need to be freed here. That is done as part of nogvl_cleanup.
218
+ userdata->nonblocking_errors_length = 0;
219
+ userdata->nonblocking_errors_size = 0;
175
220
  }
176
221
 
177
222
  static void rb_tinytds_client_mark(void *ptr) {
@@ -381,6 +426,7 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
381
426
  }
382
427
  }
383
428
  dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata);
429
+ dbsetinterrupt(cwrap->client, check_interrupt, handle_interrupt);
384
430
  cwrap->userdata->closed = 0;
385
431
  if (!NIL_P(database) && (azure != Qtrue)) {
386
432
  dbuse(cwrap->client, StringValueCStr(database));
@@ -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,9 @@ 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;
29
31
  VALUE message_handler;
30
32
  } tinytds_client_userdata;
31
33
 
@@ -40,7 +42,7 @@ typedef struct {
40
42
  rb_encoding *encoding;
41
43
  } tinytds_client_wrapper;
42
44
 
43
- 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);
44
46
 
45
47
  // Lib Macros
46
48
 
File without changes
@@ -3,7 +3,7 @@ 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
5
  OPENSSL_VERSION = ENV['TINYTDS_OPENSSL_VERSION'] || '1.1.1d'
6
- OPENSSL_SOURCE_URI = "https://www.openssl.org/source/old/1.1.1/openssl-#{OPENSSL_VERSION}.tar.gz"
6
+ OPENSSL_SOURCE_URI = "https://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz"
7
7
 
8
8
  FREETDS_VERSION = ENV['TINYTDS_FREETDS_VERSION'] || "1.1.24"
9
9
  FREETDS_VERSION_INFO = Hash.new { |h,k|
@@ -86,26 +86,40 @@ 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
+ short int i;
103
+ for (i = 0; i < userdata->nonblocking_errors_length; i++) {
104
+ tinytds_errordata error = userdata->nonblocking_errors[i];
105
+
106
+ // lookahead to drain any info messages ahead of raising error
107
+ if (!error.is_message) {
108
+ short int j;
109
+ for (j = i; j < userdata->nonblocking_errors_length; j++) {
110
+ tinytds_errordata msg_error = userdata->nonblocking_errors[j];
111
+ if (msg_error.is_message) {
112
+ rb_tinytds_raise_error(client, msg_error);
113
+ }
114
+ }
115
+ }
116
+
117
+ rb_tinytds_raise_error(client, error);
108
118
  }
119
+
120
+ free(userdata->nonblocking_errors);
121
+ userdata->nonblocking_errors_length = 0;
122
+ userdata->nonblocking_errors_size = 0;
109
123
  }
110
124
 
111
125
  static RETCODE nogvl_dbsqlok(DBPROCESS *client) {