From 609d71a0e9d8b2c0ac9fa56e430c1bfba8687dec Mon Sep 17 00:00:00 2001 From: jvelilla Date: Mon, 13 Oct 2014 18:45:45 -0300 Subject: [PATCH] Added nested transaction support. Added test cases showing transaction support scenarios. --- .../common/database/database_connection.e | 136 ++++++++++++++ .../tests/transactions/transaction_test_set.e | 168 ++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 persistence/implementation/mysql/tests/transactions/transaction_test_set.e diff --git a/persistence/implementation/common/database/database_connection.e b/persistence/implementation/common/database/database_connection.e index cfb9fd9..a6c2d2c 100644 --- a/persistence/implementation/common/database/database_connection.e +++ b/persistence/implementation/common/database/database_connection.e @@ -131,6 +131,142 @@ feature -- Transactions retry end + +feature -- + + is_connected_to_storage: BOOLEAN + -- Is connected to the database + do + Result := db_control.is_connected + end + +feature -- Transaction Status + + in_transaction_session: BOOLEAN + -- Is session started? + + transaction_session_depth: INTEGER + -- Depth in the transaction session + +feature -- Transaction Operation + + begin_transaction2 + -- Start session + -- if already started, increase the `transaction_session_depth' + require + in_transaction_session implies transaction_session_depth > 0 + not in_transaction_session implies transaction_session_depth = 0 + local + l_session_control: like db_control + l_retried: INTEGER + do + if l_retried = 0 then + if not in_transaction_session then + database_error_handler.reset + + check transaction_session_depth = 0 end + + debug ("database_session") + print ("..Start session%N") + end + connect -- connect the DB + if is_connected_to_storage then + in_transaction_session := True + db_control.begin -- start transaction + else + l_session_control := db_control + if not l_session_control.is_ok then + database_error_handler.add_database_error (l_session_control.error_message_32, l_session_control.error_code) + else + database_error_handler.add_database_error ("Session_not_started_error_message", 0) + end + l_session_control.reset + end + end + transaction_session_depth := transaction_session_depth + 1 + else + if l_retried = 1 then + transaction_session_depth := transaction_session_depth + 1 + if attached (create {EXCEPTION_MANAGER}).last_exception as e then + if attached {ASSERTION_VIOLATION} e then + --| Ignore for now with MYSQL ... + else + exception_as_error (e) + end + end + + in_transaction_session := False + db_control.reset + else + in_transaction_session := False + end + end + ensure + transaction_session_depth = (old transaction_session_depth) + 1 + rescue + l_retried := l_retried + 1 + retry + end + + commit2 + -- End session + local + l_retried: BOOLEAN + do + if not l_retried then + transaction_session_depth := transaction_session_depth - 1 + if transaction_session_depth = 0 then + debug ("database_session") + print ("..End session%N") + end + if is_connected_to_storage then + if not database_error_handler.has_error then + db_control.commit -- Commit transaction + else + db_control.rollback -- Rollback transaction + end + end + in_transaction_session := False + end + else + exception_as_error ((create {EXCEPTION_MANAGER}).last_exception) + in_transaction_session := False + transaction_session_depth := transaction_session_depth - 1 + db_control.reset + end + rescue + l_retried := True + retry + end + + + + rollback2 + -- End session + local + l_retried: BOOLEAN + do + if not l_retried then + transaction_session_depth := transaction_session_depth - 1 + if transaction_session_depth = 0 then + debug ("database_session") + print ("..End session%N") + end + if is_connected_to_storage then + db_control.rollback -- Rollback transaction + end + in_transaction_session := False + end + else + exception_as_error ((create {EXCEPTION_MANAGER}).last_exception) + in_transaction_session := False + transaction_session_depth := transaction_session_depth - 1 + db_control.reset + end + rescue + l_retried := True + retry + end feature -- Change Element not_keep_connection diff --git a/persistence/implementation/mysql/tests/transactions/transaction_test_set.e b/persistence/implementation/mysql/tests/transactions/transaction_test_set.e new file mode 100644 index 0000000..3199f71 --- /dev/null +++ b/persistence/implementation/mysql/tests/transactions/transaction_test_set.e @@ -0,0 +1,168 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +class + TRANSACTION_TEST_SET + +inherit + + EQA_TEST_SET + redefine + on_prepare, + on_clean + select + default_create + end + ABSTRACT_DB_TEST + rename + default_create as default_db_test + end + + +feature {NONE} -- Events + + on_prepare + -- + do + (create {CLEAN_DB}).clean_db(connection) + end + + on_clean + -- + do + end + +feature -- Test routines + + test_user_rollback + note + testing: "execution/isolated" + do + connection.begin_transaction + user_provider.new_user ("test", "test","test@admin.com") + assert ("Has user:", user_provider.has_user) + connection.rollback + assert ("Not has user:", not user_provider.has_user) + end + + test_user_node_rollback + note + testing: "execution/isolated" + do + connection.begin_transaction + user_provider.new_user ("test", "test","test@admin.com") + assert ("Has user:", user_provider.has_user) + node_provider.new_node (default_node) + node_provider.add_author (1, 1) + assert ("Has one node:", node_provider.count = 1) + connection.rollback + assert ("Not has user:", not user_provider.has_user) + assert ("Not has nodes:", node_provider.count = 0) + end + + + + test_user_node_nested_transaction_with_rollback_not_supported + note + testing: "execution/isolated" + do + connection.begin_transaction2 + user_provider.new_user ("test", "test","test@admin.com") + assert ("Has user:", user_provider.has_user) + node_provider.new_node (default_node) + assert ("Has one node:", node_provider.count = 1) + + connection.begin_transaction2 + user_provider.new_user ("test1", "test1","test1@admin.com") + assert ("Has user: test1", attached user_provider.user_by_name ("test1")) + connection.rollback2 + + connection.commit2 + assert ("Has user test:", attached user_provider.user_by_name ("test")) + assert ("Has nodes:", node_provider.count = 1) + assert ("Not has user: test1", user_provider.user_by_name ("test1") = Void) + end + + + test_user_node_nested_transaction_with_commit + note + testing: "execution/isolated" + do + connection.begin_transaction2 + user_provider.new_user ("test", "test","test@admin.com") + assert ("Has user:", user_provider.has_user) + node_provider.new_node (default_node) + assert ("Has one node:", node_provider.count = 1) + + connection.begin_transaction2 + user_provider.new_user ("test1", "test1","test1@admin.com") + assert ("Has user: test1", attached user_provider.user_by_name ("test1")) + connection.commit2 + + connection.commit2 + assert ("Has user test:", attached user_provider.user_by_name ("test")) + assert ("Has user test1:", attached user_provider.user_by_name ("test1")) + assert ("Has nodes:", node_provider.count = 1) + end + + + test_user_node_nested_transaction_with_rollback + note + testing: "execution/isolated" + do + connection.begin_transaction2 + user_provider.new_user ("test", "test","test@admin.com") + assert ("Has user:", user_provider.has_user) + node_provider.new_node (default_node) + assert ("Has one node:", node_provider.count = 1) + + connection.begin_transaction2 + user_provider.new_user ("test1", "test1","test1@admin.com") + assert ("Has user: test1", attached user_provider.user_by_name ("test1")) + connection.commit2 + + connection.rollback2 + assert ("Not Has user test:", user_provider.user_by_name ("test") = void) + assert ("Not Has user test1:", user_provider.user_by_name ("test1") = void) + assert ("Has 0 nodes:", node_provider.count = 0) + end + + + + + +feature {NONE} -- Implementation + + node_provider: NODE_DATA_PROVIDER + -- node provider. + once + create Result.make (connection) + end + + user_provider: USER_DATA_PROVIDER + -- user provider. + once + create Result.make (connection) + end + + +feature {NONE} -- Implementation Fixture Factories + + default_node: CMS_NODE + do + Result := custom_node ("Default content", "default summary", "Default") + end + + custom_node (a_content, a_summary, a_title: READABLE_STRING_32): CMS_NODE + do + create Result.make (a_content, a_summary, a_title) + end +end + +