#sqlx

sqlxは優れたビルトインdatabase/sqlパッケージを拡張したGoのパッケージです。

このドキュメントはsqlxのGoでの使い方にフォーカスしています。 だから、このドキュメントで使われているSQLがベストプラクティスとは限りません。 また、ここではGo開発環境のセットアップ、GoやSQLの基本的な使い方については扱っていません。

これ以降では標準のerr変数を使用してエラーが返されていますが、簡潔にするためにこれらを無視しています。 実際のプログラムでは絶対にすべてのエラーを確認してください。

#参考資料

GoでSQLを使用する方法についての優れたドキュメントは他にもあります。

Goの使い方を学習したい場合は、以下のドキュメントをお勧めします。

database/sqlインターフェースはsqlxのサブセットです。 だから、これらのドキュメントにあるdatabase/sqlの使い方に関する情報はsqlxでも通用します。

#はじめに

sqlxとデータベースドライバーをインストールする必要があります。 データベースのインストールなどの環境構築を省くために、ここではmattnのsqlite3ドライバをインストールします。

$ go get github.com/jmoiron/sqlx
$ go get github.com/mattn/go-sqlite3

#データベース操作に関連した型

sqlxdatabase/sqlと同じような感じになるようにしています。 sqlxには4つの主要なデータベース操作に関連した型(Handle Types)があります。

データベース操作に関連した型はすべて、database/sqlの同等物を埋め込み(Embedding)しています。 つまり、sqlx.DB.Queryを呼び出すとき、sql.DB.Queryと同じコードを呼び出しています。 これにより、既存のコードベースに簡単に導入することができます。

これに加えて、2つのカーソルタイプがあります:

データベース操作に関連した型と同様に、sqlx.Rowssql.Rowsを埋め込んでいます。 基本的な実装がアクセス不可能だったため、sqlx.Rowsql.Rowの部分的な再実装であり、標準インターフェースを維持しています。

#データベースへの接続

DBインスタンスは接続そのものではなく、データベースを抽象化したものです。 これがDBを作成してもエラーが返されず、パニックにならない理由です。 sqlxは内部にコネクションプールを保持しており、初めて接続が必要になった時に接続を試みます。 下記のように、sqlx.DBOpenを使って作成する、もしくは既存のsql.DBNewDbに渡すことで作成することができます。

var db *sqlx.DB

// ビルトインと全く同じ
db = sqlx.Open("sqlite3", ":memory:")

// 既存のsql.DBを使って作成する(ドライバ名が必要なことに注意)
db = sqlx.NewDb(sql.Open("sqlite3", ":memory:"), "sqlite3")

// 強制的に接続して、正常に動作するかテストする
err = db.Ping()

DBをオープンと接続をまとめて行いたい場合もあるかもしれません。 (例: 初期化フェーズ中に設定の問題を検出する) Connectを使うとDBのオープンと接続をまとめて行うことができます。 Connectは新しいDBをオープンしてPingを試みます。 エラーが発生した場合にパニックを起こすMustConnectは、パッケージのモジュールレベルで使用することに適しています。

var err error
// オープンして接続する
db, err = sqlx.Connect("sqlite3", ":memory:")

// オープンして接続し、エラー時にパニックを起こす
db = sqlx.MustConnect("sqlite3", ":memory:")

#クエリ入門

下記のようにsqlxにおけるデータベース操作に関連した型は、データベースへのクエリに関する同じ基本的な関数を実装しています。

下記はこれらのビルトイン関数への拡張です。

下記は加えられた全く新しい関数です。

変更されていないインターフェースの関数から新しい関数へこれらの使い方を説明してきます。

#Exec

ExecMustExecはコネクションプールから接続を取得します。 そして、渡されたクエリをデータベースサーバ上で実行します。 この時、アドホックなクエリ実行をサポートしていないドライバの場合、自動的にプリペアドステートメントが作成されて実行されることがあります。 そして、結果が返される前に接続をコネクションプールに返します。

