# ETC決済システムとOracle DB連携のODBCソースコード
以下は、Oracle Databaseを使用したETC決済システムのODBC連携コードです。MySQL版からの主な変更点は、Oracle特有のSQL構文とデータ型への対応です。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#include <sql.h>
#include <sqlext.h>
#define MAX_CARDS 10000
#define MAX_TRANSACTIONS 100000
#define CARD_NUM_LENGTH 16
#define SQL_RESULT_LEN 240
#define SQL_RETURN_CODE_LEN 1000
// ODBC接続ハンドル
SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc = SQL_NULL_HDBC;
SQLHSTMT hstmt = SQL_NULL_HSTMT;
// ODBC接続初期化 (Oracle用)
bool init_oracle_odbc_connection() {
SQLRETURN ret;
// 環境ハンドル割り当て
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "環境ハンドルの割り当てに失敗\n");
return false;
}
// ODBCバージョン設定
ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "ODBCバージョン設定に失敗\n");
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return false;
}
// 接続ハンドル割り当て
ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "接続ハンドルの割り当てに失敗\n");
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return false;
}
// Oracle接続文字列 (DSNを使用しない直接接続)
SQLCHAR connStr[] = "DRIVER={Oracle ODBC Driver};DBQ=//hostname:port/service_name;UID=username;PWD=password";
// データソースに接続
ret = SQLDriverConnect(hdbc, NULL, connStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
SQLCHAR sqlstate[6], message[SQL_RETURN_CODE_LEN];
SQLINTEGER native_error;
SQLSMALLINT length;
SQLError(henv, hdbc, SQL_NULL_HSTMT, sqlstate, &native_error,
message, sizeof(message), &length);
fprintf(stderr, "Oracle接続失敗: %s\n", message);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return false;
}
// ステートメントハンドル割り当て
ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "ステートメントハンドルの割り当てに失敗\n");
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return false;
}
// Oracleセッション設定
ret = SQLExecDirect(hstmt, (SQLCHAR*)"ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'", SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "セッション設定に失敗\n");
// 致命的なエラーではないので続行
}
return true;
}
// ODBC接続解放
void free_odbc_connection() {
if (hstmt != SQL_NULL_HSTMT) {
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
if (hdbc != SQL_NULL_HDBC) {
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}
if (henv != SQL_NULL_HENV) {
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
}
// ETCカード登録 (Oracle版)
bool oracle_register_etc_card(const char *card_number, const char *user_name,
const char *vehicle_type, int balance) {
SQLRETURN ret;
char query[SQL_RETURN_CODE_LEN];
// Oracle用のINSERT文 (シーケンスを使用)
snprintf(query, sizeof(query),
"INSERT INTO etc_cards (card_number, user_name, vehicle_type, balance) "
"VALUES ('%s', '%s', '%s', %d)",
card_number, user_name, vehicle_type, balance);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
SQLCHAR sqlstate[6], message[SQL_RETURN_CODE_LEN];
SQLINTEGER native_error;
SQLSMALLINT length;
SQLError(henv, hdbc, hstmt, sqlstate, &native_error,
message, sizeof(message), &length);
fprintf(stderr, "カード登録エラー: %s\n", message);
return false;
}
// Oracleでは明示的なコミットが必要
ret = SQLExecDirect(hstmt, (SQLCHAR*)"COMMIT", SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "コミットに失敗\n");
return false;
}
return true;
}
// 料金計算 (Oracle版)
int oracle_calculate_toll(const char *highway_section, const char *vehicle_type, time_t exit_time) {
SQLRETURN ret;
char query[SQL_RETURN_CODE_LEN];
char column_name[20];
int rate = 0;
// 料金表から基本料金を取得 (Oracle用SQL)
snprintf(query, sizeof(query),
"SELECT standard_rate, light_rate, large_rate FROM toll_rates "
"WHERE section = '%s'", highway_section);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
return 0;
}
// 結果を取得
ret = SQLFetch(hstmt);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
SQLINTEGER standard_rate, light_rate, large_rate;
SQLINTEGER indicator;
SQLGetData(hstmt, 1, SQL_C_LONG, &standard_rate, 0, &indicator);
SQLGetData(hstmt, 2, SQL_C_LONG, &light_rate, 0, &indicator);
SQLGetData(hstmt, 3, SQL_C_LONG, &large_rate, 0, &indicator);
if (strcmp(vehicle_type, "standard") == 0) {
rate = (int)standard_rate;
} else if (strcmp(vehicle_type, "light") == 0) {
rate = (int)light_rate;
} else if (strcmp(vehicle_type, "large") == 0) {
rate = (int)large_rate;
}
}
SQLCloseCursor(hstmt);
// 時間帯による割引 (Oracleの時間関数を使用する場合もある)
struct tm *timeinfo = localtime(&exit_time);
int hour = timeinfo->tm_hour;
if (hour >= 22 || hour < 6) {
rate = (int)(rate * 0.7);
}
return rate;
}
// 通行料金処理 (Oracle版)
bool oracle_process_toll_payment(const char *card_number, const char *highway_section,
time_t entry_time, time_t exit_time) {
SQLRETURN ret;
char query[SQL_RETURN_CODE_LEN];
char entry_time_str[20], exit_time_str[20];
struct tm *entry_tm = localtime(&entry_time);
struct tm *exit_tm = localtime(&exit_time);
// 時間を文字列に変換 (OracleのTO_DATE関数用)
strftime(entry_time_str, sizeof(entry_time_str), "%Y-%m-%d %H:%M:%S", entry_tm);
strftime(exit_time_str, sizeof(exit_time_str), "%Y-%m-%d %H:%M:%S", exit_tm);
// 1. カード情報と残高を確認
int balance = 0;
bool active = false;
snprintf(query, sizeof(query),
"SELECT balance, active FROM etc_cards WHERE card_number = '%s' FOR UPDATE",
card_number);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
ret = SQLFetch(hstmt);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
SQLINTEGER bal, act;
SQLINTEGER indicator;
SQLGetData(hstmt, 1, SQL_C_LONG, &bal, 0, &indicator);
SQLGetData(hstmt, 2, SQL_C_LONG, &act, 0, &indicator);
balance = (int)bal;
active = (bool)act;
}
}
SQLCloseCursor(hstmt);
if (!active) {
return false;
}
// 2. 料金計算
char vehicle_type[20];
snprintf(query, sizeof(query),
"SELECT vehicle_type FROM etc_cards WHERE card_number = '%s'",
card_number);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
ret = SQLFetch(hstmt);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
SQLCHAR vt[20];
SQLINTEGER indicator;
SQLGetData(hstmt, 1, SQL_C_CHAR, vt, sizeof(vt), &indicator);
strncpy(vehicle_type, (char*)vt, sizeof(vehicle_type));
}
}
SQLCloseCursor(hstmt);
int amount = oracle_calculate_toll(highway_section, vehicle_type, exit_time);
if (balance < amount) {
return false;
}
// 3. トランザクション開始 (Oracleでは自動的に開始)
// 4. 残高更新
snprintf(query, sizeof(query),
"UPDATE etc_cards SET balance = balance - %d WHERE card_number = '%s'",
amount, card_number);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
SQLExecDirect(hstmt, (SQLCHAR*)"ROLLBACK", SQL_NTS);
return false;
}
// 5. 取引記録 (Oracleのシーケンスを使用)
char transaction_id[33];
snprintf(transaction_id, sizeof(transaction_id), "%s%ld", card_number, time(NULL));
snprintf(query, sizeof(query),
"INSERT INTO transactions (transaction_id, card_number, highway_section, "
"entry_time, exit_time, amount) VALUES ('%s', '%s', '%s', "
"TO_DATE('%s', 'YYYY-MM-DD HH24:MI:SS'), "
"TO_DATE('%s', 'YYYY-MM-DD HH24:MI:SS'), %d)",
transaction_id, card_number, highway_section,
entry_time_str, exit_time_str, amount);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
SQLExecDirect(hstmt, (SQLCHAR*)"ROLLBACK", SQL_NTS);
return false;
}
// 6. トランザクションコミット
ret = SQLExecDirect(hstmt, (SQLCHAR*)"COMMIT", SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
SQLExecDirect(hstmt, (SQLCHAR*)"ROLLBACK", SQL_NTS);
return false;
}
return true;
}
// 残高追加 (Oracle版)
bool oracle_add_balance(const char *card_number, int amount) {
SQLRETURN ret;
char query[SQL_RETURN_CODE_LEN];
snprintf(query, sizeof(query),
"UPDATE etc_cards SET balance = balance + %d WHERE card_number = '%s'",
amount, card_number);
ret = SQLExecDirect(hstmt, (SQLCHAR*)query, SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
return false;
}
// Oracleでは明示的なコミットが必要
ret = SQLExecDirect(hstmt, (SQLCHAR*)"COMMIT", SQL_NTS);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
return false;
}
return true;
}
// 使用例
int main() {
if (!init_oracle_odbc_connection()) {
fprintf(stderr, "Oracle ODBC接続に失敗しました\n");
return 1;
}
// カード登録
if (!oracle_register_etc_card("1234567890123456", "Taro Yamada", "standard", 10000)) {
fprintf(stderr, "カード登録に失敗しました\n");
free_odbc_connection();
return 1;
}
// 通行処理
time_t entry_time = time(NULL) - 3600; // 1時間前
time_t exit_time = time(NULL);
if (oracle_process_toll_payment("1234567890123456", "highway1", entry_time, exit_time)) {
printf("通行料金の支払いに成功しました\n");
} else {
fprintf(stderr, "通行料金の支払いに失敗しました\n");
}
free_odbc_connection();
return 0;
}
```
## Oracle Database用の変更点
1. **接続方法**:
- `SQLDriverConnect`を使用した直接接続
- Oracle固有の接続文字列形式
2. **SQL構文**:
- `TO_DATE`関数を使用した日時変換
- OracleのFOR UPDATE句を使用した行ロック
- 明示的なCOMMIT文の追加
3. **トランザクション処理**:
- Oracleの自動トランザクション開始モデルに対応
- 明示的なCOMMIT/ROLLBACKの使用
4. **エラーハンドリング**:
- Oracle固有のエラーコード処理
- より詳細なエラーメッセージ取得
5. **セッション設定**:
- NLS_DATE_FORMATの設定で日付形式を統一
## コンパイル方法 (Linuxの場合)
```bash
gcc -o etc_payment_oracle etc_payment_oracle.c -lodbc -lodbcinst -I$ORACLE_HOME/rdbms/public -L$ORACLE_HOME/lib -lclntsh
```
## Oracle Database用のテーブル定義
```sql
-- ETCカードテーブル
CREATE TABLE etc_cards (
card_number CHAR(16) PRIMARY KEY,
user_name VARCHAR2(50) NOT NULL,
vehicle_type VARCHAR2(20) NOT NULL,
balance NUMBER(10) NOT NULL,
active NUMBER(1) DEFAULT 1 CHECK (active IN (0,1)),
registered_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- 料金テーブル
CREATE TABLE toll_rates (
section VARCHAR2(20) PRIMARY KEY,
standard_rate NUMBER(10) NOT NULL,
light_rate NUMBER(10) NOT NULL,
large_rate NUMBER(10) NOT NULL
);
-- 取引テーブル
CREATE TABLE transactions (
transaction_id VARCHAR2(32) PRIMARY KEY,
card_number CHAR(16) NOT NULL,
highway_section VARCHAR2(20) NOT NULL,
entry_time TIMESTAMP NOT NULL,
exit_time TIMESTAMP NOT NULL,
amount NUMBER(10) NOT NULL,
processed_at TIMESTAMP DEFAULT SYSTIMESTAMP,
CONSTRAINT fk_card FOREIGN KEY (card_number) REFERENCES etc_cards(card_number)
);
-- 料金表データの例
INSERT INTO toll_rates VALUES ('highway1', 1000, 800, 1500);
INSERT INTO toll_rates VALUES ('highway2', 1500, 1200, 2000);
COMMIT;
```
このコードは、Oracle Databaseの特性を活かしたETC決済システムの実装例です。実際の運用環境では、さらにパフォーマンスチューニングやセキュリティ対策が必要になります。