tiny_tds 2.1.2 → 3.0.0
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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +470 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +39 -1
- data/Gemfile +0 -7
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +50 -59
- data/Rakefile +15 -10
- data/VERSION +1 -1
- data/docker-compose.yml +34 -0
- data/ext/tiny_tds/client.c +100 -59
- data/ext/tiny_tds/client.h +5 -3
- data/ext/tiny_tds/extconf.rb +38 -16
- data/ext/tiny_tds/extconsts.rb +4 -10
- data/ext/tiny_tds/result.c +52 -45
- data/ext/tiny_tds/tiny_tds_ext.c +4 -1
- data/lib/tiny_tds/gem.rb +1 -6
- data/setup_cimgruby_dev.sh +25 -0
- data/start_dev.sh +21 -0
- data/tasks/native_gem.rake +15 -6
- data/tasks/ports/freetds.rb +1 -6
- data/tasks/ports/libiconv.rb +0 -17
- data/tasks/ports/openssl.rb +2 -18
- data/tasks/ports/recipe.rb +16 -4
- data/tasks/ports.rake +61 -40
- data/test/bin/install-mssql.ps1 +42 -0
- data/test/bin/install-mssqltools.sh +9 -0
- data/test/bin/setup_tinytds_db.sh +7 -0
- data/test/bin/setup_volume_permissions.sh +10 -0
- data/test/client_test.rb +101 -59
- data/test/gem_test.rb +25 -28
- data/test/result_test.rb +130 -182
- data/test/schema_test.rb +366 -388
- data/test/sql/db-create.sql +18 -0
- data/test/sql/db-login.sql +38 -0
- data/test/test_helper.rb +63 -31
- data/test/thread_test.rb +1 -1
- data/tiny_tds.gemspec +10 -7
- metadata +70 -52
- data/.travis.yml +0 -24
- data/BACKERS.md +0 -32
- data/appveyor.yml +0 -51
- data/test/appveyor/dbsetup.ps1 +0 -27
- data/test/appveyor/dbsetup.sql +0 -9
- data/test/bin/setup.sh +0 -19
- data/test/schema/sqlserver_2000.sql +0 -140
- data/test/schema/sqlserver_2005.sql +0 -140
- data/test/schema/sqlserver_2014.sql +0 -140
- data/test/schema/sqlserver_2016.sql +0 -140
- data/test/schema/sybase_ase.sql +0 -138
- /data/test/schema/{sqlserver_2008.sql → sqlserver_2017.sql} +0 -0
data/README.md
CHANGED
@@ -1,21 +1,13 @@
|
|
1
1
|
# TinyTDS - Simple and fast FreeTDS bindings for Ruby using DB-Library.
|
2
2
|
|
3
|
-
* [](https://travis-ci.org/rails-sqlserver/tiny_tds) - TravisCI
|
4
|
-
* [](https://ci.appveyor.com/project/rails-sqlserver/tiny-tds/branch/master) - Appveyor
|
5
3
|
* [](https://rubygems.org/gems/tiny_tds) - Gem Version
|
6
|
-
* [](https://dependencyci.com/github/rails-sqlserver/tiny_tds) - Dependency Status
|
7
4
|
* [](https://gitter.im/rails-sqlserver/activerecord-sqlserver-adapter) - Community
|
8
5
|
|
9
|
-
## Supporting TinyTDS/Adapter
|
10
|
-
|
11
|
-
Both TinyTDS and the Rails SQL Server Adapter are MIT-licensed open source projects. Its ongoing development is made possible thanks to the support by these awesome [backers](https://github.com/rails-sqlserver/tiny_tds/blob/master/BACKERS.md). If you'd like to join them, check out our [Patreon Campaign](https://www.patreon.com/metaskills).
|
12
|
-
|
13
|
-
|
14
6
|
## About TinyTDS
|
15
7
|
|
16
|
-
The TinyTDS gem is meant to serve the extremely common use-case of connecting, querying and iterating over results to Microsoft SQL Server
|
8
|
+
The TinyTDS gem is meant to serve the extremely common use-case of connecting, querying and iterating over results to Microsoft SQL Server from Ruby using the FreeTDS's DB-Library API.
|
17
9
|
|
18
|
-
TinyTDS offers automatic casting to Ruby primitives along with proper encoding support. It converts all SQL Server datatypes to native Ruby primitives while supporting :utc or :local time zones for time-like types. To date it is the only Ruby client library that allows client encoding options, defaulting to UTF-8, while connecting to SQL Server. It also properly encodes all string and binary data.
|
10
|
+
TinyTDS offers automatic casting to Ruby primitives along with proper encoding support. It converts all SQL Server datatypes to native Ruby primitives while supporting :utc or :local time zones for time-like types. To date it is the only Ruby client library that allows client encoding options, defaulting to UTF-8, while connecting to SQL Server. It also properly encodes all string and binary data.
|
19
11
|
|
20
12
|
The API is simple and consists of these classes:
|
21
13
|
|
@@ -26,14 +18,14 @@ The API is simple and consists of these classes:
|
|
26
18
|
|
27
19
|
## Install
|
28
20
|
|
29
|
-
Installing with rubygems should just work. TinyTDS is currently tested on Ruby version 2.
|
21
|
+
Installing with rubygems should just work. TinyTDS is currently tested on Ruby version 2.7.0 and upward.
|
30
22
|
|
31
23
|
```
|
32
24
|
$ gem install tiny_tds
|
33
25
|
```
|
34
26
|
|
35
27
|
If you use Windows, we pre-compile TinyTDS with static versions of FreeTDS and supporting libraries.
|
36
|
-
If you're using RubyInstaller the binary gem will require that devkit is installed and in your path to operate properly.
|
28
|
+
If you're using RubyInstaller, the binary gem will require that devkit is installed and in your path to operate properly.
|
37
29
|
|
38
30
|
On all other platforms, we will find these dependencies. It is recommended that you install the latest FreeTDS via your method of choice. For example, here is how to install FreeTDS on Ubuntu. You might also need the `build-essential` and possibly the `libc6-dev` packages.
|
39
31
|
|
@@ -42,15 +34,15 @@ $ apt-get install wget
|
|
42
34
|
$ apt-get install build-essential
|
43
35
|
$ apt-get install libc6-dev
|
44
36
|
|
45
|
-
$ wget http://www.freetds.org/files/stable/freetds-1.
|
46
|
-
$ tar -xzf freetds-1.
|
47
|
-
$ cd freetds-1.
|
48
|
-
$ ./configure --prefix=/usr/local --with-tdsver=7.
|
37
|
+
$ wget http://www.freetds.org/files/stable/freetds-1.4.23.tar.gz
|
38
|
+
$ tar -xzf freetds-1.4.23.tar.gz
|
39
|
+
$ cd freetds-1.4.23
|
40
|
+
$ ./configure --prefix=/usr/local --with-tdsver=7.4
|
49
41
|
$ make
|
50
42
|
$ make install
|
51
43
|
```
|
52
44
|
|
53
|
-
|
45
|
+
Please read the MiniPortile and/or Windows sections at the end of this file for advanced configuration options past the following:
|
54
46
|
|
55
47
|
```
|
56
48
|
--with-freetds-dir=DIR
|
@@ -60,26 +52,22 @@ If none exist, our native extension will use MiniPortile to install any missing
|
|
60
52
|
|
61
53
|
## Getting Started
|
62
54
|
|
63
|
-
Optionally, Microsoft has done a great job writing
|
64
|
-
|
65
|
-
* [SQL Server on a Mac](https://www.microsoft.com/en-us/sql-server/developer-get-started/ruby/mac)
|
66
|
-
* [SQL Server on RHEL](https://www.microsoft.com/en-us/sql-server/developer-get-started/ruby/rhel)
|
67
|
-
* [SQL Server on Ubuntu](https://www.microsoft.com/en-us/sql-server/developer-get-started/ruby/ubuntu)
|
55
|
+
Optionally, Microsoft has done a great job writing [an article](https://learn.microsoft.com/en-us/sql/connect/ruby/ruby-driver-for-sql-server?view=sql-server-ver16) on how to get started with SQL Server and Ruby using TinyTDS, however, the articles are using outdated versions.
|
68
56
|
|
69
57
|
|
70
58
|
## FreeTDS Compatibility & Configuration
|
71
59
|
|
72
|
-
TinyTDS is developed against
|
60
|
+
TinyTDS is developed against FreeTDs 1.1+. We also test with SQL Server 2017, 2019, 2022 and Azure. Older version of SQL Server or FreeTDS could work, but are not supported.
|
73
61
|
|
74
|
-
|
62
|
+
> [!IMPORTANT]
|
63
|
+
>
|
64
|
+
> Windows users of our pre-compiled native gems need not worry about installing FreeTDS and its dependencies.
|
75
65
|
|
76
|
-
* **Do I need to install FreeTDS?** Yes! Somehow, someway, you are going to need FreeTDS for TinyTDS to compile against.
|
66
|
+
* **Do I need to install FreeTDS?** Yes! Somehow, someway, you are going to need FreeTDS for TinyTDS to compile against.
|
77
67
|
|
78
68
|
* **OK, I am installing FreeTDS, how do I configure it?** Contrary to what most people think, you do not need to specially configure FreeTDS in any way for client libraries like TinyTDS to use it. About the only requirement is that you compile it with libiconv for proper encoding support. FreeTDS must also be compiled with OpenSSL (or the like) to use it with Azure. See the "Using TinyTDS with Azure" section below for more info.
|
79
69
|
|
80
|
-
* **Do I need to configure `--with-tdsver` equal to anything?** Most likely! Technically you should not have
|
81
|
-
|
82
|
-
* **But I want to use TDS version 7.2 for SQL Server 2005 and up!** TinyTDS uses TDS version 7.1 (previously named 8.0) and fully supports all the data types supported by FreeTDS, this includes `varchar(max)` and `nvarchar(max)`. Technically compiling and using TDS version 7.2 with FreeTDS is not supported. But this does not mean those data types will not work. I know, it's confusing If you want to learn more, read this thread. http://lists.ibiblio.org/pipermail/freetds/2011q3/027306.html
|
70
|
+
* **Do I need to configure `--with-tdsver` equal to anything?** Most likely! Technically you should not have to. This is only a default for clients/configs that do not specify what TDS version they want to use. We are currently having issues with passing down a TDS version with the login bit. Till we get that fixed, if you are not using a freetds.conf or a TDSVER environment variable, then make sure to use 7.4.
|
83
71
|
|
84
72
|
* **I want to configure FreeTDS using `--enable-msdblib` and/or `--enable-sybase-compat` so it works for my database. Cool?** It's a waste of time and totally moot! Client libraries like TinyTDS define their own C structure names where they diverge from Sybase to SQL Server. Technically we use the MSDBLIB structures which does not mean we only work with that database vs Sybase. These configs are just a low level default for C libraries that do not define what they want. So I repeat, you do not NEED to use any of these, nor will they hurt anything since we control what C structure names we use internally!
|
85
73
|
|
@@ -88,7 +76,7 @@ TinyTDS is developed against FreeTDS 0.95, 0.99, and 1.0 current. Our default an
|
|
88
76
|
|
89
77
|
Our goal is to support every SQL Server data type and covert it to a logical Ruby object. When dates or times are returned, they are instantiated to either `:utc` or `:local` time depending on the query options. Only [datetimeoffset] types are excluded. All strings are associated the to the connection's encoding and all binary data types are associated to Ruby's `ASCII-8BIT/BINARY` encoding.
|
90
78
|
|
91
|
-
Below is a list of the data types we support when using the 7.
|
79
|
+
Below is a list of the data types we support when using the 7.4 TDS protocol version. Using a lower protocol version will result in these types being returned as strings.
|
92
80
|
|
93
81
|
* [date]
|
94
82
|
* [datetime2]
|
@@ -115,7 +103,7 @@ Creating a new client takes a hash of options. For valid iconv encoding options,
|
|
115
103
|
* :appname - Short string seen in SQL Servers process/activity window.
|
116
104
|
* :tds_version - TDS version. Defaults to "7.3".
|
117
105
|
* :login_timeout - Seconds to wait for login. Default to 60 seconds.
|
118
|
-
* :timeout - Seconds to wait for a response to a SQL command. Default 5 seconds.
|
106
|
+
* :timeout - Seconds to wait for a response to a SQL command. Default 5 seconds. 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).
|
119
107
|
* :encoding - Any valid iconv value like CP1251 or ISO-8859-1. Default UTF-8.
|
120
108
|
* :azure - Pass true to signal that you are connecting to azure.
|
121
109
|
* :contained - Pass true to signal that you are connecting with a contained database user.
|
@@ -327,9 +315,13 @@ By default row caching is turned on because the SQL Server adapter for ActiveRec
|
|
327
315
|
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.
|
328
316
|
|
329
317
|
|
318
|
+
## Timeout Error Handling
|
319
|
+
|
320
|
+
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.
|
321
|
+
|
330
322
|
## Binstubs
|
331
323
|
|
332
|
-
The TinyTDS gem uses binstub wrappers which mirror compiled [FreeTDS Utilities](
|
324
|
+
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.
|
333
325
|
|
334
326
|
* tsql - Used to test connections and debug compile time settings.
|
335
327
|
* defncopy - Used to dump schema structures.
|
@@ -393,24 +385,16 @@ Please read our [thread_test.rb](https://github.com/rails-sqlserver/tiny_tds/blo
|
|
393
385
|
|
394
386
|
## Emoji Support 😍
|
395
387
|
|
396
|
-
This is possible
|
397
|
-
|
398
|
-
```ini
|
399
|
-
[global]
|
400
|
-
use utf-16 = true
|
401
|
-
```
|
402
|
-
|
403
|
-
The default is true and since FreeTDS v1.0 would do this as well.
|
404
|
-
|
388
|
+
This is possible. Since FreeTDS v1.0, utf-16 is enabled by default and supported by tiny_tds. You can toggle it by using `use_utf16` when establishing the connection.
|
405
389
|
|
406
390
|
## Compiling Gems for Windows
|
407
391
|
|
408
|
-
For the convenience of Windows users, TinyTDS ships pre-compiled gems for
|
392
|
+
For the convenience of Windows users, TinyTDS ships pre-compiled gems for supported versions of Ruby on Windows. In order to generate these gems, [rake-compiler-dock](https://github.com/rake-compiler/rake-compiler-dock) is used. This project provides several [Docker images](https://registry.hub.docker.com/u/larskanis/) with rvm, cross-compilers and a number of different target versions of Ruby.
|
409
393
|
|
410
394
|
Run the following rake task to compile the gems for Windows. This will check the availability of [Docker](https://www.docker.com/) (and boot2docker on Windows or OS-X) and will give some advice for download and installation. When docker is running, it will download the docker image (once-only) and start the build:
|
411
395
|
|
412
396
|
```
|
413
|
-
$ rake gem:
|
397
|
+
$ rake gem:native
|
414
398
|
```
|
415
399
|
|
416
400
|
The compiled gems will exist in `./pkg` directory.
|
@@ -424,30 +408,27 @@ First, clone the repo using the command line or your Git GUI of choice.
|
|
424
408
|
$ git clone git@github.com:rails-sqlserver/tiny_tds.git
|
425
409
|
```
|
426
410
|
|
427
|
-
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
|
411
|
+
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.
|
428
412
|
|
429
413
|
```shell
|
430
|
-
$
|
414
|
+
$ docker-compose up -d
|
431
415
|
```
|
432
416
|
|
433
|
-
This will download
|
417
|
+
This will download the official SQL Server for Linux Docker image from [Microsoft](https://hub.docker.com/r/microsoft/mssql-server-linux/). 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:
|
434
418
|
|
435
419
|
```shell
|
436
|
-
$ docker
|
437
|
-
$ docker
|
420
|
+
$ docker network create main-network
|
421
|
+
$ docker pull mcr.microsoft.com/mssql/server:2017-latest
|
422
|
+
$ docker run -p 1433:1433 -d --name sqlserver --network main-network mcr.microsoft.com/mssql/server:2017-latest
|
423
|
+
$ docker pull shopify/toxiproxy
|
424
|
+
$ docker run -p 8474:8474 -p 1234:1234 -d --name toxiproxy --network main-network shopify/toxiproxy
|
438
425
|
```
|
439
426
|
|
440
|
-
|
427
|
+
Make sure to run these SQL scripts as SA to get the test database and user installed. If needed, install [sqlcmd as described by Microsoft for your platform](https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility?view=sql-server-ver16&tabs=go%2Clinux&pivots=cs1-bash).
|
441
428
|
|
442
|
-
```
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
```sql
|
447
|
-
CREATE LOGIN [tinytds] WITH PASSWORD = '', CHECK_POLICY = OFF, DEFAULT_DATABASE = [tinytdstest];
|
448
|
-
USE [tinytdstest];
|
449
|
-
CREATE USER [tinytds] FOR LOGIN [tinytds];
|
450
|
-
EXEC sp_addrolemember N'db_owner', N'tinytds';
|
429
|
+
```shell
|
430
|
+
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P super01S3cUr3 -i ./test/sql/db-create.sql
|
431
|
+
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P super01S3cUr3 -i ./test/sql/db-login.sql
|
451
432
|
```
|
452
433
|
|
453
434
|
From here you can build and run tests against an installed version of FreeTDS.
|
@@ -461,11 +442,21 @@ Examples us using enviornment variables to customize the test task.
|
|
461
442
|
|
462
443
|
```
|
463
444
|
$ rake TINYTDS_UNIT_DATASERVER=mydbserver
|
464
|
-
$ rake TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=
|
445
|
+
$ rake TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=sqlserver_2017
|
465
446
|
$ rake TINYTDS_UNIT_HOST=mydb.host.net TINYTDS_SCHEMA=sqlserver_azure
|
466
|
-
$ rake TINYTDS_UNIT_HOST=mydb.host.net TINYTDS_UNIT_PORT=5000 TINYTDS_SCHEMA=sybase_ase
|
467
447
|
```
|
468
448
|
|
449
|
+
## Docker Builds
|
450
|
+
|
451
|
+
If you use a [multi stage](https://docs.docker.com/develop/develop-images/multistage-build/) Docker build to assemble your gems in one phase and then copy your app and gems
|
452
|
+
into another, lighter, container without build tools you will need to make sure you tell the OS how to find dependencies for TinyTDS.
|
453
|
+
|
454
|
+
After you have built and installed FreeTDS it will normally place library files in `/usr/local/lib`. When TinyTDS builds native extensions,
|
455
|
+
it [already knows to look here](https://github.com/rails-sqlserver/tiny_tds/blob/master/ext/tiny_tds/extconf.rb#L31) but if you copy your app to a new container that link will be broken.
|
456
|
+
|
457
|
+
Set the LD_LIBRARY_PATH environment variable `export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}` and run `ldconfig`. If you run `ldd tiny_tds.so` you should not see any broken links. Make
|
458
|
+
sure you also copied in the library dependencies from your build container with a command like `COPY --from=builder /usr/local/lib /usr/local/lib`.
|
459
|
+
|
469
460
|
## Help & Support
|
470
461
|
|
471
462
|
* Github Source: http://github.com/rails-sqlserver/tiny_tds
|
data/Rakefile
CHANGED
@@ -6,19 +6,29 @@ require 'rake/extensiontask'
|
|
6
6
|
require_relative './ext/tiny_tds/extconsts'
|
7
7
|
|
8
8
|
SPEC = Gem::Specification.load(File.expand_path('../tiny_tds.gemspec', __FILE__))
|
9
|
+
|
10
|
+
ruby_cc_ucrt_versions = "3.3.0:3.2.0:3.1.0".freeze
|
11
|
+
ruby_cc_mingw32_versions = "3.0.0:2.7.0".freeze
|
12
|
+
|
9
13
|
GEM_PLATFORM_HOSTS = {
|
10
|
-
'
|
11
|
-
|
14
|
+
'x64-mingw32' => {
|
15
|
+
host: 'x86_64-w64-mingw32',
|
16
|
+
ruby_versions: ruby_cc_mingw32_versions
|
17
|
+
},
|
18
|
+
'x64-mingw-ucrt' => {
|
19
|
+
host: 'x86_64-w64-mingw32',
|
20
|
+
ruby_versions: ruby_cc_ucrt_versions
|
21
|
+
},
|
12
22
|
}
|
13
23
|
|
14
24
|
# Add our project specific files to clean for a rebuild
|
15
25
|
CLEAN.include FileList["{ext,lib}/**/*.{so,#{RbConfig::CONFIG['DLEXT']},o}"],
|
16
|
-
|
26
|
+
FileList["exe/*"]
|
17
27
|
|
18
28
|
# Clobber all our temp files and ports files including .install files
|
19
29
|
# and archives
|
20
30
|
CLOBBER.include FileList["tmp/**/*"],
|
21
|
-
|
31
|
+
FileList["ports/**/*"].exclude(%r{^ports/archives})
|
22
32
|
|
23
33
|
Dir['tasks/*.rake'].sort.each { |f| load f }
|
24
34
|
|
@@ -32,15 +42,11 @@ Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext|
|
|
32
42
|
# The fat binary gem doesn't depend on the freetds package, since it bundles the library.
|
33
43
|
spec.metadata.delete('msys2_mingw_dependencies')
|
34
44
|
|
35
|
-
platform_host_map = GEM_PLATFORM_HOSTS
|
36
|
-
gemplat = spec.platform.to_s
|
37
|
-
host = platform_host_map[gemplat]
|
38
|
-
|
39
45
|
# We don't need the sources in a fat binary gem
|
40
46
|
spec.files = spec.files.reject { |f| f =~ %r{^ports\/archives/} }
|
41
47
|
|
42
48
|
# Make sure to include the ports binaries and libraries
|
43
|
-
spec.files += FileList["ports/#{
|
49
|
+
spec.files += FileList["ports/#{spec.platform.to_s}/**/**/{bin,lib}/*"].exclude do |f|
|
44
50
|
File.directory? f
|
45
51
|
end
|
46
52
|
|
@@ -50,4 +56,3 @@ end
|
|
50
56
|
|
51
57
|
task build: [:clean, :compile]
|
52
58
|
task default: [:build, :test]
|
53
|
-
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.0
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
version: '3'
|
2
|
+
|
3
|
+
networks:
|
4
|
+
main-network:
|
5
|
+
|
6
|
+
services:
|
7
|
+
mssql:
|
8
|
+
image: mcr.microsoft.com/mssql/server:${MSSQL_VERSION:-2017}-latest
|
9
|
+
container_name: sqlserver
|
10
|
+
environment:
|
11
|
+
ACCEPT_EULA: Y
|
12
|
+
MSSQL_SA_PASSWORD: super01S3cUr3
|
13
|
+
ports:
|
14
|
+
- "1433:1433"
|
15
|
+
network_mode: "host"
|
16
|
+
|
17
|
+
toxiproxy:
|
18
|
+
image: shopify/toxiproxy
|
19
|
+
container_name: toxiproxy
|
20
|
+
command: '/toxiproxy -host=127.0.0.1'
|
21
|
+
network_mode: "host"
|
22
|
+
|
23
|
+
cimgruby:
|
24
|
+
image: "cimg/ruby:${RUBY_VERSION:-2.7}"
|
25
|
+
container_name: cimg_ruby
|
26
|
+
environment:
|
27
|
+
TESTOPTS: '-v'
|
28
|
+
TINYTDS_UNIT_HOST: '127.0.0.1'
|
29
|
+
SA_PASSWORD: super01S3cUr3
|
30
|
+
TOXIPROXY_HOST: '127.0.0.1'
|
31
|
+
command: tail -F anything
|
32
|
+
volumes:
|
33
|
+
- .:/home/circleci/project
|
34
|
+
network_mode: "host"
|
data/ext/tiny_tds/client.c
CHANGED
@@ -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,
|
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,
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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,
|
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
|
-
|
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) {
|
@@ -187,6 +232,7 @@ static void rb_tinytds_client_free(void *ptr) {
|
|
187
232
|
dbloginfree(cwrap->login);
|
188
233
|
if (cwrap->client && !cwrap->closed) {
|
189
234
|
dbclose(cwrap->client);
|
235
|
+
cwrap->client = NULL;
|
190
236
|
cwrap->closed = 1;
|
191
237
|
cwrap->userdata->closed = 1;
|
192
238
|
}
|
@@ -218,6 +264,7 @@ static VALUE rb_tinytds_close(VALUE self) {
|
|
218
264
|
GET_CLIENT_WRAPPER(self);
|
219
265
|
if (cwrap->client && !cwrap->closed) {
|
220
266
|
dbclose(cwrap->client);
|
267
|
+
cwrap->client = NULL;
|
221
268
|
cwrap->closed = 1;
|
222
269
|
cwrap->userdata->closed = 1;
|
223
270
|
}
|
@@ -252,8 +299,7 @@ static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
|
|
252
299
|
REQUIRE_OPEN_CLIENT(cwrap);
|
253
300
|
dbcmd(cwrap->client, StringValueCStr(sql));
|
254
301
|
if (dbsqlsend(cwrap->client) == FAIL) {
|
255
|
-
|
256
|
-
return Qfalse;
|
302
|
+
rb_raise(cTinyTdsError, "failed dbsqlsend() function");
|
257
303
|
}
|
258
304
|
cwrap->userdata->dbsql_sent = 1;
|
259
305
|
result = rb_tinytds_new_result_obj(cwrap);
|
@@ -357,17 +403,15 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
|
357
403
|
#endif
|
358
404
|
}
|
359
405
|
}
|
360
|
-
|
361
|
-
|
362
|
-
if (use_utf16 == Qfalse) { DBSETLUTF16(cwrap->login, 0); }
|
363
|
-
#else
|
364
|
-
if (use_utf16 == Qtrue || use_utf16 == Qfalse) {
|
365
|
-
rb_warning("TinyTds: Please consider upgrading to FreeTDS 0.99 or higher for better unicode support.\n");
|
366
|
-
}
|
367
|
-
#endif
|
406
|
+
if (use_utf16 == Qtrue) { DBSETLUTF16(cwrap->login, 1); }
|
407
|
+
if (use_utf16 == Qfalse) { DBSETLUTF16(cwrap->login, 0); }
|
368
408
|
|
369
409
|
cwrap->client = dbopen(cwrap->login, StringValueCStr(dataserver));
|
370
410
|
if (cwrap->client) {
|
411
|
+
if (dbtds(cwrap->client) < 11) {
|
412
|
+
rb_raise(cTinyTdsError, "connecting with a TDS version older than 7.3!");
|
413
|
+
}
|
414
|
+
|
371
415
|
VALUE transposed_encoding, timeout_string;
|
372
416
|
|
373
417
|
cwrap->closed = 0;
|
@@ -381,17 +425,14 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
|
381
425
|
}
|
382
426
|
}
|
383
427
|
dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata);
|
428
|
+
dbsetinterrupt(cwrap->client, check_interrupt, handle_interrupt);
|
384
429
|
cwrap->userdata->closed = 0;
|
385
430
|
if (!NIL_P(database) && (azure != Qtrue)) {
|
386
431
|
dbuse(cwrap->client, StringValueCStr(database));
|
387
432
|
}
|
388
433
|
transposed_encoding = rb_funcall(cTinyTdsClient, intern_transpose_iconv_encoding, 1, charset);
|
389
434
|
cwrap->encoding = rb_enc_find(StringValueCStr(transposed_encoding));
|
390
|
-
|
391
|
-
cwrap->identity_insert_sql = "SELECT CAST(@@IDENTITY AS bigint) AS Ident";
|
392
|
-
} else {
|
393
|
-
cwrap->identity_insert_sql = "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident";
|
394
|
-
}
|
435
|
+
cwrap->identity_insert_sql = "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident";
|
395
436
|
}
|
396
437
|
return self;
|
397
438
|
}
|
data/ext/tiny_tds/client.h
CHANGED
@@ -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
|
-
|
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,
|
45
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error);
|
44
46
|
|
45
47
|
// Lib Macros
|
46
48
|
|