schema := `CREATE TABLE place (
    country text,
    city text NULL,
    telcode integer);`

// データベースサーバでクエリを実行する
result, err := db.Exec(schema)

// または、エラー時にパニックを起こすMustExecを使うことができます
cityState := `INSERT INTO place (country, telcode) VALUES (?, ?)`
countryCity := `INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)`
db.MustExec(cityState, "Hong Kong", 852)
db.MustExec(cityState, "Singapore", 65)
db.MustExec(countryCity, "South Africa", "Johannesburg", 27)

ResultにはLastInsertId()RowsAffected()という2つの関数があります。 これらはドライバによって利用可能かどうかが異なります。 例えばMySQLではオートインクリメントキーを持つ挿入についてはLastInsertId()が利用可能です。 一方、PostgreSQLではこの情報はRETURNING句を使用して通常の行カーソルから取得する必要があります。

#バインド変数(bindvars)

クエリ内のプレースホルダである?は、内部的にバインド変数(bindvars)と呼ばれます。 データベースサーバに値を送る際には、これらを 常に 使用すべきです。 なぜなら、SQLインジェクション攻撃を防ぐことができるからです。 database/sqlはクエリテキストに対して 何の 検証も行いません。 クエリはエンコードされたパラメータと共にそのままデータベースサーバに送信されます。 ドライバが特別なインターフェースを実装していない限り、クエリは実行前にまずデータベースサーバで準備されます。 また、下記のようにバインド変数はデータベースによって異なります。

他のデータベースは異なる場合があります。 現在のデータベースタイプで実行に適したクエリを取得するために、sqlx.DB.Rebind(string) string関数を?バインド変数構文と共に使用できます。

バインド変数に関する一般的な誤解は、それらが補間に使用されるということです。 バインド変数は パラメータ化 のためだけに使用され、SQL文の構造を変更することは許されていません。 たとえば、バインド変数を使用して列名やテーブル名をパラメータ化しようとすると機能しません。

// 機能しない
db.Query("SELECT * FROM ?", "mytable")

// これも機能しない
db.Query("SELECT ?, ? FROM people", "name", "location")

#Query

Querydatabase/sqlを使用して行の結果を返すクエリを実行します。 下記のようにQuerysql.Rowsオブジェクトとエラーを返します。

// placeテーブルからすべてのデータを取得する 
rows, err := db.Query("SELECT country, city, telcode FROM place")

// 各行を反復処理する
for rows.Next() {
    var country string
    // cityはNULLになる可能性があるため、NullString型を使用する
    var city    sql.NullString
    var telcode int
    err = rows.Scan(&country, &city, &telcode)
}

Rowsはクエリの結果を格納したリストというよりデータベースカーソルのように扱うべきです。 ドライバのバッファリング動作は異なる場合がありますが、Next()を使って反復処理することは大きな結果セットのメモリ使用量を制限する良い方法です。 なぜなら、一度に1行ずつしかスキャン(scan)するからです。 Scan()reflectを使用して、sqlの列の戻り値の型をstring[]byteなどのGoの型にマッピングします。 全ての行の結果を反復処理しない場合は、接続をコネクションプールに戻すためにrows.Close()を必ず呼び出してください。

Queryによって返されるエラーは、サーバ上での準備や実行中に発生した可能性のあるエラーです。 これには、コネクションプールから不良な接続を取得することも含まれますが、database/sqlは動作するコネクションを見つけたり作成したりするために10回まで再試行します。 一般的に、エラーは不正なSQL構文、型の不一致、または不正なフィールドやテーブル名によるものでしょう。

ほとんどの場合、Rows.Scanはドライバがバッファをどのように再利用するかを知らないため、ドライバから取得したデータをコピーします。 sql.RawBytes型の値をScan()に渡すとドライバから実際に返されたデータの ゼロコピー バイトスライスを取得することがきます。 次のNext()の呼び出し後、そのような値は有効でなくなります。 なぜなら、そのメモリはドライバによって上書きされている可能性があるからです。

