~oif-team/ubuntu/natty/qt4-x11/xi2.1

« back to all changes in this revision

Viewing changes to src/sql/drivers/mysql/qsql_mysql.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-08-24 04:09:09 UTC
  • Revision ID: james.westby@ubuntu.com-20050824040909-xmxe9jfr4a0w5671
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
 
4
**
 
5
** This file is part of the sql module of the Qt Toolkit.
 
6
**
 
7
** This file may be distributed under the terms of the Q Public License
 
8
** as defined by Trolltech AS of Norway and appearing in the file
 
9
** LICENSE.QPL included in the packaging of this file.
 
10
**
 
11
** This file may be distributed and/or modified under the terms of the
 
12
** GNU General Public License version 2 as published by the Free Software
 
13
** Foundation and appearing in the file LICENSE.GPL included in the
 
14
** packaging of this file.
 
15
**
 
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
 
17
**   information about Qt Commercial License Agreements.
 
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
 
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
 
20
**
 
21
** Contact info@trolltech.com if any conditions of this licensing are
 
22
** not clear to you.
 
23
**
 
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
26
**
 
27
****************************************************************************/
 
28
 
 
29
#include "qsql_mysql.h"
 
30
 
 
31
#include <qcoreapplication.h>
 
32
#include <qvariant.h>
 
33
#include <qdatetime.h>
 
34
#include <qsqlerror.h>
 
35
#include <qsqlfield.h>
 
36
#include <qsqlindex.h>
 
37
#include <qsqlquery.h>
 
38
#include <qsqlrecord.h>
 
39
#include <qstringlist.h>
 
40
#include <qtextcodec.h>
 
41
#include <qvector.h>
 
42
 
 
43
#include <qdebug.h>
 
44
 
 
45
#ifdef Q_OS_WIN32
 
46
// comment the next line out if you want to use MySQL/embedded on Win32 systems.
 
47
// note that it will crash if you don't statically link to the mysql/e library!
 
48
# define Q_NO_MYSQL_EMBEDDED
 
49
#endif
 
50
 
 
51
Q_DECLARE_METATYPE(MYSQL_RES*)
 
52
Q_DECLARE_METATYPE(MYSQL*)
 
53
 
 
54
#if MYSQL_VERSION_ID >= 40108
 
55
Q_DECLARE_METATYPE(MYSQL_STMT*)
 
56
#endif
 
57
 
 
58
class QMYSQLDriverPrivate
 
59
{
 
60
public:
 
61
    QMYSQLDriverPrivate() : mysql(0), tc(0), preparedQuerys(false), preparedQuerysEnabled(false) {}
 
62
    MYSQL *mysql;
 
63
    QTextCodec *tc;
 
64
 
 
65
    bool preparedQuerys;
 
66
    bool preparedQuerysEnabled;
 
67
};
 
68
 
 
69
class QMYSQLResultPrivate : public QMYSQLDriverPrivate
 
70
{
 
71
public:
 
72
    QMYSQLResultPrivate() : QMYSQLDriverPrivate(), result(0), tc(QTextCodec::codecForLocale()),
 
73
        rowsAffected(0), hasBlobs(false)
 
74
#if MYSQL_VERSION_ID >= 40108
 
75
        , stmt(0), meta(0), inBinds(0), outBinds(0)
 
76
#endif
 
77
        {}
 
78
 
 
79
    MYSQL_RES *result;
 
80
    MYSQL_ROW row;
 
81
    QTextCodec *tc;
 
82
 
 
83
    int rowsAffected;
 
84
 
 
85
    bool bindInValues();
 
86
    void bindBlobs();
 
87
 
 
88
    bool hasBlobs;
 
89
    struct QMyField
 
90
    {
 
91
        QMyField()
 
92
            : outField(0), nullIndicator(false), bufLength(0ul),
 
93
              myField(0), type(QVariant::Invalid)
 
94
        {}
 
95
        char *outField;
 
96
        my_bool nullIndicator;
 
97
        ulong bufLength;
 
98
        MYSQL_FIELD *myField;
 
99
        QVariant::Type type;
 
100
    };
 
101
 
 
102
    QVector<QMyField> fields;
 
103
 
 
104
#if MYSQL_VERSION_ID >= 40108
 
105
    MYSQL_STMT* stmt;
 
106
    MYSQL_RES* meta;
 
107
 
 
108
    MYSQL_BIND *inBinds;
 
109
    MYSQL_BIND *outBinds;
 
110
#endif
 
111
};
 
112
 
 
113
static QTextCodec* codec(MYSQL* mysql)
 
114
{
 
115
#if MYSQL_VERSION_ID >= 32321
 
116
    QTextCodec* heuristicCodec = QTextCodec::codecForName(mysql_character_set_name(mysql));
 
117
    if (heuristicCodec)
 
118
        return heuristicCodec;
 
119
#endif
 
120
    return QTextCodec::codecForLocale();
 
121
}
 
122
 
 
123
static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
 
124
                            const QMYSQLDriverPrivate* p)
 
125
{
 
126
    const char *cerr = mysql_error(p->mysql);
 
127
    return QSqlError(QLatin1String("QMYSQL: ") + err,
 
128
                     p->tc ? p->tc->toUnicode(cerr) : QString::fromLatin1(cerr),
 
129
                     type, mysql_errno(p->mysql));
 
130
}
 
131
 
 
132
 
 
133
static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags)
 
