diff options
-rw-r--r-- | src/plugins/sqldrivers/db2/qsql_db2.cpp | 2 | ||||
-rw-r--r-- | src/plugins/sqldrivers/ibase/qsql_ibase.cpp | 4 | ||||
-rw-r--r-- | src/plugins/sqldrivers/mysql/qsql_mysql.cpp | 2 | ||||
-rw-r--r-- | src/plugins/sqldrivers/oci/qsql_oci.cpp | 6 | ||||
-rw-r--r-- | src/plugins/sqldrivers/odbc/qsql_odbc.cpp | 2 | ||||
-rw-r--r-- | src/plugins/sqldrivers/psql/qsql_psql.cpp | 2 | ||||
-rw-r--r-- | src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp | 2 | ||||
-rw-r--r-- | src/sql/kernel/qsqlresult.cpp | 34 | ||||
-rw-r--r-- | src/sql/kernel/qsqlresult_p.h | 2 | ||||
-rw-r--r-- | tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp | 92 |
10 files changed, 136 insertions, 12 deletions
diff --git a/src/plugins/sqldrivers/db2/qsql_db2.cpp b/src/plugins/sqldrivers/db2/qsql_db2.cpp index e819d0ea86..050f9f7bd6 100644 --- a/src/plugins/sqldrivers/db2/qsql_db2.cpp +++ b/src/plugins/sqldrivers/db2/qsql_db2.cpp @@ -697,7 +697,7 @@ bool QDB2Result::exec() for (i = 0; i < values.count(); ++i) { // bind parameters - only positional binding allowed SQLLEN *ind = &indicators[i]; - if (values.at(i).isNull()) + if (QSqlResultPrivate::isVariantNull(values.at(i))) *ind = SQL_NULL_DATA; if (bindValueType(i) & QSql::Out) values[i].detach(); diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp index ba820a4416..d8cb3ae5f3 100644 --- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp +++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp @@ -968,7 +968,7 @@ bool QIBaseResult::exec() setAt(QSql::BeforeFirstRow); if (d->inda) { - QList<QVariant>& values = boundValues(); + const QList<QVariant> &values = boundValues(); int i; if (values.count() > d->inda->sqld) { qWarning() << QLatin1String("QIBaseResult::exec: Parameter mismatch, expected") << @@ -984,7 +984,7 @@ bool QIBaseResult::exec() continue; const QVariant val(values[i]); if (d->inda->sqlvar[para].sqltype & 1) { - if (val.isNull()) { + if (QSqlResultPrivate::isVariantNull(val)) { // set null indicator *(d->inda->sqlvar[para].sqlind) = -1; // and set the value to 0, otherwise it would count as empty string. diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp index e22677c03a..2d06d4ef69 100644 --- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp +++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp @@ -902,7 +902,7 @@ bool QMYSQLResult::exec() MYSQL_BIND* currBind = &d->outBinds[i]; - nullVector[i] = static_cast<my_bool>(val.isNull()); + nullVector[i] = static_cast<my_bool>(QSqlResultPrivate::isVariantNull(val)); currBind->is_null = &nullVector[i]; currBind->length = 0; currBind->is_unsigned = 0; diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp index 58628373da..359fd33d2f 100644 --- a/src/plugins/sqldrivers/oci/qsql_oci.cpp +++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp @@ -515,7 +515,7 @@ int QOCIResultPrivate::bindValues(QVariantList &values, IndicatorArray &indicato OCIBind * hbnd = nullptr; // Oracle handles these automatically sb2 *indPtr = &indicators[i]; - *indPtr = val.isNull() ? -1 : 0; + *indPtr = QSqlResultPrivate::isVariantNull(val) ? -1 : 0; bindValue(sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage); } @@ -1373,7 +1373,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a // not a list - create a deep-copy of the single value QOCIBatchColumn &singleCol = columns[i]; singleCol.indicators = new sb2[1]; - *singleCol.indicators = boundValues.at(i).isNull() ? -1 : 0; + *singleCol.indicators = QSqlResultPrivate::isVariantNull(boundValues.at(i)) ? -1 : 0; r = d->bindValue(d->sql, &singleCol.bindh, d->err, i, boundValues.at(i), singleCol.indicators, &tmpSizes[i], tmpStorage); @@ -1470,7 +1470,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a for (uint row = 0; row < col.recordCount; ++row) { const QVariant &val = boundValues.at(i).toList().at(row); - if (val.isNull() && !d->isOutValue(i)) { + if (QSqlResultPrivate::isVariantNull(val) && !d->isOutValue(i)) { columns[i].indicators[row] = -1; columns[i].lengths[row] = 0; } else { diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index 5a77c26b62..dc65e3a4e4 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -1419,7 +1419,7 @@ bool QODBCResult::exec() values[i].detach(); const QVariant &val = values.at(i); SQLLEN *ind = &indicators[i]; - if (val.isNull()) + if (QSqlResultPrivate::isVariantNull(val)) *ind = SQL_NULL_DATA; switch (val.typeId()) { case QMetaType::QDate: { diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp index 4c3d6ca13f..07cc29d78c 100644 --- a/src/plugins/sqldrivers/psql/qsql_psql.cpp +++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp @@ -856,7 +856,7 @@ static QString qCreateParamString(const QList<QVariant> &boundValues, const QSql QSqlField f; for (const QVariant &val : boundValues) { f.setMetaType(val.metaType()); - if (val.isNull()) + if (QSqlResultPrivate::isVariantNull(val)) f.clear(); else f.setValue(val); diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp index d13fb1787c..9f90d91e82 100644 --- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp @@ -501,7 +501,7 @@ bool QSQLiteResult::exec() res = SQLITE_OK; const QVariant &value = values.at(i); - if (value.isNull()) { + if (QSqlResultPrivate::isVariantNull(value)) { res = sqlite3_bind_null(d->stmt, i + 1); } else { switch (value.userType()) { diff --git a/src/sql/kernel/qsqlresult.cpp b/src/sql/kernel/qsqlresult.cpp index a948abb13e..568520cd5c 100644 --- a/src/sql/kernel/qsqlresult.cpp +++ b/src/sql/kernel/qsqlresult.cpp @@ -47,7 +47,9 @@ #include "qsqlfield.h" #include "qsqlrecord.h" #include "qsqlresult_p.h" +#include "quuid.h" #include "qvariant.h" +#include "qdatetime.h" #include "private/qsqldriver_p.h" #include <QDebug> @@ -619,6 +621,31 @@ bool QSqlResult::prepare(const QString& query) return true; // fake prepares should always succeed } +bool QSqlResultPrivate::isVariantNull(const QVariant &variant) +{ + if (variant.isNull()) + return true; + + switch (variant.typeId()) { + case qMetaTypeId<QString>(): + return static_cast<const QString*>(variant.constData())->isNull(); + case qMetaTypeId<QByteArray>(): + return static_cast<const QByteArray*>(variant.constData())->isNull(); + case qMetaTypeId<QDateTime>(): + return static_cast<const QDateTime*>(variant.constData())->isNull(); + case qMetaTypeId<QDate>(): + return static_cast<const QDate*>(variant.constData())->isNull(); + case qMetaTypeId<QTime>(): + return static_cast<const QTime*>(variant.constData())->isNull(); + case qMetaTypeId<QUuid>(): + return static_cast<const QUuid*>(variant.constData())->isNull(); + default: + break; + } + + return false; +} + /*! Executes the query, returning true if successful; otherwise returns false. @@ -639,7 +666,10 @@ bool QSqlResult::exec() holder = d->holders.at(i).holderName; val = d->values.value(d->indexes.value(holder).value(0,-1)); QSqlField f(QLatin1String(""), val.metaType()); - f.setValue(val); + if (QSqlResultPrivate::isVariantNull(val)) + f.setValue(QVariant()); + else + f.setValue(val); query = query.replace(d->holders.at(i).holderPos, holder.length(), driver()->formatValue(f)); } @@ -653,7 +683,7 @@ bool QSqlResult::exec() continue; QVariant var = d->values.value(idx); QSqlField f(QLatin1String(""), var.metaType()); - if (var.isNull()) + if (QSqlResultPrivate::isVariantNull(var)) f.clear(); else f.setValue(var); diff --git a/src/sql/kernel/qsqlresult_p.h b/src/sql/kernel/qsqlresult_p.h index 672f3ac59b..4aa2f2e838 100644 --- a/src/sql/kernel/qsqlresult_p.h +++ b/src/sql/kernel/qsqlresult_p.h @@ -133,6 +133,8 @@ public: bool active = false; bool isSel = false; bool forwardOnly = false; + + static bool isVariantNull(const QVariant &variant); }; QT_END_NAMESPACE diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp index 409038c909..cbc2cc2c1b 100644 --- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp +++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp @@ -64,6 +64,8 @@ private slots: void size(); void isNull_data() { generic_data(); } void isNull(); + void writeNull_data() { generic_data(); } + void writeNull(); void query_exec_data() { generic_data(); } void query_exec(); void execErrorRecovery_data() { generic_data(); } @@ -355,6 +357,7 @@ void tst_QSqlQuery::dropTestTables( QSqlDatabase db ) // drop all the table in case a testcase failed tablenames << qtest << qTableName("qtest_null", __FILE__, db) + << qTableName("qtest_writenull", __FILE__, db) << qTableName("qtest_blob", __FILE__, db) << qTableName("qtest_bittest", __FILE__, db) << qTableName("qtest_nullblob", __FILE__, db) @@ -1769,6 +1772,95 @@ void tst_QSqlQuery::isNull() QVERIFY(q.isNull("unknown")); } +void tst_QSqlQuery::writeNull() +{ + QFETCH(QString, dbName); + QSqlDatabase db = QSqlDatabase::database(dbName); + CHECK_DATABASE(db); + const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db); + + QSqlQuery q(db); + const QString tableName = qTableName("qtest_writenull", __FILE__, db); + + // the test data table is already used, so use a local hash to exercise the various + // cases from the QSqlResultPrivate::isVariantNull helper. Only PostgreSQL supports + // QUuid. + QMultiHash<QString, QVariant> nullableTypes = { + {"varchar(20)", QString("not null")}, + {"varchar(20)", QByteArray("not null")}, + {"date", QDateTime::currentDateTime()}, + {"date", QDate::currentDate()}, + {"date", QTime::currentTime()}, + }; + if (dbType == QSqlDriver::PostgreSQL) + nullableTypes["uuid"] = QUuid::createUuid(); + + // Helper to count rows with null values in the data column. + // Since QSqlDriver::QuerySize might not be supported, we have to count anyway + const auto countRowsWithNull = [&]{ + q.exec("select id, data from " + tableName + " where data is null"); + int size = 0; + while (q.next()) + ++size; + return size; + }; + + for (const auto &nullableType : nullableTypes.keys()) { + auto tableGuard = qScopeGuard([&]{ + q.exec("drop table " + tableName); + }); + const QVariant nonNullValue = nullableTypes.value(nullableType); + // some useful diagnostic output in case of any test failure + auto errorHandler = qScopeGuard([&]{ + qWarning() << "Test failure for data type" << nonNullValue.metaType().name(); + q.exec("select id, data from " + tableName); + while (q.next()) + qWarning() << q.value(0) << q.value(1); + }); + QString createQuery = "create table " + tableName + " (id int, data " + nullableType; + if (dbType == QSqlDriver::MSSqlServer || dbType == QSqlDriver::Sybase) + createQuery += " null"; + createQuery += ")"; + QVERIFY_SQL(q, exec(createQuery)); + + int expectedNullCount = 0; + // verify that inserting a non-null value works + QVERIFY_SQL(q, prepare("insert into " + tableName + " values(:id, :data)")); + q.bindValue(":id", expectedNullCount); + q.bindValue(":data", nonNullValue); + QVERIFY_SQL(q, exec()); + QCOMPARE(countRowsWithNull(), expectedNullCount); + + // verify that inserting using a null QVariant produces a null entry in the database + QVERIFY_SQL(q, prepare("insert into " + tableName + " values(:id, :data)")); + q.bindValue(":id", ++expectedNullCount); + q.bindValue(":data", QVariant()); + QVERIFY_SQL(q, exec()); + QCOMPARE(countRowsWithNull(), expectedNullCount); + + // verify that writing a null-value (but not a null-variant) produces a null entry in the database + const QMetaType nullableMetaType = nullableTypes.value(nullableType).metaType(); + // creating a QVariant with meta type and nullptr does create a null-QVariant. We want + // to explicitly create a non-null variant, so we have to pass in a default-constructed + // value as well (and make sure that the default value is also destroyed again, + // which is clumsy to do using std::unique_ptr with a custom deleter, so use another + // scope guard). + void* defaultData = nullableMetaType.create(); + const auto defaultTypeDeleter = qScopeGuard([&]{ nullableMetaType.destroy(defaultData); }); + const QVariant nullValueVariant(nullableMetaType, defaultData); + QVERIFY(!nullValueVariant.isNull()); + + QVERIFY_SQL(q, prepare("insert into " + tableName + " values(:id, :data)")); + q.bindValue(":id", ++expectedNullCount); + q.bindValue(":data", nullValueVariant); + QVERIFY_SQL(q, exec()); + QCOMPARE(countRowsWithNull(), expectedNullCount); + + // all tests passed for this type if we got here, so don't print diagnostics + errorHandler.dismiss(); + } +} + /*! TDS specific BIT field test */ void tst_QSqlQuery::tds_bitField() { |