Queryによって使用される接続はNextによる反復処理で全ての行が使い果たされるか、rows.Close()が呼び出されるまでオープンなままです。 詳細については、コネクションプールのセクションを参照してください。

sqlxQueryxQueryと全く同じように動作しますが、新たに機能追加されたスキャン(scan)を持つsqlx.Rowsを返します。

type Place struct {
    Country       string
    City          sql.NullString
    TelephoneCode int `db:"telcode"`
}

rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    var p Place
    err = rows.StructScan(&p)
}

sqlx.Rowsのよく使う追加された機能はStructScan()です。 これにより結果が自動的に渡された構造体のフィールドに割り当てられます。 sqlxがそれらに書き込むことができるためには、フィールドがエクスポートされている(大文字で始まる)必要があることに注意してください。 これはGoの すべて のマーシャラー(marshaller)に当てはまることです。 db構造体タグを使用して、どの列名が各構造体フィールドにマップするかを指定するか、db.MapperFunc()を使って新しいデフォルトマッピングを設定できます。 StructScan()のデフォルトの動作はフィールド名にstrings.Lowerを使用して列名と一致させることです。 StructScanSliceScanMapScanに関する詳しい情報は詳細なScanに関するセクションを見てください。

#QueryRow

QueryRowはサーバから1行を取得します。 QueryRowはコネクションプールから接続を取得し、Queryを使用してクエリを実行し、内部にRowsオブジェクトを持つRowオブジェクトを返します。

row := db.QueryRow("SELECT * FROM place WHERE telcode=?", 852)
var telcode int
err = row.Scan(&telcode)

Queryとは異なり、QueryRowはエラーなしでRow型の結果を返します。 これにより、返されたものから直接Scanを連鎖させることが安全になります。 クエリの実行中にエラーが発生した場合、そのエラーはScanによって返されます。 該当する行がない場合、Scansql.ErrNoRowsを返します。 スキャン自体が失敗した場合(例えば型の不一致のため)、そのエラーも返されます。

Row結果内のRows構造体はScan時にクローズされます。 つまり、QueryRowによって使用されるコネクションは、結果をスキャンするまで開いたままです。 また、これはsql.RawBytesがここで使用できないことも意味します。 なぜなら、参照されたメモリはドライバに属しており、呼び出し元に制御が戻る時には既に無効になっている可能性があるからです。

sqlxQueryRowxsql.Rowの代わりにsqlx.Rowを返します。 上記および高度なスキャンで説明されているように、sqlx.Rowsと同じスキャン機能を実装しています。

var p Place
err := db.QueryRowx("SELECT city, telcode FROM place LIMIT 1").StructScan(&p)

#GetとSelect

GetSelectは、データベース操作に関連した型の処理をまとめた関数です。 具体的にはクエリの実行と柔軟なスキャンをまとめています。 これらの処理を説明するために、ここでのスキャン可能の定義を示します。

GetSelectはスキャン可能な型に対して、rows.Scanrows.StructScanをスキャン不可能な型に対して使います。 GetSelectQueryRowQueryに似ています。 Getは結果を1つ取得してスキャンすることに使います。 Selectは結果のスライスを取得することに使います。

p := Place{}
pp := []Place{}

// これは最初のレコードをpに割り当てます
err = db.Get(&p, "SELECT * FROM place LIMIT 1")

// これはtelcodeが50より大きいレコードをスライスppに割り当てます。
err = db.Select(&pp, "SELECT * FROM place WHERE telcode > ?", 50)

// intも利用可能です。
var id int
err = db.Get(&id, "SELECT count(*) FROM place")

// 名前を最大10個取得します。
var names []string
err = db.Select(&names, "SELECT name FROM place LIMIT 10")

GetSelectは、クエリ実行中に作成されたRowsを閉じ、プロセスのどの段階で発生したエラーでも返します。 StructScanを内部的に使用しているため、高度なスキャンに記載されている詳細もGetSelectに適用されます。