134
{
 
135
    QVariant::Type type;
 
136
    switch (mysqltype) {
 
137
    case FIELD_TYPE_TINY :
 
138
    case FIELD_TYPE_SHORT :
 
139
    case FIELD_TYPE_LONG :
 
140
    case FIELD_TYPE_INT24 :
 
141
        type = (flags & UNSIGNED_FLAG) ? QVariant::UInt : QVariant::Int;
 
142
        break;
 
143
    case FIELD_TYPE_YEAR :
 
144
        type = QVariant::Int;
 
145
        break;
 
146
    case FIELD_TYPE_LONGLONG :
 
147
        type = (flags & UNSIGNED_FLAG) ? QVariant::ULongLong : QVariant::LongLong;
 
148
        break;
 
149
    case FIELD_TYPE_DECIMAL :
 
150
    case FIELD_TYPE_FLOAT :
 
151
    case FIELD_TYPE_DOUBLE :
 
152
        type = QVariant::Double;
 
153
        break;
 
154
    case FIELD_TYPE_DATE :
 
155
        type = QVariant::Date;
 
156
        break;
 
157
    case FIELD_TYPE_TIME :
 
158
        type = QVariant::Time;
 
159
        break;
 
160
    case FIELD_TYPE_DATETIME :
 
161
    case FIELD_TYPE_TIMESTAMP :
 
162
        type = QVariant::DateTime;
 
163
        break;
 
164
    case FIELD_TYPE_BLOB :
 
165
    case FIELD_TYPE_TINY_BLOB :
 
166
    case FIELD_TYPE_MEDIUM_BLOB :
 
167
    case FIELD_TYPE_LONG_BLOB :
 
168
        type = QVariant::ByteArray;
 
169
        break;
 
170
    default:
 
171
    case FIELD_TYPE_ENUM :
 
172
    case FIELD_TYPE_SET :
 
173
    case FIELD_TYPE_STRING :
 
174
    case FIELD_TYPE_VAR_STRING :
 
175
        type = QVariant::String;
 
176
        break;
 
177
    }
 
178
    return type;
 
179
}
 
180
 
 
181
static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc)
 
182
{
 
183
    QSqlField f(tc->toUnicode(field->name),
 
184
                qDecodeMYSQLType(int(field->type), field->flags));
 
185
    f.setRequired(IS_NOT_NULL(field->flags));
 
186
    f.setLength(field->length);
 
187
    f.setPrecision(field->decimals);
 
188
    f.setSqlType(field->type);
 
189
    return f;
 
190
}
 
191
 
 
192
#if MYSQL_VERSION_ID >= 40108
 
193
 
 
194
static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type,
 
195
                            MYSQL_STMT* stmt)
 
196
{
 
197
    const char *cerr = mysql_stmt_error(stmt);
 
198
    return QSqlError(QLatin1String("QMYSQL3: ") + err,
 
199
                     QString::fromLatin1(cerr),
 
200
                     type, mysql_stmt_errno(stmt));
 
201
}
 
202
 
 
203
static bool qIsBlob(int t)
 
204
{
 
205
    return t == MYSQL_TYPE_TINY_BLOB
 
206
           || t == MYSQL_TYPE_BLOB
 
207
           || t == MYSQL_TYPE_MEDIUM_BLOB
 
208
           || t == MYSQL_TYPE_LONG_BLOB;
 
209
}
 
210
 
 
211
void QMYSQLResultPrivate::bindBlobs()
 
212
{
 
213
    int i;
 
214
    MYSQL_FIELD *fieldInfo;
 
215
    MYSQL_BIND  *bind;
 
216
//    Q_ASSERT(meta);
 
217
 
 
218
    for(i = 0; i < fields.count(); ++i) {
 
219
        fieldInfo = fields.at(i).myField;
 
220
        if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) {
 
221
            bind = &inBinds[i];
 
222
            bind->buffer_length = fieldInfo->max_length;
 
223
            delete static_cast<char*>(bind->buffer);
 
224
            bind->buffer = new char[fieldInfo->max_length];
 
225
            fields[i].outField = static_cast<char*>(bind->buffer);
 
226
            bind->buffer_type = MYSQL_TYPE_STRING;
 
227
        }
 
228
    }
 
229
}
 
230
 
 
231
bool QMYSQLResultPrivate::bindInValues()
 
232
{
 
233
    MYSQL_BIND *bind;
 
234
    char *field;
 
235
    int i = 0;
 
236
 
 
237
    if (!meta)
 
238
        meta = mysql_stmt_result_metadata(stmt);
 
239
    if (!meta)
 
240
        return false;
 
241
 
 
242
    fields.resize(mysql_num_fields(meta));
 
243
 
 
244
    inBinds = new MYSQL_BIND[fields.size()];
 
245
 
 
246
    MYSQL_FIELD *fieldInfo;
 
247
 
 
248
    while((fieldInfo = mysql_fetch_field(meta))) {
 
249
        QMyField &f = fields[i];
 
250
        f.myField = fieldInfo;
 
251
        if (fieldInfo->type == FIELD_TYPE_DECIMAL)
 
252
            f.type = QVariant::String;
 
253
        else
 
254
            f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags);
 
255
 
 
256
        if (qIsBlob(fieldInfo->type)) {
 
257
            // the size of a blob-field is available as soon as we call
 
258
            // mysql_stmt_store_result()
 
259
            // after mysql_stmt_exec() in QMYSQLResult::exec()
 
260
            fieldInfo->length = 0;
 
261
            hasBlobs = true;
 
262
        } else {
 
263
            fieldInfo->type = MYSQL_TYPE_STRING;
 
264
        }
 
265
        bind = &inBinds[i];
 
266
        field = new char[fieldInfo->length + 1];
 
267
        memset(field, 0, fieldInfo->length + 1);
 
268
 
 
269
        bind->buffer_type = fieldInfo->type;
 
270
        bind->buffer = field;
 
271
        bind->buffer_length = f.bufLength = fieldInfo->length + 1;
 
272
        bind->is_null = &f.nullIndicator;
 
273
        bind->length = &f.bufLength;
 
274
        f.outField=field;
 
275
 
 
276
        ++i;
 
277
    }
 
278
    return true;
 
279
}
 
280
#endif
 
281
 
 
282
QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db)
 
283
: QSqlResult(db)
 
