blob: 174db2863ef7c4c50291271bb89fece599c48e49 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* possible connection parameters */
#ifndef TST_DATABASES_H
#define TST_DATABASES_H
#include <QSqlDatabase>
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlQuery>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDir>
#include <QScopedPointer>
#include <QVariant>
#include <QDebug>
#include <QSqlTableModel>
#include <QtSql/private/qsqldriver_p.h>
#include <QtTest/QtTest>
#define CHECK_DATABASE( db ) \
if ( !db.isValid() ) { qFatal( "db is Invalid" ); }
#define QVERIFY_SQL(q, stmt) QVERIFY2((q).stmt, tst_Databases::printError((q).lastError(), db))
#define QFAIL_SQL(q, stmt) QVERIFY2(!(q).stmt, tst_Databases::printError((q).lastError(), db))
#define DBMS_SPECIFIC(db, driver) \
if (!db.driverName().startsWith(driver)) { QSKIP(driver " specific test"); }
// ### use QSystem::hostName if it is integrated in qtest/main
static QString qGetHostName()
{
static QString hostname;
if (hostname.isEmpty()) {
hostname = QSysInfo::machineHostName();
hostname.replace(QLatin1Char( '.' ), QLatin1Char( '_' ));
hostname.replace(QLatin1Char( '-' ), QLatin1Char( '_' ));
}
return hostname;
}
// to prevent nameclashes on our database server, each machine
// will use its own set of table names. Call this function to get
// "tablename_hostname"
inline QString fixupTableName(const QString &tableName, QSqlDatabase db)
{
QString tbName = tableName;
// On Oracle we are limited to 30 character tablenames
QSqlDriverPrivate *d = static_cast<QSqlDriverPrivate *>(QObjectPrivate::get(db.driver()));
if (d && d->dbmsType == QSqlDriver::Oracle)
tbName.truncate(30);
return tbName;
}
inline static QString qTableName(const QString &prefix, const char *sourceFileName,
QSqlDatabase db, bool escape = true)
{
const auto tableStr = fixupTableName(QString(QLatin1String("dbtst") + db.driverName() +
QString::number(qHash(QLatin1String(sourceFileName) +
"_" + qGetHostName().replace("-", "_")), 16) +
"_" + prefix), db);
return escape ? db.driver()->escapeIdentifier(tableStr, QSqlDriver::TableName) : tableStr;
}
inline static QString qTableName(const QString& prefix, QSqlDatabase db)
{
QString tableStr;
if (db.driverName().toLower().contains("ODBC"))
tableStr += QLatin1String("_odbc");
return fixupTableName(QString(db.driver()->escapeIdentifier(prefix + tableStr + QLatin1Char('_') +
qGetHostName(), QSqlDriver::TableName)),db);
}
inline static bool testWhiteSpaceNames( const QString &name )
{
/* return name.startsWith( "QPSQL" )
|| name.startsWith( "QODBC" )
|| name.startsWith( "QSQLITE" )
|| name.startsWith( "QMYSQL" );*/
return name != QLatin1String("QSQLITE2");
}
inline static QString toHex( const QString& binary )
{
QString str;
static char const hexchars[] = "0123456789ABCDEF";
for ( int i = 0; i < binary.size(); i++ ) {
ushort code = binary.at(i).unicode();
str += (QChar)(hexchars[ (code >> 12) & 0x0F ]);
str += (QChar)(hexchars[ (code >> 8) & 0x0F ]);
str += (QChar)(hexchars[ (code >> 4) & 0x0F ]);
str += (QChar)(hexchars[ code & 0x0F ]);
}
return str;
}
class tst_Databases
{
public:
tst_Databases(): counter( 0 )
{
}
~tst_Databases()
{
close();
}
// returns a testtable consisting of the names of all database connections if
// driverPrefix is empty, otherwise only those that start with driverPrefix.
int fillTestTable( const QString& driverPrefix = QString() ) const
{
QTest::addColumn<QString>( "dbName" );
int count = 0;
for ( int i = 0; i < dbNames.count(); ++i ) {
QSqlDatabase db = QSqlDatabase::database( dbNames.at( i ) );
if ( !db.isValid() )
continue;
if ( driverPrefix.isEmpty() || db.driverName().startsWith( driverPrefix ) ) {
QTest::newRow( dbNames.at( i ).toLatin1() ) << dbNames.at( i );
++count;
}
}
return count;
}
int fillTestTableWithStrategies( const QString& driverPrefix = QString() ) const
{
QTest::addColumn<QString>( "dbName" );
QTest::addColumn<int>("submitpolicy_i");
int count = 0;
for ( int i = 0; i < dbNames.count(); ++i ) {
QSqlDatabase db = QSqlDatabase::database( dbNames.at( i ) );
if ( !db.isValid() )
continue;
if ( driverPrefix.isEmpty() || db.driverName().startsWith( driverPrefix ) ) {
QTest::newRow( QString("%1 [field]").arg(dbNames.at( i )).toLatin1() ) << dbNames.at( i ) << (int)QSqlTableModel::OnFieldChange;
QTest::newRow( QString("%1 [row]").arg(dbNames.at( i )).toLatin1() ) << dbNames.at( i ) << (int)QSqlTableModel::OnRowChange;
QTest::newRow( QString("%1 [manual]").arg(dbNames.at( i )).toLatin1() ) << dbNames.at( i ) << (int)QSqlTableModel::OnManualSubmit;
++count;
}
}
return count;
}
void addDb( const QString& driver, const QString& dbName,
const QString& user = QString(), const QString& passwd = QString(),
const QString& host = QString(), int port = -1, const QString params = QString() )
{
QSqlDatabase db;
if ( !QSqlDatabase::drivers().contains( driver ) ) {
qWarning() << "Driver" << driver << "is not installed";
return;
}
// construct a stupid unique name
QString cName = QString::number( counter++ ) + QLatin1Char('_') + driver + QLatin1Char('@');
cName += host.isEmpty() ? dbName : host;
if ( port > 0 )
cName += QLatin1Char(':') + QString::number( port );
db = QSqlDatabase::addDatabase( driver, cName );
if ( !db.isValid() ) {
qWarning( "Could not create database object" );
return;
}
db.setDatabaseName( dbName );
db.setUserName( user );
db.setPassword( passwd );
db.setHostName( host );
db.setPort( port );
db.setConnectOptions( params );
dbNames.append( cName );
}
bool addDbs()
{
// Test databases can be defined in a file using the following format:
//
// {
// "entries": [
// {
// "driver": "QPSQL",
// "name": "testdb",
// "username": "postgres",
// "password": "password",
// "hostname": "localhost",
// "port": 5432,
// "parameters": "extraoptions"
// },
// {
// ....
// }
// ]
// }
bool added = false;
const QString databasesFile(qgetenv("QT_TEST_DATABASES_FILE"));
QFile f(databasesFile.isEmpty() ? "testdbs.json" : databasesFile);
if (f.exists() && f.open(QIODevice::ReadOnly)) {
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
f.close();
const QJsonValue entriesV = doc.object().value(QLatin1String("entries"));
if (!entriesV.isArray()) {
qWarning() << "No entries in " + f.fileName();
} else {
const QJsonArray entriesA = entriesV.toArray();
QJsonArray::const_iterator it = entriesA.constBegin();
while (it != entriesA.constEnd()) {
if ((*it).isObject()) {
const QJsonObject object = (*it).toObject();
addDb(object.value(QStringLiteral("driver")).toString(),
object.value(QStringLiteral("name")).toString(),
object.value(QStringLiteral("username")).toString(),
object.value(QStringLiteral("password")).toString(),
object.value(QStringLiteral("hostname")).toString(),
object.value(QStringLiteral("port")).toInt(),
object.value(QStringLiteral("parameters")).toString());
added = true;
}
++it;
}
}
}
QTemporaryDir *sqLiteDir = dbDir();
if (sqLiteDir) {
addDb(QStringLiteral("QSQLITE"), QDir::toNativeSeparators(sqLiteDir->path() + QStringLiteral("/foo.db")));
added = true;
}
return added;
}
// 'false' return indicates a system error, for example failure to create a temporary directory.
bool open()
{
if (!addDbs())
return false;
QStringList::Iterator it = dbNames.begin();
while ( it != dbNames.end() ) {
QSqlDatabase db = QSqlDatabase::database(( *it ), false );
qDebug() << "Opening:" << (*it);
if ( db.isValid() && !db.isOpen() ) {
if ( !db.open() ) {
qWarning( "tst_Databases: Unable to open %s on %s:\n%s", qPrintable( db.driverName() ), qPrintable( *it ), qPrintable( db.lastError().databaseText() ) );
// well... opening failed, so we just ignore the server, maybe it is not running
it = dbNames.erase( it );
} else {
++it;
}
}
}
return true;
}
void close()
{
for ( QStringList::Iterator it = dbNames.begin(); it != dbNames.end(); ++it ) {
{
QSqlDatabase db = QSqlDatabase::database(( *it ), false );
if ( db.isValid() && db.isOpen() )
db.close();
}
QSqlDatabase::removeDatabase(( *it ) );
}
dbNames.clear();
}
// for debugging only: outputs the connection as string
static QString dbToString( const QSqlDatabase db )
{
QString res = db.driverName() + QLatin1Char('@');
if ( db.driverName().startsWith( "QODBC" ) || db.driverName().startsWith( "QOCI" ) ) {
res += db.databaseName();
} else {
res += db.hostName();
}
if ( db.port() > 0 ) {
res += QLatin1Char(':') + QString::number( db.port() );
}
return res;
}
// drop a table only if it exists to prevent warnings
static void safeDropTables( QSqlDatabase db, const QStringList& tableNames )
{
bool wasDropped;
QSqlQuery q( db );
QStringList dbtables=db.tables();
QSqlDriver::DbmsType dbType = getDatabaseType(db);
foreach(const QString &tableName, tableNames)
{
wasDropped = true;
QString table=tableName;
if ( db.driver()->isIdentifierEscaped(table, QSqlDriver::TableName))
table = db.driver()->stripDelimiters(table, QSqlDriver::TableName);
if ( dbtables.contains( table, Qt::CaseInsensitive ) ) {
foreach(const QString &table2, dbtables.filter(table, Qt::CaseInsensitive)) {
if(table2.compare(table.section('.', -1, -1), Qt::CaseInsensitive) == 0) {
table=db.driver()->escapeIdentifier(table2, QSqlDriver::TableName);
if (dbType == QSqlDriver::PostgreSQL)
wasDropped = q.exec( "drop table " + table + " cascade");
else
wasDropped = q.exec( "drop table " + table);
dbtables.removeAll(table2);
}
}
}
if ( !wasDropped ) {
qWarning() << dbToString(db) << "unable to drop table" << tableName << ':' << q.lastError();
// qWarning() << "last query:" << q.lastQuery();
// qWarning() << "dbtables:" << dbtables;
// qWarning() << "db.tables():" << db.tables();
}
}
}
static void safeDropTable( QSqlDatabase db, const QString& tableName )
{
safeDropTables(db, QStringList() << tableName);
}
static void safeDropViews( QSqlDatabase db, const QStringList &viewNames )
{
if ( isMSAccess( db ) ) // Access is sooo stupid.
safeDropTables( db, viewNames );
bool wasDropped;
QSqlQuery q( db );
QStringList dbtables=db.tables(QSql::Views);
foreach(QString viewName, viewNames)
{
wasDropped = true;
QString view=viewName;
if ( db.driver()->isIdentifierEscaped(view, QSqlDriver::TableName))
view = db.driver()->stripDelimiters(view, QSqlDriver::TableName);
if ( dbtables.contains( view, Qt::CaseInsensitive ) ) {
foreach(const QString &view2, dbtables.filter(view, Qt::CaseInsensitive)) {
if(view2.compare(view.section('.', -1, -1), Qt::CaseInsensitive) == 0) {
view=db.driver()->escapeIdentifier(view2, QSqlDriver::TableName);
wasDropped = q.exec( "drop view " + view);
dbtables.removeAll(view);
}
}
}
if ( !wasDropped )
qWarning() << dbToString(db) << "unable to drop view" << viewName << ':' << q.lastError();
// << "\nlast query:" << q.lastQuery()
// << "\ndbtables:" << dbtables
// << "\ndb.tables(QSql::Views):" << db.tables(QSql::Views);
}
}
static void safeDropView( QSqlDatabase db, const QString& tableName )
{
safeDropViews(db, QStringList() << tableName);
}
// returns the type name of the blob datatype for the database db.
// blobSize is only used if the db doesn't have a generic blob type
static QString blobTypeName( QSqlDatabase db, int blobSize = 10000 )
{
const QSqlDriver::DbmsType dbType = getDatabaseType(db);
if (dbType == QSqlDriver::MySqlServer)
return "longblob";
if (dbType == QSqlDriver::PostgreSQL)
return "bytea";
if (dbType == QSqlDriver::Sybase
|| dbType == QSqlDriver::MSSqlServer
|| isMSAccess( db ) )
return "image";
if (dbType == QSqlDriver::DB2)
return QString( "blob(%1)" ).arg( blobSize );
if (dbType == QSqlDriver::Interbase)
return QString( "blob sub_type 0 segment size 4096" );
if (dbType == QSqlDriver::Oracle
|| dbType == QSqlDriver::SQLite)
return "blob";
qDebug() << "tst_Databases::blobTypeName: Don't know the blob type for" << dbToString( db );
return "blob";
}
static QString dateTimeTypeName(QSqlDatabase db)
{
const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db);
if (dbType == QSqlDriver::PostgreSQL)
return QLatin1String("timestamptz");
if (dbType == QSqlDriver::Oracle && getOraVersion(db) >= 9)
return QLatin1String("timestamp(0)");
return QLatin1String("datetime");
}
static QString autoFieldName( QSqlDatabase db )
{
const QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db);
if (dbType == QSqlDriver::MySqlServer)
return "AUTO_INCREMENT";
if (dbType == QSqlDriver::Sybase || dbType == QSqlDriver::MSSqlServer)
return "IDENTITY";
/* if (dbType == QSqlDriver::PostgreSQL)
return "SERIAL";*/
// if (dbType == QSqlDriver::DB2)
// return "GENERATED BY DEFAULT AS IDENTITY";
return QString();
}
static QByteArray printError( const QSqlError& err )
{
QString result;
if (!err.nativeErrorCode().isEmpty())
result += '(' + err.nativeErrorCode() + ") ";
result += '\'';
if(!err.driverText().isEmpty())
result += err.driverText() + "' || '";
result += err.databaseText() + QLatin1Char('\'');
return result.toLocal8Bit();
}
static QByteArray printError( const QSqlError& err, const QSqlDatabase& db )
{
QString result(dbToString(db) + ": ");
if (!err.nativeErrorCode().isEmpty())
result += '(' + err.nativeErrorCode() + ") ";
result += '\'';
if(!err.driverText().isEmpty())
result += err.driverText() + "' || '";
result += err.databaseText() + QLatin1Char('\'');
return result.toLocal8Bit();
}
static QSqlDriver::DbmsType getDatabaseType(QSqlDatabase db)
{
QSqlDriverPrivate *d = static_cast<QSqlDriverPrivate *>(QObjectPrivate::get(db.driver()));
return d->dbmsType;
}
static bool isMSAccess( QSqlDatabase db )
{
return db.databaseName().contains( "Access Driver", Qt::CaseInsensitive );
}
// -1 on fail, else Oracle version
static int getOraVersion( QSqlDatabase db )
{
int ver = -1;
QSqlQuery q( "SELECT banner FROM v$version", db );
q.next();
QRegularExpression vers("([0-9]+)\\.[0-9\\.]+[0-9]");
QRegularExpressionMatch match = vers.match(q.value(0).toString());
if (match.hasMatch()) {
bool ok;
ver = match.captured(1).toInt(&ok);
if (!ok)
ver = -1;
}
return ver;
}
static QString getMySqlVersion( const QSqlDatabase &db )
{
QSqlQuery q(db);
q.exec( "select version()" );
if(q.next())
return q.value( 0 ).toString();
else
return QString();
}
static QString getPSQLVersion( const QSqlDatabase &db )
{
QSqlQuery q(db);
q.exec( "select version()" );
if(q.next())
return q.value( 0 ).toString();
else
return QString();
}
QStringList dbNames;
int counter;
private:
QTemporaryDir *dbDir()
{
if (m_dbDir.isNull()) {
m_dbDir.reset(new QTemporaryDir);
if (!m_dbDir->isValid()) {
qWarning() << Q_FUNC_INFO << "Unable to create a temporary directory: " << QDir::toNativeSeparators(m_dbDir->path());
m_dbDir.reset();
}
}
return m_dbDir.data();
}
QScopedPointer<QTemporaryDir> m_dbDir;
};
#endif