Selectはコードを短くすることができますが、注意が必要です! これはQueryxと内部処理に異なり、結果セット全体を一度にメモリにロードします。 その結果セットがクエリによって何らかの合理的なサイズに制限されていない場合、 メモリを節約するためにQueryx/StructScanの反復処理を使った方が良いかもしれません。

#トランザクション

トランザクションを使用するには、DB.Begin()でトランザクションハンドルを作成する必要があります。 以下のようなコードは 機能しません

// コネクションプールが1より大きい場合、これは機能しません
db.MustExec("BEGIN;")
db.MustExec(...)
db.MustExec("COMMIT;")

Execや他のすべてのクエリ関数は、DBにコネクションを要求し、その都度プールに返します。 BEGIN文が実行されたのと同じコネクションを受け取る保証はありません。 したがって、トランザクションを使用するにはDB.Begin()を使用する必要があります。

tx, err := db.Begin()
err = tx.Exec(...)
err = tx.Commit()

sqlxはDBハンドルを生成するためにBeginx()MustBegin()を用意しています。 これらはsql.Txの代わりにsqlx.Txを返します。

tx := db.MustBegin()
tx.MustExec(...)
err = tx.Commit()

sqlx.Txには、sqlx.DBが持つすべての追加された関数があります。

トランザクションはコネクションの状態であるため、Txオブジェクトはプールから1つのコネクションをバインドし制御する必要があります。 Txはそのライフサイクル全体で1つのコネクションを維持し、Commit()またはRollback()が呼び出されたときにのみそれを解放します。 ガベージコレクションまでコネクションが保持されるのを防ぐため、これらのうち少なくとも1つを呼び出すように注意してください。

トランザクションでは1つのコネクションのみを使用するため、一度に1つのステートメントのみを実行できます。 カーソルタイプのRowRowsは、別のクエリを実行する前にそれぞれScannedまたはClosedされる必要があります。 サーバーが結果を送信している最中にデータを送信しようとすると、接続が破損する可能性があります。

最後に、Txオブジェクトはデータベースサーバで実際の動作を意味するものではありません。 BEGIN文を実行して1つのコネクションをバインドするだけです。 トランザクションの実際の動作(ロックや分離などを含む)は完全に仕様が決まっていません。 それらはデータベース依存です。

#プリペアドステートメント

ほとんどのデータベースではクエリが実行される毎に自動的にそのクエリのプリペアドステートメントを内部で作成しています。 しかし、下記のようにsqlx.DB.Prepare()を使うと、他の場所で再利用するために明示的にステートメントを準備(prepare)することができます。

stmt, err := db.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = stmt.QueryRow(65)

tx, err := db.Begin()
txStmt, err := tx.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = txStmt.QueryRow(852)

Prepareはデータベースサーバでステートメントの準備を行うために接続が必要です。 database/sqlはこれを抽象化し、新しいコネクションでステートメントを自動的に作成することで、1つのStmtオブジェクトを多くのコネクションで同時に実行できるようにします。 Preparex()sqlx.DBおよびsqlx.Txが持つ全ての追加された機能を持つsqlx.Stmtを返します。

stmt, err := db.Preparex(`SELECT * FROM place WHERE telcode=?`)
var p Place
err = stmt.Get(&p, 852)

sql.Txオブジェクトには既存のステートメントからトランザクション固有のステートメントを返すStmt()メソッドがあります。 sqlx.TxにはStmtxバージョンがあり、既存のsql.Stmt または sqlx.Stmtから新しいトランザクション固有のsqlx.Stmtを作成します。

#クエリヘルパー

database/sqlパッケージは実際のクエリテキストに対して何も行いません。 これにより、コード内で特定のデータベースエンジン専用の機能を使用することが非常に簡単になります。 データベースプロンプトで行うようにクエリをそのまま書くことができます。 これは非常に柔軟ですが特定の種類のクエリを書くことが難しくなります。