284
{
 
285
    d = new QMYSQLResultPrivate();
 
286
    d->mysql = db->d->mysql;
 
287
    d->tc = db->d->tc;
 
288
    d->preparedQuerysEnabled = db->d->preparedQuerysEnabled;
 
289
}
 
290
 
 
291
QMYSQLResult::~QMYSQLResult()
 
292
{
 
293
    cleanup();
 
294
    delete d;
 
295
}
 
296
 
 
297
QVariant QMYSQLResult::handle() const
 
298
{
 
299
#if MYSQL_VERSION_ID >= 40108
 
300
    return d->meta ? qVariantFromValue(d->meta) : qVariantFromValue(d->stmt);
 
301
#else
 
302
    return qVariantFromValue(d->result);
 
303
#endif
 
304
}
 
305
 
 
306
void QMYSQLResult::cleanup()
 
307
{
 
308
    if (d->result)
 
309
        mysql_free_result(d->result);
 
310
 
 
311
#if MYSQL_VERSION_ID >= 40108
 
312
    if (d->stmt) {
 
313
        if (mysql_stmt_close(d->stmt))
 
314
            qWarning("QMYSQLResult::cleanup: unable to free statement handle");
 
315
        d->stmt = 0;
 
316
    }
 
317
 
 
318
    if (d->meta) {
 
319
        mysql_free_result(d->meta);
 
320
        d->meta = 0;
 
321
    }
 
322
 
 
323
    int i;
 
324
    for (i = 0; i < d->fields.count(); ++i)
 
325
        delete[] d->fields[i].outField;
 
326
 
 
327
    if (d->outBinds) {
 
328
        delete[] d->outBinds;
 
329
        d->outBinds = 0;
 
330
    }
 
331
 
 
332
    if (d->inBinds) {
 
333
        delete[] d->inBinds;
 
334
        d->inBinds = 0;
 
335
    }
 
336
 
 
337
//    for(i = 0; i < d->outFields.size(); ++i)
 
338
//        delete[] d->outFields.at(i);
 
339
#endif
 
340
    d->hasBlobs = false;
 
341
    d->fields.clear();
 
342
    d->result = NULL;
 
343
    d->row = NULL;
 
344
    setAt(-1);
 
345
    setActive(false);
 
346
 
 
347
    d->preparedQuerys = d->preparedQuerysEnabled;
 
348
}
 
349
 
 
350
bool QMYSQLResult::fetch(int i)
 
351
{
 
352
    if (isForwardOnly()) { // fake a forward seek
 
353
        if (at() < i) {
 
354
            int x = i - at();
 
355
            while (--x && fetchNext());
 
356
            return fetchNext();
 
357
        } else {
 
358
            return false;
 
359
        }
 
360
    }
 
361
    if (at() == i)
 
362
        return true;
 
363
    if (d->preparedQuerys) {
 
364
#if MYSQL_VERSION_ID >= 40108
 
365
        mysql_stmt_data_seek(d->stmt, i);
 
366
 
 
367
        if (mysql_stmt_fetch(d->stmt)) {
 
368
            setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
369
                         "Unable to fetch data"), QSqlError::StatementError, d->stmt));
 
370
            return false;
 
371
        }
 
372
#else
 
373
        return false;
 
374
#endif
 
375
    } else {
 
376
        mysql_data_seek(d->result, i);
 
377
        d->row = mysql_fetch_row(d->result);
 
378
        if (!d->row)
 
379
            return false;
 
380
    }
 
381
 
 
382
    setAt(i);
 
383
    return true;
 
384
}
 
385
 
 
386
bool QMYSQLResult::fetchNext()
 
387
{
 
388
    if (d->preparedQuerys) {
 
389
#if MYSQL_VERSION_ID >= 40108
 
390
        if (mysql_stmt_fetch(d->stmt))
 
391
            return false;
 
392
#else
 
393
        return false;
 
394
#endif
 
395
    } else {
 
396
    d->row = mysql_fetch_row(d->result);
 
397
    if (!d->row)
 
398
        return false;
 
399
    }
 
400
    setAt(at() + 1);
 
401
    return true;
 
402
}
 
403
 
 
404
bool QMYSQLResult::fetchLast()
 
405
{
 
406
    if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries
 
407
        bool success = fetchNext(); // did we move at all?
 
408
        while (fetchNext());
 
409
        return success;
 
410
    }
 
411
 
 
412
    my_ulonglong numRows;
 
413
    if (d->preparedQuerys) {
 
414
#if MYSQL_VERSION_ID >= 40108
 
415
        numRows = mysql_stmt_num_rows(d->stmt);
 
416
#else
 
417
        numRows = 0;
 
418
#endif
 
419
    } else {
 
420
        numRows = mysql_num_rows(d->result);
 
421
    }
 
422
    if (at() == int(numRows))
 
423
        return true;
 
424
    if (!numRows)
 
425
        return false;
 
426
    return fetch(numRows - 1);
 
427
}
 
428
 
 
429
bool QMYSQLResult::fetchFirst()
 
430
{
 
431
    if (at() == 0)
 
432
        return true;
 
433
 
 
434
    if (isForwardOnly())
 
435
        return (at() == QSql::BeforeFirstRow) ? fetchNext() : false;
 
436
    return fetch(0);
 
437
}
 
438
 
 
439
QVariant QMYSQLResult::data(int field)
 
