データベース¶
リレーショナルデータベースにアクセスするには、使用したいデータベースサーバー用に設計されたshardが必要です。パッケージcrystal-lang/crystal-dbは、異なるドライバー間で統一されたAPIを提供します。
以下のパッケージはcrystal-dbに準拠しています。
- crystal-lang/crystal-sqlite3 for sqlite
- crystal-lang/crystal-mysql for mysql & mariadb
- will/crystal-pg for postgres
そして、さらにいくつかあります。
このガイドでは、crystal-dbのAPIを紹介しますが、postgres、mysql、sqliteの違いにより、具体的なドライバーに合わせてSQLコマンドを調整する必要がある場合があります。
また、postgresのLISTEN
/NOTIFY
のように、一部のドライバーは追加機能を提供する場合があります。
shardのインストール¶
上記のリストから適切なドライバーを選択し、アプリケーションのshard.yml
に他のshardと同様に追加します。
crystal-lang/crystal-db
を明示的にrequireする必要はありません。
このガイドでは、crystal-lang/crystal-mysql
を使用します。
dependencies:
mysql:
github: crystal-lang/crystal-mysql
データベースを開く¶
DB.open
を使用すると、接続URIを使用して簡単にデータベースに接続できます。URIのスキーマによって、期待されるドライバーが決まります。次のサンプルでは、ユーザー名がroot、パスワードがblankのローカルmysqlデータベースtestに接続します。
require "db"
require "mysql"
DB.open "mysql://root@localhost/test" do |db|
# ... use db to perform queries
end
その他の接続URIは次のとおりです。
sqlite3:///path/to/data.db
mysql://user:password@server:port/database
postgres://user:password@server:port/database
または、Database#close
が最後に呼び出される限り、非yieldのDB.open
メソッドを使用することもできます。
require "db"
require "mysql"
db = DB.open "mysql://root@localhost/test"
begin
# ... use db to perform queries
ensure
db.close
end
または、プールではなくデータベースへの単一の接続を開くために、DB.connect
メソッドを使用することもできます。
Exec¶
SQLステートメントを実行するには、Database#exec
を使用できます。
db.exec "create table contacts (name varchar(30), age int)"
db.exec "insert into contacts (name, age) values ('abc', 30)"
値はクエリパラメータとして指定できます。下記を参照してください。
Query¶
クエリを実行して結果セットを取得するには、Database#query
を使用します。
Database#query
は、閉じられる必要のあるResultSet
を返します。Database#open
と同様に、ブロックで呼び出された場合、ResultSet
は暗黙的に閉じられます。
db.query "select name, age from contacts order by age desc" do |rs|
rs.each do
# ... perform for each row in the ResultSet
end
end
値はクエリパラメータとして指定できます。下記を参照してください。
クエリパラメータ¶
SQLインジェクションを回避するために、値をクエリパラメータとして指定できます。クエリパラメータを使用する構文は、データベースドライバーに依存します。これは、通常、データベースにそのまま渡されるためです。MySQLではパラメータ展開に?
を使用し、代入は引数の順序に基づいて行われます。PostgreSQLでは、引数の序数(1から始まる)である$n
を使用します。
# MySQL
db.exec "insert into contacts values (?, ?)", "John", 30
# Postgres
db.exec "insert into contacts values ($1, $2)", "Sarah", 33
# Queries:
db.query("select name from contacts where age = ?", 33) do |rs|
rs.each do
# ... perform for each row in the ResultSet
end
end
クエリパラメータは、ドライバーに応じて、準備されたステートメント(キャッシュされる場合もあります)またはクライアント側での挿入によって裏で処理されますが、常にSQLインジェクションを回避します。
準備されたステートメントを手動で使用する場合は、build
メソッドを使用できます。
# MySQL
prepared_statement = db.build("select * from contacts where id=?") # Use "... where id=$1" for PostgreSQL
# Use prepared statement:
prepared_statement.query(3) do |rs|
# ... use rs
end
prepared_statement.query(4) do |rs|
# ... use rs
end
prepared_statement.close
クエリ結果の読み取り¶
データベースから値を読み取る場合、crystalが使用できるコンパイル時の型情報はありません。データベースから取得すると予想される型T
を指定して、rs.read(T)
を呼び出す必要があります。
db.query "select name, age from contacts order by age desc" do |rs|
rs.each do
name = rs.read(String)
age = rs.read(Int32)
puts "#{name} (#{age})"
# => Sarah (33)
# => John Doe (30)
end
end
これを容易にするために、#query
の上に構築された便利なクエリメソッドが多数あります。
一度に複数の列を読み取ることができます。
name, age = rs.read(String, Int32)
または、単一行を読み取ることができます。
name, age = db.query_one "select name, age from contacts order by age desc limit 1", as: {String, Int32}
または、ResultSetを明示的に処理することなく、スカラ値を読み取ることができます。
max_age = db.scalar "select max(age) from contacts"
型を指定してクエリしたり、型を指定して列名をクエリしたりするための、他の多くのヘルパーメソッドがあります。データベースでステートメントを実行するための利用可能なすべてのメソッドは、DB::QueryMethods
で定義されています。