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 <qvector.h>
|
||||
#include <qdebug.h>
|
||||
#ifndef QT_NO_REGULAREXPRESSION
|
||||
#include <qcache.h>
|
||||
#include <qregularexpression.h>
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN
|
||||
# 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)
|
||||
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
|
||||
{
|
||||
@ -615,6 +650,11 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
||||
bool sharedCache = false;
|
||||
bool openReadOnlyOption = 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(';'));
|
||||
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")) {
|
||||
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));
|
||||
@ -646,6 +702,13 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
||||
sqlite3_busy_timeout(d->access, timeOut);
|
||||
setOpen(true);
|
||||
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;
|
||||
} else {
|
||||
if (d->access) {
|
||||
|
@ -90,3 +90,8 @@ qDebug() << q.value(0); // outputs the first RETURN/OUT value
|
||||
QSqlDatabase: QMYSQL driver not loaded
|
||||
QSqlDatabase: available drivers: QMYSQL
|
||||
//! [31]
|
||||
|
||||
|
||||
//! [34]
|
||||
column.contains(QRegularExpression("pattern"));
|
||||
//! [34]
|
||||
|
@ -568,6 +568,22 @@
|
||||
|
||||
\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
|
||||
|
||||
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_URI
|
||||
\li QSQLITE_ENABLE_SHARED_CACHE
|
||||
\li QSQLITE_ENABLE_REGEXP
|
||||
\endlist
|
||||
|
||||
\li
|
||||
|
@ -188,6 +188,9 @@ private slots:
|
||||
void sqlite_enable_cache_mode_data() { generic_data("QSQLITE"); }
|
||||
void sqlite_enable_cache_mode();
|
||||
|
||||
void sqlite_enableRegexp_data() { generic_data("QSQLITE"); }
|
||||
void sqlite_enableRegexp();
|
||||
|
||||
private:
|
||||
void createTestTables(QSqlDatabase db);
|
||||
void dropTestTables(QSqlDatabase db);
|
||||
@ -2259,5 +2262,33 @@ void tst_QSqlDatabase::sqlite_enable_cache_mode()
|
||||
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)
|
||||
#include "tst_qsqldatabase.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user