もくじを開く3.3Project Database

PART 3成果を保存・共有する3.3

Project Database — 構造化した成果を保存・検索する

schemaの作成から行の保存・検索、WebUIでの確認(総行数・ページ送り)、Session外からの操作、SQL制限、migration競合、書き込み結果不明時の復旧までを説明します。

Project Databaseは、顧客記録、調査結果、在庫など、agentと人間が再利用する構造化データの保存先です。行の背景や判断はShared Documentsに残します。通常Projectごとにserver管理のCloudflare D1 databaseを持ち、最初のmigrationを適用した時点で作成されます。schemaを読むだけでは作成されません。ローカルSQLiteファイルや、HTMLに自動接続されるbackendではありません。

前提と権限

操作する人入口条件
Session内のagentchat db schema / query / migrateactive Projectをcoverageに持つrunning Session。schema/SELECTは現在の横断閲覧権限でも利用可能
Session外の人間・外部agentaachat db schema / query / migrate本人のログインと、そのProjectのactive membership
人間のWebUIProjectのDatabaseタブ(閲覧のみ)Project member

Project roleがViewerならschemaとSELECTを読めます。CollaboratorとAdminはmigration、INSERT、UPDATE、DELETEも実行できます。これは被覆Projectと人間向け操作のroleです。横断閲覧を許可されたSessionは対象のmembershipなしでschemaとSELECTを読めますが、変更SQLとmigrationはCROSS_PROJECT_READ_ONLYで拒否されます。人間向けAPIではTeam ownerでもProject membershipがなければ操作できません。

Projectがactiveでない場合、DatabaseのCLI操作は受け付けません。被覆Projectと人間向け操作ではArchivedは凍結扱いのエラー、Done(completed)とPlanningはproject_not_active、deleting中は見つからない扱いになります。再開するにはProjectをIn progressへ戻します(Projects)。WebUIのDatabaseタブも、active以外ではDatabase unavailable(Database browsing is available only for active projects.)と表示して行を表示しません。

agentへD1のcredentialを渡す必要はありません。

tableを作り、成果を1件保存する(Session内)

chat dbはSession内のコマンドです。Team、Project、Database ID、Sessionを選ぶflagはありません。schemaとqueryは現在のディレクトリ、migrateはmigration fileのpathからProjectを解決します。Project rootの外や曖昧なpathでは失敗します。

acme/customer-researchをSessionのcoverageまたは横断閲覧先にある実在Projectへ置き換え、まず現在のschemaを読みます。

sh
cd aachat/teams/acme/projects/customer-research
chat db schema

応答のdataにはschema_sql、schema_version、migration_count、head_migrationが入ります。被覆Projectではread-onlyのdb/schema.sql投影も同じschemaで、空のDBなら-- aachat-schema-version: root、migration後ならsha256:...のversionで始まります。

横断閲覧では$AA_WS_DIR/aachat/teams/<team>/projects/<target>へ移動します。AA_WS_DIRはworkspace rootです。対象のdb/やschema.sql投影は不要です。schemaを読み、既存の表・列にSELECTを送ります。以下のmigration・変更SQLの例は書き込み可能なProject用です。

db/schema.sqlは編集しません。編集すると同期されず、db/_errors.mdに診断が書かれて元の内容へ戻ります。schemaを変えるときはdb/migrations/<id>.sqlを作ります。以下は空のDBであることを確認してから、db/migrations/add_customer.sqlへ保存する例です。既存schemaがある場合は、parent headerへ現在のversion全体を正確にコピーし、既存tableに合わせてSQLを直します。rootを再利用しません。

sql
-- aachat-parent: root
CREATE TABLE customer (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  status TEXT NOT NULL
);
CREATE INDEX customer_status ON customer(status);

parent headerはbyte 0から始めます。BOMやNULを含まないUTF-8の通常ファイルを使い、symlinkは使いません。migration IDは英小文字・数字・_・-で、先頭は英小文字または数字、最大128文字です。ファイルの同期はSQL sourceの保存であり、DBへの適用ではありません。同期が済んだら明示的に適用し、行を書いて読み戻します。

