postgres-nio について
postgres-nio は、非同期データベースアプリケーションを構築する Swift 開発者向けの PostgreSQL クライアントライブラリです。async/await、コネクションプーリング、パラメーター化クエリ、行データのストリーミング処理を備えています。
主な機能
- 01async/await 対応 PostgreSQL クライアント
- 02コネクションプーリング
- 03型安全な補間クエリ
- 04位置または列名による行デコード
- 05プリペアドステートメントとトランザクション
- 06結果のストリーミングと COPY FROM
活用シーン
- 01非同期 Swift データベースサービスの構築
- 02PostgreSQL へのパラメーター化クエリの実行
- 03バックプレッシャー付きで大規模なクエリ結果を処理
- 04COPY FROM による行データの一括読み込み
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ wendylabsinc/ claude-skills --skill postgres-nioインストール後、Claude Code で/postgres-nioを入力して読み込まれたか確認します。
同梱ファイルreferences/postgres-patterns.md
詳細
PostgreSQL
postgres-nio ライブラリは、完全な async/await サポート、型安全なクエリ構築、コネクションプーリング、プリペアドステートメントを備えた Swift PostgreSQL クライアントを提供します。
クイックスタート
インストール
Package.swift に追加します。
dependencies: [
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.21.0")
]
基本的な使い方
import PostgresNIO
// Using PostgresClient (recommended - includes connection pooling)
let client = PostgresClient(
configuration: .init(
host: "localhost",
port: 5432,
username: "postgres",
password: "secret",
database: "myapp",
tls: .disable
)
)
// Run as a service
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { try await client.run() }
// Use client for queries
let rows = try await client.query("SELECT id, name FROM users")
for try await row in rows {
let (id, name) = try row.decode((Int.self, String.self))
print("User \(id): \(name)")
}
group.cancelAll()
}
型安全なクエリ
SQL インジェクションを防ぐため、クエリでは文字列補間を使用します。
let userId = 123
let email = "[email protected]"
// Interpolated values become parameter bindings ($1, $2, ...)
let query: PostgresQuery = "SELECT * FROM users WHERE id = \(userId) AND email = \(email)"
// Generates: "SELECT * FROM users WHERE id = $1 AND email = $2" with bindings [123, "[email protected]"]
let rows = try await client.query(query)
動的な識別子
テーブル名やカラム名には unescaped を使用します(ユーザー入力には使用しないでください)。
let tableName = "users" // Must be trusted, not user input
let query: PostgresQuery = "SELECT * FROM \(unescaped: tableName) WHERE id = \(userId)"
行のデコード
位置によるデコード
let rows = try await client.query("SELECT id, name, email, created_at FROM users")
for try await row in rows {
let (id, name, email, createdAt) = try row.decode((Int.self, String.self, String.self, Date.self))
}
カラム名によるデコード
for try await row in rows {
let id = try row["id"].decode(Int.self)
let name = try row["name"].decode(String.self)
let email = try row["email"].decode(String?.self) // Optional
}
カスタム型
struct User: Decodable {
let id: Int
let name: String
let email: String
}
// Decode into custom type
for try await row in rows {
let user = try row.decode(User.self)
}
プリペアドステートメント
パフォーマンス向上のため、再利用可能なプリペアドステートメントを定義します。
struct GetUserByEmail: PostgresPreparedStatement {
typealias Row = (Int, String, Date)
static let sql = "SELECT id, name, created_at FROM users WHERE email = $1"
var email: String
func makeBindings() -> PostgresBindings {
var bindings = PostgresBindings()
bindings.append(email)
return bindings
}
func decodeRow(_ row: PostgresRow) throws -> Row {
try row.decode(Row.self)
}
}
// Execute prepared statement
let results = try await connection.execute(GetUserByEmail(email: "[email protected]"))
for try await (id, name, createdAt) in results {
print("Found user \(id)")
}
トランザクション
try await client.withConnection { connection in
try await connection.query("BEGIN")
do {
try await connection.query("INSERT INTO accounts (id, balance) VALUES (\(fromId), \(fromBalance - amount))")
try await connection.query("INSERT INTO accounts (id, balance) VALUES (\(toId), \(toBalance + amount))")
try await connection.query("COMMIT")
} catch {
try await connection.query("ROLLBACK")
throw error
}
}
一括ロード(COPY FROM)
大規模なデータセットを効率的にロードします。
let rowCount = try await connection.copyFrom(
table: "users",
columns: ["name", "email", "created_at"],
format: .csv(delimiter: ",", header: false)
) { writer in
for user in users {
var buffer = ByteBuffer()
buffer.writeString("\(user.name),\(user.email),\(user.createdAt)\n")
try await writer.write(buffer)
}
}
print("Inserted \(rowCount) rows")
接続設定
let config = PostgresClient.Configuration(
host: "localhost",
port: 5432,
username: "postgres",
password: "secret",
database: "myapp",
tls: .prefer(.makeClientConfiguration()),
options: .init(
minimumConnections: 2,
maximumConnections: 10,
connectionIdleTimeout: .minutes(5),
keepAliveBehavior: .init(frequency: .seconds(30))
)
)
let client = PostgresClient(configuration: config)
TLS オプション
// Disable TLS (local development)
tls: .disable
// Prefer TLS (use if available)
tls: .prefer(.makeClientConfiguration())
// Require TLS (production)
tls: .require(.makeClientConfiguration())
Unix ドメインソケット
let config = PostgresClient.Configuration(
unixSocketPath: "/var/run/postgresql/.s.PGSQL.5432",
username: "postgres",
database: "myapp"
)
エラー処理
do {
let rows = try await client.query("SELECT * FROM users WHERE id = \(id)")
// ...
} catch let error as PSQLError {
switch error.code {
case .uniqueViolation:
throw MyError.duplicateEntry
case .foreignKeyViolation:
throw MyError.invalidReference
default:
throw error
}
}
大きな結果セットのストリーミング
結果はバックプレッシャーに対応して自動的にストリーミングされます。
let rows = try await client.query("SELECT * FROM large_table")
// Process one row at a time - memory efficient
for try await row in rows {
let data = try row.decode(LargeData.self)
try await processData(data)
}
対応する型
| Swift 型 | PostgreSQL 型 |
|---|---|
Int、Int32、Int64 | integer、bigint |
Float、Double | real、double precision |
String | text、varchar |
Bool | boolean |
Date | timestamp、timestamptz |
Data | bytea |
UUID | uuid |
[T] | array |
| JSON 型 | json、jsonb |
参考ファイル
トピックに応じて、必要なファイルを読み込んでください。
references/postgres-patterns.md- SQL の安全性を確保する文字列補間、エンコード/デコードのプロトコル階層、行のデコードにおける可変長ジェネリクス、プリペアドステートメントのパターン、階層型ステートマシン、バックプレッシャーに対応した行のストリーミング、COPY FROM による一括ロード、ワイヤープロトコルのエンコード
コメント
Markdown 対応