440
{
 
441
 
 
442
    if (!isSelect() || field >= d->fields.count()) {
 
443
        qWarning("QMYSQLResult::data: column %d out of range", field);
 
444
        return QVariant();
 
445
    }
 
446
 
 
447
    const QMYSQLResultPrivate::QMyField &f = d->fields.at(field);
 
448
    QString val;
 
449
    if (d->preparedQuerys) {
 
450
        if (f.nullIndicator)
 
451
            return QVariant(f.type);
 
452
 
 
453
        if (f.type != QVariant::ByteArray)
 
454
            val = d->tc->toUnicode(f.outField);
 
455
    } else {
 
456
        if (d->row[field] == NULL) {
 
457
            // NULL value
 
458
            return QVariant(f.type);
 
459
        }
 
460
        if (f.type != QVariant::ByteArray)
 
461
            val = d->tc->toUnicode(d->row[field]);
 
462
    }
 
463
 
 
464
    switch(f.type) {
 
465
    case QVariant::LongLong:
 
466
        return QVariant(val.toLongLong());
 
467
    case QVariant::ULongLong:
 
468
        return QVariant(val.toULongLong());
 
469
    case QVariant::Int:
 
470
        return QVariant(val.toInt());
 
471
    case QVariant::UInt:
 
472
        return QVariant(val.toUInt());
 
473
    case QVariant::Double:
 
474
        return QVariant(val.toDouble());
 
475
    case QVariant::Date:
 
476
        if (val.isEmpty()) {
 
477
            return QVariant(QDate());
 
478
        } else {
 
479
            return QVariant(QDate::fromString(val, Qt::ISODate) );
 
480
        }
 
481
    case QVariant::Time:
 
482
        if (val.isEmpty()) {
 
483
            return QVariant(QTime());
 
484
        } else {
 
485
            return QVariant(QTime::fromString(val, Qt::ISODate));
 
486
        }
 
487
    case QVariant::DateTime:
 
488
        if (val.isEmpty())
 
489
            return QVariant(QDateTime());
 
490
        if (val.length() == 14)
 
491
            // TIMESTAMPS have the format yyyyMMddhhmmss
 
492
            val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10,
 
493
                    QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':'));
 
494
        return QVariant(QDateTime::fromString(val, Qt::ISODate));
 
495
    case QVariant::ByteArray: {
 
496
 
 
497
        QByteArray ba;
 
498
        if (d->preparedQuerys) {
 
499
            ba = QByteArray(f.outField, f.bufLength);
 
500
        } else {
 
501
            unsigned long* fl = mysql_fetch_lengths(d->result);
 
502
            ba = QByteArray(d->row[field], fl[field]);
 
503
        }
 
504
        return QVariant(ba);
 
505
    }
 
506
    default:
 
507
    case QVariant::String:
 
508
        return QVariant(val);
 
509
    }
 
510
    qWarning("QMYSQLResult::data: unknown data type");
 
511
    return QVariant();
 
512
}
 
513
 
 
514
bool QMYSQLResult::isNull(int field)
 
515
{
 
516
   if (d->preparedQuerys)
 
517
       return d->fields.at(field).nullIndicator;
 
518
   else
 
519
       return d->row[field] == NULL;
 
520
}
 
521
 
 
522
bool QMYSQLResult::reset (const QString& query)
 
523
{
 
524
    if (!driver() || !driver()->isOpen() || driver()->isOpenError())
 
525
        return false;
 
526
 
 
527
    cleanup();
 
528
    d->preparedQuerys = false;
 
529
 
 
530
    const QByteArray encQuery(d->tc->fromUnicode(query));
 
531
    if (mysql_real_query(d->mysql, encQuery.data(), encQuery.length())) {
 
532
        setLastError(qMakeError(QCoreApplication::tr("QMYSQLResult", "Unable to execute query"),
 
533
                     QSqlError::StatementError, d));
 
534
        return false;
 
535
    }
 
536
    d->result = mysql_store_result(d->mysql);
 
537
    if (!d->result && mysql_field_count(d->mysql) > 0) {
 
538
        setLastError(qMakeError(QCoreApplication::tr("QMYSQLResult", "Unable to store result"),
 
539
                    QSqlError::StatementError, d));
 
540
        return false;
 
541
    }
 
542
    int numFields = mysql_field_count(d->mysql);
 
543
    setSelect(numFields != 0);
 
544
    d->fields.resize(numFields);
 
545
    d->rowsAffected = mysql_affected_rows(d->mysql);
 
546
    if (isSelect()) {
 
547
        for(int i = 0; i < numFields; i++) {
 
548
            MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
 
549
            if (field->type == FIELD_TYPE_DECIMAL)
 
550
                d->fields[i].type = QVariant::String;
 
551
            else
 
552
                d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
 
553
        }
 
554
    }
 
555
    setActive(true);
 
556
    return true;
 
557
}
 
558
 
 
559
int QMYSQLResult::size()
 
560
{
 
561
    if (isSelect())
 
562
        if (d->preparedQuerys)
 
563
#if MYSQL_VERSION_ID >= 40108
 
564
            return int(mysql_stmt_num_rows(d->stmt));
 
565
#else
 
566
            return -1;
 
567
#endif
 
568
        else
 
569
            return int(mysql_num_rows(d->result));
 
570
    else
 
571
        return -1;
 
572
}
 
573
 
 
574
int QMYSQLResult::numRowsAffected()
 
575
{
 
576
    return d->rowsAffected;
 
577
}
 
578
 
 
579
QVariant QMYSQLResult::lastInsertId() const
 
580
{
 
581
    if (!isActive())
 
582
        return QVariant();
 
583
 
 
584
    if (d->preparedQuerys) {
 
585
#if MYSQL_VERSION_ID >= 40108
 
586
        quint64 id = mysql_stmt_insert_id(d->stmt);
 
587
        if (id)
 
588
            return QVariant(id);
 
589
#endif
 
590
    } else {
 
591
        quint64 id = mysql_insert_id(d->mysql);
 
592
        if (id)
 
593
            return QVariant(id);
 
594
    }
 
595
    return QVariant();
 
596
}
 
597
 
 
598
QSqlRecord QMYSQLResult::record() const
 
