diff options
author | Christian Ehrlicher <[email protected]> | 2023-03-27 21:20:43 +0200 |
---|---|---|
committer | Christian Ehrlicher <[email protected]> | 2023-07-08 00:08:44 +0200 |
commit | 4b7b5edf26b895932d0bee2d3315989c41c2d283 (patch) | |
tree | da4b7ff71c73684e809ae518d4c00262a44ec430 | |
parent | 98e4e992fee5152912852fb686fa3a9e546853f2 (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.cpp | 33 | ||||
-rw-r--r-- | src/sql/doc/src/sql-driver.qdoc | 4 | ||||
-rw-r--r-- | tests/auto/sql/kernel/qsqldatabase/tst_databases.h | 4 | ||||
-rw-r--r-- | tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp | 15 |
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); } } |