sh
chat db migrate db/migrations/add_customer.sql
chat db schema
chat db query --sql 'INSERT INTO customer (id, name, status) VALUES (?, ?, ?)' --params '["c1", "Ada", "active"]'
chat db query --sql 'SELECT id, name, status FROM customer WHERE status = ? ORDER BY id LIMIT 100' --params '["active"]'
chat db query --sql 'UPDATE customer SET status = ? WHERE id = ?' --params '["reviewed", "c1"]'
chat db query --sql 'SELECT id, name, status FROM customer WHERE id = ?' --params '["c1"]'

期待結果は次のとおりです。

  • migrateはdata.dispositionがapplied(すでに適用済みならreconciled)で、新しいschema_versionとmigration_countを返します。続くschemaにtableとindexが現れます。
  • queryはdata.result_set.columnsとdata.result_set.items(行の配列)、変更件数のchanges、last_row_idを返します。最初のSELECTはc1、Ada、active、UPDATE後のSELECTはc1、Ada、reviewedを返します。

各段階の実際のJSON応答を読んでから次へ進みます。同じ主キーでINSERTを繰り返しても新しい記録にはなりません。捨ててよい練習用の行ならchat db query --sql 'DELETE FROM customer WHERE id = ?' --params '["c1"]'で削除し、同じキーのSELECTで不在を確認します。

長いSQLやparameterは、--sqlの代わりに--file <path>または--stdin、--paramsの代わりに--params-file <path>(stringのJSON配列)で渡せます。queryは読み取り専用ではなく、変更SQLもそのまま実行します。

Session外から操作する

aachatにログインした人間や外部のcoding agentは、Sessionを使わずに同じDatabaseを操作できます。Session内ではこのコマンドは使えず、chat dbを使います。接続repoの外ではTeamを--teamで明示します。

sh
aachat db schema customer-research --team acme
echo '{"sql":"SELECT id, name FROM customer WHERE status = ?","params":["active"]}' \
  | aachat db query customer-research --team acme --stdin
aachat db migrate customer-research ~/aachat/teams/acme/projects/customer-research/db/migrations/add_customer.sql --team acme
  • queryは--stdinで{"sql": "...", "params": [...]}のJSONを受け取ります。
  • migrateのFILEは、共通root(~/aachat/teams/<team>/projects/<project>/、AACHAT_HOMEで変更可)または接続repoにある、対象Projectのdb/migrations/<id>.sqlに限ります。source全体のdigestを送るため、未同期や同期後に編集したsourceは失敗します。
  • 権限は本人のProject roleです(ViewerはschemaとSELECTのみ)。
  • aachat project database <project> --team <team>はschemaのcatalogを読むだけで、SQLは実行しません。

queryとmigrationのSQL規則は異なる

両方ともSQLは最大100 KiBです。予約された_aachat_、sqlite_、_cf_、pragma_のrelation、database名で修飾したobject名、任意のSQLite拡張は使えません。

操作許可する入力主な制限
querySELECT、INSERT、UPDATE、DELETEのいずれか1文。RETURNINGとOUTPUTは不可placeholderは位置指定?のみ。parameterは最大100個のstringのJSON配列で、placeholder数と一致させる
migrate下記allowlistの1〜100文parameter bindingは不可。現在のparent versionが必要

queryでDDLやPRAGMAは実行できません。SELECT結果は最大1,000行かつdecoded 1 MiBで、超過は部分結果ではなくエラーです。必要なcolumn、WHERE、小さなLIMITを指定します。LIMITを指定するなら静的な非負整数を使い、placeholderや式にしません。queryのUPDATEとDELETEではORDER BYやLIMITを使えません。

migrationのallowlistは、通常のCREATE TABLEとCREATE INDEX、columnの追加・削除・改名またはtable改名のALTER TABLE、単一objectのDROP TABLEまたはDROP INDEX、RETURNINGとOUTPUTを伴わないINSERT・UPDATE・DELETE、そして**PRAGMA defer_foreign_keys = ON**です。このPRAGMAはmigrationだけの例外です。SELECT、任意PRAGMA、view、trigger、明示的なtransaction制御はmigrationで使えません。allowlistに含まれていてもSQLiteの全方言を許可するわけではありません。

WebUIで結果を見る

