コンテンツへスキップ

データベース

リレーショナルデータベースにアクセスするには、使用したいデータベースサーバー用に設計されたshardが必要です。パッケージcrystal-lang/crystal-dbは、異なるドライバー間で統一されたAPIを提供します。

以下のパッケージはcrystal-dbに準拠しています。

そして、さらにいくつかあります。

このガイドでは、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で定義されています。