Skip to content
Permalink
Browse files Browse the repository at this point in the history
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
66767ee 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

Change-Id: I3696381d0a93af8861ce2b7915f212d9e5e9a243
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 46af1fe)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
  • Loading branch information
marcmutz authored and Qt Cherry-pick Bot committed Feb 2, 2023
1 parent d4c76a6 commit aaf1381
Showing 1 changed file with 83 additions and 71 deletions.
154 changes: 83 additions & 71 deletions src/plugins/sqldrivers/odbc/qsql_odbc.cpp
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit aaf1381

Please sign in to comment.