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
|
#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)
|
QSQLiteDriver::QSQLiteDriver(QObject * parent)
|
||||||
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
|
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
|
||||||
{
|
{
|
||||||
@ -692,6 +716,7 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
|||||||
bool openUriOption = false;
|
bool openUriOption = false;
|
||||||
bool useExtendedResultCodes = true;
|
bool useExtendedResultCodes = true;
|
||||||
bool useQtVfs = false;
|
bool useQtVfs = false;
|
||||||
|
bool useQtCaseFolding = false;
|
||||||
#if QT_CONFIG(regularexpression)
|
#if QT_CONFIG(regularexpression)
|
||||||
static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1;
|
static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1;
|
||||||
bool defineRegexp = false;
|
bool defineRegexp = false;
|
||||||
@ -719,6 +744,8 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
|||||||
sharedCache = true;
|
sharedCache = true;
|
||||||
} else if (option == "QSQLITE_NO_USE_EXTENDED_RESULT_CODES"_L1) {
|
} else if (option == "QSQLITE_NO_USE_EXTENDED_RESULT_CODES"_L1) {
|
||||||
useExtendedResultCodes = false;
|
useExtendedResultCodes = false;
|
||||||
|
} else if (option == "QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING"_L1) {
|
||||||
|
useQtCaseFolding = true;
|
||||||
}
|
}
|
||||||
#if QT_CONFIG(regularexpression)
|
#if QT_CONFIG(regularexpression)
|
||||||
else if (option.startsWith(regexpConnectOption)) {
|
else if (option.startsWith(regexpConnectOption)) {
|
||||||
@ -760,6 +787,12 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
|
|||||||
nullptr, &_q_regexp_cleanup);
|
nullptr, &_q_regexp_cleanup);
|
||||||
}
|
}
|
||||||
#endif
|
#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;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
setLastError(qMakeError(d->access, tr("Error opening database"),
|
setLastError(qMakeError(d->access, tr("Error opening database"),
|
||||||
|
@ -757,6 +757,10 @@
|
|||||||
\li QSQLITE_NO_USE_EXTENDED_RESULT_CODES
|
\li QSQLITE_NO_USE_EXTENDED_RESULT_CODES
|
||||||
\li Disables the usage of the \l {https://www.sqlite.org/c3ref/extended_result_codes.html}
|
\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)
|
{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
|
\endtable
|
||||||
|
|
||||||
\section3 How to Build the QSQLITE Plugin
|
\section3 How to Build the QSQLITE Plugin
|
||||||
|
@ -118,12 +118,14 @@ public:
|
|||||||
if (port > 0)
|
if (port > 0)
|
||||||
cName += QLatin1Char(':') + QString::number(port);
|
cName += QLatin1Char(':') + QString::number(port);
|
||||||
|
|
||||||
|
QString opts = params;
|
||||||
if (driver == "QSQLITE") {
|
if (driver == "QSQLITE") {
|
||||||
// Since the database for sqlite is generated at runtime it's always
|
// Since the database for sqlite is generated at runtime it's always
|
||||||
// available, but we use QTempDir so it's always in a different
|
// available, but we use QTempDir so it's always in a different
|
||||||
// location. Thus, let's ignore the path completely.
|
// location. Thus, let's ignore the path completely.
|
||||||
cName = "SQLite";
|
cName = "SQLite";
|
||||||
qInfo("SQLite will use the database located at %ls", qUtf16Printable(dbName));
|
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);
|
auto db = QSqlDatabase::addDatabase(driver, cName);
|
||||||
@ -137,7 +139,7 @@ public:
|
|||||||
db.setPassword(passwd);
|
db.setPassword(passwd);
|
||||||
db.setHostName(host);
|
db.setHostName(host);
|
||||||
db.setPort(port);
|
db.setPort(port);
|
||||||
db.setConnectOptions(params);
|
db.setConnectOptions(opts);
|
||||||
dbNames.append(cName);
|
dbNames.append(cName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4454,7 +4454,7 @@ void tst_QSqlQuery::aggregateFunctionTypes()
|
|||||||
.arg(tableName)));
|
.arg(tableName)));
|
||||||
|
|
||||||
QVERIFY_SQL(q, exec("SELECT MAX(txt) FROM " + tableName));
|
QVERIFY_SQL(q, exec("SELECT MAX(txt) FROM " + tableName));
|
||||||
QVERIFY(q.next());
|
QVERIFY_SQL(q, next());
|
||||||
if (dbType == QSqlDriver::SQLite)
|
if (dbType == QSqlDriver::SQLite)
|
||||||
QCOMPARE(q.record().field(0).metaType().id(), QMetaType::UnknownType);
|
QCOMPARE(q.record().field(0).metaType().id(), QMetaType::UnknownType);
|
||||||
else
|
else
|
||||||
@ -4469,6 +4469,19 @@ void tst_QSqlQuery::aggregateFunctionTypes()
|
|||||||
QVERIFY(q.next());
|
QVERIFY(q.next());
|
||||||
QCOMPARE(q.value(0).toString(), QLatin1String("upper"));
|
QCOMPARE(q.value(0).toString(), QLatin1String("upper"));
|
||||||
QCOMPARE(q.record().field(0).metaType().id(), QMetaType::QString);
|
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