599
{
 
600
    QSqlRecord info;
 
601
    MYSQL_RES *res;
 
602
    if (!isActive() || !isSelect())
 
603
        return info;
 
604
 
 
605
#if MYSQL_VERSION_ID >= 40108
 
606
    res = d->preparedQuerys ? d->meta : d->result;
 
607
#else
 
608
    res = d->result;
 
609
#endif
 
610
 
 
611
    if (!mysql_errno(d->mysql)) {
 
612
        mysql_field_seek(res, 0);
 
613
        MYSQL_FIELD* field = mysql_fetch_field(res);
 
614
        while(field) {
 
615
            info.append(qToField(field, d->tc));
 
616
            field = mysql_fetch_field(res);
 
617
        }
 
618
    }
 
619
    mysql_field_seek(res, 0);
 
620
    return info;
 
621
}
 
622
 
 
623
 
 
624
#if MYSQL_VERSION_ID >= 40108
 
625
 
 
626
static MYSQL_TIME *toMySqlDate(QDate date, QTime time, QVariant::Type type)
 
627
{
 
628
    Q_ASSERT(type == QVariant::Time || type == QVariant::Date
 
629
             || type == QVariant::DateTime);
 
630
 
 
631
    MYSQL_TIME *myTime = new MYSQL_TIME;
 
632
    memset(myTime, 0, sizeof(MYSQL_TIME));
 
633
 
 
634
    if (type == QVariant::Time || type == QVariant::DateTime) {
 
635
        myTime->hour = time.hour();
 
636
        myTime->minute = time.minute();
 
637
        myTime->second = time.second();
 
638
        myTime->second_part = time.msec();
 
639
    }
 
640
    if (type == QVariant::Date || type == QVariant::DateTime) {
 
641
        myTime->year = date.year();
 
642
        myTime->month = date.month();
 
643
        myTime->day = date.day();
 
644
    }
 
645
 
 
646
    return myTime;
 
647
}
 
648
 
 
649
bool QMYSQLResult::prepare(const QString& query)
 
650
{
 
651
    cleanup();
 
652
    if (!d->preparedQuerys)
 
653
        return QSqlResult::prepare(query);
 
654
 
 
655
    int r;
 
656
 
 
657
    if (query.isEmpty())
 
658
        return false;
 
659
 
 
660
    if (!d->stmt)
 
661
        d->stmt = mysql_stmt_init(d->mysql);
 
662
    if (!d->stmt) {
 
663
        setLastError(qMakeError(QCoreApplication::tr("QMYSQLResult", "Unable to prepare statement"),
 
664
                     QSqlError::StatementError, d));
 
665
        return false;
 
666
    }
 
667
 
 
668
    const QByteArray encQuery(d->tc->fromUnicode(query));
 
669
    r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length());
 
670
    if (r != 0) {
 
671
        setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
672
                     "Unable to prepare statement"), QSqlError::StatementError, d->stmt));
 
673
        cleanup();
 
674
        return false;
 
675
    }
 
676
 
 
677
    if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues
 
678
        d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)];
 
679
    }
 
680
 
 
681
    setSelect(d->bindInValues());
 
682
    return true;
 
683
}
 
684
 
 
685
bool QMYSQLResult::exec()
 
686
{
 
687
    if (!d->preparedQuerys)
 
688
        return QSqlResult::exec();
 
689
    if (!d->stmt)
 
690
        return false;
 
691
 
 
692
    int r = 0;
 
693
    MYSQL_BIND* currBind;
 
694
    QVector<MYSQL_TIME *> timeVector;
 
695
    QVector<QByteArray> stringVector;
 
696
    QVector<my_bool> nullVector;
 
697
 
 
698
    const QVector<QVariant> values = boundValues();
 
699
 
 
700
    r = mysql_stmt_reset(d->stmt);
 
701
    if (r != 0) {
 
702
        setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
703
                     "Unable to reset statement"), QSqlError::StatementError, d->stmt));
 
704
        return false;
 
705
    }
 
706
 
 
707
    if (mysql_stmt_param_count(d->stmt) > 0 &&
 
708
        mysql_stmt_param_count(d->stmt) == (uint)values.count()) {
 
709
 
 
710
        nullVector.resize(values.count());
 
711
        for (int i = 0; i < values.count(); ++i) {
 
712
            const QVariant &val = boundValues().at(i);
 
713
            void *data = const_cast<void *>(val.constData());
 
714
 
 
715
            currBind = &d->outBinds[i];
 
716
 
 
717
            nullVector[i] = static_cast<my_bool>(val.isNull());
 
718
            currBind->is_null = &nullVector[i];
 
719
            currBind->length = 0;
 
720
 
 
721
            switch (val.type()) {
 
722
                case QVariant::ByteArray:
 
723
                currBind->buffer_type = MYSQL_TYPE_BLOB;
 
724
                currBind->buffer = const_cast<char *>(val.toByteArray().constData());
 
725
                currBind->buffer_length = val.toByteArray().size();
 
726
                break;
 
727
 
 
728
                case QVariant::Time:
 
729
                case QVariant::Date:
 
730
                case QVariant::DateTime: {
 
731
                    MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.type());
 
732
                    timeVector.append(myTime);
 
733
 
 
734
                    currBind->buffer = myTime;
 
735
                    switch(val.type()) {
 
736
                    case QVariant::Time:
 
737
                        currBind->buffer_type = MYSQL_TYPE_TIME;
 
738
                        myTime->time_type = MYSQL_TIMESTAMP_TIME;
 
739
                        break;
 
740
                    case QVariant::Date:
 
741
                        currBind->buffer_type = MYSQL_TYPE_DATE;
 
742
                        myTime->time_type = MYSQL_TIMESTAMP_DATE;
 
743
                        break;
 
744
                    case QVariant::DateTime:
 
745
                        currBind->buffer_type = MYSQL_TYPE_DATETIME;
 
746
                        myTime->time_type = MYSQL_TIMESTAMP_DATETIME;
 
747
                        break;
 
748
                    default:
 
749
                        break;
 
750
                    }
 
751
                    currBind->buffer_length = sizeof(MYSQL_TIME);
 
752
                    currBind->length = 0;
 
753
                    break; }
 
754
                case QVariant::UInt:
 
755
                case QVariant::Int:
 
756
                    currBind->buffer_type =  MYSQL_TYPE_LONG;
 
757
                    currBind->buffer = data;
 
758
                    currBind->buffer_length = sizeof(uint);
 
759
                    currBind->is_unsigned = (val.type() == QVariant::UInt);
 
760
                    break;
 
761
                case QVariant::Double:
 
762
                    currBind->buffer_type =  MYSQL_TYPE_DOUBLE;
 
763
                    currBind->buffer = data;
 
764
                    currBind->buffer_length = sizeof(double);
 
765
                    currBind->is_unsigned = 0;
 
766
                    break;
 
767
                case QVariant::LongLong:
 
768
                case QVariant::ULongLong:
 
769
                    currBind->buffer_type =  MYSQL_TYPE_LONGLONG;
 
770
                    currBind->buffer = data;
 
771
                    currBind->buffer_length = sizeof(qint64);
 
772
                    currBind->is_unsigned = (val.type() == QVariant::ULongLong);
 
773
                    break;
 
774
                case QVariant::String:
 
775
                default: {
 
776
                    QByteArray ba = d->tc->fromUnicode(val.toString());
 
777
                    stringVector.append(ba);
 
778
                    currBind->buffer_type =  MYSQL_TYPE_STRING;
 
779
                    currBind->buffer = const_cast<char *>(ba.constData());
 
780
                    currBind->buffer_length = ba.length();
 
781
                    currBind->is_unsigned = 0;
 
782
                    break; }
 
783
            }
 
784
        }
 