#IN句

database/sqlはクエリを検査せず、引数をそのままドライバに渡すため、IN句を含むクエリの扱いが難しくなります:

SELECT * FROM users WHERE level IN (?);

このクエリがデータベースサーバでステートメントとして準備されると、バインド変数?は 1つ の引数にのみ対応します。 しかし、通常はいくつかのスライスの長さに応じて変数数の引数であることが望まれます。例えば

var levels = []int{4, 6, 7}
rows, err := db.Query("SELECT * FROM users WHERE level IN (?);", levels)

このパターンは、最初にsqlx.Inでクエリを処理することにより可能になります:

var levels = []int{4, 6, 7}
query, args, err := sqlx.In("SELECT * FROM users WHERE level IN (?);", levels)

// sqlx.Inは`?`バインド変数でクエリを返します。これを使用しているデータベースエンジン用にに再バインドできます。
query = db.Rebind(query)
rows, err := db.Query(query, args...)

sqlx.Inが行うことはそれに渡されたクエリ内の任意のバインド変数を引数内のスライスに対応するスライスの長さに拡張します。 そして、そのスライス要素を新しい引数リストに追加することです。 これは?バインド変数のみで行われます。 データベースエンジンに適合したクエリに変換ためにdb.Rebindを使います。

#名前付きクエリ

名前付きクエリは多くの他のデータベースパッケージで一般的です。 これらはバインド変数構文を使用し、構造体のフィールド名やマップのキー名に基づいてクエリ変数をバインドすることを可能にします。 これにより、バインド変数の位置に基づいてすべてのクエリ変数を参照する必要がなくなります。 構造体フィールドの命名規則はStructScanに従い、NameMapperおよびdb構造体タグを使用します。 名前付きクエリに関連する追加のクエリ関数は次のとおりです:

そして、追加のデータベース操作に関連した型が1つあります:

// 構造体を使った名前付きクエリ
p := Place{Country: "South Africa"}
rows, err := db.NamedQuery(`SELECT * FROM place WHERE country=:country`, p)

// マップを使った名前付きクエリ
m := map[string]interface{}{"city": "Johannesburg"}
result, err := db.NamedExec(`SELECT * FROM place WHERE city=:city`, m)

名前付きクエリの実行と準備は構造体とマップの両方で行われます。 下記のように名前付きステートメントを準備して、クエリ関数を実行します。

type Place struct {
    TelephoneCode int `db:"telcode"`
}
pp := []Place{}

// telcode > 50の全てを選択
nstmt, err := db.PrepareNamed(`SELECT * FROM place WHERE telcode > :telcode`)
err = nstmt.Select(&pp, p)

名前付きクエリの処理は:param構文のクエリ解析と基になるデータベースがサポートするバインド変数への置き換えます。 そして、実行に変数をマッピングします。 したがって、sqlxがサポートする任意のデータベースで使用可能です。 下記のように、sqlx.Namedを使用することもできます。 これはsqlx.Inと組み合わせて使用することができます。

arg := map[string]interface{}{
    "published": true,
    "authors": []{8, 19, 32, 44},
}
query, args, err := sqlx.Named("SELECT * FROM articles WHERE published=:published AND author_id IN (:authors)", arg)
query, args, err := sqlx.In(query, args...)
query = db.Rebind(query)
db.Query(query, args...)

#高度なスキャン

StructScanは見かけよりも洗練されています。 この関数は埋め込み構造体をサポートし、Goが埋め込み属性やメソッドアクセスに使用するのと同じ優先順位ルールを用いてフィールドに割り当てます。 これの一般的な使用例は、多くのテーブルでテーブルモデルの共通部分を共有することです。 例えば

type AutoIncr struct {
    ID       uint64
    Created  time.Time
}

type Place struct {
    Address string
    AutoIncr
}

type Person struct {
    Name string
    AutoIncr
}

