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 <volker.hilsheimer@qt.io>
This commit is contained in:
parent
98e4e992fe
commit
4b7b5edf26
@ -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"),
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user