785
 
 
786
        r = mysql_stmt_bind_param(d->stmt, d->outBinds);
 
787
        if (r != 0) {
 
788
            setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
789
                         "Unable to bind value"), QSqlError::StatementError, d->stmt));
 
790
            qDeleteAll(timeVector);
 
791
            return false;
 
792
        }
 
793
    }
 
794
    r = mysql_stmt_execute(d->stmt);
 
795
 
 
796
    qDeleteAll(timeVector);
 
797
 
 
798
    if (r != 0) {
 
799
        setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
800
                     "Unable to execute statement"), QSqlError::StatementError, d->stmt));
 
801
        return false;
 
802
    }
 
803
    //if there is meta-data there is also data
 
804
    setSelect(d->meta);
 
805
 
 
806
    d->rowsAffected = mysql_stmt_affected_rows(d->stmt);
 
807
 
 
808
    if (isSelect()) {
 
809
        my_bool update_max_length = true;
 
810
 
 
811
        r = mysql_stmt_bind_result(d->stmt, d->inBinds);
 
812
        if (r != 0) {
 
813
            setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
814
                         "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
 
815
            return false;
 
816
        }
 
817
        if (d->hasBlobs)
 
818
            mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length);
 
819
 
 
820
        r = mysql_stmt_store_result(d->stmt);
 
821
        if (r != 0) {
 
822
            setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
823
                         "Unable to store statement results"), QSqlError::StatementError, d->stmt));
 
824
            return false;
 
825
        }
 
826
 
 
827
        if (d->hasBlobs) {
 
828
            // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes
 
829
            // when called without a preceding call to mysql_stmt_bind_result()
 
830
            // in versions < 4.1.8
 
831
            d->bindBlobs();
 
832
            r = mysql_stmt_bind_result(d->stmt, d->inBinds);
 
833
            if (r != 0) {
 
834
                setLastError(qMakeStmtError(QCoreApplication::tr("QMYSQLResult",
 
835
                             "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
 
836
                return false;
 
837
            }
 
838
        }
 
839
        setAt(QSql::BeforeFirstRow);
 
840
    }
 
841
    setActive(true);
 
842
    return true;
 
843
}
 
844
#endif
 
845
/////////////////////////////////////////////////////////
 
846
 
 
847
static void qServerInit()
 
848
{
 
849
#ifndef Q_NO_MYSQL_EMBEDDED
 
850
# if MYSQL_VERSION_ID >= 40000
 
851
    static bool init = false;
 
852
    if (init)
 
853
        return;
 
854
 
 
855
    // this should only be called once
 
856
    // has no effect on client/server library
 
857
    // but is vital for the embedded lib
 
858
    if (mysql_server_init(0, 0, 0)) {
 
859
        qWarning("QMYSQLDriver::qServerInit: unable to start server.");
 
860
    }
 
861
    init = true;
 
862
# endif // MYSQL_VERSION_ID
 
863
#endif // Q_NO_MYSQL_EMBEDDED
 
864
}
 
865
 
 
866
QMYSQLDriver::QMYSQLDriver(QObject * parent)
 
867
    : QSqlDriver(parent)
 
868
{
 
869
    init();
 
870
    qServerInit();
 
871
}
 
872
 
 
873
/*!
 
874
    Create a driver instance with the open connection handle, \a con.
 
875
    The instance's parent (owner) is \a parent.
 
876
*/
 
877
 
 
878
QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent)
 
879
    : QSqlDriver(parent)
 
880
{
 
881
    init();
 
882
    if (con) {
 
883
        d->mysql = (MYSQL *) con;
 
884
        d->tc = codec(con);
 
885
        setOpen(true);
 
886
        setOpenError(false);
 
887
    } else {
 
888
        qServerInit();
 
889
    }
 
890
}
 
891
 
 
892
void QMYSQLDriver::init()
 
893
{
 
894
    d = new QMYSQLDriverPrivate();
 
895
    d->mysql = 0;
 
896
}
 
897
 
 
898
QMYSQLDriver::~QMYSQLDriver()
 
