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