summaryrefslogtreecommitdiffstats
path: root/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp')
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp509
1 files changed, 287 insertions, 222 deletions
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
index 009e8a39ef..c574772fd7 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
@@ -1,47 +1,13 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtSql module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qsql_sqlite_p.h"
#include <qcoreapplication.h>
#include <qdatetime.h>
-#include <qvariant.h>
+#include <qdebug.h>
+#include <qlist.h>
+#include <qloggingcategory.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
@@ -49,8 +15,7 @@
#include <QtSql/private/qsqlcachedresult_p.h>
#include <QtSql/private/qsqldriver_p.h>
#include <qstringlist.h>
-#include <qvector.h>
-#include <qdebug.h>
+#include <qvariant.h>
#if QT_CONFIG(regularexpression)
#include <qcache.h>
#include <qregularexpression.h>
@@ -74,35 +39,26 @@ Q_DECLARE_METATYPE(sqlite3_stmt*)
QT_BEGIN_NAMESPACE
-static QString _q_escapeIdentifier(const QString &identifier)
-{
- QString res = identifier;
- if (!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"'))) {
- res.replace(QLatin1Char('"'), QLatin1String("\"\""));
- res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
- res.replace(QLatin1Char('.'), QLatin1String("\".\""));
- }
- return res;
-}
+static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite")
+
+using namespace Qt::StringLiterals;
-static QVariant::Type qGetColumnType(const QString &tpName)
+static int qGetColumnType(const QString &tpName)
{
const QString typeName = tpName.toLower();
- if (typeName == QLatin1String("integer")
- || typeName == QLatin1String("int"))
- return QVariant::Int;
- if (typeName == QLatin1String("double")
- || typeName == QLatin1String("float")
- || typeName == QLatin1String("real")
- || typeName.startsWith(QLatin1String("numeric")))
- return QVariant::Double;
- if (typeName == QLatin1String("blob"))
- return QVariant::ByteArray;
- if (typeName == QLatin1String("boolean")
- || typeName == QLatin1String("bool"))
- return QVariant::Bool;
- return QVariant::String;
+ if (typeName == "integer"_L1 || typeName == "int"_L1)
+ return QMetaType::Int;
+ if (typeName == "double"_L1
+ || typeName == "float"_L1
+ || typeName == "real"_L1
+ || typeName.startsWith("numeric"_L1))
+ return QMetaType::Double;
+ if (typeName == "blob"_L1)
+ return QMetaType::QByteArray;
+ if (typeName == "boolean"_L1 || typeName == "bool"_L1)
+ return QMetaType::Bool;
+ return QMetaType::QString;
}
static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type,
@@ -144,42 +100,86 @@ class QSQLiteDriverPrivate : public QSqlDriverPrivate
Q_DECLARE_PUBLIC(QSQLiteDriver)
public:
- inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; }
- sqlite3 *access;
- QList <QSQLiteResult *> results;
+ inline QSQLiteDriverPrivate() : QSqlDriverPrivate(QSqlDriver::SQLite) {}
+ bool isIdentifierEscaped(QStringView identifier) const;
+ QSqlIndex getTableInfo(QSqlQuery &query, const QString &tableName,
+ bool onlyPIndex = false) const;
+
+ sqlite3 *access = nullptr;
+ QList<QSQLiteResult *> results;
QStringList notificationid;
};
+bool QSQLiteDriverPrivate::isIdentifierEscaped(QStringView identifier) const
+{
+ return identifier.size() > 2
+ && ((identifier.startsWith(u'"') && identifier.endsWith(u'"'))
+ || (identifier.startsWith(u'`') && identifier.endsWith(u'`'))
+ || (identifier.startsWith(u'[') && identifier.endsWith(u']')));
+}
-class QSQLiteResultPrivate: public QSqlCachedResultPrivate
+QSqlIndex QSQLiteDriverPrivate::getTableInfo(QSqlQuery &query, const QString &tableName,
+ bool onlyPIndex) const
+{
+ Q_Q(const QSQLiteDriver);
+ QString schema;
+ QString table = q->escapeIdentifier(tableName, QSqlDriver::TableName);
+ const auto indexOfSeparator = table.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{table}.first(indexOfSeparator);
+ auto rightName = QStringView{table}.sliced(indexOfSeparator + 1);
+ if (isIdentifierEscaped(leftName) && isIdentifierEscaped(rightName)) {
+ schema = leftName.toString() + u'.';
+ table = rightName.toString();
+ }
+ }
+
+ query.exec("PRAGMA "_L1 + schema + "table_info ("_L1 + table + u')');
+ QSqlIndex ind;
+ while (query.next()) {
+ bool isPk = query.value(5).toInt();
+ if (onlyPIndex && !isPk)
+ continue;
+ QString typeName = query.value(2).toString().toLower();
+ QString defVal = query.value(4).toString();
+ if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
+ const int end = defVal.lastIndexOf(u'\'');
+ if (end > 0)
+ defVal = defVal.mid(1, end - 1);
+ }
+
+ QSqlField fld(query.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName);
+ if (isPk && (typeName == "integer"_L1))
+ // INTEGER PRIMARY KEY fields are auto-generated in sqlite
+ // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
+ fld.setAutoValue(true);
+ fld.setRequired(query.value(3).toInt() != 0);
+ fld.setDefaultValue(defVal);
+ ind.append(fld);
+ }
+ return ind;
+}
+
+class QSQLiteResultPrivate : public QSqlCachedResultPrivate
{
Q_DECLARE_PUBLIC(QSQLiteResult)
public:
Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver)
- QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv);
+ using QSqlCachedResultPrivate::QSqlCachedResultPrivate;
void cleanup();
bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch);
// initializes the recordInfo and the cache
void initColumns(bool emptyResultset);
void finalize();
- sqlite3_stmt *stmt;
-
- bool skippedStatus; // the status of the fetchNext() that's skipped
- bool skipRow; // skip the next fetchNext()?
+ sqlite3_stmt *stmt = nullptr;
QSqlRecord rInf;
- QVector<QVariant> firstRow;
+ QList<QVariant> firstRow;
+ bool skippedStatus = false; // the status of the fetchNext() that's skipped
+ bool skipRow = false; // skip the next fetchNext()?
};
-QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv)
- : QSqlCachedResultPrivate(q, drv),
- stmt(0),
- skippedStatus(false),
- skipRow(false)
-{
-}
-
void QSQLiteResultPrivate::cleanup()
{
Q_Q(QSQLiteResult);
@@ -198,7 +198,7 @@ void QSQLiteResultPrivate::finalize()
return;
sqlite3_finalize(stmt);
- stmt = 0;
+ stmt = nullptr;
}
void QSQLiteResultPrivate::initColumns(bool emptyResultset)
@@ -213,17 +213,17 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
for (int i = 0; i < nCols; ++i) {
QString colName = QString(reinterpret_cast<const QChar *>(
sqlite3_column_name16(stmt, i))
- ).remove(QLatin1Char('"'));
+ ).remove(u'"');
const QString tableName = QString(reinterpret_cast<const QChar *>(
sqlite3_column_table_name16(stmt, i))
- ).remove(QLatin1Char('"'));
+ ).remove(u'"');
// must use typeName for resolving the type to match QSqliteDriver::record
QString typeName = QString(reinterpret_cast<const QChar *>(
sqlite3_column_decltype16(stmt, i)));
// sqlite3_column_type is documented to have undefined behavior if the result set is empty
int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i);
- QVariant::Type fieldType;
+ int fieldType;
if (!typeName.isEmpty()) {
fieldType = qGetColumnType(typeName);
@@ -231,26 +231,25 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
// Get the proper type for the field based on stp value
switch (stp) {
case SQLITE_INTEGER:
- fieldType = QVariant::Int;
+ fieldType = QMetaType::Int;
break;
case SQLITE_FLOAT:
- fieldType = QVariant::Double;
+ fieldType = QMetaType::Double;
break;
case SQLITE_BLOB:
- fieldType = QVariant::ByteArray;
+ fieldType = QMetaType::QByteArray;
break;
case SQLITE_TEXT:
- fieldType = QVariant::String;
+ fieldType = QMetaType::QString;
break;
case SQLITE_NULL:
default:
- fieldType = QVariant::Invalid;
+ fieldType = QMetaType::UnknownType;
break;
}
}
- QSqlField fld(colName, fieldType, tableName);
- fld.setSqlType(stp);
+ QSqlField fld(colName, QMetaType(fieldType), tableName);
rInf.append(fld);
}
}
@@ -258,20 +257,18 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch)
{
Q_Q(QSQLiteResult);
- int res;
- int i;
if (skipRow) {
// already fetched
Q_ASSERT(!initialFetch);
skipRow = false;
- for(int i=0;i<firstRow.count();i++)
+ for(int i=0;i<firstRow.size();i++)
values[i]=firstRow[i];
return skippedStatus;
}
skipRow = initialFetch;
- if(initialFetch) {
+ if (initialFetch) {
firstRow.clear();
firstRow.resize(sqlite3_column_count(stmt));
}
@@ -282,8 +279,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i
q->setAt(QSql::AfterLastRow);
return false;
}
- res = sqlite3_step(stmt);
-
+ int res = sqlite3_step(stmt);
switch(res) {
case SQLITE_ROW:
// check to see if should fill out columns
@@ -292,7 +288,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i
initColumns(false);
if (idx < 0 && !initialFetch)
return true;
- for (i = 0; i < rInf.count(); ++i) {
+ for (int i = 0; i < rInf.count(); ++i) {
switch (sqlite3_column_type(stmt, i)) {
case SQLITE_BLOB:
values[i + idx] = QByteArray(static_cast<const char *>(
@@ -318,7 +314,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i
};
break;
case SQLITE_NULL:
- values[i + idx] = QVariant(QVariant::String);
+ values[i + idx] = QVariant(QMetaType::fromType<QString>());
break;
default:
values[i + idx] = QString(reinterpret_cast<const QChar *>(
@@ -394,13 +390,14 @@ bool QSQLiteResult::prepare(const QString &query)
setSelect(false);
- const void *pzTail = NULL;
+ const void *pzTail = nullptr;
+ const auto size = int((query.size() + 1) * sizeof(QChar));
#if (SQLITE_VERSION_NUMBER >= 3003011)
- int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar),
+ int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), size,
&d->stmt, &pzTail);
#else
- int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar),
+ int res = sqlite3_prepare16(d->access, query.constData(), size,
&d->stmt, &pzTail);
#endif
@@ -422,15 +419,15 @@ bool QSQLiteResult::execBatch(bool arrayBind)
{
Q_UNUSED(arrayBind);
Q_D(QSqlResult);
- QScopedValueRollback<QVector<QVariant>> valuesScope(d->values);
- QVector<QVariant> values = d->values;
- if (values.count() == 0)
+ QScopedValueRollback<QList<QVariant>> valuesScope(d->values);
+ QList<QVariant> values = d->values;
+ if (values.size() == 0)
return false;
- for (int i = 0; i < values.at(0).toList().count(); ++i) {
+ for (int i = 0; i < values.at(0).toList().size(); ++i) {
d->values.clear();
- QScopedValueRollback<QHash<QString, QVector<int>>> indexesScope(d->indexes);
- QHash<QString, QVector<int>>::const_iterator it = d->indexes.constBegin();
+ QScopedValueRollback<QHash<QString, QList<int>>> indexesScope(d->indexes);
+ auto it = d->indexes.constBegin();
while (it != d->indexes.constEnd()) {
bindValue(it.key(), values.at(it.value().first()).toList().at(i), QSql::In);
++it;
@@ -444,7 +441,7 @@ bool QSQLiteResult::execBatch(bool arrayBind)
bool QSQLiteResult::exec()
{
Q_D(QSQLiteResult);
- QVector<QVariant> values = boundValues();
+ QList<QVariant> values = boundValues();
d->skippedStatus = false;
d->skipRow = false;
@@ -461,16 +458,16 @@ bool QSQLiteResult::exec()
}
int paramCount = sqlite3_bind_parameter_count(d->stmt);
- bool paramCountIsValid = paramCount == values.count();
+ bool paramCountIsValid = paramCount == values.size();
#if (SQLITE_VERSION_NUMBER >= 3003011)
// In the case of the reuse of a named placeholder
// We need to check explicitly that paramCount is greater than or equal to 1, as sqlite
// can end up in a case where for virtual tables it returns 0 even though it
// has parameters
- if (paramCount >= 1 && paramCount < values.count()) {
- const auto countIndexes = [](int counter, const QVector<int> &indexList) {
- return counter + indexList.length();
+ if (paramCount >= 1 && paramCount < values.size()) {
+ const auto countIndexes = [](int counter, const QList<int> &indexList) {
+ return counter + indexList.size();
};
const int bindParamCount = std::accumulate(d->indexes.cbegin(),
@@ -478,16 +475,21 @@ bool QSQLiteResult::exec()
0,
countIndexes);
- paramCountIsValid = bindParamCount == values.count();
+ paramCountIsValid = bindParamCount == values.size();
// When using named placeholders, it will reuse the index for duplicated
- // placeholders. So we need to ensure the QVector has only one instance of
+ // placeholders. So we need to ensure the QList has only one instance of
// each value as SQLite will do the rest for us.
- QVector<QVariant> prunedValues;
- QVector<int> handledIndexes;
+ QList<QVariant> prunedValues;
+ QList<int> handledIndexes;
for (int i = 0, currentIndex = 0; i < values.size(); ++i) {
if (handledIndexes.contains(i))
continue;
- const auto placeHolder = QString::fromUtf8(sqlite3_bind_parameter_name(d->stmt, currentIndex + 1));
+ const char *parameterName = sqlite3_bind_parameter_name(d->stmt, currentIndex + 1);
+ if (!parameterName) {
+ paramCountIsValid = false;
+ continue;
+ }
+ const auto placeHolder = QString::fromUtf8(parameterName);
const auto &indexes = d->indexes.value(placeHolder);
handledIndexes << indexes;
prunedValues << values.at(indexes.first());
@@ -500,53 +502,57 @@ bool QSQLiteResult::exec()
if (paramCountIsValid) {
for (int i = 0; i < paramCount; ++i) {
res = SQLITE_OK;
- const QVariant value = values.at(i);
+ const QVariant &value = values.at(i);
- if (value.isNull()) {
+ if (QSqlResultPrivate::isVariantNull(value)) {
res = sqlite3_bind_null(d->stmt, i + 1);
} else {
switch (value.userType()) {
- case QVariant::ByteArray: {
+ case QMetaType::QByteArray: {
const QByteArray *ba = static_cast<const QByteArray*>(value.constData());
res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(),
ba->size(), SQLITE_STATIC);
break; }
- case QVariant::Int:
- case QVariant::Bool:
+ case QMetaType::Int:
+ case QMetaType::Bool:
res = sqlite3_bind_int(d->stmt, i + 1, value.toInt());
break;
- case QVariant::Double:
+ case QMetaType::Double:
res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble());
break;
- case QVariant::UInt:
- case QVariant::LongLong:
+ case QMetaType::UInt:
+ case QMetaType::LongLong:
res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong());
break;
- case QVariant::DateTime: {
+ case QMetaType::QDateTime: {
const QDateTime dateTime = value.toDateTime();
const QString str = dateTime.toString(Qt::ISODateWithMs);
- res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(),
- str.size() * sizeof(ushort), SQLITE_TRANSIENT);
+ res = sqlite3_bind_text16(d->stmt, i + 1, str.data(),
+ int(str.size() * sizeof(ushort)),
+ SQLITE_TRANSIENT);
break;
}
- case QVariant::Time: {
+ case QMetaType::QTime: {
const QTime time = value.toTime();
const QString str = time.toString(u"hh:mm:ss.zzz");
- res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(),
- str.size() * sizeof(ushort), SQLITE_TRANSIENT);
+ res = sqlite3_bind_text16(d->stmt, i + 1, str.data(),
+ int(str.size() * sizeof(ushort)),
+ SQLITE_TRANSIENT);
break;
}
- case QVariant::String: {
+ case QMetaType::QString: {
// lifetime of string == lifetime of its qvariant
const QString *str = static_cast<const QString*>(value.constData());
- res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(),
- (str->size()) * sizeof(QChar), SQLITE_STATIC);
+ res = sqlite3_bind_text16(d->stmt, i + 1, str->unicode(),
+ int(str->size()) * sizeof(QChar),
+ SQLITE_STATIC);
break; }
default: {
- QString str = value.toString();
+ const QString str = value.toString();
// SQLITE_TRANSIENT makes sure that sqlite buffers the data
- res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(),
- (str.size()) * sizeof(QChar), SQLITE_TRANSIENT);
+ res = sqlite3_bind_text16(d->stmt, i + 1, str.data(),
+ int(str.size()) * sizeof(QChar),
+ SQLITE_TRANSIENT);
break; }
}
}
@@ -658,6 +664,30 @@ static void _q_regexp_cleanup(void *cache)
}
#endif
+static void _q_lower(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+ if (Q_UNLIKELY(argc != 1)) {
+ sqlite3_result_text(context, nullptr, 0, nullptr);
+ return;
+ }
+ const QString lower = QString::fromUtf8(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toLower();
+ const QByteArray ba = lower.toUtf8();
+ sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT);
+}
+
+static void _q_upper(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+ if (Q_UNLIKELY(argc != 1)) {
+ sqlite3_result_text(context, nullptr, 0, nullptr);
+ return;
+ }
+ const QString upper = QString::fromUtf8(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toUpper();
+ const QByteArray ba = upper.toUtf8();
+ sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT);
+}
+
QSQLiteDriver::QSQLiteDriver(QObject * parent)
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
{
@@ -723,36 +753,48 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
bool sharedCache = false;
bool openReadOnlyOption = false;
bool openUriOption = false;
+ bool useExtendedResultCodes = true;
+ bool useQtVfs = false;
+ bool useQtCaseFolding = false;
+ bool openNoFollow = false;
#if QT_CONFIG(regularexpression)
- static const QLatin1String regexpConnectOption = QLatin1String("QSQLITE_ENABLE_REGEXP");
+ static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1;
bool defineRegexp = false;
int regexpCacheSize = 25;
#endif
- const auto opts = conOpts.splitRef(QLatin1Char(';'));
+ const auto opts = QStringView{conOpts}.split(u';', Qt::SkipEmptyParts);
for (auto option : opts) {
option = option.trimmed();
- if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) {
+ if (option.startsWith("QSQLITE_BUSY_TIMEOUT"_L1)) {
option = option.mid(20).trimmed();
- if (option.startsWith(QLatin1Char('='))) {
+ if (option.startsWith(u'=')) {
bool ok;
const int nt = option.mid(1).trimmed().toInt(&ok);
if (ok)
timeOut = nt;
}
- } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) {
+ } else if (option == "QSQLITE_USE_QT_VFS"_L1) {
+ useQtVfs = true;
+ } else if (option == "QSQLITE_OPEN_READONLY"_L1) {
openReadOnlyOption = true;
- } else if (option == QLatin1String("QSQLITE_OPEN_URI")) {
+ } else if (option == "QSQLITE_OPEN_URI"_L1) {
openUriOption = true;
- } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) {
+ } else if (option == "QSQLITE_ENABLE_SHARED_CACHE"_L1) {
sharedCache = true;
+ } else if (option == "QSQLITE_NO_USE_EXTENDED_RESULT_CODES"_L1) {
+ useExtendedResultCodes = false;
+ } else if (option == "QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING"_L1) {
+ useQtCaseFolding = true;
+ } else if (option == "QSQLITE_OPEN_NOFOLLOW"_L1) {
+ openNoFollow = true;
}
#if QT_CONFIG(regularexpression)
else if (option.startsWith(regexpConnectOption)) {
option = option.mid(regexpConnectOption.size()).trimmed();
if (option.isEmpty()) {
defineRegexp = true;
- } else if (option.startsWith(QLatin1Char('='))) {
+ } else if (option.startsWith(u'=')) {
bool ok = false;
const int cacheSize = option.mid(1).trimmed().toInt(&ok);
if (ok) {
@@ -763,28 +805,45 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
}
}
#endif
+ else
+ qCWarning(lcSqlite, "Unsupported option '%ls'", qUtf16Printable(option.toString()));
}
int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE);
if (openUriOption)
openMode |= SQLITE_OPEN_URI;
+ if (openNoFollow) {
+#if defined(SQLITE_OPEN_NOFOLLOW)
+ openMode |= SQLITE_OPEN_NOFOLLOW;
+#else
+ qCWarning(lcSqlite, "SQLITE_OPEN_NOFOLLOW not supported with the SQLite version %s", sqlite3_libversion());
+#endif
+ }
openMode |= SQLITE_OPEN_NOMUTEX;
- const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL);
+ const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, useQtVfs ? "QtVFS" : nullptr);
if (res == SQLITE_OK) {
sqlite3_busy_timeout(d->access, timeOut);
+ sqlite3_extended_result_codes(d->access, useExtendedResultCodes);
setOpen(true);
setOpenError(false);
#if QT_CONFIG(regularexpression)
if (defineRegexp) {
auto cache = new QCache<QString, QRegularExpression>(regexpCacheSize);
- sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache, &_q_regexp, NULL,
- NULL, &_q_regexp_cleanup);
+ sqlite3_create_function_v2(d->access, "regexp", 2, SQLITE_UTF8, cache,
+ &_q_regexp, nullptr,
+ nullptr, &_q_regexp_cleanup);
}
#endif
+ if (useQtCaseFolding) {
+ sqlite3_create_function_v2(d->access, "lower", 1, SQLITE_UTF8, nullptr,
+ &_q_lower, nullptr, nullptr, nullptr);
+ sqlite3_create_function_v2(d->access, "upper", 1, SQLITE_UTF8, nullptr,
+ &_q_upper, nullptr, nullptr, nullptr);
+ }
return true;
} else {
setLastError(qMakeError(d->access, tr("Error opening database"),
@@ -793,7 +852,7 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
if (d->access) {
sqlite3_close(d->access);
- d->access = 0;
+ d->access = nullptr;
}
return false;
@@ -804,19 +863,19 @@ void QSQLiteDriver::close()
{
Q_D(QSQLiteDriver);
if (isOpen()) {
- for (QSQLiteResult *result : qAsConst(d->results))
+ for (QSQLiteResult *result : std::as_const(d->results))
result->d_func()->finalize();
- if (d->access && (d->notificationid.count() > 0)) {
+ if (d->access && (d->notificationid.size() > 0)) {
d->notificationid.clear();
- sqlite3_update_hook(d->access, NULL, NULL);
+ sqlite3_update_hook(d->access, nullptr, nullptr);
}
const int res = sqlite3_close(d->access);
if (res != SQLITE_OK)
setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError, res));
- d->access = 0;
+ d->access = nullptr;
setOpen(false);
setOpenError(false);
}
@@ -833,7 +892,7 @@ bool QSQLiteDriver::beginTransaction()
return false;
QSqlQuery q(createResult());
- if (!q.exec(QLatin1String("BEGIN"))) {
+ if (!q.exec("BEGIN"_L1)) {
setLastError(QSqlError(tr("Unable to begin transaction"),
q.lastError().databaseText(), QSqlError::TransactionError));
return false;
@@ -848,7 +907,7 @@ bool QSQLiteDriver::commitTransaction()
return false;
QSqlQuery q(createResult());
- if (!q.exec(QLatin1String("COMMIT"))) {
+ if (!q.exec("COMMIT"_L1)) {
setLastError(QSqlError(tr("Unable to commit transaction"),
q.lastError().databaseText(), QSqlError::TransactionError));
return false;
@@ -863,7 +922,7 @@ bool QSQLiteDriver::rollbackTransaction()
return false;
QSqlQuery q(createResult());
- if (!q.exec(QLatin1String("ROLLBACK"))) {
+ if (!q.exec("ROLLBACK"_L1)) {
setLastError(QSqlError(tr("Unable to rollback transaction"),
q.lastError().databaseText(), QSqlError::TransactionError));
return false;
@@ -881,14 +940,14 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const
QSqlQuery q(createResult());
q.setForwardOnly(true);
- QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 "
- "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1");
+ QString sql = "SELECT name FROM sqlite_master WHERE %1 "
+ "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"_L1;
if ((type & QSql::Tables) && (type & QSql::Views))
- sql = sql.arg(QLatin1String("type='table' OR type='view'"));
+ sql = sql.arg("type='table' OR type='view'"_L1);
else if (type & QSql::Tables)
- sql = sql.arg(QLatin1String("type='table'"));
+ sql = sql.arg("type='table'"_L1);
else if (type & QSql::Views)
- sql = sql.arg(QLatin1String("type='view'"));
+ sql = sql.arg("type='view'"_L1);
else
sql.clear();
@@ -899,74 +958,32 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const
if (type & QSql::SystemTables) {
// there are no internal tables beside this one:
- res.append(QLatin1String("sqlite_master"));
+ res.append("sqlite_master"_L1);
}
return res;
}
-static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false)
-{
- QString schema;
- QString table(tableName);
- int indexOfSeparator = tableName.indexOf(QLatin1Char('.'));
- if (indexOfSeparator > -1) {
- schema = tableName.left(indexOfSeparator).append(QLatin1Char('.'));
- table = tableName.mid(indexOfSeparator + 1);
- }
- q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')'));
-
- QSqlIndex ind;
- while (q.next()) {
- bool isPk = q.value(5).toInt();
- if (onlyPIndex && !isPk)
- continue;
- QString typeName = q.value(2).toString().toLower();
- QString defVal = q.value(4).toString();
- if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) {
- const int end = defVal.lastIndexOf(QLatin1Char('\''));
- if (end > 0)
- defVal = defVal.mid(1, end - 1);
- }
-
- QSqlField fld(q.value(1).toString(), qGetColumnType(typeName), tableName);
- if (isPk && (typeName == QLatin1String("integer")))
- // INTEGER PRIMARY KEY fields are auto-generated in sqlite
- // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
- fld.setAutoValue(true);
- fld.setRequired(q.value(3).toInt() != 0);
- fld.setDefaultValue(defVal);
- ind.append(fld);
- }
- return ind;
-}
-
-QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const
+QSqlIndex QSQLiteDriver::primaryIndex(const QString &tablename) const
{
+ Q_D(const QSQLiteDriver);
if (!isOpen())
return QSqlIndex();
- QString table = tblname;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
QSqlQuery q(createResult());
q.setForwardOnly(true);
- return qGetTableInfo(q, table, true);
+ return d->getTableInfo(q, tablename, true);
}
-QSqlRecord QSQLiteDriver::record(const QString &tbl) const
+QSqlRecord QSQLiteDriver::record(const QString &tablename) const
{
+ Q_D(const QSQLiteDriver);
if (!isOpen())
return QSqlRecord();
- QString table = tbl;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
QSqlQuery q(createResult());
q.setForwardOnly(true);
- return qGetTableInfo(q, table);
+ return d->getTableInfo(q, tablename);
}
QVariant QSQLiteDriver::handle() const
@@ -977,8 +994,52 @@ QVariant QSQLiteDriver::handle() const
QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
{
+ Q_D(const QSQLiteDriver);
+ if (identifier.isEmpty() || isIdentifierEscaped(identifier, type))
+ return identifier;
+
+ const auto indexOfSeparator = identifier.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{identifier}.first(indexOfSeparator);
+ auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1);
+ const QStringView leftEnclose = d->isIdentifierEscaped(leftName) ? u"" : u"\"";
+ const QStringView rightEnclose = d->isIdentifierEscaped(rightName) ? u"" : u"\"";
+ if (leftEnclose.isEmpty() || rightEnclose.isEmpty())
+ return (leftEnclose + leftName + leftEnclose + u'.' + rightEnclose + rightName
+ + rightEnclose);
+ }
+ return u'"' + identifier + u'"';
+}
+
+bool QSQLiteDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const
+{
+ Q_D(const QSQLiteDriver);
Q_UNUSED(type);
- return _q_escapeIdentifier(identifier);
+ return d->isIdentifierEscaped(QStringView{identifier});
+}
+
+QString QSQLiteDriver::stripDelimiters(const QString &identifier, IdentifierType type) const
+{
+ Q_D(const QSQLiteDriver);
+ const auto indexOfSeparator = identifier.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{identifier}.first(indexOfSeparator);
+ auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1);
+ const auto leftEscaped = d->isIdentifierEscaped(leftName);
+ const auto rightEscaped = d->isIdentifierEscaped(rightName);
+ if (leftEscaped || rightEscaped) {
+ if (leftEscaped)
+ leftName = leftName.sliced(1).chopped(1);
+ if (rightEscaped)
+ rightName = rightName.sliced(1).chopped(1);
+ return leftName + u'.' + rightName;
+ }
+ }
+
+ if (isIdentifierEscaped(identifier, type))
+ return identifier.mid(1, identifier.size() - 2);
+
+ return identifier;
}
static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename,
@@ -997,18 +1058,19 @@ bool QSQLiteDriver::subscribeToNotification(const QString &name)
{
Q_D(QSQLiteDriver);
if (!isOpen()) {
- qWarning("Database not open.");
+ qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Database not open.");
return false;
}
if (d->notificationid.contains(name)) {
- qWarning("Already subscribing to '%s'.", qPrintable(name));
+ qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Already subscribing to '%ls'.",
+ qUtf16Printable(name));
return false;
}
//sqlite supports only one notification callback, so only the first is registered
d->notificationid << name;
- if (d->notificationid.count() == 1)
+ if (d->notificationid.size() == 1)
sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast<void *> (this));
return true;
@@ -1018,18 +1080,19 @@ bool QSQLiteDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QSQLiteDriver);
if (!isOpen()) {
- qWarning("Database not open.");
+ qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Database not open.");
return false;
}
if (!d->notificationid.contains(name)) {
- qWarning("Not subscribed to '%s'.", qPrintable(name));
+ qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
d->notificationid.removeAll(name);
if (d->notificationid.isEmpty())
- sqlite3_update_hook(d->access, NULL, NULL);
+ sqlite3_update_hook(d->access, nullptr, nullptr);
return true;
}
@@ -1048,3 +1111,5 @@ void QSQLiteDriver::handleNotification(const QString &tableName, qint64 rowid)
}
QT_END_NAMESPACE
+
+#include "moc_qsql_sqlite_p.cpp"