mcpField
S
serverpod-database

serverpod-database

github.com/serverpodデータ・データベース3.3k44 回インストール1 日前に更新
リポジトリ

serverpod-database について

serverpod-databaseは、Serverpodアプリを開発する人向けにORMとデータベース操作を解説するスキルです。モデルのCRUD、クエリ、リレーション、トランザクション、クライアント側SQLiteを扱います。

主な機能

  1. 01モデルに基づくORM生成
  2. 02CRUDと条件付きクエリ
  3. 03リレーションの読み込みと関連付け
  4. 04トランザクションと行ロック
  5. 05生SQLクエリ
  6. 06クライアント側SQLiteデータベース

活用シーン

  1. 01モデルを定義してCRUDを実行
  2. 02条件やリレーションによる検索と並べ替え
  3. 03トランザクション内で複数のDB操作を実行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serverpod/serverpod --skill serverpod-database

インストール後、Claude Code で/serverpod-databaseを入力して読み込まれたか確認します。

詳細

Serverpod データベース

Serverpod は、.spy.yaml で table が指定されたモデルの ORM コードを生成します。PostgreSQL は本番環境のデフォルトのデータベースです。また、データベースを config/<runMode>.yaml で database.filePath: <path> と設定するプロジェクトやテストでは、SQLite もサポートされています。

CRUD

class: Company
table: company
fields:
  name: String
  foundedDate: DateTime

モデルとリレーションの定義について詳しくは、Serverpod Models を参照してください。

var company = Company(name: 'Serverpod Inc.', foundedDate: DateTime.now());
company = await Company.db.insertRow(session, company);
var stored = await Company.db.findById(session, company.id);
company = company.copyWith(name: 'New Name');
await Company.db.updateRow(session, company);
await Company.db.deleteRow(session, company);

フィルター

テーブル記述子 t を使い、where コールバックで記述する流れるようなフィルター API です。

var activeCompanies = await Company.db.find(session,
  where: (t) => t.name.ilike('a%') & (t.foundedDate > DateTime(2020)));
  • 等値:t.column.equals(value)、t.column.notEquals(value)
  • 比較(int/double/Duration/DateTime):>、>=、<、<=
  • 範囲:t.column.between(a, b)、notBetween
  • 集合:t.column.inSet(set)、notInSet
  • 文字列:t.column.like('A%')(大文字・小文字を区別)、ilike(大文字・小文字を区別しない);% = 任意の文字列、_ = 1 文字
  • 結合:&(AND)、|(OR)、~(NOT);優先順位を指定するには括弧を使います
  • 関連(1 対 1):t.address.street.like('%road%')
  • 関連(1 対多):t.orders.count() > 3、t.orders.count((o) => o.itemType.equals('book')) > 3、t.orders.none()、t.orders.any()、t.orders.any((o) => ...)、t.orders.every((o) => ...)
  • ベクトル:類似検索用の距離演算子(distanceCosine、distanceL2)を使用します(PostgreSQL のみ)。

並べ替えとページネーション

  • 並べ替え:orderBy: (t) => t.column(デフォルトは昇順);降順にするには orderBy: (t) => t.column.desc()
  • 複数の項目:orderByList: (t) => [t.name.desc(), t.id.asc()]
  • リレーションで並べ替え:orderBy: (t) => t.ceo.name;件数で並べ替え:orderBy: (t) => t.employees.count()
  • ページネーション:オフセット方式には limit と offset を使用します。カーソル方式では where: (t) => t.id > lastId と orderBy: (t) => t.id、limit を組み合わせます

リレーション(include、attach、detach)

include を使って関連オブジェクトを取得します。

var employee = await Employee.db.findById(session, id,
  include: Employee.include(address: Address.include()));

var company = await Company.db.findById(session, id,
  include: Company.include(
    employees: Employee.includeList(
      where: (t) => t.name.ilike('a%'),
      orderBy: (t) => t.name,
      limit: 10,
      includes: Employee.include(address: Address.include()),
    ),
  ));

オブジェクトリレーションを持つモデルには、attach/detach 用の専用メソッドもあります。

await Company.db.attachRow.employees(session, company, employee);
await Company.db.attach.employees(session, company, [e1, e2]);
await Company.db.detachRow.employees(session, employee);

attach/detach するオブジェクトには id が設定されている必要があります(通常は事前にデータベースから取得します)。

トランザクション

await session.db.transaction((tx) async {
  await Company.db.insertRow(session, company, transaction: tx);
  await OtherModel.db.updateRow(session, other, transaction: tx);
});

トランザクション内のすべての DB 呼び出しには tx を使用してください。

行ロック

トランザクションが必要です。find/findFirstRow/findById に lockMode と transaction を渡します。

  • LockMode.forUpdate(排他)、forNoKeyUpdate、forShare、forKeyShare
  • LockBehavior.wait(デフォルト)、noWait(例外をスロー)、skipLocked(スキップ。ジョブキューに適しています)
  • 読み込みを伴わないロック:Company.db.lockRows(session, where: ..., lockMode: ..., transaction: tx)

SQLite は一度に 1 つの書き込みトランザクションしかサポートしないため、SQLite で行をロックしようとしても何も行われません。

実行時パラメーター

Postgres のパラメーターをグローバルに設定するには、Serverpod の初期化時に runtimeParametersBuilder: (params) => [params.searchPaths(['my_schema', 'public'])] を指定します。トランザクション単位では await tx.setRuntimeParameters(...) を使用します。検索パス、ベクトルインデックスのオプション、カスタムの MapRuntimeParameters に使用できます。コネクションプーリングのため、セッション単位では設定できません。

生 SQL

生 SQL クエリを実行するには、以下のいずれかのメソッドを使用します。

late DatabaseResult result;
result = await session.db.unsafeQuery(query, parameters: parameters);
result = await session.db.unsafeSimpleQuery(query);

late int rowsAffected;
rowsAffected = await session.db.unsafeExecute(query, parameters: parameters);
rowsAffected = await session.db.unsafeSimpleExecute(query);

標準的な CRUD には ORM を優先し、SQL の文字列連結ではなく、ユーザー入力をパラメーター化してください。

クライアント側データベース

少なくとも 1 つのモデルで database: client または database: all が設定されている場合、生成された Client クラスには、SQLite データベースファイル用の ClientDatabaseSession を返す createSession メソッドが追加されます。Flutter では、次のようにデータベースを開きます。

import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:my_project_client/my_project_client.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Set the client URL to the server URL
  final client = Client(clientUrl);

  // Resolve the database path
  final path = await resolveDatabasePath('app.db');

  // Store the session in your state manager to later use on database operations.
  final session = await client.createSession(path, isDebugMode: kDebugMode);
}

Future<String> resolveDatabasePath(String fileName) async {
  if (kIsWeb) return fileName;
  final dir = await getApplicationSupportDirectory();
  return p.join(dir.path, fileName);
}

クライアントパッケージの依存関係に serverpod_database パッケージを追加する必要があることに注意してください。

コメント