summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/sqldrivers/db2/qsql_db2.cpp2
-rw-r--r--src/plugins/sqldrivers/ibase/qsql_ibase.cpp4
-rw-r--r--src/plugins/sqldrivers/mysql/qsql_mysql.cpp2
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci.cpp6
-rw-r--r--src/plugins/sqldrivers/odbc/qsql_odbc.cpp2
-rw-r--r--src/plugins/sqldrivers/psql/qsql_psql.cpp2
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp2
-rw-r--r--src/sql/kernel/qsqlresult.cpp34
-rw-r--r--src/sql/kernel/qsqlresult_p.h2
-rw-r--r--tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp92
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 e605ac91a2..00bcc1cc4a 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 caf406da46..837805cc46 100644
--- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
+++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
@@ -929,7 +929,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 5ba862f430..324e502f8c 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);
}
@@ -1372,7 +1372,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);
@@ -1469,7 +1469,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 1b3a55d096..dbbf984fa9 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()
{