上記の構造体では、PersonPlaceは共にStructScanからidcreated列を受け取ることができます。 なぜなら、それらはAutoIncr構造体を埋め込んでおり、それがこれらを定義しているからです。この機能により、結合のための臨時テーブルを迅速に作成することができます。 また、この機能は再帰的にも動作します。 以下の例では、PersonNameとそのAutoIncrIDCreatedフィールドが、Goのドット演算子(例: employee.id)とStructScan(例: idカラムの値をemployee.idに格納する)の両方を通してアクセス可能です。

type Employee struct {
    BossID uint64
    EmployeeID uint64
    Person
}

非埋め込み構造体に対しても、以前はsqlxがこの機能をサポートしていました。しかし、これは混乱を招く原因となりました。 というのも、ユーザーがこの機能を使って関係性を定義し、下記のように同じ構造体を二度埋め込むことと同じ効果が生じるような構造体を定義しました。

type Child struct {
    Father Person
    Mother Person
}

これにはいくつかの問題があります。 Goでは、子孫のフィールドをシャドウ(覆い隠す)することは動作します。 つまり、埋め込みの例で示したEmployeeNameを定義していれば、それはPersonNameよりも優先されます。 しかし、曖昧なセレクタ(ambiguous selectors)は動作しません。 ランタイムエラーを引き起こします。 PersonPlaceに対する効率的なJOINを反映した型を作成したい場合、埋め込んだAutoIncr経由で定義されているid列をどこに配置すればよいでしょうか?エラーになるでしょうか?

sqlxがフィールド名からフィールドアドレスへのマッピングを構築する方法に従って構造体へのスキャンではカラム名前が構造体ツリーの走査中に二度出会ったかどうかを考慮しません。 したがって、Goとは異なり、StructScanはその名前を持つ「最初」に出会ったフィールドを選択します。 Goの構造体フィールドは上から下へと順序付けられており、sqlxが優先順位ルールを維持するために幅優先探索を行うため、これは最も浅く、最も上部の定義で発生します。 例えば、次の型では

type PersonPlace struct {
    Person
    Place
}

StructScanid列の結果をPerson.AutoIncr.IDに設定し、Person.IDとしてもアクセスできます。 混乱を避けるためには、SQLでASを使用して列エイリアスを作成することをお勧めします。

#スキャン先の安全性について

デフォルトでは、StructScanはカラムが対象のフィールドにマッピングされていない場合にエラーを返します。 これはGoでの未使用変数の扱いに似ていますが、encoding/json のような標準ライブラリのマーシャラーの動作とは一致しません。 SQLは一般的にJSONのパースよりも予め定義された方法で実行されます。 これらのエラーは通常コーディングエラーだと判断したので、デフォルトでエラーを返すことに決められました。

未使用の変数と同様に、無視されるカラムはネットワークやデータベースのリソースの無駄です。 そして、不適合なマッピングや構造体タグのタイポなどを早期に検出するのは、マッパーが何かが見つからなかったことを教えてくれない限り難しいでしょう。

それにもかかわらず、マッピング対象がないカラムを無視したい場合もあります。 そのために、安全性をオフにしたそのデータベース操作に関連した型のインスタンスの新しいコピーを返すUnsafeメソッドがあります。

var p Person
// ここでのerrはnilではありません。`place`のカラムに対応するフィールドがありません
err = db.Get(&p, "SELECT * FROM person, place LIMIT 1;")

// `place`のカラムに対応するフィールドがなくても、これはエラーを返しません
udb := db.Unsafe()
err = udb.Get(&p, "SELECT * FROM person, place LIMIT 1;")

#名前マッピングの制御

StructScansでデータの保存先として使用される構造体のフィールドは、sqlxによってアクセス可能であるために大文字でなければなりません。 このため、sqlxNameMapper を使用してフィールド名にstrings.ToLowerを適用し、行結果のカラムにマッピングします。 これはスキーマによっては常に望ましいわけではないので、sqlxではマッピングをいくつかの方法でカスタマイズすることができます。

