|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtSql module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "qsql_odbc.h" |
|
43 #include <qsqlrecord.h> |
|
44 |
|
45 #if defined (Q_OS_WIN32) |
|
46 #include <qt_windows.h> |
|
47 #endif |
|
48 #include <qcoreapplication.h> |
|
49 #include <qvariant.h> |
|
50 #include <qdatetime.h> |
|
51 #include <qsqlerror.h> |
|
52 #include <qsqlfield.h> |
|
53 #include <qsqlindex.h> |
|
54 #include <qstringlist.h> |
|
55 #include <qvarlengtharray.h> |
|
56 #include <qvector.h> |
|
57 #include <QDebug> |
|
58 #include <QSqlQuery> |
|
59 |
|
60 QT_BEGIN_NAMESPACE |
|
61 |
|
62 // undefine this to prevent initial check of the ODBC driver |
|
63 #define ODBC_CHECK_DRIVER |
|
64 |
|
65 #if defined(Q_ODBC_VERSION_2) |
|
66 //crude hack to get non-unicode capable driver managers to work |
|
67 # undef UNICODE |
|
68 # define SQLTCHAR SQLCHAR |
|
69 # define SQL_C_TCHAR SQL_C_CHAR |
|
70 #endif |
|
71 |
|
72 // newer platform SDKs use SQLLEN instead of SQLINTEGER |
|
73 #if defined(WIN32) && (_MSC_VER < 1300) |
|
74 # define QSQLLEN SQLINTEGER |
|
75 # define QSQLULEN SQLUINTEGER |
|
76 #else |
|
77 # define QSQLLEN SQLLEN |
|
78 # define QSQLULEN SQLULEN |
|
79 #endif |
|
80 |
|
81 static const int COLNAMESIZE = 256; |
|
82 //Map Qt parameter types to ODBC types |
|
83 static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT }; |
|
84 |
|
85 inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, int size=-1) |
|
86 { |
|
87 QString result; |
|
88 |
|
89 int realsize = qMin(size, input.size()); |
|
90 if(realsize > 0 && input[realsize-1] == 0) |
|
91 realsize--; |
|
92 switch(sizeof(SQLTCHAR)) { |
|
93 case 1: |
|
94 result=QString::fromUtf8((const char *)input.constData(), realsize); |
|
95 break; |
|
96 case 2: |
|
97 result=QString::fromUtf16((const ushort *)input.constData(), realsize); |
|
98 break; |
|
99 case 4: |
|
100 result=QString::fromUcs4((const uint *)input.constData(), realsize); |
|
101 break; |
|
102 default: |
|
103 qCritical() << "sizeof(SQLTCHAR) is " << sizeof(SQLTCHAR) << "Don't know how to handle this"; |
|
104 } |
|
105 return result; |
|
106 } |
|
107 |
|
108 inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input) |
|
109 { |
|
110 QVarLengthArray<SQLTCHAR> result; |
|
111 result.resize(input.size()); |
|
112 switch(sizeof(SQLTCHAR)) { |
|
113 case 1: |
|
114 memcpy(result.data(), input.toUtf8().data(), input.size()); |
|
115 break; |
|
116 case 2: |
|
117 memcpy(result.data(), input.unicode(), input.size() * 2); |
|
118 break; |
|
119 case 4: |
|
120 memcpy(result.data(), input.toUcs4().data(), input.size() * 4); |
|
121 break; |
|
122 default: |
|
123 qCritical() << "sizeof(SQLTCHAR) is " << sizeof(SQLTCHAR) << "Don't know how to handle this"; |
|
124 } |
|
125 result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't. |
|
126 return result; |
|
127 } |
|
128 |
|
129 class QODBCDriverPrivate |
|
130 { |
|
131 public: |
|
132 enum DefaultCase{Lower, Mixed, Upper, Sensitive}; |
|
133 QODBCDriverPrivate() |
|
134 : hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), isMySqlServer(false), |
|
135 isMSSqlServer(false), isFreeTDSDriver(false), hasSQLFetchScroll(true), |
|
136 hasMultiResultSets(false), isQuoteInitialized(false), quote(QLatin1Char('"')) |
|
137 { |
|
138 } |
|
139 |
|
140 SQLHANDLE hEnv; |
|
141 SQLHANDLE hDbc; |
|
142 |
|
143 bool unicode; |
|
144 bool useSchema; |
|
145 int disconnectCount; |
|
146 bool isMySqlServer; |
|
147 bool isMSSqlServer; |
|
148 bool isFreeTDSDriver; |
|
149 bool hasSQLFetchScroll; |
|
150 bool hasMultiResultSets; |
|
151 |
|
152 bool checkDriver() const; |
|
153 void checkUnicode(); |
|
154 void checkSqlServer(); |
|
155 void checkHasSQLFetchScroll(); |
|
156 void checkHasMultiResults(); |
|
157 void checkSchemaUsage(); |
|
158 bool setConnectionOptions(const QString& connOpts); |
|
159 void splitTableQualifier(const QString &qualifier, QString &catalog, |
|
160 QString &schema, QString &table); |
|
161 DefaultCase defaultCase() const; |
|
162 QString adjustCase(const QString&) const; |
|
163 QChar quoteChar(); |
|
164 private: |
|
165 bool isQuoteInitialized; |
|
166 QChar quote; |
|
167 }; |
|
168 |
|
169 class QODBCPrivate |
|
170 { |
|
171 public: |
|
172 QODBCPrivate(QODBCDriverPrivate *dpp) |
|
173 : hStmt(0), useSchema(false), hasSQLFetchScroll(true), driverPrivate(dpp), userForwardOnly(false) |
|
174 { |
|
175 unicode = dpp->unicode; |
|
176 useSchema = dpp->useSchema; |
|
177 disconnectCount = dpp->disconnectCount; |
|
178 hasSQLFetchScroll = dpp->hasSQLFetchScroll; |
|
179 } |
|
180 |
|
181 inline void clearValues() |
|
182 { fieldCache.fill(QVariant()); fieldCacheIdx = 0; } |
|
183 |
|
184 SQLHANDLE dpEnv() const { return driverPrivate ? driverPrivate->hEnv : 0;} |
|
185 SQLHANDLE dpDbc() const { return driverPrivate ? driverPrivate->hDbc : 0;} |
|
186 SQLHANDLE hStmt; |
|
187 |
|
188 bool unicode; |
|
189 bool useSchema; |
|
190 |
|
191 QSqlRecord rInf; |
|
192 QVector<QVariant> fieldCache; |
|
193 int fieldCacheIdx; |
|
194 int disconnectCount; |
|
195 bool hasSQLFetchScroll; |
|
196 QODBCDriverPrivate *driverPrivate; |
|
197 bool userForwardOnly; |
|
198 |
|
199 bool isStmtHandleValid(const QSqlDriver *driver); |
|
200 void updateStmtHandleState(const QSqlDriver *driver); |
|
201 }; |
|
202 |
|
203 bool QODBCPrivate::isStmtHandleValid(const QSqlDriver *driver) |
|
204 { |
|
205 const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver); |
|
206 return disconnectCount == odbcdriver->d->disconnectCount; |
|
207 } |
|
208 |
|
209 void QODBCPrivate::updateStmtHandleState(const QSqlDriver *driver) |
|
210 { |
|
211 const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver); |
|
212 disconnectCount = odbcdriver->d->disconnectCount; |
|
213 } |
|
214 |
|
215 static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0) |
|
216 { |
|
217 SQLINTEGER nativeCode_ = 0; |
|
218 SQLSMALLINT msgLen = 0; |
|
219 SQLRETURN r = SQL_NO_DATA; |
|
220 SQLTCHAR state_[SQL_SQLSTATE_SIZE+1]; |
|
221 QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH); |
|
222 QString result; |
|
223 int i = 1; |
|
224 |
|
225 description_[0] = 0; |
|
226 do { |
|
227 r = SQLGetDiagRec(handleType, |
|
228 handle, |
|
229 i, |
|
230 state_, |
|
231 &nativeCode_, |
|
232 0, |
|
233 NULL, |
|
234 &msgLen); |
|
235 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && msgLen > 0) |
|
236 description_.resize(msgLen+1); |
|
237 r = SQLGetDiagRec(handleType, |
|
238 handle, |
|
239 i, |
|
240 state_, |
|
241 &nativeCode_, |
|
242 description_.data(), |
|
243 description_.size(), |
|
244 &msgLen); |
|
245 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { |
|
246 if (nativeCode) |
|
247 *nativeCode = nativeCode_; |
|
248 QString tmpstore; |
|
249 #ifdef UNICODE |
|
250 tmpstore = fromSQLTCHAR(description_, msgLen); |
|
251 #else |
|
252 tmpstore = QString::fromUtf8((const char*)description_.constData(), msgLen); |
|
253 #endif |
|
254 if(result != tmpstore) { |
|
255 if(!result.isEmpty()) |
|
256 result += QLatin1Char(' '); |
|
257 result += tmpstore; |
|
258 } |
|
259 } else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) { |
|
260 return result; |
|
261 } |
|
262 ++i; |
|
263 } while (r != SQL_NO_DATA); |
|
264 return result; |
|
265 } |
|
266 |
|
267 static QString qODBCWarn(const QODBCPrivate* odbc, int *nativeCode = 0) |
|
268 { |
|
269 return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->dpEnv()) + QLatin1Char(' ') |
|
270 + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->dpDbc()) + QLatin1Char(' ') |
|
271 + qWarnODBCHandle(SQL_HANDLE_STMT, odbc->hStmt, nativeCode)).simplified(); |
|
272 } |
|
273 |
|
274 static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0) |
|
275 { |
|
276 return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1Char(' ') |
|
277 + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc, nativeCode)).simplified(); |
|
278 } |
|
279 |
|
280 static void qSqlWarning(const QString& message, const QODBCPrivate* odbc) |
|
281 { |
|
282 qWarning() << message << "\tError:" << qODBCWarn(odbc); |
|
283 } |
|
284 |
|
285 static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc) |
|
286 { |
|
287 qWarning() << message << "\tError:" << qODBCWarn(odbc); |
|
288 } |
|
289 |
|
290 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCPrivate* p) |
|
291 { |
|
292 int nativeCode = -1; |
|
293 QString message = qODBCWarn(p, &nativeCode); |
|
294 return QSqlError(QLatin1String("QODBC3: ") + err, message, type, nativeCode); |
|
295 } |
|
296 |
|
297 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, |
|
298 const QODBCDriverPrivate* p) |
|
299 { |
|
300 int nativeCode = -1; |
|
301 QString message = qODBCWarn(p, &nativeCode); |
|
302 return QSqlError(QLatin1String("QODBC3: ") + err, qODBCWarn(p), type, nativeCode); |
|
303 } |
|
304 |
|
305 template<class T> |
|
306 static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, const T* p, bool isSigned = true) |
|
307 { |
|
308 Q_UNUSED(p); |
|
309 QVariant::Type type = QVariant::Invalid; |
|
310 switch (sqltype) { |
|
311 case SQL_DECIMAL: |
|
312 case SQL_NUMERIC: |
|
313 case SQL_REAL: |
|
314 case SQL_FLOAT: |
|
315 case SQL_DOUBLE: |
|
316 type = QVariant::Double; |
|
317 break; |
|
318 case SQL_SMALLINT: |
|
319 case SQL_INTEGER: |
|
320 case SQL_BIT: |
|
321 type = isSigned ? QVariant::Int : QVariant::UInt; |
|
322 break; |
|
323 case SQL_TINYINT: |
|
324 type = QVariant::UInt; |
|
325 break; |
|
326 case SQL_BIGINT: |
|
327 type = isSigned ? QVariant::LongLong : QVariant::ULongLong; |
|
328 break; |
|
329 case SQL_BINARY: |
|
330 case SQL_VARBINARY: |
|
331 case SQL_LONGVARBINARY: |
|
332 type = QVariant::ByteArray; |
|
333 break; |
|
334 case SQL_DATE: |
|
335 case SQL_TYPE_DATE: |
|
336 type = QVariant::Date; |
|
337 break; |
|
338 case SQL_TIME: |
|
339 case SQL_TYPE_TIME: |
|
340 type = QVariant::Time; |
|
341 break; |
|
342 case SQL_TIMESTAMP: |
|
343 case SQL_TYPE_TIMESTAMP: |
|
344 type = QVariant::DateTime; |
|
345 break; |
|
346 #ifndef Q_ODBC_VERSION_2 |
|
347 case SQL_WCHAR: |
|
348 case SQL_WVARCHAR: |
|
349 case SQL_WLONGVARCHAR: |
|
350 type = QVariant::String; |
|
351 break; |
|
352 #endif |
|
353 case SQL_CHAR: |
|
354 case SQL_VARCHAR: |
|
355 #if (ODBCVER >= 0x0350) |
|
356 case SQL_GUID: |
|
357 #endif |
|
358 case SQL_LONGVARCHAR: |
|
359 type = QVariant::String; |
|
360 break; |
|
361 default: |
|
362 type = QVariant::ByteArray; |
|
363 break; |
|
364 } |
|
365 return type; |
|
366 } |
|
367 |
|
368 static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false) |
|
369 { |
|
370 QString fieldVal; |
|
371 SQLRETURN r = SQL_ERROR; |
|
372 QSQLLEN lengthIndicator = 0; |
|
373 |
|
374 // NB! colSize must be a multiple of 2 for unicode enabled DBs |
|
375 if (colSize <= 0) { |
|
376 colSize = 256; |
|
377 } else if (colSize > 65536) { // limit buffer size to 64 KB |
|
378 colSize = 65536; |
|
379 } else { |
|
380 colSize++; // make sure there is room for more than the 0 termination |
|
381 } |
|
382 if(unicode) { |
|
383 r = SQLGetData(hStmt, |
|
384 column+1, |
|
385 SQL_C_TCHAR, |
|
386 NULL, |
|
387 0, |
|
388 &lengthIndicator); |
|
389 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0) |
|
390 colSize = lengthIndicator/sizeof(SQLTCHAR) + 1; |
|
391 QVarLengthArray<SQLTCHAR> buf(colSize); |
|
392 memset(buf.data(), 0, colSize*sizeof(SQLTCHAR)); |
|
393 while (true) { |
|
394 r = SQLGetData(hStmt, |
|
395 column+1, |
|
396 SQL_C_TCHAR, |
|
397 (SQLPOINTER)buf.data(), |
|
398 colSize*sizeof(SQLTCHAR), |
|
399 &lengthIndicator); |
|
400 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { |
|
401 if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) { |
|
402 fieldVal.clear(); |
|
403 break; |
|
404 } |
|
405 // if SQL_SUCCESS_WITH_INFO is returned, indicating that |
|
406 // more data can be fetched, the length indicator does NOT |
|
407 // contain the number of bytes returned - it contains the |
|
408 // total number of bytes that CAN be fetched |
|
409 // colSize-1: remove 0 termination when there is more data to fetch |
|
410 int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator/sizeof(SQLTCHAR); |
|
411 fieldVal += fromSQLTCHAR(buf, rSize); |
|
412 if (lengthIndicator < (unsigned int)colSize*sizeof(SQLTCHAR)) { |
|
413 // workaround for Drivermanagers that don't return SQL_NO_DATA |
|
414 break; |
|
415 } |
|
416 } else if (r == SQL_NO_DATA) { |
|
417 break; |
|
418 } else { |
|
419 qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; |
|
420 fieldVal.clear(); |
|
421 break; |
|
422 } |
|
423 } |
|
424 } else { |
|
425 r = SQLGetData(hStmt, |
|
426 column+1, |
|
427 SQL_C_CHAR, |
|
428 NULL, |
|
429 0, |
|
430 &lengthIndicator); |
|
431 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0) |
|
432 colSize = lengthIndicator + 1; |
|
433 QVarLengthArray<SQLCHAR> buf(colSize); |
|
434 while (true) { |
|
435 r = SQLGetData(hStmt, |
|
436 column+1, |
|
437 SQL_C_CHAR, |
|
438 (SQLPOINTER)buf.data(), |
|
439 colSize, |
|
440 &lengthIndicator); |
|
441 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { |
|
442 if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) { |
|
443 fieldVal.clear(); |
|
444 break; |
|
445 } |
|
446 // if SQL_SUCCESS_WITH_INFO is returned, indicating that |
|
447 // more data can be fetched, the length indicator does NOT |
|
448 // contain the number of bytes returned - it contains the |
|
449 // total number of bytes that CAN be fetched |
|
450 // colSize-1: remove 0 termination when there is more data to fetch |
|
451 int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator; |
|
452 fieldVal += QString::fromUtf8((const char *)buf.constData(), rSize); |
|
453 if (lengthIndicator < (unsigned int)colSize) { |
|
454 // workaround for Drivermanagers that don't return SQL_NO_DATA |
|
455 break; |
|
456 } |
|
457 } else if (r == SQL_NO_DATA) { |
|
458 break; |
|
459 } else { |
|
460 qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; |
|
461 fieldVal.clear(); |
|
462 break; |
|
463 } |
|
464 } |
|
465 } |
|
466 return fieldVal; |
|
467 } |
|
468 |
|
469 static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) |
|
470 { |
|
471 QByteArray fieldVal; |
|
472 SQLSMALLINT colNameLen; |
|
473 SQLSMALLINT colType; |
|
474 QSQLULEN colSize; |
|
475 SQLSMALLINT colScale; |
|
476 SQLSMALLINT nullable; |
|
477 QSQLLEN lengthIndicator = 0; |
|
478 SQLRETURN r = SQL_ERROR; |
|
479 |
|
480 QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE); |
|
481 |
|
482 r = SQLDescribeCol(hStmt, |
|
483 column + 1, |
|
484 colName.data(), |
|
485 COLNAMESIZE, |
|
486 &colNameLen, |
|
487 &colType, |
|
488 &colSize, |
|
489 &colScale, |
|
490 &nullable); |
|
491 if (r != SQL_SUCCESS) |
|
492 qWarning() << "qGetBinaryData: Unable to describe column" << column; |
|
493 // SQLDescribeCol may return 0 if size cannot be determined |
|
494 if (!colSize) |
|
495 colSize = 255; |
|
496 else if (colSize > 65536) // read the field in 64 KB chunks |
|
497 colSize = 65536; |
|
498 fieldVal.resize(colSize); |
|
499 ulong read = 0; |
|
500 while (true) { |
|
501 r = SQLGetData(hStmt, |
|
502 column+1, |
|
503 SQL_C_BINARY, |
|
504 (SQLPOINTER)(fieldVal.constData() + read), |
|
505 colSize, |
|
506 &lengthIndicator); |
|
507 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) |
|
508 break; |
|
509 if (lengthIndicator == SQL_NULL_DATA) |
|
510 return QVariant(QVariant::ByteArray); |
|
511 if (lengthIndicator > QSQLLEN(colSize) || lengthIndicator == SQL_NO_TOTAL) { |
|
512 read += colSize; |
|
513 colSize = 65536; |
|
514 } else { |
|
515 read += lengthIndicator; |
|
516 } |
|
517 if (r == SQL_SUCCESS) { // the whole field was read in one chunk |
|
518 fieldVal.resize(read); |
|
519 break; |
|
520 } |
|
521 fieldVal.resize(fieldVal.size() + colSize); |
|
522 } |
|
523 return fieldVal; |
|
524 } |
|
525 |
|
526 static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true) |
|
527 { |
|
528 SQLINTEGER intbuf = 0; |
|
529 QSQLLEN lengthIndicator = 0; |
|
530 SQLRETURN r = SQLGetData(hStmt, |
|
531 column+1, |
|
532 isSigned ? SQL_C_SLONG : SQL_C_ULONG, |
|
533 (SQLPOINTER)&intbuf, |
|
534 sizeof(intbuf), |
|
535 &lengthIndicator); |
|
536 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) |
|
537 return QVariant(QVariant::Invalid); |
|
538 if (lengthIndicator == SQL_NULL_DATA) |
|
539 return QVariant(QVariant::Int); |
|
540 if (isSigned) |
|
541 return int(intbuf); |
|
542 else |
|
543 return uint(intbuf); |
|
544 } |
|
545 |
|
546 static QVariant qGetDoubleData(SQLHANDLE hStmt, int column) |
|
547 { |
|
548 SQLDOUBLE dblbuf; |
|
549 QSQLLEN lengthIndicator = 0; |
|
550 SQLRETURN r = SQLGetData(hStmt, |
|
551 column+1, |
|
552 SQL_C_DOUBLE, |
|
553 (SQLPOINTER) &dblbuf, |
|
554 0, |
|
555 &lengthIndicator); |
|
556 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
557 return QVariant(QVariant::Invalid); |
|
558 } |
|
559 if(lengthIndicator == SQL_NULL_DATA) |
|
560 return QVariant(QVariant::Double); |
|
561 |
|
562 return (double) dblbuf; |
|
563 } |
|
564 |
|
565 |
|
566 static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true) |
|
567 { |
|
568 SQLBIGINT lngbuf = 0; |
|
569 QSQLLEN lengthIndicator = 0; |
|
570 SQLRETURN r = SQLGetData(hStmt, |
|
571 column+1, |
|
572 isSigned ? SQL_C_SBIGINT : SQL_C_UBIGINT, |
|
573 (SQLPOINTER) &lngbuf, |
|
574 sizeof(lngbuf), |
|
575 &lengthIndicator); |
|
576 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) |
|
577 return QVariant(QVariant::Invalid); |
|
578 if (lengthIndicator == SQL_NULL_DATA) |
|
579 return QVariant(QVariant::LongLong); |
|
580 |
|
581 if (isSigned) |
|
582 return qint64(lngbuf); |
|
583 else |
|
584 return quint64(lngbuf); |
|
585 } |
|
586 |
|
587 // creates a QSqlField from a valid hStmt generated |
|
588 // by SQLColumns. The hStmt has to point to a valid position. |
|
589 static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p) |
|
590 { |
|
591 QString fname = qGetStringData(hStmt, 3, -1, p->unicode); |
|
592 int type = qGetIntData(hStmt, 4).toInt(); // column type |
|
593 QSqlField f(fname, qDecodeODBCType(type, p)); |
|
594 int required = qGetIntData(hStmt, 10).toInt(); // nullable-flag |
|
595 // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN |
|
596 if (required == SQL_NO_NULLS) |
|
597 f.setRequired(true); |
|
598 else if (required == SQL_NULLABLE) |
|
599 f.setRequired(false); |
|
600 // else we don't know |
|
601 QVariant var = qGetIntData(hStmt, 6); |
|
602 f.setLength(var.isNull() ? -1 : var.toInt()); // column size |
|
603 var = qGetIntData(hStmt, 8).toInt(); |
|
604 f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision |
|
605 f.setSqlType(type); |
|
606 return f; |
|
607 } |
|
608 |
|
609 static QSqlField qMakeFieldInfo(const QODBCPrivate* p, int i ) |
|
610 { |
|
611 SQLSMALLINT colNameLen; |
|
612 SQLSMALLINT colType; |
|
613 QSQLULEN colSize; |
|
614 SQLSMALLINT colScale; |
|
615 SQLSMALLINT nullable; |
|
616 SQLRETURN r = SQL_ERROR; |
|
617 QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE); |
|
618 r = SQLDescribeCol(p->hStmt, |
|
619 i+1, |
|
620 colName.data(), |
|
621 (SQLSMALLINT)COLNAMESIZE, |
|
622 &colNameLen, |
|
623 &colType, |
|
624 &colSize, |
|
625 &colScale, |
|
626 &nullable); |
|
627 |
|
628 if (r != SQL_SUCCESS) { |
|
629 qSqlWarning(QString::fromLatin1("qMakeField: Unable to describe column %1").arg(i), p); |
|
630 return QSqlField(); |
|
631 } |
|
632 |
|
633 QSQLLEN unsignedFlag = SQL_FALSE; |
|
634 r = SQLColAttribute (p->hStmt, |
|
635 i + 1, |
|
636 SQL_DESC_UNSIGNED, |
|
637 0, |
|
638 0, |
|
639 0, |
|
640 &unsignedFlag); |
|
641 if (r != SQL_SUCCESS) { |
|
642 qSqlWarning(QString::fromLatin1("qMakeField: Unable to get column attributes for column %1").arg(i), p); |
|
643 } |
|
644 |
|
645 #ifdef UNICODE |
|
646 QString qColName(fromSQLTCHAR(colName, colNameLen)); |
|
647 #else |
|
648 QString qColName = QString::fromUtf8((const char *)colName.constData()); |
|
649 #endif |
|
650 // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN |
|
651 int required = -1; |
|
652 if (nullable == SQL_NO_NULLS) { |
|
653 required = 1; |
|
654 } else if (nullable == SQL_NULLABLE) { |
|
655 required = 0; |
|
656 } |
|
657 QVariant::Type type = qDecodeODBCType(colType, p, unsignedFlag == SQL_FALSE); |
|
658 QSqlField f(qColName, type); |
|
659 f.setSqlType(colType); |
|
660 f.setLength(colSize == 0 ? -1 : int(colSize)); |
|
661 f.setPrecision(colScale == 0 ? -1 : int(colScale)); |
|
662 if (nullable == SQL_NO_NULLS) |
|
663 f.setRequired(true); |
|
664 else if (nullable == SQL_NULLABLE) |
|
665 f.setRequired(false); |
|
666 // else we don't know |
|
667 return f; |
|
668 } |
|
669 |
|
670 static int qGetODBCVersion(const QString &connOpts) |
|
671 { |
|
672 #ifndef Q_ODBC_VERSION_2 |
|
673 if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive)) |
|
674 return SQL_OV_ODBC3; |
|
675 #endif |
|
676 if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC2"), Qt::CaseInsensitive)) |
|
677 return SQL_OV_ODBC2; |
|
678 #ifdef _IODBCUNIX_H |
|
679 return SQL_OV_ODBC3; |
|
680 #else |
|
681 return SQL_OV_ODBC2; |
|
682 #endif |
|
683 } |
|
684 |
|
685 QChar QODBCDriverPrivate::quoteChar() |
|
686 { |
|
687 if (!isQuoteInitialized) { |
|
688 SQLTCHAR driverResponse[4]; |
|
689 SQLSMALLINT length; |
|
690 int r = SQLGetInfo(hDbc, |
|
691 SQL_IDENTIFIER_QUOTE_CHAR, |
|
692 &driverResponse, |
|
693 sizeof(driverResponse), |
|
694 &length); |
|
695 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) |
|
696 #ifdef UNICODE |
|
697 quote = QChar(driverResponse[0]); |
|
698 #else |
|
699 quote = QLatin1Char(driverResponse[0]); |
|
700 #endif |
|
701 else |
|
702 quote = QLatin1Char('"'); |
|
703 isQuoteInitialized = true; |
|
704 } |
|
705 return quote; |
|
706 } |
|
707 |
|
708 |
|
709 bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) |
|
710 { |
|
711 // Set any connection attributes |
|
712 const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); |
|
713 SQLRETURN r = SQL_SUCCESS; |
|
714 for (int i = 0; i < opts.count(); ++i) { |
|
715 const QString tmp(opts.at(i)); |
|
716 int idx; |
|
717 if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { |
|
718 qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\''; |
|
719 continue; |
|
720 } |
|
721 const QString opt(tmp.left(idx)); |
|
722 const QString val(tmp.mid(idx + 1).simplified()); |
|
723 SQLUINTEGER v = 0; |
|
724 |
|
725 r = SQL_SUCCESS; |
|
726 if (opt.toUpper() == QLatin1String("SQL_ATTR_ACCESS_MODE")) { |
|
727 if (val.toUpper() == QLatin1String("SQL_MODE_READ_ONLY")) { |
|
728 v = SQL_MODE_READ_ONLY; |
|
729 } else if (val.toUpper() == QLatin1String("SQL_MODE_READ_WRITE")) { |
|
730 v = SQL_MODE_READ_WRITE; |
|
731 } else { |
|
732 qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; |
|
733 continue; |
|
734 } |
|
735 r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) v, 0); |
|
736 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) { |
|
737 v = val.toUInt(); |
|
738 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) v, 0); |
|
739 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { |
|
740 v = val.toUInt(); |
|
741 r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0); |
|
742 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) { |
|
743 val.utf16(); // 0 terminate |
|
744 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, |
|
745 #ifdef UNICODE |
|
746 toSQLTCHAR(val).data(), |
|
747 #else |
|
748 (SQLCHAR*) val.toUtf8().data(), |
|
749 #endif |
|
750 val.length()*sizeof(SQLTCHAR)); |
|
751 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_METADATA_ID")) { |
|
752 if (val.toUpper() == QLatin1String("SQL_TRUE")) { |
|
753 v = SQL_TRUE; |
|
754 } else if (val.toUpper() == QLatin1String("SQL_FALSE")) { |
|
755 v = SQL_FALSE; |
|
756 } else { |
|
757 qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; |
|
758 continue; |
|
759 } |
|
760 r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) v, 0); |
|
761 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) { |
|
762 v = val.toUInt(); |
|
763 r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) v, 0); |
|
764 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) { |
|
765 val.utf16(); // 0 terminate |
|
766 r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, |
|
767 #ifdef UNICODE |
|
768 toSQLTCHAR(val).data(), |
|
769 #else |
|
770 (SQLCHAR*) val.toUtf8().data(), |
|
771 #endif |
|
772 val.length()*sizeof(SQLTCHAR)); |
|
773 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACE")) { |
|
774 if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_OFF")) { |
|
775 v = SQL_OPT_TRACE_OFF; |
|
776 } else if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_ON")) { |
|
777 v = SQL_OPT_TRACE_ON; |
|
778 } else { |
|
779 qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; |
|
780 continue; |
|
781 } |
|
782 r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) v, 0); |
|
783 #ifndef Q_ODBC_VERSION_2 |
|
784 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_POOLING")) { |
|
785 if (val == QLatin1String("SQL_CP_OFF")) |
|
786 v = SQL_CP_OFF; |
|
787 else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_DRIVER")) |
|
788 v = SQL_CP_ONE_PER_DRIVER; |
|
789 else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_HENV")) |
|
790 v = SQL_CP_ONE_PER_HENV; |
|
791 else if (val.toUpper() == QLatin1String("SQL_CP_DEFAULT")) |
|
792 v = SQL_CP_DEFAULT; |
|
793 else { |
|
794 qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; |
|
795 continue; |
|
796 } |
|
797 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER)v, 0); |
|
798 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CP_MATCH")) { |
|
799 if (val.toUpper() == QLatin1String("SQL_CP_STRICT_MATCH")) |
|
800 v = SQL_CP_STRICT_MATCH; |
|
801 else if (val.toUpper() == QLatin1String("SQL_CP_RELAXED_MATCH")) |
|
802 v = SQL_CP_RELAXED_MATCH; |
|
803 else if (val.toUpper() == QLatin1String("SQL_CP_MATCH_DEFAULT")) |
|
804 v = SQL_CP_MATCH_DEFAULT; |
|
805 else { |
|
806 qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\''; |
|
807 continue; |
|
808 } |
|
809 r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER)v, 0); |
|
810 #endif |
|
811 } else if (opt.toUpper() == QLatin1String("SQL_ATTR_ODBC_VERSION")) { |
|
812 // Already handled in QODBCDriver::open() |
|
813 continue; |
|
814 } else { |
|
815 qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\''; |
|
816 } |
|
817 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) |
|
818 qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg( |
|
819 opt), this); |
|
820 } |
|
821 return true; |
|
822 } |
|
823 |
|
824 void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog, |
|
825 QString &schema, QString &table) |
|
826 { |
|
827 if (!useSchema) { |
|
828 table = qualifier; |
|
829 return; |
|
830 } |
|
831 QStringList l = qualifier.split(QLatin1Char('.')); |
|
832 if (l.count() > 3) |
|
833 return; // can't possibly be a valid table qualifier |
|
834 int i = 0, n = l.count(); |
|
835 if (n == 1) { |
|
836 table = qualifier; |
|
837 } else { |
|
838 for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) { |
|
839 if (n == 3) { |
|
840 if (i == 0) { |
|
841 catalog = *it; |
|
842 } else if (i == 1) { |
|
843 schema = *it; |
|
844 } else if (i == 2) { |
|
845 table = *it; |
|
846 } |
|
847 } else if (n == 2) { |
|
848 if (i == 0) { |
|
849 schema = *it; |
|
850 } else if (i == 1) { |
|
851 table = *it; |
|
852 } |
|
853 } |
|
854 i++; |
|
855 } |
|
856 } |
|
857 } |
|
858 |
|
859 QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const |
|
860 { |
|
861 DefaultCase ret; |
|
862 SQLUSMALLINT casing; |
|
863 int r = SQLGetInfo(hDbc, |
|
864 SQL_IDENTIFIER_CASE, |
|
865 &casing, |
|
866 sizeof(casing), |
|
867 NULL); |
|
868 if ( r != SQL_SUCCESS) |
|
869 ret = Mixed;//arbitrary case if driver cannot be queried |
|
870 else { |
|
871 switch (casing) { |
|
872 case (SQL_IC_UPPER): |
|
873 ret = Upper; |
|
874 break; |
|
875 case (SQL_IC_LOWER): |
|
876 ret = Lower; |
|
877 break; |
|
878 case (SQL_IC_SENSITIVE): |
|
879 ret = Sensitive; |
|
880 break; |
|
881 case (SQL_IC_MIXED): |
|
882 default: |
|
883 ret = Mixed; |
|
884 break; |
|
885 } |
|
886 } |
|
887 return ret; |
|
888 } |
|
889 |
|
890 /* |
|
891 Adjust the casing of an identifier to match what the |
|
892 database engine would have done to it. |
|
893 */ |
|
894 QString QODBCDriverPrivate::adjustCase(const QString &identifier) const |
|
895 { |
|
896 QString ret = identifier; |
|
897 switch(defaultCase()) { |
|
898 case (Lower): |
|
899 ret = identifier.toLower(); |
|
900 break; |
|
901 case (Upper): |
|
902 ret = identifier.toUpper(); |
|
903 break; |
|
904 case(Mixed): |
|
905 case(Sensitive): |
|
906 default: |
|
907 ret = identifier; |
|
908 } |
|
909 return ret; |
|
910 } |
|
911 |
|
912 //////////////////////////////////////////////////////////////////////////// |
|
913 |
|
914 QODBCResult::QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p) |
|
915 : QSqlResult(db) |
|
916 { |
|
917 d = new QODBCPrivate(p); |
|
918 } |
|
919 |
|
920 QODBCResult::~QODBCResult() |
|
921 { |
|
922 if (d->hStmt && d->isStmtHandleValid(driver()) && driver()->isOpen()) { |
|
923 SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); |
|
924 if (r != SQL_SUCCESS) |
|
925 qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") |
|
926 + QString::number(r), d); |
|
927 } |
|
928 |
|
929 delete d; |
|
930 } |
|
931 |
|
932 bool QODBCResult::reset (const QString& query) |
|
933 { |
|
934 setActive(false); |
|
935 setAt(QSql::BeforeFirstRow); |
|
936 d->rInf.clear(); |
|
937 d->fieldCache.clear(); |
|
938 d->fieldCacheIdx = 0; |
|
939 |
|
940 // Always reallocate the statement handle - the statement attributes |
|
941 // are not reset if SQLFreeStmt() is called which causes some problems. |
|
942 SQLRETURN r; |
|
943 if (d->hStmt && d->isStmtHandleValid(driver())) { |
|
944 r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); |
|
945 if (r != SQL_SUCCESS) { |
|
946 qSqlWarning(QLatin1String("QODBCResult::reset: Unable to free statement handle"), d); |
|
947 return false; |
|
948 } |
|
949 } |
|
950 r = SQLAllocHandle(SQL_HANDLE_STMT, |
|
951 d->dpDbc(), |
|
952 &d->hStmt); |
|
953 if (r != SQL_SUCCESS) { |
|
954 qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d); |
|
955 return false; |
|
956 } |
|
957 |
|
958 d->updateStmtHandleState(driver()); |
|
959 |
|
960 if (d->userForwardOnly) { |
|
961 r = SQLSetStmtAttr(d->hStmt, |
|
962 SQL_ATTR_CURSOR_TYPE, |
|
963 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, |
|
964 SQL_IS_UINTEGER); |
|
965 } else { |
|
966 r = SQLSetStmtAttr(d->hStmt, |
|
967 SQL_ATTR_CURSOR_TYPE, |
|
968 (SQLPOINTER)SQL_CURSOR_STATIC, |
|
969 SQL_IS_UINTEGER); |
|
970 } |
|
971 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
972 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
973 "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. " |
|
974 "Please check your ODBC driver configuration"), QSqlError::StatementError, d)); |
|
975 return false; |
|
976 } |
|
977 |
|
978 #ifdef UNICODE |
|
979 r = SQLExecDirect(d->hStmt, |
|
980 toSQLTCHAR(query).data(), |
|
981 (SQLINTEGER) query.length()); |
|
982 #else |
|
983 QByteArray query8 = query.toUtf8(); |
|
984 r = SQLExecDirect(d->hStmt, |
|
985 (SQLCHAR*) query8.data(), |
|
986 (SQLINTEGER) query8.length()); |
|
987 #endif |
|
988 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) { |
|
989 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
990 "Unable to execute statement"), QSqlError::StatementError, d)); |
|
991 return false; |
|
992 } |
|
993 |
|
994 if(r == SQL_NO_DATA) { |
|
995 setSelect(false); |
|
996 return true; |
|
997 } |
|
998 |
|
999 SQLINTEGER isScrollable, bufferLength; |
|
1000 r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, &bufferLength); |
|
1001 if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) |
|
1002 QSqlResult::setForwardOnly(isScrollable==SQL_NONSCROLLABLE); |
|
1003 |
|
1004 SQLSMALLINT count; |
|
1005 SQLNumResultCols(d->hStmt, &count); |
|
1006 if (count) { |
|
1007 setSelect(true); |
|
1008 for (int i = 0; i < count; ++i) { |
|
1009 d->rInf.append(qMakeFieldInfo(d, i)); |
|
1010 } |
|
1011 d->fieldCache.resize(count); |
|
1012 } else { |
|
1013 setSelect(false); |
|
1014 } |
|
1015 setActive(true); |
|
1016 |
|
1017 return true; |
|
1018 } |
|
1019 |
|
1020 bool QODBCResult::fetch(int i) |
|
1021 { |
|
1022 if (!driver()->isOpen()) |
|
1023 return false; |
|
1024 |
|
1025 if (isForwardOnly() && i < at()) |
|
1026 return false; |
|
1027 if (i == at()) |
|
1028 return true; |
|
1029 d->clearValues(); |
|
1030 int actualIdx = i + 1; |
|
1031 if (actualIdx <= 0) { |
|
1032 setAt(QSql::BeforeFirstRow); |
|
1033 return false; |
|
1034 } |
|
1035 SQLRETURN r; |
|
1036 if (isForwardOnly()) { |
|
1037 bool ok = true; |
|
1038 while (ok && i > at()) |
|
1039 ok = fetchNext(); |
|
1040 return ok; |
|
1041 } else { |
|
1042 r = SQLFetchScroll(d->hStmt, |
|
1043 SQL_FETCH_ABSOLUTE, |
|
1044 actualIdx); |
|
1045 } |
|
1046 if (r != SQL_SUCCESS) { |
|
1047 if (r != SQL_NO_DATA) |
|
1048 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1049 "Unable to fetch"), QSqlError::ConnectionError, d)); |
|
1050 return false; |
|
1051 } |
|
1052 setAt(i); |
|
1053 return true; |
|
1054 } |
|
1055 |
|
1056 bool QODBCResult::fetchNext() |
|
1057 { |
|
1058 SQLRETURN r; |
|
1059 d->clearValues(); |
|
1060 |
|
1061 if (d->hasSQLFetchScroll) |
|
1062 r = SQLFetchScroll(d->hStmt, |
|
1063 SQL_FETCH_NEXT, |
|
1064 0); |
|
1065 else |
|
1066 r = SQLFetch(d->hStmt); |
|
1067 |
|
1068 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
1069 if (r != SQL_NO_DATA) |
|
1070 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1071 "Unable to fetch next"), QSqlError::ConnectionError, d)); |
|
1072 return false; |
|
1073 } |
|
1074 setAt(at() + 1); |
|
1075 return true; |
|
1076 } |
|
1077 |
|
1078 bool QODBCResult::fetchFirst() |
|
1079 { |
|
1080 if (isForwardOnly() && at() != QSql::BeforeFirstRow) |
|
1081 return false; |
|
1082 SQLRETURN r; |
|
1083 d->clearValues(); |
|
1084 if (isForwardOnly()) { |
|
1085 return fetchNext(); |
|
1086 } |
|
1087 r = SQLFetchScroll(d->hStmt, |
|
1088 SQL_FETCH_FIRST, |
|
1089 0); |
|
1090 if (r != SQL_SUCCESS) { |
|
1091 if (r != SQL_NO_DATA) |
|
1092 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1093 "Unable to fetch first"), QSqlError::ConnectionError, d)); |
|
1094 return false; |
|
1095 } |
|
1096 setAt(0); |
|
1097 return true; |
|
1098 } |
|
1099 |
|
1100 bool QODBCResult::fetchPrevious() |
|
1101 { |
|
1102 if (isForwardOnly()) |
|
1103 return false; |
|
1104 SQLRETURN r; |
|
1105 d->clearValues(); |
|
1106 r = SQLFetchScroll(d->hStmt, |
|
1107 SQL_FETCH_PRIOR, |
|
1108 0); |
|
1109 if (r != SQL_SUCCESS) { |
|
1110 if (r != SQL_NO_DATA) |
|
1111 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1112 "Unable to fetch previous"), QSqlError::ConnectionError, d)); |
|
1113 return false; |
|
1114 } |
|
1115 setAt(at() - 1); |
|
1116 return true; |
|
1117 } |
|
1118 |
|
1119 bool QODBCResult::fetchLast() |
|
1120 { |
|
1121 SQLRETURN r; |
|
1122 d->clearValues(); |
|
1123 |
|
1124 if (isForwardOnly()) { |
|
1125 // cannot seek to last row in forwardOnly mode, so we have to use brute force |
|
1126 int i = at(); |
|
1127 if (i == QSql::AfterLastRow) |
|
1128 return false; |
|
1129 if (i == QSql::BeforeFirstRow) |
|
1130 i = 0; |
|
1131 while (fetchNext()) |
|
1132 ++i; |
|
1133 setAt(i); |
|
1134 return true; |
|
1135 } |
|
1136 |
|
1137 r = SQLFetchScroll(d->hStmt, |
|
1138 SQL_FETCH_LAST, |
|
1139 0); |
|
1140 if (r != SQL_SUCCESS) { |
|
1141 if (r != SQL_NO_DATA) |
|
1142 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1143 "Unable to fetch last"), QSqlError::ConnectionError, d)); |
|
1144 return false; |
|
1145 } |
|
1146 SQLINTEGER currRow; |
|
1147 r = SQLGetStmtAttr(d->hStmt, |
|
1148 SQL_ROW_NUMBER, |
|
1149 &currRow, |
|
1150 SQL_IS_INTEGER, |
|
1151 0); |
|
1152 if (r != SQL_SUCCESS) |
|
1153 return false; |
|
1154 setAt(currRow-1); |
|
1155 return true; |
|
1156 } |
|
1157 |
|
1158 QVariant QODBCResult::data(int field) |
|
1159 { |
|
1160 if (field >= d->rInf.count() || field < 0) { |
|
1161 qWarning() << "QODBCResult::data: column" << field << "out of range"; |
|
1162 return QVariant(); |
|
1163 } |
|
1164 if (field < d->fieldCacheIdx) |
|
1165 return d->fieldCache.at(field); |
|
1166 |
|
1167 SQLRETURN r(0); |
|
1168 QSQLLEN lengthIndicator = 0; |
|
1169 |
|
1170 for (int i = d->fieldCacheIdx; i <= field; ++i) { |
|
1171 // some servers do not support fetching column n after we already |
|
1172 // fetched column n+1, so cache all previous columns here |
|
1173 const QSqlField info = d->rInf.field(i); |
|
1174 switch (info.type()) { |
|
1175 case QVariant::LongLong: |
|
1176 d->fieldCache[i] = qGetBigIntData(d->hStmt, i); |
|
1177 break; |
|
1178 case QVariant::ULongLong: |
|
1179 d->fieldCache[i] = qGetBigIntData(d->hStmt, i, false); |
|
1180 break; |
|
1181 case QVariant::Int: |
|
1182 d->fieldCache[i] = qGetIntData(d->hStmt, i); |
|
1183 break; |
|
1184 case QVariant::UInt: |
|
1185 d->fieldCache[i] = qGetIntData(d->hStmt, i, false); |
|
1186 break; |
|
1187 case QVariant::Date: |
|
1188 DATE_STRUCT dbuf; |
|
1189 r = SQLGetData(d->hStmt, |
|
1190 i + 1, |
|
1191 SQL_C_DATE, |
|
1192 (SQLPOINTER)&dbuf, |
|
1193 0, |
|
1194 &lengthIndicator); |
|
1195 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) |
|
1196 d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day)); |
|
1197 else |
|
1198 d->fieldCache[i] = QVariant(QVariant::Date); |
|
1199 break; |
|
1200 case QVariant::Time: |
|
1201 TIME_STRUCT tbuf; |
|
1202 r = SQLGetData(d->hStmt, |
|
1203 i + 1, |
|
1204 SQL_C_TIME, |
|
1205 (SQLPOINTER)&tbuf, |
|
1206 0, |
|
1207 &lengthIndicator); |
|
1208 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) |
|
1209 d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second)); |
|
1210 else |
|
1211 d->fieldCache[i] = QVariant(QVariant::Time); |
|
1212 break; |
|
1213 case QVariant::DateTime: |
|
1214 TIMESTAMP_STRUCT dtbuf; |
|
1215 r = SQLGetData(d->hStmt, |
|
1216 i + 1, |
|
1217 SQL_C_TIMESTAMP, |
|
1218 (SQLPOINTER)&dtbuf, |
|
1219 0, |
|
1220 &lengthIndicator); |
|
1221 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) |
|
1222 d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day), |
|
1223 QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000))); |
|
1224 else |
|
1225 d->fieldCache[i] = QVariant(QVariant::DateTime); |
|
1226 break; |
|
1227 case QVariant::ByteArray: |
|
1228 d->fieldCache[i] = qGetBinaryData(d->hStmt, i); |
|
1229 break; |
|
1230 case QVariant::String: |
|
1231 d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode); |
|
1232 break; |
|
1233 case QVariant::Double: |
|
1234 switch(numericalPrecisionPolicy()) { |
|
1235 case QSql::LowPrecisionInt32: |
|
1236 d->fieldCache[i] = qGetIntData(d->hStmt, i); |
|
1237 break; |
|
1238 case QSql::LowPrecisionInt64: |
|
1239 d->fieldCache[i] = qGetBigIntData(d->hStmt, i); |
|
1240 break; |
|
1241 case QSql::LowPrecisionDouble: |
|
1242 d->fieldCache[i] = qGetDoubleData(d->hStmt, i); |
|
1243 break; |
|
1244 case QSql::HighPrecision: |
|
1245 d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false); |
|
1246 break; |
|
1247 } |
|
1248 break; |
|
1249 default: |
|
1250 d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false)); |
|
1251 break; |
|
1252 } |
|
1253 d->fieldCacheIdx = field + 1; |
|
1254 } |
|
1255 return d->fieldCache[field]; |
|
1256 } |
|
1257 |
|
1258 bool QODBCResult::isNull(int field) |
|
1259 { |
|
1260 if (field < 0 || field > d->fieldCache.size()) |
|
1261 return true; |
|
1262 if (field <= d->fieldCacheIdx) { |
|
1263 // since there is no good way to find out whether the value is NULL |
|
1264 // without fetching the field we'll fetch it here. |
|
1265 // (data() also sets the NULL flag) |
|
1266 data(field); |
|
1267 } |
|
1268 return d->fieldCache.at(field).isNull(); |
|
1269 } |
|
1270 |
|
1271 int QODBCResult::size() |
|
1272 { |
|
1273 return -1; |
|
1274 } |
|
1275 |
|
1276 int QODBCResult::numRowsAffected() |
|
1277 { |
|
1278 QSQLLEN affectedRowCount = 0; |
|
1279 SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount); |
|
1280 if (r == SQL_SUCCESS) |
|
1281 return affectedRowCount; |
|
1282 else |
|
1283 qSqlWarning(QLatin1String("QODBCResult::numRowsAffected: Unable to count affected rows"), d); |
|
1284 return -1; |
|
1285 } |
|
1286 |
|
1287 bool QODBCResult::prepare(const QString& query) |
|
1288 { |
|
1289 setActive(false); |
|
1290 setAt(QSql::BeforeFirstRow); |
|
1291 SQLRETURN r; |
|
1292 |
|
1293 d->rInf.clear(); |
|
1294 if (d->hStmt && d->isStmtHandleValid(driver())) { |
|
1295 r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); |
|
1296 if (r != SQL_SUCCESS) { |
|
1297 qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to close statement"), d); |
|
1298 return false; |
|
1299 } |
|
1300 } |
|
1301 r = SQLAllocHandle(SQL_HANDLE_STMT, |
|
1302 d->dpDbc(), |
|
1303 &d->hStmt); |
|
1304 if (r != SQL_SUCCESS) { |
|
1305 qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d); |
|
1306 return false; |
|
1307 } |
|
1308 |
|
1309 d->updateStmtHandleState(driver()); |
|
1310 |
|
1311 if (d->userForwardOnly) { |
|
1312 r = SQLSetStmtAttr(d->hStmt, |
|
1313 SQL_ATTR_CURSOR_TYPE, |
|
1314 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, |
|
1315 SQL_IS_UINTEGER); |
|
1316 } else { |
|
1317 r = SQLSetStmtAttr(d->hStmt, |
|
1318 SQL_ATTR_CURSOR_TYPE, |
|
1319 (SQLPOINTER)SQL_CURSOR_STATIC, |
|
1320 SQL_IS_UINTEGER); |
|
1321 } |
|
1322 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
1323 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1324 "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. " |
|
1325 "Please check your ODBC driver configuration"), QSqlError::StatementError, d)); |
|
1326 return false; |
|
1327 } |
|
1328 |
|
1329 #ifdef UNICODE |
|
1330 r = SQLPrepare(d->hStmt, |
|
1331 toSQLTCHAR(query).data(), |
|
1332 (SQLINTEGER) query.length()); |
|
1333 #else |
|
1334 QByteArray query8 = query.toUtf8(); |
|
1335 r = SQLPrepare(d->hStmt, |
|
1336 (SQLCHAR*) query8.data(), |
|
1337 (SQLINTEGER) query8.length()); |
|
1338 #endif |
|
1339 |
|
1340 if (r != SQL_SUCCESS) { |
|
1341 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1342 "Unable to prepare statement"), QSqlError::StatementError, d)); |
|
1343 return false; |
|
1344 } |
|
1345 return true; |
|
1346 } |
|
1347 |
|
1348 bool QODBCResult::exec() |
|
1349 { |
|
1350 setActive(false); |
|
1351 setAt(QSql::BeforeFirstRow); |
|
1352 d->rInf.clear(); |
|
1353 d->fieldCache.clear(); |
|
1354 d->fieldCacheIdx = 0; |
|
1355 |
|
1356 if (!d->hStmt) { |
|
1357 qSqlWarning(QLatin1String("QODBCResult::exec: No statement handle available"), d); |
|
1358 return false; |
|
1359 } |
|
1360 |
|
1361 if (isSelect()) |
|
1362 SQLCloseCursor(d->hStmt); |
|
1363 |
|
1364 QList<QByteArray> tmpStorage; // holds temporary buffers |
|
1365 QVarLengthArray<QSQLLEN, 32> indicators(boundValues().count()); |
|
1366 memset(indicators.data(), 0, indicators.size() * sizeof(QSQLLEN)); |
|
1367 |
|
1368 // bind parameters - only positional binding allowed |
|
1369 QVector<QVariant>& values = boundValues(); |
|
1370 int i; |
|
1371 SQLRETURN r; |
|
1372 for (i = 0; i < values.count(); ++i) { |
|
1373 if (bindValueType(i) & QSql::Out) |
|
1374 values[i].detach(); |
|
1375 const QVariant &val = values.at(i); |
|
1376 QSQLLEN *ind = &indicators[i]; |
|
1377 if (val.isNull()) |
|
1378 *ind = SQL_NULL_DATA; |
|
1379 switch (val.type()) { |
|
1380 case QVariant::Date: { |
|
1381 QByteArray ba; |
|
1382 ba.resize(sizeof(DATE_STRUCT)); |
|
1383 DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData(); |
|
1384 QDate qdt = val.toDate(); |
|
1385 dt->year = qdt.year(); |
|
1386 dt->month = qdt.month(); |
|
1387 dt->day = qdt.day(); |
|
1388 r = SQLBindParameter(d->hStmt, |
|
1389 i + 1, |
|
1390 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1391 SQL_C_DATE, |
|
1392 SQL_DATE, |
|
1393 0, |
|
1394 0, |
|
1395 (void *) dt, |
|
1396 0, |
|
1397 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1398 tmpStorage.append(ba); |
|
1399 break; } |
|
1400 case QVariant::Time: { |
|
1401 QByteArray ba; |
|
1402 ba.resize(sizeof(TIME_STRUCT)); |
|
1403 TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData(); |
|
1404 QTime qdt = val.toTime(); |
|
1405 dt->hour = qdt.hour(); |
|
1406 dt->minute = qdt.minute(); |
|
1407 dt->second = qdt.second(); |
|
1408 r = SQLBindParameter(d->hStmt, |
|
1409 i + 1, |
|
1410 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1411 SQL_C_TIME, |
|
1412 SQL_TIME, |
|
1413 0, |
|
1414 0, |
|
1415 (void *) dt, |
|
1416 0, |
|
1417 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1418 tmpStorage.append(ba); |
|
1419 break; } |
|
1420 case QVariant::DateTime: { |
|
1421 QByteArray ba; |
|
1422 ba.resize(sizeof(TIMESTAMP_STRUCT)); |
|
1423 TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData(); |
|
1424 QDateTime qdt = val.toDateTime(); |
|
1425 dt->year = qdt.date().year(); |
|
1426 dt->month = qdt.date().month(); |
|
1427 dt->day = qdt.date().day(); |
|
1428 dt->hour = qdt.time().hour(); |
|
1429 dt->minute = qdt.time().minute(); |
|
1430 dt->second = qdt.time().second(); |
|
1431 dt->fraction = qdt.time().msec() * 1000000; |
|
1432 r = SQLBindParameter(d->hStmt, |
|
1433 i + 1, |
|
1434 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1435 SQL_C_TIMESTAMP, |
|
1436 SQL_TIMESTAMP, |
|
1437 19, |
|
1438 0, |
|
1439 (void *) dt, |
|
1440 0, |
|
1441 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1442 tmpStorage.append(ba); |
|
1443 break; } |
|
1444 case QVariant::Int: |
|
1445 r = SQLBindParameter(d->hStmt, |
|
1446 i + 1, |
|
1447 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1448 SQL_C_SLONG, |
|
1449 SQL_INTEGER, |
|
1450 0, |
|
1451 0, |
|
1452 (void *) val.constData(), |
|
1453 0, |
|
1454 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1455 break; |
|
1456 case QVariant::UInt: |
|
1457 r = SQLBindParameter(d->hStmt, |
|
1458 i + 1, |
|
1459 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1460 SQL_C_ULONG, |
|
1461 SQL_NUMERIC, |
|
1462 15, |
|
1463 0, |
|
1464 (void *) val.constData(), |
|
1465 0, |
|
1466 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1467 break; |
|
1468 case QVariant::Double: |
|
1469 r = SQLBindParameter(d->hStmt, |
|
1470 i + 1, |
|
1471 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1472 SQL_C_DOUBLE, |
|
1473 SQL_DOUBLE, |
|
1474 0, |
|
1475 0, |
|
1476 (void *) val.constData(), |
|
1477 0, |
|
1478 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1479 break; |
|
1480 case QVariant::LongLong: |
|
1481 r = SQLBindParameter(d->hStmt, |
|
1482 i + 1, |
|
1483 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1484 SQL_C_SBIGINT, |
|
1485 SQL_BIGINT, |
|
1486 0, |
|
1487 0, |
|
1488 (void *) val.constData(), |
|
1489 0, |
|
1490 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1491 break; |
|
1492 case QVariant::ULongLong: |
|
1493 r = SQLBindParameter(d->hStmt, |
|
1494 i + 1, |
|
1495 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1496 SQL_C_UBIGINT, |
|
1497 SQL_BIGINT, |
|
1498 0, |
|
1499 0, |
|
1500 (void *) val.constData(), |
|
1501 0, |
|
1502 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1503 break; |
|
1504 case QVariant::ByteArray: |
|
1505 if (*ind != SQL_NULL_DATA) { |
|
1506 *ind = val.toByteArray().size(); |
|
1507 } |
|
1508 r = SQLBindParameter(d->hStmt, |
|
1509 i + 1, |
|
1510 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1511 SQL_C_BINARY, |
|
1512 SQL_LONGVARBINARY, |
|
1513 val.toByteArray().size(), |
|
1514 0, |
|
1515 (void *) val.toByteArray().constData(), |
|
1516 val.toByteArray().size(), |
|
1517 ind); |
|
1518 break; |
|
1519 case QVariant::Bool: |
|
1520 r = SQLBindParameter(d->hStmt, |
|
1521 i + 1, |
|
1522 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1523 SQL_C_BIT, |
|
1524 SQL_BIT, |
|
1525 0, |
|
1526 0, |
|
1527 (void *) val.constData(), |
|
1528 0, |
|
1529 *ind == SQL_NULL_DATA ? ind : NULL); |
|
1530 break; |
|
1531 case QVariant::String: |
|
1532 #ifndef Q_ODBC_VERSION_2 |
|
1533 if (d->unicode) { |
|
1534 QString str = val.toString(); |
|
1535 if (*ind != SQL_NULL_DATA) |
|
1536 *ind = str.length() * sizeof(SQLTCHAR); |
|
1537 int strSize = str.length() * sizeof(SQLTCHAR); |
|
1538 |
|
1539 if (bindValueType(i) & QSql::Out) { |
|
1540 QVarLengthArray<SQLTCHAR> ba(toSQLTCHAR(str)); |
|
1541 ba.reserve(str.capacity()); |
|
1542 r = SQLBindParameter(d->hStmt, |
|
1543 i + 1, |
|
1544 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1545 SQL_C_TCHAR, |
|
1546 strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, |
|
1547 0, // god knows... don't change this! |
|
1548 0, |
|
1549 (void *)ba.constData(), |
|
1550 ba.size(), |
|
1551 ind); |
|
1552 tmpStorage.append(QByteArray((const char *)ba.constData(), ba.size()*sizeof(SQLTCHAR))); |
|
1553 break; |
|
1554 } |
|
1555 QByteArray strba((const char *)toSQLTCHAR(str).constData(), str.size()*sizeof(SQLTCHAR)); |
|
1556 r = SQLBindParameter(d->hStmt, |
|
1557 i + 1, |
|
1558 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1559 SQL_C_TCHAR, |
|
1560 strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, |
|
1561 strSize, |
|
1562 0, |
|
1563 (SQLPOINTER)strba.constData(), |
|
1564 strba.size(), |
|
1565 ind); |
|
1566 tmpStorage.append(strba); |
|
1567 break; |
|
1568 } |
|
1569 else |
|
1570 #endif |
|
1571 { |
|
1572 QByteArray str = val.toString().toUtf8(); |
|
1573 if (*ind != SQL_NULL_DATA) |
|
1574 *ind = str.length(); |
|
1575 int strSize = str.length(); |
|
1576 |
|
1577 r = SQLBindParameter(d->hStmt, |
|
1578 i + 1, |
|
1579 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1580 SQL_C_CHAR, |
|
1581 strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR, |
|
1582 strSize, |
|
1583 0, |
|
1584 (void *)str.constData(), |
|
1585 strSize, |
|
1586 ind); |
|
1587 tmpStorage.append(str); |
|
1588 break; |
|
1589 } |
|
1590 // fall through |
|
1591 default: { |
|
1592 QByteArray ba = val.toByteArray(); |
|
1593 if (*ind != SQL_NULL_DATA) |
|
1594 *ind = ba.size(); |
|
1595 r = SQLBindParameter(d->hStmt, |
|
1596 i + 1, |
|
1597 qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], |
|
1598 SQL_C_BINARY, |
|
1599 SQL_VARBINARY, |
|
1600 ba.length() + 1, |
|
1601 0, |
|
1602 (void *) ba.constData(), |
|
1603 ba.length() + 1, |
|
1604 ind); |
|
1605 tmpStorage.append(ba); |
|
1606 break; } |
|
1607 } |
|
1608 if (r != SQL_SUCCESS) { |
|
1609 qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d); |
|
1610 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1611 "Unable to bind variable"), QSqlError::StatementError, d)); |
|
1612 return false; |
|
1613 } |
|
1614 } |
|
1615 r = SQLExecute(d->hStmt); |
|
1616 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
1617 qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d); |
|
1618 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1619 "Unable to execute statement"), QSqlError::StatementError, d)); |
|
1620 return false; |
|
1621 } |
|
1622 |
|
1623 SQLINTEGER isScrollable, bufferLength; |
|
1624 r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, &bufferLength); |
|
1625 if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) |
|
1626 QSqlResult::setForwardOnly(isScrollable==SQL_NONSCROLLABLE); |
|
1627 |
|
1628 SQLSMALLINT count; |
|
1629 SQLNumResultCols(d->hStmt, &count); |
|
1630 if (count) { |
|
1631 setSelect(true); |
|
1632 for (int i = 0; i < count; ++i) { |
|
1633 d->rInf.append(qMakeFieldInfo(d, i)); |
|
1634 } |
|
1635 d->fieldCache.resize(count); |
|
1636 } else { |
|
1637 setSelect(false); |
|
1638 } |
|
1639 setActive(true); |
|
1640 |
|
1641 |
|
1642 //get out parameters |
|
1643 if (!hasOutValues()) |
|
1644 return true; |
|
1645 |
|
1646 for (i = 0; i < values.count(); ++i) { |
|
1647 switch (values.at(i).type()) { |
|
1648 case QVariant::Date: { |
|
1649 DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData()); |
|
1650 values[i] = QVariant(QDate(ds.year, ds.month, ds.day)); |
|
1651 break; } |
|
1652 case QVariant::Time: { |
|
1653 TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData()); |
|
1654 values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second)); |
|
1655 break; } |
|
1656 case QVariant::DateTime: { |
|
1657 TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*) |
|
1658 tmpStorage.takeFirst().constData()); |
|
1659 values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day), |
|
1660 QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000))); |
|
1661 break; } |
|
1662 case QVariant::Bool: |
|
1663 case QVariant::Int: |
|
1664 case QVariant::UInt: |
|
1665 case QVariant::Double: |
|
1666 case QVariant::ByteArray: |
|
1667 case QVariant::LongLong: |
|
1668 case QVariant::ULongLong: |
|
1669 //nothing to do |
|
1670 break; |
|
1671 case QVariant::String: |
|
1672 if (d->unicode) { |
|
1673 if (bindValueType(i) & QSql::Out) { |
|
1674 QByteArray first = tmpStorage.takeFirst(); |
|
1675 QVarLengthArray<SQLTCHAR> array; |
|
1676 array.append((SQLTCHAR *)first.constData(), first.size()); |
|
1677 values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR*)); |
|
1678 } |
|
1679 break; |
|
1680 } |
|
1681 // fall through |
|
1682 default: { |
|
1683 if (bindValueType(i) & QSql::Out) |
|
1684 values[i] = tmpStorage.takeFirst(); |
|
1685 break; } |
|
1686 } |
|
1687 if (indicators[i] == SQL_NULL_DATA) |
|
1688 values[i] = QVariant(values[i].type()); |
|
1689 } |
|
1690 return true; |
|
1691 } |
|
1692 |
|
1693 QSqlRecord QODBCResult::record() const |
|
1694 { |
|
1695 if (!isActive() || !isSelect()) |
|
1696 return QSqlRecord(); |
|
1697 return d->rInf; |
|
1698 } |
|
1699 |
|
1700 QVariant QODBCResult::handle() const |
|
1701 { |
|
1702 return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt); |
|
1703 } |
|
1704 |
|
1705 bool QODBCResult::nextResult() |
|
1706 { |
|
1707 setActive(false); |
|
1708 setAt(QSql::BeforeFirstRow); |
|
1709 d->rInf.clear(); |
|
1710 d->fieldCache.clear(); |
|
1711 d->fieldCacheIdx = 0; |
|
1712 setSelect(false); |
|
1713 |
|
1714 SQLRETURN r = SQLMoreResults(d->hStmt); |
|
1715 if (r != SQL_SUCCESS) { |
|
1716 if (r == SQL_SUCCESS_WITH_INFO) { |
|
1717 int nativeCode = -1; |
|
1718 QString message = qODBCWarn(d, &nativeCode); |
|
1719 qWarning() << "QODBCResult::nextResult():" << message; |
|
1720 } else { |
|
1721 if (r != SQL_NO_DATA) |
|
1722 setLastError(qMakeError(QCoreApplication::translate("QODBCResult", |
|
1723 "Unable to fetch last"), QSqlError::ConnectionError, d)); |
|
1724 return false; |
|
1725 } |
|
1726 } |
|
1727 |
|
1728 SQLSMALLINT count; |
|
1729 SQLNumResultCols(d->hStmt, &count); |
|
1730 if (count) { |
|
1731 setSelect(true); |
|
1732 for (int i = 0; i < count; ++i) { |
|
1733 d->rInf.append(qMakeFieldInfo(d, i)); |
|
1734 } |
|
1735 d->fieldCache.resize(count); |
|
1736 } else { |
|
1737 setSelect(false); |
|
1738 } |
|
1739 setActive(true); |
|
1740 |
|
1741 return true; |
|
1742 } |
|
1743 |
|
1744 void QODBCResult::virtual_hook(int id, void *data) |
|
1745 { |
|
1746 switch (id) { |
|
1747 case QSqlResult::DetachFromResultSet: |
|
1748 if (d->hStmt) |
|
1749 SQLCloseCursor(d->hStmt); |
|
1750 break; |
|
1751 case QSqlResult::NextResult: |
|
1752 Q_ASSERT(data); |
|
1753 *static_cast<bool*>(data) = nextResult(); |
|
1754 break; |
|
1755 default: |
|
1756 QSqlResult::virtual_hook(id, data); |
|
1757 } |
|
1758 } |
|
1759 |
|
1760 void QODBCResult::setForwardOnly(bool forward) |
|
1761 { |
|
1762 d->userForwardOnly = forward; |
|
1763 QSqlResult::setForwardOnly(forward); |
|
1764 } |
|
1765 |
|
1766 //////////////////////////////////////// |
|
1767 |
|
1768 |
|
1769 QODBCDriver::QODBCDriver(QObject *parent) |
|
1770 : QSqlDriver(parent) |
|
1771 { |
|
1772 init(); |
|
1773 } |
|
1774 |
|
1775 QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent) |
|
1776 : QSqlDriver(parent) |
|
1777 { |
|
1778 init(); |
|
1779 d->hEnv = env; |
|
1780 d->hDbc = con; |
|
1781 if (env && con) { |
|
1782 setOpen(true); |
|
1783 setOpenError(false); |
|
1784 } |
|
1785 } |
|
1786 |
|
1787 void QODBCDriver::init() |
|
1788 { |
|
1789 d = new QODBCDriverPrivate(); |
|
1790 } |
|
1791 |
|
1792 QODBCDriver::~QODBCDriver() |
|
1793 { |
|
1794 cleanup(); |
|
1795 delete d; |
|
1796 } |
|
1797 |
|
1798 bool QODBCDriver::hasFeature(DriverFeature f) const |
|
1799 { |
|
1800 switch (f) { |
|
1801 case Transactions: { |
|
1802 if (!d->hDbc) |
|
1803 return false; |
|
1804 SQLUSMALLINT txn; |
|
1805 SQLSMALLINT t; |
|
1806 int r = SQLGetInfo(d->hDbc, |
|
1807 (SQLUSMALLINT)SQL_TXN_CAPABLE, |
|
1808 &txn, |
|
1809 sizeof(txn), |
|
1810 &t); |
|
1811 if (r != SQL_SUCCESS || txn == SQL_TC_NONE) |
|
1812 return false; |
|
1813 else |
|
1814 return true; |
|
1815 } |
|
1816 case Unicode: |
|
1817 return d->unicode; |
|
1818 case PreparedQueries: |
|
1819 case PositionalPlaceholders: |
|
1820 case FinishQuery: |
|
1821 case LowPrecisionNumbers: |
|
1822 return true; |
|
1823 case QuerySize: |
|
1824 case NamedPlaceholders: |
|
1825 case LastInsertId: |
|
1826 case BatchOperations: |
|
1827 case SimpleLocking: |
|
1828 case EventNotifications: |
|
1829 return false; |
|
1830 case MultipleResultSets: |
|
1831 return d->hasMultiResultSets; |
|
1832 case BLOB: { |
|
1833 if(d->isMySqlServer) |
|
1834 return true; |
|
1835 else |
|
1836 return false; |
|
1837 } |
|
1838 } |
|
1839 return false; |
|
1840 } |
|
1841 |
|
1842 bool QODBCDriver::open(const QString & db, |
|
1843 const QString & user, |
|
1844 const QString & password, |
|
1845 const QString &, |
|
1846 int, |
|
1847 const QString& connOpts) |
|
1848 { |
|
1849 if (isOpen()) |
|
1850 close(); |
|
1851 SQLRETURN r; |
|
1852 r = SQLAllocHandle(SQL_HANDLE_ENV, |
|
1853 SQL_NULL_HANDLE, |
|
1854 &d->hEnv); |
|
1855 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
1856 qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate environment"), d); |
|
1857 setOpenError(true); |
|
1858 return false; |
|
1859 } |
|
1860 r = SQLSetEnvAttr(d->hEnv, |
|
1861 SQL_ATTR_ODBC_VERSION, |
|
1862 (SQLPOINTER)qGetODBCVersion(connOpts), |
|
1863 SQL_IS_UINTEGER); |
|
1864 r = SQLAllocHandle(SQL_HANDLE_DBC, |
|
1865 d->hEnv, |
|
1866 &d->hDbc); |
|
1867 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
1868 qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate connection"), d); |
|
1869 setOpenError(true); |
|
1870 return false; |
|
1871 } |
|
1872 |
|
1873 if (!d->setConnectionOptions(connOpts)) |
|
1874 return false; |
|
1875 |
|
1876 // Create the connection string |
|
1877 QString connQStr; |
|
1878 // support the "DRIVER={SQL SERVER};SERVER=blah" syntax |
|
1879 if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive)) |
|
1880 connQStr = QLatin1String("FILEDSN=") + db; |
|
1881 else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive) |
|
1882 || db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive)) |
|
1883 connQStr = db; |
|
1884 else |
|
1885 connQStr = QLatin1String("DSN=") + db; |
|
1886 |
|
1887 if (!user.isEmpty()) |
|
1888 connQStr += QLatin1String(";UID=") + user; |
|
1889 if (!password.isEmpty()) |
|
1890 connQStr += QLatin1String(";PWD=") + password; |
|
1891 |
|
1892 SQLSMALLINT cb; |
|
1893 QVarLengthArray<SQLTCHAR> connOut(1024); |
|
1894 memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR)); |
|
1895 r = SQLDriverConnect(d->hDbc, |
|
1896 NULL, |
|
1897 #ifdef UNICODE |
|
1898 toSQLTCHAR(connQStr).data(), |
|
1899 #else |
|
1900 (SQLCHAR*)connQStr.toUtf8().data(), |
|
1901 #endif |
|
1902 (SQLSMALLINT)connQStr.length(), |
|
1903 connOut.data(), |
|
1904 1024, |
|
1905 &cb, |
|
1906 /*SQL_DRIVER_NOPROMPT*/0); |
|
1907 |
|
1908 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { |
|
1909 setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); |
|
1910 setOpenError(true); |
|
1911 return false; |
|
1912 } |
|
1913 |
|
1914 if (!d->checkDriver()) { |
|
1915 setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all " |
|
1916 "functionality required"), QSqlError::ConnectionError, d)); |
|
1917 setOpenError(true); |
|
1918 return false; |
|
1919 } |
|
1920 |
|
1921 d->checkUnicode(); |
|
1922 d->checkSchemaUsage(); |
|
1923 d->checkSqlServer(); |
|
1924 d->checkHasSQLFetchScroll(); |
|
1925 d->checkHasMultiResults(); |
|
1926 setOpen(true); |
|
1927 setOpenError(false); |
|
1928 if(d->isMSSqlServer) { |
|
1929 QSqlQuery i(createResult()); |
|
1930 i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON")); |
|
1931 } |
|
1932 return true; |
|
1933 } |
|
1934 |
|
1935 void QODBCDriver::close() |
|
1936 { |
|
1937 cleanup(); |
|
1938 setOpen(false); |
|
1939 setOpenError(false); |
|
1940 } |
|
1941 |
|
1942 void QODBCDriver::cleanup() |
|
1943 { |
|
1944 SQLRETURN r; |
|
1945 if (!d) |
|
1946 return; |
|
1947 |
|
1948 if(d->hDbc) { |
|
1949 // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect |
|
1950 if (isOpen()) { |
|
1951 r = SQLDisconnect(d->hDbc); |
|
1952 if (r != SQL_SUCCESS) |
|
1953 qSqlWarning(QLatin1String("QODBCDriver::disconnect: Unable to disconnect datasource"), d); |
|
1954 else |
|
1955 d->disconnectCount++; |
|
1956 } |
|
1957 |
|
1958 r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc); |
|
1959 if (r != SQL_SUCCESS) |
|
1960 qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free connection handle"), d); |
|
1961 d->hDbc = 0; |
|
1962 } |
|
1963 |
|
1964 if (d->hEnv) { |
|
1965 r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv); |
|
1966 if (r != SQL_SUCCESS) |
|
1967 qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free environment handle"), d); |
|
1968 d->hEnv = 0; |
|
1969 } |
|
1970 } |
|
1971 |
|
1972 // checks whether the server can return char, varchar and longvarchar |
|
1973 // as two byte unicode characters |
|
1974 void QODBCDriverPrivate::checkUnicode() |
|
1975 { |
|
1976 #if defined(Q_ODBC_VERSION_2) |
|
1977 unicode = false; |
|
1978 return; |
|
1979 #endif |
|
1980 |
|
1981 SQLRETURN r; |
|
1982 SQLUINTEGER fFunc; |
|
1983 |
|
1984 unicode = false; |
|
1985 r = SQLGetInfo(hDbc, |
|
1986 SQL_CONVERT_CHAR, |
|
1987 (SQLPOINTER)&fFunc, |
|
1988 sizeof(fFunc), |
|
1989 NULL); |
|
1990 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) { |
|
1991 unicode = true; |
|
1992 return; |
|
1993 } |
|
1994 |
|
1995 r = SQLGetInfo(hDbc, |
|
1996 SQL_CONVERT_VARCHAR, |
|
1997 (SQLPOINTER)&fFunc, |
|
1998 sizeof(fFunc), |
|
1999 NULL); |
|
2000 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) { |
|
2001 unicode = true; |
|
2002 return; |
|
2003 } |
|
2004 |
|
2005 r = SQLGetInfo(hDbc, |
|
2006 SQL_CONVERT_LONGVARCHAR, |
|
2007 (SQLPOINTER)&fFunc, |
|
2008 sizeof(fFunc), |
|
2009 NULL); |
|
2010 if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) { |
|
2011 unicode = true; |
|
2012 return; |
|
2013 } |
|
2014 SQLHANDLE hStmt; |
|
2015 r = SQLAllocHandle(SQL_HANDLE_STMT, |
|
2016 hDbc, |
|
2017 &hStmt); |
|
2018 |
|
2019 r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS); |
|
2020 if(r == SQL_SUCCESS) { |
|
2021 r = SQLFetch(hStmt); |
|
2022 if(r == SQL_SUCCESS) { |
|
2023 QVarLengthArray<SQLWCHAR> buffer(10); |
|
2024 r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL); |
|
2025 if(r == SQL_SUCCESS && fromSQLTCHAR(buffer) == QLatin1String("test")) { |
|
2026 unicode = true; |
|
2027 } |
|
2028 } |
|
2029 } |
|
2030 r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); |
|
2031 } |
|
2032 |
|
2033 bool QODBCDriverPrivate::checkDriver() const |
|
2034 { |
|
2035 #ifdef ODBC_CHECK_DRIVER |
|
2036 static const SQLUSMALLINT reqFunc[] = { |
|
2037 SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS, |
|
2038 SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT, |
|
2039 SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0 |
|
2040 }; |
|
2041 |
|
2042 // these functions are optional |
|
2043 static const SQLUSMALLINT optFunc[] = { |
|
2044 SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0 |
|
2045 }; |
|
2046 |
|
2047 SQLRETURN r; |
|
2048 SQLUSMALLINT sup; |
|
2049 |
|
2050 int i; |
|
2051 // check the required functions |
|
2052 for (i = 0; reqFunc[i] != 0; ++i) { |
|
2053 |
|
2054 r = SQLGetFunctions(hDbc, reqFunc[i], &sup); |
|
2055 |
|
2056 if (r != SQL_SUCCESS) { |
|
2057 qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this); |
|
2058 return false; |
|
2059 } |
|
2060 if (sup == SQL_FALSE) { |
|
2061 qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality (" << reqFunc[i] << |
|
2062 ").\nPlease look at the Qt SQL Module Driver documentation for more information."; |
|
2063 return false; |
|
2064 } |
|
2065 } |
|
2066 |
|
2067 // these functions are optional and just generate a warning |
|
2068 for (i = 0; optFunc[i] != 0; ++i) { |
|
2069 |
|
2070 r = SQLGetFunctions(hDbc, optFunc[i], &sup); |
|
2071 |
|
2072 if (r != SQL_SUCCESS) { |
|
2073 qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this); |
|
2074 return false; |
|
2075 } |
|
2076 if (sup == SQL_FALSE) { |
|
2077 qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (" << optFunc[i] << ')'; |
|
2078 return true; |
|
2079 } |
|
2080 } |
|
2081 #endif //ODBC_CHECK_DRIVER |
|
2082 |
|
2083 return true; |
|
2084 } |
|
2085 |
|
2086 void QODBCDriverPrivate::checkSchemaUsage() |
|
2087 { |
|
2088 SQLRETURN r; |
|
2089 SQLUINTEGER val; |
|
2090 |
|
2091 r = SQLGetInfo(hDbc, |
|
2092 SQL_SCHEMA_USAGE, |
|
2093 (SQLPOINTER) &val, |
|
2094 sizeof(val), |
|
2095 NULL); |
|
2096 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) |
|
2097 useSchema = (val != 0); |
|
2098 } |
|
2099 |
|
2100 void QODBCDriverPrivate::checkSqlServer() |
|
2101 { |
|
2102 SQLRETURN r; |
|
2103 QVarLengthArray<SQLTCHAR> serverString(200); |
|
2104 SQLSMALLINT t; |
|
2105 memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR)); |
|
2106 |
|
2107 r = SQLGetInfo(hDbc, |
|
2108 SQL_DBMS_NAME, |
|
2109 serverString.data(), |
|
2110 serverString.size() * sizeof(SQLTCHAR), |
|
2111 &t); |
|
2112 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { |
|
2113 QString serverType; |
|
2114 #ifdef UNICODE |
|
2115 serverType = fromSQLTCHAR(serverString, t/sizeof(SQLTCHAR)); |
|
2116 #else |
|
2117 serverType = QString::fromUtf8((const char *)serverString.constData(), t); |
|
2118 #endif |
|
2119 isMySqlServer = serverType.contains(QLatin1String("mysql"), Qt::CaseInsensitive); |
|
2120 isMSSqlServer = serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive); |
|
2121 } |
|
2122 r = SQLGetInfo(hDbc, |
|
2123 SQL_DRIVER_NAME, |
|
2124 serverString.data(), |
|
2125 serverString.size() * sizeof(SQLTCHAR), |
|
2126 &t); |
|
2127 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { |
|
2128 QString serverType; |
|
2129 #ifdef UNICODE |
|
2130 serverType = fromSQLTCHAR(serverString, t/sizeof(SQLTCHAR)); |
|
2131 #else |
|
2132 serverType = QString::fromUtf8((const char *)serverString.constData(), t); |
|
2133 #endif |
|
2134 isFreeTDSDriver = serverType.contains(QLatin1String("tdsodbc"), Qt::CaseInsensitive); |
|
2135 unicode = isFreeTDSDriver == false; |
|
2136 } |
|
2137 } |
|
2138 |
|
2139 void QODBCDriverPrivate::checkHasSQLFetchScroll() |
|
2140 { |
|
2141 SQLUSMALLINT sup; |
|
2142 SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup); |
|
2143 if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || sup != SQL_TRUE) { |
|
2144 hasSQLFetchScroll = false; |
|
2145 qWarning() << "QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries"; |
|
2146 } |
|
2147 } |
|
2148 |
|
2149 void QODBCDriverPrivate::checkHasMultiResults() |
|
2150 { |
|
2151 QVarLengthArray<SQLTCHAR> driverResponse(2); |
|
2152 SQLSMALLINT length; |
|
2153 SQLRETURN r = SQLGetInfo(hDbc, |
|
2154 SQL_MULT_RESULT_SETS, |
|
2155 driverResponse.data(), |
|
2156 driverResponse.size() * sizeof(SQLTCHAR), |
|
2157 &length); |
|
2158 if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) |
|
2159 #ifdef UNICODE |
|
2160 hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(QLatin1Char('Y')); |
|
2161 #else |
|
2162 hasMultiResultSets = QString::fromUtf8((const char *)driverResponse.constData(), length).startsWith(QLatin1Char('Y')); |
|
2163 #endif |
|
2164 } |
|
2165 |
|
2166 QSqlResult *QODBCDriver::createResult() const |
|
2167 { |
|
2168 return new QODBCResult(this, d); |
|
2169 } |
|
2170 |
|
2171 bool QODBCDriver::beginTransaction() |
|
2172 { |
|
2173 if (!isOpen()) { |
|
2174 qWarning() << "QODBCDriver::beginTransaction: Database not open"; |
|
2175 return false; |
|
2176 } |
|
2177 SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF); |
|
2178 SQLRETURN r = SQLSetConnectAttr(d->hDbc, |
|
2179 SQL_ATTR_AUTOCOMMIT, |
|
2180 (SQLPOINTER)ac, |
|
2181 sizeof(ac)); |
|
2182 if (r != SQL_SUCCESS) { |
|
2183 setLastError(qMakeError(tr("Unable to disable autocommit"), |
|
2184 QSqlError::TransactionError, d)); |
|
2185 return false; |
|
2186 } |
|
2187 return true; |
|
2188 } |
|
2189 |
|
2190 bool QODBCDriver::commitTransaction() |
|
2191 { |
|
2192 if (!isOpen()) { |
|
2193 qWarning() << "QODBCDriver::commitTransaction: Database not open"; |
|
2194 return false; |
|
2195 } |
|
2196 SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, |
|
2197 d->hDbc, |
|
2198 SQL_COMMIT); |
|
2199 if (r != SQL_SUCCESS) { |
|
2200 setLastError(qMakeError(tr("Unable to commit transaction"), |
|
2201 QSqlError::TransactionError, d)); |
|
2202 return false; |
|
2203 } |
|
2204 return endTrans(); |
|
2205 } |
|
2206 |
|
2207 bool QODBCDriver::rollbackTransaction() |
|
2208 { |
|
2209 if (!isOpen()) { |
|
2210 qWarning() << "QODBCDriver::rollbackTransaction: Database not open"; |
|
2211 return false; |
|
2212 } |
|
2213 SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC, |
|
2214 d->hDbc, |
|
2215 SQL_ROLLBACK); |
|
2216 if (r != SQL_SUCCESS) { |
|
2217 setLastError(qMakeError(tr("Unable to rollback transaction"), |
|
2218 QSqlError::TransactionError, d)); |
|
2219 return false; |
|
2220 } |
|
2221 return endTrans(); |
|
2222 } |
|
2223 |
|
2224 bool QODBCDriver::endTrans() |
|
2225 { |
|
2226 SQLUINTEGER ac(SQL_AUTOCOMMIT_ON); |
|
2227 SQLRETURN r = SQLSetConnectAttr(d->hDbc, |
|
2228 SQL_ATTR_AUTOCOMMIT, |
|
2229 (SQLPOINTER)ac, |
|
2230 sizeof(ac)); |
|
2231 if (r != SQL_SUCCESS) { |
|
2232 setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d)); |
|
2233 return false; |
|
2234 } |
|
2235 return true; |
|
2236 } |
|
2237 |
|
2238 QStringList QODBCDriver::tables(QSql::TableType type) const |
|
2239 { |
|
2240 QStringList tl; |
|
2241 if (!isOpen()) |
|
2242 return tl; |
|
2243 SQLHANDLE hStmt; |
|
2244 |
|
2245 SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, |
|
2246 d->hDbc, |
|
2247 &hStmt); |
|
2248 if (r != SQL_SUCCESS) { |
|
2249 qSqlWarning(QLatin1String("QODBCDriver::tables: Unable to allocate handle"), d); |
|
2250 return tl; |
|
2251 } |
|
2252 r = SQLSetStmtAttr(hStmt, |
|
2253 SQL_ATTR_CURSOR_TYPE, |
|
2254 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, |
|
2255 SQL_IS_UINTEGER); |
|
2256 QStringList tableType; |
|
2257 if (type & QSql::Tables) |
|
2258 tableType += QLatin1String("TABLE"); |
|
2259 if (type & QSql::Views) |
|
2260 tableType += QLatin1String("VIEW"); |
|
2261 if (type & QSql::SystemTables) |
|
2262 tableType += QLatin1String("SYSTEM TABLE"); |
|
2263 if (tableType.isEmpty()) |
|
2264 return tl; |
|
2265 |
|
2266 QString joinedTableTypeString = tableType.join(QLatin1String(",")); |
|
2267 |
|
2268 r = SQLTables(hStmt, |
|
2269 NULL, |
|
2270 0, |
|
2271 NULL, |
|
2272 0, |
|
2273 NULL, |
|
2274 0, |
|
2275 #ifdef UNICODE |
|
2276 toSQLTCHAR(joinedTableTypeString).data(), |
|
2277 #else |
|
2278 (SQLCHAR*)joinedTableTypeString.toUtf8().data(), |
|
2279 #endif |
|
2280 joinedTableTypeString.length() /* characters, not bytes */); |
|
2281 |
|
2282 if (r != SQL_SUCCESS) |
|
2283 qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d); |
|
2284 |
|
2285 if (d->hasSQLFetchScroll) |
|
2286 r = SQLFetchScroll(hStmt, |
|
2287 SQL_FETCH_NEXT, |
|
2288 0); |
|
2289 else |
|
2290 r = SQLFetch(hStmt); |
|
2291 |
|
2292 if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) { |
|
2293 qWarning() << "QODBCDriver::tables failed to retrieve table/view list: (" << r << "," << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ")"; |
|
2294 return QStringList(); |
|
2295 } |
|
2296 |
|
2297 while (r == SQL_SUCCESS) { |
|
2298 QString fieldVal = qGetStringData(hStmt, 2, -1, false); |
|
2299 tl.append(fieldVal); |
|
2300 |
|
2301 if (d->hasSQLFetchScroll) |
|
2302 r = SQLFetchScroll(hStmt, |
|
2303 SQL_FETCH_NEXT, |
|
2304 0); |
|
2305 else |
|
2306 r = SQLFetch(hStmt); |
|
2307 } |
|
2308 |
|
2309 r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); |
|
2310 if (r!= SQL_SUCCESS) |
|
2311 qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d); |
|
2312 return tl; |
|
2313 } |
|
2314 |
|
2315 QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const |
|
2316 { |
|
2317 QSqlIndex index(tablename); |
|
2318 if (!isOpen()) |
|
2319 return index; |
|
2320 bool usingSpecialColumns = false; |
|
2321 QSqlRecord rec = record(tablename); |
|
2322 |
|
2323 SQLHANDLE hStmt; |
|
2324 SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, |
|
2325 d->hDbc, |
|
2326 &hStmt); |
|
2327 if (r != SQL_SUCCESS) { |
|
2328 qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to list primary key"), d); |
|
2329 return index; |
|
2330 } |
|
2331 QString catalog, schema, table; |
|
2332 d->splitTableQualifier(tablename, catalog, schema, table); |
|
2333 |
|
2334 if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) |
|
2335 catalog = stripDelimiters(catalog, QSqlDriver::TableName); |
|
2336 else |
|
2337 catalog = d->adjustCase(catalog); |
|
2338 |
|
2339 if (isIdentifierEscaped(schema, QSqlDriver::TableName)) |
|
2340 schema = stripDelimiters(schema, QSqlDriver::TableName); |
|
2341 else |
|
2342 schema = d->adjustCase(schema); |
|
2343 |
|
2344 if (isIdentifierEscaped(table, QSqlDriver::TableName)) |
|
2345 table = stripDelimiters(table, QSqlDriver::TableName); |
|
2346 else |
|
2347 table = d->adjustCase(table); |
|
2348 |
|
2349 r = SQLSetStmtAttr(hStmt, |
|
2350 SQL_ATTR_CURSOR_TYPE, |
|
2351 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, |
|
2352 SQL_IS_UINTEGER); |
|
2353 r = SQLPrimaryKeys(hStmt, |
|
2354 #ifdef UNICODE |
|
2355 catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), |
|
2356 #else |
|
2357 catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toUtf8().data(), |
|
2358 #endif |
|
2359 catalog.length(), |
|
2360 #ifdef UNICODE |
|
2361 schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), |
|
2362 #else |
|
2363 schema.length() == 0 ? NULL : (SQLCHAR*)schema.toUtf8().data(), |
|
2364 #endif |
|
2365 schema.length(), |
|
2366 #ifdef UNICODE |
|
2367 toSQLTCHAR(table).data(), |
|
2368 #else |
|
2369 (SQLCHAR*)table.toUtf8().data(), |
|
2370 #endif |
|
2371 table.length() /* in characters, not in bytes */); |
|
2372 |
|
2373 // if the SQLPrimaryKeys() call does not succeed (e.g the driver |
|
2374 // does not support it) - try an alternative method to get hold of |
|
2375 // the primary index (e.g MS Access and FoxPro) |
|
2376 if (r != SQL_SUCCESS) { |
|
2377 r = SQLSpecialColumns(hStmt, |
|
2378 SQL_BEST_ROWID, |
|
2379 #ifdef UNICODE |
|
2380 catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), |
|
2381 #else |
|
2382 catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toUtf8().data(), |
|
2383 #endif |
|
2384 catalog.length(), |
|
2385 #ifdef UNICODE |
|
2386 schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), |
|
2387 #else |
|
2388 schema.length() == 0 ? NULL : (SQLCHAR*)schema.toUtf8().data(), |
|
2389 #endif |
|
2390 schema.length(), |
|
2391 #ifdef UNICODE |
|
2392 toSQLTCHAR(table).data(), |
|
2393 #else |
|
2394 (SQLCHAR*)table.toUtf8().data(), |
|
2395 #endif |
|
2396 table.length(), |
|
2397 SQL_SCOPE_CURROW, |
|
2398 SQL_NULLABLE); |
|
2399 |
|
2400 if (r != SQL_SUCCESS) { |
|
2401 qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d); |
|
2402 } else { |
|
2403 usingSpecialColumns = true; |
|
2404 } |
|
2405 } |
|
2406 |
|
2407 if (d->hasSQLFetchScroll) |
|
2408 r = SQLFetchScroll(hStmt, |
|
2409 SQL_FETCH_NEXT, |
|
2410 0); |
|
2411 else |
|
2412 r = SQLFetch(hStmt); |
|
2413 |
|
2414 int fakeId = 0; |
|
2415 QString cName, idxName; |
|
2416 // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop |
|
2417 while (r == SQL_SUCCESS) { |
|
2418 if (usingSpecialColumns) { |
|
2419 cName = qGetStringData(hStmt, 1, -1, d->unicode); // column name |
|
2420 idxName = QString::number(fakeId++); // invent a fake index name |
|
2421 } else { |
|
2422 cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name |
|
2423 idxName = qGetStringData(hStmt, 5, -1, d->unicode); // pk index name |
|
2424 } |
|
2425 index.append(rec.field(cName)); |
|
2426 index.setName(idxName); |
|
2427 |
|
2428 if (d->hasSQLFetchScroll) |
|
2429 r = SQLFetchScroll(hStmt, |
|
2430 SQL_FETCH_NEXT, |
|
2431 0); |
|
2432 else |
|
2433 r = SQLFetch(hStmt); |
|
2434 |
|
2435 } |
|
2436 r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); |
|
2437 if (r!= SQL_SUCCESS) |
|
2438 qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d); |
|
2439 return index; |
|
2440 } |
|
2441 |
|
2442 QSqlRecord QODBCDriver::record(const QString& tablename) const |
|
2443 { |
|
2444 QSqlRecord fil; |
|
2445 if (!isOpen()) |
|
2446 return fil; |
|
2447 |
|
2448 SQLHANDLE hStmt; |
|
2449 QString catalog, schema, table; |
|
2450 d->splitTableQualifier(tablename, catalog, schema, table); |
|
2451 |
|
2452 if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) |
|
2453 catalog = stripDelimiters(catalog, QSqlDriver::TableName); |
|
2454 else |
|
2455 catalog = d->adjustCase(catalog); |
|
2456 |
|
2457 if (isIdentifierEscaped(schema, QSqlDriver::TableName)) |
|
2458 schema = stripDelimiters(schema, QSqlDriver::TableName); |
|
2459 else |
|
2460 schema = d->adjustCase(schema); |
|
2461 |
|
2462 if (isIdentifierEscaped(table, QSqlDriver::TableName)) |
|
2463 table = stripDelimiters(table, QSqlDriver::TableName); |
|
2464 else |
|
2465 table = d->adjustCase(table); |
|
2466 |
|
2467 SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, |
|
2468 d->hDbc, |
|
2469 &hStmt); |
|
2470 if (r != SQL_SUCCESS) { |
|
2471 qSqlWarning(QLatin1String("QODBCDriver::record: Unable to allocate handle"), d); |
|
2472 return fil; |
|
2473 } |
|
2474 r = SQLSetStmtAttr(hStmt, |
|
2475 SQL_ATTR_CURSOR_TYPE, |
|
2476 (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, |
|
2477 SQL_IS_UINTEGER); |
|
2478 r = SQLColumns(hStmt, |
|
2479 #ifdef UNICODE |
|
2480 catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), |
|
2481 #else |
|
2482 catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toUtf8().data(), |
|
2483 #endif |
|
2484 catalog.length(), |
|
2485 #ifdef UNICODE |
|
2486 schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), |
|
2487 #else |
|
2488 schema.length() == 0 ? NULL : (SQLCHAR*)schema.toUtf8().data(), |
|
2489 #endif |
|
2490 schema.length(), |
|
2491 #ifdef UNICODE |
|
2492 toSQLTCHAR(table).data(), |
|
2493 #else |
|
2494 (SQLCHAR*)table.toUtf8().data(), |
|
2495 #endif |
|
2496 table.length(), |
|
2497 NULL, |
|
2498 0); |
|
2499 if (r != SQL_SUCCESS) |
|
2500 qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d); |
|
2501 |
|
2502 if (d->hasSQLFetchScroll) |
|
2503 r = SQLFetchScroll(hStmt, |
|
2504 SQL_FETCH_NEXT, |
|
2505 0); |
|
2506 else |
|
2507 r = SQLFetch(hStmt); |
|
2508 |
|
2509 // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop |
|
2510 while (r == SQL_SUCCESS) { |
|
2511 |
|
2512 fil.append(qMakeFieldInfo(hStmt, d)); |
|
2513 |
|
2514 if (d->hasSQLFetchScroll) |
|
2515 r = SQLFetchScroll(hStmt, |
|
2516 SQL_FETCH_NEXT, |
|
2517 0); |
|
2518 else |
|
2519 r = SQLFetch(hStmt); |
|
2520 } |
|
2521 |
|
2522 r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); |
|
2523 if (r!= SQL_SUCCESS) |
|
2524 qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + QString::number(r), d); |
|
2525 |
|
2526 return fil; |
|
2527 } |
|
2528 |
|
2529 QString QODBCDriver::formatValue(const QSqlField &field, |
|
2530 bool trimStrings) const |
|
2531 { |
|
2532 QString r; |
|
2533 if (field.isNull()) { |
|
2534 r = QLatin1String("NULL"); |
|
2535 } else if (field.type() == QVariant::DateTime) { |
|
2536 // Use an escape sequence for the datetime fields |
|
2537 if (field.value().toDateTime().isValid()){ |
|
2538 QDate dt = field.value().toDateTime().date(); |
|
2539 QTime tm = field.value().toDateTime().time(); |
|
2540 // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10 |
|
2541 r = QLatin1String("{ ts '") + |
|
2542 QString::number(dt.year()) + QLatin1Char('-') + |
|
2543 QString::number(dt.month()).rightJustified(2, QLatin1Char('0'), true) + |
|
2544 QLatin1Char('-') + |
|
2545 QString::number(dt.day()).rightJustified(2, QLatin1Char('0'), true) + |
|
2546 QLatin1Char(' ') + |
|
2547 tm.toString() + |
|
2548 QLatin1String("' }"); |
|
2549 } else |
|
2550 r = QLatin1String("NULL"); |
|
2551 } else if (field.type() == QVariant::ByteArray) { |
|
2552 QByteArray ba = field.value().toByteArray(); |
|
2553 QString res; |
|
2554 static const char hexchars[] = "0123456789abcdef"; |
|
2555 for (int i = 0; i < ba.size(); ++i) { |
|
2556 uchar s = (uchar) ba[i]; |
|
2557 res += QLatin1Char(hexchars[s >> 4]); |
|
2558 res += QLatin1Char(hexchars[s & 0x0f]); |
|
2559 } |
|
2560 r = QLatin1String("0x") + res; |
|
2561 } else { |
|
2562 r = QSqlDriver::formatValue(field, trimStrings); |
|
2563 } |
|
2564 return r; |
|
2565 } |
|
2566 |
|
2567 QVariant QODBCDriver::handle() const |
|
2568 { |
|
2569 return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc); |
|
2570 } |
|
2571 |
|
2572 QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const |
|
2573 { |
|
2574 QChar quote = d->quoteChar(); |
|
2575 QString res = identifier; |
|
2576 if(!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) { |
|
2577 res.replace(quote, QString(quote)+QString(quote)); |
|
2578 res.prepend(quote).append(quote); |
|
2579 res.replace(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote)); |
|
2580 } |
|
2581 return res; |
|
2582 } |
|
2583 |
|
2584 bool QODBCDriver::isIdentifierEscapedImplementation(const QString &identifier, IdentifierType) const |
|
2585 { |
|
2586 QChar quote = d->quoteChar(); |
|
2587 return identifier.size() > 2 |
|
2588 && identifier.startsWith(quote) //left delimited |
|
2589 && identifier.endsWith(quote); //right delimited |
|
2590 } |
|
2591 |
|
2592 QT_END_NAMESPACE |