diff --git a/Makefile.am b/Makefile.am index c67afca..fc59deb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,7 @@ EXTRA_DIST = license.txt readme.txt readme_winbuild.txt \ test/expected/cursors_1.out \ test/expected/cvtnulldate.out \ test/expected/dataatexecution.out \ + test/expected/error-rollback.out \ test/expected/getresult.out \ test/expected/insertreturning.out \ test/expected/lfconversion.out \ @@ -129,6 +130,7 @@ EXTRA_DIST = license.txt readme.txt readme_winbuild.txt \ test/src/cursors-test.c \ test/src/cvtnulldate-test.c \ test/src/dataatexecution-test.c \ + test/src/error-rollback-test.c \ test/src/getresult-test.c \ test/src/insertreturning-test.c \ test/src/lfconversion-test.c \ diff --git a/test/Makefile.in b/test/Makefile.in index c9b82e8..920ea9e 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -1,7 +1,7 @@ TESTS = connect stmthandles select commands multistmt getresult prepare \ params notice arraybinding insertreturning dataatexecution \ boolsaschar cvtnulldate alter quotes cursors positioned-update \ - catalogfunctions bindcol lfconversion cte + catalogfunctions bindcol lfconversion cte error-rollback TESTBINS = $(patsubst %,src/%-test, $(TESTS)) TESTSQLS = $(patsubst %,sql/%.sql, $(TESTS)) diff --git a/test/expected/error-rollback.out b/test/expected/error-rollback.out new file mode 100644 index 0000000..1ae3d76 --- /dev/null +++ b/test/expected/error-rollback.out @@ -0,0 +1,32 @@ +\! ./src/error-rollback-test +Test for rollback protocol 0 +connected +Executing query that will succeed +Executing query that will fail +Failed to execute statement +22P02=ERROR: invalid input syntax for integer: "foo" +Executing query that will succeed +Result set: +1 +disconnecting +Test for rollback protocol 1 +connected +Executing query that will succeed +Executing query that will fail +Failed to execute statement +22P02=ERROR: invalid input syntax for integer: "foo" +Executing query that will succeed +Result set: +1 +disconnecting +Test for rollback protocol 2 +connected +Executing query that will succeed +Executing query that will fail +Failed to execute statement +22P02=ERROR: invalid input syntax for integer: "foo" +Executing query that will succeed +Result set: +1 +1 +disconnecting diff --git a/test/src/error-rollback-test.c b/test/src/error-rollback-test.c new file mode 100644 index 0000000..ea37eb5 --- /dev/null +++ b/test/src/error-rollback-test.c @@ -0,0 +1,221 @@ +/* + * Tests for the existing behaviors of rollback on errors: + * 0 -> Do nothing and let the application do it + * 1 -> Rollback the entire transaction + * 2 -> Rollback only the statement + */ + +#include +#include +#include + +#include "common.h" + +HSTMT hstmt = SQL_NULL_HSTMT; + +void +error_rollback_init(char *options) +{ + SQLRETURN rc; + + /* Error if initialization is already done */ + if (hstmt != SQL_NULL_HSTMT) + { + printf("Initialization already done, leaving...\n"); + exit(1); + } + + test_connect_ext(options); + rc = SQLAllocStmt(conn, &hstmt); + if (!SQL_SUCCEEDED(rc)) + { + print_diag("failed to allocate stmt handle", SQL_HANDLE_DBC, conn); + exit(1); + } + + /* Disable autocommit */ + rc = SQLSetConnectAttr(conn, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)SQL_AUTOCOMMIT_OFF, + SQL_IS_UINTEGER); + + /* Create a table to use */ + rc = SQLExecDirect(hstmt, + (SQLCHAR *) "CREATE TEMPORARY TABLE errortab (i int4)", + SQL_NTS); + CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt); + + /* And of course commit... */ + rc = SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT); + CHECK_STMT_RESULT(rc, "SQLEndTran failed", hstmt); +} + +void +error_rollback_clean(void) +{ + SQLRETURN rc; + + /* Leave if trying to clean an empty handle */ + if (hstmt == SQL_NULL_HSTMT) + { + printf("Handle is NULL, leaving...\n"); + exit(1); + } + + /* Clean up everything */ + rc = SQLFreeStmt(hstmt, SQL_CLOSE); + CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt); + test_disconnect(); + hstmt = SQL_NULL_HSTMT; +} + +void +error_rollback_exec_success(void) +{ + SQLRETURN rc; + + /* Leave if executing with an empty handle */ + if (hstmt == SQL_NULL_HSTMT) + { + printf("Cannot execute query with NULL handle\n"); + exit(1); + } + + printf("Executing query that will succeed\n"); + + /* Now execute the query */ + rc = SQLExecDirect(hstmt, + (SQLCHAR *) "INSERT INTO errortab VALUES (1)", + SQL_NTS); + + /* Print error if any, but do not exit */ + CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt); +} + +void +error_rollback_exec_failure(void) +{ + SQLRETURN rc; + + /* Leave if executing with an empty handle */ + if (hstmt == SQL_NULL_HSTMT) + { + printf("Cannot execute query with NULL handle\n"); + exit(1); + } + + printf("Executing query that will fail\n"); + + /* Now execute the query */ + rc = SQLExecDirect(hstmt, + (SQLCHAR *) "INSERT INTO errortab VALUES ('foo')", + SQL_NTS); + if (SQL_SUCCEEDED(rc)) + { + printf("SQLExecDirect should have failed but it succeeded\n"); + exit(1); + } + + /* Print error, it is expected */ + print_diag("Failed to execute statement", SQL_HANDLE_DBC, conn); +} + +void +error_rollback_print(void) +{ + SQLRETURN rc; + + /* Leave if executing with an empty handle */ + if (hstmt == SQL_NULL_HSTMT) + { + printf("Cannot execute query with NULL handle\n"); + exit(1); + } + + /* Create a table to use */ + rc = SQLExecDirect(hstmt, + (SQLCHAR *) "SELECT i FROM errortab", + SQL_NTS); + CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt); + + /* Show results */ + print_result(hstmt); +} + +int +main(int argc, char **argv) +{ + SQLRETURN rc; + + /* + * Test for protocol at 0. + * Do nothing when error occurs and let application do necessary + * ROLLBACK on error. + */ + printf("Test for rollback protocol 0\n"); + error_rollback_init("Protocol=7.4-0"); + + /* Insert a row correctly */ + error_rollback_exec_success(); + + /* Now trigger an error, the row previously inserted will disappear */ + error_rollback_exec_failure(); + + /* + * Now rollback the transaction block, it is the responsability of + * application. + */ + rc = SQLEndTran(SQL_HANDLE_DBC, conn, SQL_ROLLBACK); + CHECK_STMT_RESULT(rc, "SQLEndTran failed", hstmt); + + /* Insert row correctly now */ + error_rollback_exec_success(); + + /* Not yet committed... */ + rc = SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT); + CHECK_STMT_RESULT(rc, "SQLEndTran failed", hstmt); + + /* Print result */ + error_rollback_print(); + + /* Clean up */ + error_rollback_clean(); + + /* + * Test for rollback protocol 1 + * In case of an error rollback the entire transaction. + */ + printf("Test for rollback protocol 1\n"); + error_rollback_init("Protocol=7.4-1"); + + /* + * Insert a row, trigger an error, and re-insert a row. Only one + * row should be visible here. + */ + error_rollback_exec_success(); + error_rollback_exec_failure(); + error_rollback_exec_success(); + error_rollback_print(); + + /* Clean up */ + error_rollback_clean(); + + /* + * Test for rollback protocol 2 + * In the case of an error rollback only the latest statement. + */ + printf("Test for rollback protocol 2\n"); + error_rollback_init("Protocol=7.4-2"); + + /* + * Similarly to previous case, do insert, error and insert. This + * time two rows should be visible. + */ + error_rollback_exec_success(); + error_rollback_exec_failure(); + error_rollback_exec_success(); + error_rollback_print(); + + /* Clean up */ + error_rollback_clean(); +}