これらの方法の中で最もシンプルなのは、sqlx.DB.MapperFuncを使用してDBハンドルに設定することです。 func(string) string型の引数を受け取ります。 特定のマッパーが必要なライブラリを使用しており、受け取ったsqlx.DBを汚染したくない場合は、特定のデフォルトマッピングを保証するためにライブラリで使用するためのコピーを作成することができます:

// もしDBスキーマがALLCAPSカラムを使用している場合、通常のフィールドを使うことができます
db.MapperFunc(strings.ToUpper)

// あるライブラリが小文字のカラムを使用していると仮定すると、コピーを作成することができます
copy := sqlx.NewDb(db.DB, db.DriverName())
copy.MapperFunc(strings.ToLower)

sqlx.DBsqlx/reflectxパッケージのMapperを使用してこのマッピングを行います。 各sqlx.DBはアクティブなマッパーをsqlx.DB.Mapperとして公開しています。 DBに直接設定することで、さらにマッピングをカスタマイズすることができます。

import "github.com/jmoiron/sqlx/reflectx"

// "db"の代わりに構造体フィールドタグ"json"を使用する新しいマッパーを作成する
db.Mapper = reflectx.NewMapperFunc("json", strings.ToLower)

#スライスやマップにスキャンする

ScanStructScanだけでなく、sqlxRowRowsは結果をスライスやマップに格納するメソッドを持っています。

rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    // colsはすべてのカラム結果の[]interface{}です
    cols, err := rows.SliceScan()
}

rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    results := make(map[string]interface{})
    err = rows.MapScan(results)
}

SliceScanはすべてのカラムの[]interface{}を返し、第三者のためにクエリを実行しているsituationsのような状況で、どのカラムが返されるかわからない場合に便利です。 MapScanも同様の方法で動作しますが、キーをカラム名、値をinterface{}のマップにマッピングします。 ここでの重要な注意点は、rows.Columns()によって返される結果にはテーブル名を含むカラム名が含まれていないので、SELECT a.id, b.id FROM a NATURAL JOIN b[]string{"id", "id"}のカラム名の配列になり、マップ内の1つの結果が上書きされることです。

#カスタムタイプ

上記の例ではすべてビルトインのタイプを使用してスキャンやクエリを行っていますが、 database/sqlは任意のカスタムタイプを使用するためのインターフェースを提供しています。

これらは標準的なインターフェースであり、database/sqlの上にサービスを提供している可能性のある任意のライブラリに移植性を保証するために使用されます。 これらの使用方法の詳細については、このブログ記事を読むか、 いくつかの標準的で有用なタイプを実装しているsqlx/typesパッケージを確認してください。

#コネクションプール

ステートメントの準備とクエリの実行には接続が必要です。 DBオブジェクトは接続のプールを管理して、並行クエリで安全に使用できるようにします。 Go 1.2時点でコネクションプールのサイズを制御する方法は2つあります。

デフォルトではプールは無制限に拡大します。 そして、プール内に利用可能な接続がない場合はいつでも接続が作成されます。 DB.SetMaxOpenConnsを使用してプールの最大サイズを設定できます。 使用されていない接続はアイドル状態とされ、必要がなければ閉じられます。 接続を頻繁に作成・閉鎖することを避けるために、DB.SetMaxIdleConnsを使用してクエリ負荷に合理的なサイズで最大アイドルサイズを設定します。

接続を誤って保持することにより問題が発生しやすいです。 これを防ぐために以下のことに注意してください。

これらのいずれかを怠ると、それらが使用する接続はガベージコレクションまで保持される可能性があります。 DBは使用中のものを補うために一度にはるかに多くの接続を作成することになります。 Rows.Close()は何度でも安全に呼び出すことができるので、必要がない場合でもそれを呼び出しても大丈夫です。


#License

MIT License

Copyright (c) 2013, Jason Moiron

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.