rails dbconsoleでSQL Serverに接続しようとしたときのメモ

Rails5から``rails db``を使ってSQL Serverに接続したかったので調べて試した結果をメモ。
結果としては接続はできた。
ただしSQL Server側がDatabaseの切り替えをサポートしていないので接続先のデータベースが指定できないということもあり実用性無し。
そのため``tsql``コマンドで接続することに変更した。

##環境
OS : CentOS7.3
ruby : 2.4.2
Rails : 5.1.4
DB : SQL Server(Azure)のtest-database(事前に作成済み)

## FreeTSDのインストール
レポジトリにあるFreeTDS(0.9系)を使うと接続時に``warning: TinyTds: :use_utf16 option not supported in this version of FreeTDS.``という警告が出る。
それを回避するにはバージョンが1.0以上のFreeTDSをインストールしないと駄目なのでソースからビルドする。

```
$ cd /usr/local/src
$ wget ftp://ftp.freetds.org/pub/freetds/stable/freetds-1.00.9.tar.gz
$ tar zxvf freetds-1.00.9.tar.gz
$ cd  freetds-1.00.9
$ ./configure --prefix=/usr/local/freetds
$ make
$ make install
$ vi /etc/profile.d/freetds.sh
export PATH="/usr/local/freetds/bin:${PATH}"
export LD_LIBRARY_PATH="/usr/local/freetds/lib:${LD_LIBRARY_PATH}"
$ exec ${SHELL} -l
$ tsql -C
Compile-time settings (established with the "configure" script)
                            Version: freetds v1.00.9
             freetds.conf directory: /usr/local/freetds/etc
     MS db-lib source compatibility: no
        Sybase binary compatibility: no
                      Thread safety: yes
                      iconv library: yes
                        TDS version: auto
                              iODBC: no
                           unixodbc: no
              SSPI "trusted" logins: no
                           Kerberos: no
                            OpenSSL: yes
                             GnuTLS: no
                               MARS: no
```

## sqshのインストール
``rails db``で接続するには``sqsh``というコマンドが必要なのでこれもソースからインストールする。

```
$ SYBASE=/usr/local/freetds
$ export SYBASE
$ cd /usr/local/src
$ wget https://jaist.dl.sourceforge.net/project/sqsh/sqsh/sqsh-2.5/sqsh-2.5.16.1.tgz
$ tar zxvf sqsh-2.5.16.1.tgz
$ cd sqsh-2.5/
```

三箇所ほどソースに手を加えないとエラーでこけるので以下のように修正する。
まず一箇所目。
修正前

```c:/usr/local/src/sqsh-2.5/src/cmd_connect.c
/* 860行目付近 */
#if !defined(CS_TDS_50)
        /* Then we use freetds which uses enum instead of defines */
        else if (strcmp(tds_version, "7.0") == 0)
            version = CS_TDS_70;
        else if (strcmp(tds_version, "8.0") == 0)
            version = CS_TDS_80;
```


修正後

```c:/usr/local/src/sqsh-2.5/src/cmd_connect.c
#if !defined(CS_TDS_50)
        /* Then we use freetds which uses enum instead of defines */
        else if (strcmp(tds_version, "7.0") == 0)
            version = CS_TDS_70;
        #else if (strcmp(tds_version, "8.0") == 0)
        #    version = CS_TDS_80;
        else if (strcmp(tds_version, "7.1") == 0)
            version = CS_TDS_71;
        else if (strcmp(tds_version, "7.2") == 0)
            version = CS_TDS_72;
        else if (strcmp(tds_version, "7.3") == 0)
            version = CS_TDS_73;
```

続いて二箇所目。
修正前

```c:/usr/local/src/sqsh-2.5/src/cmd_connect.c
/* 1260行目付近 */
#if !defined(CS_TDS_50)
                case CS_TDS_70:
                    env_set( g_env, "tds_version", "7.0" );
                case CS_TDS_80:
                    env_set( g_env, "tds_version", "8.0" );
                    break;
```

修正後