ProjectのDatabaseタブを開き、画面下のバーでtableを選んでcolumnと行を確認します。閲覧専用で、SQL editorや更新操作はありません。

  • column見出しをクリックすると昇順・降順のsortが切り替わります。
  • 1ページの行数は25・50・100行(既定100行)から選び、Previous / Nextで移動します。行数の選択は狭い画面では表示されません。
  • 選択中tableの総行数を表示します。広い画面はRows 1–100 of 1,234のような行範囲と総数、狭い画面はTotal 1,234です。空のtableは0件、件数を取得できない・失敗したときはTotal unavailableと表示します。
  • table、page、行数、sortはURLに保持されます。tableが9個以上あるときはChoose tableから検索して選べます。
  • データが変わるとUpdates availableが表示され、押すと新しい行を読み込みます。Refreshでcatalogと行を読み直します。schema変更時はcatalogを再取得します。

空文字(Empty)、NULL、値の入ったセルは区別して表示されます。NULL以外のセルを押すと値の全文を開け、Copyでコピーできます。NULLのセルは詳細を開けません。

agentへtableを指定するときは、下部バーの選択中table名の横にあるCopy table referenceを押し、依頼文へ貼り付けます。Databaseのpathと正確なtable名をまとめてコピーします。

text
Database: aachat/teams/<team>/projects/<project>/db/
Table: "customers"

Table行はJSON文字列です。tableは個別ファイルではありません。空のtableや行の取得失敗時も、tableを選択できていれば参照をコピーできます。参照にページやsort条件は含まれず、アクセス権も付与しません。

表示意味と次の操作
No database yet最初のmigrationを適用していません。agentにmigrationの作成と適用を依頼します。schemaを読むだけでは初期化されません
No tables yetDBは初期化済みですがuser tableがありません。migrationでtableを作り、Refreshします
Table no longer existsURLのtableが削除・改名されました。下のバーから現在のtableを選びます
This page is too large to display1ページが1 MiBを超えました。Show 25 rowsで25行に減らします
These rows exceed the Database viewer limit25行でも1 MiBを超えます。agentへcolumnや値を絞ったSELECTを依頼します

書き込みを重複させずに復旧する

失敗時もstdoutに1つのJSON {"ok":false,"error":{"code","message",...},"data":...}が返ります。error.message、あればerror.hint、dataを読みます。chat dbの失敗ではdata.mutation_stateがrejected(受け付けられていない)かunknown(結果不明)かを示します。aachat dbではdata.change.stateのnot_appliedかunknownです。timeoutだけでは書き込み失敗と判断できません。

症状次の操作
Projectを解決できない・権限がないProject rootのcwd、Session coverageまたは現在の横断閲覧権限、Projectのactive状態、roleを確認します。ファイルを移しても権限は増えません
SELECTがDATABASE_NOT_INITIALIZED書き込み可能なProjectでは最初のmigrationを作成・適用します。横断閲覧先では自分のProject経由で人間または対象ProjectのLeadへ初期化を引き継ぎます。空schemaはD1を初期化しません
migration sourceが拒否された・見つからないdb/_errors.mdを読み、path、header、encoding、SQLを直し、同期が受理されてから適用します
sourceが同期と一致しない通常の同期が同じファイルを送り終えるのを待ち、同じmigrateを再実行します
受理済みmigrationと別内容を同じIDで保存したmigration SQLは自動mergeされません。ローカルの内容はそのまま残ります。下書きを新しいmigration IDへ移し、元のpathを同期し直してから、現在のschemaから新しいmigrationを作ります
parent versionが古いchat db schemaで現在のversionを読み、そのversion向けにmigrationを作り直します。1つのparentから進める子は1つだけで、force操作はありません
migrationの結果が不明・reconciling照合(reconciling)中はschemaの読み取りと次のmigrationが待たされますが、SELECTとDMLは続けられます。照合を待ってから、同じmigration fileでmigrateを再実行します。適用済みなら再適用せずreconciledを返します
queryの結果がunknown同じINSERT・UPDATE・DELETEを再送しません。既知の主キーと現在値をSELECTで読んで、追加の書き込みが必要か判断します
DBの準備中・混雑少し待って同じコマンドを再実行します。並列書き込みで回避しません
結果が大きすぎるcolumnと行を絞り、区切って読みます。途中までのデータを全件と見なしません
provisioningの競合運用側の修復が必要です。SQLやcredentialを含めずにエラーを報告します

ローカルのmigrationやschemaファイルを消しても適用済みデータは戻りません。即時rollbackやbackupの仕組みはありません。Project削除は受理済みqueryと未解決migrationの終了、物理DBの削除を待ちます。信頼境界とProjectsも確認してください。