util/src/sql/drivers/mysql/qsql_mysql.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     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_mysql.h"
       
    43 
       
    44 #include <qcoreapplication.h>
       
    45 #include <qvariant.h>
       
    46 #include <qdatetime.h>
       
    47 #include <qsqlerror.h>
       
    48 #include <qsqlfield.h>
       
    49 #include <qsqlindex.h>
       
    50 #include <qsqlquery.h>
       
    51 #include <qsqlrecord.h>
       
    52 #include <qstringlist.h>
       
    53 #include <qtextcodec.h>
       
    54 #include <qvector.h>
       
    55 
       
    56 #include <qdebug.h>
       
    57 
       
    58 #ifdef Q_OS_WIN32
       
    59 // comment the next line out if you want to use MySQL/embedded on Win32 systems.
       
    60 // note that it will crash if you don't statically link to the mysql/e library!
       
    61 # define Q_NO_MYSQL_EMBEDDED
       
    62 #endif
       
    63 
       
    64 Q_DECLARE_METATYPE(MYSQL_RES*)
       
    65 Q_DECLARE_METATYPE(MYSQL*)
       
    66 
       
    67 #if MYSQL_VERSION_ID >= 40108
       
    68 Q_DECLARE_METATYPE(MYSQL_STMT*)
       
    69 #endif
       
    70 
       
    71 #if MYSQL_VERSION_ID >= 40100
       
    72 #  define Q_CLIENT_MULTI_STATEMENTS CLIENT_MULTI_STATEMENTS
       
    73 #else
       
    74 #  define Q_CLIENT_MULTI_STATEMENTS 0
       
    75 #endif
       
    76 
       
    77 QT_BEGIN_NAMESPACE
       
    78 
       
    79 class QMYSQLDriverPrivate
       
    80 {
       
    81 public:
       
    82     QMYSQLDriverPrivate() : mysql(0),
       
    83 #ifndef QT_NO_TEXTCODEC
       
    84         tc(QTextCodec::codecForLocale()),
       
    85 #else
       
    86         tc(0),
       
    87 #endif
       
    88         preparedQuerysEnabled(false) {}
       
    89     MYSQL *mysql;
       
    90     QTextCodec *tc;
       
    91 
       
    92     bool preparedQuerysEnabled;
       
    93 };
       
    94 
       
    95 static inline QString toUnicode(QTextCodec *tc, const char *str)
       
    96 {
       
    97 #ifdef QT_NO_TEXTCODEC
       
    98     Q_UNUSED(tc);
       
    99     return QString::fromLatin1(str);
       
   100 #else
       
   101     return tc->toUnicode(str);
       
   102 #endif
       
   103 }
       
   104 
       
   105 static inline QString toUnicode(QTextCodec *tc, const char *str, int length)
       
   106 {
       
   107 #ifdef QT_NO_TEXTCODEC
       
   108     Q_UNUSED(tc);
       
   109     return QString::fromLatin1(str, length);
       
   110 #else
       
   111     return tc->toUnicode(str, length);
       
   112 #endif
       
   113 }
       
   114 
       
   115 static inline QByteArray fromUnicode(QTextCodec *tc, const QString &str)
       
   116 {
       
   117 #ifdef QT_NO_TEXTCODEC
       
   118     Q_UNUSED(tc);
       
   119     return str.toLatin1();
       
   120 #else
       
   121     return tc->fromUnicode(str);
       
   122 #endif
       
   123 }
       
   124 
       
   125 static inline QVariant qDateFromString(const QString &val)
       
   126 {
       
   127 #ifdef QT_NO_DATESTRING
       
   128     Q_UNUSED(val);
       
   129     return QVariant(val);
       
   130 #else
       
   131     if (val.isEmpty())
       
   132         return QVariant(QDate());
       
   133     return QVariant(QDate::fromString(val, Qt::ISODate));
       
   134 #endif
       
   135 }
       
   136 
       
   137 static inline QVariant qTimeFromString(const QString &val)
       
   138 {
       
   139 #ifdef QT_NO_DATESTRING
       
   140     Q_UNUSED(val);
       
   141     return QVariant(val);
       
   142 #else
       
   143     if (val.isEmpty())
       
   144         return QVariant(QTime());
       
   145     return QVariant(QTime::fromString(val, Qt::ISODate));
       
   146 #endif
       
   147 }
       
   148 
       
   149 static inline QVariant qDateTimeFromString(QString &val)
       
   150 {
       
   151 #ifdef QT_NO_DATESTRING
       
   152     Q_UNUSED(val);
       
   153     return QVariant(val);
       
   154 #else
       
   155     if (val.isEmpty())
       
   156         return QVariant(QDateTime());
       
   157     if (val.length() == 14)
       
   158         // TIMESTAMPS have the format yyyyMMddhhmmss
       
   159         val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10,
       
   160                     QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':'));
       
   161     return QVariant(QDateTime::fromString(val, Qt::ISODate));
       
   162 #endif
       
   163 }
       
   164 
       
   165 class QMYSQLResultPrivate : public QObject
       
   166 {
       
   167     Q_OBJECT
       
   168 public:
       
   169     QMYSQLResultPrivate(const QMYSQLDriver* dp, const QMYSQLResult* d) : driver(dp), result(0), q(d), 
       
   170         rowsAffected(0), hasBlobs(false)
       
   171 #if MYSQL_VERSION_ID >= 40108
       
   172         , stmt(0), meta(0), inBinds(0), outBinds(0)
       
   173 #endif
       
   174         , preparedQuery(false)
       
   175         {
       
   176             connect(dp, SIGNAL(destroyed()), this, SLOT(driverDestroyed()));
       
   177         }
       
   178 
       
   179     const QMYSQLDriver* driver;
       
   180     MYSQL_RES *result;
       
   181     MYSQL_ROW row;
       
   182     const QMYSQLResult* q;
       
   183 
       
   184     int rowsAffected;
       
   185 
       
   186     bool bindInValues();
       
   187     void bindBlobs();
       
   188 
       
   189     bool hasBlobs;
       
   190     struct QMyField
       
   191     {
       
   192         QMyField()
       
   193             : outField(0), nullIndicator(false), bufLength(0ul),
       
   194               myField(0), type(QVariant::Invalid)
       
   195         {}
       
   196         char *outField;
       
   197         my_bool nullIndicator;
       
   198         ulong bufLength;
       
   199         MYSQL_FIELD *myField;
       
   200         QVariant::Type type;
       
   201     };
       
   202 
       
   203     QVector<QMyField> fields;
       
   204 
       
   205 #if MYSQL_VERSION_ID >= 40108
       
   206     MYSQL_STMT* stmt;
       
   207     MYSQL_RES* meta;
       
   208 
       
   209     MYSQL_BIND *inBinds;
       
   210     MYSQL_BIND *outBinds;
       
   211 #endif
       
   212 
       
   213     bool preparedQuery;
       
   214 
       
   215 private Q_SLOTS:
       
   216     void driverDestroyed() { driver = NULL; }
       
   217 };
       
   218 
       
   219 #ifndef QT_NO_TEXTCODEC
       
   220 static QTextCodec* codec(MYSQL* mysql)
       
   221 {
       
   222 #if MYSQL_VERSION_ID >= 32321
       
   223     QTextCodec* heuristicCodec = QTextCodec::codecForName(mysql_character_set_name(mysql));
       
   224     if (heuristicCodec)
       
   225         return heuristicCodec;
       
   226 #endif
       
   227     return QTextCodec::codecForLocale();
       
   228 }
       
   229 #endif // QT_NO_TEXTCODEC
       
   230 
       
   231 static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
       
   232                             const QMYSQLDriverPrivate* p)
       
   233 {
       
   234     const char *cerr = p->mysql ? mysql_error(p->mysql) : 0;
       
   235     return QSqlError(QLatin1String("QMYSQL: ") + err,
       
   236                      p->tc ? toUnicode(p->tc, cerr) : QString::fromLatin1(cerr),
       
   237                      type, mysql_errno(p->mysql));
       
   238 }
       
   239 
       
   240 
       
   241 static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags)
       
   242 {
       
   243     QVariant::Type type;
       
   244     switch (mysqltype) {
       
   245     case FIELD_TYPE_TINY :
       
   246     case FIELD_TYPE_SHORT :
       
   247     case FIELD_TYPE_LONG :
       
   248     case FIELD_TYPE_INT24 :
       
   249         type = (flags & UNSIGNED_FLAG) ? QVariant::UInt : QVariant::Int;
       
   250         break;
       
   251     case FIELD_TYPE_YEAR :
       
   252         type = QVariant::Int;
       
   253         break;
       
   254     case FIELD_TYPE_LONGLONG :
       
   255         type = (flags & UNSIGNED_FLAG) ? QVariant::ULongLong : QVariant::LongLong;
       
   256         break;
       
   257     case FIELD_TYPE_FLOAT :
       
   258     case FIELD_TYPE_DOUBLE :
       
   259     case FIELD_TYPE_DECIMAL :
       
   260 #if defined(FIELD_TYPE_NEWDECIMAL)
       
   261     case FIELD_TYPE_NEWDECIMAL:
       
   262 #endif
       
   263         type = QVariant::Double;
       
   264         break;
       
   265     case FIELD_TYPE_DATE :
       
   266         type = QVariant::Date;
       
   267         break;
       
   268     case FIELD_TYPE_TIME :
       
   269         type = QVariant::Time;
       
   270         break;
       
   271     case FIELD_TYPE_DATETIME :
       
   272     case FIELD_TYPE_TIMESTAMP :
       
   273         type = QVariant::DateTime;
       
   274         break;
       
   275     case FIELD_TYPE_STRING :
       
   276     case FIELD_TYPE_VAR_STRING :
       
   277     case FIELD_TYPE_BLOB :
       
   278     case FIELD_TYPE_TINY_BLOB :
       
   279     case FIELD_TYPE_MEDIUM_BLOB :
       
   280     case FIELD_TYPE_LONG_BLOB :
       
   281         type = (flags & BINARY_FLAG) ? QVariant::ByteArray : QVariant::String;
       
   282         break;
       
   283     default:
       
   284     case FIELD_TYPE_ENUM :
       
   285     case FIELD_TYPE_SET :
       
   286         type = QVariant::String;
       
   287         break;
       
   288     }
       
   289     return type;
       
   290 }
       
   291 
       
   292 static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc)
       
   293 {
       
   294     QSqlField f(toUnicode(tc, field->name),
       
   295                 qDecodeMYSQLType(int(field->type), field->flags));
       
   296     f.setRequired(IS_NOT_NULL(field->flags));
       
   297     f.setLength(field->length);
       
   298     f.setPrecision(field->decimals);
       
   299     f.setSqlType(field->type);
       
   300     f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG);
       
   301     return f;
       
   302 }
       
   303 
       
   304 #if MYSQL_VERSION_ID >= 40108
       
   305 
       
   306 static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type,
       
   307                             MYSQL_STMT* stmt)
       
   308 {
       
   309     const char *cerr = mysql_stmt_error(stmt);
       
   310     return QSqlError(QLatin1String("QMYSQL3: ") + err,
       
   311                      QString::fromLatin1(cerr),
       
   312                      type, mysql_stmt_errno(stmt));
       
   313 }
       
   314 
       
   315 static bool qIsBlob(int t)
       
   316 {
       
   317     return t == MYSQL_TYPE_TINY_BLOB
       
   318            || t == MYSQL_TYPE_BLOB
       
   319            || t == MYSQL_TYPE_MEDIUM_BLOB
       
   320            || t == MYSQL_TYPE_LONG_BLOB;
       
   321 }
       
   322 
       
   323 static bool qIsInteger(int t)
       
   324 {
       
   325     return t == MYSQL_TYPE_TINY
       
   326            || t == MYSQL_TYPE_SHORT
       
   327            || t == MYSQL_TYPE_LONG
       
   328            || t == MYSQL_TYPE_LONGLONG
       
   329            || t == MYSQL_TYPE_INT24;
       
   330 }
       
   331 
       
   332 
       
   333 void QMYSQLResultPrivate::bindBlobs()
       
   334 {
       
   335     int i;
       
   336     MYSQL_FIELD *fieldInfo;
       
   337     MYSQL_BIND *bind;
       
   338 
       
   339     for(i = 0; i < fields.count(); ++i) {
       
   340         fieldInfo = fields.at(i).myField;
       
   341         if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) {
       
   342             bind = &inBinds[i];
       
   343             bind->buffer_length = fieldInfo->max_length;
       
   344             delete[] static_cast<char*>(bind->buffer);
       
   345             bind->buffer = new char[fieldInfo->max_length];
       
   346             fields[i].outField = static_cast<char*>(bind->buffer);
       
   347         }
       
   348     }
       
   349 }
       
   350 
       
   351 bool QMYSQLResultPrivate::bindInValues()
       
   352 {
       
   353     MYSQL_BIND *bind;
       
   354     char *field;
       
   355     int i = 0;
       
   356 
       
   357     if (!meta)
       
   358         meta = mysql_stmt_result_metadata(stmt);
       
   359     if (!meta)
       
   360         return false;
       
   361 
       
   362     fields.resize(mysql_num_fields(meta));
       
   363 
       
   364     inBinds = new MYSQL_BIND[fields.size()];
       
   365     memset(inBinds, 0, fields.size() * sizeof(MYSQL_BIND));
       
   366 
       
   367     MYSQL_FIELD *fieldInfo;
       
   368 
       
   369     while((fieldInfo = mysql_fetch_field(meta))) {
       
   370         QMyField &f = fields[i];
       
   371         f.myField = fieldInfo;
       
   372 
       
   373         f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags);
       
   374         if (qIsBlob(fieldInfo->type)) {
       
   375             // the size of a blob-field is available as soon as we call
       
   376             // mysql_stmt_store_result()
       
   377             // after mysql_stmt_exec() in QMYSQLResult::exec()
       
   378             fieldInfo->length = 0;
       
   379             hasBlobs = true;
       
   380         } else {
       
   381             // fieldInfo->length specifies the display width, which may be too
       
   382             // small to hold valid integer values (see
       
   383             // http://dev.mysql.com/doc/refman/5.0/en/numeric-types.html ), so
       
   384             // always use the MAX_BIGINT_WIDTH for integer types
       
   385             if (qIsInteger(fieldInfo->type)) {
       
   386                 fieldInfo->length = MAX_BIGINT_WIDTH;
       
   387             }
       
   388             fieldInfo->type = MYSQL_TYPE_STRING;
       
   389         }
       
   390         bind = &inBinds[i];
       
   391         field = new char[fieldInfo->length + 1];
       
   392         memset(field, 0, fieldInfo->length + 1);
       
   393 
       
   394         bind->buffer_type = fieldInfo->type;
       
   395         bind->buffer = field;
       
   396         bind->buffer_length = f.bufLength = fieldInfo->length + 1;
       
   397         bind->is_null = &f.nullIndicator;
       
   398         bind->length = &f.bufLength;
       
   399         f.outField=field;
       
   400 
       
   401         ++i;
       
   402     }
       
   403     return true;
       
   404 }
       
   405 #endif
       
   406 
       
   407 QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db)
       
   408 : QSqlResult(db)
       
   409 {
       
   410     d = new QMYSQLResultPrivate(db, this);
       
   411 }
       
   412 
       
   413 QMYSQLResult::~QMYSQLResult()
       
   414 {
       
   415     cleanup();
       
   416     delete d;
       
   417 }
       
   418 
       
   419 QVariant QMYSQLResult::handle() const
       
   420 {
       
   421 #if MYSQL_VERSION_ID >= 40108
       
   422     if(d->preparedQuery)
       
   423         return d->meta ? qVariantFromValue(d->meta) : qVariantFromValue(d->stmt);
       
   424     else
       
   425 #endif
       
   426         return qVariantFromValue(d->result);
       
   427 }
       
   428 
       
   429 void QMYSQLResult::cleanup()
       
   430 {
       
   431     if (d->result)
       
   432         mysql_free_result(d->result);
       
   433 
       
   434 // must iterate trough leftover result sets from multi-selects or stored procedures
       
   435 // if this isn't done subsequent queries will fail with "Commands out of sync"
       
   436 #if MYSQL_VERSION_ID >= 40100
       
   437     while (d->driver && d->driver->d->mysql && mysql_next_result(d->driver->d->mysql) == 0) {
       
   438         MYSQL_RES *res = mysql_store_result(d->driver->d->mysql);
       
   439         if (res)
       
   440             mysql_free_result(res);
       
   441     }
       
   442 #endif
       
   443 
       
   444 #if MYSQL_VERSION_ID >= 40108
       
   445     if (d->stmt) {
       
   446         if (mysql_stmt_close(d->stmt))
       
   447             qWarning("QMYSQLResult::cleanup: unable to free statement handle");
       
   448         d->stmt = 0;
       
   449     }
       
   450 
       
   451     if (d->meta) {
       
   452         mysql_free_result(d->meta);
       
   453         d->meta = 0;
       
   454     }
       
   455 
       
   456     int i;
       
   457     for (i = 0; i < d->fields.count(); ++i)
       
   458         delete[] d->fields[i].outField;
       
   459 
       
   460     if (d->outBinds) {
       
   461         delete[] d->outBinds;
       
   462         d->outBinds = 0;
       
   463     }
       
   464 
       
   465     if (d->inBinds) {
       
   466         delete[] d->inBinds;
       
   467         d->inBinds = 0;
       
   468     }
       
   469 #endif
       
   470 
       
   471     d->hasBlobs = false;
       
   472     d->fields.clear();
       
   473     d->result = NULL;
       
   474     d->row = NULL;
       
   475     setAt(-1);
       
   476     setActive(false);
       
   477 }
       
   478 
       
   479 bool QMYSQLResult::fetch(int i)
       
   480 {
       
   481     if(!d->driver)
       
   482         return false;
       
   483     if (isForwardOnly()) { // fake a forward seek
       
   484         if (at() < i) {
       
   485             int x = i - at();
       
   486             while (--x && fetchNext()) {};
       
   487             return fetchNext();
       
   488         } else {
       
   489             return false;
       
   490         }
       
   491     }
       
   492     if (at() == i)
       
   493         return true;
       
   494     if (d->preparedQuery) {
       
   495 #if MYSQL_VERSION_ID >= 40108
       
   496         mysql_stmt_data_seek(d->stmt, i);
       
   497 
       
   498         int nRC = mysql_stmt_fetch(d->stmt);
       
   499         if (nRC) {
       
   500 #ifdef MYSQL_DATA_TRUNCATED
       
   501             if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
       
   502 #else
       
   503             if (nRC == 1)
       
   504 #endif
       
   505                 setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
   506                          "Unable to fetch data"), QSqlError::StatementError, d->stmt));
       
   507             return false;
       
   508         }
       
   509 #else
       
   510         return false;
       
   511 #endif
       
   512     } else {
       
   513         mysql_data_seek(d->result, i);
       
   514         d->row = mysql_fetch_row(d->result);
       
   515         if (!d->row)
       
   516             return false;
       
   517     }
       
   518 
       
   519     setAt(i);
       
   520     return true;
       
   521 }
       
   522 
       
   523 bool QMYSQLResult::fetchNext()
       
   524 {
       
   525     if(!d->driver)
       
   526         return false;
       
   527     if (d->preparedQuery) {
       
   528 #if MYSQL_VERSION_ID >= 40108
       
   529         int nRC = mysql_stmt_fetch(d->stmt);
       
   530         if (nRC) {
       
   531 #ifdef MYSQL_DATA_TRUNCATED
       
   532             if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
       
   533 #else
       
   534             if (nRC == 1)
       
   535 #endif // MYSQL_DATA_TRUNCATED
       
   536                 setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
   537                                     "Unable to fetch data"), QSqlError::StatementError, d->stmt));
       
   538             return false;
       
   539         }
       
   540 #else
       
   541         return false;
       
   542 #endif
       
   543     } else {
       
   544         d->row = mysql_fetch_row(d->result);
       
   545         if (!d->row)
       
   546             return false;
       
   547     }
       
   548     setAt(at() + 1);
       
   549     return true;
       
   550 }
       
   551 
       
   552 bool QMYSQLResult::fetchLast()
       
   553 {
       
   554     if(!d->driver)
       
   555         return false;
       
   556     if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries
       
   557         bool success = fetchNext(); // did we move at all?
       
   558         while (fetchNext()) {};
       
   559         return success;
       
   560     }
       
   561 
       
   562     my_ulonglong numRows;
       
   563     if (d->preparedQuery) {
       
   564 #if MYSQL_VERSION_ID >= 40108
       
   565         numRows = mysql_stmt_num_rows(d->stmt);
       
   566 #else
       
   567         numRows = 0;
       
   568 #endif
       
   569     } else {
       
   570         numRows = mysql_num_rows(d->result);
       
   571     }
       
   572     if (at() == int(numRows))
       
   573         return true;
       
   574     if (!numRows)
       
   575         return false;
       
   576     return fetch(numRows - 1);
       
   577 }
       
   578 
       
   579 bool QMYSQLResult::fetchFirst()
       
   580 {
       
   581     if (at() == 0)
       
   582         return true;
       
   583 
       
   584     if (isForwardOnly())
       
   585         return (at() == QSql::BeforeFirstRow) ? fetchNext() : false;
       
   586     return fetch(0);
       
   587 }
       
   588 
       
   589 QVariant QMYSQLResult::data(int field)
       
   590 {
       
   591 
       
   592     if (!isSelect() || field >= d->fields.count()) {
       
   593         qWarning("QMYSQLResult::data: column %d out of range", field);
       
   594         return QVariant();
       
   595     }
       
   596 
       
   597     if (!d->driver)
       
   598         return QVariant();
       
   599 
       
   600     int fieldLength = 0;
       
   601     const QMYSQLResultPrivate::QMyField &f = d->fields.at(field);
       
   602     QString val;
       
   603     if (d->preparedQuery) {
       
   604         if (f.nullIndicator)
       
   605             return QVariant(f.type);
       
   606 
       
   607         if (f.type != QVariant::ByteArray)
       
   608             val = toUnicode(d->driver->d->tc, f.outField, f.bufLength);
       
   609     } else {
       
   610         if (d->row[field] == NULL) {
       
   611             // NULL value
       
   612             return QVariant(f.type);
       
   613         }
       
   614         fieldLength = mysql_fetch_lengths(d->result)[field];
       
   615         if (f.type != QVariant::ByteArray)
       
   616             val = toUnicode(d->driver->d->tc, d->row[field], fieldLength);
       
   617     }
       
   618 
       
   619     switch(f.type) {
       
   620     case QVariant::LongLong:
       
   621         return QVariant(val.toLongLong());
       
   622     case QVariant::ULongLong:
       
   623         return QVariant(val.toULongLong());
       
   624     case QVariant::Int:
       
   625         return QVariant(val.toInt());
       
   626     case QVariant::UInt:
       
   627         return QVariant(val.toUInt());
       
   628     case QVariant::Double: {
       
   629         QVariant v;
       
   630         bool ok=false;
       
   631         double dbl = val.toDouble(&ok);
       
   632         switch(numericalPrecisionPolicy()) {
       
   633             case QSql::LowPrecisionInt32:
       
   634                 v=QVariant(dbl).toInt();
       
   635                 break;
       
   636             case QSql::LowPrecisionInt64:
       
   637                 v = QVariant(dbl).toLongLong();
       
   638                 break;
       
   639             case QSql::LowPrecisionDouble:
       
   640                 v = QVariant(dbl);
       
   641                 break;
       
   642             case QSql::HighPrecision:
       
   643             default:
       
   644                 v = val;
       
   645                 ok = true;
       
   646                 break;
       
   647         }
       
   648         if(ok)
       
   649             return v;
       
   650         else
       
   651             return QVariant();
       
   652     }
       
   653         return QVariant(val.toDouble());
       
   654     case QVariant::Date:
       
   655         return qDateFromString(val);
       
   656     case QVariant::Time:
       
   657         return qTimeFromString(val);
       
   658     case QVariant::DateTime:
       
   659         return qDateTimeFromString(val);
       
   660     case QVariant::ByteArray: {
       
   661 
       
   662         QByteArray ba;
       
   663         if (d->preparedQuery) {
       
   664             ba = QByteArray(f.outField, f.bufLength);
       
   665         } else {
       
   666             ba = QByteArray(d->row[field], fieldLength);
       
   667         }
       
   668         return QVariant(ba);
       
   669     }
       
   670     default:
       
   671     case QVariant::String:
       
   672         return QVariant(val);
       
   673     }
       
   674     qWarning("QMYSQLResult::data: unknown data type");
       
   675     return QVariant();
       
   676 }
       
   677 
       
   678 bool QMYSQLResult::isNull(int field)
       
   679 {
       
   680    if (d->preparedQuery)
       
   681        return d->fields.at(field).nullIndicator;
       
   682    else
       
   683        return d->row[field] == NULL;
       
   684 }
       
   685 
       
   686 bool QMYSQLResult::reset (const QString& query)
       
   687 {
       
   688     if (!driver() || !driver()->isOpen() || driver()->isOpenError() || !d->driver)
       
   689         return false;
       
   690 
       
   691     d->preparedQuery = false;
       
   692 
       
   693     cleanup();
       
   694 
       
   695     const QByteArray encQuery(fromUnicode(d->driver->d->tc, query));
       
   696     if (mysql_real_query(d->driver->d->mysql, encQuery.data(), encQuery.length())) {
       
   697         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"),
       
   698                      QSqlError::StatementError, d->driver->d));
       
   699         return false;
       
   700     }
       
   701     d->result = mysql_store_result(d->driver->d->mysql);
       
   702     if (!d->result && mysql_field_count(d->driver->d->mysql) > 0) {
       
   703         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"),
       
   704                     QSqlError::StatementError, d->driver->d));
       
   705         return false;
       
   706     }
       
   707     int numFields = mysql_field_count(d->driver->d->mysql);
       
   708     setSelect(numFields != 0);
       
   709     d->fields.resize(numFields);
       
   710     d->rowsAffected = mysql_affected_rows(d->driver->d->mysql);
       
   711 
       
   712     if (isSelect()) {
       
   713         for(int i = 0; i < numFields; i++) {
       
   714             MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
       
   715             d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
       
   716         }
       
   717         setAt(QSql::BeforeFirstRow);
       
   718     }
       
   719     setActive(true);
       
   720     return isActive();
       
   721 }
       
   722 
       
   723 int QMYSQLResult::size()
       
   724 {
       
   725     if (d->driver && isSelect())
       
   726         if (d->preparedQuery)
       
   727 #if MYSQL_VERSION_ID >= 40108
       
   728             return mysql_stmt_num_rows(d->stmt);
       
   729 #else
       
   730             return -1;
       
   731 #endif
       
   732         else
       
   733             return int(mysql_num_rows(d->result));
       
   734     else
       
   735         return -1;
       
   736 }
       
   737 
       
   738 int QMYSQLResult::numRowsAffected()
       
   739 {
       
   740     return d->rowsAffected;
       
   741 }
       
   742 
       
   743 QVariant QMYSQLResult::lastInsertId() const
       
   744 {
       
   745     if (!isActive() || !d->driver)
       
   746         return QVariant();
       
   747 
       
   748     if (d->preparedQuery) {
       
   749 #if MYSQL_VERSION_ID >= 40108
       
   750         quint64 id = mysql_stmt_insert_id(d->stmt);
       
   751         if (id)
       
   752             return QVariant(id);
       
   753 #endif
       
   754     } else {
       
   755         quint64 id = mysql_insert_id(d->driver->d->mysql);
       
   756         if (id)
       
   757             return QVariant(id);
       
   758     }
       
   759     return QVariant();
       
   760 }
       
   761 
       
   762 QSqlRecord QMYSQLResult::record() const
       
   763 {
       
   764     QSqlRecord info;
       
   765     MYSQL_RES *res;
       
   766     if (!isActive() || !isSelect() || !d->driver)
       
   767         return info;
       
   768 
       
   769 #if MYSQL_VERSION_ID >= 40108
       
   770     res = d->preparedQuery ? d->meta : d->result;
       
   771 #else
       
   772     res = d->result;
       
   773 #endif
       
   774 
       
   775     if (!mysql_errno(d->driver->d->mysql)) {
       
   776         mysql_field_seek(res, 0);
       
   777         MYSQL_FIELD* field = mysql_fetch_field(res);
       
   778         while(field) {
       
   779             info.append(qToField(field, d->driver->d->tc));
       
   780             field = mysql_fetch_field(res);
       
   781         }
       
   782     }
       
   783     mysql_field_seek(res, 0);
       
   784     return info;
       
   785 }
       
   786 
       
   787 bool QMYSQLResult::nextResult()
       
   788 {
       
   789     if(!d->driver)
       
   790         return false;
       
   791 #if MYSQL_VERSION_ID >= 40100 
       
   792     setAt(-1);
       
   793     setActive(false);
       
   794 
       
   795     if (d->result && isSelect())
       
   796         mysql_free_result(d->result);
       
   797     d->result = 0;
       
   798     setSelect(false);
       
   799    
       
   800     for (int i = 0; i < d->fields.count(); ++i)
       
   801         delete[] d->fields[i].outField;
       
   802     d->fields.clear();
       
   803 
       
   804     int status = mysql_next_result(d->driver->d->mysql);
       
   805     if (status > 0) {
       
   806         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"),
       
   807                      QSqlError::StatementError, d->driver->d));
       
   808         return false;
       
   809     } else if (status == -1) {
       
   810         return false;   // No more result sets
       
   811     }
       
   812 
       
   813     d->result = mysql_store_result(d->driver->d->mysql);
       
   814     int numFields = mysql_field_count(d->driver->d->mysql);
       
   815     if (!d->result && numFields > 0) {
       
   816         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"),
       
   817                      QSqlError::StatementError, d->driver->d));
       
   818         return false;
       
   819     }
       
   820 
       
   821     setSelect(numFields > 0);
       
   822     d->fields.resize(numFields);
       
   823     d->rowsAffected = mysql_affected_rows(d->driver->d->mysql);
       
   824 
       
   825     if (isSelect()) {
       
   826         for (int i = 0; i < numFields; i++) {
       
   827             MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
       
   828             d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
       
   829         }
       
   830     }
       
   831 
       
   832     setActive(true);
       
   833     return true;
       
   834 #else
       
   835     return false;
       
   836 #endif
       
   837 }
       
   838 
       
   839 void QMYSQLResult::virtual_hook(int id, void *data)
       
   840 {
       
   841     switch (id) {
       
   842     case QSqlResult::NextResult:
       
   843         Q_ASSERT(data);
       
   844         *static_cast<bool*>(data) = nextResult();
       
   845         break;
       
   846     default:
       
   847         QSqlResult::virtual_hook(id, data);
       
   848     }
       
   849 }
       
   850 
       
   851 
       
   852 #if MYSQL_VERSION_ID >= 40108
       
   853 
       
   854 static MYSQL_TIME *toMySqlDate(QDate date, QTime time, QVariant::Type type)
       
   855 {
       
   856     Q_ASSERT(type == QVariant::Time || type == QVariant::Date
       
   857              || type == QVariant::DateTime);
       
   858 
       
   859     MYSQL_TIME *myTime = new MYSQL_TIME;
       
   860     memset(myTime, 0, sizeof(MYSQL_TIME));
       
   861 
       
   862     if (type == QVariant::Time || type == QVariant::DateTime) {
       
   863         myTime->hour = time.hour();
       
   864         myTime->minute = time.minute();
       
   865         myTime->second = time.second();
       
   866         myTime->second_part = time.msec();
       
   867     }
       
   868     if (type == QVariant::Date || type == QVariant::DateTime) {
       
   869         myTime->year = date.year();
       
   870         myTime->month = date.month();
       
   871         myTime->day = date.day();
       
   872     }
       
   873 
       
   874     return myTime;
       
   875 }
       
   876 
       
   877 bool QMYSQLResult::prepare(const QString& query)
       
   878 {
       
   879     if(!d->driver)
       
   880         return false;
       
   881 #if MYSQL_VERSION_ID >= 40108
       
   882     cleanup();
       
   883     if (!d->driver->d->preparedQuerysEnabled)
       
   884         return QSqlResult::prepare(query);
       
   885 
       
   886     int r;
       
   887 
       
   888     if (query.isEmpty())
       
   889         return false;
       
   890 
       
   891     if (!d->stmt)
       
   892         d->stmt = mysql_stmt_init(d->driver->d->mysql);
       
   893     if (!d->stmt) {
       
   894         setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"),
       
   895                      QSqlError::StatementError, d->driver->d));
       
   896         return false;
       
   897     }
       
   898 
       
   899     const QByteArray encQuery(fromUnicode(d->driver->d->tc, query));
       
   900     r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length());
       
   901     if (r != 0) {
       
   902         setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
   903                      "Unable to prepare statement"), QSqlError::StatementError, d->stmt));
       
   904         cleanup();
       
   905         return false;
       
   906     }
       
   907 
       
   908     if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues
       
   909         d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)];
       
   910     }
       
   911 
       
   912     setSelect(d->bindInValues());
       
   913     d->preparedQuery = true;
       
   914     return true;
       
   915 #else
       
   916     return false;
       
   917 #endif
       
   918 }
       
   919 
       
   920 bool QMYSQLResult::exec()
       
   921 {
       
   922     if (!d->driver)
       
   923         return false;
       
   924     if (!d->preparedQuery)
       
   925         return QSqlResult::exec();
       
   926     if (!d->stmt)
       
   927         return false;
       
   928 
       
   929     int r = 0;
       
   930     MYSQL_BIND* currBind;
       
   931     QVector<MYSQL_TIME *> timeVector;
       
   932     QVector<QByteArray> stringVector;
       
   933     QVector<my_bool> nullVector;
       
   934 
       
   935     const QVector<QVariant> values = boundValues();
       
   936 
       
   937     r = mysql_stmt_reset(d->stmt);
       
   938     if (r != 0) {
       
   939         setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
   940                      "Unable to reset statement"), QSqlError::StatementError, d->stmt));
       
   941         return false;
       
   942     }
       
   943 
       
   944     if (mysql_stmt_param_count(d->stmt) > 0 &&
       
   945         mysql_stmt_param_count(d->stmt) == (uint)values.count()) {
       
   946 
       
   947         nullVector.resize(values.count());
       
   948         for (int i = 0; i < values.count(); ++i) {
       
   949             const QVariant &val = boundValues().at(i);
       
   950             void *data = const_cast<void *>(val.constData());
       
   951 
       
   952             currBind = &d->outBinds[i];
       
   953 
       
   954             nullVector[i] = static_cast<my_bool>(val.isNull());
       
   955             currBind->is_null = &nullVector[i];
       
   956             currBind->length = 0;
       
   957             currBind->is_unsigned = 0;
       
   958 
       
   959             switch (val.type()) {
       
   960                 case QVariant::ByteArray:
       
   961                     currBind->buffer_type = MYSQL_TYPE_BLOB;
       
   962                     currBind->buffer = const_cast<char *>(val.toByteArray().constData());
       
   963                     currBind->buffer_length = val.toByteArray().size();
       
   964                     break;
       
   965 
       
   966                 case QVariant::Time:
       
   967                 case QVariant::Date:
       
   968                 case QVariant::DateTime: {
       
   969                     MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.type());
       
   970                     timeVector.append(myTime);
       
   971 
       
   972                     currBind->buffer = myTime;
       
   973                     switch(val.type()) {
       
   974                     case QVariant::Time:
       
   975                         currBind->buffer_type = MYSQL_TYPE_TIME;
       
   976                         myTime->time_type = MYSQL_TIMESTAMP_TIME;
       
   977                         break;
       
   978                     case QVariant::Date:
       
   979                         currBind->buffer_type = MYSQL_TYPE_DATE;
       
   980                         myTime->time_type = MYSQL_TIMESTAMP_DATE;
       
   981                         break;
       
   982                     case QVariant::DateTime:
       
   983                         currBind->buffer_type = MYSQL_TYPE_DATETIME;
       
   984                         myTime->time_type = MYSQL_TIMESTAMP_DATETIME;
       
   985                         break;
       
   986                     default:
       
   987                         break;
       
   988                     }
       
   989                     currBind->buffer_length = sizeof(MYSQL_TIME);
       
   990                     currBind->length = 0;
       
   991                     break; }
       
   992                 case QVariant::UInt:
       
   993                 case QVariant::Int:
       
   994                 case QVariant::Bool:
       
   995                     currBind->buffer_type = MYSQL_TYPE_LONG;
       
   996                     currBind->buffer = data;
       
   997                     currBind->buffer_length = sizeof(int);
       
   998                     currBind->is_unsigned = (val.type() != QVariant::Int);
       
   999                     break;
       
  1000                 case QVariant::Double:
       
  1001                     currBind->buffer_type = MYSQL_TYPE_DOUBLE;
       
  1002                     currBind->buffer = data;
       
  1003                     currBind->buffer_length = sizeof(double);
       
  1004                     break;
       
  1005                 case QVariant::LongLong:
       
  1006                 case QVariant::ULongLong:
       
  1007                     currBind->buffer_type = MYSQL_TYPE_LONGLONG;
       
  1008                     currBind->buffer = data;
       
  1009                     currBind->buffer_length = sizeof(qint64);
       
  1010                     currBind->is_unsigned = (val.type() == QVariant::ULongLong);
       
  1011                     break;
       
  1012                 case QVariant::String:
       
  1013                 default: {
       
  1014                     QByteArray ba = fromUnicode(d->driver->d->tc, val.toString());
       
  1015                     stringVector.append(ba);
       
  1016                     currBind->buffer_type = MYSQL_TYPE_STRING;
       
  1017                     currBind->buffer = const_cast<char *>(ba.constData());
       
  1018                     currBind->buffer_length = ba.length();
       
  1019                     break; }
       
  1020             }
       
  1021         }
       
  1022 
       
  1023         r = mysql_stmt_bind_param(d->stmt, d->outBinds);
       
  1024         if (r != 0) {
       
  1025             setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
  1026                          "Unable to bind value"), QSqlError::StatementError, d->stmt));
       
  1027             qDeleteAll(timeVector);
       
  1028             return false;
       
  1029         }
       
  1030     }
       
  1031     r = mysql_stmt_execute(d->stmt);
       
  1032 
       
  1033     qDeleteAll(timeVector);
       
  1034 
       
  1035     if (r != 0) {
       
  1036         setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
  1037                      "Unable to execute statement"), QSqlError::StatementError, d->stmt));
       
  1038         return false;
       
  1039     }
       
  1040     //if there is meta-data there is also data
       
  1041     setSelect(d->meta);
       
  1042 
       
  1043     d->rowsAffected = mysql_stmt_affected_rows(d->stmt);
       
  1044 
       
  1045     if (isSelect()) {
       
  1046         my_bool update_max_length = true;
       
  1047 
       
  1048         r = mysql_stmt_bind_result(d->stmt, d->inBinds);
       
  1049         if (r != 0) {
       
  1050             setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
  1051                          "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
       
  1052             return false;
       
  1053         }
       
  1054         if (d->hasBlobs)
       
  1055             mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length);
       
  1056 
       
  1057         r = mysql_stmt_store_result(d->stmt);
       
  1058         if (r != 0) {
       
  1059             setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
  1060                          "Unable to store statement results"), QSqlError::StatementError, d->stmt));
       
  1061             return false;
       
  1062         }
       
  1063 
       
  1064         if (d->hasBlobs) {
       
  1065             // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes
       
  1066             // when called without a preceding call to mysql_stmt_bind_result()
       
  1067             // in versions < 4.1.8
       
  1068             d->bindBlobs();
       
  1069             r = mysql_stmt_bind_result(d->stmt, d->inBinds);
       
  1070             if (r != 0) {
       
  1071                 setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
       
  1072                              "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
       
  1073                 return false;
       
  1074             }
       
  1075         }
       
  1076         setAt(QSql::BeforeFirstRow);
       
  1077     }
       
  1078     setActive(true);
       
  1079     return true;
       
  1080 }
       
  1081 #endif
       
  1082 /////////////////////////////////////////////////////////
       
  1083 
       
  1084 static int qMySqlConnectionCount = 0;
       
  1085 static bool qMySqlInitHandledByUser = false;
       
  1086 
       
  1087 static void qLibraryInit()
       
  1088 {
       
  1089 #ifndef Q_NO_MYSQL_EMBEDDED
       
  1090 # if MYSQL_VERSION_ID >= 40000
       
  1091     if (qMySqlInitHandledByUser || qMySqlConnectionCount > 1)
       
  1092         return;
       
  1093 
       
  1094 # if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003
       
  1095     if (mysql_library_init(0, 0, 0)) {
       
  1096 # else
       
  1097     if (mysql_server_init(0, 0, 0)) {
       
  1098 # endif
       
  1099         qWarning("QMYSQLDriver::qServerInit: unable to start server.");
       
  1100     }
       
  1101 # endif // MYSQL_VERSION_ID
       
  1102 #endif // Q_NO_MYSQL_EMBEDDED
       
  1103 }
       
  1104 
       
  1105 static void qLibraryEnd()
       
  1106 {
       
  1107 #ifndef Q_NO_MYSQL_EMBEDDED
       
  1108 # if MYSQL_VERSION_ID > 40000
       
  1109 #  if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003
       
  1110     mysql_library_end();
       
  1111 #  else
       
  1112     mysql_server_end();
       
  1113 #  endif
       
  1114 # endif
       
  1115 #endif
       
  1116 }
       
  1117 
       
  1118 QMYSQLDriver::QMYSQLDriver(QObject * parent)
       
  1119     : QSqlDriver(parent)
       
  1120 {
       
  1121     init();
       
  1122     qLibraryInit();
       
  1123 }
       
  1124 
       
  1125 /*!
       
  1126     Create a driver instance with the open connection handle, \a con.
       
  1127     The instance's parent (owner) is \a parent.
       
  1128 */
       
  1129 
       
  1130 QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent)
       
  1131     : QSqlDriver(parent)
       
  1132 {
       
  1133     init();
       
  1134     if (con) {
       
  1135         d->mysql = (MYSQL *) con;
       
  1136 #ifndef QT_NO_TEXTCODEC
       
  1137         d->tc = codec(con);
       
  1138 #endif
       
  1139         setOpen(true);
       
  1140         setOpenError(false);
       
  1141         if (qMySqlConnectionCount == 1)
       
  1142             qMySqlInitHandledByUser = true;
       
  1143     } else {
       
  1144         qLibraryInit();
       
  1145     }
       
  1146 }
       
  1147 
       
  1148 void QMYSQLDriver::init()
       
  1149 {
       
  1150     d = new QMYSQLDriverPrivate();
       
  1151     d->mysql = 0;
       
  1152     qMySqlConnectionCount++;
       
  1153 }
       
  1154 
       
  1155 QMYSQLDriver::~QMYSQLDriver()
       
  1156 {
       
  1157     qMySqlConnectionCount--;
       
  1158     if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser)
       
  1159         qLibraryEnd();
       
  1160     delete d;
       
  1161 }
       
  1162 
       
  1163 bool QMYSQLDriver::hasFeature(DriverFeature f) const
       
  1164 {
       
  1165     switch (f) {
       
  1166     case Transactions:
       
  1167 // CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34
       
  1168 #ifdef CLIENT_TRANSACTIONS
       
  1169         if (d->mysql) {
       
  1170             if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS)
       
  1171                 return true;
       
  1172         }
       
  1173 #endif
       
  1174         return false;
       
  1175     case NamedPlaceholders:
       
  1176     case BatchOperations:
       
  1177     case SimpleLocking:
       
  1178     case EventNotifications:
       
  1179     case FinishQuery:
       
  1180         return false;
       
  1181     case QuerySize:
       
  1182     case BLOB:
       
  1183     case LastInsertId:
       
  1184     case Unicode:
       
  1185     case LowPrecisionNumbers:
       
  1186         return true;
       
  1187     case PreparedQueries:
       
  1188     case PositionalPlaceholders:
       
  1189 #if MYSQL_VERSION_ID >= 40108
       
  1190         return d->preparedQuerysEnabled;
       
  1191 #else
       
  1192         return false;
       
  1193 #endif
       
  1194     case MultipleResultSets:
       
  1195 #if MYSQL_VERSION_ID >= 40100
       
  1196         return true;
       
  1197 #else
       
  1198         return false;
       
  1199 #endif
       
  1200     }
       
  1201     return false;
       
  1202 }
       
  1203 
       
  1204 static void setOptionFlag(uint &optionFlags, const QString &opt)
       
  1205 {
       
  1206     if (opt == QLatin1String("CLIENT_COMPRESS"))
       
  1207         optionFlags |= CLIENT_COMPRESS;
       
  1208     else if (opt == QLatin1String("CLIENT_FOUND_ROWS"))
       
  1209         optionFlags |= CLIENT_FOUND_ROWS;
       
  1210     else if (opt == QLatin1String("CLIENT_IGNORE_SPACE"))
       
  1211         optionFlags |= CLIENT_IGNORE_SPACE;
       
  1212     else if (opt == QLatin1String("CLIENT_INTERACTIVE"))
       
  1213         optionFlags |= CLIENT_INTERACTIVE;
       
  1214     else if (opt == QLatin1String("CLIENT_NO_SCHEMA"))
       
  1215         optionFlags |= CLIENT_NO_SCHEMA;
       
  1216     else if (opt == QLatin1String("CLIENT_ODBC"))
       
  1217         optionFlags |= CLIENT_ODBC;
       
  1218     else if (opt == QLatin1String("CLIENT_SSL"))
       
  1219         optionFlags |= CLIENT_SSL;
       
  1220     else
       
  1221         qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData());
       
  1222 }
       
  1223 
       
  1224 bool QMYSQLDriver::open(const QString& db,
       
  1225                          const QString& user,
       
  1226                          const QString& password,
       
  1227                          const QString& host,
       
  1228                          int port,
       
  1229                          const QString& connOpts)
       
  1230 {
       
  1231     if (isOpen())
       
  1232         close();
       
  1233 
       
  1234     /* This is a hack to get MySQL's stored procedure support working.
       
  1235        Since a stored procedure _may_ return multiple result sets,
       
  1236        we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_
       
  1237        stored procedure call will fail.
       
  1238     */
       
  1239     unsigned int optionFlags = Q_CLIENT_MULTI_STATEMENTS;
       
  1240     const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
       
  1241     QString unixSocket;
       
  1242 #if MYSQL_VERSION_ID >= 50000
       
  1243     my_bool reconnect=false;
       
  1244 #endif
       
  1245 
       
  1246     // extract the real options from the string
       
  1247     for (int i = 0; i < opts.count(); ++i) {
       
  1248         QString tmp(opts.at(i).simplified());
       
  1249         int idx;
       
  1250         if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) {
       
  1251             QString val = tmp.mid(idx + 1).simplified();
       
  1252             QString opt = tmp.left(idx).simplified();
       
  1253             if (opt == QLatin1String("UNIX_SOCKET"))
       
  1254                 unixSocket = val;
       
  1255 #if MYSQL_VERSION_ID >= 50000
       
  1256             else if (opt == QLatin1String("MYSQL_OPT_RECONNECT")) {
       
  1257                 if (val == QLatin1String("TRUE") || val == QLatin1String("1") || val.isEmpty())
       
  1258                     reconnect = true;
       
  1259             }
       
  1260 #endif
       
  1261             else if (val == QLatin1String("TRUE") || val == QLatin1String("1"))
       
  1262                 setOptionFlag(optionFlags, tmp.left(idx).simplified());
       
  1263             else
       
  1264                 qWarning("QMYSQLDriver::open: Illegal connect option value '%s'",
       
  1265                          tmp.toLocal8Bit().constData());
       
  1266         } else {
       
  1267             setOptionFlag(optionFlags, tmp);
       
  1268         }
       
  1269     }
       
  1270 
       
  1271     if ((d->mysql = mysql_init((MYSQL*) 0)) &&
       
  1272             mysql_real_connect(d->mysql,
       
  1273                                host.isNull() ? static_cast<const char *>(0)
       
  1274                                              : host.toLocal8Bit().constData(),
       
  1275                                user.isNull() ? static_cast<const char *>(0)
       
  1276                                              : user.toLocal8Bit().constData(),
       
  1277                                password.isNull() ? static_cast<const char *>(0)
       
  1278                                                  : password.toLocal8Bit().constData(),
       
  1279                                db.isNull() ? static_cast<const char *>(0)
       
  1280                                            : db.toLocal8Bit().constData(),
       
  1281                                (port > -1) ? port : 0,
       
  1282                                unixSocket.isNull() ? static_cast<const char *>(0)
       
  1283                                            : unixSocket.toLocal8Bit().constData(),
       
  1284                                optionFlags))
       
  1285     {
       
  1286         if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
       
  1287             setLastError(qMakeError(tr("Unable to open database '") + db +
       
  1288                          QLatin1Char('\''), QSqlError::ConnectionError, d));
       
  1289             mysql_close(d->mysql);
       
  1290             setOpenError(true);
       
  1291             return false;
       
  1292         }
       
  1293 #if MYSQL_VERSION_ID >= 50000
       
  1294         if(reconnect)
       
  1295             mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect);
       
  1296 #endif
       
  1297     } else {
       
  1298         setLastError(qMakeError(tr("Unable to connect"),
       
  1299                      QSqlError::ConnectionError, d));
       
  1300         mysql_close(d->mysql);
       
  1301         d->mysql = NULL;
       
  1302         setOpenError(true);
       
  1303         return false;
       
  1304     }
       
  1305 
       
  1306 #if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007
       
  1307     // force the communication to be utf8
       
  1308     mysql_set_character_set(d->mysql, "utf8");
       
  1309 #endif
       
  1310 #ifndef QT_NO_TEXTCODEC
       
  1311     d->tc = codec(d->mysql);
       
  1312 #endif
       
  1313 
       
  1314 #if MYSQL_VERSION_ID >= 40108
       
  1315     d->preparedQuerysEnabled = mysql_get_client_version() >= 40108
       
  1316                         && mysql_get_server_version(d->mysql) >= 40100;
       
  1317 #else
       
  1318     d->preparedQuerysEnabled = false;
       
  1319 #endif
       
  1320 
       
  1321 #ifndef QT_NO_THREAD
       
  1322     mysql_thread_init();
       
  1323 #endif
       
  1324 
       
  1325 
       
  1326     setOpen(true);
       
  1327     setOpenError(false);
       
  1328     return true;
       
  1329 }
       
  1330 
       
  1331 void QMYSQLDriver::close()
       
  1332 {
       
  1333     if (isOpen()) {
       
  1334 #ifndef QT_NO_THREAD
       
  1335         mysql_thread_end();
       
  1336 #endif
       
  1337         mysql_close(d->mysql);
       
  1338         d->mysql = NULL;
       
  1339         setOpen(false);
       
  1340         setOpenError(false);
       
  1341     }
       
  1342 }
       
  1343 
       
  1344 QSqlResult *QMYSQLDriver::createResult() const
       
  1345 {
       
  1346     return new QMYSQLResult(this);
       
  1347 }
       
  1348 
       
  1349 QStringList QMYSQLDriver::tables(QSql::TableType type) const
       
  1350 {
       
  1351     QStringList tl;
       
  1352 #if MYSQL_VERSION_ID >= 40100
       
  1353     if( mysql_get_server_version(d->mysql) < 50000)
       
  1354     {
       
  1355 #endif
       
  1356         if (!isOpen())
       
  1357             return tl;
       
  1358         if (!(type & QSql::Tables))
       
  1359             return tl;
       
  1360 
       
  1361         MYSQL_RES* tableRes = mysql_list_tables(d->mysql, NULL);
       
  1362         MYSQL_ROW row;
       
  1363         int i = 0;
       
  1364         while (tableRes) {
       
  1365             mysql_data_seek(tableRes, i);
       
  1366             row = mysql_fetch_row(tableRes);
       
  1367             if (!row)
       
  1368                 break;
       
  1369             tl.append(toUnicode(d->tc, row[0]));
       
  1370             i++;
       
  1371         }
       
  1372         mysql_free_result(tableRes);
       
  1373 #if MYSQL_VERSION_ID >= 40100
       
  1374     } else {
       
  1375         QSqlQuery q(createResult());
       
  1376         if(type & QSql::Tables) {
       
  1377             q.exec(QLatin1String("select table_name from information_schema.tables where table_type = 'BASE TABLE'"));
       
  1378             while(q.next())
       
  1379                 tl.append(q.value(0).toString());
       
  1380         }
       
  1381         if(type & QSql::Views) {
       
  1382             q.exec(QLatin1String("select table_name from information_schema.tables where table_type = 'VIEW'"));
       
  1383             while(q.next())
       
  1384                 tl.append(q.value(0).toString());
       
  1385         }
       
  1386     }
       
  1387 #endif
       
  1388     return tl;
       
  1389 }
       
  1390 
       
  1391 QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const
       
  1392 {
       
  1393     QSqlIndex idx;
       
  1394     if (!isOpen())
       
  1395         return idx;
       
  1396 
       
  1397     QSqlQuery i(createResult());
       
  1398     QString stmt(QLatin1String("show index from %1;"));
       
  1399     QSqlRecord fil = record(tablename);
       
  1400     i.exec(stmt.arg(tablename));
       
  1401     while (i.isActive() && i.next()) {
       
  1402         if (i.value(2).toString() == QLatin1String("PRIMARY")) {
       
  1403             idx.append(fil.field(i.value(4).toString()));
       
  1404             idx.setCursorName(i.value(0).toString());
       
  1405             idx.setName(i.value(2).toString());
       
  1406         }
       
  1407     }
       
  1408 
       
  1409     return idx;
       
  1410 }
       
  1411 
       
  1412 QSqlRecord QMYSQLDriver::record(const QString& tablename) const
       
  1413 {
       
  1414     QString table=tablename;
       
  1415     if(isIdentifierEscaped(table, QSqlDriver::TableName))
       
  1416         table = stripDelimiters(table, QSqlDriver::TableName);
       
  1417 
       
  1418     QSqlRecord info;
       
  1419     if (!isOpen())
       
  1420         return info;
       
  1421     MYSQL_RES* r = mysql_list_fields(d->mysql, table.toLocal8Bit().constData(), 0);
       
  1422     if (!r) {
       
  1423         return info;
       
  1424     }
       
  1425     MYSQL_FIELD* field;
       
  1426 
       
  1427     while ((field = mysql_fetch_field(r)))
       
  1428         info.append(qToField(field, d->tc));
       
  1429     mysql_free_result(r);
       
  1430     return info;
       
  1431 }
       
  1432 
       
  1433 QVariant QMYSQLDriver::handle() const
       
  1434 {
       
  1435     return qVariantFromValue(d->mysql);
       
  1436 }
       
  1437 
       
  1438 bool QMYSQLDriver::beginTransaction()
       
  1439 {
       
  1440 #ifndef CLIENT_TRANSACTIONS
       
  1441     return false;
       
  1442 #endif
       
  1443     if (!isOpen()) {
       
  1444         qWarning("QMYSQLDriver::beginTransaction: Database not open");
       
  1445         return false;
       
  1446     }
       
  1447     if (mysql_query(d->mysql, "BEGIN WORK")) {
       
  1448         setLastError(qMakeError(tr("Unable to begin transaction"),
       
  1449                                 QSqlError::StatementError, d));
       
  1450         return false;
       
  1451     }
       
  1452     return true;
       
  1453 }
       
  1454 
       
  1455 bool QMYSQLDriver::commitTransaction()
       
  1456 {
       
  1457 #ifndef CLIENT_TRANSACTIONS
       
  1458     return false;
       
  1459 #endif
       
  1460     if (!isOpen()) {
       
  1461         qWarning("QMYSQLDriver::commitTransaction: Database not open");
       
  1462         return false;
       
  1463     }
       
  1464     if (mysql_query(d->mysql, "COMMIT")) {
       
  1465         setLastError(qMakeError(tr("Unable to commit transaction"),
       
  1466                                 QSqlError::StatementError, d));
       
  1467         return false;
       
  1468     }
       
  1469     return true;
       
  1470 }
       
  1471 
       
  1472 bool QMYSQLDriver::rollbackTransaction()
       
  1473 {
       
  1474 #ifndef CLIENT_TRANSACTIONS
       
  1475     return false;
       
  1476 #endif
       
  1477     if (!isOpen()) {
       
  1478         qWarning("QMYSQLDriver::rollbackTransaction: Database not open");
       
  1479         return false;
       
  1480     }
       
  1481     if (mysql_query(d->mysql, "ROLLBACK")) {
       
  1482         setLastError(qMakeError(tr("Unable to rollback transaction"),
       
  1483                                 QSqlError::StatementError, d));
       
  1484         return false;
       
  1485     }
       
  1486     return true;
       
  1487 }
       
  1488 
       
  1489 QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
       
  1490 {
       
  1491     QString r;
       
  1492     if (field.isNull()) {
       
  1493         r = QLatin1String("NULL");
       
  1494     } else {
       
  1495         switch(field.type()) {
       
  1496         case QVariant::String:
       
  1497             // Escape '\' characters
       
  1498             r = QSqlDriver::formatValue(field, trimStrings);
       
  1499             r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
       
  1500             break;
       
  1501         case QVariant::ByteArray:
       
  1502             if (isOpen()) {
       
  1503                 const QByteArray ba = field.value().toByteArray();
       
  1504                 // buffer has to be at least length*2+1 bytes
       
  1505                 char* buffer = new char[ba.size() * 2 + 1];
       
  1506                 int escapedSize = int(mysql_real_escape_string(d->mysql, buffer,
       
  1507                                       ba.data(), ba.size()));
       
  1508                 r.reserve(escapedSize + 3);
       
  1509                 r.append(QLatin1Char('\'')).append(toUnicode(d->tc, buffer)).append(QLatin1Char('\''));
       
  1510                 delete[] buffer;
       
  1511                 break;
       
  1512             } else {
       
  1513                 qWarning("QMYSQLDriver::formatValue: Database not open");
       
  1514             }
       
  1515             // fall through
       
  1516         default:
       
  1517             r = QSqlDriver::formatValue(field, trimStrings);
       
  1518         }
       
  1519     }
       
  1520     return r;
       
  1521 }
       
  1522 
       
  1523 QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
       
  1524 {
       
  1525     QString res = identifier;
       
  1526     if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('`')) && !identifier.endsWith(QLatin1Char('`')) ) {
       
  1527         res.prepend(QLatin1Char('`')).append(QLatin1Char('`'));
       
  1528         res.replace(QLatin1Char('.'), QLatin1String("`.`"));
       
  1529     }
       
  1530     return res;
       
  1531 }
       
  1532 
       
  1533 bool QMYSQLDriver::isIdentifierEscapedImplementation(const QString &identifier, IdentifierType type) const
       
  1534 {
       
  1535     Q_UNUSED(type);
       
  1536     return identifier.size() > 2
       
  1537         && identifier.startsWith(QLatin1Char('`')) //left delimited
       
  1538         && identifier.endsWith(QLatin1Char('`')); //right delimited
       
  1539 }
       
  1540 
       
  1541 QT_END_NAMESPACE
       
  1542 
       
  1543 #include "qsql_mysql.moc"