【環境構築】OracleDB×Pro*C【今更】

# はじめに
本記事は、個人開発（勉強）環境として、Oracle環境を導入する手順まとめです。記事を書こうと思った理由は、最新の記事がどこにも見当たらず、非常に困ったため。需要あるか分かりませんが、同じような状況の同志と、今後の自分のために。
「クラウドやりたい！」、「Webデザイナーやりたい！」という人も多いと思いますが、（私みたいに）業務上必要なシーンがくるやもしれません。
記事の構成としては以下の通りです。1については、興味ない人は読み飛ばしてください。

1. 昨今のOracle
- Oracle Linux環境の構築
- Oracle DBの導入
- データベースの作成
- Oracle DBのセットアップ
- 動作確認（追記予定）

なぜLinuxかというと、今回はC言語からPro*Cを用いてDatabaseを利用します。WindowsにC言語のコンパイル環境を導入するよりもLinux構築のほうが（個人的に）容易なので、今回はOracle Linuxを選定しました。なぜOracle Linuxを選定したかについては下記参照。

- [参考：勉強の為に、LinuxにOracleをインストールしたいと思っています。CentOSかUbuntuで迷っています。。。](https://teratail.com/questions/75167)
    - ここでは、Linuxの系譜として、DebianとRHELがあり、RHEL系列に構築するほうが良いことが述べられています
- [参考：Oracle Linux 出典: フリー百科事典『ウィキペディア（Wikipedia）』](https://ja.wikipedia.org/wiki/Oracle_Linux)
    - ここでは、Oracle LinuxがRHEL系列であることが述べられています

また当記事については、先人の良記事を利用して環境構築するため、~~基本はサボります~~リンク多めですがご了承ください。リンク先で触れられていない部分については、当記事で追記しています。


最終的な目標環境は以下の通り。

```
Windows OS (各個人のバージョン)
　┗ VirtualBox (6.0.6)
　　┗ Oracle Linux (7.5.0.0.0)
　　　┗ C言語実行環境 (gccコンパイル環境)
　　　┗ Oracle Database (12c Release 2)
```


# 1. 昨今のOracle
Oracle Javaをはじめ、Oracleについては昨今いろいろ言われていますが、Oracle DBを個人で利用するくらいならまだ無償で利用可能。
その辺の話はあまり詳しくないので参考いくつか載せておきます。

- [参考：Javaの有償化問題、Java8のサポート終了問題について、2019年1月以降どうしたらいいか](https://qiita.com/machidadada/items/8137d3c079490f764524)
- [参考：2019年にJavaを利用している人は全員理解すべきことを説明してみる](https://www.orangeitems.com/entry/2018/02/08/152022)
- [参考：Oracle Java 有償化。その現状と企業の今後の対策について調査報告。](https://callcenter-trend.com/2019/01/10/oracle-java-roadmap-and-plans/)

上記記事ではOracle DBについては触れられてはいませんが、オンプレのパッケージ商品を販売したり、クラウドに注力したりと、そのうち個人利用ですらサブスクリプション課金になりそうな勢いですね。

# 2. Oracle Linux環境の構築

## 構築手順
基本的には、「[参考：VirtualBoxでOracle Linux 7.5をインストールする（だけのメモ）](https://qiita.com/mkyz08/items/891d99330ecfa5464276)」を参考に構築する。

- [Oracle](https://edelivery.oracle.com/osdc/faces/SoftwareDelivery)にアクセスする
    - Oracleユーザを作成する。住所やメールアドレスなど入力するが、個人でも会社のでも問題ない
    - 作成したユーザIDでログインすると、検索ボックスがあるため、「Oracle Linux 7.5.0.0.0」と入力し「Search」ボタンを押下する
    - 「DLP:Oracle Linux 7.5.0.0.0 ( Oracle Linux ) 」を押下すると、カートに追加される
    - 右上の「Checkout」を押下する
    - カートのSelected Softwareに「Oracle Linux7.5.0.0.0」が入っていることが確認出来たら、Platforms/Languagesを「x86 64bit」にして「Continue」ボタンを押下する
    - 「I accept the terms in the license agreement」という同意書的な文章が出てきた場合、「I accept the terms in the license agreement」にチェックをして「Continue」ボタンを押下する
    - 「V975367-01.iso　Oracle Linux Release 7 Update 5 for x86(64 bit), 4.1GB」以外のチェックをすべて外して、「Download」ボタンを押下し、ダウンロードする
- 「[参考：Windows7環境なのにdocker入れて開発することになった話【①環境設定編】](https://qiita.com/Shunichirou/items/ce7cc3c69eef690e08a8)」の「1.VirtualBoxのインストール」を参考にViertualboxをインストールする
- Oracle Linuxをイメージをマウントする
    - VM VirtualBoxを起動する。
    - 「新規(N)」をクリック
    - 名前に「Oracle」っぽい名前（OracleVMとかOracleTestとか）を入れると、OSが勝手にOracleになるので、次へ
    - メモリーサイズを任意の値で設定（4G以上あるとさくさく）し、次へ
    - ハードディスクの設定で「仮想ハードディスクを作成する」を選択し、任意のサイズで作成する（データベース入れるので40GB程度は欲しい）
    - 仮想ハードディスクの作成では、全てデフォルトのまま次へ（可変サイズ/固定サイズはどちらでも良い）
- 「[参考：VirtualBoxでOracle Linux 7.5をインストールする（だけのメモ）](https://qiita.com/mkyz08/items/891d99330ecfa5464276)」の、「Oracle Linux」に従ってインストールを進める

## 詰まりそうなPOINTのTIPS
- 日本語入力がうまくできない場合は次の記事を参照「[参考：Oracle Linux 7.4 デスクトップ上での日本語入力方法](https://hillbasesoftmemo.blogspot.com/2018/04/oracle-linux-74.html)」
- プロキシ環境下で利用している場合
    - アプリケーション→システムツール→設定→ネットワーク
    - 手動の設定画面を開く
    - HTTPプロキシ～Socksホストにproxyサーバの「`http://proxy.co.jp`」の「proxy.co.jp」部分を入力する
    - ポート番号は各社様々であるが、80番、8080番、1080番、3128番などが使われることが多い

# 3. Oracle DBの導入

## 構築手順
ここからは、2で構築したOracle Linux上で環境構築を行う。
基本的には「[参考：Oracle Linux7.5にOracle 12cR2をインストールする](https://qiita.com/mkyz08/items/945cdf72597b0e044b14)」を参考に構築する。

- Oracle Preinstall RPMを実行する
    - Oracle Preinstall RPMは、OracleDBをインストールしやすくしてくれるソフトウェア（[参考：Oracle Preinstall RPMは何をしているのか？](https://qiita.com/plusultra/items/b04c43357d996e4a08de)）
    - `$ yum install -y oracle-rdbms-server-12cR1-preinstall`
    - `$ export LANG=C`
    - `$ oracle-rdbms-server-12cR1-preinstall-verify`
- Oracle DBのインストール用のディレクトリ（俗にいうOracle homeディレクトリ）を作成する
    - `$ mkdir -p /u01/app/oraInventory`
    - `$ mkdir -p /u01/app/oracle`
    - `$ mkdir /home/oracle/tmp`
    - 必要に応じて`$ chmod`や`$ chown`でディレクトリに所有者、アクセス権限を設定する。今回はすべてルートかつフルアクセス権限で行うため、以下の設定
        - `$ chown -R root:root /u01/app/oracle`
        - `$ chown -R root:root /u01/app/oraInventory`
        - `$ chmod -R 775 /u01/app/oracle`
        - `$ chmod -R 775 /u01/app/oraInventory`
    - `$ ls -l /u01/app/`を実行し、各権限を確認

```
drwxrwxrwx.  5 root root   89  4月 24 18:21 oraInventory
drwxrwxrwx. 10 root root 4096  4月 24 18:29 oracle
```
- [Oracle](https://www.oracle.com/technetwork/jp/database/enterprise-edition/downloads/index.html)にアクセスする
    - Oracle Database 12c Release 2（俗にいうOracleDB 12cR2）のLinux x86-64の「See All」を押下
    - ライセンス同意を求められたら、ページ上部の「ライセンスに同意する」にチェック
    - 「linuxx64_12201_database.zip」を押下し、ダウンロードする
- ダウンロードした「linuxx64_12201_database.zip」を解凍し、インストールをする
    - ダウンロードフォルダ下で、`$ unzip linuxx64_12201_database.zip`を実行する
    - 回答したdatabaseフォルダ下にrunInstallerがあるので、databaseフォルダ下で`$ ./runInstaller`を実行する（通常のexeファイルの実行と同じ実行の仕方）
- 「[参考：Oracle Linux7.5にOracle 12cR2をインストールする](https://qiita.com/mkyz08/items/945cdf72597b0e044b14)」の「Oracle インストール」に従ってインストールを進める

※今回作成するOracleDBには、Oracle Linuxログインユーザが触れられれば良いので、「インベントリの作成」及び「権限のあるオペレーション・システム・グループ」については、すべてOracle Linuxへログインしているユーザ（が所属するグループ）を選択する。グループについては、デフォルトでユーザIDと同一名称のグループが作成されている。

## 詰まりそうなPOINTのTIPS
- yumのプロキシ突破について、/etc/yum.confファイルの一番下に`proxy=http://proxy.xxx.xxx:pp`を追記する
- スワップサイズでワーニングが出た場合は、以下コマンドを実施
    - [参考：Oracle Database 12cのインストール中にスワップ・サイズの警告が発生した場合 ](https://sutepulu.com/vagrant-oracle-database-install-swap-warning/)
    - `$ dd if=/dev/zero of=/var/swpfile bs=1M count=2048`
    - `$ mkswap /var/swpfile`
    - `$ swapon /var/swpfile`
    - `$ free -m`
- インストーる中に修正スクリプトを促すメッセージが出た場合は、ルートユーザに移行して、修正スクリプトを実行する
    - `$ su -`
    - `$ cd /tmp/CVU_XXXXXXXX`←警告メッセージ中に記載
    - `$ ./runfixup.sh`

# 4. データベースの作成

## 構築手順
ここからは、3でインストールしたOracle 12cR2上にデータベースを作成する。基本的には「[参考：Oracle Linux7.5にOracle 12cR2をインストールする](https://qiita.com/mkyz08/items/945cdf72597b0e044b14)」のデータベース作成以降を参考に構築する。

- 環境変数の修正のために、`.bash_profile`に以下を追記する（通常ログインユーザフォルダ直下に存在する）

```
export TMPDIR=$HOME/tmp
export TEMP=$HOME/tmp
export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=/u01/app/oracle/product/12.2.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$ORACLE_HOME/jdk/bin:${PATH}:PATH=$ORACLE_HOME/sqldeveloper/sqldeveloper/bin:
export LD_LIBRARY_PATH=$ORACLE_HOME/lib
export NLS_LANG=JAPANESE_JAPAN.UTF8
export LANG=ja_JP.UTF-8

export ORACLE_SID=testdb
```

- 環境変数の設定を反映させる
  - `$ source .bash_profile`
  - `$ sudo reboot` 念のためリブート

- `$ dbca`を実行し、データベース作成画面をGUIで起動
- 「[参考：Oracle Linux7.5にOracle 12cR2をインストールする](https://qiita.com/mkyz08/items/945cdf72597b0e044b14)」の「データベース作成」に従ってデータベースを作成する

※作成する際に入力した以下は控えておくとよい

- SID : testdb
- リスナー名 : LISTENER
- リスナー・ポート : 1521

# 5. Oracle DBのセットアップ
- sqlplusを起動し、データベースを起動
    - `$ sqlplus / as sysdba`でシステムユーザで実行
    - `SQL> show user`の結果はSYSであるはず
    - `SQL> startup`でデータベースが起動する

以下より、すべてsqlplusにSYSでログインした状態で行う


```
・ユーザの作成
SQL> create user username identified by password;

・テーブルスペースの作成
SQL> create tablespace TEST_TBS 
     datafile '/u01/app/oracle/oradata/testdb/test01.dbf' 
     size 2G 
     autoextend off;

・一時領域テーブルスペースの作成
SQL> create temporary tablespace TEST_TMP 
     tempfile '/u01/app/oracle/oradata/testdb/testtmp01.dbf' 
     size 2G 
     autoextend off;

・user権限の設定（作成したテーブルスペースにuser権限を割り当てる）
SQL> alter user username
     default tablespace TEST_TBS
     temporary tablespace TEST_TMP;

・user権限の設定（接続権限、CRUD権限の追加）
SQL> grant connect, resource to username

・表領域の無制限割り当て
SQL> alter user username quota unlimited on TEST_TBS ;

・作成したユーザでアクセス可能であるかを確認
　下記のSQLが実行できればOK
SQL> connect username/password
SQL> show user
     USERNAME
```

- 外部アプリケーションからアクセス可能にするために、listener.oraを下記の通り書き換える（HOST名称については、Oracle Linuxをインストール時に決定した名称）

```
$ cat /u01/app/oracle/product/12.2.0/dbhome_1/network/admin/listener.ora

/u01/app/oracle/product/12.2.0/dbhome_1/network/admin
listener.ora
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (ORACLE_HOME = /u01/app/oracle/product/12.2.0/dbhome_1)
      (SID_NAME = testdb)
      (SERVICE_NAME = testdb)
    )
  )

LISTENER =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = oracle12test)(PORT = 1521))
  )

ADR_BASE_LISTENER = /u01/app/oracle
```

リスナーを立ち上げる
`$ lsnrctl start`

- 今回はクライアントからのアクセスも同OSから行うため、transnames.oraも下記の通り書き換える（HOST名称については以下略）

```

LISTENER_TESTDB =
  (ADDRESS = (PROTOCOL = TCP)(HOST = oracle12test)(PORT = 1521))


TESTDB =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = oracle12test)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = testdb)
    )
  )
```

※listener.oraは、サーバ側（OracleDBをインストールした端末）に配置する、ネットワーク設定ファイル。サーバが受け取った通信を、どのデータベースに飛ばすか設定する

※transnames.oraは、クライアント側に配置する、ネットワーク設定ファイル。どのサーバのどのポートに、どういったプロトコルで接続するかを設定する

[参考：オラクル　ちょこっとリファンレンス](https://oracle-chokotto.com/ora_nwfile.html)


# 6. 動作確認

### サンプルデータ

「[参考：底辺過ぎてちょっとビビる](http://starscream.hatenablog.com/entry/20130217/1361040309)」を参考に、ソースコードを作成しました。

まずはsqlplusに、5で作成したユーザでログインする。

```
$ sqlplus / as sysdba

SQL> connect username/password

SQL> show user
     USERNAME

```

sqlplusにログインした状態で、テーブルを作成する。

```
SQL> CREATE TABLE username.MEMBER 
     (CODE varchar2 (10) NOT NULL, 
     NAME varchar2 (10) NOT NULL, 
     UPDATE_YMD varchar2 (8) NULL)

SQL> ALTER TABLE username.MEMBER ADD PRIMARY KEY 
     (CODE,
      NAME);

SQL> commit;
```

sqlplusにログインした状態で、サンプルデータをインサートする。

```
insert into username.MEMBER(
  CODE,
  NAME,
  UPDATE_YMD 
)
values
(
  '0000000000',
  'aaaaaaaaaa',
  '20190101'
);

insert into username.MEMBER(
  CODE,
  NAME,
  UPDATE_YMD 
)
values
(
  '1111111111',
  'bbbbbbbbbb',
  '20190101'
);

commit;

```

sqlplusからログアウトする。

### ソースコード作成

以下のソースコードを記載する。

```test_p.pc
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

/* SQLアクセス用定数（テスト用のためハードコーディング）*/
#define ORAUSER    "username"
#define ORAPASSWD  "password"
#define ORASYSID_T "testdb"

/* フェッチ時マックス件数マクロ定義 */
#define MAX 100

/* SQLアクセス用変数（いわゆるホスト変数）宣言部 */
/* "h_"始まりの変数はselectに利用 */
EXEC SQL BEGIN DECLARE SECTION;
    varchar username[20];
    varchar password[20];
    varchar systemid[20];
    char    h_code[MAX][10];
    char    h_name[MAX][10];
EXEC SQL END DECLARE SECTION;

/* ProCプリコンパイラ用ヘッダファイル宣言 */
EXEC SQL INCLUDE sqlca.h;

/******************************/
/* ここからメイン関数 */
/* メイン関数内での流れ */
/* 1.変数定義（ホスト変数についてはグローバル定義） */
/* 2.値の初期化 */
/* 3.DBコネクト */
/* 4.select文の発行 */
/* 5.カーソルオープン */
/* 6.フェッチ実行 */
/* 7.カーソルクローズ */
/* 8.DB切断（コミット） */
/******************************/
int main(int argc, char *argv[])
{
    /* フェッチ時のカーソルループ用変数 */
    int i;

    /* ホスト変数初期化 */
    memset(&username, 0x00, sizeof(username));
    memset(&password, 0x00, sizeof(password));
    memset(&systemid, 0x00, sizeof(systemid));
    memset(&h_code, 0x00, sizeof(h_code));
    memset(&h_name, 0x00, sizeof(h_name));

    /* oracleへの接続（SQLアクセス用変数に定数をセット） */
    strcpy(username.arr, ORAUSER);
    username.len = strlen(username.arr);
    strcpy(password.arr, ORAPASSWD);
    password.len = strlen(password.arr);
    strcpy(systemid.arr, ORASYSID_T);
    systemid.len = strlen(systemid.arr);

    /* 例外宣言→errorptへ飛ぶ */
    EXEC SQL WHENEVER SQLERROR GOTO errorpt;


    printf("user: %s, password: %s 接続開始します\n",
    username.arr,password.arr);

    /* 接続 */
    EXEC SQL CONNECT :username
      IDENTIFIED BY :password
      AT :systemid;

    printf("%s 接続成功しました\n",systemid.arr);

    printf("system: %s 検索を開始します\n", systemid.arr);

    /* カーソル宣言（selectの場合のsql発行はここ） */
    EXEC SQL AT :systemid DECLARE cursor CURSOR FOR
      SELECT CODE, NAME
        FROM MEMBER
        WHERE UPDATE_YMD > 20190101;

    printf("検索完了しました\n");

    printf("カーソルオープンします\n");

    /* カーソルオープン */
    EXEC SQL OPEN cursor;

    printf("カーソルオープンしました\n\n");

    /* データ取り出し（いわゆるフェッチ） */
    EXEC SQL FETCH cursor INTO
    :h_code,:h_name;

    /* 値が入ってない場合は抜ける */
    for(i=0;i<MAX;i++){
      if(0 == strlen(h_code[i])){
        printf("\n");
        break;
      }else{
        printf("%s,%s\n",
        h_code[i],h_name[i]);
      }
    }

    printf("フェッチ完了しました\n");

    /* カーソルクローズ */
    EXEC SQL CLOSE cursor;

    printf("カーソルクローズしました\n");

    /* commitと書いているが、切断も行っている */
    /* select以外の場合、これを書かないとrollbackされる */
    /* selectではカーソルクローズで切断されるため不要 */
    /* EXEC SQL COMMIT WORK RELEASE; */

    return 0;

    /* error時の処理 */
errorpt:
    printf("\n\n%-79s \n",sqlca.sqlerrm.sqlerrmc);
    EXEC SQL WHENEVER SQLERROR CONTINUE;
}
```

### コンパイル

- まずはプリコンパイル（pcファイルをcファイルにする）
    - `$ proc iname=test_p oname=test_p.c sqlcheck=full`
        - 実行することで、`test_p.c`が生成される
        - inameの拡張子は書いても書かなくてもよい
        - onameは書かない場合inputと同一名称（拡張子は"c"）となる
        - sqlcheck=fullオプションで、この時点で静的構文解析を行う
- オブジェクトファイルを生成する
    - `$ gcc -c test_p.c -I $ORACLE_HOME/precomp/public`
        - 実行することで、`test_p.o`が生成される
        - 4で設定した環境変数"$ORACLE_HOME"下に、precomp/publicフォルダが存在し、pro*c用のsqlcaヘッダファイルが存在する
        - "-I"オプションは、ヘッダを追加するオプション
- 実行ファイル作成
    - `$ gcc -o test_p test_p.o -L$ORACLE_HOME/lib -lclntsh`
        - 実行することで、`test_p`が生成される
        - 4で設定した環境変数"$ORACLE_HOME"下に、libフォルダが存在し、pro*c用のライブラリ群が存在する
        - "-L"オプションは、ライブラリを追加するオプション

### 実行

- 実行形式ファイルなので、`$ ./test_p`で実行する

実行結果

```
$ ./test_p
user: username, password: password接続開始します
testdb 接続成功しました
system: testdb 検索を開始します
検索完了しました
カーソルオープンします
カーソルオープンしました

0000000000,aaaaaaaaaa
1111111111,bbbbbbbbbb

フェッチ完了しました
カーソルクローズしました
```




### 詰まりそうなPOINTのTIPS

- pcコンパイルにてライブラリ参照ができないエラー発生

pcのコンパイルの時点で、複数のライブラリが呼べないような以下のようなエラーが発生

```
行0、列0でエラーが発生しました。ファイルtest_p.pc
PCC-F-02102, Cプリプロセッサ処理を実行中に致命エラーが発生しました。
行33、列11でエラーが発生しました。ファイル/usr/include/stdio.h
    33  # include <stddef.h>
    33  ..........1
    33  PCC-S-02015, 挿入ファイルをオープンできません。
行15、列10でエラーが発生しました。ファイル/usr/include/_G_config.h
    15  #include <stddef.h>
    15  .........1
    15  PCC-S-02015, 挿入ファイルをオープンできません。
行51、列11でエラーが発生しました。ファイル/usr/include/wchar.h
    51  # include <stddef.h>
    51  ..........1
    51  PCC-S-02015, 挿入ファイルをオープンできません。
行50、列10でエラーが発生しました。ファイル/usr/include/libio.h
    50  #include <stdarg.h>
    50  .........1
    50  PCC-S-02015, 挿入ファイルをオープンできません。
行307、列3でエラーが発生しました。ファイル/usr/include/libio.h
   307    size_t __pad5;
   307  ..1
   307  PCC-S-02201, 記号"size_t"が見つかりました。 次のうちの1つが入るとき:
```

環境によっては、各ヘッダファイルの配置場所が、エラーメッセージ通りの`/usr/include/`下に無いことがある。ため、設定ファイルである`pcscfg.cfg`にリンクを追加する必要がある。

 - 設定ファイルを検索`$ find / -name pcscfg.cfg`
    - `/u01/app/oracle/product/12.2.0/dbhome_1/precomp/admin/pcscfg.cfg`
    - sys_includeに、実際にヘッダファイルが存在するパスを追記する（私の場合は`/usr/lib/gcc/x86_64-redhat-linux/4.8.2/include/stddef.h`）

```pcscfg.cfg
...
sys_include=($ORACLE_HOME/precomp/public,/usr/include,/usr/lib/gcc/x86_64-redhat-linux/4.1.2/include,/usr/lib/gcc/x86_64-redhat-linux/4.4.7/include,/usr/lib/gcc/x86_64-redhat-linux/4.4.4/include,/usr/lib64/gcc/x86_64-suse-linux/4.8/include,/usr/lib/gcc/x86_64-redhat-linux/4.8.2/include/)
...
```

[参考：Pro*Cのプリコンパイルでstddef.hが見つからないエラー発生](http://mokky14.hatenablog.com/entry/2013/06/12/122722)