Oracle Databaseに保存されているデータを操作するiOSアプリを、Oracle Backend for Firebase(Fusabase)とOpenAI Codexを使って作成します。本記事の著者もそうですが、技術者としての背景がバックエンドだったりDBAだったりすると、iOSアプリやAndroidといったモバイル・アプリケーションの作成はハードルが高く、取り組むのに躊躇してしまいます。
本記事では、Oracle Backend for Firebase(Fusabase)をiOSアプリケーションに組み込んで、Oracle Databaseを操作するアプリケーションを作成してみます。iOSアプリケーションはSwiftで記述しますが、コードはすべてOpenAI Codexが生成します。
(同様の手順でCodexで生成したAndroidとWebアプリについて、記事の末尾に追記しました。)
OpenAI Codexは以下のようなiOSアプリケーションを作成しました。
Oracle APEXのサンプル・データセットに含まれるプロジェクト・データを、本アプリケーションが使用するデータセットとして使用しています。
サンプル・データセットのプロジェクト・データはすべてリレーショナル・データベースの
表(複数の列を持つ行の集合)です。Oracle Backend for Firebase(Fusabase)では、Relational to collection mappingを定義して、これらの表を
コレクション(JSONドキュメントの集合)としてアクセスできるようにします。
Codexにアプリケーションの要件として、以下の文書を与えています。
スキーマ定義として与えているfusabase-schema.mdは以下です。この文書は、Codexにfusabase-cliを使ってコレクションにアクセスさせて生成させています。
スキーマ定義の本文に
inspect_fusabase_schema.shによりと記載されている、fusabase-cliを再帰的に呼び出し、コレクションやサブコレクション、およびそれらに含まれているドキュメントを取り出しているスクリプトです。こちらもCodexで生成しています。
Codexに要件を与えてiOSアプリケーションを生成するまでの準備を含め、以下の作業を行なっています。
- APEXのサンプル・データセットをOracle Backend for Firebaseのプロジェクトを構成したスキーマにインストールした上で、APEXのサンプル・アプリケーションを作成します。
- SQL Developer Extension for VS Codeを使って、インストールされたサンプル・スキーマのER図を作成し親子関係を確認します。Oracle Backend for FirebaseのRelational to collection mappingでは、サブ・コレクションの親コレクションは1つだけです。表の参照制約では参照先となる表(親)はいくつあっても良いため、サブ・コレクションの親コレクションは定義されている参照制約だけでは決められません。
- Oracle Backend for Firebaseのコンソールより、ステップ2で決めたRelational to collection mappingを定義します。
- Relational to collection mappingが定義されると、コレクションに対応するJSON Duality Viewが生成されます。生成されたJSON Duality Viewはトリガーによるサーバー側の更新を考慮せずにメタデータETAGを生成するため、ドキュメントの更新時に同時実行制御のエラーが発生します(ORA-42698: Cannot updateJSON Relational Duality View, Concurrent modification detected)。そのため、トリガーでアップデートされる列をGENERATED USINGで囲んでETAGの計算対象外にするよう、JSON Duality Viewの定義を変更します。
- 以上の準備を行なった後に、iOSアプリケーションを作成するフォルダをプロジェクトのフォルダとして、Codexのプロジェクトを作成します。プロジェクトのフォルダにアプリケーションの要件を配置します。
- 定義されたRelational to collection mappingにfusabase-cliでアクセスし取り出したコレクションのデータを元にして、参照するスキーマ情報を記述したファイルをプロジェクトのフォルダ以下に生成します。
- CodexにiOSアプリケーションの要件とスキーマ情報を元にして、iOSアプリケーションを生成するよう指示します。
- 生成されたiOSアプリケーションをXcodeで開き、アプリケーションを実行します。思った動きと違っていたり、こうして欲しいという要求をCodexに伝えてアプリケーションを改良します。
以下より、それぞれの作業について紹介します。
サンプル・データセットのインストール
APEXワークスペースへのスキーマの割り当ては、管理サービスのワークスペースの管理に含まれる、ワークスペースとスキーマの割当ての管理から実施します。
APEXワークスペースとして
APEXDEV、Oracle Backend for Firebaseのプロジェクトが構成されているスキーマが
TESTUSERである場合、ワークスペースがAPEXDEV、スキーマがTESTUSERの行が存在しています。
この状態でAPEXのワークスペースにサインインし、スキーマTESTUSERにサンプル・データセットをインストールします。
ユーティリティのサンプル・データセットを開きます。
プロジェクト・データのインストールをクリックします。
サンプル・データセットの管理のダイアログが開きます。
データセットのインストール先のスキーマとして、Oracle Backend for Firebaseのプロジェクトが構成されているスキーマを選択します。今回の例ではTESTUSERです。
Oracle Backend for FirebaseのRelational to collection mappingでは、対象がプロジェクトが構成されているスキーマに存在している表に限定されています。そのため、Oracle Backend for Firebaseでアクセスする表は、スキーマAPEXDEVに配置できません。
次へ進みます。
データセットのインストールを実行します。
サンプル・データセットがスキーマTESTUSERにインストールされます。そのデータセットを扱うAPEXアプリケーションを作成します。
アプリケーションの作成を実行します。
アプリケーション作成ウィザードが開きます。機能はすべて不要なので、すべてチェックをクリックして、チェックを外します。
以上でアプリケーションを作成します。
作成するアプリケーションには、表EBA_PROJECT_TASK_TODOS、EBA_PROJECT_TASK_LINKS、EBA_PROJECT_COMMENTSの一覧と編集を行うページが含まれていません。これらは必要に応じて追加することとし、本記事の手順には含めません。
アプリケーションDemonstration - Projectsが作成されます。
アプリケーションを実行し、Tasksのファセット検索のページを表示します。インストールされたサンプル・データセットの内容を確認できます。
SQL Developer Extension for VS Codeによるスキーマの確認
SQL Developer Extension for VS CodeのER図描画機能を使って、インストールされたサンプル・データセットの構造を確認します。
Oracle Backend for Firebaseのプロジェクトが構成されたスキーマへの接続を作成します。その作成された接続(以下のスクリーンショットではtestuser)の上でコンテキスト・メニューを表示し、Open Diagramを実行します。
左のツリーより、今回の対象である表
EBA_PROJECTS、
EBA_PROJECT_COMMENTS、
EBA_PROJECT_MILESTONES、
EBA_PROJECT_STATUS、
EBA_PROJECT_TASKS、
EBA_PROJECT_TASK_TODOS、
EBA_PROJECT_TASK_LINKSの7つの表を選択します。
選択した表をDiagramまでドラッグし、Shiftキーを押し、そしてShiftキーを押したままDiagram上でドロップします。
ドロップした表のER図が生成されます。
見やすくなるように、表の配置を調整します。
スキーマの構造を見ると、表EBA_PROJECT_TASK_TODOSおよびEBA_PROJECT_TASK_LINKSにはそれぞれ、EBA_PROJECTSとEBA_PROJECT_TASKSを対象とした外部キー(列PROJECT_IDとTASK_ID)が定義されています。TODOおよびLinkは、同一プロジェクトの異なるタスクに付け替えることを可能にしているのかもしれませんが、Relational to collection mappingではどちらかを親コレクションとして選択する必要があります。
今回は表EBA_PROJECT_TASKSに表EBA_PROJECTSへの外部キーが定義されていることより、表EBA_PROJECT_TASK_TODOS、EBA_PROJECT_TASK_LINKSの親はEBA_PROJECT_TASKSとします。
表EBA_PROJECTSは表EBA_PROJECT_STATUSを参照していますが、親コレクションとはいえないので、それぞれ独立したコレクションとして定義します。
以上より、コレクションとサブコレクションは以下のように設定します。
- eba_projects
- eba_project_milestones
- eba_project_comments
- eba_project_tasks
- eba_project_task_todos
- eba_project_task_links
- eba_project_status
Relational to collection mappingの定義
Oracle Backend for Firebaseのコンソールを開きます。
DatabaseのRelational to collection mappingのタブを選択し、Link existing tablesをクリックします。
Link to existing relational tableにて、リレーショナル表を元にしたコレクションを作成します。
コレクションとサブ・コレクションの関係を間違えないように、親となる表にあるサブ・コレクションの追加ボタンをクリックし、サブ・コレクションとなる表を追加します。
- 最上位のコレクションであるeba_projectsとeba_project_statusはAdd tableをクリックして追加します。
- eba_project_milestones、eba_project_comments、eba_project_tasksは、eba_projectsのサブ・コレクションの追加ボタンをクリックして追加します。
- eba_project_task_todos、eba_project_task_linksは、eba_project_tasksのサブ・コレクションの追加ボタンをクリックして追記します。
7つの表をコレクションとして登録して、
Saveをクリックします。
リレーショナル表がコレクションとして作成されます。Expand allをクリックして、作成されたコレクションの階層が想定通りになっているかを確認します。
JSON Duality Viewの更新
Relational to collection mappingにより作成されたJSON Duality Viewの定義を確認します。Relational to collection mappingにより作成されたコレクションは、プロジェクトが構成されているスキーマに作成された表BAAS_COLLECTION_METADATAに定義されています。
select table_name, path from baas_collection_metadata where table_type = 'rel';
SQL> select table_name, path from baas_collection_metadata where table_type = 'rel';
TABLE_NAME PATH
______________________________ _______________________________________________________________________
EBA_PROJECT_COMMENTS$BAAS /eba_projects/_docId/eba_project_comments
EBA_PROJECT_MILESTONES$BAAS /eba_projects/_docId/eba_project_milestones
EBA_PROJECT_TASKS$BAAS /eba_projects/_docId/eba_project_tasks
EBA_PROJECT_TASK_LINKS$BAAS /eba_projects/_docId/eba_project_tasks/_docId/eba_project_task_links
EBA_PROJECT_TASK_TODOS$BAAS /eba_projects/_docId/eba_project_tasks/_docId/eba_project_task_todos
EBA_PROJECT_STATUS$BAAS /eba_project_status
EBA_PROJECTS$BAAS /eba_projects
7行が選択されました。
SQL>
TABLE_NAMEとなっていますが、PATHでアクセスされるコレクションの実体となるJSON Duality Viewの名前です。
このJSON Duality Viewを作成したDDLは、列CONDITIONに記載されています。
select condition from baas_collection_metadata where table_type = 'rel';SQL> select condition from baas_collection_metadata where table_type = 'rel';
CONDITION
____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
create or replace json relational duality view "EBA_PROJECT_COMMENTS$BAAS" as select json {'project_id':"PROJECT_ID", 'comment_text':"COMMENT_TEXT", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid': GENERATED USING ( select '/' ||SYS_MAKE_OID_FROM_PK("EBA_PROJECTS"."ID") from "EBA_PROJECTS" where "EBA_PROJECTS"."ID" = "EBA_PROJECT_COMMENTS"."PROJECT_ID")} from "EBA_PROJECT_COMMENTS" with insert delete update
create or replace json relational duality view "EBA_PROJECT_MILESTONES$BAAS" as select json {'project_id':"PROJECT_ID", 'name':"NAME", 'description':"DESCRIPTION", 'due_date':"DUE_DATE", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid': GENERATED USING ( select '/' ||SYS_MAKE_OID_FROM_PK("EBA_PROJECTS"."ID") from "EBA_PROJECTS" where "EBA_PROJECTS"."ID" = "EBA_PROJECT_MILESTONES"."PROJECT_ID")} from "EBA_PROJECT_MILESTONES" with insert delete update
create or replace json relational duality view "EBA_PROJECT_TASKS$BAAS" as select json {'project_id':"PROJECT_ID", 'milestone_id':"MILESTONE_ID", 'name':"NAME", 'description':"DESCRIPTION", 'assignee':"ASSIGNEE", 'start_date':"START_DATE", 'end_date':"END_DATE", 'cost':"COST", 'is_complete_yn':"IS_COMPLETE_YN", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid': GENERATED USING ( select '/' ||SYS_MAKE_OID_FROM_PK("EBA_PROJECTS"."ID") from "EBA_PROJECTS" where "EBA_PROJECTS"."ID" = "EBA_PROJECT_TASKS"."PROJECT_ID")} from "EBA_PROJECT_TASKS" with insert delete update
create or replace json relational duality view "EBA_PROJECT_TASK_LINKS$BAAS" as select json {'project_id':"PROJECT_ID", 'task_id':"TASK_ID", 'link_type':"LINK_TYPE", 'url':"URL", 'application_id':"APPLICATION_ID", 'application_page':"APPLICATION_PAGE", 'description':"DESCRIPTION", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid': GENERATED USING ( select '/' ||SYS_MAKE_OID_FROM_PK("EBA_PROJECTS"."ID") || '/' || SYS_MAKE_OID_FROM_PK("EBA_PROJECT_TASKS"."ID") from "EBA_PROJECTS","EBA_PROJECT_TASKS" where "EBA_PROJECTS"."ID" = "EBA_PROJECT_TASKS"."PROJECT_ID" and "EBA_PROJECT_TASKS"."ID" = "EBA_PROJECT_TASK_LINKS"."TASK_ID")} from "EBA_PROJECT_TASK_LINKS" with insert delete update
create or replace json relational duality view "EBA_PROJECT_TASK_TODOS$BAAS" as select json {'project_id':"PROJECT_ID", 'task_id':"TASK_ID", 'name':"NAME", 'description':"DESCRIPTION", 'assignee':"ASSIGNEE", 'is_complete_yn':"IS_COMPLETE_YN", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid': GENERATED USING ( select '/' ||SYS_MAKE_OID_FROM_PK("EBA_PROJECTS"."ID") || '/' || SYS_MAKE_OID_FROM_PK("EBA_PROJECT_TASKS"."ID") from "EBA_PROJECTS","EBA_PROJECT_TASKS" where "EBA_PROJECTS"."ID" = "EBA_PROJECT_TASKS"."PROJECT_ID" and "EBA_PROJECT_TASKS"."ID" = "EBA_PROJECT_TASK_TODOS"."TASK_ID")} from "EBA_PROJECT_TASK_TODOS" with insert delete update
create or replace json relational duality view "EBA_PROJECT_STATUS$BAAS" as select json {'code':"CODE", 'description':"DESCRIPTION", 'display_order':"DISPLAY_ORDER", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid':'_docId'} from "EBA_PROJECT_STATUS" with insert delete update
create or replace json relational duality view "EBA_PROJECTS$BAAS" as select json {'status_id':"STATUS_ID", 'name':"NAME", 'description':"DESCRIPTION", 'project_lead':"PROJECT_LEAD", 'budget':"BUDGET", 'completed_date':"COMPLETED_DATE", 'created':"CREATED", 'created_by':"CREATED_BY", 'updated':"UPDATED", 'updated_by':"UPDATED_BY", 'OID' : SYS_MAKE_OID_FROM_PK("ID"),'_id':"ID",'parent_oid':'_docId'} from "EBA_PROJECTS" with insert delete update
7行が選択されました。
SQL>
このDDLに含まれるトリガーで更新される監査列(CREATED、CREATED_BY、UPDATED、UPDATED_BY)と、念の為parent_oid: '_docId'となっている列定義をGENERATED USINGで囲みETAGの計算対象外とします。トークンがもったいないとも思いましたが、これも上記の検索結果をファイルにスプールして、Codexに指示して監査列と_docIdをGENERATED USINGで囲ませています。
修正したDDLは以下になります。
このDDLをOracle Backend for Firebaseのプロジェクトを構成したスキーマで実行します。
SQL> @json-duality-views.sql
View "EBA_PROJECT_COMMENTS$BAAS"は作成されました。
View "EBA_PROJECT_MILESTONES$BAAS"は作成されました。
View "EBA_PROJECT_TASKS$BAAS"は作成されました。
View "EBA_PROJECT_STATUS$BAAS"は作成されました。
View "EBA_PROJECTS$BAAS"は作成されました。
View "EBA_PROJECT_TASK_LINKS$BAAS"は作成されました。
View "EBA_PROJECT_TASK_TODOS$BAAS"は作成されました。
SQL>
スキーマ情報の生成
このスクリプトは以下のプロンプトを与えてCodexに作成させたものです。プロンプトに例となるgetコマンドやqueryコマンドを与えていますが、すでにコレクションの内容を取り出すスクリプトが作成できているため、これらの内容については説明を割愛します。
スクリプトinspect_fusabase_schema.shはbashで実行します。zshでは動作しないようです。デフォルトでは各コレクションから3件ずつドキュメントを取得します。もっとサンプルを増やしたい場合はSCHEMA_SAMPLE_LIMITの指定を上げます。
SCHEMA_SAMPLE_LIMIT=100 bash inspect_fusabase_schema.sh
生成されたドキュメントを元にCodexにスキーマ情報を生成させるため、スクリプトもCodexに指示して実行させた方が良いかもしれません。
出力されたドキュメントから、Codexにスキーマ情報を生成させます。
以上でiOSアプリケーションが扱うスキーマ情報の準備もできました。
CodexによるiOSアプリケーションの生成
CodexがiOSアプリケーションをビルドできるように、あらかじめホストであるMacにXcodeとCommand Line Toolをインストールしておきます。Oracle Backend for FirebaseのiOS向けのLiveLabsを実施していると必要な準備はできているので、最初にLiveLabsに取り組んでおくことをお勧めします。
Oracle Backend for Firebaseのコンソールを開き、iOSアプリケーションを登録します。
App NicknameはProjectsとします。Register Appを実行します。
SDKのインストール方法の説明を表示するだけなので、どれを選択しても登録されるアプリケーションに違いはありませんが、今回の作業で使用するSDK installationの方法であるGithubを選択します。
Nextをクリックします。
画面に表示されたSDK構成をfusabasa-config.jsonの値として、iOSアプリケーションの要件に含めます。
Doneをクリックして、アプリケーションの登録を終了します。
作成するiOSアプリケーションの要件にはユーザーを登録する機能は含んでいません。Oracle Backend for Firebaseのコンソールより、あらかじめ、テスト用のアカウントを作成しておきます。
今回はとりあえずiOSアプリケーションの作成だけが目的なので、DatabaseのSecurity rulesでは制限をかけません。
match /{document=**} { allow read, write: if true;}
StorageのSecurity rulesについても同様です。
match /{document=**} { allow read, write: if true;}
Codexでプロジェクトを作成します。ローカルを選択して次へ進みます。
プロジェクト名を設定し、iOSアプリケーションを作成するフォルダをプロジェクトに紐づけます。
Codexのプロジェクトを作成します。
作成したフォルダにアプリケーションの要件を記述したファイル(今回はspec.md)とスキーマ情報を取り出すスクリプトinspect_fusabase_schema.shを配置します。
最初にスキーマ情報を生成させました。スキーマ情報を出力するにあたって、アプリケーションの要件を与えておくと、生成される情報の精度が上がるようです。
スキーマ情報が出力されたら、iOSアプリケーションを作成するように指示しました。
推論過程を見てみると、私の作業環境にダウンロードされていたiOSアプリケーションのLiveLabsとiOSのFusabase SDKのコードを参照していました。なので、これらをダウンロードしてプロジェクトで参照できるようにしていると、より精度が高いiOSアプリケーションのアプリケーションが生成されると思われます。
git clone https://github.com/KillianLynch/fusabase-livelabs-ios.git
git clone https://github.com/oracle/fusabase-ios-sdk.git
iOSアプリケーションのデバッグと更新
Codexが生成したiOSアプリケーションを、Xcodeで開いて実行します。
iOSシミュレータが起動し、作成したアプリケーションが実行されます。
こうじゃないな、と思うところを指示してアプリケーションを更新します。
この作業を繰り返し、iOSアプリケーションの完成度を上げていきます。
本記事では、Oracle Databaseを操作するiOSアプリをOracle Backend for FirebaseとOpenAI Codexで作成する手順を紹介しました。
本記事は以上になります。
追記
ほとんど同じ手順でspec.mdにiOSの代わりにAndroidを指定して、CodexにAndroidアプリを生成させてみました。spec.mdの変更部分は以下です。
# Projects Androidアプリケーション要件
## 概要
Oracle Backend for Firebase(Fusabase)をバックエンドとして使用する、プロジェクト管理用の Android アプリケーション。
アプリケーション名: `Projects`
対象プラットフォーム: Android / Java
認証方式: Basic 認証
AndroidアプリケーションはAndroid Studioで扱える形式で作成してください。
## Fusabase 接続設定
app/fusabase-config.jsonとして以下を記述します。
```json
{
"schema": "testuser",
"app_name": "com.oracle.fusabase.projects",
"app_type": "ANDROID",
"app_id": "5A79FA609C221B92E063020012ACF003",
"objs_type": "dbfs",
"project_id": "59C1663EAB6F13DFE063020012AC5621",
"storage_bucket": "dbfs_BQYKZXSFPIUUWSH",
"auth_type": "base",
"auth_id": "59C1663EAB7313DFE063020012AC5621",
"ords_host": "http://10.0.2.2:8181/ords/testuser/"
}
```
Fusabase iOS SDK は以下のリポジトリから取得する。
https://github.com/oracle/fusabase-android-sdk.git
出来上がったアプリケーションは以下です。アプリの見かけについて、きちんと指示を出せばもう少し見栄えの良いアプリケーションになると思います。
Webアプリケーションはspec.mdの変更を以下に変更して、Codexでアプリケーションを生成させてみました。
# Projects Webアプリケーション要件
## 概要
Oracle Backend for Firebase(Fusabase)をバックエンドとして使用する、プロジェクト管理用の Web アプリケーション。
アプリケーション名: `Projects`
対象プラットフォーム: Web / JavaScript
認証方式: Basic 認証
Webアプリケーションとして扱える形式で作成してください。
## Fusabase 接続設定
fusabase-config.jsとして以下を記述します。
```json
{
"schema": "testuser",
"app_name": "ProjectWeb",
"app_type": "WEB",
"app_id": "5A7B034D62F4B85DE063020012ACC847",
"objs_type": "dbfs",
"project_id": "59C1663EAB6F13DFE063020012AC5621",
"storage_bucket": "dbfs_BQYKZXSFPIUUWSH",
"auth_type": "base",
"auth_id": "59C1663EAB7313DFE063020012AC5621",
"ords_host": "http://localhost:8181/ords/testuser/"
}
```
Fusabase Web SDK は以下のリポジトリから取得する。
https://github.com/oracle/fusabase-js-sdk.git
出来上がったアプリケーションは以下です。こちらもアプリの見かけについて、きちんと指示を出せばもう少し見栄えの良いアプリケーションになると思います。
どちらにしても、Oracle Backend for Firebase側でコレクションを準備してくれていれば、アプリケーションを作成する側は、バックエンドがOracle Databaseかどうかを意識することはありません。UIのデザインについては、巷で評判の高い方法を採用することができます。
完