DB Tester仕様 - データベース操作
Operation列挙型
パッケージ: io.github.seijikohara.dbtester.api.operation.Operation
利用可能な操作
| 操作 | 説明 | ユースケース |
|---|---|---|
NONE | データベース操作なし | 読み取り専用の検証 |
UPDATE | 主キーで既存行を更新 | 既存データの変更 |
INSERT | 新規行を挿入 | 空テーブルまたは追加 |
UPSERT | Upsert(挿入または更新) | 挿入と更新の混在 |
DELETE | 主キーで特定行を削除 | 選択的な削除 |
DELETE_ALL | テーブルの全行を削除 | シーケンスリセットなしのクリア |
TRUNCATE_TABLE | テーブルを切り捨て | シーケンスリセット付きのクリア |
CLEAN_INSERT | 全削除後に挿入(デフォルト) | 新鮮なテストデータ |
TRUNCATE_INSERT | 切り捨て後に挿入 | シーケンスリセット付きの新鮮なデータ |
操作の詳細
NONE
データベース操作を実行しません。
動作:
- データセット内のすべてのテーブルをスキップ
- データベース状態を変更しない
ユースケース: 以前のテストからデータが存在するExpectedDataSetのみのテスト、または手動セットアップシナリオ。
UPDATE
主キーで識別される既存行を更新します。
生成されるSQL: UPDATE table SET columns = ? WHERE pk_columns = ?
動作:
- 主キーで行を照合
- 非キーカラムを更新
- データベースに存在しない行は無視
ユースケース: 既存テストデータの変更やテスト内での状態遷移。
要件:
- テーブルに主キーが定義されていること
- データセットに主キーカラムが含まれていること
INSERT
既存データを変更せずに新規行を挿入します。
生成されるSQL: INSERT INTO table (columns) VALUES (?)
動作:
- データセットから各行を挿入
- 主キー重複違反時は失敗
- 既存行には影響しない
ユースケース: 既存データを持つテーブルへの追加、または特定の行追加を必要とするテスト。
制約: ターゲット行が空であるか、一意のキーが必要。
UPSERT
Upsert操作(挿入または更新)を実行します。INSERTとUPDATEを単一の論理操作に組み合わせます。
生成されるSQL:
- 更新の試行:
UPDATE table SET col2 = ?, col3 = ? WHERE col1 = ? - 挿入のフォールバック:
INSERT INTO table (col1, col2, col3) VALUES (?, ?, ?)
動作:
- 各行に対して、まず
WHERE句で主キーを使用して更新を試行 - 更新が0行に影響した場合、行を挿入
- データセット定義の先頭カラムが主キーとして扱われる
- 残りのすべてのカラムは更新文の
SET句に含まれる
ユースケース: 既存データが部分的に存在する混合シナリオ、増分データセットアップ、または冪等なテストデータ準備。
要件:
- データセットファイル(CSVヘッダー、JSONキー順序、YAMLキー順序)の先頭カラムが主キーであること
- テーブルにデータセットの先頭カラムと一致する主キーカラムがあること
規約:
本フレームワークは先頭カラムを主キーとする規約を使用します。データセットファイルでは主キーカラムを先頭カラムとして定義してください:
ID,NAME,EMAIL
1,alice,alice@example.com
2,bob,bob@example.comこの例では、IDがWHERE句の主キーとして使用されます。フレームワークは以下のSQLを生成します:
UPDATE USERS SET NAME = ?, EMAIL = ? WHERE ID = ?(更新の試行)INSERT INTO USERS (ID, NAME, EMAIL) VALUES (?, ?, ?)(挿入のフォールバック)
DELETE
主キーで識別される特定行を削除します。
生成されるSQL: DELETE FROM table WHERE pk_columns = ?
動作:
- データセットの主キーで行を照合
- 一致する行のみを削除
- 他の行は変更されない
ユースケース: 特定のテストレコードの削除や個別行のクリーンアップ。
要件: データセットに主キーカラムが含まれていること。
DELETE_ALL
参照されているテーブルの全行を削除します。
生成されるSQL: DELETE FROM table
動作:
- データセット内の各テーブルから全行を削除
- ID/シーケンスカラムをリセットしない
- 外部キー制約を尊重(失敗する場合あり)
ユースケース: シーケンスを保持しながらテーブルをクリア、または挿入専用操作のセットアップ。
テーブル処理順序: テーブルは外部キーの逆順で処理。
TRUNCATE_TABLE
テーブルを切り捨て、サポートされている場合はIDカラムをリセットします。
生成されるSQL: TRUNCATE TABLE table
動作:
- テーブルから全行を削除
- ID/自動インクリメントカラムをリセット
- 外部キーの動作はデータベース依存
ユースケース: シーケンスを含む完全なテーブルリセット、またはパフォーマンス重視のクリーンアップ。
データベースサポート:
| データベース | IDリセット | FK処理 |
|---|---|---|
| H2 | 対応 | CASCADEが必要 |
| MySQL | 対応 | FKチェックの無効化が必要 |
| PostgreSQL | 対応 | CASCADEが必要 |
| SQL Server | 対応 | FK参照がないことが必要 |
| Oracle | 対応 | CASCADEが必要 |
CLEAN_INSERT
全行を削除してからデータセットの行を挿入します。
等価操作: DELETE_ALLに続いてINSERT
動作:
- 各テーブルから全行を削除(FK逆順)
- データセットから全行を挿入(FK順)
ユースケース: 標準的なテスト準備(デフォルト操作)。決定論的な初期状態の確立。
実行順序:
- DELETEフェーズ: 子テーブルが先
- INSERTフェーズ: 親テーブルが先
TRUNCATE_INSERT
テーブルを切り捨ててからデータセットの行を挿入します。
等価操作: TRUNCATE_TABLEに続いてINSERT
動作:
- 各テーブルを切り捨て(CASCADEが必要な場合あり)
- データセットから全行を挿入
ユースケース: シーケンスリセット付きのテスト準備、またはパフォーマンス最適化されたセットアップ。
実行フロー
準備フェーズ
- 設定された場所からデータセットファイルを読み込む
- シナリオマーカーで行をフィルタリング
- 設定された戦略に基づいてテーブル順序を解決
- 設定された操作を実行
- トランザクションをコミット
期待フェーズ
- 期待データセットファイルを読み込む
- シナリオマーカーで行をフィルタリング
- データベースから実際のデータを読み取る
- 期待値と実際のデータを比較
- 不一致をテスト失敗として報告
操作の実行
テーブル順序戦略
TableOrderingStrategy列挙型
パッケージ: io.github.seijikohara.dbtester.api.operation.TableOrderingStrategy
TableOrderingStrategy列挙型は、データベース操作中のテーブル処理順序の決定方法を制御します。
利用可能な戦略
| 戦略 | 説明 |
|---|---|
AUTO | 最適な順序を自動的に決定(デフォルト) |
LOAD_ORDER_FILE | load-order.txtファイルを使用(見つからない場合はエラー) |
FOREIGN_KEY | FKベースの順序付けにJDBCメタデータを使用 |
ALPHABETICAL | テーブル名でアルファベット順にソート |
戦略の詳細
AUTO(デフォルト)
本フレームワークは以下の順序で戦略を試みます。
- LOAD_ORDER_FILE - データセットディレクトリに
load-order.txtが存在する場合、それを使用 - FOREIGN_KEY - JDBCメタデータをクエリして外部キー依存関係を解決
- ALPHABETICAL - 大文字小文字を区別しないアルファベット順にフォールバック
AUTO戦略は最も柔軟な動作を提供し、ほとんどのユースケースに適しています。
LOAD_ORDER_FILE
データセットディレクトリにload-order.txtファイルが必要です。ファイルが存在しない場合、フレームワークはDataSetLoadExceptionをスローします。
ユースケース: テーブル順序を明示的に制御し、順序が常に指定されていることを保証したい場合。
@DataSet(tableOrdering = TableOrderingStrategy.LOAD_ORDER_FILE)
void testWithExplicitOrder() { }FOREIGN_KEY
JDBCデータベースメタデータ(DatabaseMetaData.getExportedKeys())を使用して外部キー依存関係を分析し、トポロジカルソートを実行します。
動作:
- 親テーブル(外部キーによって参照されるテーブル)が子テーブルより先に処理される
- 外部キーメタデータを取得できない場合、元のテーブル順序にフォールバック
- 循環依存が検出された場合、警告をログに出力して宣言順を使用
ユースケース: 外部キー制約が適切に定義されており、自動順序付けが望ましいデータベース。
@DataSet(tableOrdering = TableOrderingStrategy.FOREIGN_KEY)
void testWithFkOrdering() { }ALPHABETICAL
テーブルはアルファベット昇順(大文字小文字を区別しない)でソートされます。
ユースケース: テーブル順序が重要でない場合(FK制約がない)、または依存関係のないシナリオでの決定論的な順序付け。
@DataSet(tableOrdering = TableOrderingStrategy.ALPHABETICAL)
void testWithAlphabeticalOrder() { }アノテーションでの使用
// デフォルトのAUTO戦略
@DataSet
void testDefault() { }
// @DataSetで明示的な戦略
@DataSet(tableOrdering = TableOrderingStrategy.FOREIGN_KEY)
void testWithFkOrder() { }
// @ExpectedDataSetでの戦略(検証順序に影響)
@ExpectedDataSet(tableOrdering = TableOrderingStrategy.ALPHABETICAL)
void testExpectedDataSetOrder() { }
// 組み合わせて使用
@DataSet(operation = Operation.CLEAN_INSERT, tableOrdering = TableOrderingStrategy.LOAD_ORDER_FILE)
@ExpectedDataSet(tableOrdering = TableOrderingStrategy.ALPHABETICAL)
void testBothPhases() { }テーブル順序
load-order.txtによる手動順序付け
テーブル処理順序を制御する推奨方法はload-order.txtファイルです。load-order.txtファイルは、テーブルが処理される正確な順序を指定します。
ファイル形式と使用方法の詳細については、データフォーマット - 読み込み順序を参照してください。
外部キーの認識
load-order.txtファイルが存在しない場合、本フレームワークはデータベースメタデータを使用してテーブル依存関係を解決します。
- 各テーブルに対して
DatabaseMetaData.getExportedKeys()をクエリ - 依存関係グラフを構築
- テーブルをトポロジカルソート
順序付けルール
| 操作 | 順序 |
|---|---|
| INSERT, UPSERT | 親テーブルが先(FK順) |
| DELETE, DELETE_ALL | 子テーブルが先(FK逆順) |
| TRUNCATE_TABLE | 子テーブルが先 |
| CLEAN_INSERT | DELETE逆順、INSERT順方向 |
| TRUNCATE_INSERT | TRUNCATE逆順、INSERT順方向 |
順序解決の優先順位
テーブル順序はTableOrderingStrategyによって決定されます。AUTO(デフォルト)を使用する場合、優先順位は以下の通りです:
- 手動順序付け: データセットディレクトリの
load-order.txtファイル - FKベースの順序付け: データベースメタデータを使用した自動解決
- アルファベット順: 他の順序付けが利用できない場合のフォールバック
注意: DB Testerはload-order.txtファイルを自動生成しません。詳細はテーブル順序戦略を参照してください。
循環依存
循環外部キー参照を持つテーブルの場合の処理:
- 依存関係グラフで循環を検出
- 警告をログに出力
- データセット宣言順で処理
トランザクション処理
デフォルト動作
- 操作中はAutoCommitが無効
- データセットごとに単一トランザクション
- 成功時はコミット、失敗時はロールバック
トランザクション境界
| フェーズ | トランザクションスコープ |
|---|---|
| 準備 | 全テーブルで単一トランザクション |
| 期待 | 読み取り専用(トランザクションなし) |
コネクション管理
DataSourceからコネクションを取得- 現在のAutoCommit設定を保存
- AutoCommit = falseに設定
- 操作を実行
- コミットまたはロールバック
- AutoCommit設定を復元
- コネクションをクローズ
エラー回復
例外発生時:
- トランザクションをロールバック
- コネクションをクローズ
- 例外を
DatabaseOperationExceptionでラップ - テストフレームワークに伝播
SQL識別子の検証
検証ルール
本フレームワークは、SQL文に識別子を挿入する前にすべてのSQL識別子(テーブル名とカラム名)を検証します。不正な入力によるSQL構文エラーやセキュリティ問題を防止します。
識別子は以下のパターンに一致する必要があります:
^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?$有効な識別子:
| 例 | 説明 |
|---|---|
USERS | 大文字のみの標準名 |
user_accounts | アンダースコア付きの小文字 |
_temp_table | アンダースコアで始まる |
public.users | スキーマ修飾名 |
Table123 | 数字を含む混合ケース |
無効な識別子:
| 例 | 理由 |
|---|---|
123table | 数字で始まる |
user-accounts | ハイフンを含む |
user name | スペースを含む |
table;DROP | セミコロンを含む |
エラーハンドリング
無効な識別子が検出されると、説明的なメッセージを含むIllegalArgumentExceptionがスローされます:
Invalid SQL identifier: 'user-accounts'. Identifiers must start with a letter or underscore and contain only letters, digits, and underscores.この例外は通常、データベース操作中にDatabaseOperationExceptionでラップされます。
識別子のソース
識別子は以下から派生します:
| ソース | 例 |
|---|---|
| データセットファイル名 | USERS.csv → テーブル USERS |
| CSV/TSVヘッダー行またはJSON/YAMLキー | ID,NAME,EMAIL → カラム |
@DataSetSource属性 | カスタムパスと名前 |
検証エラーを避けるため、すべてのデータファイルが命名規則に従っていることを確認してください。
型変換
文字列からSQL型への変換
本フレームワークは、データセットファイルの文字列値を対応するSQL型に変換します。
| ターゲットSQL型 | 変換メソッド |
|---|---|
VARCHAR, CHAR, TEXT | 文字列のまま |
INTEGER, SMALLINT | Integer.parseInt() |
BIGINT | Long.parseLong() |
DECIMAL, NUMERIC | new BigDecimal() |
REAL, FLOAT | Float.parseFloat() |
DOUBLE | Double.parseDouble() |
BOOLEAN, BIT | Boolean.parseBoolean() |
DATE | LocalDate.parse() |
TIME | LocalTime.parse() |
TIMESTAMP | LocalDateTime.parse() |
BLOB, BINARY | Base64デコード |
CLOB | 文字列のまま |
NULL処理
- 空のCSVフィールド = SQL NULL
- 適切なSQL型で
PreparedStatement.setNull()を使用
日付/時刻形式
| 型 | 受け入れられる形式 |
|---|---|
| DATE | yyyy-MM-dd |
| TIME | HH:mm:ss, HH:mm:ss.SSS |
| TIMESTAMP | yyyy-MM-dd HH:mm:ss, yyyy-MM-dd HH:mm:ss.SSS |
LOB処理
| 型 | 変換 |
|---|---|
| BLOB | Base64文字列 → byte[] |
| CLOB | String → Reader |