diff options
Diffstat (limited to 'src/sql/drivers/odbc')
-rw-r--r-- | src/sql/drivers/odbc/qsql_odbc.cpp | 250 | ||||
-rw-r--r-- | src/sql/drivers/odbc/qsql_odbc.h | 4 |
2 files changed, 187 insertions, 67 deletions
diff --git a/src/sql/drivers/odbc/qsql_odbc.cpp b/src/sql/drivers/odbc/qsql_odbc.cpp index 1c7675a..742d596 100644 --- a/src/sql/drivers/odbc/qsql_odbc.cpp +++ b/src/sql/drivers/odbc/qsql_odbc.cpp @@ -86,9 +86,11 @@ static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL class QODBCDriverPrivate { public: + enum DefaultCase{Lower, Mixed, Upper, Sensitive}; QODBCDriverPrivate() : hEnv(0), hDbc(0), useSchema(false), disconnectCount(0), isMySqlServer(false), - isMSSqlServer(false), hasSQLFetchScroll(true), hasMultiResultSets(false) + isMSSqlServer(false), hasSQLFetchScroll(true), hasMultiResultSets(false), + isQuoteInitialized(false), quote(QLatin1Char('"')) { unicode = false; } @@ -113,13 +115,19 @@ public: bool setConnectionOptions(const QString& connOpts); void splitTableQualifier(const QString &qualifier, QString &catalog, QString &schema, QString &table); + DefaultCase defaultCase() const; + QString adjustCase(const QString&) const; + QChar quoteChar(); +private: + bool isQuoteInitialized; + QChar quote; }; class QODBCPrivate { public: - QODBCPrivate() - : hEnv(0), hDbc(0), hStmt(0), useSchema(false), hasSQLFetchScroll(true), precisionPolicy(QSql::HighPrecision) + QODBCPrivate(QODBCDriverPrivate *dpp) + : hStmt(0), useSchema(false), hasSQLFetchScroll(true), driverPrivate(dpp) { unicode = false; } @@ -127,8 +135,8 @@ public: inline void clearValues() { fieldCache.fill(QVariant()); fieldCacheIdx = 0; } - SQLHANDLE hEnv; - SQLHANDLE hDbc; + SQLHANDLE dpEnv() const { return driverPrivate ? driverPrivate->hEnv : 0;} + SQLHANDLE dpDbc() const { return driverPrivate ? driverPrivate->hDbc : 0;} SQLHANDLE hStmt; uint unicode :1; @@ -139,7 +147,7 @@ public: int fieldCacheIdx; int disconnectCount; bool hasSQLFetchScroll; - QSql::NumericalPrecisionPolicy precisionPolicy; + QODBCDriverPrivate *driverPrivate; bool isStmtHandleValid(const QSqlDriver *driver); void updateStmtHandleState(const QSqlDriver *driver); @@ -201,14 +209,14 @@ static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode static QString qODBCWarn(const QODBCPrivate* odbc, int *nativeCode = 0) { - return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1String(" ") - + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc) + QLatin1String(" ") + return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->dpEnv()) + QLatin1Char(' ') + + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->dpDbc()) + QLatin1Char(' ') + qWarnODBCHandle(SQL_HANDLE_STMT, odbc->hStmt, nativeCode)); } static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0) { - return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1String(" ") + return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1Char(' ') + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc, nativeCode)); } @@ -240,6 +248,7 @@ static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, template<class T> static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, const T* p, bool isSigned = true) { + Q_UNUSED(p); QVariant::Type type = QVariant::Invalid; switch (sqltype) { case SQL_DECIMAL: @@ -430,6 +439,26 @@ static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true) return uint(intbuf); } +static QVariant qGetDoubleData(SQLHANDLE hStmt, int column) +{ + SQLDOUBLE dblbuf; + QSQLLEN lengthIndicator = 0; + SQLRETURN r = SQLGetData(hStmt, + column+1, + SQL_C_DOUBLE, + (SQLPOINTER) &dblbuf, + 0, + &lengthIndicator); + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + return QVariant(QVariant::Invalid); + } + if(lengthIndicator == SQL_NULL_DATA) + return QVariant(QVariant::Double); + + return (double) dblbuf; +} + + static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true) { SQLBIGINT lngbuf = 0; @@ -539,10 +568,31 @@ static int qGetODBCVersion(const QString &connOpts) #ifndef Q_ODBC_VERSION_2 if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive)) return SQL_OV_ODBC3; -#endif +#endif return SQL_OV_ODBC2; } +QChar QODBCDriverPrivate::quoteChar() +{ + if (!isQuoteInitialized) { + char driverResponse[4]; + SQLSMALLINT length; + int r = SQLGetInfo(hDbc, + SQL_IDENTIFIER_QUOTE_CHAR, + &driverResponse, + sizeof(driverResponse), + &length); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + quote = QLatin1Char(driverResponse[0]); + } else { + quote = QLatin1Char('"'); + } + isQuoteInitialized = true; + } + return quote; +} + + bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) { // Set any connection attributes @@ -693,14 +743,65 @@ void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString } } +QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const +{ + DefaultCase ret; + SQLUSMALLINT casing; + int r = SQLGetInfo(hDbc, + SQL_IDENTIFIER_CASE, + &casing, + sizeof(casing), + NULL); + if ( r != SQL_SUCCESS) + ret = Mixed;//arbitrary case if driver cannot be queried + else { + switch (casing) { + case (SQL_IC_UPPER): + ret = Upper; + break; + case (SQL_IC_LOWER): + ret = Lower; + break; + case (SQL_IC_SENSITIVE): + ret = Sensitive; + break; + case (SQL_IC_MIXED): + default: + ret = Mixed; + break; + } + } + return ret; +} + +/* + Adjust the casing of an identifier to match what the + database engine would have done to it. +*/ +QString QODBCDriverPrivate::adjustCase(const QString &identifier) const +{ + QString ret = identifier; + switch(defaultCase()) { + case (Lower): + ret = identifier.toLower(); + break; + case (Upper): + ret = identifier.toUpper(); + break; + case(Mixed): + case(Sensitive): + default: + ret = identifier; + } + return ret; +} + //////////////////////////////////////////////////////////////////////////// QODBCResult::QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p) : QSqlResult(db) { - d = new QODBCPrivate(); - d->hEnv = p->hEnv; - d->hDbc = p->hDbc; + d = new QODBCPrivate(p); d->unicode = p->unicode; d->useSchema = p->useSchema; d->disconnectCount = p->disconnectCount; @@ -738,7 +839,7 @@ bool QODBCResult::reset (const QString& query) } } r = SQLAllocHandle(SQL_HANDLE_STMT, - d->hDbc, + d->dpDbc(), &d->hStmt); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d); @@ -867,7 +968,7 @@ bool QODBCResult::fetchFirst() r = SQLFetchScroll(d->hStmt, SQL_FETCH_FIRST, 0); - if (r != SQL_SUCCESS) { + if (r != SQL_SUCCESS) { if (r != SQL_NO_DATA) setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to fetch first"), QSqlError::ConnectionError, d)); @@ -886,7 +987,7 @@ bool QODBCResult::fetchPrevious() r = SQLFetchScroll(d->hStmt, SQL_FETCH_PRIOR, 0); - if (r != SQL_SUCCESS) { + if (r != SQL_SUCCESS) { if (r != SQL_NO_DATA) setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to fetch previous"), QSqlError::ConnectionError, d)); @@ -917,7 +1018,7 @@ bool QODBCResult::fetchLast() r = SQLFetchScroll(d->hStmt, SQL_FETCH_LAST, 0); - if (r != SQL_SUCCESS) { + if (r != SQL_SUCCESS) { if (r != SQL_NO_DATA) setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to fetch last"), QSqlError::ConnectionError, d)); @@ -1011,29 +1112,21 @@ QVariant QODBCResult::data(int field) d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode); break; case QVariant::Double: - { - QString value=qGetStringData(d->hStmt, i, info.length(), false); - bool ok=false; - switch(d->precisionPolicy) { - case QSql::LowPrecisionInt32: - d->fieldCache[i] = value.toInt(&ok); - break; - case QSql::LowPrecisionInt64: - d->fieldCache[i] = value.toLongLong(&ok); - break; - case QSql::LowPrecisionDouble: - d->fieldCache[i] = value.toDouble(&ok); - break; - case QSql::HighPrecision: - default: - d->fieldCache[i] = value; - ok=true; - break; - } - if(ok==false) - d->fieldCache[i] = QVariant(); - break; + switch(numericalPrecisionPolicy()) { + case QSql::LowPrecisionInt32: + d->fieldCache[i] = qGetIntData(d->hStmt, i); + break; + case QSql::LowPrecisionInt64: + d->fieldCache[i] = qGetBigIntData(d->hStmt, i); + break; + case QSql::LowPrecisionDouble: + d->fieldCache[i] = qGetDoubleData(d->hStmt, i); + break; + case QSql::HighPrecision: + d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false); + break; } + break; default: d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false)); break; @@ -1087,7 +1180,7 @@ bool QODBCResult::prepare(const QString& query) } } r = SQLAllocHandle(SQL_HANDLE_STMT, - d->hDbc, + d->dpDbc(), &d->hStmt); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d); @@ -1360,7 +1453,7 @@ bool QODBCResult::exec() if (*ind != SQL_NULL_DATA) *ind = str.length(); int strSize = str.length(); - + r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], @@ -1531,10 +1624,6 @@ void QODBCResult::virtual_hook(int id, void *data) Q_ASSERT(data); *static_cast<bool*>(data) = nextResult(); break; - case QSqlResult::SetNumericalPrecision: - Q_ASSERT(data); - d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); - break; default: QSqlResult::virtual_hook(id, data); } @@ -1655,7 +1744,7 @@ bool QODBCDriver::open(const QString & db, // support the "DRIVER={SQL SERVER};SERVER=blah" syntax if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive)) connQStr = QLatin1String("FILEDSN=") + db; - else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive) + else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive) || db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive)) connQStr = db; else @@ -1665,7 +1754,7 @@ bool QODBCDriver::open(const QString & db, connQStr += QLatin1String(";UID=") + user; if (!password.isEmpty()) connQStr += QLatin1String(";PWD=") + password; - + SQLSMALLINT cb; SQLTCHAR connOut[1024]; r = SQLDriverConnect(d->hDbc, @@ -1688,7 +1777,7 @@ bool QODBCDriver::open(const QString & db, if (!d->checkDriver()) { setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all " - "needed functionality"), QSqlError::ConnectionError, d)); + "functionality required"), QSqlError::ConnectionError, d)); setOpenError(true); return false; } @@ -1752,14 +1841,7 @@ void QODBCDriverPrivate::checkUnicode() unicode = false; return; #endif -#if defined(Q_WS_WIN) - QT_WA( - {}, - { - unicode = false; - return; - }) -#endif + SQLRETURN r; SQLUINTEGER fFunc; @@ -2071,6 +2153,22 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const } QString catalog, schema, table; d->splitTableQualifier(tablename, catalog, schema, table); + + if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) + catalog = stripDelimiters(catalog, QSqlDriver::TableName); + else + catalog = d->adjustCase(catalog); + + if (isIdentifierEscaped(schema, QSqlDriver::TableName)) + schema = stripDelimiters(schema, QSqlDriver::TableName); + else + schema = d->adjustCase(schema); + + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + else + table = d->adjustCase(table); + r = SQLSetStmtAttr(hStmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, @@ -2173,6 +2271,22 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const SQLHANDLE hStmt; QString catalog, schema, table; d->splitTableQualifier(tablename, catalog, schema, table); + + if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) + catalog = stripDelimiters(catalog, QSqlDriver::TableName); + else + catalog = d->adjustCase(catalog); + + if (isIdentifierEscaped(schema, QSqlDriver::TableName)) + schema = stripDelimiters(schema, QSqlDriver::TableName); + else + schema = d->adjustCase(schema); + + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + else + table = d->adjustCase(table); + SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, d->hDbc, &hStmt); @@ -2280,20 +2394,22 @@ QVariant QODBCDriver::handle() const QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const { + QChar quote = d->quoteChar(); QString res = identifier; - if (d->isMySqlServer) { - if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('`')) && identifier.right(1) != QString(QLatin1Char('`')) ) { - res.prepend(QLatin1Char('`')).append(QLatin1Char('`')); - res.replace(QLatin1Char('.'), QLatin1String("`.`")); - } - } else { - if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { - res.replace(QLatin1Char('"'), QLatin1String("\"\"")); - res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); - res.replace(QLatin1Char('.'), QLatin1String("\".\"")); - } + if(!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) { + res.replace(quote, QString(quote)+QString(quote)); + res.prepend(quote).append(quote); + res.replace(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote)); } return res; } +bool QODBCDriver::isIdentifierEscapedImplementation(const QString &identifier, IdentifierType) const +{ + QChar quote = d->quoteChar(); + return identifier.size() > 2 + && identifier.startsWith(quote) //left delimited + && identifier.endsWith(quote); //right delimited +} + QT_END_NAMESPACE diff --git a/src/sql/drivers/odbc/qsql_odbc.h b/src/sql/drivers/odbc/qsql_odbc.h index 98e4fb0..359c890 100644 --- a/src/sql/drivers/odbc/qsql_odbc.h +++ b/src/sql/drivers/odbc/qsql_odbc.h @@ -145,10 +145,14 @@ public: QString escapeIdentifier(const QString &identifier, IdentifierType type) const; +protected Q_SLOTS: + bool isIdentifierEscapedImplementation(const QString &identifier, IdentifierType type) const; + protected: bool beginTransaction(); bool commitTransaction(); bool rollbackTransaction(); + private: void init(); bool endTrans(); |