From c9f77564df14f91276e9ca693a9b3f339dab9334 Mon Sep 17 00:00:00 2001 From: Bill King Date: Fri, 12 Jun 2009 10:29:44 +1000 Subject: Stage 1 of incorporating database level precision policy. Queries now ask the database object what precision they should be, allows for setting precision policies on sql models, where previously this was not available. Also fixes some failing precisionpolicy implementations, as well as working around the fact qstring can't convert from "123.00" to an int based type. --- src/sql/drivers/db2/qsql_db2.cpp | 7 +- src/sql/drivers/mysql/qsql_mysql.cpp | 30 +-- src/sql/drivers/mysql/qsql_mysql.h | 1 + src/sql/drivers/oci/qsql_oci.cpp | 280 +++++++++++++-------------- src/sql/drivers/odbc/qsql_odbc.cpp | 63 +++--- src/sql/drivers/psql/qsql_psql.cpp | 21 +- src/sql/drivers/sqlite/qsql_sqlite.cpp | 11 +- src/sql/drivers/sqlite2/qsql_sqlite2.cpp | 7 +- src/sql/kernel/qsqldatabase.cpp | 55 +++++- src/sql/kernel/qsqldatabase.h | 2 + src/sql/kernel/qsqldriver.cpp | 28 ++- src/sql/kernel/qsqldriver.h | 3 + src/sql/kernel/qsqlquery.cpp | 23 ++- src/sql/kernel/qsqlresult.cpp | 17 +- src/sql/kernel/qsqlresult.h | 1 + tests/auto/qsqldatabase/tst_qsqldatabase.cpp | 26 ++- 16 files changed, 327 insertions(+), 248 deletions(-) diff --git a/src/sql/drivers/db2/qsql_db2.cpp b/src/sql/drivers/db2/qsql_db2.cpp index c7cbc9b..b8f52f4 100644 --- a/src/sql/drivers/db2/qsql_db2.cpp +++ b/src/sql/drivers/db2/qsql_db2.cpp @@ -84,7 +84,7 @@ public: class QDB2ResultPrivate { public: - QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), hStmt(0), precisionPolicy(QSql::HighPrecision) + QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), hStmt(0) {} ~QDB2ResultPrivate() { @@ -107,7 +107,6 @@ public: SQLHANDLE hStmt; QSqlRecord recInf; QVector valueCache; - QSql::NumericalPrecisionPolicy precisionPolicy; }; static QString qFromTChar(SQLTCHAR* str) @@ -1147,10 +1146,6 @@ void QDB2Result::virtual_hook(int id, void *data) Q_ASSERT(data); *static_cast(data) = nextResult(); break; - case QSqlResult::SetNumericalPrecision: - Q_ASSERT(data); - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } diff --git a/src/sql/drivers/mysql/qsql_mysql.cpp b/src/sql/drivers/mysql/qsql_mysql.cpp index 82ed124..0ea2f96 100644 --- a/src/sql/drivers/mysql/qsql_mysql.cpp +++ b/src/sql/drivers/mysql/qsql_mysql.cpp @@ -167,12 +167,11 @@ class QMYSQLResultPrivate : public QObject { Q_OBJECT public: - QMYSQLResultPrivate(const QMYSQLDriver* dp) : driver(dp), result(0), + QMYSQLResultPrivate(const QMYSQLDriver* dp, const QMYSQLResult* d) : driver(dp), result(0), q(d), rowsAffected(0), hasBlobs(false) #if MYSQL_VERSION_ID >= 40108 , stmt(0), meta(0), inBinds(0), outBinds(0) #endif - , precisionPolicy(QSql::HighPrecision) { connect(dp, SIGNAL(destroyed()), this, SLOT(driverDestroyed())); } @@ -180,6 +179,7 @@ public: const QMYSQLDriver* driver; MYSQL_RES *result; MYSQL_ROW row; + const QMYSQLResult* q; int rowsAffected; @@ -209,7 +209,6 @@ public: MYSQL_BIND *inBinds; MYSQL_BIND *outBinds; #endif - QSql::NumericalPrecisionPolicy precisionPolicy; private Q_SLOTS: void driverDestroyed() { driver = NULL; } }; @@ -254,6 +253,7 @@ static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags) break; case FIELD_TYPE_FLOAT : case FIELD_TYPE_DOUBLE : + case FIELD_TYPE_DECIMAL : type = QVariant::Double; break; case FIELD_TYPE_DATE : @@ -277,7 +277,6 @@ static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags) default: case FIELD_TYPE_ENUM : case FIELD_TYPE_SET : - case FIELD_TYPE_DECIMAL : type = QVariant::String; break; } @@ -354,6 +353,7 @@ bool QMYSQLResultPrivate::bindInValues() while((fieldInfo = mysql_fetch_field(meta))) { QMyField &f = fields[i]; f.myField = fieldInfo; + f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags); if (qIsBlob(fieldInfo->type)) { // the size of a blob-field is available as soon as we call @@ -384,7 +384,7 @@ bool QMYSQLResultPrivate::bindInValues() QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db) : QSqlResult(db) { - d = new QMYSQLResultPrivate(db); + d = new QMYSQLResultPrivate(db, this); } QMYSQLResult::~QMYSQLResult() @@ -599,15 +599,16 @@ QVariant QMYSQLResult::data(int field) case QVariant::Double: { QVariant v; bool ok=false; - switch(d->precisionPolicy) { + double dbl = val.toDouble(&ok); + switch(numericalPrecisionPolicy()) { case QSql::LowPrecisionInt32: - v=val.toInt(&ok); + v=QVariant(dbl).toInt(); break; case QSql::LowPrecisionInt64: - v = val.toLongLong(&ok); + v = QVariant(dbl).toLongLong(); break; case QSql::LowPrecisionDouble: - v = val.toDouble(&ok); + v = QVariant(dbl); break; case QSql::HighPrecision: default: @@ -620,6 +621,7 @@ QVariant QMYSQLResult::data(int field) else return QVariant(); } + return QVariant(val.toDouble()); case QVariant::Date: return qDateFromString(val); case QVariant::Time: @@ -679,6 +681,7 @@ bool QMYSQLResult::reset (const QString& query) setSelect(numFields != 0); d->fields.resize(numFields); d->rowsAffected = mysql_affected_rows(d->driver->d->mysql); + if (isSelect()) { for(int i = 0; i < numFields; i++) { MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); @@ -813,10 +816,6 @@ void QMYSQLResult::virtual_hook(int id, void *data) Q_ASSERT(data); *static_cast(data) = nextResult(); break; - case QSqlResult::SetNumericalPrecision: - Q_ASSERT(data); - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } @@ -1148,7 +1147,6 @@ bool QMYSQLDriver::hasFeature(DriverFeature f) const case NamedPlaceholders: case BatchOperations: case SimpleLocking: - case LowPrecisionNumbers: case EventNotifications: case FinishQuery: return false; @@ -1156,6 +1154,7 @@ bool QMYSQLDriver::hasFeature(DriverFeature f) const case BLOB: case LastInsertId: case Unicode: + case LowPrecisionNumbers: return true; case PreparedQueries: case PositionalPlaceholders: @@ -1369,6 +1368,7 @@ QSqlRecord QMYSQLDriver::record(const QString& tablename) const return info; } MYSQL_FIELD* field; + while ((field = mysql_fetch_field(r))) info.append(qToField(field, d->tc)); mysql_free_result(r); @@ -1485,4 +1485,4 @@ bool QMYSQLDriver::isIdentifierEscapedImplementation(const QString &identifier, QT_END_NAMESPACE -#include "qsql_mysql.moc" \ No newline at end of file +#include "qsql_mysql.moc" diff --git a/src/sql/drivers/mysql/qsql_mysql.h b/src/sql/drivers/mysql/qsql_mysql.h index 31d9dcf..3c27d16 100644 --- a/src/sql/drivers/mysql/qsql_mysql.h +++ b/src/sql/drivers/mysql/qsql_mysql.h @@ -69,6 +69,7 @@ class QSqlRecordInfo; class QMYSQLResult : public QSqlResult { friend class QMYSQLDriver; + friend class QMYSQLResultPrivate; public: explicit QMYSQLResult(const QMYSQLDriver* db); ~QMYSQLResult(); diff --git a/src/sql/drivers/oci/qsql_oci.cpp b/src/sql/drivers/oci/qsql_oci.cpp index e6ee63d..0f45aad 100644 --- a/src/sql/drivers/oci/qsql_oci.cpp +++ b/src/sql/drivers/oci/qsql_oci.cpp @@ -148,7 +148,6 @@ struct QOCIResultPrivate bool transaction; int serverVersion; int prefetchRows, prefetchMem; - QSql::NumericalPrecisionPolicy precisionPolicy; void setCharset(OCIBind* hbnd); void setStatementAttributes(); @@ -405,7 +404,6 @@ struct QOCIDriverPrivate int serverVersion; ub4 prefetchRows; ub2 prefetchMem; - QSql::NumericalPrecisionPolicy precisionPolicy; QString user; void allocErrorHandle(); @@ -413,7 +411,7 @@ struct QOCIDriverPrivate QOCIDriverPrivate::QOCIDriverPrivate() : env(0), svc(0), srvhp(0), authp(0), err(0), transaction(false), serverVersion(-1), - prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM), precisionPolicy(QSql::HighPrecision) + prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM) { } @@ -621,136 +619,6 @@ static QSqlField qFromOraInf(const OraFieldInfo &ofi) return f; } -static OraFieldInfo qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) -{ - OraFieldInfo ofi; - ub2 colType(0); - text *colName = 0; - ub4 colNameLen(0); - sb1 colScale(0); - ub2 colLength(0); - ub2 colFieldLength(0); - sb2 colPrecision(0); - ub1 colIsNull(0); - int r(0); - QVariant::Type type(QVariant::Invalid); - - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colType, - 0, - OCI_ATTR_DATA_TYPE, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colName, - &colNameLen, - OCI_ATTR_NAME, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colLength, - 0, - OCI_ATTR_DATA_SIZE, /* in bytes */ - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - -#ifdef OCI_ATTR_CHAR_SIZE - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colFieldLength, - 0, - OCI_ATTR_CHAR_SIZE, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); -#else - // for Oracle8. - colFieldLength = colLength; -#endif - - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colPrecision, - 0, - OCI_ATTR_PRECISION, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colScale, - 0, - OCI_ATTR_SCALE, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colType, - 0, - OCI_ATTR_DATA_TYPE, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - r = OCIAttrGet(param, - OCI_DTYPE_PARAM, - &colIsNull, - 0, - OCI_ATTR_IS_NULL, - p->err); - if (r != 0) - qOraWarning("qMakeOraField:", p->err); - - type = qDecodeOCIType(colType, p->precisionPolicy); - - if (type == QVariant::Int) { - if (colLength == 22 && colPrecision == 0 && colScale == 0) - type = QVariant::String; - if (colScale > 0) - type = QVariant::String; - } - - // bind as double if the precision policy asks for it - if (((colType == SQLT_FLT) || (colType == SQLT_NUM)) - && (p->precisionPolicy == QSql::LowPrecisionDouble)) { - type = QVariant::Double; - } - - // bind as int32 or int64 if the precision policy asks for it - if ((colType == SQLT_NUM) || (colType == SQLT_VNU) || (colType == SQLT_UIN) - || (colType == SQLT_INT)) { - if (p->precisionPolicy == QSql::LowPrecisionInt64) - type = QVariant::LongLong; - else if (p->precisionPolicy == QSql::LowPrecisionInt32) - type = QVariant::Int; - } - - if (colType == SQLT_BLOB) - colLength = 0; - - // colNameLen is length in bytes - ofi.name = QString(reinterpret_cast(colName), colNameLen / 2); - ofi.type = type; - ofi.oraType = colType; - ofi.oraFieldLength = colFieldLength; - ofi.oraLength = colLength; - ofi.oraScale = colScale; - ofi.oraPrecision = colPrecision; - ofi.oraIsNull = colIsNull; - - return ofi; -} - - /*! \internal @@ -806,6 +674,7 @@ public: private: char* create(int position, int size); OCILobLocator ** createLobLocator(int position, OCIEnv* env); + OraFieldInfo qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const; class OraFieldInf { @@ -1137,6 +1006,135 @@ int QOCICols::readPiecewise(QVector &values, int index) return r; } +OraFieldInfo QOCICols::qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const +{ + OraFieldInfo ofi; + ub2 colType(0); + text *colName = 0; + ub4 colNameLen(0); + sb1 colScale(0); + ub2 colLength(0); + ub2 colFieldLength(0); + sb2 colPrecision(0); + ub1 colIsNull(0); + int r(0); + QVariant::Type type(QVariant::Invalid); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colName, + &colNameLen, + OCI_ATTR_NAME, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colLength, + 0, + OCI_ATTR_DATA_SIZE, /* in bytes */ + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + +#ifdef OCI_ATTR_CHAR_SIZE + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colFieldLength, + 0, + OCI_ATTR_CHAR_SIZE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); +#else + // for Oracle8. + colFieldLength = colLength; +#endif + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colPrecision, + 0, + OCI_ATTR_PRECISION, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colScale, + 0, + OCI_ATTR_SCALE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colIsNull, + 0, + OCI_ATTR_IS_NULL, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + type = qDecodeOCIType(colType, p->q->numericalPrecisionPolicy()); + + if (type == QVariant::Int) { + if (colLength == 22 && colPrecision == 0 && colScale == 0) + type = QVariant::String; + if (colScale > 0) + type = QVariant::String; + } + + // bind as double if the precision policy asks for it + if (((colType == SQLT_FLT) || (colType == SQLT_NUM)) + && (p->q->numericalPrecisionPolicy() == QSql::LowPrecisionDouble)) { + type = QVariant::Double; + } + + // bind as int32 or int64 if the precision policy asks for it + if ((colType == SQLT_NUM) || (colType == SQLT_VNU) || (colType == SQLT_UIN) + || (colType == SQLT_INT)) { + if (p->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt64) + type = QVariant::LongLong; + else if (p->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt32) + type = QVariant::Int; + } + + if (colType == SQLT_BLOB) + colLength = 0; + + // colNameLen is length in bytes + ofi.name = QString(reinterpret_cast(colName), colNameLen / 2); + ofi.type = type; + ofi.oraType = colType; + ofi.oraFieldLength = colFieldLength; + ofi.oraLength = colLength; + ofi.oraScale = colScale; + ofi.oraPrecision = colPrecision; + ofi.oraIsNull = colIsNull; + + return ofi; +} + struct QOCIBatchColumn { inline QOCIBatchColumn() @@ -1580,19 +1578,19 @@ void QOCICols::getValues(QVector &v, int index) case QVariant::Double: case QVariant::Int: case QVariant::LongLong: - if (d->precisionPolicy != QSql::HighPrecision) { - if ((d->precisionPolicy == QSql::LowPrecisionDouble) + if (d->q->numericalPrecisionPolicy() != QSql::HighPrecision) { + if ((d->q->numericalPrecisionPolicy() == QSql::LowPrecisionDouble) && (fld.typ == QVariant::Double)) { v[index + i] = *reinterpret_cast(fld.data); break; - } else if ((d->precisionPolicy == QSql::LowPrecisionInt64) + } else if ((d->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt64) && (fld.typ == QVariant::LongLong)) { qint64 qll = 0; OCINumberToInt(d->err, reinterpret_cast(fld.data), sizeof(qint64), OCI_NUMBER_SIGNED, &qll); v[index + i] = qll; break; - } else if ((d->precisionPolicy == QSql::LowPrecisionInt32) + } else if ((d->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt32) && (fld.typ == QVariant::Int)) { v[index + i] = *reinterpret_cast(fld.data); break; @@ -1618,8 +1616,7 @@ void QOCICols::getValues(QVector &v, int index) QOCIResultPrivate::QOCIResultPrivate(QOCIResult *result, const QOCIDriverPrivate *driver) : cols(0), q(result), env(driver->env), err(0), svc(const_cast(driver->svc)), sql(0), transaction(driver->transaction), serverVersion(driver->serverVersion), - prefetchRows(driver->prefetchRows), prefetchMem(driver->prefetchMem), - precisionPolicy(driver->precisionPolicy) + prefetchRows(driver->prefetchRows), prefetchMem(driver->prefetchMem) { int r = OCIHandleAlloc(env, reinterpret_cast(&err), @@ -1901,9 +1898,6 @@ void QOCIResult::virtual_hook(int id, void *data) case QSqlResult::BatchOperation: QOCICols::execBatch(d, boundValues(), *reinterpret_cast(data)); break; - case QSqlResult::SetNumericalPrecision: - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } @@ -2314,7 +2308,7 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const << QLatin1String("BINARY_DOUBLE"); if (buildRecordInfo) { do { - QVariant::Type ty = qDecodeOCIType(t.value(1).toString(),t.numericalPrecisionPolicy()); + QVariant::Type ty = qDecodeOCIType(t.value(1).toString(), t.numericalPrecisionPolicy()); QSqlField f(t.value(0).toString(), ty); f.setRequired(t.value(5).toString() == QLatin1String("N")); f.setPrecision(t.value(4).toInt()); diff --git a/src/sql/drivers/odbc/qsql_odbc.cpp b/src/sql/drivers/odbc/qsql_odbc.cpp index 8eaa8bf..f60c33c 100644 --- a/src/sql/drivers/odbc/qsql_odbc.cpp +++ b/src/sql/drivers/odbc/qsql_odbc.cpp @@ -127,7 +127,7 @@ class QODBCPrivate { public: QODBCPrivate() - : hEnv(0), hDbc(0), hStmt(0), useSchema(false), hasSQLFetchScroll(true), precisionPolicy(QSql::HighPrecision) + : hEnv(0), hDbc(0), hStmt(0), useSchema(false), hasSQLFetchScroll(true) { sql_char_type = sql_varchar_type = sql_longvarchar_type = QVariant::ByteArray; unicode = false; @@ -151,7 +151,6 @@ public: int fieldCacheIdx; int disconnectCount; bool hasSQLFetchScroll; - QSql::NumericalPrecisionPolicy precisionPolicy; bool isStmtHandleValid(const QSqlDriver *driver); void updateStmtHandleState(const QSqlDriver *driver); @@ -444,6 +443,26 @@ static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true) return uint(intbuf); } +static QVariant qGetDoubleData(SQLHANDLE hStmt, int column) +{ + SQLDOUBLE dblbuf; + SQLINTEGER 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; @@ -1109,29 +1128,21 @@ QVariant QODBCResult::data(int field) d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), true); 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; @@ -1628,10 +1639,6 @@ void QODBCResult::virtual_hook(int id, void *data) Q_ASSERT(data); *static_cast(data) = nextResult(); break; - case QSqlResult::SetNumericalPrecision: - Q_ASSERT(data); - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } diff --git a/src/sql/drivers/psql/qsql_psql.cpp b/src/sql/drivers/psql/qsql_psql.cpp index ce0b8c5..169a371 100644 --- a/src/sql/drivers/psql/qsql_psql.cpp +++ b/src/sql/drivers/psql/qsql_psql.cpp @@ -136,13 +136,12 @@ void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type) class QPSQLResultPrivate { public: - QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1), precisionPolicy(QSql::HighPrecision) {} + QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1) {} QPSQLResult *q; const QPSQLDriverPrivate *driver; PGresult *result; int currentSize; - QSql::NumericalPrecisionPolicy precisionPolicy; bool preparedQueriesEnabled; QString preparedStmtId; @@ -320,15 +319,16 @@ QVariant QPSQLResult::data(int i) return atoi(val); case QVariant::Double: if (ptype == QNUMERICOID) { - if (d->precisionPolicy != QSql::HighPrecision) { + if (numericalPrecisionPolicy() != QSql::HighPrecision) { QVariant retval; bool convert; - if (d->precisionPolicy == QSql::LowPrecisionInt64) - retval = QString::fromAscii(val).toLongLong(&convert); - else if (d->precisionPolicy == QSql::LowPrecisionInt32) - retval = QString::fromAscii(val).toInt(&convert); - else if (d->precisionPolicy == QSql::LowPrecisionDouble) - retval = QString::fromAscii(val).toDouble(&convert); + double dbl=QString::fromAscii(val).toDouble(&convert); + if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64) + retval = (qlonglong)dbl; + else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32) + retval = (int)dbl; + else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble) + retval = dbl; if (!convert) return QVariant(); return retval; @@ -467,9 +467,6 @@ void QPSQLResult::virtual_hook(int id, void *data) Q_ASSERT(data); switch (id) { - case QSqlResult::SetNumericalPrecision: - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } diff --git a/src/sql/drivers/sqlite/qsql_sqlite.cpp b/src/sql/drivers/sqlite/qsql_sqlite.cpp index 05d63ca..0f6fb25 100644 --- a/src/sql/drivers/sqlite/qsql_sqlite.cpp +++ b/src/sql/drivers/sqlite/qsql_sqlite.cpp @@ -115,13 +115,12 @@ public: uint skipRow: 1; // skip the next fetchNext()? uint utf8: 1; QSqlRecord rInf; - QSql::NumericalPrecisionPolicy precisionPolicy; }; static const uint initial_cache_size = 128; QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult* res) : q(res), access(0), - stmt(0), skippedStatus(false), skipRow(false), utf8(false), precisionPolicy(QSql::HighPrecision) + stmt(0), skippedStatus(false), skipRow(false), utf8(false) { } @@ -212,7 +211,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i values[i + idx] = sqlite3_column_int64(stmt, i); break; case SQLITE_FLOAT: - switch(precisionPolicy) { + switch(q->numericalPrecisionPolicy()) { case QSql::LowPrecisionInt32: values[i + idx] = sqlite3_column_int(stmt, i); break; @@ -289,10 +288,6 @@ void QSQLiteResult::virtual_hook(int id, void *data) if (d->stmt) sqlite3_reset(d->stmt); break; - case QSqlResult::SetNumericalPrecision: - Q_ASSERT(data); - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } @@ -481,11 +476,11 @@ bool QSQLiteDriver::hasFeature(DriverFeature f) const case PositionalPlaceholders: case SimpleLocking: case FinishQuery: + case LowPrecisionNumbers: return true; case QuerySize: case NamedPlaceholders: case BatchOperations: - case LowPrecisionNumbers: case EventNotifications: case MultipleResultSets: return false; diff --git a/src/sql/drivers/sqlite2/qsql_sqlite2.cpp b/src/sql/drivers/sqlite2/qsql_sqlite2.cpp index cb72ff0..bd7c367 100644 --- a/src/sql/drivers/sqlite2/qsql_sqlite2.cpp +++ b/src/sql/drivers/sqlite2/qsql_sqlite2.cpp @@ -114,13 +114,12 @@ public: uint skipRow: 1; // skip the next fetchNext()? uint utf8: 1; QSqlRecord rInf; - QSql::NumericalPrecisionPolicy precisionPolicy; }; static const uint initial_cache_size = 128; QSQLite2ResultPrivate::QSQLite2ResultPrivate(QSQLite2Result* res) : q(res), access(0), currentTail(0), - currentMachine(0), skippedStatus(false), skipRow(false), utf8(false), precisionPolicy(QSql::HighPrecision) + currentMachine(0), skippedStatus(false), skipRow(false), utf8(false) { } @@ -260,10 +259,6 @@ void QSQLite2Result::virtual_hook(int id, void *data) case QSqlResult::DetachFromResultSet: d->finalize(); break; - case QSqlResult::SetNumericalPrecision: - Q_ASSERT(data); - d->precisionPolicy = *reinterpret_cast(data); - break; default: QSqlResult::virtual_hook(id, data); } diff --git a/src/sql/kernel/qsqldatabase.cpp b/src/sql/kernel/qsqldatabase.cpp index a270c0e..990ad9a 100644 --- a/src/sql/kernel/qsqldatabase.cpp +++ b/src/sql/kernel/qsqldatabase.cpp @@ -129,11 +129,16 @@ Q_GLOBAL_STATIC(QConnectionDict, dbDict) class QSqlDatabasePrivate { public: - QSqlDatabasePrivate(QSqlDriver *dr = 0): + QSqlDatabasePrivate(QSqlDatabase *d, QSqlDriver *dr = 0): + q(d), driver(dr), port(-1) { ref = 1; + if(driver) + precisionPolicy = driver->numericalPrecisionPolicy(); + else + precisionPolicy= QSql::LowPrecisionDouble; } QSqlDatabasePrivate(const QSqlDatabasePrivate &other); ~QSqlDatabasePrivate(); @@ -142,6 +147,7 @@ public: void disable(); QAtomicInt ref; + QSqlDatabase *q; QSqlDriver* driver; QString dbname; QString uname; @@ -151,6 +157,7 @@ public: int port; QString connOptions; QString connName; + QSql::NumericalPrecisionPolicy precisionPolicy; static QSqlDatabasePrivate *shared_null(); static QSqlDatabase database(const QString& name, bool open); @@ -164,6 +171,7 @@ public: QSqlDatabasePrivate::QSqlDatabasePrivate(const QSqlDatabasePrivate &other) { ref = 1; + q = other.q; dbname = other.dbname; uname = other.uname; pword = other.pword; @@ -172,6 +180,7 @@ QSqlDatabasePrivate::QSqlDatabasePrivate(const QSqlDatabasePrivate &other) port = other.port; connOptions = other.connOptions; driver = other.driver; + precisionPolicy = other.precisionPolicy; } QSqlDatabasePrivate::~QSqlDatabasePrivate() @@ -216,7 +225,7 @@ DriverDict &QSqlDatabasePrivate::driverDict() QSqlDatabasePrivate *QSqlDatabasePrivate::shared_null() { static QSqlNullDriver dr; - static QSqlDatabasePrivate n(&dr); + static QSqlDatabasePrivate n(NULL, &dr); return &n; } @@ -281,6 +290,7 @@ QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open) */ void QSqlDatabasePrivate::copy(const QSqlDatabasePrivate *other) { + q = other->q; dbname = other->dbname; uname = other->uname; pword = other->pword; @@ -288,6 +298,7 @@ void QSqlDatabasePrivate::copy(const QSqlDatabasePrivate *other) drvName = other->drvName; port = other->port; connOptions = other->connOptions; + precisionPolicy = other->precisionPolicy; } void QSqlDatabasePrivate::disable() @@ -658,7 +669,7 @@ QStringList QSqlDatabase::connectionNames() QSqlDatabase::QSqlDatabase(const QString &type) { - d = new QSqlDatabasePrivate(); + d = new QSqlDatabasePrivate(this); d->init(type); } @@ -670,7 +681,7 @@ QSqlDatabase::QSqlDatabase(const QString &type) QSqlDatabase::QSqlDatabase(QSqlDriver *driver) { - d = new QSqlDatabasePrivate(driver); + d = new QSqlDatabasePrivate(this, driver); } /*! @@ -1469,6 +1480,42 @@ QString QSqlDatabase::connectionName() const return d->connName; } +/*! + + Sets the default numerical precision policy that queries use when created + on this database connection. + + Note: Drivers that don't support fetching numerical values with low + precision will ignore the precision policy. You can use + QSqlDriver::hasFeature() to find out whether a driver supports this + feature. + + Note: Setting the default precision policy doesn't affect any currently + active queries. + + \sa QSql::NumericalPrecisionPolicy, numericalPrecisionPolicy(), QSqlQuery::setNumericalPrecisionPolicy(), QSqlQuery::numericalPrecisionPolicy() +*/ +void QSqlDatabase::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy) +{ + if(driver()) + driver()->setNumericalPrecisionPolicy(precisionPolicy); + d->precisionPolicy = precisionPolicy; +} + +/*! + Returns the current default precision policy for the database connection. + + \sa QSql::NumericalPrecisionPolicy, setNumericalPrecisionPolicy(), QSqlQuery::numericalPrecisionPolicy(), QSqlQuery::setNumericalPrecisionPolicy() +*/ +QSql::NumericalPrecisionPolicy QSqlDatabase::numericalPrecisionPolicy() const +{ + if(driver()) + return driver()->numericalPrecisionPolicy(); + else + return d->precisionPolicy; +} + + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QSqlDatabase &d) { diff --git a/src/sql/kernel/qsqldatabase.h b/src/sql/kernel/qsqldatabase.h index ca6f0b0..638a8a5 100644 --- a/src/sql/kernel/qsqldatabase.h +++ b/src/sql/kernel/qsqldatabase.h @@ -120,6 +120,8 @@ public: int port() const; QString connectOptions() const; QString connectionName() const; + void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy); + QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const; QSqlDriver* driver() const; diff --git a/src/sql/kernel/qsqldriver.cpp b/src/sql/kernel/qsqldriver.cpp index 477d531..f7492c3 100644 --- a/src/sql/kernel/qsqldriver.cpp +++ b/src/sql/kernel/qsqldriver.cpp @@ -72,10 +72,11 @@ public: uint isOpen : 1; uint isOpenError : 1; QSqlError error; + QSql::NumericalPrecisionPolicy precisionPolicy; }; inline QSqlDriverPrivate::QSqlDriverPrivate() - : QObjectPrivate(), isOpen(false), isOpenError(false) + : QObjectPrivate(), isOpen(false), isOpenError(false), precisionPolicy(QSql::LowPrecisionDouble) { } @@ -912,4 +913,29 @@ QString QSqlDriver::stripDelimitersImplementation(const QString &identifier, Ide return ret; } +/*! + + Sets the default numerical precision policy that queries use when created + by this driver. + + Note: Setting the default precision policy doesn't affect any currently + active queries. + + \sa QSql::NumericalPrecisionPolicy, numericalPrecisionPolicy(), QSqlQuery::setNumericalPrecisionPolicy(), QSqlQuery::numericalPrecisionPolicy() +*/ +void QSqlDriver::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy) +{ + d_func()->precisionPolicy = precisionPolicy; +} + +/*! + Returns the current default precision policy for the database connection. + + \sa QSql::NumericalPrecisionPolicy, setNumericalPrecisionPolicy(), QSqlQuery::numericalPrecisionPolicy(), QSqlQuery::setNumericalPrecisionPolicy() +*/ +QSql::NumericalPrecisionPolicy QSqlDriver::numericalPrecisionPolicy() const +{ + return d_func()->precisionPolicy; +} + QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqldriver.h b/src/sql/kernel/qsqldriver.h index 8ac1471..1b52437 100644 --- a/src/sql/kernel/qsqldriver.h +++ b/src/sql/kernel/qsqldriver.h @@ -130,6 +130,9 @@ public: bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const; // ### Qt 5: make virtual QString stripDelimiters(const QString &identifier, IdentifierType type) const; // ### Qt 5: make virtual + void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy); + QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const; + Q_SIGNALS: void notification(const QString &name); diff --git a/src/sql/kernel/qsqlquery.cpp b/src/sql/kernel/qsqlquery.cpp index 2a07e28..2e531b4 100644 --- a/src/sql/kernel/qsqlquery.cpp +++ b/src/sql/kernel/qsqlquery.cpp @@ -61,7 +61,6 @@ public: ~QSqlQueryPrivate(); QAtomicInt ref; QSqlResult* sqlResult; - QSql::NumericalPrecisionPolicy precisionPolicy; static QSqlQueryPrivate* shared_null(); }; @@ -81,7 +80,7 @@ QSqlQueryPrivate* QSqlQueryPrivate::shared_null() \internal */ QSqlQueryPrivate::QSqlQueryPrivate(QSqlResult* result) - : ref(1), sqlResult(result), precisionPolicy(QSql::HighPrecision) + : ref(1), sqlResult(result) { if (!sqlResult) sqlResult = nullResult(); @@ -351,14 +350,14 @@ bool QSqlQuery::exec(const QString& query) if (d->ref != 1) { bool fo = isForwardOnly(); *this = QSqlQuery(driver()->createResult()); - d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy()); setForwardOnly(fo); } else { d->sqlResult->clear(); d->sqlResult->setActive(false); d->sqlResult->setLastError(QSqlError()); d->sqlResult->setAt(QSql::BeforeFirstRow); - d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy()); } d->sqlResult->setQuery(query.trimmed()); if (!driver()->isOpen() || driver()->isOpenError()) { @@ -891,12 +890,12 @@ bool QSqlQuery::prepare(const QString& query) bool fo = isForwardOnly(); *this = QSqlQuery(driver()->createResult()); setForwardOnly(fo); - d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy()); } else { d->sqlResult->setActive(false); d->sqlResult->setLastError(QSqlError()); d->sqlResult->setAt(QSql::BeforeFirstRow); - d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy()); } if (!driver()) { qWarning("QSqlQuery::prepare: no driver"); @@ -1126,10 +1125,10 @@ QVariant QSqlQuery::lastInsertId() const Instruct the database driver to return numerical values with a precision specified by \a precisionPolicy. - The Oracle driver, for example, retrieves numerical values as - strings by default to prevent the loss of precision. If the high - precision doesn't matter, use this method to increase execution - speed by bypassing string conversions. + The Oracle driver, for example, can retrieve numerical values as + strings to prevent the loss of precision. If high precision doesn't + matter, use this method to increase execution speed by bypassing + string conversions. Note: Drivers that don't support fetching numerical values with low precision will ignore the precision policy. You can use @@ -1144,7 +1143,7 @@ QVariant QSqlQuery::lastInsertId() const */ void QSqlQuery::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy) { - d->precisionPolicy = precisionPolicy; + d->sqlResult->setNumericalPrecisionPolicy(precisionPolicy); } /*! @@ -1154,7 +1153,7 @@ void QSqlQuery::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy preci */ QSql::NumericalPrecisionPolicy QSqlQuery::numericalPrecisionPolicy() const { - return d->precisionPolicy; + return d->sqlResult->numericalPrecisionPolicy(); } /*! diff --git a/src/sql/kernel/qsqlresult.cpp b/src/sql/kernel/qsqlresult.cpp index 180bfc7..a621b24 100644 --- a/src/sql/kernel/qsqlresult.cpp +++ b/src/sql/kernel/qsqlresult.cpp @@ -48,6 +48,7 @@ #include "qsqlresult.h" #include "qvector.h" #include "qsqldriver.h" +#include QT_BEGIN_NAMESPACE @@ -64,7 +65,7 @@ class QSqlResultPrivate public: QSqlResultPrivate(QSqlResult* d) : q(d), sqldriver(0), idx(QSql::BeforeFirstRow), active(false), - isSel(false), forwardOnly(false), bindCount(0), binds(QSqlResult::PositionalBinding) + isSel(false), forwardOnly(false), precisionPolicy(QSql::LowPrecisionDouble), bindCount(0), binds(QSqlResult::PositionalBinding) {} void clearValues() @@ -104,6 +105,7 @@ public: bool isSel; QSqlError error; bool forwardOnly; + QSql::NumericalPrecisionPolicy precisionPolicy; int bindCount; QSqlResult::BindingSyntax binds; @@ -249,6 +251,9 @@ QSqlResult::QSqlResult(const QSqlDriver *db) { d = new QSqlResultPrivate(this); d->sqldriver = db; + if(db) { + setNumericalPrecisionPolicy(db->numericalPrecisionPolicy()); + } } /*! @@ -965,8 +970,14 @@ void QSqlResult::detachFromResultSet() */ void QSqlResult::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy) { - if (driver()->hasFeature(QSqlDriver::LowPrecisionNumbers)) - virtual_hook(SetNumericalPrecision, &policy); + d->precisionPolicy = policy; +} + +/*! \internal + */ +QSql::NumericalPrecisionPolicy QSqlResult::numericalPrecisionPolicy() const +{ + return d->precisionPolicy; } /*! \internal diff --git a/src/sql/kernel/qsqlresult.h b/src/sql/kernel/qsqlresult.h index 0a3d8b9..30c933b 100644 --- a/src/sql/kernel/qsqlresult.h +++ b/src/sql/kernel/qsqlresult.h @@ -135,6 +135,7 @@ protected: bool execBatch(bool arrayBind = false); void detachFromResultSet(); void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy); + QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const; bool nextResult(); private: diff --git a/tests/auto/qsqldatabase/tst_qsqldatabase.cpp b/tests/auto/qsqldatabase/tst_qsqldatabase.cpp index 4fa3dc4..d8c21ac 100644 --- a/tests/auto/qsqldatabase/tst_qsqldatabase.cpp +++ b/tests/auto/qsqldatabase/tst_qsqldatabase.cpp @@ -1078,8 +1078,8 @@ void tst_QSqlDatabase::recordMySQL() FieldDef("bigint unsigned", QVariant::ULongLong, Q_UINT64_C(18446744073709551615)), FieldDef("float", QVariant::Double, 1.12345), FieldDef("double", QVariant::Double, 1.123456789), - FieldDef("decimal(10, 9)", QVariant::String,1.123456789), - FieldDef("numeric(5, 2)", QVariant::String, 123.67), + FieldDef("decimal(10, 9)", QVariant::Double,1.123456789), + FieldDef("numeric(5, 2)", QVariant::Double, 123.67), FieldDef("date", QVariant::Date, QDate::currentDate()), FieldDef("datetime", QVariant::DateTime, dt), FieldDef("timestamp", QVariant::DateTime, dt, false), @@ -1616,16 +1616,17 @@ void tst_QSqlDatabase::precisionPolicy() QSKIP("Driver or database doesn't support setting precision policy", SkipSingle); // Create a test table with some data - QVERIFY_SQL(q, exec(QString("CREATE TABLE %1 (id smallint, num numeric(20,0))").arg(tableName))); + QVERIFY_SQL(q, exec(QString("CREATE TABLE %1 (id smallint, num numeric(18,5))").arg(tableName))); QVERIFY_SQL(q, prepare(QString("INSERT INTO %1 VALUES (?, ?)").arg(tableName))); q.bindValue(0, 1); q.bindValue(1, 123); QVERIFY_SQL(q, exec()); q.bindValue(0, 2); - q.bindValue(1, QString("18500000000000000000")); + q.bindValue(1, QString("1850000000000.0001")); QVERIFY_SQL(q, exec()); // These are expected to pass + q.setNumericalPrecisionPolicy(QSql::HighPrecision); QString query = QString("SELECT num FROM %1 WHERE id = 1").arg(tableName); QVERIFY_SQL(q, exec(query)); QVERIFY_SQL(q, next()); @@ -1653,7 +1654,7 @@ void tst_QSqlDatabase::precisionPolicy() QVERIFY_SQL(q, exec(query)); QVERIFY_SQL(q, next()); QCOMPARE(q.value(0).type(), QVariant::Double); - QCOMPARE(q.value(0).toDouble(), QString("18500000000000000000").toDouble()); + QCOMPARE(q.value(0).toDouble(), QString("1850000000000.0001").toDouble()); // Postgres returns invalid QVariants on overflow q.setNumericalPrecisionPolicy(QSql::HighPrecision); @@ -1662,14 +1663,19 @@ void tst_QSqlDatabase::precisionPolicy() QCOMPARE(q.value(0).type(), QVariant::String); q.setNumericalPrecisionPolicy(QSql::LowPrecisionInt64); + QEXPECT_FAIL("QOCI", "Oracle fails here, to retrieve next", Continue); QVERIFY_SQL(q, exec(query)); QVERIFY_SQL(q, next()); - QCOMPARE(q.value(0).type(), QVariant::Invalid); + QCOMPARE(q.value(0).type(), QVariant::LongLong); - q.setNumericalPrecisionPolicy(QSql::LowPrecisionInt32); - QVERIFY_SQL(q, exec(query)); - QVERIFY_SQL(q, next()); - QCOMPARE(q.value(0).type(), QVariant::Invalid); + QSql::NumericalPrecisionPolicy oldPrecision= db.numericalPrecisionPolicy(); + db.setNumericalPrecisionPolicy(QSql::LowPrecisionDouble); + QSqlQuery q2(db); + q2.exec(QString("SELECT num FROM %1 WHERE id = 2").arg(tableName)); + QVERIFY_SQL(q2, exec(query)); + QVERIFY_SQL(q2, next()); + QCOMPARE(q2.value(0).type(), QVariant::Double); + db.setNumericalPrecisionPolicy(oldPrecision); } // This test needs a ODBC data source containing MYSQL in it's name -- cgit v0.12