sqldelight-patterns
Kotlin Multiplatform環境で、SQLDelightを使ってデータベースを効率的に扱うための、.sqファイルの定義、プラットフォームごとのドライバ、型変換アダプタ、移行処理、共有データベースアクセスなどを設定・管理するSkill。
📜 元の英語説明(参考)
SQLDelight patterns for Kotlin Multiplatform - .sq file definitions, platform drivers, type adapters, migrations, and shared database access.
🇯🇵 日本人クリエイター向け解説
Kotlin Multiplatform環境で、SQLDelightを使ってデータベースを効率的に扱うための、.sqファイルの定義、プラットフォームごとのドライバ、型変換アダプタ、移行処理、共有データベースアクセスなどを設定・管理するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o sqldelight-patterns.zip https://jpskill.com/download/16442.zip && unzip -o sqldelight-patterns.zip && rm sqldelight-patterns.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/16442.zip -OutFile "$d\sqldelight-patterns.zip"; Expand-Archive "$d\sqldelight-patterns.zip" -DestinationPath $d -Force; ri "$d\sqldelight-patterns.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
sqldelight-patterns.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
sqldelight-patternsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
SQLDelight Patterns for KMP
Gradle Plugin Configuration
// build.gradle.kts (shared module)
plugins {
id("app.cash.sqldelight") version "2.0.2"
}
sqldelight {
databases {
create("AppDatabase") {
packageName.set("com.example.db")
schemaOutputDirectory.set(file("src/commonMain/sqldelight/databases"))
verifyMigrations.set(true)
}
}
}
// Dependencies
kotlin {
sourceSets {
commonMain.dependencies {
implementation("app.cash.sqldelight:coroutines-extensions:2.0.2")
implementation("app.cash.sqldelight:primitive-adapters:2.0.2")
}
androidMain.dependencies {
implementation("app.cash.sqldelight:android-driver:2.0.2")
}
iosMain.dependencies {
implementation("app.cash.sqldelight:native-driver:2.0.2")
}
}
}
.sq File Syntax
.sqファイルをsrc/commonMain/sqldelight/com/example/db/に配置します。
Table Definitions
-- User.sq
CREATE TABLE User (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
display_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
avatar_url TEXT,
status TEXT NOT NULL DEFAULT 'active',
created_at INTEGER NOT NULL
);
CREATE INDEX idx_user_email ON User(email);
CREATE INDEX idx_user_created_at ON User(created_at);
Named Queries
-- User.sq (continued)
selectAll:
SELECT * FROM User ORDER BY created_at DESC;
selectById:
SELECT * FROM User WHERE id = ?;
selectByEmail:
SELECT * FROM User WHERE email = ?;
selectByStatus:
SELECT * FROM User WHERE status = :status;
insert:
INSERT OR REPLACE INTO User(display_name, email, avatar_url, status, created_at)
VALUES (?, ?, ?, ?, ?);
updateDisplayName:
UPDATE User SET display_name = ? WHERE id = ?;
deleteById:
DELETE FROM User WHERE id = ?;
deleteAll:
DELETE FROM User;
countByStatus:
SELECT COUNT(*) FROM User WHERE status = ?;
Parameterized Queries with Joins
-- Post.sq
CREATE TABLE Post (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL REFERENCES User(id),
title TEXT NOT NULL,
body TEXT NOT NULL,
published_at INTEGER
);
selectWithAuthor:
SELECT Post.*, User.display_name AS author_name
FROM Post
INNER JOIN User ON Post.author_id = User.id
WHERE Post.id = ?;
selectByAuthorId:
SELECT * FROM Post WHERE author_id = ? ORDER BY published_at DESC;
Platform Driver Setup
Common Interface
// commonMain
expect class DriverFactory {
fun createDriver(): SqlDriver
}
fun createDatabase(driverFactory: DriverFactory): AppDatabase {
val driver = driverFactory.createDriver()
return AppDatabase(
driver = driver,
UserAdapter = User.Adapter(
statusAdapter = statusAdapter,
created_atAdapter = instantAdapter
)
)
}
Android Driver
// androidMain
actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(
schema = AppDatabase.Schema,
context = context,
name = "app.db"
)
}
}
iOS Driver
// iosMain
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(
schema = AppDatabase.Schema,
name = "app.db"
)
}
}
Type Adapters
// Enum adapter
val statusAdapter = object : ColumnAdapter<UserStatus, String> {
override fun decode(databaseValue: String): UserStatus =
UserStatus.valueOf(databaseValue)
override fun encode(value: UserStatus): String =
value.name
}
// Instant adapter (kotlinx.datetime)
val instantAdapter = object : ColumnAdapter<Instant, Long> {
override fun decode(databaseValue: Long): Instant =
Instant.fromEpochMilliseconds(databaseValue)
override fun encode(value: Instant): Long =
value.toEpochMilliseconds()
}
// Usage in database creation
val database = AppDatabase(
driver = driver,
UserAdapter = User.Adapter(
statusAdapter = statusAdapter,
created_atAdapter = instantAdapter
)
)
Migration Strategy
マイグレーションファイルをsrc/commonMain/sqldelight/databases/migrations/に配置します。
-- 1.sqm (migration from version 1 to 2)
ALTER TABLE User ADD COLUMN avatar_url TEXT;
-- 2.sqm (migration from version 2 to 3)
CREATE INDEX idx_user_created_at ON User(created_at);
ALTER TABLE User ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
Gradleでマイグレーション検証を有効にします。
sqldelight {
databases {
create("AppDatabase") {
verifyMigrations.set(true)
}
}
}
Coroutines Flow Extension
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import app.cash.sqldelight.coroutines.mapToOne
import app.cash.sqldelight.coroutines.mapToOneOrNull
class UserRepository(private val database: AppDatabase) {
fun observeAll(): Flow<List<User>> =
database.userQueries.selectAll()
.asFlow()
.mapToList(Dispatchers.IO)
fun observeById(id: Long): Flow<User?> =
database.userQueries.selectById(id)
.asFlow()
.mapToOneOrNull(Dispatchers.IO)
suspend fun insert(user: User) = withContext(Dispatchers.IO) {
database.userQueries.insert(
display_name = user.displayName,
email = user.email,
avatar_url = user.avatarUrl,
status = user.status,
created_at = user.createdAt
)
}
suspend fun deleteById(id: Long) = withContext(Dispatchers.IO) {
database.userQueries.deleteById(id)
}
suspend fun replaceAll(users: List<User>) = withContext(Dispatchers.IO) {
database.transaction {
database.userQueries.deleteAll()
users.forEach { user ->
database.userQueries 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
SQLDelight Patterns for KMP
Gradle Plugin Configuration
// build.gradle.kts (shared module)
plugins {
id("app.cash.sqldelight") version "2.0.2"
}
sqldelight {
databases {
create("AppDatabase") {
packageName.set("com.example.db")
schemaOutputDirectory.set(file("src/commonMain/sqldelight/databases"))
verifyMigrations.set(true)
}
}
}
// Dependencies
kotlin {
sourceSets {
commonMain.dependencies {
implementation("app.cash.sqldelight:coroutines-extensions:2.0.2")
implementation("app.cash.sqldelight:primitive-adapters:2.0.2")
}
androidMain.dependencies {
implementation("app.cash.sqldelight:android-driver:2.0.2")
}
iosMain.dependencies {
implementation("app.cash.sqldelight:native-driver:2.0.2")
}
}
}
.sq File Syntax
Place .sq files in src/commonMain/sqldelight/com/example/db/.
Table Definitions
-- User.sq
CREATE TABLE User (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
display_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
avatar_url TEXT,
status TEXT NOT NULL DEFAULT 'active',
created_at INTEGER NOT NULL
);
CREATE INDEX idx_user_email ON User(email);
CREATE INDEX idx_user_created_at ON User(created_at);
Named Queries
-- User.sq (continued)
selectAll:
SELECT * FROM User ORDER BY created_at DESC;
selectById:
SELECT * FROM User WHERE id = ?;
selectByEmail:
SELECT * FROM User WHERE email = ?;
selectByStatus:
SELECT * FROM User WHERE status = :status;
insert:
INSERT OR REPLACE INTO User(display_name, email, avatar_url, status, created_at)
VALUES (?, ?, ?, ?, ?);
updateDisplayName:
UPDATE User SET display_name = ? WHERE id = ?;
deleteById:
DELETE FROM User WHERE id = ?;
deleteAll:
DELETE FROM User;
countByStatus:
SELECT COUNT(*) FROM User WHERE status = ?;
Parameterized Queries with Joins
-- Post.sq
CREATE TABLE Post (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL REFERENCES User(id),
title TEXT NOT NULL,
body TEXT NOT NULL,
published_at INTEGER
);
selectWithAuthor:
SELECT Post.*, User.display_name AS author_name
FROM Post
INNER JOIN User ON Post.author_id = User.id
WHERE Post.id = ?;
selectByAuthorId:
SELECT * FROM Post WHERE author_id = ? ORDER BY published_at DESC;
Platform Driver Setup
Common Interface
// commonMain
expect class DriverFactory {
fun createDriver(): SqlDriver
}
fun createDatabase(driverFactory: DriverFactory): AppDatabase {
val driver = driverFactory.createDriver()
return AppDatabase(
driver = driver,
UserAdapter = User.Adapter(
statusAdapter = statusAdapter,
created_atAdapter = instantAdapter
)
)
}
Android Driver
// androidMain
actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(
schema = AppDatabase.Schema,
context = context,
name = "app.db"
)
}
}
iOS Driver
// iosMain
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(
schema = AppDatabase.Schema,
name = "app.db"
)
}
}
Type Adapters
// Enum adapter
val statusAdapter = object : ColumnAdapter<UserStatus, String> {
override fun decode(databaseValue: String): UserStatus =
UserStatus.valueOf(databaseValue)
override fun encode(value: UserStatus): String =
value.name
}
// Instant adapter (kotlinx.datetime)
val instantAdapter = object : ColumnAdapter<Instant, Long> {
override fun decode(databaseValue: Long): Instant =
Instant.fromEpochMilliseconds(databaseValue)
override fun encode(value: Instant): Long =
value.toEpochMilliseconds()
}
// Usage in database creation
val database = AppDatabase(
driver = driver,
UserAdapter = User.Adapter(
statusAdapter = statusAdapter,
created_atAdapter = instantAdapter
)
)
Migration Strategy
Place migration files in src/commonMain/sqldelight/databases/migrations/.
-- 1.sqm (migration from version 1 to 2)
ALTER TABLE User ADD COLUMN avatar_url TEXT;
-- 2.sqm (migration from version 2 to 3)
CREATE INDEX idx_user_created_at ON User(created_at);
ALTER TABLE User ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
Enable migration verification in Gradle:
sqldelight {
databases {
create("AppDatabase") {
verifyMigrations.set(true)
}
}
}
Coroutines Flow Extension
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import app.cash.sqldelight.coroutines.mapToOne
import app.cash.sqldelight.coroutines.mapToOneOrNull
class UserRepository(private val database: AppDatabase) {
fun observeAll(): Flow<List<User>> =
database.userQueries.selectAll()
.asFlow()
.mapToList(Dispatchers.IO)
fun observeById(id: Long): Flow<User?> =
database.userQueries.selectById(id)
.asFlow()
.mapToOneOrNull(Dispatchers.IO)
suspend fun insert(user: User) = withContext(Dispatchers.IO) {
database.userQueries.insert(
display_name = user.displayName,
email = user.email,
avatar_url = user.avatarUrl,
status = user.status,
created_at = user.createdAt
)
}
suspend fun deleteById(id: Long) = withContext(Dispatchers.IO) {
database.userQueries.deleteById(id)
}
suspend fun replaceAll(users: List<User>) = withContext(Dispatchers.IO) {
database.transaction {
database.userQueries.deleteAll()
users.forEach { user ->
database.userQueries.insert(
display_name = user.displayName,
email = user.email,
avatar_url = user.avatarUrl,
status = user.status,
created_at = user.createdAt
)
}
}
}
}
KMP Module Structure
shared/
src/
commonMain/
kotlin/com/example/db/
DriverFactory.kt (expect class)
DatabaseModule.kt (Koin module)
UserRepository.kt
sqldelight/com/example/db/
User.sq
Post.sq
sqldelight/databases/
1.sqm
androidMain/
kotlin/com/example/db/
DriverFactory.android.kt (actual class)
iosMain/
kotlin/com/example/db/
DriverFactory.ios.kt (actual class)
Testing with In-Memory Driver
class UserRepositoryTest {
private lateinit var database: AppDatabase
private lateinit var repository: UserRepository
@BeforeTest
fun setup() {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
AppDatabase.Schema.create(driver)
database = AppDatabase(
driver = driver,
UserAdapter = User.Adapter(
statusAdapter = statusAdapter,
created_atAdapter = instantAdapter
)
)
repository = UserRepository(database)
}
@Test
fun insertAndRetrieve() = runTest {
repository.insert(testUser)
val users = repository.observeAll().first()
assertEquals(1, users.size)
assertEquals("alice@test.com", users.first().email)
}
}
Best Practices
- Let SQLDelight generate type-safe Kotlin code from
.sqfiles; do not write manual SQL wrappers. - Use
database.transaction { }for multi-statement atomic operations. - Place type adapters in
commonMainso they are shared across platforms. - Use
.asFlow().mapToList()for reactive UI; use direct query calls for one-shot reads. - Keep
.sqfiles organized by table name, one file per table. - Enable
verifyMigrationsto catch schema drift at build time. - Prefer
INSERT OR REPLACEfor upsert semantics in .sq files.