Skip to content

Commit

Permalink
Remove code no longer relevant when dropping support for SQL Server <…
Browse files Browse the repository at this point in the history
… 2017
  • Loading branch information
andyundso committed Mar 28, 2024
1 parent a4ed939 commit 13f9c93
Show file tree
Hide file tree
Showing 13 changed files with 514 additions and 1,391 deletions.
44 changes: 18 additions & 26 deletions README.md
Expand Up @@ -6,9 +6,9 @@

## About TinyTDS

The TinyTDS gem is meant to serve the extremely common use-case of connecting, querying and iterating over results to Microsoft SQL Server or Sybase databases from Ruby using the FreeTDS's DB-Library API.
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.

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. The motivation for TinyTDS is to become the de-facto low level connection mode for the SQL Server Adapter for ActiveRecord.
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.

The API is simple and consists of these classes:

Expand All @@ -19,14 +19,14 @@ The API is simple and consists of these classes:

## Install

Installing with rubygems should just work. TinyTDS is currently tested on Ruby version 2.0.0 and upward.
Installing with rubygems should just work. TinyTDS is currently tested on Ruby version 2.7.0 and upward.

```
$ gem install tiny_tds
```

If you use Windows, we pre-compile TinyTDS with static versions of FreeTDS and supporting libraries.
If you're using RubyInstaller the binary gem will require that devkit is installed and in your path to operate properly.
If you're using RubyInstaller, the binary gem will require that devkit is installed and in your path to operate properly.

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.

Expand All @@ -35,10 +35,10 @@ $ apt-get install wget
$ apt-get install build-essential
$ apt-get install libc6-dev

