db_sqlite

高水準 SQLite データベースのラッパーです。そのほかのデータベース実装でもインタフェースは共通化してあります。

基本の用法

このモジュールを使うための基本手順は、

  1. データベースの接続開始
  2. SQL クエリの実行
  3. データベースの接続終了

パラメータの置換

すべての db_* モジュールにおいてパラメータの置換は共通形式をサポートしています。つまり、値の置換先を表すのに ? (疑問符) を使います。用例:

sql"INSERT INTO my_table (colA, colB, colC) VALUES (?, ?, ?)"

データベースの接続開始

import db_sqlite

# user, password, database の名称は無指定にできます。
# db_sqlite モジュールにおいて前述のパラメータは未使用です。
let db = open("mytest.db", "", "", "")
db.close()

テーブルの作成

db.exec(sql"DROP TABLE IF EXISTS my_table")
db.exec(sql"""CREATE TABLE my_table (
                 id   INTEGER,
                 name VARCHAR(50) NOT NULL
              )""")

データの挿入

db.exec(sql"INSERT INTO my_table (id, name) VALUES (0, ?)",
        "Jack")

本格的な用例

import db_sqlite, math

let db = open("mytest.db", "", "", "")

db.exec(sql"DROP TABLE IF EXISTS my_table")
db.exec(sql"""CREATE TABLE my_table (
                 id    INTEGER PRIMARY KEY,
                 name  VARCHAR(50) NOT NULL,
                 i     INT(11),
                 f     DECIMAL(18, 10)
              )""")

db.exec(sql"BEGIN")
for i in 1..1000:
  db.exec(sql"INSERT INTO my_table (name, i, f) VALUES (?, ?, ?)",
          "Item#" & $i, i, sqrt(i.float))
db.exec(sql"COMMIT")

for x in db.fastRows(sql"SELECT * FROM my_table"):
  echo x

let id = db.tryInsertId(sql"""INSERT INTO my_table (name, i, f)
                              VALUES (?, ?, ?)""",
                        "Item#1001", 1001, sqrt(1001.0))
echo "Inserted item: ", db.getValue(sql"SELECT name FROM my_table WHERE id=?", id)

db.close()

注意

このモジュールはスキーマによる型のマッピングなどの ORM 機能は実装していません。代わりに、各行で seq[string] が返されます。

これは下記の論拠によるものです:

  1. ほとんどのデータベースで標準装備 (char**) されているものに近いからです
  2. データベースでサポートする型の数値を隠せるからです

(int?int64?十桁までの十進数?ジオメトリック座標?)

3. つまり、データの外部転送時 (エコー、ログ、新規クエリへのデータ入力) に都合が良いです

関連

DbConn = PSqlite3
データベース接続のカプセル化  ソース 編集
Row = seq[string]
データセットの行。NULL データベース値は空文字列へ変換されます。  ソース 編集
InstantRow = PStmt
要求された行にあるテキスト列を得るために使うハンドル  ソース 編集

プロシージャ

proc dbError(db: DbConn) {...}{.noreturn, raises: [DbError], tags: [].}

DbError 例外を発生させます。

用例:

let db = open("mytest.db", "", "", "")
if not db.tryExec(sql"SELECT * FROM not_exist_table"):
  dbError(db)
db.close()
  ソース 編集
