1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtSql module of the Qt Toolkit.
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
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.
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.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
43 #ifdef Q_OS_WIN32 // We assume that MS SQL Server is used. Set Q_USE_SYBASE to force Sybase.
44 // Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h
54 #include <qdatetime.h>
57 #include <qsqlerror.h>
58 #include <qsqlfield.h>
59 #include <qsqlindex.h>
60 #include <qsqlquery.h>
61 #include <qstringlist.h>
69 #define QMSGHANDLE DBMSGHANDLE_PROC
70 #define QERRHANDLE DBERRHANDLE_PROC
71 #define QTDSCHAR SQLCHAR
72 #define QTDSDATETIME4 SQLDATETIM4
73 #define QTDSDATETIME SQLDATETIME
74 #define QTDSDATETIME_N SQLDATETIMN
75 #define QTDSDECIMAL SQLDECIMAL
76 #define QTDSFLT4 SQLFLT4
77 #define QTDSFLT8 SQLFLT8
78 #define QTDSFLT8_N SQLFLTN
79 #define QTDSINT1 SQLINT1
80 #define QTDSINT2 SQLINT2
81 #define QTDSINT4 SQLINT4
82 #define QTDSINT4_N SQLINTN
83 #define QTDSMONEY4 SQLMONEY4
84 #define QTDSMONEY SQLMONEY
85 #define QTDSMONEY_N SQLMONEYN
86 #define QTDSNUMERIC SQLNUMERIC
87 #define QTDSTEXT SQLTEXT
88 #define QTDSVARCHAR SQLVARCHAR
89 #define QTDSBIT SQLBIT
90 #define QTDSBINARY SQLBINARY
91 #define QTDSVARBINARY SQLVARBINARY
92 #define QTDSIMAGE SQLIMAGE
94 #define QMSGHANDLE MHANDLEFUNC
95 #define QERRHANDLE EHANDLEFUNC
96 #define QTDSCHAR SYBCHAR
97 #define QTDSDATETIME4 SYBDATETIME4
98 #define QTDSDATETIME SYBDATETIME
99 #define QTDSDATETIME_N SYBDATETIMN
100 #define QTDSDECIMAL SYBDECIMAL
101 #define QTDSFLT8 SYBFLT8
102 #define QTDSFLT8_N SYBFLTN
103 #define QTDSFLT4 SYBREAL
104 #define QTDSINT1 SYBINT1
105 #define QTDSINT2 SYBINT2
106 #define QTDSINT4 SYBINT4
107 #define QTDSINT4_N SYBINTN
108 #define QTDSMONEY4 SYBMONEY4
109 #define QTDSMONEY SYBMONEY
110 #define QTDSMONEY_N SYBMONEYN
111 #define QTDSNUMERIC SYBNUMERIC
112 #define QTDSTEXT SYBTEXT
113 #define QTDSVARCHAR SYBVARCHAR
114 #define QTDSBIT SYBBIT
115 #define QTDSBINARY SYBBINARY
116 #define QTDSVARBINARY SYBVARBINARY
117 #define QTDSIMAGE SYBIMAGE
118 // magic numbers not defined anywhere in Sybase headers
119 #define QTDSDECIMAL_2 55
120 #define QTDSNUMERIC_2 63
123 #define TDS_CURSOR_SIZE 50
125 // workaround for FreeTDS
130 QSqlError
qMakeError(const QString
& err
, QSqlError::ErrorType type
, int errNo
= -1)
132 return QSqlError(QLatin1String("QTDS: ") + err
, QString(), type
, errNo
);
135 class QTDSDriverPrivate
138 QTDSDriverPrivate(): login(0) {}
139 LOGINREC
* login
; // login information
145 class QTDSResultPrivate
148 QTDSResultPrivate():login(0), dbproc(0) {}
149 LOGINREC
* login
; // login information
150 DBPROCESS
* dbproc
; // connection from app to server
152 void addErrorMsg(QString
& errMsg
) { errorMsgs
.append(errMsg
); }
153 QString
getErrorMsgs() { return errorMsgs
.join(QLatin1String("\n")); }
154 void clearErrorMsgs() { errorMsgs
.clear(); }
155 QVector
<void *> buffer
;
159 QStringList errorMsgs
;
162 typedef QHash
<DBPROCESS
*, QTDSResultPrivate
*> QTDSErrorHash
;
163 Q_GLOBAL_STATIC(QTDSErrorHash
, errs
)
166 static int CS_PUBLIC
qTdsMsgHandler (DBPROCESS
* dbproc
,
175 QTDSResultPrivate
* p
= errs()->value(dbproc
);
178 // ### umm... temporary disabled since this throws a lot of warnings...
179 // qWarning("QTDSDriver warning (%d): [%s] from server [%s]", msgstate, msgtext, srvname);
184 QString errMsg
= QString::fromLatin1("%1 (Msg %2, Level %3, State %4, Server %5, Line %6)")
185 .arg(QString::fromAscii(msgtext
))
189 .arg(QString::fromAscii(srvname
))
191 p
->addErrorMsg(errMsg
);
193 // Severe messages are really errors in the sense of lastError
194 errMsg
= p
->getErrorMsgs();
195 p
->lastError
= qMakeError(errMsg
, QSqlError::UnknownError
, msgno
);
203 static int CS_PUBLIC
qTdsErrHandler(DBPROCESS
* dbproc
,
210 QTDSResultPrivate
* p
= errs()->value(dbproc
);
212 qWarning("QTDSDriver error (%d): [%s] [%s]", dberr
, dberrstr
, oserrstr
);
216 * If the process is dead or NULL and
217 * we are not in the middle of logging in...
219 if((dbproc
== NULL
|| DBDEAD(dbproc
))) {
220 qWarning("QTDSDriver error (%d): [%s] [%s]", dberr
, dberrstr
, oserrstr
);
225 QString errMsg
= QString::fromLatin1("%1 %2\n").arg(QLatin1String(dberrstr
)).arg(
226 QLatin1String(oserrstr
));
227 errMsg
+= p
->getErrorMsgs();
228 p
->lastError
= qMakeError(errMsg
, QSqlError::UnknownError
, dberr
);
237 QVariant::Type
qDecodeTDSType(int type
)
239 QVariant::Type t
= QVariant::Invalid
;
244 t
= QVariant::String
;
267 t
= QVariant::Double
;
272 t
= QVariant::DateTime
;
277 t
= QVariant::ByteArray
;
280 t
= QVariant::Invalid
;
286 QVariant::Type
qFieldType(QTDSResultPrivate
* d
, int i
)
288 QVariant::Type type
= qDecodeTDSType(dbcoltype(d
->dbproc
, i
+1));
293 QTDSResult::QTDSResult(const QTDSDriver
* db
)
294 : QSqlCachedResult(db
)
296 d
= new QTDSResultPrivate();
297 d
->login
= db
->d
->login
;
299 d
->dbproc
= dbopen(d
->login
, const_cast<char*>(db
->d
->hostName
.toLatin1().constData()));
302 if (dbuse(d
->dbproc
, const_cast<char*>(db
->d
->db
.toLatin1().constData())) == FAIL
)
305 // insert d in error handler dict
306 errs()->insert(d
->dbproc
, d
);
307 dbcmd(d
->dbproc
, "set quoted_identifier on");
308 dbsqlexec(d
->dbproc
);
311 QTDSResult::~QTDSResult()
316 errs()->remove(d
->dbproc
);
320 void QTDSResult::cleanup()
324 for (int i
= 0; i
< d
->buffer
.size() / 2; ++i
)
325 free(d
->buffer
.at(i
* 2));
327 // "can" stands for "cancel"... very clever.
328 dbcanquery(d
->dbproc
);
329 dbfreebuf(d
->dbproc
);
331 QSqlCachedResult::cleanup();
334 QVariant
QTDSResult::handle() const
336 return QVariant(qRegisterMetaType
<DBPROCESS
*>("DBPROCESS*"), &d
->dbproc
);
339 static inline bool qIsNull(const void *ind
)
341 return *reinterpret_cast<const DBINT
*>(&ind
) == -1;
344 bool QTDSResult::gotoNext(QSqlCachedResult::ValueCache
&values
, int index
)
346 STATUS stat
= dbnextrow(d
->dbproc
);
347 if (stat
== NO_MORE_ROWS
) {
348 setAt(QSql::AfterLastRow
);
351 if ((stat
== FAIL
) || (stat
== BUF_FULL
)) {
352 setLastError(d
->lastError
);
359 for (int i
= 0; i
< d
->rec
.count(); ++i
) {
361 switch (d
->rec
.field(i
).type()) {
362 case QVariant::DateTime
:
363 if (qIsNull(d
->buffer
.at(i
* 2 + 1))) {
364 values
[idx
] = QVariant(QVariant::DateTime
);
366 DBDATETIME
*bdt
= (DBDATETIME
*) d
->buffer
.at(i
* 2);
367 QDate date
= QDate::fromString(QLatin1String("1900-01-01"), Qt::ISODate
);
368 QTime time
= QTime::fromString(QLatin1String("00:00:00"), Qt::ISODate
);
369 values
[idx
] = QDateTime(date
.addDays(bdt
->dtdays
), time
.addMSecs(int(bdt
->dttime
/ 0.3)));
373 if (qIsNull(d
->buffer
.at(i
* 2 + 1)))
374 values
[idx
] = QVariant(QVariant::Int
);
376 values
[idx
] = *((int*)d
->buffer
.at(i
* 2));
378 case QVariant::Double
:
379 case QVariant::String
:
380 if (qIsNull(d
->buffer
.at(i
* 2 + 1)))
381 values
[idx
] = QVariant(QVariant::String
);
383 values
[idx
] = QString::fromLocal8Bit((const char*)d
->buffer
.at(i
* 2)).trimmed();
385 case QVariant::ByteArray
: {
386 if (qIsNull(d
->buffer
.at(i
* 2 + 1)))
387 values
[idx
] = QVariant(QVariant::ByteArray
);
389 values
[idx
] = QByteArray((const char*)d
->buffer
.at(i
* 2));
393 // should never happen, and we already fired
394 // a warning while binding.
395 values
[idx
] = QVariant();
403 bool QTDSResult::reset (const QString
& query
)
406 if (!driver() || !driver()-> isOpen() || driver()->isOpenError())
409 setAt(QSql::BeforeFirstRow
);
410 if (dbcmd(d
->dbproc
, const_cast<char*>(query
.toLocal8Bit().constData())) == FAIL
) {
411 setLastError(d
->lastError
);
415 if (dbsqlexec(d
->dbproc
) == FAIL
) {
416 setLastError(d
->lastError
);
417 dbfreebuf(d
->dbproc
);
420 if (dbresults(d
->dbproc
) != SUCCEED
) {
421 setLastError(d
->lastError
);
422 dbfreebuf(d
->dbproc
);
426 setSelect((DBCMDROW(d
->dbproc
) == SUCCEED
)); // decide whether or not we are dealing with a SELECT query
427 int numCols
= dbnumcols(d
->dbproc
);
429 d
->buffer
.resize(numCols
* 2);
432 for (int i
= 0; i
< numCols
; ++i
) {
433 int dbType
= dbcoltype(d
->dbproc
, i
+1);
434 QVariant::Type vType
= qDecodeTDSType(dbType
);
435 QSqlField
f(QString::fromAscii(dbcolname(d
->dbproc
, i
+1)), vType
);
436 f
.setSqlType(dbType
);
437 f
.setLength(dbcollen(d
->dbproc
, i
+1));
445 ret
= dbbind(d
->dbproc
, i
+1, INTBIND
, (DBINT
) 4, (unsigned char *)p
);
447 case QVariant::Double
:
448 // use string binding to prevent loss of precision
450 ret
= dbbind(d
->dbproc
, i
+1, STRINGBIND
, 50, (unsigned char *)p
);
452 case QVariant::String
:
453 p
= malloc(dbcollen(d
->dbproc
, i
+1) + 1);
454 ret
= dbbind(d
->dbproc
, i
+1, STRINGBIND
, DBINT(dbcollen(d
->dbproc
, i
+1) + 1), (unsigned char *)p
);
456 case QVariant::DateTime
:
458 ret
= dbbind(d
->dbproc
, i
+1, DATETIMEBIND
, (DBINT
) 8, (unsigned char *)p
);
460 case QVariant::ByteArray
:
461 p
= malloc(dbcollen(d
->dbproc
, i
+1) + 1);
462 ret
= dbbind(d
->dbproc
, i
+1, BINARYBIND
, DBINT(dbcollen(d
->dbproc
, i
+1) + 1), (unsigned char *)p
);
464 default: //don't bind the field since we do not support it
465 qWarning("QTDSResult::reset: Unsupported type for field \"%s\"", dbcolname(d
->dbproc
, i
+1));
468 if (ret
== SUCCEED
) {
469 d
->buffer
[i
* 2] = p
;
470 ret
= dbnullbind(d
->dbproc
, i
+1, (DBINT
*)(&d
->buffer
[i
* 2 + 1]));
472 d
->buffer
[i
* 2] = 0;
473 d
->buffer
[i
* 2 + 1] = 0;
476 if ((ret
!= SUCCEED
) && (ret
!= -1)) {
477 setLastError(d
->lastError
);
486 int QTDSResult::size()
491 int QTDSResult::numRowsAffected()
494 if (dbiscount(d
->dbproc
)) {
495 return DBCOUNT(d
->dbproc
);
499 return DBCOUNT(d
->dbproc
);
503 QSqlRecord
QTDSResult::record() const
508 ///////////////////////////////////////////////////////////////////
510 QTDSDriver::QTDSDriver(QObject
* parent
)
516 QTDSDriver::QTDSDriver(LOGINREC
* rec
, const QString
& host
, const QString
&db
, QObject
* parent
)
529 QVariant
QTDSDriver::handle() const
531 return QVariant(qRegisterMetaType
<LOGINREC
*>("LOGINREC*"), &d
->login
);
534 void QTDSDriver::init()
536 d
= new QTDSDriverPrivate();
537 // the following two code-lines will fail compilation on some FreeTDS versions
538 // just comment them out if you have FreeTDS (you won't get any errors and warnings then)
539 dberrhandle((QERRHANDLE
)qTdsErrHandler
);
540 dbmsghandle((QMSGHANDLE
)qTdsMsgHandler
);
543 QTDSDriver::~QTDSDriver()
547 // dbexit also calls dbclose if necessary
552 bool QTDSDriver::hasFeature(DriverFeature f
) const
559 case EventNotifications
:
560 case MultipleResultSets
:
569 bool QTDSDriver::open(const QString
& db
,
570 const QString
& user
,
571 const QString
& password
,
572 const QString
& host
,
574 const QString
& /*connOpts*/)
582 d
->login
= dblogin();
587 DBSETLPWD(d
->login
, const_cast<char*>(password
.toLocal8Bit().constData()));
588 DBSETLUSER(d
->login
, const_cast<char*>(user
.toLocal8Bit().constData()));
590 // Now, try to open and use the database. If this fails, return false.
593 dbproc
= dbopen(d
->login
, const_cast<char*>(host
.toLatin1().constData()));
595 setLastError(qMakeError(tr("Unable to open connection"), QSqlError::ConnectionError
, -1));
599 if (dbuse(dbproc
, const_cast<char*>(db
.toLatin1().constData())) == FAIL
) {
600 setLastError(qMakeError(tr("Unable to use database"), QSqlError::ConnectionError
, -1));
613 void QTDSDriver::close()
617 dbloginfree(d
->login
);
619 dbfreelogin(d
->login
);
627 QSqlResult
*QTDSDriver::createResult() const
629 return new QTDSResult(this);
632 bool QTDSDriver::beginTransaction()
637 qWarning("QTDSDriver::beginTransaction: Database not open");
640 if (dbcmd(d->dbproc, "BEGIN TRANSACTION") == FAIL) {
641 setLastError(d->lastError);
642 dbfreebuf(d->dbproc);
645 if (dbsqlexec(d->dbproc) == FAIL) {
646 setLastError(d->lastError);
647 dbfreebuf(d->dbproc);
650 while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
651 dbfreebuf(d->dbproc);
652 inTransaction = true;
657 bool QTDSDriver::commitTransaction()
662 qWarning("QTDSDriver::commitTransaction: Database not open");
665 if (dbcmd(d->dbproc, "COMMIT TRANSACTION") == FAIL) {
666 setLastError(d->lastError);
667 dbfreebuf(d->dbproc);
670 if (dbsqlexec(d->dbproc) == FAIL) {
671 setLastError(d->lastError);
672 dbfreebuf(d->dbproc);
675 while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
676 dbfreebuf(d->dbproc);
677 inTransaction = false;
682 bool QTDSDriver::rollbackTransaction()
687 qWarning("QTDSDriver::rollbackTransaction: Database not open");
690 if (dbcmd(d->dbproc, "ROLLBACK TRANSACTION") == FAIL) {
691 setLastError(d->lastError);
692 dbfreebuf(d->dbproc);
695 if (dbsqlexec(d->dbproc) == FAIL) {
696 setLastError(d->lastError);
697 dbfreebuf(d->dbproc);
700 while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
701 dbfreebuf(d->dbproc);
702 inTransaction = false;
707 QSqlRecord
QTDSDriver::record(const QString
& tablename
) const
712 QSqlQuery
t(createResult());
713 t
.setForwardOnly(true);
715 QString table
= tablename
;
716 if (isIdentifierEscaped(table
, QSqlDriver::TableName
))
717 table
= stripDelimiters(table
, QSqlDriver::TableName
);
719 QString
stmt (QLatin1String("select name, type, length, prec from syscolumns "
720 "where id = (select id from sysobjects where name = '%1')"));
721 t
.exec(stmt
.arg(table
));
723 QSqlField
f(t
.value(0).toString().simplified(), qDecodeTDSType(t
.value(1).toInt()));
724 f
.setLength(t
.value(2).toInt());
725 f
.setPrecision(t
.value(3).toInt());
726 f
.setSqlType(t
.value(1).toInt());
732 QStringList
QTDSDriver::tables(QSql::TableType type
) const
739 QStringList typeFilter
;
741 if (type
& QSql::Tables
)
742 typeFilter
+= QLatin1String("type='U'");
743 if (type
& QSql::SystemTables
)
744 typeFilter
+= QLatin1String("type='S'");
745 if (type
& QSql::Views
)
746 typeFilter
+= QLatin1String("type='V'");
748 if (typeFilter
.isEmpty())
751 QSqlQuery
t(createResult());
752 t
.setForwardOnly(true);
753 t
.exec(QLatin1String("select name from sysobjects where ") + typeFilter
.join(QLatin1String(" or ")));
755 list
.append(t
.value(0).toString().simplified());
760 QString
QTDSDriver::formatValue(const QSqlField
&field
,
765 r
= QLatin1String("NULL");
766 else if (field
.type() == QVariant::DateTime
) {
767 if (field
.value().toDateTime().isValid()){
768 r
= field
.value().toDateTime().toString(QLatin1String("yyyyMMdd hh:mm:ss"));
769 r
.prepend(QLatin1String("'"));
770 r
.append(QLatin1String("'"));
772 r
= QLatin1String("NULL");
773 } else if (field
.type() == QVariant::ByteArray
) {
774 QByteArray ba
= field
.value().toByteArray();
776 static const char hexchars
[] = "0123456789abcdef";
777 for (int i
= 0; i
< ba
.size(); ++i
) {
778 uchar s
= (uchar
) ba
[i
];
779 res
+= QLatin1Char(hexchars
[s
>> 4]);
780 res
+= QLatin1Char(hexchars
[s
& 0x0f]);
782 r
= QLatin1String("0x") + res
;
784 r
= QSqlDriver::formatValue(field
, trim
);
789 QSqlIndex
QTDSDriver::primaryIndex(const QString
& tablename
) const
791 QSqlRecord rec
= record(tablename
);
793 QString table
= tablename
;
794 if (isIdentifierEscaped(table
, QSqlDriver::TableName
))
795 table
= stripDelimiters(table
, QSqlDriver::TableName
);
797 QSqlIndex
idx(table
);
798 if ((!isOpen()) || (table
.isEmpty()))
801 QSqlQuery
t(createResult());
802 t
.setForwardOnly(true);
803 t
.exec(QString::fromLatin1("sp_helpindex '%1'").arg(table
));
805 QStringList fNames
= t
.value(2).toString().simplified().split(QLatin1Char(','));
806 QRegExp
regx(QLatin1String("\\s*(\\S+)(?:\\s+(DESC|desc))?\\s*"));
807 for(QStringList::Iterator it
= fNames
.begin(); it
!= fNames
.end(); ++it
) {
809 QSqlField
f(regx
.cap(1), rec
.field(regx
.cap(1)).type());
810 if (regx
.cap(2).toLower() == QLatin1String("desc")) {
813 idx
.append(f
, false);
816 idx
.setName(t
.value(0).toString().simplified());
821 QString
QTDSDriver::escapeIdentifier(const QString
&identifier
, IdentifierType type
) const
823 QString res
= identifier
;
824 if(!identifier
.isEmpty() && !identifier
.startsWith(QLatin1Char('"')) && !identifier
.endsWith(QLatin1Char('"')) ) {
825 res
.replace(QLatin1Char('"'), QLatin1String("\"\""));
826 res
.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
827 res
.replace(QLatin1Char('.'), QLatin1String("\".\""));