summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Ehrlicher <[email protected]>2023-03-27 21:20:43 +0200
committerChristian Ehrlicher <[email protected]>2023-07-08 00:08:44 +0200
commit4b7b5edf26b895932d0bee2d3315989c41c2d283 (patch)
treeda4b7ff71c73684e809ae518d4c00262a44ec430
parent98e4e992fee5152912852fb686fa3a9e546853f2 (diff)
SQL/SQLite: add case folding for non-ascii characters
SQLite does not provide a proper case folding for non-ascii characters due to a lack of a proper ICU library. Therefore add an option so Qt can do it for SQLite. [ChangeLog][SQL][SQLite] Add new option QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING for correct case folding of non-ascii characters. Fixes: QTBUG-18871 Change-Id: Ib62fedf750f05e50a581604253cf30d81e367b42 Reviewed-by: Volker Hilsheimer <[email protected]>
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp33
-rw-r--r--src/sql/doc/src/sql-driver.qdoc4
-rw-r--r--tests/auto/sql/kernel/qsqldatabase/tst_databases.h4
-rw-r--r--tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp15
4 files changed, 54 insertions, 2 deletions
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
index c5d1e7a2c71..92fee0abda9 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
@@ -625,6 +625,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)
{
@@ -692,6 +716,7 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
bool openUriOption = false;
bool useExtendedResultCodes = true;
bool useQtVfs = false;
+ bool useQtCaseFolding = false;
#if QT_CONFIG(regularexpression)
static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1;
bool defineRegexp = false;
@@ -719,6 +744,8 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
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;
}
#if QT_CONFIG(regularexpression)
else if (option.startsWith(regexpConnectOption)) {
@@ -760,6 +787,12 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
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"),
diff --git a/src/sql/doc/src/sql-driver.qdoc b/src/sql/doc/src/sql-driver.qdoc
index 3cac1e8fe31..97adae5e59e 100644
--- a/src/sql/doc/src/sql-driver.qdoc
+++ b/src/sql/doc/src/sql-driver.qdoc
@@ -757,6 +757,10 @@
\li QSQLITE_NO_USE_EXTENDED_RESULT_CODES
\li Disables the usage of the \l {https://www.sqlite.org/c3ref/extended_result_codes.html}
{extended result code} feature in SQLite (for backwards compatibility)
+ \row
+ \li QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING
+ \li If set, the plugin replaces the functions 'lower' and 'upper' with
+ QString functions for correct case folding of non-ascii characters
\endtable
\section3 How to Build the QSQLITE Plugin
diff --git a/tests/auto/sql/kernel/qsqldatabase/tst_databases.h b/tests/auto/sql/kernel/qsqldatabase/tst_databases.h
index 0debe5d5495..87efdb4441a 100644
--- a/tests/auto/sql/kernel/qsqldatabase/tst_databases.h
+++ b/tests/auto/sql/kernel/qsqldatabase/tst_databases.h
@@ -118,12 +118,14 @@ public:
if (port > 0)
cName += QLatin1Char(':') + QString::number(port);
+ QString opts = params;
if (driver == "QSQLITE") {
// Since the database for sqlite is generated at runtime it's always
// available, but we use QTempDir so it's always in a different
// location. Thus, let's ignore the path completely.
cName = "SQLite";
qInfo("SQLite will use the database located at %ls", qUtf16Printable(dbName));
+ opts += QStringLiteral(";QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING");
}
auto db = QSqlDatabase::addDatabase(driver, cName);
@@ -137,7 +139,7 @@ public:
db.setPassword(passwd);
db.setHostName(host);
db.setPort(port);
- db.setConnectOptions(params);
+ db.setConnectOptions(opts);
dbNames.append(cName);
}
diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
index 7ab17802b92..6f471cb55e6 100644
--- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
+++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
@@ -4454,7 +4454,7 @@ void tst_QSqlQuery::aggregateFunctionTypes()
.arg(tableName)));
QVERIFY_SQL(q, exec("SELECT MAX(txt) FROM " + tableName));
- QVERIFY(q.next());
+ QVERIFY_SQL(q, next());
if (dbType == QSqlDriver::SQLite)
QCOMPARE(q.record().field(0).metaType().id(), QMetaType::UnknownType);
else
@@ -4469,6 +4469,19 @@ void tst_QSqlQuery::aggregateFunctionTypes()
QVERIFY(q.next());
QCOMPARE(q.value(0).toString(), QLatin1String("upper"));
QCOMPARE(q.record().field(0).metaType().id(), QMetaType::QString);
+
+ QVERIFY_SQL(q, exec(QLatin1String("DELETE FROM %1").arg(tableName)));
+ QVERIFY_SQL(q, exec(QString::fromUtf8("INSERT INTO %1 (id, txt) VALUES (1, 'löW€RÄ')")
+ .arg(tableName)));
+ QVERIFY_SQL(q, exec("SELECT LOWER(txt) FROM " + tableName));
+ QVERIFY(q.next());
+ QCOMPARE(q.value(0).toString(), QString::fromUtf8("löw€rä"));
+ QCOMPARE(q.record().field(0).metaType().id(), QMetaType::QString);
+
+ QVERIFY_SQL(q, exec("SELECT UPPER(txt) FROM " + tableName));
+ QVERIFY(q.next());
+ QCOMPARE(q.value(0).toString(), QString::fromUtf8("LÖW€RÄ"));
+ QCOMPARE(q.record().field(0).metaType().id(), QMetaType::QString);
}
}