1 | // |
2 | // SessionImpl.cpp |
3 | // |
4 | // Library: Data/ODBC |
5 | // Package: ODBC |
6 | // Module: SessionImpl |
7 | // |
8 | // Copyright (c) 2006, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Data/ODBC/SessionImpl.h" |
16 | #include "Poco/Data/ODBC/Utility.h" |
17 | #include "Poco/Data/ODBC/ODBCStatementImpl.h" |
18 | #include "Poco/Data/ODBC/Error.h" |
19 | #include "Poco/Data/ODBC/ODBCException.h" |
20 | #include "Poco/Data/Session.h" |
21 | #include "Poco/String.h" |
22 | #include <sqlext.h> |
23 | |
24 | |
25 | namespace Poco { |
26 | namespace Data { |
27 | namespace ODBC { |
28 | |
29 | |
30 | SessionImpl::SessionImpl(const std::string& connect, |
31 | std::size_t loginTimeout, |
32 | std::size_t maxFieldSize, |
33 | bool autoBind, |
34 | bool ): |
35 | Poco::Data::AbstractSessionImpl<SessionImpl>(connect, loginTimeout), |
36 | _connector(Connector::KEY), |
37 | _maxFieldSize(maxFieldSize), |
38 | _autoBind(autoBind), |
39 | _autoExtract(autoExtract), |
40 | _canTransact(ODBC_TXN_CAPABILITY_UNKNOWN), |
41 | _inTransaction(false), |
42 | _queryTimeout(-1) |
43 | { |
44 | init(); |
45 | } |
46 | |
47 | |
48 | SessionImpl::SessionImpl(const std::string& connect, |
49 | Poco::Any maxFieldSize, |
50 | bool enforceCapability, |
51 | bool autoBind, |
52 | bool ): Poco::Data::AbstractSessionImpl<SessionImpl>(connect), |
53 | _connector(Connector::KEY), |
54 | _maxFieldSize(maxFieldSize), |
55 | _autoBind(autoBind), |
56 | _autoExtract(autoExtract), |
57 | _canTransact(ODBC_TXN_CAPABILITY_UNKNOWN), |
58 | _inTransaction(false), |
59 | _queryTimeout(-1) |
60 | { |
61 | init(); |
62 | } |
63 | |
64 | |
65 | void SessionImpl::init() |
66 | { |
67 | setFeature("bulk" , true); |
68 | open(); |
69 | setProperty("handle" , _db.handle()); |
70 | setProperty("handle" , _db.handle()); |
71 | } |
72 | |
73 | |
74 | SessionImpl::~SessionImpl() |
75 | { |
76 | try |
77 | { |
78 | if (static_cast<bool>(_db) && isTransaction() && !getFeature("autoCommit" )) |
79 | { |
80 | try { rollback(); } |
81 | catch (...) { } |
82 | } |
83 | |
84 | close(); |
85 | } |
86 | catch (...) |
87 | { |
88 | poco_unexpected(); |
89 | } |
90 | } |
91 | |
92 | |
93 | Poco::Data::StatementImpl* SessionImpl::createStatementImpl() |
94 | { |
95 | return new ODBCStatementImpl(*this); |
96 | } |
97 | |
98 | |
99 | void SessionImpl::open(const std::string& connect) |
100 | { |
101 | if (connect != connectionString()) |
102 | { |
103 | if (isConnected()) |
104 | throw InvalidAccessException("Session already connected" ); |
105 | |
106 | if (!connect.empty()) |
107 | setConnectionString(connect); |
108 | } |
109 | |
110 | poco_assert_dbg (!connectionString().empty()); |
111 | |
112 | SQLULEN tout = static_cast<SQLULEN>(getLoginTimeout()); |
113 | if (Utility::isError(Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)tout, 0))) |
114 | { |
115 | if (Utility::isError(Poco::Data::ODBC::SQLGetConnectAttr(_db, SQL_ATTR_LOGIN_TIMEOUT, &tout, 0, 0)) || |
116 | getLoginTimeout() != tout) |
117 | { |
118 | ConnectionException e(_db); |
119 | throw ConnectionFailedException(e.errorString(), e); |
120 | } |
121 | } |
122 | |
123 | SQLCHAR connectOutput[512] = {0}; |
124 | SQLSMALLINT result; |
125 | |
126 | if (Utility::isError(Poco::Data::ODBC::SQLDriverConnect(_db |
127 | , NULL |
128 | ,(SQLCHAR*) connectionString().c_str() |
129 | ,(SQLSMALLINT) SQL_NTS |
130 | , connectOutput |
131 | , sizeof(connectOutput) |
132 | , &result |
133 | , SQL_DRIVER_NOPROMPT))) |
134 | { |
135 | ConnectionException e(_db); |
136 | close(); |
137 | throw ConnectionFailedException(e.errorString(), e); |
138 | } |
139 | |
140 | _dataTypes.fillTypeInfo(_db); |
141 | |
142 | addProperty("dataTypeInfo" , |
143 | &SessionImpl::setDataTypeInfo, |
144 | &SessionImpl::dataTypeInfo); |
145 | |
146 | addFeature("autoCommit" , |
147 | &SessionImpl::autoCommit, |
148 | &SessionImpl::isAutoCommit); |
149 | |
150 | addFeature("autoBind" , |
151 | &SessionImpl::autoBind, |
152 | &SessionImpl::isAutoBind); |
153 | |
154 | addFeature("autoExtract" , |
155 | &SessionImpl::autoExtract, |
156 | &SessionImpl::isAutoExtract); |
157 | |
158 | addProperty("maxFieldSize" , |
159 | &SessionImpl::setMaxFieldSize, |
160 | &SessionImpl::getMaxFieldSize); |
161 | |
162 | addProperty("queryTimeout" , |
163 | &SessionImpl::setQueryTimeout, |
164 | &SessionImpl::getQueryTimeout); |
165 | |
166 | Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_QUIET_MODE, 0, 0); |
167 | |
168 | if (!canTransact()) autoCommit("" , true); |
169 | } |
170 | |
171 | |
172 | bool SessionImpl::isConnected() |
173 | { |
174 | SQLULEN value = 0; |
175 | |
176 | if (!static_cast<bool>(_db) || Utility::isError(Poco::Data::ODBC::SQLGetConnectAttr(_db, |
177 | SQL_ATTR_CONNECTION_DEAD, |
178 | &value, |
179 | 0, |
180 | 0))) return false; |
181 | |
182 | return (SQL_CD_FALSE == value); |
183 | } |
184 | |
185 | |
186 | void SessionImpl::setConnectionTimeout(std::size_t timeout) |
187 | { |
188 | SQLUINTEGER value = static_cast<SQLUINTEGER>(timeout); |
189 | |
190 | checkError(Poco::Data::ODBC::SQLSetConnectAttr(_db, |
191 | SQL_ATTR_CONNECTION_TIMEOUT, |
192 | &value, |
193 | SQL_IS_UINTEGER), "Failed to set connection timeout." ); |
194 | } |
195 | |
196 | |
197 | std::size_t SessionImpl::getConnectionTimeout() |
198 | { |
199 | SQLULEN value = 0; |
200 | |
201 | checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db, |
202 | SQL_ATTR_CONNECTION_TIMEOUT, |
203 | &value, |
204 | 0, |
205 | 0), "Failed to get connection timeout." ); |
206 | |
207 | return value; |
208 | } |
209 | |
210 | |
211 | bool SessionImpl::canTransact() |
212 | { |
213 | if (ODBC_TXN_CAPABILITY_UNKNOWN == _canTransact) |
214 | { |
215 | SQLUSMALLINT ret; |
216 | SQLRETURN res = Poco::Data::ODBC::SQLGetInfo(_db, SQL_TXN_CAPABLE, &ret, 0, 0); |
217 | if (!Utility::isError(res)) |
218 | { |
219 | _canTransact = (SQL_TC_NONE != ret) ? |
220 | ODBC_TXN_CAPABILITY_TRUE : |
221 | ODBC_TXN_CAPABILITY_FALSE; |
222 | } |
223 | else |
224 | { |
225 | Error<SQLHDBC, SQL_HANDLE_DBC> err(_db); |
226 | _canTransact = ODBC_TXN_CAPABILITY_FALSE; |
227 | } |
228 | } |
229 | |
230 | return ODBC_TXN_CAPABILITY_TRUE == _canTransact; |
231 | } |
232 | |
233 | |
234 | void SessionImpl::setTransactionIsolation(Poco::UInt32 ti) |
235 | { |
236 | #if POCO_PTR_IS_64_BIT |
237 | Poco::UInt64 isolation = 0; |
238 | #else |
239 | Poco::UInt32 isolation = 0; |
240 | #endif |
241 | |
242 | if (ti & Session::TRANSACTION_READ_UNCOMMITTED) |
243 | isolation |= SQL_TXN_READ_UNCOMMITTED; |
244 | |
245 | if (ti & Session::TRANSACTION_READ_COMMITTED) |
246 | isolation |= SQL_TXN_READ_COMMITTED; |
247 | |
248 | if (ti & Session::TRANSACTION_REPEATABLE_READ) |
249 | isolation |= SQL_TXN_REPEATABLE_READ; |
250 | |
251 | if (ti & Session::TRANSACTION_SERIALIZABLE) |
252 | isolation |= SQL_TXN_SERIALIZABLE; |
253 | |
254 | checkError(Poco::Data::ODBC::SQLSetConnectAttr(_db, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)isolation, 0)); |
255 | } |
256 | |
257 | |
258 | Poco::UInt32 SessionImpl::getTransactionIsolation() |
259 | { |
260 | SQLULEN isolation = 0; |
261 | checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db, SQL_ATTR_TXN_ISOLATION, |
262 | &isolation, |
263 | 0, |
264 | 0)); |
265 | |
266 | return transactionIsolation(isolation); |
267 | } |
268 | |
269 | |
270 | bool SessionImpl::hasTransactionIsolation(Poco::UInt32 ti) |
271 | { |
272 | if (isTransaction()) throw InvalidAccessException(); |
273 | |
274 | bool retval = true; |
275 | Poco::UInt32 old = getTransactionIsolation(); |
276 | try { setTransactionIsolation(ti); } |
277 | catch (Poco::Exception&) { retval = false; } |
278 | setTransactionIsolation(old); |
279 | return retval; |
280 | } |
281 | |
282 | |
283 | Poco::UInt32 SessionImpl::getDefaultTransactionIsolation() |
284 | { |
285 | SQLUINTEGER isolation = 0; |
286 | checkError(Poco::Data::ODBC::SQLGetInfo(_db, SQL_DEFAULT_TXN_ISOLATION, |
287 | &isolation, |
288 | 0, |
289 | 0)); |
290 | |
291 | return transactionIsolation(isolation); |
292 | } |
293 | |
294 | |
295 | Poco::UInt32 SessionImpl::transactionIsolation(SQLULEN isolation) |
296 | { |
297 | if (0 == isolation) |
298 | throw InvalidArgumentException("transactionIsolation(SQLUINTEGER)" ); |
299 | |
300 | Poco::UInt32 ret = 0; |
301 | |
302 | if (isolation & SQL_TXN_READ_UNCOMMITTED) |
303 | ret |= Session::TRANSACTION_READ_UNCOMMITTED; |
304 | |
305 | if (isolation & SQL_TXN_READ_COMMITTED) |
306 | ret |= Session::TRANSACTION_READ_COMMITTED; |
307 | |
308 | if (isolation & SQL_TXN_REPEATABLE_READ) |
309 | ret |= Session::TRANSACTION_REPEATABLE_READ; |
310 | |
311 | if (isolation & SQL_TXN_SERIALIZABLE) |
312 | ret |= Session::TRANSACTION_SERIALIZABLE; |
313 | |
314 | if (0 == ret) |
315 | throw InvalidArgumentException("transactionIsolation(SQLUINTEGER)" ); |
316 | |
317 | return ret; |
318 | } |
319 | |
320 | |
321 | void SessionImpl::autoCommit(const std::string&, bool val) |
322 | { |
323 | checkError(Poco::Data::ODBC::SQLSetConnectAttr(_db, |
324 | SQL_ATTR_AUTOCOMMIT, |
325 | val ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : |
326 | (SQLPOINTER) SQL_AUTOCOMMIT_OFF, |
327 | SQL_IS_UINTEGER), "Failed to set automatic commit." ); |
328 | } |
329 | |
330 | |
331 | bool SessionImpl::isAutoCommit(const std::string&) |
332 | { |
333 | SQLULEN value = 0; |
334 | |
335 | checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db, |
336 | SQL_ATTR_AUTOCOMMIT, |
337 | &value, |
338 | 0, |
339 | 0)); |
340 | |
341 | return (0 != value); |
342 | } |
343 | |
344 | |
345 | bool SessionImpl::isTransaction() |
346 | { |
347 | if (!canTransact()) return false; |
348 | |
349 | SQLULEN value = 0; |
350 | checkError(Poco::Data::ODBC::SQLGetConnectAttr(_db, |
351 | SQL_ATTR_AUTOCOMMIT, |
352 | &value, |
353 | 0, |
354 | 0)); |
355 | |
356 | if (0 == value) return _inTransaction; |
357 | else return false; |
358 | } |
359 | |
360 | |
361 | void SessionImpl::begin() |
362 | { |
363 | if (isAutoCommit()) |
364 | throw InvalidAccessException("Session in auto commit mode." ); |
365 | |
366 | { |
367 | Poco::FastMutex::ScopedLock l(_mutex); |
368 | |
369 | if (_inTransaction) |
370 | throw InvalidAccessException("Transaction in progress." ); |
371 | |
372 | _inTransaction = true; |
373 | } |
374 | } |
375 | |
376 | |
377 | void SessionImpl::commit() |
378 | { |
379 | if (!isAutoCommit()) |
380 | checkError(SQLEndTran(SQL_HANDLE_DBC, _db, SQL_COMMIT)); |
381 | |
382 | _inTransaction = false; |
383 | } |
384 | |
385 | |
386 | void SessionImpl::rollback() |
387 | { |
388 | if (!isAutoCommit()) |
389 | checkError(SQLEndTran(SQL_HANDLE_DBC, _db, SQL_ROLLBACK)); |
390 | |
391 | _inTransaction = false; |
392 | } |
393 | |
394 | |
395 | void SessionImpl::close() |
396 | { |
397 | if (!isConnected()) return; |
398 | |
399 | try |
400 | { |
401 | commit(); |
402 | } |
403 | catch (ConnectionException&) |
404 | { |
405 | } |
406 | |
407 | SQLCHAR sqlState[5+1]; |
408 | SQLCHAR sqlErrorMessage[SQL_MAX_MESSAGE_LENGTH]; |
409 | SQLINTEGER nativeErrorCode; |
410 | SQLSMALLINT sqlErrorMessageLength; |
411 | unsigned int retryCount = 10; |
412 | do |
413 | { |
414 | SQLRETURN rc = SQLDisconnect(_db); |
415 | if (SQL_SUCCESS != rc && SQL_SUCCESS_WITH_INFO != rc) |
416 | { |
417 | SQLGetDiagRec(SQL_HANDLE_DBC, _db, |
418 | 1, |
419 | sqlState, |
420 | &nativeErrorCode, |
421 | sqlErrorMessage, SQL_MAX_MESSAGE_LENGTH, &sqlErrorMessageLength); |
422 | if (std::string(reinterpret_cast<const char *>(sqlState)) == "25000" |
423 | || std::string(reinterpret_cast<const char *>(sqlState)) == "25501" ) |
424 | { |
425 | --retryCount; |
426 | Poco::Thread::sleep(100); |
427 | } |
428 | else |
429 | { |
430 | break; |
431 | } |
432 | } |
433 | else |
434 | { |
435 | return; |
436 | } |
437 | } |
438 | while(retryCount > 0); |
439 | |
440 | throw ODBCException |
441 | (std::string(reinterpret_cast<const char *>(sqlErrorMessage)), nativeErrorCode); |
442 | } |
443 | |
444 | |
445 | int SessionImpl::maxStatementLength() |
446 | { |
447 | SQLUINTEGER info; |
448 | SQLRETURN rc = 0; |
449 | if (Utility::isError(rc = Poco::Data::ODBC::SQLGetInfo(_db, |
450 | SQL_MAXIMUM_STATEMENT_LENGTH, |
451 | (SQLPOINTER) &info, |
452 | 0, |
453 | 0))) |
454 | { |
455 | throw ConnectionException(_db, |
456 | "SQLGetInfo(SQL_MAXIMUM_STATEMENT_LENGTH)" ); |
457 | } |
458 | |
459 | return info; |
460 | } |
461 | |
462 | |
463 | } } } // namespace Poco::Data::ODBC |
464 | |