(WIP)mssql(SQLServer)のDBから、Rustのstructを自動生成する
全体
Rustで、mssql(SQLServer)へのCRUD処理を行う要件があります。
prisma周りのモジュール群が、Rustで開発されていたり、
Rustのprisma clientなどもあり、mssqlに限らずRustでデータベース処理を行うにあたって
色々と親和性がありそうなので、この辺りを掘っていきます。
関連リンク
▼Prisma
▼Prisma Client Rust ▼SeaORMのsql server版(202409時点では非公開)ざっくりの流れ
PrismaはNodeのツール(?)というイメージがあり、これまで使ったことが無かったので、
説明を読みながら進めていきます。
スタート時点での手順イメージ
- Prismaで、mssqlのDBに接続し、スキーマを自動的にインポートしてPrisma Schemaファイル (schema.prisma) を生成する
- prisma-client-rust を用いて、Prisma Schemaファイルから、Rustのコードを生成する
- あとはサンプル見ながらCRUDする
なんとなく1,2まではこんな感じのイメージ。
3以降は、mssql接続だとtiberiusという、これまたPrisma管理化のmssql専用ドライバcrateがあったり、
ORMやクエリビルダとしての各種crate(SeaquelとかSeaORMとか)を上手く使って処理できるのかな?ぐらいな感じ。
Dieselは一気通貫のため他ツールとの連携に向いて無さそう(かつmssqlは非対応)、
sqlxは、明示的にv0.6以降mssql対応していないと記載があるので、連携はできるけど難しいのかな?
このような認識で進めていきます。
まず、chatGPTに聞いてみる
prismaを使って、mssqlのデータベース構成を元に、構成ファイルを生成することはできる?
node処理系を使って進めていくようなのだが、
普段はbunを使っているので、prismaに対応しているか調査したところ、問題なさそう。
という訳で、bunを使ったコマンドに置き換えて、処理を進めていく。
1. Prisma CLIのインストール
mkdir prisma-app
cd prisma-app
bun init
bun add -d prisma
bun add @prisma/client
bunx prisma init --datasource-provider sqlserver
これで、prismaの構成ファイルが生成される。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
2. Prismaのデータベース接続設定
prisma/schema.prisma
ファイルは上記の通り自動的に生成されているので、
特に変更は不要です。
デフォルトでは、DATABASE_URLを環境変数から取得するようになっているので、
以下の形式のURLを事前にDATABASE_URLにセットしておきます。
フォーマットは、以下のような形です。
sqlserver://username:password@host:port/database
sqlserver://HOST[:PORT];database=DATABASE;user={MyServer/MyUser};password={ThisIsA:SecurePassword;};encrypt=true
今回、私の環境では、ローカル環境に接続するため、以下のようにしています。
例:
sqlserver://localhost:1433;database=SAMPLEDB;user=sa;password=Zaq12wsx;encrypt=true;TrustServerCertificate=true;
3. データベースのスキーマをインポート
次に、Prismaのスキーマを生成するために、以下のコマンドを実行します。
bunx prisma db pull
接続情報が正しければ、処理が走り、
このコマンドは、指定されたデータベースからすべてのテーブルとその構造をスキャンし、schema.prisma
ファイルにスキーマを自動的に追加します。
4. スキーマの確認と修正
schema.prisma
に生成されたスキーマを確認し、必要に応じて修正します。Prismaのスキーマファイルでは、リレーションシップやデータ型などを細かく設定できます。
5. Prisma Clientの生成
スキーマを定義したら、次のコマンドを実行してPrisma Clientを生成します。
bunx prisma generate
問題発覚
今回扱うDBは、テーブル名、フィールド名に日本語が含まれており、
スキーマ情報は生成されたのですが、
テーブル名に含まれる日本語を自動的に削除、
フィールド名に日本語が含まれる場合、コメントアウトされてしまいました。
model TEST________ {
/// This field was commented out because of an invalid name. Please provide a valid one that matches [a-zA-Z][a-zA-Z0-9_]*
// フィールド1 String @map("フィールド1") @db.VarChar(2)
/// This field was commented out because of an invalid name. Please provide a valid one that matches [a-zA-Z][a-zA-Z0-9_]*
// ふぃーるど2 String @map("ふぃーるど2") @db.VarChar(9)
@@map("TESTテーブル")
}
Rustの場合、このようなマルチバイトのテーブル、フィールド名はコンパイルエラーにはならないのですが、
struct あいうえお {
てすと: String,
abc: i32,
}
Prisma用のschemaデータが正しく生成されないと、prisma-client-rustも使えません。
というわけで、Prismaを使う以上は、条件にあった名称に変更しないといけない様です。
なので、生成されたファイルを、強制的にスクリプトで編集するという強引なやり方を試します。
(@map で、もともとの名称とのマッピングはされているようなので、そこは安心)
schemaファイルの書き換え
ファイルを見ると、
@@map(xxx)で、テーブル名のマッピングが、
@map(xxx)で、フィールド名のマッピングがされているようです。
なので、
- @@map(xxx)を探して、元のテーブル名の頭にTを付けた名前に変更したものに、model名を更新する
- コメントアウトされているフィールドのコメントアウトを外す
これをスクリプト書いて処理します。
use std::io::{self, Write};
use std::fs::{self, OpenOptions};
fn main() {
update_japanese_tables().unwrap();
}
fn update_japanese_tables() -> io::Result<()> {
// 読み込むファイル名
let input_file_path = "./schema.prisma";
// 書き込むファイル名
let output_file_path = "./schema_updated.prisma";
// ファイルを読み込む
let content = fs::read_to_string(input_file_path)?;
// 内容を行ごとに処理
let mut updated_content = String::new();
let mut line_buffer = String::new();
let mut this_model_head = String::new();
let model_start = regex::Regex::new(r#"model.+\{"#).unwrap();
let table_map_regex = regex::Regex::new(r#"@@map\("(.+)"\)"#).unwrap();
let mut is_remove_comment_out = false;
for line in content.lines() {
// モデルの開始行を探す
let line_replaced = if is_remove_comment_out{
is_remove_comment_out = false;
line.replacen("//","",1)
} else {
line.to_string()
};
if model_start.find(&line_replaced).is_some() {
//前のmodel情報を更新する
if line_buffer.len() > 0 {
if this_model_head.len() > 0 {
updated_content.push_str(&this_model_head);
updated_content.push('\n');
}
updated_content.push_str(&line_buffer);
line_buffer = String::new();
this_model_head = String::new();
}
this_model_head = line_replaced.clone();
} else if let Some(caps) = table_map_regex.captures(&line_replaced) {
// @@mapの内容を抽出
let table_name = caps.get(1).unwrap().as_str();
// モデル名を更新
this_model_head = format!("model {} {{\n", table_name);
line_buffer.push_str(&line_replaced);
line_buffer.push('\n');
} else {
line_buffer.push_str(&line_replaced);
line_buffer.push('\n');
}
//次フィールドがコメントアウトされている場合、indexをマークしておく
if line_replaced.contains("/// This field was commented out because of an invalid name") {
is_remove_comment_out = true;
}
}
if line_buffer.len() > 0 {
if this_model_head.len() > 0 {
updated_content.push_str(&this_model_head);
updated_content.push('\n');
}
updated_content.push_str(&line_buffer);
}
// 更新された内容を新しいファイルに書き込む
let mut output_file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(output_file_path)?;
output_file.write_all(updated_content.as_bytes())?;
println!("ファイルの更新が完了しました: {}", output_file_path);
Ok(())
}
これで、上記のスキーマファイルは、以下の様に更新されました。
model TESTテーブル {
/// This field was commented out because of an invalid name. Please provide a valid one that matches [a-zA-Z][a-zA-Z0-9_]*
フィールド1 String @map("フィールド1") @db.VarChar(2)
/// This field was commented out because of an invalid name. Please provide a valid one that matches [a-zA-Z][a-zA-Z0-9_]*
ふぃーるど2 String @map("ふぃーるど2") @db.VarChar(9)
@@map("TESTテーブル")
}
Prisma Client Rustを使う
Prismaのschemaデータができたので、
Prisma Client Rustを使っていきます。
現在はバイナリが無いので、
Rustプロジェクトを作って、そこにcrateとして読み込んで、処理を行います。
ディレクトリ構成(作成したschema.prismaを、配置しておく
update_japanese_tables
- migration(dir)
- src(dir)
- main.rs
- schema.prisma
Cargo.toml
[package]
name = "update_japanese_tables"
version = "0.1.0"
edition = "2021"
[dependencies]
regex = "1.10.6"
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11", default-features = false, features=["mssql"] }
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11", default-features = false, features=["mssql"] }
serde = "1.0.210"
fn main() {
prisma_client_rust_cli::run();
}
コマンドは、以下の通り
cargo run -- generate
エラー出まくり・・・
error: Error validating: This line is invalid. It does not start with any known Prisma schema keyword.
--> schema.prisma:16189
|
ここで気づいたのだが、これはあくまでNodeのコードジェネレータで、
Rustのコードを吐き出すわけではなさそうだ。
ここまでやったが、別の方法で仕切り直し・・・
他の方法
試してないが、0.6系のsqlxは、mssql featureがあるので、これを使ってやる方法もあるか?
cargo install sqlx-cli --version 0.6.3 --no-default-features --features sqlserver
sqlxからmssqlがサポート対象外になっている理由
よく見たら、sqlxは対応してるが、sqlx-cliはmssqlに対応してなかった・・
これもだめ。