SQL/ODBC: fix some users of toSQLTCHAR() to not assume identical UTF-8/16/32 string lengths

We already fixed the implementation of toSQLTCHAR() in
66767eea46 to not assume that a UTF-8 or
UTF-32-encoded string has the same number of code points as the
equivalent UTF-16 string, but it turns out that users of the function,
as well as other code, also failed to account for this.

This patch fixes callers of toSQLTCHAR() to use

    const auto encoded = toSQLTCHAR(s);
    ~~~ use encoded.data(), encoded.size() ~~~

(except we can't make `encoded` const, because the SQL API isn't
const-correct and takes void* instead of const void*) instead of the
anti-pattern

   ~~~ use toSQLTCHAR(s).data(), s.size() ~~~

As a drive-by:
- Extract Method qt_string_SQLSetConnectAttr()
  - skipping an unneeded .utf16() call (a NUL-terminated string is not
    required for calling toSQLTCHAR())
- de-duplicate some code in exec()
  - and make a comment there slightly more informative
- replace
  - NULL with nullptr
  - size() == 0 with isEmpty()
  - C-style with constructor-style casts

Pick-to: 6.5 6.4 6.2 5.15
Change-Id: I3696381d0a93af8861ce2b7915f212d9e5e9a243
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2023-01-30 15:37:13 +01:00
parent e73389874d
commit 46af1fe49f

View File