$ wget http://www.freetds.org/files/stable/freetds-1.1.24.tar.gz
$ tar -xzf freetds-1.1.24.tar.gz
$ cd freetds-1.1.24
$ ./configure --prefix=/usr/local --with-tdsver=7.3
$ wget http://www.freetds.org/files/stable/freetds-1.4.10.tar.gz
$ tar -xzf freetds-1.4.10.tar.gz
$ cd freetds-1.4.10
$ ./configure --prefix=/usr/local --with-tdsver=7.4
$ make
$ make install
```
Expand All @@ -53,26 +53,22 @@ Please read the MiniPortile and/or Windows sections at the end of this file for

## Getting Started

Optionally, Microsoft has done a great job writing some articles on how to get started with SQL Server and Ruby using TinyTDS. Please checkout one of the following posts that match your platform.

* [SQL Server on a Mac](https://www.microsoft.com/en-us/sql-server/developer-get-started/ruby/mac)
* [SQL Server on RHEL](https://www.microsoft.com/en-us/sql-server/developer-get-started/ruby/rhel)
* [SQL Server on Ubuntu](https://www.microsoft.com/en-us/sql-server/developer-get-started/ruby/ubuntu)
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.


## FreeTDS Compatibility & Configuration

TinyTDS is developed against FreeTDS 0.95, 0.99, and 1.0 current. Our default and recommended is 1.0. We also test with SQL Server 2008, 2014, and Azure. However, usage of TinyTDS with SQL Server 2000 or 2005 should be just fine. Below are a few QA style notes about installing FreeTDS.
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.

**NOTE:** Windows users of our pre-compiled native gems need not worry about installing FreeTDS and its dependencies.
> [!IMPORTANT]
>
> Windows users of our pre-compiled native gems need not worry about installing FreeTDS and its dependencies.
* **Do I need to install FreeTDS?** Yes! Somehow, someway, you are going to need FreeTDS for TinyTDS to compile against.

* **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.

* **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.1.

* **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
* **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.

* **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!

Expand All @@ -81,7 +77,7 @@ TinyTDS is developed against FreeTDS 0.95, 0.99, and 1.0 current. Our default an

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.

Below is a list of the data types we support when using the 7.3 TDS protocol version. Using a lower protocol version will result in these types being returned as strings.
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.

* [date]
* [datetime2]
Expand All @@ -108,7 +104,7 @@ Creating a new client takes a hash of options. For valid iconv encoding options,
* :appname - Short string seen in SQL Servers process/activity window.
* :tds_version - TDS version. Defaults to "7.3".
* :login_timeout - Seconds to wait for login. Default to 60 seconds.
* :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).
* :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).
* :encoding - Any valid iconv value like CP1251 or ISO-8859-1. Default UTF-8.
* :azure - Pass true to signal that you are connecting to azure.
* :contained - Pass true to signal that you are connecting with a contained database user.
Expand Down Expand Up @@ -390,16 +386,13 @@ Please read our [thread_test.rb](https://github.com/rails-sqlserver/tiny_tds/blo

## Emoji Support 😍

This is possible using FreeTDS version 0.95 or higher. You must use the `use_utf16` login option or add the following config to your `freetds.conf` in either the global section or a specfic dataserver. If you are on Windows, the default location for your conf file will be in `C:\Sites`.
This is possible. You must use the `use_utf16` login option or add the following config to your `freetds.conf` in either the global section or a specfic dataserver. If you are on Windows, the default location for your conf file will be in `C:\Sites`.

```ini
[global]
use utf-16 = true
```

The default is true and since FreeTDS v1.0 would do this as well.


## Compiling Gems for Windows

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.
Expand Down Expand Up @@ -461,9 +454,8 @@ Examples us using enviornment variables to customize the test task.

```
$ rake TINYTDS_UNIT_DATASERVER=mydbserver
$ rake TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=sqlserver_2008
$ rake TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=sqlserver_2016
$ rake TINYTDS_UNIT_HOST=mydb.host.net TINYTDS_SCHEMA=sqlserver_azure
$ rake TINYTDS_UNIT_HOST=mydb.host.net TINYTDS_UNIT_PORT=5000 TINYTDS_SCHEMA=sybase_ase
```

## Docker Builds
Expand Down
10 changes: 2 additions & 8 deletions ext/tiny_tds/client.c
Expand Up @@ -404,14 +404,8 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
#endif
}
}
#ifdef DBSETUTF16
if (use_utf16 == Qtrue) { DBSETLUTF16(cwrap->login, 1); }
if (use_utf16 == Qfalse) { DBSETLUTF16(cwrap->login, 0); }
#else
if (use_utf16 == Qtrue || use_utf16 == Qfalse) {
rb_warning("TinyTds: Please consider upgrading to FreeTDS 0.99 or higher for better unicode support.\n");
}
#endif
if (use_utf16 == Qtrue) { DBSETLUTF16(cwrap->login, 1); }
if (use_utf16 == Qfalse) { DBSETLUTF16(cwrap->login, 0); }

cwrap->client = dbopen(cwrap->login, StringValueCStr(dataserver));
if (cwrap->client) {
Expand Down
54 changes: 23 additions & 31 deletions ext/tiny_tds/result.c
Expand Up @@ -302,38 +302,30 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
case 41: // SYBMSTIME
case 42: // SYBMSDATETIME2
case 43: { // SYBMSDATETIMEOFFSET
#ifdef DBVERSION_73
if (dbtds(rwrap->client) >= DBTDS_7_3) {
DBDATEREC2 dr2;
dbanydatecrack(rwrap->client, &dr2, coltype, data);
switch(coltype) {
case 40: { // SYBMSDATE
val = rb_funcall(cDate, intern_new, 3, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day));
break;
}
case 41: { // SYBMSTIME
VALUE rational_nsec = rb_Rational(INT2NUM(dr2.nanosecond), opt_onek);
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(1900), INT2NUM(1), INT2NUM(1), INT2NUM(dr2.hour), INT2NUM(dr2.minute), INT2NUM(dr2.second), rational_nsec);
break;
}
case 42: { // SYBMSDATETIME2
VALUE rational_nsec = rb_Rational(INT2NUM(dr2.nanosecond), opt_onek);
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day), INT2NUM(dr2.hour), INT2NUM(dr2.minute), INT2NUM(dr2.second), rational_nsec);
break;
}
case 43: { // SYBMSDATETIMEOFFSET
long long numerator = ((long)dr2.second * (long long)1000000000) + (long long)dr2.nanosecond;
VALUE rational_sec = rb_Rational(LL2NUM(numerator), opt_onebil);
val = rb_funcall(rb_cTime, intern_new, 7, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day), INT2NUM(dr2.hour), INT2NUM(dr2.minute), rational_sec, INT2NUM(dr2.tzone*60));
break;
}
}
} else {
val = ENCODED_STR_NEW(data, data_len);
DBDATEREC2 dr2;
dbanydatecrack(rwrap->client, &dr2, coltype, data);
switch(coltype) {
case 40: { // SYBMSDATE
val = rb_funcall(cDate, intern_new, 3, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day));
break;
}
#else
val = ENCODED_STR_NEW(data, data_len);
#endif
case 41: { // SYBMSTIME
VALUE rational_nsec = rb_Rational(INT2NUM(dr2.nanosecond), opt_onek);
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(1900), INT2NUM(1), INT2NUM(1), INT2NUM(dr2.hour), INT2NUM(dr2.minute), INT2NUM(dr2.second), rational_nsec);
break;
}
case 42: { // SYBMSDATETIME2
VALUE rational_nsec = rb_Rational(INT2NUM(dr2.nanosecond), opt_onek);
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day), INT2NUM(dr2.hour), INT2NUM(dr2.minute), INT2NUM(dr2.second), rational_nsec);
break;
}
case 43: { // SYBMSDATETIMEOFFSET
long long numerator = ((long)dr2.second * (long long)1000000000) + (long long)dr2.nanosecond;
VALUE rational_sec = rb_Rational(LL2NUM(numerator), opt_onebil);
val = rb_funcall(rb_cTime, intern_new, 7, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day), INT2NUM(dr2.hour), INT2NUM(dr2.minute), rational_sec, INT2NUM(dr2.tzone*60));
break;
}
}
break;
}
case SYBCHAR:
Expand Down
7 changes: 1 addition & 6 deletions tasks/ports/freetds.rb
Expand Up @@ -15,12 +15,7 @@ def configure_defaults

opts << '--with-pic'
opts << '--disable-odbc'

if version =~ /0\.91/
opts << '--with-tdsver=7.1'
else
opts << '--with-tdsver=7.3'
end
opts << '--with-tdsver=7.3'

if windows?
opts << '--sysconfdir=C:/Sites'
Expand Down
7 changes: 2 additions & 5 deletions test/client_test.rb
Expand Up @@ -24,10 +24,7 @@ class ClientTest < TinyTds::TestCase
end

it 'has getters for the tds version information (brittle since conf takes precedence)' do
if sybase_ase?
assert_equal 7, @client.tds_version
assert_equal 'DBTDS_5_0 - 5.0 SQL Server', @client.tds_version_info
elsif @client.tds_73?
if @client.tds_73?
assert_equal 11, @client.tds_version
assert_equal 'DBTDS_7_3 - Microsoft SQL Server 2008', @client.tds_version_info
else
Expand Down Expand Up @@ -199,7 +196,7 @@ class ClientTest < TinyTds::TestCase
options = connection_options :username => 'willnotwork'
action = lambda { new_connection(options) }
assert_raise_tinytds_error(action) do |e|
assert_equal sybase_ase? ? 4002 : 18456, e.db_error_number
assert_equal 18456, e.db_error_number
assert_equal 14, e.severity
assert_match %r{login failed}i, e.message, 'ignore if non-english test run'
end
Expand Down

0 comments on commit 13f9c93

Please sign in to comment.