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