Changeset View
Changeset View
Standalone View
Standalone View
kmymoney/plugins/sqlcipher/QSQLite/5.10/qsql_sqlite.cpp
- This file was added.
1 | //krazy:skip | ||||
---|---|---|---|---|---|
2 | /**************************************************************************** | ||||
3 | ** | ||||
4 | ** Copyright (C) 2016 The Qt Company Ltd. | ||||
5 | ** Contact: https://www.qt.io/licensing/ | ||||
6 | ** | ||||
7 | ** This file is part of the QtSql module of the Qt Toolkit. | ||||
8 | ** | ||||
9 | ** $QT_BEGIN_LICENSE:LGPL$ | ||||
10 | ** Commercial License Usage | ||||
11 | ** Licensees holding valid commercial Qt licenses may use this file in | ||||
12 | ** accordance with the commercial license agreement provided with the | ||||
13 | ** Software or, alternatively, in accordance with the terms contained in | ||||
14 | ** a written agreement between you and The Qt Company. For licensing terms | ||||
15 | ** and conditions see https://www.qt.io/terms-conditions. For further | ||||
16 | ** information use the contact form at https://www.qt.io/contact-us. | ||||
17 | ** | ||||
18 | ** GNU Lesser General Public License Usage | ||||
19 | ** Alternatively, this file may be used under the terms of the GNU Lesser | ||||
20 | ** General Public License version 3 as published by the Free Software | ||||
21 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | ||||
22 | ** packaging of this file. Please review the following information to | ||||
23 | ** ensure the GNU Lesser General Public License version 3 requirements | ||||
24 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | ||||
25 | ** | ||||
26 | ** GNU General Public License Usage | ||||
27 | ** Alternatively, this file may be used under the terms of the GNU | ||||
28 | ** General Public License version 2.0 or (at your option) the GNU General | ||||
29 | ** Public license version 3 or any later version approved by the KDE Free | ||||
30 | ** Qt Foundation. The licenses are as published by the Free Software | ||||
31 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | ||||
32 | ** included in the packaging of this file. Please review the following | ||||
33 | ** information to ensure the GNU General Public License requirements will | ||||
34 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | ||||
35 | ** https://www.gnu.org/licenses/gpl-3.0.html. | ||||
36 | ** | ||||
37 | ** $QT_END_LICENSE$ | ||||
38 | ** | ||||
39 | ****************************************************************************/ | ||||
40 | | ||||
41 | #include "qsql_sqlite_p.h" | ||||
42 | | ||||
43 | #include <qcoreapplication.h> | ||||
44 | #include <qdatetime.h> | ||||
45 | #include <qvariant.h> | ||||
46 | #include <qsqlerror.h> | ||||
47 | #include <qsqlfield.h> | ||||
48 | #include <qsqlindex.h> | ||||
49 | #include <qsqlquery.h> | ||||
50 | #include <QtSql/private/qsqlcachedresult_p.h> | ||||
51 | #include <QtSql/private/qsqldriver_p.h> | ||||
52 | #include <qstringlist.h> | ||||
53 | #include <qvector.h> | ||||
54 | #include <qdebug.h> | ||||
55 | #ifndef QT_NO_REGULAREXPRESSION | ||||
56 | #include <qcache.h> | ||||
57 | #include <qregularexpression.h> | ||||
58 | #endif | ||||
59 | #include <QTimeZone> | ||||
60 | | ||||
61 | #if defined Q_OS_WIN | ||||
62 | # include <qt_windows.h> | ||||
63 | #else | ||||
64 | # include <unistd.h> | ||||
65 | #endif | ||||
66 | | ||||
67 | #include <sqlite3.h> | ||||
68 | #include <functional> | ||||
69 | | ||||
70 | Q_DECLARE_OPAQUE_POINTER(sqlite3*) | ||||
71 | Q_DECLARE_METATYPE(sqlite3*) | ||||
72 | | ||||
73 | Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*) | ||||
74 | Q_DECLARE_METATYPE(sqlite3_stmt*) | ||||
75 | | ||||
76 | QT_BEGIN_NAMESPACE | ||||
77 | | ||||
78 | static QString _q_escapeIdentifier(const QString &identifier) | ||||
79 | { | ||||
80 | QString res = identifier; | ||||
81 | if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"'))) { | ||||
82 | res.replace(QLatin1Char('"'), QLatin1String("\"\"")); | ||||
83 | res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); | ||||
84 | res.replace(QLatin1Char('.'), QLatin1String("\".\"")); | ||||
85 | } | ||||
86 | return res; | ||||
87 | } | ||||
88 | | ||||
89 | static QVariant::Type qGetColumnType(const QString &tpName) | ||||
90 | { | ||||
91 | const QString typeName = tpName.toLower(); | ||||
92 | | ||||
93 | if (typeName == QLatin1String("integer") | ||||
94 | || typeName == QLatin1String("int")) | ||||
95 | return QVariant::Int; | ||||
96 | if (typeName == QLatin1String("double") | ||||
97 | || typeName == QLatin1String("float") | ||||
98 | || typeName == QLatin1String("real") | ||||
99 | || typeName.startsWith(QLatin1String("numeric"))) | ||||
100 | return QVariant::Double; | ||||
101 | if (typeName == QLatin1String("blob")) | ||||
102 | return QVariant::ByteArray; | ||||
103 | if (typeName == QLatin1String("boolean") | ||||
104 | || typeName == QLatin1String("bool")) | ||||
105 | return QVariant::Bool; | ||||
106 | return QVariant::String; | ||||
107 | } | ||||
108 | | ||||
109 | static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, | ||||
110 | int errorCode = -1) | ||||
111 | { | ||||
112 | return QSqlError(descr, | ||||
113 | QString(reinterpret_cast<const QChar *>(sqlite3_errmsg16(access))), | ||||
114 | type, QString::number(errorCode)); | ||||
115 | } | ||||
116 | | ||||
117 | class QSQLiteResultPrivate; | ||||
118 | | ||||
119 | class QSQLiteResult : public QSqlCachedResult | ||||
120 | { | ||||
121 | Q_DECLARE_PRIVATE(QSQLiteResult) | ||||
122 | friend class QSQLiteDriver; | ||||
123 | | ||||
124 | public: | ||||
125 | explicit QSQLiteResult(const QSQLiteDriver* db); | ||||
126 | ~QSQLiteResult(); | ||||
127 | QVariant handle() const Q_DECL_OVERRIDE; | ||||
128 | | ||||
129 | protected: | ||||
130 | bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) Q_DECL_OVERRIDE; | ||||
131 | bool reset(const QString &query) Q_DECL_OVERRIDE; | ||||
132 | bool prepare(const QString &query) Q_DECL_OVERRIDE; | ||||
133 | bool exec() Q_DECL_OVERRIDE; | ||||
134 | int size() Q_DECL_OVERRIDE; | ||||
135 | int numRowsAffected() Q_DECL_OVERRIDE; | ||||
136 | QVariant lastInsertId() const Q_DECL_OVERRIDE; | ||||
137 | QSqlRecord record() const Q_DECL_OVERRIDE; | ||||
138 | void detachFromResultSet() Q_DECL_OVERRIDE; | ||||
139 | void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; | ||||
140 | }; | ||||
141 | | ||||
142 | class QSQLiteDriverPrivate : public QSqlDriverPrivate | ||||
143 | { | ||||
144 | Q_DECLARE_PUBLIC(QSQLiteDriver) | ||||
145 | | ||||
146 | public: | ||||
147 | inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; } | ||||
148 | sqlite3 *access; | ||||
149 | QList <QSQLiteResult *> results; | ||||
150 | QStringList notificationid; | ||||
151 | }; | ||||
152 | | ||||
153 | | ||||
154 | class QSQLiteResultPrivate: public QSqlCachedResultPrivate | ||||
155 | { | ||||
156 | Q_DECLARE_PUBLIC(QSQLiteResult) | ||||
157 | | ||||
158 | public: | ||||
159 | Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver) | ||||
160 | QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv); | ||||
161 | void cleanup(); | ||||
162 | bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); | ||||
163 | // initializes the recordInfo and the cache | ||||
164 | void initColumns(bool emptyResultset); | ||||
165 | void finalize(); | ||||
166 | | ||||
167 | sqlite3_stmt *stmt; | ||||
168 | | ||||
169 | bool skippedStatus; // the status of the fetchNext() that's skipped | ||||
170 | bool skipRow; // skip the next fetchNext()? | ||||
171 | QSqlRecord rInf; | ||||
172 | QVector<QVariant> firstRow; | ||||
173 | }; | ||||
174 | | ||||
175 | QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv) | ||||
176 | : QSqlCachedResultPrivate(q, drv), | ||||
177 | stmt(0), | ||||
178 | skippedStatus(false), | ||||
179 | skipRow(false) | ||||
180 | { | ||||
181 | } | ||||
182 | | ||||
183 | void QSQLiteResultPrivate::cleanup() | ||||
184 | { | ||||
185 | Q_Q(QSQLiteResult); | ||||
186 | finalize(); | ||||
187 | rInf.clear(); | ||||
188 | skippedStatus = false; | ||||
189 | skipRow = false; | ||||
190 | q->setAt(QSql::BeforeFirstRow); | ||||
191 | q->setActive(false); | ||||
192 | q->cleanup(); | ||||
193 | } | ||||
194 | | ||||
195 | void QSQLiteResultPrivate::finalize() | ||||
196 | { | ||||
197 | if (!stmt) | ||||
198 | return; | ||||
199 | | ||||
200 | sqlite3_finalize(stmt); | ||||
201 | stmt = 0; | ||||
202 | } | ||||
203 | | ||||
204 | void QSQLiteResultPrivate::initColumns(bool emptyResultset) | ||||
205 | { | ||||
206 | Q_Q(QSQLiteResult); | ||||
207 | int nCols = sqlite3_column_count(stmt); | ||||
208 | if (nCols <= 0) | ||||
209 | return; | ||||
210 | | ||||
211 | q->init(nCols); | ||||
212 | | ||||
213 | for (int i = 0; i < nCols; ++i) { | ||||
214 | QString colName = QString(reinterpret_cast<const QChar *>( | ||||
215 | sqlite3_column_name16(stmt, i)) | ||||
216 | ).remove(QLatin1Char('"')); | ||||
217 | | ||||
218 | // must use typeName for resolving the type to match QSqliteDriver::record | ||||
219 | QString typeName = QString(reinterpret_cast<const QChar *>( | ||||
220 | sqlite3_column_decltype16(stmt, i))); | ||||
221 | // sqlite3_column_type is documented to have undefined behavior if the result set is empty | ||||
222 | int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); | ||||
223 | | ||||
224 | QVariant::Type fieldType; | ||||
225 | | ||||
226 | if (!typeName.isEmpty()) { | ||||
227 | fieldType = qGetColumnType(typeName); | ||||
228 | } else { | ||||
229 | // Get the proper type for the field based on stp value | ||||
230 | switch (stp) { | ||||
231 | case SQLITE_INTEGER: | ||||
232 | fieldType = QVariant::Int; | ||||
233 | break; | ||||
234 | case SQLITE_FLOAT: | ||||
235 | fieldType = QVariant::Double; | ||||
236 | break; | ||||
237 | case SQLITE_BLOB: | ||||
238 | fieldType = QVariant::ByteArray; | ||||
239 | break; | ||||
240 | case SQLITE_TEXT: | ||||
241 | fieldType = QVariant::String; | ||||
242 | break; | ||||
243 | case SQLITE_NULL: | ||||
244 | default: | ||||
245 | fieldType = QVariant::Invalid; | ||||
246 | break; | ||||
247 | } | ||||
248 | } | ||||
249 | | ||||
250 | QSqlField fld(colName, fieldType); | ||||
251 | fld.setSqlType(stp); | ||||
252 | rInf.append(fld); | ||||
253 | } | ||||
254 | } | ||||
255 | | ||||
256 | bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) | ||||
257 | { | ||||
258 | Q_Q(QSQLiteResult); | ||||
259 | int res; | ||||
260 | int i; | ||||
261 | | ||||
262 | if (skipRow) { | ||||
263 | // already fetched | ||||
264 | Q_ASSERT(!initialFetch); | ||||
265 | skipRow = false; | ||||
266 | for(int i=0;i<firstRow.count();i++) | ||||
267 | values[i]=firstRow[i]; | ||||
268 | return skippedStatus; | ||||
269 | } | ||||
270 | skipRow = initialFetch; | ||||
271 | | ||||
272 | if(initialFetch) { | ||||
273 | firstRow.clear(); | ||||
274 | firstRow.resize(sqlite3_column_count(stmt)); | ||||
275 | } | ||||
276 | | ||||
277 | if (!stmt) { | ||||
278 | q->setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), | ||||
279 | QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); | ||||
280 | q->setAt(QSql::AfterLastRow); | ||||
281 | return false; | ||||
282 | } | ||||
283 | res = sqlite3_step(stmt); | ||||
284 | | ||||
285 | switch(res) { | ||||
286 | case SQLITE_ROW: | ||||
287 | // check to see if should fill out columns | ||||
288 | if (rInf.isEmpty()) | ||||
289 | // must be first call. | ||||
290 | initColumns(false); | ||||
291 | if (idx < 0 && !initialFetch) | ||||
292 | return true; | ||||
293 | for (i = 0; i < rInf.count(); ++i) { | ||||
294 | switch (sqlite3_column_type(stmt, i)) { | ||||
295 | case SQLITE_BLOB: | ||||
296 | values[i + idx] = QByteArray(static_cast<const char *>( | ||||
297 | sqlite3_column_blob(stmt, i)), | ||||
298 | sqlite3_column_bytes(stmt, i)); | ||||
299 | break; | ||||
300 | case SQLITE_INTEGER: | ||||
301 | values[i + idx] = sqlite3_column_int64(stmt, i); | ||||
302 | break; | ||||
303 | case SQLITE_FLOAT: | ||||
304 | switch(q->numericalPrecisionPolicy()) { | ||||
305 | case QSql::LowPrecisionInt32: | ||||
306 | values[i + idx] = sqlite3_column_int(stmt, i); | ||||
307 | break; | ||||
308 | case QSql::LowPrecisionInt64: | ||||
309 | values[i + idx] = sqlite3_column_int64(stmt, i); | ||||
310 | break; | ||||
311 | case QSql::LowPrecisionDouble: | ||||
312 | case QSql::HighPrecision: | ||||
313 | default: | ||||
314 | values[i + idx] = sqlite3_column_double(stmt, i); | ||||
315 | break; | ||||
316 | }; | ||||
317 | break; | ||||
318 | case SQLITE_NULL: | ||||
319 | values[i + idx] = QVariant(QVariant::String); | ||||
320 | break; | ||||
321 | default: | ||||
322 | values[i + idx] = QString(reinterpret_cast<const QChar *>( | ||||
323 | sqlite3_column_text16(stmt, i)), | ||||
324 | sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); | ||||
325 | break; | ||||
326 | } | ||||
327 | } | ||||
328 | return true; | ||||
329 | case SQLITE_DONE: | ||||
330 | if (rInf.isEmpty()) | ||||
331 | // must be first call. | ||||
332 | initColumns(true); | ||||
333 | q->setAt(QSql::AfterLastRow); | ||||
334 | sqlite3_reset(stmt); | ||||
335 | return false; | ||||
336 | case SQLITE_CONSTRAINT: | ||||
337 | case SQLITE_ERROR: | ||||
338 | // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() | ||||
339 | // to get the specific error message. | ||||
340 | res = sqlite3_reset(stmt); | ||||
341 | q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", | ||||
342 | "Unable to fetch row"), QSqlError::ConnectionError, res)); | ||||
343 | q->setAt(QSql::AfterLastRow); | ||||
344 | return false; | ||||
345 | case SQLITE_MISUSE: | ||||
346 | case SQLITE_BUSY: | ||||
347 | default: | ||||
348 | // something wrong, don't get col info, but still return false | ||||
349 | q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", | ||||
350 | "Unable to fetch row"), QSqlError::ConnectionError, res)); | ||||
351 | sqlite3_reset(stmt); | ||||
352 | q->setAt(QSql::AfterLastRow); | ||||
353 | return false; | ||||
354 | } | ||||
355 | return false; | ||||
356 | } | ||||
357 | | ||||
358 | QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) | ||||
359 | : QSqlCachedResult(*new QSQLiteResultPrivate(this, db)) | ||||
360 | { | ||||
361 | Q_D(QSQLiteResult); | ||||
362 | const_cast<QSQLiteDriverPrivate*>(d->drv_d_func())->results.append(this); | ||||
363 | } | ||||
364 | | ||||
365 | QSQLiteResult::~QSQLiteResult() | ||||
366 | { | ||||
367 | Q_D(QSQLiteResult); | ||||
368 | if (d->drv_d_func()) | ||||
369 | const_cast<QSQLiteDriverPrivate*>(d->drv_d_func())->results.removeOne(this); | ||||
370 | d->cleanup(); | ||||
371 | } | ||||
372 | | ||||
373 | void QSQLiteResult::virtual_hook(int id, void *data) | ||||
374 | { | ||||
375 | QSqlCachedResult::virtual_hook(id, data); | ||||
376 | } | ||||
377 | | ||||
378 | bool QSQLiteResult::reset(const QString &query) | ||||
379 | { | ||||
380 | if (!prepare(query)) | ||||
381 | return false; | ||||
382 | return exec(); | ||||
383 | } | ||||
384 | | ||||
385 | bool QSQLiteResult::prepare(const QString &query) | ||||
386 | { | ||||
387 | Q_D(QSQLiteResult); | ||||
388 | if (!driver() || !driver()->isOpen() || driver()->isOpenError()) | ||||
389 | return false; | ||||
390 | | ||||
391 | d->cleanup(); | ||||
392 | | ||||
393 | setSelect(false); | ||||
394 | | ||||
395 | const void *pzTail = NULL; | ||||
396 | | ||||
397 | #if (SQLITE_VERSION_NUMBER >= 3003011) | ||||
398 | int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar), | ||||
399 | &d->stmt, &pzTail); | ||||
400 | #else | ||||
401 | int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), | ||||
402 | &d->stmt, &pzTail); | ||||
403 | #endif | ||||
404 | | ||||
405 | if (res != SQLITE_OK) { | ||||
406 | setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", | ||||
407 | "Unable to execute statement"), QSqlError::StatementError, res)); | ||||
408 | d->finalize(); | ||||
409 | return false; | ||||
410 | } else if (pzTail && !QString(reinterpret_cast<const QChar *>(pzTail)).trimmed().isEmpty()) { | ||||
411 | setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", | ||||
412 | "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); | ||||
413 | d->finalize(); | ||||
414 | return false; | ||||
415 | } | ||||
416 | return true; | ||||
417 | } | ||||
418 | | ||||
419 | static QString secondsToOffset(int seconds) | ||||
420 | { | ||||
421 | const QChar sign = ushort(seconds < 0 ? '-' : '+'); | ||||
422 | seconds = qAbs(seconds); | ||||
423 | const int hours = seconds / 3600; | ||||
424 | const int minutes = (seconds % 3600) / 60; | ||||
425 | | ||||
426 | return QString(QStringLiteral("%1%2:%3")).arg(sign).arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')); | ||||
427 | } | ||||
428 | | ||||
429 | static QString timespecToString(const QDateTime &dateTime) | ||||
430 | { | ||||
431 | switch (dateTime.timeSpec()) { | ||||
432 | case Qt::LocalTime: | ||||
433 | return QString(); | ||||
434 | case Qt::UTC: | ||||
435 | return QStringLiteral("Z"); | ||||
436 | case Qt::OffsetFromUTC: | ||||
437 | return secondsToOffset(dateTime.offsetFromUtc()); | ||||
438 | case Qt::TimeZone: | ||||
439 | return secondsToOffset(dateTime.timeZone().offsetFromUtc(dateTime)); | ||||
440 | default: | ||||
441 | return QString(); | ||||
442 | } | ||||
443 | } | ||||
444 | | ||||
445 | bool QSQLiteResult::exec() | ||||
446 | { | ||||
447 | Q_D(QSQLiteResult); | ||||
448 | QVector<QVariant> values = boundValues(); | ||||
449 | | ||||
450 | d->skippedStatus = false; | ||||
451 | d->skipRow = false; | ||||
452 | d->rInf.clear(); | ||||
453 | clearValues(); | ||||
454 | setLastError(QSqlError()); | ||||
455 | | ||||
456 | int res = sqlite3_reset(d->stmt); | ||||
457 | if (res != SQLITE_OK) { | ||||
458 | setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", | ||||
459 | "Unable to reset statement"), QSqlError::StatementError, res)); | ||||
460 | d->finalize(); | ||||
461 | return false; | ||||
462 | } | ||||
463 | | ||||
464 | int paramCount = sqlite3_bind_parameter_count(d->stmt); | ||||
465 | bool paramCountIsValid = paramCount == values.count(); | ||||
466 | | ||||
467 | #if (SQLITE_VERSION_NUMBER >= 3003011) | ||||
468 | // In the case of the reuse of a named placeholder | ||||
469 | if (paramCount < values.count()) { | ||||
470 | const auto countIndexes = [](int counter, const QList<int>& indexList) { | ||||
471 | return counter + indexList.length(); | ||||
472 | }; | ||||
473 | | ||||
474 | const int bindParamCount = std::accumulate(d->indexes.cbegin(), | ||||
475 | d->indexes.cend(), | ||||
476 | 0, | ||||
477 | countIndexes); | ||||
478 | | ||||
479 | paramCountIsValid = bindParamCount == values.count(); | ||||
480 | // When using named placeholders, it will reuse the index for duplicated | ||||
481 | // placeholders. So we need to ensure the QVector has only one instance of | ||||
482 | // each value as SQLite will do the rest for us. | ||||
483 | QVector<QVariant> prunedValues; | ||||
484 | QList<int> handledIndexes; | ||||
485 | for (int i = 0, currentIndex = 0; i < values.size(); ++i) { | ||||
486 | if (handledIndexes.contains(i)) | ||||
487 | continue; | ||||
488 | const auto placeHolder = QString::fromUtf8(sqlite3_bind_parameter_name(d->stmt, currentIndex + 1)); | ||||
489 | handledIndexes << d->indexes[placeHolder]; | ||||
490 | prunedValues << values.at(d->indexes[placeHolder].first()); | ||||
491 | ++currentIndex; | ||||
492 | } | ||||
493 | values = prunedValues; | ||||
494 | } | ||||
495 | #endif | ||||
496 | | ||||
497 | if (paramCountIsValid) { | ||||
498 | for (int i = 0; i < paramCount; ++i) { | ||||
499 | res = SQLITE_OK; | ||||
500 | const QVariant value = values.at(i); | ||||
501 | | ||||
502 | if (value.isNull()) { | ||||
503 | res = sqlite3_bind_null(d->stmt, i + 1); | ||||
504 | } else { | ||||
505 | switch (value.type()) { | ||||
506 | case QVariant::ByteArray: { | ||||
507 | const QByteArray *ba = static_cast<const QByteArray*>(value.constData()); | ||||
508 | res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), | ||||
509 | ba->size(), SQLITE_STATIC); | ||||
510 | break; } | ||||
511 | case QVariant::Int: | ||||
512 | case QVariant::Bool: | ||||
513 | res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); | ||||
514 | break; | ||||
515 | case QVariant::Double: | ||||
516 | res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); | ||||
517 | break; | ||||
518 | case QVariant::UInt: | ||||
519 | case QVariant::LongLong: | ||||
520 | res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); | ||||
521 | break; | ||||
522 | case QVariant::DateTime: { | ||||
523 | const QDateTime dateTime = value.toDateTime(); | ||||
524 | const QString str = dateTime.toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz") + timespecToString(dateTime)); | ||||
525 | res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), | ||||
526 | str.size() * sizeof(ushort), SQLITE_TRANSIENT); | ||||
527 | break; | ||||
528 | } | ||||
529 | case QVariant::Time: { | ||||
530 | const QTime time = value.toTime(); | ||||
531 | const QString str = time.toString(QStringViewLiteral("hh:mm:ss.zzz")); | ||||
532 | res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), | ||||
533 | str.size() * sizeof(ushort), SQLITE_TRANSIENT); | ||||
534 | break; | ||||
535 | } | ||||
536 | case QVariant::String: { | ||||
537 | // lifetime of string == lifetime of its qvariant | ||||
538 | const QString *str = static_cast<const QString*>(value.constData()); | ||||
539 | res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), | ||||
540 | (str->size()) * sizeof(QChar), SQLITE_STATIC); | ||||
541 | break; } | ||||
542 | default: { | ||||
543 | QString str = value.toString(); | ||||
544 | // SQLITE_TRANSIENT makes sure that sqlite buffers the data | ||||
545 | res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), | ||||
546 | (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); | ||||
547 | break; } | ||||
548 | } | ||||
549 | } | ||||
550 | if (res != SQLITE_OK) { | ||||
551 | setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult", | ||||
552 | "Unable to bind parameters"), QSqlError::StatementError, res)); | ||||
553 | d->finalize(); | ||||
554 | return false; | ||||
555 | } | ||||
556 | } | ||||
557 | } else { | ||||
558 | setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", | ||||
559 | "Parameter count mismatch"), QString(), QSqlError::StatementError)); | ||||
560 | return false; | ||||
561 | } | ||||
562 | d->skippedStatus = d->fetchNext(d->firstRow, 0, true); | ||||
563 | if (lastError().isValid()) { | ||||
564 | setSelect(false); | ||||
565 | setActive(false); | ||||
566 | return false; | ||||
567 | } | ||||
568 | setSelect(!d->rInf.isEmpty()); | ||||
569 | setActive(true); | ||||
570 | return true; | ||||
571 | } | ||||
572 | | ||||
573 | bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) | ||||
574 | { | ||||
575 | Q_D(QSQLiteResult); | ||||
576 | return d->fetchNext(row, idx, false); | ||||
577 | } | ||||
578 | | ||||
579 | int QSQLiteResult::size() | ||||
580 | { | ||||
581 | return -1; | ||||
582 | } | ||||
583 | | ||||
584 | int QSQLiteResult::numRowsAffected() | ||||
585 | { | ||||
586 | Q_D(const QSQLiteResult); | ||||
587 | return sqlite3_changes(d->drv_d_func()->access); | ||||
588 | } | ||||
589 | | ||||
590 | QVariant QSQLiteResult::lastInsertId() const | ||||
591 | { | ||||
592 | Q_D(const QSQLiteResult); | ||||
593 | if (isActive()) { | ||||
594 | qint64 id = sqlite3_last_insert_rowid(d->drv_d_func()->access); | ||||
595 | if (id) | ||||
596 | return id; | ||||
597 | } | ||||
598 | return QVariant(); | ||||
599 | } | ||||
600 | | ||||
601 | QSqlRecord QSQLiteResult::record() const | ||||
602 | { | ||||
603 | Q_D(const QSQLiteResult); | ||||
604 | if (!isActive() || !isSelect()) | ||||
605 | return QSqlRecord(); | ||||
606 | return d->rInf; | ||||
607 | } | ||||
608 | | ||||
609 | void QSQLiteResult::detachFromResultSet() | ||||
610 | { | ||||
611 | Q_D(QSQLiteResult); | ||||
612 | if (d->stmt) | ||||
613 | sqlite3_reset(d->stmt); | ||||
614 | } | ||||
615 | | ||||
616 | QVariant QSQLiteResult::handle() const | ||||
617 | { | ||||
618 | Q_D(const QSQLiteResult); | ||||
619 | return QVariant::fromValue(d->stmt); | ||||
620 | } | ||||
621 | | ||||
622 | ///////////////////////////////////////////////////////// | ||||
623 | | ||||
624 | #ifndef QT_NO_REGULAREXPRESSION | ||||
625 | static void _q_regexp(sqlite3_context* context, int argc, sqlite3_value** argv) | ||||
626 | { | ||||
627 | if (Q_UNLIKELY(argc != 2)) { | ||||
628 | sqlite3_result_int(context, 0); | ||||
629 | return; | ||||
630 | } | ||||
631 | | ||||
632 | const QString pattern = QString::fromUtf8( | ||||
633 | reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))); | ||||
634 | const QString subject = QString::fromUtf8( | ||||
635 | reinterpret_cast<const char*>(sqlite3_value_text(argv[1]))); | ||||
636 | | ||||
637 | auto cache = static_cast<QCache<QString, QRegularExpression>*>(sqlite3_user_data(context)); | ||||
638 | auto regexp = cache->object(pattern); | ||||
639 | const bool wasCached = regexp; | ||||
640 | | ||||
641 | if (!wasCached) | ||||
642 | regexp = new QRegularExpression(pattern, QRegularExpression::DontCaptureOption | QRegularExpression::OptimizeOnFirstUsageOption); | ||||
643 | | ||||
644 | const bool found = subject.contains(*regexp); | ||||
645 | | ||||
646 | if (!wasCached) | ||||
647 | cache->insert(pattern, regexp); | ||||
648 | | ||||
649 | sqlite3_result_int(context, int(found)); | ||||
650 | } | ||||
651 | | ||||
652 | static void _q_regexp_cleanup(void *cache) | ||||
653 | { | ||||
654 | delete static_cast<QCache<QString, QRegularExpression>*>(cache); | ||||
655 | } | ||||
656 | #endif | ||||
657 | | ||||
658 | QSQLiteDriver::QSQLiteDriver(QObject * parent) | ||||
659 | : QSqlDriver(*new QSQLiteDriverPrivate, parent) | ||||
660 | { | ||||
661 | } | ||||
662 | | ||||
663 | QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) | ||||
664 | : QSqlDriver(*new QSQLiteDriverPrivate, parent) | ||||
665 | { | ||||
666 | Q_D(QSQLiteDriver); | ||||
667 | d->access = connection; | ||||
668 | setOpen(true); | ||||
669 | setOpenError(false); | ||||
670 | } | ||||
671 | | ||||
672 | | ||||
673 | QSQLiteDriver::~QSQLiteDriver() | ||||
674 | { | ||||
675 | close(); | ||||
676 | } | ||||
677 | | ||||
678 | bool QSQLiteDriver::hasFeature(DriverFeature f) const | ||||
679 | { | ||||
680 | switch (f) { | ||||
681 | case BLOB: | ||||
682 | case Transactions: | ||||
683 | case Unicode: | ||||
684 | case LastInsertId: | ||||
685 | case PreparedQueries: | ||||
686 | case PositionalPlaceholders: | ||||
687 | case SimpleLocking: | ||||
688 | case FinishQuery: | ||||
689 | case LowPrecisionNumbers: | ||||
690 | case EventNotifications: | ||||
691 | return true; | ||||
692 | case QuerySize: | ||||
693 | case BatchOperations: | ||||
694 | case MultipleResultSets: | ||||
695 | case CancelQuery: | ||||
696 | return false; | ||||
697 | case NamedPlaceholders: | ||||
698 | #if (SQLITE_VERSION_NUMBER < 3003011) | ||||
699 | return false; | ||||
700 | #else | ||||
701 | return true; | ||||
702 | #endif | ||||
703 | | ||||
704 | } | ||||
705 | return false; | ||||
706 | } | ||||
707 | | ||||
708 | /* | ||||
709 | SQLite dbs have no user name, passwords, hosts or ports. | ||||
710 | just file names. | ||||
711 | */ | ||||
712 | bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) | ||||
713 | { | ||||
714 | Q_D(QSQLiteDriver); | ||||
715 | if (isOpen()) | ||||
716 | close(); | ||||
717 | | ||||
718 | | ||||
719 | int timeOut = 5000; | ||||
720 | bool sharedCache = false; | ||||
721 | bool openReadOnlyOption = false; | ||||
722 | bool openUriOption = false; | ||||
723 | #ifndef QT_NO_REGULAREXPRESSION | ||||
724 | static const QLatin1String regexpConnectOption = QLatin1String("QSQLITE_ENABLE_REGEXP"); | ||||
725 | bool defineRegexp = false; | ||||
726 | int regexpCacheSize = 25; | ||||
727 | #endif | ||||
728 | | ||||
729 | const auto opts = conOpts.splitRef(QLatin1Char(';')); | ||||
730 | for (auto option : opts) { | ||||
731 | option = option.trimmed(); | ||||
732 | if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) { | ||||
733 | option = option.mid(20).trimmed(); | ||||
734 | if (option.startsWith(QLatin1Char('='))) { | ||||
735 | bool ok; | ||||
736 | const int nt = option.mid(1).trimmed().toInt(&ok); | ||||
737 | if (ok) | ||||
738 | timeOut = nt; | ||||
739 | } | ||||
740 | } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) { | ||||
741 | openReadOnlyOption = true; | ||||
742 | } else if (option == QLatin1String("QSQLITE_OPEN_URI")) { | ||||
743 | openUriOption = true; | ||||
744 | } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) { | ||||
745 | sharedCache = true; | ||||
746 | } | ||||
747 | #ifndef QT_NO_REGULAREXPRESSION | ||||
748 | else if (option.startsWith(regexpConnectOption)) { | ||||
749 | option = option.mid(regexpConnectOption.size()).trimmed(); | ||||
750 | if (option.isEmpty()) { | ||||
751 | defineRegexp = true; | ||||
752 | } else if (option.startsWith(QLatin1Char('='))) { | ||||
753 | bool ok = false; | ||||
754 | const int cacheSize = option.mid(1).trimmed().toInt(&ok); | ||||
755 | if (ok) { | ||||
756 | defineRegexp = true; | ||||
757 | if (cacheSize > 0) | ||||
758 | regexpCacheSize = cacheSize; | ||||
759 | } | ||||
760 | } | ||||
761 | } | ||||
762 | #endif | ||||
763 | } | ||||
764 | | ||||
765 | int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); | ||||
766 | openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE); | ||||
767 | if (openUriOption) | ||||
768 | openMode |= SQLITE_OPEN_URI; | ||||
769 | | ||||
770 | openMode |= SQLITE_OPEN_NOMUTEX; | ||||
771 | | ||||
772 | if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { | ||||
773 | sqlite3_busy_timeout(d->access, timeOut); | ||||
774 | setOpen(true); | ||||
775 | setOpenError(false); | ||||
776 | #ifndef QT_NO_REGULAREXPRESSION | ||||
777 | if (defineRegexp) { | ||||
778 | auto cache = new QCache<QString, QRegularExpression>(regexpCacheSize); | ||||
779 | sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache, &_q_regexp, NULL, | ||||
780 | NULL, &_q_regexp_cleanup); | ||||
781 | } | ||||
782 | #endif | ||||
783 | return true; | ||||
784 | } else { | ||||
785 | if (d->access) { | ||||
786 | sqlite3_close(d->access); | ||||
787 | d->access = 0; | ||||
788 | } | ||||
789 | | ||||
790 | setLastError(qMakeError(d->access, tr("Error opening database"), | ||||
791 | QSqlError::ConnectionError)); | ||||
792 | setOpenError(true); | ||||
793 | return false; | ||||
794 | } | ||||
795 | } | ||||
796 | | ||||
797 | void QSQLiteDriver::close() | ||||
798 | { | ||||
799 | Q_D(QSQLiteDriver); | ||||
800 | if (isOpen()) { | ||||
801 | for (QSQLiteResult *result : qAsConst(d->results)) | ||||
802 | result->d_func()->finalize(); | ||||
803 | | ||||
804 | if (d->access && (d->notificationid.count() > 0)) { | ||||
805 | d->notificationid.clear(); | ||||
806 | sqlite3_update_hook(d->access, NULL, NULL); | ||||
807 | } | ||||
808 | | ||||
809 | if (sqlite3_close(d->access) != SQLITE_OK) | ||||
810 | setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError)); | ||||
811 | d->access = 0; | ||||
812 | setOpen(false); | ||||
813 | setOpenError(false); | ||||
814 | } | ||||
815 | } | ||||
816 | | ||||
817 | QSqlResult *QSQLiteDriver::createResult() const | ||||
818 | { | ||||
819 | return new QSQLiteResult(this); | ||||
820 | } | ||||
821 | | ||||
822 | bool QSQLiteDriver::beginTransaction() | ||||
823 | { | ||||
824 | if (!isOpen() || isOpenError()) | ||||
825 | return false; | ||||
826 | | ||||
827 | QSqlQuery q(createResult()); | ||||
828 | if (!q.exec(QLatin1String("BEGIN"))) { | ||||
829 | setLastError(QSqlError(tr("Unable to begin transaction"), | ||||
830 | q.lastError().databaseText(), QSqlError::TransactionError)); | ||||
831 | return false; | ||||
832 | } | ||||
833 | | ||||
834 | return true; | ||||
835 | } | ||||
836 | | ||||
837 | bool QSQLiteDriver::commitTransaction() | ||||
838 | { | ||||
839 | if (!isOpen() || isOpenError()) | ||||
840 | return false; | ||||
841 | | ||||
842 | QSqlQuery q(createResult()); | ||||
843 | if (!q.exec(QLatin1String("COMMIT"))) { | ||||
844 | setLastError(QSqlError(tr("Unable to commit transaction"), | ||||
845 | q.lastError().databaseText(), QSqlError::TransactionError)); | ||||
846 | return false; | ||||
847 | } | ||||
848 | | ||||
849 | return true; | ||||
850 | } | ||||
851 | | ||||
852 | bool QSQLiteDriver::rollbackTransaction() | ||||
853 | { | ||||
854 | if (!isOpen() || isOpenError()) | ||||
855 | return false; | ||||
856 | | ||||
857 | QSqlQuery q(createResult()); | ||||
858 | if (!q.exec(QLatin1String("ROLLBACK"))) { | ||||
859 | setLastError(QSqlError(tr("Unable to rollback transaction"), | ||||
860 | q.lastError().databaseText(), QSqlError::TransactionError)); | ||||
861 | return false; | ||||
862 | } | ||||
863 | | ||||
864 | return true; | ||||
865 | } | ||||
866 | | ||||
867 | QStringList QSQLiteDriver::tables(QSql::TableType type) const | ||||
868 | { | ||||
869 | QStringList res; | ||||
870 | if (!isOpen()) | ||||
871 | return res; | ||||
872 | | ||||
873 | QSqlQuery q(createResult()); | ||||
874 | q.setForwardOnly(true); | ||||
875 | | ||||
876 | QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " | ||||
877 | "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); | ||||
878 | if ((type & QSql::Tables) && (type & QSql::Views)) | ||||
879 | sql = sql.arg(QLatin1String("type='table' OR type='view'")); | ||||
880 | else if (type & QSql::Tables) | ||||
881 | sql = sql.arg(QLatin1String("type='table'")); | ||||
882 | else if (type & QSql::Views) | ||||
883 | sql = sql.arg(QLatin1String("type='view'")); | ||||
884 | else | ||||
885 | sql.clear(); | ||||
886 | | ||||
887 | if (!sql.isEmpty() && q.exec(sql)) { | ||||
888 | while(q.next()) | ||||
889 | res.append(q.value(0).toString()); | ||||
890 | } | ||||
891 | | ||||
892 | if (type & QSql::SystemTables) { | ||||
893 | // there are no internal tables beside this one: | ||||
894 | res.append(QLatin1String("sqlite_master")); | ||||
895 | } | ||||
896 | | ||||
897 | return res; | ||||
898 | } | ||||
899 | | ||||
900 | static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) | ||||
901 | { | ||||
902 | QString schema; | ||||
903 | QString table(tableName); | ||||
904 | int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); | ||||
905 | if (indexOfSeparator > -1) { | ||||
906 | schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); | ||||
907 | table = tableName.mid(indexOfSeparator + 1); | ||||
908 | } | ||||
909 | q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')')); | ||||
910 | | ||||
911 | QSqlIndex ind; | ||||
912 | while (q.next()) { | ||||
913 | bool isPk = q.value(5).toInt(); | ||||
914 | if (onlyPIndex && !isPk) | ||||
915 | continue; | ||||
916 | QString typeName = q.value(2).toString().toLower(); | ||||
917 | QSqlField fld(q.value(1).toString(), qGetColumnType(typeName), tableName); | ||||
918 | if (isPk && (typeName == QLatin1String("integer"))) | ||||
919 | // INTEGER PRIMARY KEY fields are auto-generated in sqlite | ||||
920 | // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! | ||||
921 | fld.setAutoValue(true); | ||||
922 | fld.setRequired(q.value(3).toInt() != 0); | ||||
923 | fld.setDefaultValue(q.value(4)); | ||||
924 | ind.append(fld); | ||||
925 | } | ||||
926 | return ind; | ||||
927 | } | ||||
928 | | ||||
929 | QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const | ||||
930 | { | ||||
931 | if (!isOpen()) | ||||
932 | return QSqlIndex(); | ||||
933 | | ||||
934 | QString table = tblname; | ||||
935 | if (isIdentifierEscaped(table, QSqlDriver::TableName)) | ||||
936 | table = stripDelimiters(table, QSqlDriver::TableName); | ||||
937 | | ||||
938 | QSqlQuery q(createResult()); | ||||
939 | q.setForwardOnly(true); | ||||
940 | return qGetTableInfo(q, table, true); | ||||
941 | } | ||||
942 | | ||||
943 | QSqlRecord QSQLiteDriver::record(const QString &tbl) const | ||||
944 | { | ||||
945 | if (!isOpen()) | ||||
946 | return QSqlRecord(); | ||||
947 | | ||||
948 | QString table = tbl; | ||||
949 | if (isIdentifierEscaped(table, QSqlDriver::TableName)) | ||||
950 | table = stripDelimiters(table, QSqlDriver::TableName); | ||||
951 | | ||||
952 | QSqlQuery q(createResult()); | ||||
953 | q.setForwardOnly(true); | ||||
954 | return qGetTableInfo(q, table); | ||||
955 | } | ||||
956 | | ||||
957 | QVariant QSQLiteDriver::handle() const | ||||
958 | { | ||||
959 | Q_D(const QSQLiteDriver); | ||||
960 | return QVariant::fromValue(d->access); | ||||
961 | } | ||||
962 | | ||||
963 | QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const | ||||
964 | { | ||||
965 | Q_UNUSED(type); | ||||
966 | return _q_escapeIdentifier(identifier); | ||||
967 | } | ||||
968 | | ||||
969 | static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename, | ||||
970 | sqlite3_int64 arowid) | ||||
971 | { | ||||
972 | Q_UNUSED(aoperation); | ||||
973 | Q_UNUSED(adbname); | ||||
974 | QSQLiteDriver *driver = static_cast<QSQLiteDriver *>(qobj); | ||||
975 | if (driver) { | ||||
976 | QMetaObject::invokeMethod(driver, "handleNotification", Qt::QueuedConnection, | ||||
977 | Q_ARG(QString, QString::fromUtf8(atablename)), Q_ARG(qint64, arowid)); | ||||
978 | } | ||||
979 | } | ||||
980 | | ||||
981 | bool QSQLiteDriver::subscribeToNotification(const QString &name) | ||||
982 | { | ||||
983 | Q_D(QSQLiteDriver); | ||||
984 | if (!isOpen()) { | ||||
985 | qWarning("Database not open."); | ||||
986 | return false; | ||||
987 | } | ||||
988 | | ||||
989 | if (d->notificationid.contains(name)) { | ||||
990 | qWarning("Already subscribing to '%s'.", qPrintable(name)); | ||||
991 | return false; | ||||
992 | } | ||||
993 | | ||||
994 | //sqlite supports only one notification callback, so only the first is registered | ||||
995 | d->notificationid << name; | ||||
996 | if (d->notificationid.count() == 1) | ||||
997 | sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast<void *> (this)); | ||||
998 | | ||||
999 | return true; | ||||
1000 | } | ||||
1001 | | ||||
1002 | bool QSQLiteDriver::unsubscribeFromNotification(const QString &name) | ||||
1003 | { | ||||
1004 | Q_D(QSQLiteDriver); | ||||
1005 | if (!isOpen()) { | ||||
1006 | qWarning("Database not open."); | ||||
1007 | return false; | ||||
1008 | } | ||||
1009 | | ||||
1010 | if (!d->notificationid.contains(name)) { | ||||
1011 | qWarning("Not subscribed to '%s'.", qPrintable(name)); | ||||
1012 | return false; | ||||
1013 | } | ||||
1014 | | ||||
1015 | d->notificationid.removeAll(name); | ||||
1016 | if (d->notificationid.isEmpty()) | ||||
1017 | sqlite3_update_hook(d->access, NULL, NULL); | ||||
1018 | | ||||
1019 | return true; | ||||
1020 | } | ||||
1021 | | ||||
1022 | QStringList QSQLiteDriver::subscribedToNotifications() const | ||||
1023 | { | ||||
1024 | Q_D(const QSQLiteDriver); | ||||
1025 | return d->notificationid; | ||||
1026 | } | ||||
1027 | | ||||
1028 | void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid) | ||||
1029 | { | ||||
1030 | Q_D(const QSQLiteDriver); | ||||
1031 | if (d->notificationid.contains(tableName)) { | ||||
1032 | emit notification(tableName); | ||||
1033 | emit notification(tableName, QSqlDriver::UnknownSource, QVariant(rowid)); | ||||
1034 | } | ||||
1035 | } | ||||
1036 | | ||||
1037 | QT_END_NAMESPACE |