RaspberryPIからOracleDBに接続する

## 概要
LinuxからOracleへ接続するためのパッケージが、Raspbian(というかDebian系列)向けにリリースされていないので、Oracleが提供するOracleLinuxを使用する。
[Raspberry PiデバイスへのOracle Linuxのインストール](https://docs.oracle.com/ja/learn/oracle-linux-install-rpi/)

## 手順
### 1. OSイメージのダウンロード
[Oracle Linux for Arm Downloads Raspberry Pi](https://www.oracle.com/linux/downloads/linux-arm-downloads.html)
バージョン 8.1 と 9.5 があるが、9.5 だと色々と不足があるので、8.1が安定。
とりあえず確認した限り、aarch64(ラズパイのCPU)向けのInstantClientがまだ提供されていません。（2025/03/25時点）

### 2. SDカードの作成
ダウンロードしたディスクイメージを使用して、起動用のSDカードを作成します。
Raspberry PI Imager を使用するのであれば、[OS]のリストの一番下の[Use Custom]
を選択し、ダウンロードしたディスクイメージのファイルを選択して作成できます。

### 3. OSの初期設定
概要のURLを参考にして、OSの初期設定を行ってください。
日本語の言語パックは入っていないのでインストールする必要があります。
```shell
dnf install langpacks-ja
localectl set-locale ja_JP.utf8
localectl set-keymap jp
```

:::note info
OSの系列について
Oracle Linux は、RaspbianのようなDebian系列ではなく、RedHat系列のデストリビューションです。（一応、RHEL完全互換らしいです。）
RedHat系列とDebian系列の主な違いとして、パッケージの管理を apt ではなく dnf(yum) で行うことが挙げられます。
:::

### 4. DNFリポジトリの解放
dnf(yum) コマンドを使用する際のリポジトリですが、デフォルトでは開発用のパッケージなどが制限されているため、これを開放しておきます。
```shell
vi /etc/yum.repos.d/oracle-linux-ol8.repo
```
[ol8_codeready_builder] および [ol8_distro_builder] 内の enabled=0 を 1 に変更します。

### 5. InstantClientのインストール
Oracleデータベースに接続するための InstantClient をインストールします。
直接 dnf コマンドで直接インストールすることが出来なかったので、一旦ダウンロードしてからのインストールになります。
（dnf のリポジトリを登録すれば、直接できるのかも？）
[Home > Oracle Linux 8 Repositories > Oracle Instant Client](https://yum.oracle.com/repo/OracleLinux/OL8/oracle/instantclient/aarch64/index.html)
バージョンが、19.10 / 19 / 23 とありますのでお好みで。
・basic / basiclite （基本機能。どちらか必須。liteは英語のみの軽量版）
・devel （C/C++用ヘッダファイルなど）
・odbc / jdbc （Java/Phythonなどで接続したい場合）
・sqlplus / tools （コマンドライン用ツール）

一括で実行する場合は、以下のとおり
・wget が無かったのでインストール
・作業用フォルダとして /home/InstantClient を作成
・対象：v19.23 basic/devel/sqlplusのみ
```shell
dnf install wget
mkdir /home/InstantClient
cd /home/InstantClient
wget https://yum.oracle.com/repo/OracleLinux/OL8/oracle/instantclient/aarch64/getPackage/oracle-instantclient19.23-basic-19.23.0.0.0-1.aarch64.rpm
wget https://yum.oracle.com/repo/OracleLinux/OL8/oracle/instantclient/aarch64/getPackage/oracle-instantclient19.23-devel-19.23.0.0.0-1.aarch64.rpm
wget https://yum.oracle.com/repo/OracleLinux/OL8/oracle/instantclient/aarch64/getPackage/oracle-instantclient19.23-sqlplus-19.23.0.0.0-1.aarch64.rpm
dnf install oracle-instantclient19.23-basic-19.23.0.0.0-1.aarch64.rpm
dnf install oracle-instantclient19.23-devel-19.23.0.0.0-1.aarch64.rpm
dnf install oracle-instantclient19.23-sqlplus-19.23.0.0.0-1.aarch64.rpm
```

### 6. 環境変数の設定など
いつものヤツです。

・環境変数 ORACLE_HOME の設定
全ユーザー対象なので、.bashrcなどでは無く、profileで設定します。
/etc/profile.d 配下に oracle.sh を作成し、下記の内容を設定。
```shell
ORACLE_HOME=/usr/lib/oracle/19.23/client64
export ORACLE_HOME
```
※ /usr/lib/oracle 配下のフォルダはバージョンによって変わります。
※ 設定後、ログインし直さないと反映されないので気をつけてください。

・接続設定ファイル（sqlnet.ora,tnsnames.ora）を作成 
ORACLE_HOME 配下に network/admin とフォルダを作成し配下に sqlnet.ora,tnsnames.ora を作成します。
※ /usr/lib/oracle/19.23/client64/lib/network フォルダが存在するので、そちらへのリンクを作成してもいいです。

・C++ ライブラリのシンボリックリンクを作成
/usr/lib/oracle/19.23/client64/lib 配下にいくつかの動的ライブラリが配置されています。
C++用のクラスが定義されているライブラリ(libocci)は、本体（libocci.so.19.1）は
存在するのですが、このままだとリンカが認識してくれませんので、シンボリックリンクを作成しておく必要があります。
```shell
cd /usr/lib/oracle/19.23/client64/lib
ln -s libocci.so.19.1 libocci.so
```

### 7. プログラムの作成
・GNU C/C++ など、開発環境をインストール。
    ```
    dnf install gcc gcc-c++ make
    ```

・サンプルプログラム
```makefile:makefile
#-------------- basic settings ---------------
TARGET = OraTest
SRCS   = $(shell ls *.cpp)
OBJS   = $(SRCS:.cpp=.o)
CC = g++
CFLAGS = -Wall -O3
CFLAGS += -I/usr/include/oracle/19.23/client64
LDFLAGS = -L/usr/lib/oracle/19.23/client64/lib
LIBS = -locci -lclntsh

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $(TARGET) $(OBJS) $(LIBDIR) $(LIBS)

$(OBJS): $(SRCS)
	$(CC) $(CFLAGS) -c $(SRCS)

all: clean $(OBJS) $(TARGET)

.PHONY: clean
clean:
	-rm -f $(OBJS) $(TARGET) *.d
```

```cpp:main.cpp
#include <string>
#include <iostream>
#include <sstream>

#include <occi.h>
#include <oratypes.h>

using namespace std;
using namespace oracle::occi;

int main(int argh, char* argv[])
{
    // Open Connection
    string username = "scott";
    string password = "tiger";
    string connection_string = "myora";
    Environment* ora_env = Environment::createEnvironment(Environment::DEFAULT);
    Connection* ora_conn = ora_env->createConnection(username, password, connection_string);

    // for SELECT
    string sql_select = "SELECT ID, EXEC_TIME FROM SAMPLE_TABLE";
    Statement* ora_stmt = ora_conn->createStatement(sql_select);
    ResultSet* ora_rs = ora_stmt->executeQuery();
    int max_id = 0;
    while (ora_rs->next()) {
        int id = ora_rs->getNumber(1);
        string exec = ora_rs->getString(2);
        cout << id << ":" << exec << endl;
        if (max_id < id) max_id = id;
    }
    ora_stmt->closeResultSet(ora_rs);
    ora_conn->terminateStatement(ora_stmt);

    // for INSERT/UPDATE/DELETE
    stringstream sql_insert;
    sql_insert << "INSERT INTO SAMPLE_TABLE VALUES (" << max_id + 1 << ", SYSDATE)";
    ora_stmt = ora_conn->createStatement(sql_insert.str());
    unsigned int effect_row = ora_stmt->executeUpdate();
    ora_conn->terminateStatement(ora_stmt);

    // Close Connection
    ora_env->terminateConnection(ora_conn);
    Environment::terminateEnvironment(ora_env);

    return 0;
}
```