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