1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
|
/*
* 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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
HSTMT hstmt = SQL_NULL_HSTMT;
static 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);
}
static void
error_rollback_clean(void)
{
SQLRETURN rc;
/* Clean up everything */
rc = SQLFreeStmt(hstmt, SQL_CLOSE);
CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);
test_disconnect();
hstmt = SQL_NULL_HSTMT;
}
static void
error_rollback_exec_success(int arg)
{
SQLRETURN rc;
char buf[100];
printf("Executing query that will succeed\n");
/* Now execute the query */
snprintf(buf, sizeof(buf), "INSERT INTO errortab VALUES (%d)", arg);
rc = SQLExecDirect(hstmt, (SQLCHAR *) buf, SQL_NTS);
/* Print error if any, but do not exit */
CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt);
}
/* Runs a query that's expected to fail */
static void
error_rollback_exec_failure(int arg)
{
SQLRETURN rc;
char buf[100];
printf("Executing query that will fail\n");
snprintf(buf, sizeof(buf), "INSERT INTO errortab VALUES ('fail%d')", arg);
/* Now execute the query */
rc = SQLExecDirect(hstmt, (SQLCHAR *) buf, 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_STMT, hstmt);
}
/*
* Runs another query that's expected to fail.
*
* This query uses the ODBC procedure call escape syntax, because such queries
* go through a slightly different execution path in the driver.
*/
void
error_rollback_exec_proccall_failure(void)
{
SQLRETURN rc;
printf("Executing procedure call that will fail\n");
/* Now execute the query */
rc = SQLExecDirect(hstmt,
(SQLCHAR *) "{ call invalidfunction() }",
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 procedure call", SQL_HANDLE_STMT, hstmt);
}
void
error_rollback_print(void)
{
SQLRETURN rc;
/* 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(1);
/* Now trigger an error, the row previously inserted will disappear */
error_rollback_exec_failure(1);
/*
* Now rollback the transaction block, it is the responsibility of
* application.
*/
printf("Rolling back with SQLEndTran\n");
rc = SQLEndTran(SQL_HANDLE_DBC, conn, SQL_ROLLBACK);
CHECK_STMT_RESULT(rc, "SQLEndTran failed", hstmt);
/* Insert row correctly now */
error_rollback_exec_success(1);
/* 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(1);
error_rollback_exec_failure(1);
error_rollback_exec_success(1);
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");
/*
* Do a bunch of insertions and failures.
*/
error_rollback_exec_success(1);
error_rollback_exec_success(2);
error_rollback_exec_failure(-1);
error_rollback_exec_success(3);
error_rollback_exec_success(4);
error_rollback_exec_failure(-1);
error_rollback_exec_failure(-1);
error_rollback_exec_success(5);
error_rollback_exec_proccall_failure();
error_rollback_exec_success(6);
error_rollback_exec_success(7);
error_rollback_print();
/* Clean up */
error_rollback_clean();
return 0;
}
|