899
{
 
900
    delete d;
 
901
#ifndef Q_NO_MYSQL_EMBEDDED
 
902
# if MYSQL_VERSION_ID > 40000
 
903
    mysql_server_end();
 
904
# endif
 
905
#endif
 
906
}
 
907
 
 
908
bool QMYSQLDriver::hasFeature(DriverFeature f) const
 
909
{
 
910
    switch (f) {
 
911
    case Transactions:
 
912
// CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34
 
913
#ifdef CLIENT_TRANSACTIONS
 
914
        if (d->mysql) {
 
915
            if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS)
 
916
                return true;
 
917
        }
 
918
#endif
 
919
        return false;
 
920
    case QuerySize:
 
921
    case BLOB:
 
922
    case LastInsertId:
 
923
        return true;
 
924
    case Unicode:
 
925
        return false;
 
926
#if MYSQL_VERSION_ID >= 40108
 
927
    case PreparedQueries:
 
928
    case PositionalPlaceholders:
 
929
        return d->preparedQuerysEnabled;
 
930
#endif
 
931
    default:
 
932
        return false;
 
933
    }
 
934
}
 
935
 
 
936
static void setOptionFlag(uint &optionFlags, const QString &opt)
 
937
{
 
938
    if (opt == QLatin1String("CLIENT_COMPRESS"))
 
939
        optionFlags |= CLIENT_COMPRESS;
 
940
    else if (opt == QLatin1String("CLIENT_FOUND_ROWS"))
 
941
        optionFlags |= CLIENT_FOUND_ROWS;
 
942
    else if (opt == QLatin1String("CLIENT_IGNORE_SPACE"))
 
943
        optionFlags |= CLIENT_IGNORE_SPACE;
 
944
    else if (opt == QLatin1String("CLIENT_INTERACTIVE"))
 
945
        optionFlags |= CLIENT_INTERACTIVE;
 
946
    else if (opt == QLatin1String("CLIENT_NO_SCHEMA"))
 
947
        optionFlags |= CLIENT_NO_SCHEMA;
 
948
    else if (opt == QLatin1String("CLIENT_ODBC"))
 
949
        optionFlags |= CLIENT_ODBC;
 
950
    else if (opt == QLatin1String("CLIENT_SSL"))
 
951
        optionFlags |= CLIENT_SSL;
 
952
    else
 
953
        qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData());
 
954
}
 
955
 
 
956
bool QMYSQLDriver::open(const QString& db,
 
957
                         const QString& user,
 
958
                         const QString& password,
 
959
                         const QString& host,
 
960
                         int port,
 
961
                         const QString& connOpts)
 
962
{
 
963
    if (isOpen())
 
964
        close();
 
965
 
 
966
    unsigned int optionFlags = 0;
 
967
    const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
 
968
 
 
969
    // extract the real options from the string
 
970
    for (int i = 0; i < opts.count(); ++i) {
 
971
        QString tmp(opts.at(i).simplified());
 
972
        int idx;
 
973
        if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) {
 
974
            QString val(tmp.mid(idx + 1).simplified());
 
975
            if (val == QLatin1String("TRUE") || val == QLatin1String("1"))
 
976
                setOptionFlag(optionFlags, tmp.left(idx).simplified());
 
977
            else
 
978
                qWarning("QMYSQLDriver::open: Illegal connect option value '%s'",
 
979
                         tmp.toLocal8Bit().constData());
 
980
        } else {
 
981
            setOptionFlag(optionFlags, tmp);
 
982
        }
 
983
    }
 
984
 
 
985
    if ((d->mysql = mysql_init((MYSQL*) 0)) &&
 
986
            mysql_real_connect(d->mysql,
 
987
                               host.toLocal8Bit().constData(),
 
988
                               user.toLocal8Bit().constData(),
 
989
                               password.toLocal8Bit().constData(),
 
990
                               db.isNull() ? "" : db.toLocal8Bit().constData(),
 
991
                               (port > -1) ? port : 0,
 
992
                               NULL,
 
993
                               optionFlags))
 
994
    {
 
995
        if (mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
 
996
            setLastError(qMakeError(tr("Unable open database '") + db +
 
997
                        QLatin1Char('\''), QSqlError::ConnectionError, d));
 
998
            mysql_close(d->mysql);
 
999
            setOpenError(true);
 
1000
            return false;
 
1001
        }
 
1002
    } else {
 
1003
            setLastError(qMakeError(tr("Unable to connect"),
 
1004
                                    QSqlError::ConnectionError, d));
 
1005
            mysql_close(d->mysql);
 
1006
            setOpenError(true);
 
1007
            return false;
 
1008
    }
 
1009
    d->tc = codec(d->mysql);
 
1010
 
 
1011
#if MYSQL_VERSION_ID >= 40108
 
1012
    d->preparedQuerysEnabled = mysql_get_client_version() >= 40108
 
1013
                        && mysql_get_server_version(d->mysql) >= 40100;
 
1014
#else
 
1015
    d->preparedQuerysEnabled = false;
 
1016
#endif
 
1017
 
 
1018
    setOpen(true);
 
1019
    setOpenError(false);
 
1020
    return true;
 
1021
}
 
1022
 
 
1023
void QMYSQLDriver::close()
 
1024
{
 
1025
    if (isOpen()) {
 
1026
        mysql_close(d->mysql);
 
1027
        setOpen(false);
 
1028
        setOpenError(false);
 
1029
    }
 
1030
}
 
1031
 
 
1032
QSqlResult *QMYSQLDriver::createResult() const
 
1033
{
 
1034
    return new QMYSQLResult(this);
 
1035
}
 
1036
 
 
1037
QStringList QMYSQLDriver::tables(QSql::TableType type) const
 