```c:/usr/local/src/sqsh-2.5/src/cmd_connect.c
#if !defined(CS_TDS_50)
                case CS_TDS_70:
                    env_set( g_env, "tds_version", "7.0" );
                    break;
                #case CS_TDS_80:
                #    env_set( g_env, "tds_version", "8.0" );
                #    break;
                case CS_TDS_71:
                    env_set( g_env, "tds_version", "7.1" );
                    break;
                case CS_TDS_72:
                    env_set( g_env, "tds_version", "7.2" );
                    break;
                case CS_TDS_73:
                    env_set( g_env, "tds_version", "7.3" );
                    break;
```

ラスト
修正前

```c:/usr/local/src/sqsh-2.5/src/dsp_conv.c
/* 664行目付近 */
    /*
     * Take the existing format and strip it down according to the
     * type of date that we are processing and replace the ms
     * field if it exists.
     */
     #if defined(CS_BIGDATETIME_TYPE) && defined(CS_BIGTIME_TYPE)
       if (dt_fmt->datatype == CS_BIGDATETIME_TYPE || dt_fmt->datatype == CS_BIGTIME_TYPE)
          fmt = dsp_datetime_strip( dt_fmt->datatype, conv_fmt, (int) dr.datesecfrac );
       else
     #endif

        fmt = dsp_datetime_strip( dt_fmt->datatype, conv_fmt, (int) dr.datemsecond );
```
修正後


```c:/usr/local/src/sqsh-2.5/src/dsp_conv.c
/* 修正後 */
    /*
     * Take the existing format and strip it down according to the
     * type of date that we are processing and replace the ms
     * field if it exists.
     */
     fmt = dsp_datetime_strip( dt_fmt->datatype, conv_fmt, (int) dr.datemsecond );
```

修正を終えたらビルドしてインストールする。

```
$ ./configure --prefix=/usr/local/sqsh
$ make clean
$ make
$ make install
$ vi /etc/profile.d/sqsh.sh
export PATH="/usr/local/sqsh/bin:${PATH}"
$ exec ${SHELL} -l
$ sqsh -v
sqsh-2.5.16.1
```
これでRailsで接続する準備が出来た。

## Rails側の環境構築
プロジェクトを作成する際にはsqlserverを指定する。
必要なgemは一緒にインストールされるのでGemfileは編集しなくもOK。
ただしfreetdsのインストール先を設定する必要あり。
SQL Serverへの接続はactiverecord-sqlserver-adapterとtiny_tdsが使われる。


```
$ rails new Sample -d sqlserver -B -T --api
$ cd Sample
$ bundle config build.tiny_tds -- --with-freetds-dir=/usr/local/freetds
$ bundle install --path vendor/bundle
```

database.ymlを編集する。

```ruby:config/database.yml
default: &default
  adapter: sqlserver
  username: rails
  password: <%= ENV['DB_PASSWORD'] %>
  host: hostname or IP
  port: 1433  # ポート番号に変更が無いなら省略可
  azure: true # Azure上のSQL Serverにつなぐには必要らしい

development:
  <<: *default
  database: test-database
```

## 接続確認
準備できたので接続できるか試す

```
$ rails db
sqsh-2.5.16.1 Copyright (C) 1995-2001 Scott C. Gray
Portions Copyright (C) 2004-2014 Michael Peppler and Martin Wesdorp
This is free software with ABSOLUTELY NO WARRANTY
For more information type '\warranty'
Msg 40508, Level 16, State 1
Server 'XXXXXXXXXXXXXX', Line 1
USE statement is not supported to switch between databases. Use a new connection to connect to a different database.
[5] XXXXXXXXXXXXXX.master.1>
```
接続はできたっぽいけどtest-databaseには接続できていないっぽい？masterってなんだ・・・。
軽く調べた限りではSQLServer(Azure)側がUSEコマンドをサポートしてないのが原因っぽい。
DB側が未サポートなのはどうにもならないので別の方法で接続することに変更。

## tsqlコマンドで接続確認

``rails db``じゃあ接続できないのでFreeTDSの``tsql``を使って接続する。
まずfreetds.confに接続先の情報を追記

```/usr/local/freetds/etc/freetds.conf
[conn-test]
        host = hostname or IP
        port = 1433
        tds version = 7.0

```

続いてtsqlコマンドを実行。

```
$ tsql -S conn-test -U "ユーザ名" -D "test-database"
Password: 
locale is "en_US.UTF-8"
locale charset is "UTF-8"
using default charset "UTF-8"
Setting test-database as default database in login packet
1> 
```
接続できた。やったね。