1 #include "common.h"
2
3 #include <common/test_assert.h>
4
5 /* Test transaction types */
6
7 static char software_version[] = "$Id: transaction2.c 487476 2015-12-17 19:48:39Z ucko $";
8 static void *no_unused_var_warn[] = { software_version, no_unused_var_warn };
9
10 static void
ReadErrorConn(void)11 ReadErrorConn(void)
12 {
13 ODBC_BUF *odbc_buf = NULL;
14 SQLTCHAR *err = ODBC_GET(sizeof(odbc_err)*sizeof(SQLTCHAR));
15 SQLTCHAR *state = ODBC_GET(sizeof(odbc_sqlstate)*sizeof(SQLTCHAR));
16
17 memset(odbc_err, 0, sizeof(odbc_err));
18 memset(odbc_sqlstate, 0, sizeof(odbc_sqlstate));
19 CHKGetDiagRec(SQL_HANDLE_DBC, odbc_conn, 1, state, NULL, err, sizeof(odbc_err), NULL, "SI");
20 strcpy(odbc_err, C(err));
21 strcpy(odbc_sqlstate, C(state));
22 ODBC_FREE();
23 printf("Message: '%s' %s\n", odbc_sqlstate, odbc_err);
24 }
25
26 static void
AutoCommit(int onoff)27 AutoCommit(int onoff)
28 {
29 CHKSetConnectAttr(SQL_ATTR_AUTOCOMMIT, int2ptr(onoff), 0, "S");
30 }
31
32 static void
EndTransaction(SQLSMALLINT type)33 EndTransaction(SQLSMALLINT type)
34 {
35 CHKEndTran(SQL_HANDLE_DBC, odbc_conn, type, "S");
36 }
37
38 #define SWAP(t,a,b) do { t xyz = a; a = b; b = xyz; } while(0)
39 #define SWAP_CONN() do { SWAP(HENV,env,odbc_env); SWAP(HDBC,dbc,odbc_conn); SWAP(HSTMT,stmt,odbc_stmt);} while(0)
40
41 static HENV env = SQL_NULL_HENV;
42 static HDBC dbc = SQL_NULL_HDBC;
43 static HSTMT stmt = SQL_NULL_HSTMT;
44
45 static int
CheckDirtyRead(void)46 CheckDirtyRead(void)
47 {
48 SQLRETURN RetCode;
49
50 /* transaction 1 try to change a row but not commit */
51 odbc_command("UPDATE test_transaction SET t = 'second' WHERE n = 1");
52
53 SWAP_CONN();
54
55 /* second transaction try to fetch uncommited row */
56 RetCode = odbc_command2("SELECT * FROM test_transaction WHERE t = 'second' AND n = 1", "SE");
57 if (RetCode == SQL_ERROR) {
58 EndTransaction(SQL_ROLLBACK);
59 SWAP_CONN();
60 EndTransaction(SQL_ROLLBACK);
61 return 0; /* no dirty read */
62 }
63
64 CHKFetch("S");
65 CHKFetch("No");
66 SQLMoreResults(odbc_stmt);
67 EndTransaction(SQL_ROLLBACK);
68 SWAP_CONN();
69 EndTransaction(SQL_ROLLBACK);
70 return 1;
71 }
72
73 static int
CheckNonrepeatableRead(void)74 CheckNonrepeatableRead(void)
75 {
76 SQLRETURN RetCode;
77
78 /* transaction 2 read a row */
79 SWAP_CONN();
80 odbc_command("SELECT * FROM test_transaction WHERE t = 'initial' AND n = 1");
81 SQLMoreResults(odbc_stmt);
82
83 /* transaction 1 change a row and commit */
84 SWAP_CONN();
85 RetCode = odbc_command2("UPDATE test_transaction SET t = 'second' WHERE n = 1", "SE");
86 if (RetCode == SQL_ERROR) {
87 EndTransaction(SQL_ROLLBACK);
88 SWAP_CONN();
89 EndTransaction(SQL_ROLLBACK);
90 SWAP_CONN();
91 return 0; /* no dirty read */
92 }
93 EndTransaction(SQL_COMMIT);
94
95 SWAP_CONN();
96
97 /* second transaction try to fetch commited row */
98 odbc_command("SELECT * FROM test_transaction WHERE t = 'second' AND n = 1");
99
100 CHKFetch("S");
101 CHKFetch("No");
102 SQLMoreResults(odbc_stmt);
103 EndTransaction(SQL_ROLLBACK);
104 SWAP_CONN();
105 odbc_command("UPDATE test_transaction SET t = 'initial' WHERE n = 1");
106 EndTransaction(SQL_COMMIT);
107 return 1;
108 }
109
110 static int
CheckPhantom(void)111 CheckPhantom(void)
112 {
113 SQLRETURN RetCode;
114
115 /* transaction 2 read a row */
116 SWAP_CONN();
117 odbc_command("SELECT * FROM test_transaction WHERE t = 'initial'");
118 SQLMoreResults(odbc_stmt);
119
120 /* transaction 1 insert a row that match critera */
121 SWAP_CONN();
122 RetCode = odbc_command2("INSERT INTO test_transaction(n, t) VALUES(2, 'initial')", "SE");
123 if (RetCode == SQL_ERROR) {
124 EndTransaction(SQL_ROLLBACK);
125 SWAP_CONN();
126 EndTransaction(SQL_ROLLBACK);
127 SWAP_CONN();
128 return 0; /* no dirty read */
129 }
130 EndTransaction(SQL_COMMIT);
131
132 SWAP_CONN();
133
134 /* second transaction try to fetch commited row */
135 odbc_command("SELECT * FROM test_transaction WHERE t = 'initial'");
136
137 CHKFetch("S");
138 CHKFetch("S");
139 CHKFetch("No");
140 SQLMoreResults(odbc_stmt);
141 EndTransaction(SQL_ROLLBACK);
142 SWAP_CONN();
143 odbc_command("DELETE test_transaction WHERE n = 2");
144 EndTransaction(SQL_COMMIT);
145 return 1;
146 }
147
148 static int test_with_connect = 0;
149
150 static int global_txn;
151
152 static int hide_error;
153
154 static void
my_attrs(void)155 my_attrs(void)
156 {
157 CHKSetConnectAttr(SQL_ATTR_TXN_ISOLATION, int2ptr(global_txn), 0, "S");
158 AutoCommit(SQL_AUTOCOMMIT_OFF);
159 }
160
161 static void
ConnectWithTxn(int txn)162 ConnectWithTxn(int txn)
163 {
164 global_txn = txn;
165 odbc_set_conn_attr = my_attrs;
166 odbc_connect();
167 odbc_set_conn_attr = NULL;
168 }
169
170 static int
Test(int txn,const char * expected)171 Test(int txn, const char *expected)
172 {
173 int dirty, repeatable, phantom;
174 char buf[128];
175
176 SWAP_CONN();
177 if (test_with_connect) {
178 odbc_disconnect();
179 ConnectWithTxn(txn);
180 CHKSetStmtAttr(SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER) 2, 0, "S");
181 } else {
182 CHKSetConnectAttr(SQL_ATTR_TXN_ISOLATION, int2ptr(txn), 0, "S");
183 }
184 SWAP_CONN();
185
186 dirty = CheckDirtyRead();
187 repeatable = CheckNonrepeatableRead();
188 phantom = CheckPhantom();
189
190 sprintf(buf, "dirty %d non repeatable %d phantom %d", dirty, repeatable, phantom);
191 if (strcmp(buf, expected) != 0) {
192 if (hide_error) {
193 hide_error = 0;
194 return 0;
195 }
196 fprintf(stderr, "detected wrong TXN\nexpected '%s' got '%s'\n", expected, buf);
197 exit(1);
198 }
199 hide_error = 0;
200 return 1;
201 }
202
203 int
main(int argc,char * argv[])204 main(int argc, char *argv[])
205 {
206 odbc_use_version3 = 1;
207 odbc_connect();
208
209 /* Invalid argument value */
210 CHKSetConnectAttr(SQL_ATTR_TXN_ISOLATION, int2ptr(SQL_TXN_REPEATABLE_READ | SQL_TXN_READ_COMMITTED), 0, "E");
211 ReadErrorConn();
212 if (strcmp(odbc_sqlstate, "HY024") != 0) {
213 odbc_disconnect();
214 fprintf(stderr, "Unexpected success\n");
215 return 1;
216 }
217
218 /* here we can't use temporary table cause we use two connection */
219 odbc_command("IF OBJECT_ID('test_transaction') IS NOT NULL DROP TABLE test_transaction");
220 odbc_command("CREATE TABLE test_transaction(n NUMERIC(18,0) PRIMARY KEY, t VARCHAR(30))");
221
222 CHKSetStmtAttr(SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER) 2, 0, "S");
223
224 AutoCommit(SQL_AUTOCOMMIT_OFF);
225 odbc_command("INSERT INTO test_transaction(n, t) VALUES(1, 'initial')");
226
227 #ifdef ENABLE_DEVELOPING
228 /* test setting with active transaction "Operation invalid at this time" */
229 CHKSetConnectAttr(SQL_ATTR_TXN_ISOLATION, int2ptr(SQL_TXN_REPEATABLE_READ), 0, "E");
230 ReadErrorConn();
231 if (strcmp(odbc_sqlstate, "HY011") != 0) {
232 odbc_disconnect();
233 fprintf(stderr, "Unexpected success\n");
234 return 1;
235 }
236 #endif
237
238 EndTransaction(SQL_COMMIT);
239
240 odbc_command("SELECT * FROM test_transaction");
241
242 /* test setting with pending data */
243 CHKSetConnectAttr(SQL_ATTR_TXN_ISOLATION, int2ptr(SQL_TXN_REPEATABLE_READ), 0, "E");
244 ReadErrorConn();
245 if (strcmp(odbc_sqlstate, "HY011") != 0) {
246 odbc_disconnect();
247 fprintf(stderr, "Unexpected success\n");
248 return 1;
249 }
250
251 SQLMoreResults(odbc_stmt);
252
253 EndTransaction(SQL_COMMIT);
254
255
256 /* save this connection and do another */
257 SWAP_CONN();
258
259 odbc_connect();
260
261 CHKSetStmtAttr(SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER) 2, 0, "S");
262 AutoCommit(SQL_AUTOCOMMIT_OFF);
263
264 SWAP_CONN();
265
266 for (test_with_connect = 0; test_with_connect <= 1; ++test_with_connect) {
267 Test(SQL_TXN_READ_UNCOMMITTED, "dirty 1 non repeatable 1 phantom 1");
268 Test(SQL_TXN_READ_COMMITTED, "dirty 0 non repeatable 1 phantom 1");
269 if (odbc_db_is_microsoft()) {
270 Test(SQL_TXN_REPEATABLE_READ, "dirty 0 non repeatable 0 phantom 1");
271 } else {
272 hide_error = 1;
273 if (!Test(SQL_TXN_REPEATABLE_READ, "dirty 0 non repeatable 0 phantom 1"))
274 Test(SQL_TXN_REPEATABLE_READ, "dirty 0 non repeatable 0 phantom 0");
275 }
276 Test(SQL_TXN_SERIALIZABLE, "dirty 0 non repeatable 0 phantom 0");
277 }
278
279 odbc_disconnect();
280
281 SWAP_CONN();
282
283 EndTransaction(SQL_COMMIT);
284
285 /* Sybase do not accept DROP TABLE during a transaction */
286 AutoCommit(SQL_AUTOCOMMIT_ON);
287 odbc_command("DROP TABLE test_transaction");
288
289 odbc_disconnect();
290 return 0;
291 }
292