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