proc dbQuote(s: string): string {...}{.raises: [], tags: [].}
''' (シングルクォート) 文字をエスケープ処理します。これは SQL においてシングルクォートは VARCHAR の定義で使われるからです。

用例:

doAssert dbQuote("\'") == "\'\'\'\'"
doAssert dbQuote("A Foobar\'s pen.") == "\'A Foobar\'\'s pen.\'"
  ソース 編集
proc tryExec(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): bool {...}{.
    tags: [ReadDbEffect, WriteDbEffect], raises: [].}

クエリの実行を試行します。成功時はtrue を、それ以外は false を返します。

用例:

let db = open("mytest.db", "", "", "")
if not db.tryExec(sql"SELECT * FROM my_table"):
  dbError(db)
db.close()
  ソース 編集
proc exec(db: DbConn; query: SqlQuery; args: varargs[string, `$`]) {...}{.
    tags: [ReadDbEffect, WriteDbEffect], raises: [DbError].}

クエリを実行します。失敗したときは DbError 例外が発生します。

用例:

let db = open("mytest.db", "", "", "")
try:
  db.exec(sql"INSERT INTO my_table (id, name) VALUES (?, ?)",
          1, "item#1")
except:
  stderr.writeLine(getCurrentExceptionMsg())
finally:
  db.close()
  ソース 編集
proc `[]`(row: InstantRow; col: int32): string {...}{.inline, raises: [], tags: [].}

指定された行にある列のテキストを返します。

関連:

  ソース 編集
proc unsafeColumnAt(row: InstantRow; index: int32): cstring {...}{.inline, raises: [],
    tags: [].}

指定された行にある列の cstring を返します。

関連:

  ソース 編集
proc len(row: InstantRow): int32 {...}{.inline, raises: [], tags: [].}

指定された行にある列の数値を返します。

関連:

  ソース 編集
proc getRow(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): Row {...}{.
    tags: [ReadDbEffect], raises: [DbError].}

一行取得します。クエリで行が返されないときは、このプロシージャは各列を空文字にした Row を返します。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

doAssert db.getRow(sql"SELECT id, name FROM my_table"
                   ) == Row(@["1", "item#1"])
doAssert db.getRow(sql"SELECT id, name FROM my_table WHERE id = ?",
                   2) == Row(@["2", "item#2"])

# 空文字を返します。
doAssert db.getRow(sql"INSERT INTO my_table (id, name) VALUES (?, ?)",
                   3, "item#3") == @[]
doAssert db.getRow(sql"DELETE FROM my_table WHERE id = ?", 3) == @[]
doAssert db.getRow(sql"UPDATE my_table SET name = 'ITEM#1' WHERE id = ?",
                   1) == @[]
db.close()
  ソース 編集
proc getAllRows(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): seq[Row] {...}{.
    tags: [ReadDbEffect], raises: [DbError].}

クエリを実行した結果をデータセット全体として返します

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

doAssert db.getAllRows(sql"SELECT id, name FROM my_table") == @[Row(@["1", "item#1"]), Row(@["2", "item#2"])]
db.close()
  ソース 編集
proc getValue(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): string {...}{.
    tags: [ReadDbEffect], raises: [DbError].}

クエリを実行した結果としてデータセットの最初の行にある最初の列を返します。データセットに行が無いか、データベースの値が NULL ならば "" を返します。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

doAssert db.getValue(sql"SELECT name FROM my_table WHERE id = ?",
                     2) == "item#2"
doAssert db.getValue(sql"SELECT id, name FROM my_table") == "1"
doAssert db.getValue(sql"SELECT name, id FROM my_table") == "item#1"

db.close()
  ソース 編集
proc tryInsertID(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): int64 {...}{.
    tags: [WriteDbEffect], raises: [].}

クエリを実行後 (通常は "INSERT") に行の生成 ID、あるいはエラーならば -1 返します。

用例:

let db = open("mytest.db", "", "", "")
db.exec(sql"CREATE TABLE my_table (id INTEGER, name VARCHAR(50) NOT NULL)")

doAssert db.tryInsertID(sql"INSERT INTO not_exist_table (id, name) VALUES (?, ?)",
                        1, "item#1") == -1
db.close()
  ソース 編集
proc insertID(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): int64 {...}{.
    tags: [WriteDbEffect], raises: [DbError].}

クエリを実行後 (通常は "INSERT") に行の生成 ID を返します。

行の挿入に失敗したときは DbError 例外が発生します。Postgre において、これはクエリへ RETURNING id を追加します。つまり、プライマリ・キーは名前付き id として動作します。

用例:

let db = open("mytest.db", "", "", "")
db.exec(sql"CREATE TABLE my_table (id INTEGER, name VARCHAR(50) NOT NULL)")

for i in 0..2:
  let id = db.insertID(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", i, "item#" & $i)
  echo "LoopIndex = ", i, ", InsertID = ", id

# 出力:
# LoopIndex = 0, InsertID = 1
# LoopIndex = 1, InsertID = 2
# LoopIndex = 2, InsertID = 3

db.close()
  ソース 編集
proc execAffectedRows(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): int64 {...}{.
    tags: [ReadDbEffect, WriteDbEffect], raises: [DbError].}

クエリを実行後 (通常は "UPDATE") に処理対象となった行数を返します。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

doAssert db.execAffectedRows(sql"UPDATE my_table SET name = 'TEST'") == 2

db.close()
  ソース 編集
proc close(db: DbConn) {...}{.tags: [DbEffect], raises: [DbError].}

データベースの接続を終了します。

用例:

let db = open("mytest.db", "", "", "")
db.close()
  ソース 編集
proc open(connection, user, password, database: string): DbConn {...}{.tags: [DbEffect],
    raises: [DbError].}

データベースの接続を開始します。接続を確立できないときは DbError 例外が発生します。

備考: connection パラメータは sqlite 専用です。

用例:

try:
  let db = open("mytest.db", "", "", "")
  ## do something...
  ## db.getAllRows(sql"SELECT * FROM my_table")
  db.close()
except:
  stderr.writeLine(getCurrentExceptionMsg())
  ソース 編集
proc setEncoding(connection: DbConn; encoding: string): bool {...}{.tags: [DbEffect],
    raises: [DbError].}

データベース接続のエンコーディングを設定します。成功時は true を、失敗時は false を返します。

備考: 設定後のエンコーディング変更はできません。SQLite3 のドキュメンテーションによれば、データベースの作成後にエンコーディングを変更しようとしても暗黙的に無視されます。

  ソース 編集

イテレータ

iterator fastRows(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): Row {...}{.
    tags: [ReadDbEffect], raises: [DbError, DbError].}

クエリを実行した結果をデータセットとしてイテレートします。

これは非常に高速ですが、潜在的な危険性があります。全部の行で必要な場合に限り、このイテレータをお使いください。

注意: fastRows() イテレータでループ処理中に次回のデータベース・クエリを破壊してしまい、 unable to close due to ... による DbError 例外が発生する原因となることがあります。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

for row in db.fastRows(sql"SELECT id, name FROM my_table"):
  echo row

# 出力:
# @["1", "item#1"]
# @["2", "item#2"]

db.close()
  ソース 編集
iterator instantRows(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): InstantRow {...}{.
    tags: [ReadDbEffect], raises: [DbError, DbError].}

fastRows イテレータと似ていますが、 [] で要求されたテキスト列の取得に使えるハンドルを返します。返されたハンドルはイテレータ本体でのみ有効です。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

for row in db.instantRows(sql"SELECT * FROM my_table"):
  echo "id:" & row[0]
  echo "name:" & row[1]
  echo "length:" & $len(row)

# 出力:
# id:1
# name:item#1
# length:2
# id:2
# name:item#2
# length:2

db.close()
  ソース 編集
iterator instantRows(db: DbConn; columns: var DbColumns; query: SqlQuery;
                    args: varargs[string, `$`]): InstantRow {...}{.tags: [ReadDbEffect],
    raises: [DbError, DbError].}

instantRows イテレータと似ていますが、列の情報を columns へ設定します。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

var columns: DbColumns
for row in db.instantRows(columns, sql"SELECT * FROM my_table"):
  discard
echo columns[0]

# 出力:
# (name: "id", tableName: "my_table", typ: (kind: dbNull,
# notNull: false, name: "INTEGER", size: 0, maxReprLen: 0, precision: 0,
# scale: 0, min: 0, max: 0, validValues: @[]), primaryKey: false,
# foreignKey: false)

db.close()
  ソース 編集
iterator rows(db: DbConn; query: SqlQuery; args: varargs[string, `$`]): Row {...}{.
    tags: [ReadDbEffect], raises: [DbError].}

fastRows イテレータ と似ていますが、安全性を優先しているため処理速度は遅いです。

用例:

let db = open("mytest.db", "", "", "")

# my_table のレコード:
# | id | name     |
# |----|----------|
# |  1 | item#1   |
# |  2 | item#2   |

for row in db.rows(sql"SELECT id, name FROM my_table"):
  echo row

## 出力:
## @["1", "item#1"]
## @["2", "item#2"]

db.close()
  ソース 編集