1038
{
 
1039
    QStringList tl;
 
1040
    if (!isOpen())
 
1041
        return tl;
 
1042
    if (!(type & QSql::Tables))
 
1043
        return tl;
 
1044
 
 
1045
    MYSQL_RES* tableRes = mysql_list_tables(d->mysql, NULL);
 
1046
    MYSQL_ROW row;
 
1047
    int i = 0;
 
1048
    while (tableRes) {
 
1049
        mysql_data_seek(tableRes, i);
 
1050
        row = mysql_fetch_row(tableRes);
 
1051
        if (!row)
 
1052
            break;
 
1053
        tl.append(d->tc->toUnicode(row[0]));
 
1054
        i++;
 
1055
    }
 
1056
    mysql_free_result(tableRes);
 
1057
    return tl;
 
1058
}
 
1059
 
 
1060
QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const
 
1061
{
 
1062
    QSqlIndex idx;
 
1063
    bool prepQ;
 
1064
    if (!isOpen())
 
1065
        return idx;
 
1066
 
 
1067
    prepQ = d->preparedQuerys;
 
1068
    d->preparedQuerys = false;
 
1069
 
 
1070
    QSqlQuery i(createResult());
 
1071
    QString stmt(QLatin1String("show index from %1;"));
 
1072
    QSqlRecord fil = record(tablename);
 
1073
    i.exec(stmt.arg(tablename));
 
1074
    while (i.isActive() && i.next()) {
 
1075
        if (i.value(2).toString() == QLatin1String("PRIMARY")) {
 
1076
            idx.append(fil.field(i.value(4).toString()));
 
1077
            idx.setCursorName(i.value(0).toString());
 
1078
            idx.setName(i.value(2).toString());
 
1079
        }
 
1080
    }
 
1081
 
 
1082
    d->preparedQuerys = prepQ;
 
1083
    return idx;
 
1084
}
 
1085
 
 
1086
QSqlRecord QMYSQLDriver::record(const QString& tablename) const
 
1087
{
 
1088
    QSqlRecord info;
 
1089
    if (!isOpen())
 
1090
        return info;
 
1091
    MYSQL_RES* r = mysql_list_fields(d->mysql, tablename.toLocal8Bit().constData(), 0);
 
1092
    if (!r) {
 
1093
        return info;
 
1094
    }
 
1095
    MYSQL_FIELD* field;
 
1096
    while ((field = mysql_fetch_field(r)))
 
1097
        info.append(qToField(field, d->tc));
 
1098
    mysql_free_result(r);
 
1099
    return info;
 
1100
}
 
1101
 
 
1102
QVariant QMYSQLDriver::handle() const
 
1103
{
 
1104
    return qVariantFromValue(d->mysql);
 
1105
}
 
1106
 
 
1107
bool QMYSQLDriver::beginTransaction()
 
1108
{
 
1109
#ifndef CLIENT_TRANSACTIONS
 
1110
    return false;
 
1111
#endif
 
1112
    if (!isOpen()) {
 
1113
        qWarning("QMYSQLDriver::beginTransaction: Database not open");
 
1114
        return false;
 
1115
    }
 
1116
    if (mysql_query(d->mysql, "BEGIN WORK")) {
 
1117
        setLastError(qMakeError(tr("Unable to begin transaction"),
 
1118
                                QSqlError::StatementError, d));
 
1119
        return false;
 
1120
    }
 
1121
    return true;
 
1122
}
 
1123
 
 
1124
bool QMYSQLDriver::commitTransaction()
 
1125
{
 
1126
#ifndef CLIENT_TRANSACTIONS
 
1127
    return false;
 
1128
#endif
 
1129
    if (!isOpen()) {
 
1130
        qWarning("QMYSQLDriver::commitTransaction: Database not open");
 
1131
        return false;
 
1132
    }
 
1133
    if (mysql_query(d->mysql, "COMMIT")) {
 
1134
        setLastError(qMakeError(tr("Unable to commit transaction"),
 
1135
                                QSqlError::StatementError, d));
 
1136
        return false;
 
1137
    }
 
1138
    return true;
 
1139
}
 
1140
 
 
1141
bool QMYSQLDriver::rollbackTransaction()
 
1142
{
 
1143
#ifndef CLIENT_TRANSACTIONS
 
1144
    return false;
 
1145
#endif
 
1146
    if (!isOpen()) {
 
1147
        qWarning("QMYSQLDriver::rollbackTransaction: Database not open");
 
1148
        return false;
 
1149
    }
 
1150
    if (mysql_query(d->mysql, "ROLLBACK")) {
 
1151
        setLastError(qMakeError(tr("Unable to rollback transaction"),
 
1152
                                QSqlError::StatementError, d));
 
1153
        return false;
 
1154
    }
 
1155
    return true;
 
1156
}
 
1157
 
 
1158
QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
 
1159
{
 
1160
    QString r;
 
1161
    if (field.isNull()) {
 
1162
        r = QLatin1String("NULL");
 
1163
    } else {
 
1164
        switch(field.type()) {
 
1165
        case QVariant::ByteArray: {
 
1166
 
 
1167
            const QByteArray ba = field.value().toByteArray();
 
1168
            // buffer has to be at least length*2+1 bytes
 
1169
            char* buffer = new char[ba.size() * 2 + 1];
 
1170
            int escapedSize = int(mysql_escape_string(buffer, ba.data(), ba.size()));
 
1171
            r.reserve(escapedSize + 3);
 
1172
            r.append(QLatin1Char('\'')).append(d->tc->toUnicode(buffer)).append(QLatin1Char('\''));
 
1173
            delete[] buffer;
 
1174
        }
 
1175
        break;
 
1176
        case QVariant::String:
 
1177
            // Escape '\' characters
 
1178
            r = QSqlDriver::formatValue(field, trimStrings);
 
1179
            r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
 
1180
            break;
 
1181
        default:
 
1182
            r = QSqlDriver::formatValue(field, trimStrings);
 
1183
        }
 
1184
    }
 
1185
    return r;
 
1186
}