sqlite: Support using execBatch() with duplicated named placeholders
Also expands the tst_qsqlquery::batchExec() test to account for this case and generally test the functionality. In addition it is made to be more robust to avoid any discrepencies with the testing data. The test in general is also cleaned up to enable more of it being tested with the different database drivers where possible. An expected fail is added for MySQL due to the fact that it has a bug where null timestamp entries are being converted to the current datetime when adding it as a bind value. Change-Id: I0061bd1c69ae35b4858afc49420f13ce59cf48ae Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
parent
c2373fce3c
commit
7775eb248a
@ -56,6 +56,7 @@
|
||||
#include <qregularexpression.h>
|
||||
#endif
|
||||
#include <QTimeZone>
|
||||
#include <QScopedValueRollback>
|
||||
|
||||
#if defined Q_OS_WIN
|
||||
# include <qt_windows.h>
|
||||
@ -129,6 +130,7 @@ protected:
|
||||
bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) override;
|
||||
bool reset(const QString &query) override;
|
||||
bool prepare(const QString &query) override;
|
||||
bool execBatch(bool arrayBind) override;
|
||||
bool exec() override;
|
||||
int size() override;
|
||||
int numRowsAffected() override;
|
||||
@ -443,6 +445,29 @@ static QString timespecToString(const QDateTime &dateTime)
|
||||
}
|
||||
}
|
||||
|
||||
bool QSQLiteResult::execBatch(bool arrayBind)
|
||||
{
|
||||
Q_UNUSED(arrayBind);
|
||||
Q_D(QSqlResult);
|
||||
QScopedValueRollback<QVector<QVariant>> valuesScope(d->values);
|
||||
QVector<QVariant> values = d->values;
|
||||
if (values.count() == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < values.at(0).toList().count(); ++i) {
|
||||
d->values.clear();
|
||||
QScopedValueRollback<QHash<QString, QVector<int>>> indexesScope(d->indexes);
|
||||
QHash<QString, QVector<int>>::const_iterator it = d->indexes.constBegin();
|
||||
while (it != d->indexes.constEnd()) {
|
||||
bindValue(it.key(), values.at(it.value().first()).toList().at(i), QSql::In);
|
||||
++it;
|
||||
}
|
||||
if (!exec())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QSQLiteResult::exec()
|
||||
{
|
||||
Q_D(QSQLiteResult);
|
||||
|
@ -359,7 +359,6 @@ void tst_QSqlQuery::dropTestTables( QSqlDatabase db )
|
||||
<< qTableName("more_results", __FILE__, db)
|
||||
<< qTableName("blobstest", __FILE__, db)
|
||||
<< qTableName("oraRowId", __FILE__, db)
|
||||
<< qTableName("qtest_batch", __FILE__, db)
|
||||
<< qTableName("bug43874", __FILE__, db)
|
||||
<< qTableName("bug6421", __FILE__, db).toUpper()
|
||||
<< qTableName("bug5765", __FILE__, db)
|
||||
@ -2439,76 +2438,103 @@ void tst_QSqlQuery::batchExec()
|
||||
QSqlDatabase db = QSqlDatabase::database( dbName );
|
||||
CHECK_DATABASE( db );
|
||||
|
||||
if ( !db.driver()->hasFeature( QSqlDriver::BatchOperations ) )
|
||||
QSKIP( "Database can't do BatchOperations");
|
||||
|
||||
QSqlQuery q( db );
|
||||
const QString tableName = qTableName("qtest_batch", __FILE__, db);
|
||||
tst_Databases::safeDropTable(db, tableName);
|
||||
QVERIFY_SQL(q, exec(QStringLiteral("create table ") + tableName +
|
||||
QStringLiteral(" (id int, name varchar(20), dt date, num numeric(8, 4), "
|
||||
"dtstamp TIMESTAMP(3), extraId int, extraName varchar(20))")));
|
||||
|
||||
QVERIFY_SQL(q, exec("create table " + tableName + " (id int, name varchar(20), dt date, num numeric(8, 4), dtstamp TIMESTAMP(3))"));
|
||||
QVERIFY_SQL(q, prepare("insert into " + tableName + " (id, name, dt, num, dtstamp) values (?, ?, ?, ?, ?)"));
|
||||
|
||||
QVariantList intCol;
|
||||
intCol << 1 << 2 << QVariant( QVariant::Int );
|
||||
|
||||
QVariantList charCol;
|
||||
charCol << QLatin1String( "harald" ) << QLatin1String( "boris" ) << QVariant( QVariant::String );
|
||||
|
||||
QVariantList dateCol;
|
||||
QDateTime dt = QDateTime( QDate::currentDate(), QTime( 1, 2, 3 ) );
|
||||
dateCol << dt << dt.addDays( -1 ) << QVariant( QVariant::DateTime );
|
||||
|
||||
QVariantList numCol;
|
||||
numCol << 2.3 << 3.4 << QVariant( QVariant::Double );
|
||||
|
||||
QVariantList timeStampCol;
|
||||
const QDateTime dtStamp = QDateTime(QDate::currentDate(), QTime(1, 2, 3, 4));
|
||||
timeStampCol << dtStamp << dtStamp.addDays(-1) << QVariant(QVariant::DateTime);
|
||||
const QVariantList intCol = { 1, 2, QVariant(QVariant::Int) };
|
||||
const QVariantList charCol = { QStringLiteral("harald"), QStringLiteral("boris"),
|
||||
QVariant(QVariant::String) };
|
||||
const QDateTime currentDateTime = QDateTime(QDateTime::currentDateTime());
|
||||
const QVariantList dateCol = { currentDateTime.date(), currentDateTime.date().addDays(-1),
|
||||
QVariant(QVariant::Date) };
|
||||
const QVariantList numCol = { 2.3, 3.4, QVariant(QVariant::Double) };
|
||||
const QVariantList timeStampCol = { currentDateTime, currentDateTime.addDays(-1),
|
||||
QVariant(QVariant::DateTime) };
|
||||
|
||||
// Test with positional placeholders
|
||||
QVERIFY_SQL(q, prepare(QStringLiteral("insert into ") + tableName +
|
||||
QStringLiteral(" (id, name, dt, num, dtstamp, extraId, extraName) values "
|
||||
"(?, ?, ?, ?, ?, ?, ?)")));
|
||||
q.addBindValue(intCol);
|
||||
q.addBindValue( charCol );
|
||||
q.addBindValue( dateCol );
|
||||
q.addBindValue( numCol );
|
||||
q.addBindValue(timeStampCol);
|
||||
q.addBindValue(intCol);
|
||||
q.addBindValue(charCol);
|
||||
|
||||
QVERIFY_SQL( q, execBatch() );
|
||||
QVERIFY_SQL(q, exec("select id, name, dt, num, dtstamp from " + tableName + " order by id"));
|
||||
QVERIFY_SQL(q, exec(QStringLiteral("select id, name, dt, num, dtstamp, "
|
||||
"extraId, extraName from ") + tableName));
|
||||
|
||||
QVERIFY( q.next() );
|
||||
QCOMPARE( q.value( 0 ).toInt(), 1 );
|
||||
QCOMPARE( q.value( 1 ).toString(), QString( "harald" ) );
|
||||
QCOMPARE( q.value( 2 ).toDateTime(), dt );
|
||||
QCOMPARE( q.value( 3 ).toDouble(), 2.3 );
|
||||
QCOMPARE(q.value(4).toDateTime(), dtStamp);
|
||||
for (int i = 0; i < intCol.size(); ++i) {
|
||||
QVERIFY(q.next());
|
||||
QCOMPARE(q.value(0).toInt(), intCol.at(i));
|
||||
QCOMPARE(q.value(1).toString(), charCol.at(i));
|
||||
QCOMPARE(q.value(2).toDate(), dateCol.at(i));
|
||||
QCOMPARE(q.value(3).toDouble(), numCol.at(i));
|
||||
if (tst_Databases::getDatabaseType(db) == QSqlDriver::MySqlServer && timeStampCol.at(i).isNull()) {
|
||||
QEXPECT_FAIL("", "This appears to be a bug in MySQL as it converts null datetimes to the "
|
||||
"current datetime for a timestamp field", Continue);
|
||||
}
|
||||
QCOMPARE(q.value(4).toDateTime(), timeStampCol.at(i));
|
||||
QCOMPARE(q.value(5).toInt(), intCol.at(i));
|
||||
QCOMPARE(q.value(6).toString(), charCol.at(i));
|
||||
}
|
||||
|
||||
QVERIFY( q.next() );
|
||||
QCOMPARE( q.value( 0 ).toInt(), 2 );
|
||||
QCOMPARE( q.value( 1 ).toString(), QString( "boris" ) );
|
||||
QCOMPARE( q.value( 2 ).toDateTime(), dt.addDays( -1 ) );
|
||||
QCOMPARE( q.value( 3 ).toDouble(), 3.4 );
|
||||
QCOMPARE(q.value(4).toDateTime(), dtStamp.addDays(-1));
|
||||
// Empty table ready for retesting with duplicated named placeholders
|
||||
QVERIFY_SQL(q, exec(QStringLiteral("delete from ") + tableName));
|
||||
QVERIFY_SQL(q, prepare(QStringLiteral("insert into ") + tableName +
|
||||
QStringLiteral(" (id, name, dt, num, dtstamp, extraId, extraName) "
|
||||
"values (:id, :name, :dt, :num, :dtstamp, :id, :name)")));
|
||||
q.bindValue(":id", intCol);
|
||||
q.bindValue(":name", charCol);
|
||||
q.bindValue(":dt", dateCol);
|
||||
q.bindValue(":num", numCol);
|
||||
q.bindValue(":dtstamp", timeStampCol);
|
||||
|
||||
QVERIFY( q.next() );
|
||||
QVERIFY( q.value( 0 ).isNull() );
|
||||
QVERIFY( q.value( 1 ).isNull() );
|
||||
QVERIFY( q.value( 2 ).isNull() );
|
||||
QVERIFY( q.value( 3 ).isNull() );
|
||||
QVERIFY(q.value(4).isNull());
|
||||
|
||||
const QString procName = qTableName("qtest_batch_proc", __FILE__, db);
|
||||
QVERIFY_SQL(q, exec("create or replace procedure " + procName + " (x in timestamp, y out timestamp) is\n"
|
||||
"begin\n"
|
||||
" y := x;\n"
|
||||
"end;\n"));
|
||||
QVERIFY(q.prepare("call " + procName + "(?, ?)"));
|
||||
q.addBindValue(timeStampCol, QSql::In);
|
||||
QVariantList emptyDateTimes;
|
||||
emptyDateTimes.reserve(timeStampCol.size());
|
||||
for (int i = 0; i < timeStampCol.size(); i++)
|
||||
emptyDateTimes << QVariant(QDateTime());
|
||||
q.addBindValue(emptyDateTimes, QSql::Out);
|
||||
QVERIFY_SQL(q, execBatch());
|
||||
QCOMPARE(q.boundValue(1).toList(), timeStampCol);
|
||||
QVERIFY_SQL(q, exec(QStringLiteral("select id, name, dt, num, dtstamp, extraId, extraName from ") +
|
||||
tableName));
|
||||
|
||||
for (int i = 0; i < intCol.size(); ++i) {
|
||||
QVERIFY(q.next());
|
||||
QCOMPARE(q.value(0).toInt(), intCol.at(i));
|
||||
QCOMPARE(q.value(1).toString(), charCol.at(i));
|
||||
QCOMPARE(q.value(2).toDate(), dateCol.at(i));
|
||||
QCOMPARE(q.value(3).toDouble(), numCol.at(i));
|
||||
if (tst_Databases::getDatabaseType(db) == QSqlDriver::MySqlServer && timeStampCol.at(i).isNull()) {
|
||||
QEXPECT_FAIL("", "This appears to be a bug in MySQL as it converts null datetimes to the "
|
||||
"current datetime for a timestamp field", Continue);
|
||||
}
|
||||
QCOMPARE(q.value(4).toDateTime(), timeStampCol.at(i));
|
||||
QCOMPARE(q.value(5).toInt(), intCol.at(i));
|
||||
QCOMPARE(q.value(6).toString(), charCol.at(i));
|
||||
}
|
||||
|
||||
// Only test the prepared stored procedure approach where the driver has support
|
||||
// for batch operations as this will not work without it
|
||||
if (db.driver()->hasFeature(QSqlDriver::BatchOperations)) {
|
||||
const QString procName = qTableName("qtest_batch_proc", __FILE__, db);
|
||||
QVERIFY_SQL(q, exec("create or replace procedure " + procName +
|
||||
" (x in timestamp, y out timestamp) is\n"
|
||||
"begin\n"
|
||||
" y := x;\n"
|
||||
"end;\n"));
|
||||
QVERIFY(q.prepare("call " + procName + "(?, ?)"));
|
||||
q.addBindValue(timeStampCol, QSql::In);
|
||||
QVariantList emptyDateTimes;
|
||||
emptyDateTimes.reserve(timeStampCol.size());
|
||||
for (int i = 0; i < timeStampCol.size(); i++)
|
||||
emptyDateTimes << QVariant(QDateTime());
|
||||
q.addBindValue(emptyDateTimes, QSql::Out);
|
||||
QVERIFY_SQL(q, execBatch());
|
||||
QCOMPARE(q.boundValue(1).toList(), timeStampCol);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QSqlQuery::QTBUG_43874()
|
||||
|
Loading…
Reference in New Issue
Block a user