summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLorenz Haas <lorenz.haas@histomatics.de>2016-11-19 19:05:36 +0100
committerLorenz Haas <lorenz.haas@histomatics.de>2017-02-22 21:14:23 +0000
commit2a3297c7269c9a3a9e6b4129c76366bac5c9d7cb (patch)
tree779de134a974c24f448fe1dcc5102f46981575da
parent56723c6e913498b96c0c814611c46bfcbd262e67 (diff)
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>
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp63
-rw-r--r--src/sql/doc/snippets/code/doc_src_sql-driver.cpp5
-rw-r--r--src/sql/doc/src/sql-driver.qdoc16
-rw-r--r--src/sql/kernel/qsqldatabase.cpp1
-rw-r--r--tests/auto/sql/kernel/qsqldatabase/tst_qsqldatabase.cpp31
5 files changed, 116 insertions, 0 deletions
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
index 1cb0f10494..56eceeecbd 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
@@ -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) {
diff --git a/src/sql/doc/snippets/code/doc_src_sql-driver.cpp b/src/sql/doc/snippets/code/doc_src_sql-driver.cpp
index f25ad4f2b0..7983386642 100644
--- a/src/sql/doc/snippets/code/doc_src_sql-driver.cpp
+++ b/src/sql/doc/snippets/code/doc_src_sql-driver.cpp
@@ -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]
diff --git a/src/sql/doc/src/sql-driver.qdoc b/src/sql/doc/src/sql-driver.qdoc
index 364d733e92..57a53006c4 100644
--- a/src/sql/doc/src/sql-driver.qdoc
+++ b/src/sql/doc/src/sql-driver.qdoc
@@ -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.
diff --git a/src/sql/kernel/qsqldatabase.cpp b/src/sql/kernel/qsqldatabase.cpp
index 0416215870..eb3a3c444d 100644
--- a/src/sql/kernel/qsqldatabase.cpp
+++ b/src/sql/kernel/qsqldatabase.cpp
@@ -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
diff --git a/tests/auto/sql/kernel/qsqldatabase/tst_qsqldatabase.cpp b/tests/auto/sql/kernel/qsqldatabase/tst_qsqldatabase.cpp
index 8126e72ad2..a2e9140cc8 100644
--- a/tests/auto/sql/kernel/qsqldatabase/tst_qsqldatabase.cpp
+++ b/tests/auto/sql/kernel/qsqldatabase/tst_qsqldatabase.cpp
@@ -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"