@ -745,6 +745,14 @@ QChar QODBCDriverPrivate::quoteChar()
return quote;
}
static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, const QString &val)
{
auto encoded = toSQLTCHAR(val);
return SQLSetConnectAttr(handle, attr,
encoded.data(),
SQLINTEGER(encoded.size() * sizeof(SQLTCHAR))); // size in bytes
}
bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
{
@ -780,10 +788,7 @@ bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == "SQL_ATTR_CURRENT_CATALOG"_L1) {
val.utf16(); // 0 terminate
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG,
toSQLTCHAR(val).data(),
SQLINTEGER(val.length() * sizeof(SQLTCHAR)));
r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, val);
} else if (opt.toUpper() == "SQL_ATTR_METADATA_ID"_L1) {
if (val.toUpper() == "SQL_TRUE"_L1) {
v = SQL_TRUE;
@ -798,10 +803,7 @@ bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == "SQL_ATTR_TRACEFILE"_L1) {
val.utf16(); // 0 terminate
r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE,
toSQLTCHAR(val).data(),
SQLINTEGER(val.length() * sizeof(SQLTCHAR)));
r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, val);
} else if (opt.toUpper() == "SQL_ATTR_TRACE"_L1) {
if (val.toUpper() == "SQL_OPT_TRACE_OFF"_L1) {
v = SQL_OPT_TRACE_OFF;
@ -1004,9 +1006,12 @@ bool QODBCResult::reset (const QString& query)
return false;
}
r = SQLExecDirect(d->hStmt,
toSQLTCHAR(query).data(),
(SQLINTEGER) query.length());
{
auto encoded = toSQLTCHAR(query);
r = SQLExecDirect(d->hStmt,
encoded.data(),
SQLINTEGER(encoded.size()));
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
@ -1355,9 +1360,12 @@ bool QODBCResult::prepare(const QString& query)
return false;
}
r = SQLPrepare(d->hStmt,
toSQLTCHAR(query).data(),
(SQLINTEGER) query.length());
{
auto encoded = toSQLTCHAR(query);
r = SQLPrepare(d->hStmt,
encoded.data(),
SQLINTEGER(encoded.size()));
}
if (r != SQL_SUCCESS) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
@ -1385,7 +1393,7 @@ bool QODBCResult::exec()
SQLCloseCursor(d->hStmt);
QVariantList &values = boundValues();
QByteArrayList tmpStorage(values.count(), QByteArray()); // holds temporary buffers
QByteArrayList tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter()
QVarLengthArray<SQLLEN, 32> indicators(values.count());
memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN));
@ -1600,36 +1608,36 @@ bool QODBCResult::exec()
case QMetaType::QString:
if (d->unicode) {
QByteArray &ba = tmpStorage[i];
QString str = val.toString();
{
const auto encoded = toSQLTCHAR(val.toString());
ba = QByteArray(reinterpret_cast<const char *>(encoded.data()),
encoded.size() * sizeof(SQLTCHAR));
}
if (*ind != SQL_NULL_DATA)
*ind = str.length() * sizeof(SQLTCHAR);
const qsizetype strSize = str.length() * sizeof(SQLTCHAR);
*ind = ba.size();
if (bindValueType(i) & QSql::Out) {
const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str));
ba = QByteArray((const char *)a.constData(), int(a.size() * sizeof(SQLTCHAR)));
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TCHAR,
strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
0, // god knows... don't change this!
0,
ba.data(),
const_cast<char *>(ba.constData()), // don't detach
ba.size(),
ind);
break;
}
ba = QByteArray(reinterpret_cast<const char *>(toSQLTCHAR(str).constData()),
int(strSize));
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TCHAR,
strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
strSize,
ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
ba.size(),
0,
const_cast<char *>(ba.constData()),
const_cast<char *>(ba.constData()), // don't detach
ba.size(),
ind);
break;
@ -1991,14 +1999,16 @@ bool QODBCDriver::open(const QString & db,
SQLSMALLINT cb;
QVarLengthArray<SQLTCHAR> connOut(1024);
memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR));
r = SQLDriverConnect(d->hDbc,
NULL,
toSQLTCHAR(connQStr).data(),
(SQLSMALLINT)connQStr.length(),
connOut.data(),
1024,
&cb,
/*SQL_DRIVER_NOPROMPT*/0);
{
auto encoded = toSQLTCHAR(connQStr);
r = SQLDriverConnect(d->hDbc,
nullptr,
encoded.data(), SQLSMALLINT(encoded.size()),
connOut.data(),
1024,
&cb,
/*SQL_DRIVER_NOPROMPT*/0);
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
@ -2377,17 +2387,15 @@ QStringList QODBCDriver::tables(QSql::TableType type) const
if (tableType.isEmpty())
return tl;
QString joinedTableTypeString = tableType.join(u',');
{
auto joinedTableTypeString = toSQLTCHAR(tableType.join(u','));
r = SQLTables(hStmt,
NULL,
0,
NULL,
0,
NULL,
0,
toSQLTCHAR(joinedTableTypeString).data(),
joinedTableTypeString.length() /* characters, not bytes */);
r = SQLTables(hStmt,
nullptr, 0,
nullptr, 0,
nullptr, 0,
joinedTableTypeString.data(), joinedTableTypeString.size());
}
if (r != SQL_SUCCESS)
qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1, d);
@ -2460,28 +2468,30 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
r = SQLPrimaryKeys(hStmt,
catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
catalog.length(),
schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
schema.length(),
toSQLTCHAR(table).data(),
table.length() /* in characters, not in bytes */);
{
auto c = toSQLTCHAR(catalog);
auto s = toSQLTCHAR(schema);
auto t = toSQLTCHAR(table);
r = SQLPrimaryKeys(hStmt,
catalog.isEmpty() ? nullptr : c.data(), c.size(),
schema.isEmpty() ? nullptr : s.data(), s.size(),
t.data(), t.size());
}
// if the SQLPrimaryKeys() call does not succeed (e.g the driver
// does not support it) - try an alternative method to get hold of
// the primary index (e.g MS Access and FoxPro)
if (r != SQL_SUCCESS) {
r = SQLSpecialColumns(hStmt,
SQL_BEST_ROWID,
catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
catalog.length(),
schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
schema.length(),
toSQLTCHAR(table).data(),
table.length(),
SQL_SCOPE_CURROW,
SQL_NULLABLE);
auto c = toSQLTCHAR(catalog);
auto s = toSQLTCHAR(schema);
auto t = toSQLTCHAR(table);
r = SQLSpecialColumns(hStmt,
SQL_BEST_ROWID,
catalog.isEmpty() ? nullptr : c.data(), c.size(),
schema.isEmpty() ? nullptr : s.data(), s.size(),
t.data(), t.size(),
SQL_SCOPE_CURROW,
SQL_NULLABLE);
if (r != SQL_SUCCESS) {
qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1, d);
@ -2562,15 +2572,17 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
r = SQLColumns(hStmt,
catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
catalog.length(),
schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
schema.length(),
toSQLTCHAR(table).data(),
table.length(),
NULL,
0);
{
auto c = toSQLTCHAR(catalog);
auto s = toSQLTCHAR(schema);
auto t = toSQLTCHAR(table);
r = SQLColumns(hStmt,
catalog.isEmpty() ? nullptr : c.data(), c.size(),
schema.isEmpty() ? nullptr : s.data(), s.size(),
t.data(), t.size(),
nullptr,
0);
}
if (r != SQL_SUCCESS)
qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, d);