REGEXP for SQLite
Since SQLite does not define a regexp function by default, provide a Qt based implementation which can be enabled using QSQLITE_ENABLE_REGEXP as an connect option. This way statements like SELECT * FROM table WHERE col REGEXP '^[a-d]'; work out of the box. [ChangeLog][QtSql] Add QSQLITE_ENABLE_REGEXP connect option for QSQLiteDriver. If set a Qt based regexp() implementation is provided allowing to use REGEXP in SQL statements. Task-number: QTBUG-18084 Change-Id: I7f0e926fe4c5d6baea509f75497f46a61ca86679 Reviewed-by: Milian Wolff <milian.wolff@kdab.com> Reviewed-by: Jesus Fernandez <Jesus.Fernandez@qt.io> Reviewed-by: Sebastian Sauer <sebastian.sauer@kdab.com> Reviewed-by: Andy Shaw <andy.shaw@qt.io> Reviewed-by: Frederik Gladhorn <frederik.gladhorn@qt.io>
This commit is contained in:
parent
56723c6e91
commit
2a3297c726
@ -51,6 +51,10 @@
|
|||||||
#include <qstringlist.h>
|
#include <qstringlist.h>
|
||||||
#include <qvector.h>
|
#include <qvector.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
#ifndef QT_NO_REGULAREXPRESSION
|
||||||
|
#include <qcache.h>
|
||||||
|
#include <qregularexpression.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined Q_OS_WIN
|
#if defined Q_OS_WIN
|
||||||
# include <qt_windows.h>
|
# include <qt_windows.h>
|
||||||
@ -556,6 +560,37 @@ QVariant QSQLiteResult::handle() const
|
|||||||
|
|
||||||
/////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef QT_NO_REGULAREXPRESSION
|
||||||
|
static void _q_regexp(sqlite3_context* context, int argc, sqlite3_value** argv)
|
||||||
|
{
|
||||||
|
if (Q_UNLIKELY(argc != 2)) {
|
||||||
|
sqlite3_result_int(context, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString pattern = QString::fromUtf8(
|
||||||
|
reinterpret_cast<const char*>(sqlite3_value_text(argv[0])));
|
||||||
|
const QString subject = QString::fromUtf8(
|
||||||
|
reinterpret_cast<const char*>(sqlite3_value_text(argv[1])));
|
||||||
|
|
||||||
|
auto cache = static_cast<QCache<QString, QRegularExpression>*>(sqlite3_user_data(context));
|
||||||
|
QRegularExpression *regexp = cache->object(pattern);
|
||||||
|
if (!regexp) {
|
||||||
|
regexp = new QRegularExpression(pattern, QRegularExpression::DontCaptureOption
|
||||||
|
| QRegularExpression::OptimizeOnFirstUsageOption);
|
||||||
|
cache->insert(pattern, regexp);
|
||||||
|
}
|
||||||
|
const bool found = subject.contains(*regexp);
|
||||||
|
|
||||||
|
sqlite3_result_int(context, int(found));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _q_regexp_cleanup(void *cache)
|
||||||
|
{
|
||||||
|
delete static_cast<QCache<QString, QRegularExpression>*>(cache);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QSQLiteDriver::QSQLiteDriver(QObject * parent)
|
QSQLiteDriver::QSQLiteDriver(QObject * parent)
|
||||||
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
|
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
|
||||||
{
|
{
|
||||||
@ -615,6 +650,11 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
|||||||
bool sharedCache = false;
|
bool sharedCache = false;
|
||||||
bool openReadOnlyOption = false;
|
bool openReadOnlyOption = false;
|
||||||
bool openUriOption = false;
|
bool openUriOption = false;
|
||||||
|
#ifndef QT_NO_REGULAREXPRESSION
|
||||||
|
static const QLatin1String regexpConnectOption = QLatin1String("QSQLITE_ENABLE_REGEXP");
|
||||||
|
bool defineRegexp = false;
|
||||||
|
int regexpCacheSize = 25;
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto opts = conOpts.splitRef(QLatin1Char(';'));
|
const auto opts = conOpts.splitRef(QLatin1Char(';'));
|
||||||
for (auto option : opts) {
|
for (auto option : opts) {
|
||||||
@ -634,6 +674,22 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
|||||||
} else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) {
|
} else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) {
|
||||||
sharedCache = true;
|
sharedCache = true;
|
||||||
}
|
}
|
||||||
|
#ifndef QT_NO_REGULAREXPRESSION
|
||||||
|
else if (option.startsWith(regexpConnectOption)) {
|
||||||
|
option = option.mid(regexpConnectOption.size()).trimmed();
|
||||||
|
if (option.isEmpty()) {
|
||||||
|
defineRegexp = true;
|
||||||
|
} else if (option.startsWith(QLatin1Char('='))) {
|
||||||
|
bool ok = false;
|
||||||
|
const int cacheSize = option.mid(1).trimmed().toInt(&ok);
|
||||||
|
if (ok) {
|
||||||
|
defineRegexp = true;
|
||||||
|
if (cacheSize > 0)
|
||||||
|
regexpCacheSize = cacheSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
|
int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
|
||||||
@ -646,6 +702,13 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
|||||||
sqlite3_busy_timeout(d->access, timeOut);
|
sqlite3_busy_timeout(d->access, timeOut);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setOpenError(false);
|
setOpenError(false);
|
||||||
|
#ifndef QT_NO_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);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (d->access) {
|
if (d->access) {
|
||||||
|
@ -90,3 +90,8 @@ qDebug() << q.value(0); // outputs the first RETURN/OUT value
|
|||||||
QSqlDatabase: QMYSQL driver not loaded
|
QSqlDatabase: QMYSQL driver not loaded
|
||||||
QSqlDatabase: available drivers: QMYSQL
|
QSqlDatabase: available drivers: QMYSQL
|
||||||
//! [31]
|
//! [31]
|
||||||
|
|
||||||
|
|
||||||
|
//! [34]
|
||||||
|
column.contains(QRegularExpression("pattern"));
|
||||||
|
//! [34]
|
||||||
|
@ -568,6 +568,22 @@
|
|||||||
|
|
||||||
\snippet code/doc_src_sql-driver.qdoc 23
|
\snippet code/doc_src_sql-driver.qdoc 23
|
||||||
|
|
||||||
|
\section3 Enable REGEXP operator
|
||||||
|
|
||||||
|
SQLite comes with a REGEXP operation. However the needed implementation must
|
||||||
|
be provided by the user. For convenience a default implementation can be
|
||||||
|
enabled by \l{QSqlDatabase::setConnectOptions()} {setting the connect
|
||||||
|
option} \c{QSQLITE_ENABLE_REGEXP} before \l{QSqlDatabase::open()} {the
|
||||||
|
database connection is opened}. Then a SQL statement like "column REGEXP
|
||||||
|
'pattern'" basically expands to the Qt code
|
||||||
|
|
||||||
|
\snippet code/doc_src_sql-driver.cpp 34
|
||||||
|
|
||||||
|
For better performance the regular expressions are cached internally. By
|
||||||
|
default the cache size is 25, but it can be changed through the option's
|
||||||
|
value. For example passing "\c{QSQLITE_ENABLE_REGEXP=10}" reduces the
|
||||||
|
cache size to 10.
|
||||||
|
|
||||||
\section3 QSQLITE File Format Compatibility
|
\section3 QSQLITE File Format Compatibility
|
||||||
|
|
||||||
SQLite minor releases sometimes break file format forward compatibility.
|
SQLite minor releases sometimes break file format forward compatibility.
|
||||||
|
@ -1175,6 +1175,7 @@ QSqlRecord QSqlDatabase::record(const QString& tablename) const
|
|||||||
\li QSQLITE_OPEN_READONLY
|
\li QSQLITE_OPEN_READONLY
|
||||||
\li QSQLITE_OPEN_URI
|
\li QSQLITE_OPEN_URI
|
||||||
\li QSQLITE_ENABLE_SHARED_CACHE
|
\li QSQLITE_ENABLE_SHARED_CACHE
|
||||||
|
\li QSQLITE_ENABLE_REGEXP
|
||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
\li
|
\li
|
||||||
|
@ -188,6 +188,9 @@ private slots:
|
|||||||
void sqlite_enable_cache_mode_data() { generic_data("QSQLITE"); }
|
void sqlite_enable_cache_mode_data() { generic_data("QSQLITE"); }
|
||||||
void sqlite_enable_cache_mode();
|
void sqlite_enable_cache_mode();
|
||||||
|
|
||||||
|
void sqlite_enableRegexp_data() { generic_data("QSQLITE"); }
|
||||||
|
void sqlite_enableRegexp();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createTestTables(QSqlDatabase db);
|
void createTestTables(QSqlDatabase db);
|
||||||
void dropTestTables(QSqlDatabase db);
|
void dropTestTables(QSqlDatabase db);
|
||||||
@ -2259,5 +2262,33 @@ void tst_QSqlDatabase::sqlite_enable_cache_mode()
|
|||||||
db2.close();
|
db2.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QSqlDatabase::sqlite_enableRegexp()
|
||||||
|
{
|
||||||
|
QFETCH(QString, dbName);
|
||||||
|
QSqlDatabase db = QSqlDatabase::database(dbName);
|
||||||
|
CHECK_DATABASE(db);
|
||||||
|
if (db.driverName().startsWith("QSQLITE2"))
|
||||||
|
QSKIP("SQLite3 specific test");
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
db.setConnectOptions("QSQLITE_ENABLE_REGEXP");
|
||||||
|
QVERIFY_SQL(db, open());
|
||||||
|
|
||||||
|
QSqlQuery q(db);
|
||||||
|
const QString tableName(qTableName("uint_test", __FILE__, db));
|
||||||
|
QVERIFY_SQL(q, exec(QString("CREATE TABLE %1(text TEXT)").arg(tableName)));
|
||||||
|
QVERIFY_SQL(q, prepare(QString("INSERT INTO %1 VALUES(?)").arg(tableName)));
|
||||||
|
q.addBindValue("a0");
|
||||||
|
QVERIFY_SQL(q, exec());
|
||||||
|
q.addBindValue("a1");
|
||||||
|
QVERIFY_SQL(q, exec());
|
||||||
|
|
||||||
|
QVERIFY_SQL(q, exec(QString("SELECT text FROM %1 WHERE text REGEXP 'a[^0]' "
|
||||||
|
"ORDER BY text").arg(tableName)));
|
||||||
|
QVERIFY_SQL(q, next());
|
||||||
|
QCOMPARE(q.value(0).toString(), QString("a1"));
|
||||||
|
QFAIL_SQL(q, next());
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QSqlDatabase)
|
QTEST_MAIN(tst_QSqlDatabase)
|
||||||
#include "tst_qsqldatabase.moc"
|
#include "tst_qsqldatabase.moc"
|
||||||
|
Loading…
Reference in New Issue
Block a user