RustでTiberiusを使ってSQL Serverのコンテナに接続する

Microsoftが提供しているSQL ServerのDockerイメージを使うと、デフォルト設定では無料のSQL Server Developer Editionのコンテナを起動できる。

learn.microsoft.com

このコンテナに対してRustからクエリを発行したい。Rustでは、SQL ServerのクライアントとしてPrismaが開発しているTiberiusというcrateがあるので、これを使う。

github.com

今回は0.12.2を使う。

SQL Serverのコンテナを起動する

version: '3'

services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      ACCEPT_EULA: Y
      MSSQL_SA_PASSWORD: yourStrong(!)Password
    ports:
      - '1433:1433'
    volumes:
      - sqlvolume:/var/opt/mssql

volumes:
  sqlvolume:

これで起動する。mssql/serverのイメージはamd64アーキテクチャにしか対応していないので、AppleシリコンのmacOSかつDocker Desktopを使う場合、Docker Desktopの"Use Rosetta for x86/amd64 emulation on Apple Silicon"を有効にする必要がある1

sqlcmdから接続確認してもよい。

$ sqlcmd -U sa -P "yourStrong(!)Password"
1>

Tiberiusから接続する

ここから先は、すでにtestデータベースにproductsテーブルがあることにする。

create table products (
    id int identity(1,1) primary key,
    name varchar(255) not null,
    price decimal(10,2) not null
);

TiberiusからSQL Serverのコンテナに接続する。macOSでRustのコードを実行する場合、tiberius crateのfeatureとしてrustlsを有効化する必要があることに注意2。デフォルトのnative-tlsを使うとDB接続時にスタックしてしまう現象が見られた。

具体的には、tiberius crateデフォルトのfeatureをすべて無効化して、rustlsをfeatureとして追加する。Cargo.tomlに次のように設定を追加すればよい(デフォルトで有効なfeatureであるtds73も再度追加している)。

[dependencies]
tiberius = { version = "0.12.2", default-features = false, features = ["tds73", "rustls"] }
# ...

また、今回は非同期ランタイムとしてTokioを使う。cargo add tokio tokio-utilしておく。

SQL Serverのコンテナに接続するコードは次のとおり。

use tiberius::{AuthMethod, Client, Config};
use tokio::net::TcpStream;
use tokio_util::compat::TokioAsyncWriteCompatExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 接続設定を作る。デフォルトでlocalhost:1433に接続する
    let mut config = Config::new();
    config.database("test");
    config.authentication(AuthMethod::sql_server("sa", "yourStrong(!)Password"));
    config.trust_cert(); // TLS証明書を検証しない

    // TCP接続する
    let tcp = TcpStream::connect(config.get_addr()).await?;
    tcp.set_nodelay(true)?;

    // DBに接続する
    let mut client = Client::connect(config, tcp.compat_write()).await?;

    // INSERT
    client
        .query(
            "insert into products (name, price) values (@P1, @P2)",
            &[&"Mac Studio", &1999],
        )
        .await?;

    // SELECT
    let stream = client.query("select top 1 * from products", &[]).await?;
    let row = stream.into_row().await?.unwrap();

    println!("{:#?}", row);

    Ok(())
}

注意点としては、開発用なのでTLS証明書を検証しない設定でないと接続に失敗する。DB設定の作成時にtiberius::Config::trust_certを呼んでおけばよい。

このコードを実行すると、次のように出力される。

Row {
    columns: [
        Column {
            name: "id",
            column_type: Int4,
        },
        Column {
            name: "name",
            column_type: BigVarChar,
        },
        Column {
            name: "price",
            column_type: Decimaln,
        },
    ],
    data: TokenRow {
        data: [
            I32(
                Some(
                    15,
                ),
            ),
            String(
                Some(
                    "Mac Studio",
                ),
            ),
            Numeric(
                Some(
                    1999.00,
                ),
            ),
        ],
    },
    result_index: 0,
}

  1. https://github.com/microsoft/mssql-docker/issues/668#issuecomment-1436802153
  2. TiberiusのREADMEによると、"For some reasons the Security Framework on macOS does not work with SQL Server TLS settings, and on Apple platforms if needing TLS it is recommended to use rustls instead of native-tls"とのこと