Databricks AppsでDatabricks SQLウェアハウスに接続する

# はじめに
ここまでの記事で、Databricks Appsでアプリケーションを作成したり、SQLウェアハウスを構築したりしてきました。
この記事では、これらを組み合わせて、Databricks Appsで作成したアプリケーションからDatabricks SQLウェアハウスに接続する方法を解説します。

# 前段
- [Azure DatabricksでDatabricks Appsを検証するための環境構築](https://qiita.com/kei1-dev/items/c5d1f21687bd23489166)
- [Databricks Apps でカスタムアプリのデプロイ](https://qiita.com/kei1-dev/items/3e749e2519535f8c0d56)
- [FastAPIにSPAアプリケーションを統合する](https://qiita.com/kei1-dev/items/09fb914e724016b654eb)
- [Databricks AppsにSPAアプリを含めたカスタムアプリを作成する](https://qiita.com/kei1-dev/items/3e11a598436f52e0ff06)
- [Databricks WorkspaceとGitHubを連携してDatabricks Appsにデプロイする](https://qiita.com/kei1-dev/items/beb8d7f379828253827b)
- [Databricks WebTerminalを有効化する](https://qiita.com/kei1-dev/items/344072967fcafa54a4a9)
- [DatabricksでSQLウェアハウスを構築して、SQLAlchemy+AlembicでDB Migrationを行う](https://qiita.com/kei1-dev/items/b1338134729aa8ea59eb)

# 手順
※ コードベースは過去の記事のものを使用しています
- [Databricks AppsにSPAアプリを含めたカスタムアプリを作成する](https://qiita.com/kei1-dev/items/3e11a598436f52e0ff06)
- [DatabricksでSQLウェアハウスを構築して、SQLAlchemy+AlembicでDB Migrationを行う](https://qiita.com/kei1-dev/items/b1338134729aa8ea59eb)

1. 必要なライブラリの追加
   
   ```bash
   # SQLAlchemyをインストールします。これはPythonのSQLツールキットで、データベースとの対話を容易にします。
   pip install sqlalchemy
   # Alembicをインストールします。これはSQLAlchemyのためのデータベースマイグレーションツールです。
   pip install alembic
   # Databricks専用のSQLAlchemyアダプタをインストールします。
   pip install databricks-sqlalchemy
   # Databricks SQL Connectorをインストールします。これはDatabricksとの効率的なデータベース接続を提供します。
   pip install databricks-sql-connector
   ```

2. BackendにCRUD用APIを作成
   例)
    ```python
    @app.get("/api/items/", response_model=List[ItemResponse])
    def read_items(
        q: Optional[str] = None,
        skip: int = 0,
        limit: int = 100,
        db: Session = Depends(get_db)
    ):
    logger.info(f"Fetching items with query: {q}, skip: {skip}, limit: {limit}")
    
    query = db.query(Item)
    if q:
        query = query.filter(
            or_(
                Item.item_id.ilike(f"%{q}%"),
                Item.query.ilike(f"%{q}%")
                )
            )
        items = query.offset(skip).limit(limit).all()
    return items
    ```

3. BackendのDB接続用処理を作成

AlembicのMigrationではカタログとスキーマを指定していましたが、アプリで設定すると接続がうまくいかなかったため、
他の3項目を指定して接続します。

```python
    DATABASE_URL = f"databricks://token:{DATABRICKS_TOKEN}@{DATABRICKS_SERVER_HOSTNAME}?http_path={DATABRICKS_HTTP_PATH}"
    engine = create_engine(DATABASE_URL, connect_args={"verify": False}, echo=True)
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

    def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
```

3. app.yamlにDB接続用の環境変数を設定
   環境変数に登録するには、app.yamlのenvに以下のように設定します。
    ```yaml
    command: [
      "gunicorn",
      "main:app",
      "-k",
      "uvicorn.workers.UvicornWorker",
      "-w",
      "4",
    ],
    env:
        - DATABRICKS_SERVER_HOSTNAME_PROD 
          <Databricksのホスト名>
        - DATABRICKS_HTTP_PATH_PROD
          <DatabricksのHTTPパス>
        - DATABRICKS_TOKEN_PROD
          <Databricksのアクセストークン>
    ```

4. FrontendからBackendのAPIを呼び出す画面を作成
   以下画像のような画面を作成しました
   ![4_Frontend.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/479886/7b02655e-d413-7a7a-408e-35a388b9f06f.png)


5. Frontendのビルド&Backendのstaticフォルダへビルドしたファイルを配置
   ```bash
   # フロントエンドディレクトリに移動
   cd frontend
   # 静的ファイルを生成
   npm run generate
   # 古い静的ファイルをバックエンドディレクトリから削除
   rm -rf ../backend/static
   # 新しく生成した静的ファイルをバックエンドのstaticディレクトリにコピー
   cp -r .output/public ../backend/static
   ```

6. Gitコミット&Githubへのデプロイ後、Databricks Appsへデプロイ
   参考:[Databricks WorkspaceとGitHubを連携してDatabricks Appsにデプロイする](https://qiita.com/kei1-dev/items/beb8d7f379828253827b)
    ```bash
    git add .
    git commit -m "Databricks Appsへデプロイ"
    git push
    cd ./backend
    databricks apps deploy --app-name <app_name>  --source-code-path /Workspace/Users/<username>/<repo_name>/...
    ```

# つまづきポイント
- DatabricksのUnity CatalogでCreateIndexを実行すると、`The command(s): CreateIndex are not supported in Unity Catalog.`が発生
  - マイグレーションファイルでCreateIndexを実行していた
  - Unity Catalogをやめてしまうとマイグレーションの実行に失敗する
  - マイグレーションファイルでIndex作成部分を手動削除することで解決
- アプリ起動時に`sqlalchemy.exc.NoSuchModuleError: Cant load plugin: sqlalchemy.dialects:databricks`が発生
  - databricks-sqlalchemyかdatabricks-sql-connectorのあたりがないことが原因
- Insert文の実行時に、`[DELTA_INSERT_COLUMN_MISMATCH] Column id is not specified in INSERT`が発生
  - テーブルのidをオートインクリメントの設定にしていた
  - オートインクリメントの設定を削除し、インサートの度にidを指定することで解決
  - UUIDに変更
- UUIDに変更した後、`INVALID_NON_DETERMINISTIC_EXPRESSIONS] The operator expects a deterministic expression, but the actual expression is "columndefinition`が発生
  - IDが非決定性式であることが原因
  - UUIDを文字列として扱うように修正

# 補足
- Databricks AppsへのアクセスはDatabricksへの認証済みでないとアクセスできないのか検証
  - シークレットウィンドウで開くと、認証画面が表示される
  - apiを実行するとエラーではなく、空レスポンスが返ってくる
-  ということで、Databricks Appsはアプリ側で認証を実装しなくてもDatabricksの認証機構を使ってアクセス制御ができることがわかった

# まとめ
この記事では、Databricks AppsからDatabricks SQLウェアハウスへの接続方法について説明しました。

主なポイントは以下の通りです：

1. SQLAlchemy、Alembic、Databricks専用のSQLAlchemyアダプタなど、必要なライブラリの導入
2. バックエンドでのCRUD APIの実装とDB接続処理の設定
3. app.yamlでの環境変数設定による接続情報の管理
4. フロントエンドからバックエンドAPIを呼び出す実装

また、実装時の主な課題とその解決方法も記載しました：
- Unity CatalogでのCreateIndex実行時の制限
- SQLAlchemy関連の依存関係の問題
- IDカラムの扱いに関する問題（オートインクリメント、UUID）

# 参考
- [Databricks SQLAlchemy](https://docs.databricks.com/ja/dev-tools/sqlalchemy.html#language-SQL%C2%A0warehouse)
- [Databricks Python SQL Connector](https://docs.databricks.com/ja/dev-tools/python-sql-connector.html)