21 changes: 21 additions & 0 deletions src/core/qgsdatasourceuri.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ QgsDataSourceURI::QgsDataSourceURI( QString uri )
{
mPort = pval;
}
else if ( pname == "driver" )
{
mDriver = pval;
}
else if ( pname == "tty" )
{
QgsDebugMsg( "backend debug tty ignored" );
Expand Down Expand Up @@ -307,6 +311,11 @@ QString QgsDataSourceURI::port() const
return mPort;
}

QString QgsDataSourceURI::driver() const
{
return mDriver;
}

QgsDataSourceURI::SSLmode QgsDataSourceURI::sslMode() const
{
return mSSLmode;
Expand Down Expand Up @@ -337,6 +346,13 @@ QString QgsDataSourceURI::keyColumn() const
return mKeyColumn;
}


void QgsDataSourceURI::setDriver( const QString& driver )
{
mDriver = driver;
}


void QgsDataSourceURI::setKeyColumn( const QString& column )
{
mKeyColumn = column;
Expand Down Expand Up @@ -486,6 +502,11 @@ QString QgsDataSourceURI::connectionInfo( bool expandAuthConfig ) const
connectionItems << "port=" + mPort;
}

if ( mDriver != "" )
{
connectionItems << "driver='" + escape( mDriver ) + '\'';
}

if ( mUsername != "" )
{
connectionItems << "user='" + escape( mUsername ) + '\'';
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsdatasourceuri.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ class CORE_EXPORT QgsDataSourceURI
QString database() const;
//! Returns the port
QString port() const;
//! Returns the driver
// @note added in QGIS 2.16
QString driver() const;
//! Sets the driver name
// @note added in QGIS 2.16
void setDriver( const QString& driver );
//! Returns the password
QString password() const;
//! Returns the SSL mode
Expand Down Expand Up @@ -211,6 +217,8 @@ class CORE_EXPORT QgsDataSourceURI
QString mHost;
//! port the database server listens on
QString mPort;
//! device driver for ODBC
QString mDriver;
//! service name
QString mService;
//! database name
Expand Down
147 changes: 147 additions & 0 deletions src/gui/qgsmanageconnectionsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ void QgsManageConnectionsDialog::doExportImport()
case Oracle:
doc = saveOracleConnections( items );
break;
case DB2:
doc = saveDb2Connections( items );
break;
}

QFile file( mFileName );
Expand Down Expand Up @@ -189,6 +192,9 @@ void QgsManageConnectionsDialog::doExportImport()
case Oracle:
loadOracleConnections( doc, items );
break;
case DB2:
loadDb2Connections( doc, items );
break;
}
// clear connections list and close window
listConnections->clear();
Expand Down Expand Up @@ -224,6 +230,9 @@ bool QgsManageConnectionsDialog::populateConnections()
case Oracle:
settings.beginGroup( "/Oracle/connections" );
break;
case DB2:
settings.beginGroup( "/DB2/connections" );
break;
}
QStringList keys = settings.childGroups();
QStringList::Iterator it = keys.begin();
Expand Down Expand Up @@ -319,6 +328,14 @@ bool QgsManageConnectionsDialog::populateConnections()
return false;
}
break;
case DB2:
if ( root.tagName() != "qgsDb2Connections" )
{
QMessageBox::information( this, tr( "Loading connections" ),
tr( "The file is not an DB2 connections exchange file." ) );
return false;
}
break;
}

QDomElement child = root.firstChildElement();
Expand Down Expand Up @@ -522,6 +539,47 @@ QDomDocument QgsManageConnectionsDialog::saveOracleConnections( const QStringLis
return doc;
}

QDomDocument QgsManageConnectionsDialog::saveDb2Connections( const QStringList &connections )
{
QDomDocument doc( "connections" );
QDomElement root = doc.createElement( "qgsDb2Connections" );
root.setAttribute( "version", "1.0" );
doc.appendChild( root );

QSettings settings;
QString path;
for ( int i = 0; i < connections.count(); ++i )
{
path = "/DB2/connections/" + connections[ i ];
QDomElement el = doc.createElement( "db2" );
el.setAttribute( "name", connections[ i ] );
el.setAttribute( "host", settings.value( path + "/host", "" ).toString() );
el.setAttribute( "port", settings.value( path + "/port", "" ).toString() );
el.setAttribute( "database", settings.value( path + "/database", "" ).toString() );
el.setAttribute( "service", settings.value( path + "/service", "" ).toString() );
el.setAttribute( "sslmode", settings.value( path + "/sslmode", "1" ).toString() );
el.setAttribute( "estimatedMetadata", settings.value( path + "/estimatedMetadata", "0" ).toString() );

el.setAttribute( "saveUsername", settings.value( path + "/saveUsername", "false" ).toString() );

if ( settings.value( path + "/saveUsername", "false" ).toString() == "true" )
{
el.setAttribute( "username", settings.value( path + "/username", "" ).toString() );
}

el.setAttribute( "savePassword", settings.value( path + "/savePassword", "false" ).toString() );

if ( settings.value( path + "/savePassword", "false" ).toString() == "true" )
{
el.setAttribute( "password", settings.value( path + "/password", "" ).toString() );
}

root.appendChild( el );
}

return doc;
}

void QgsManageConnectionsDialog::loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service )
{
QDomElement root = doc.documentElement();
Expand Down Expand Up @@ -956,6 +1014,95 @@ void QgsManageConnectionsDialog::loadOracleConnections( const QDomDocument &doc,
}
}

void QgsManageConnectionsDialog::loadDb2Connections( const QDomDocument &doc, const QStringList &items )
{
QDomElement root = doc.documentElement();
if ( root.tagName() != "qgsDb2Connections" )
{
QMessageBox::information( this,
tr( "Loading connections" ),
tr( "The file is not an DB2 connections exchange file." ) );
return;
}

QString connectionName;
QSettings settings;
settings.beginGroup( "/DB2/connections" );
QStringList keys = settings.childGroups();
settings.endGroup();
QDomElement child = root.firstChildElement();
bool prompt = true;
bool overwrite = true;

while ( !child.isNull() )
{
connectionName = child.attribute( "name" );
if ( !items.contains( connectionName ) )
{
child = child.nextSiblingElement();
continue;
}

// check for duplicates
if ( keys.contains( connectionName ) && prompt )
{
int res = QMessageBox::warning( this,
tr( "Loading connections" ),
tr( "Connection with name '%1' already exists. Overwrite?" )
.arg( connectionName ),
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel );
switch ( res )
{
case QMessageBox::Cancel:
return;
case QMessageBox::No:
child = child.nextSiblingElement();
continue;
case QMessageBox::Yes:
overwrite = true;
break;
case QMessageBox::YesToAll:
prompt = false;
overwrite = true;
break;
case QMessageBox::NoToAll:
prompt = false;
overwrite = false;
break;
}
}

if ( keys.contains( connectionName ) && !overwrite )
{
child = child.nextSiblingElement();
continue;
}

//no dups detected or overwrite is allowed
settings.beginGroup( "/DB2/connections/" + connectionName );

settings.setValue( "/host", child.attribute( "host" ) );
settings.setValue( "/port", child.attribute( "port" ) );
settings.setValue( "/database", child.attribute( "database" ) );
if ( child.hasAttribute( "service" ) )
{
settings.setValue( "/service", child.attribute( "service" ) );
}
else
{
settings.setValue( "/service", "" );
}
settings.setValue( "/sslmode", child.attribute( "sslmode" ) );
settings.setValue( "/estimatedMetadata", child.attribute( "estimatedMetadata" ) );
settings.setValue( "/saveUsername", child.attribute( "saveUsername" ) );
settings.setValue( "/username", child.attribute( "username" ) );
settings.setValue( "/savePassword", child.attribute( "savePassword" ) );
settings.setValue( "/password", child.attribute( "password" ) );
settings.endGroup();

child = child.nextSiblingElement();
}
}
void QgsManageConnectionsDialog::selectAll()
{
listConnections->selectAll();
Expand Down
3 changes: 3 additions & 0 deletions src/gui/qgsmanageconnectionsdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan
PostGIS,
WFS,
MSSQL,
DB2,
WCS,
Oracle,
};
Expand All @@ -62,12 +63,14 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan
QDomDocument savePgConnections( const QStringList & connections );
QDomDocument saveMssqlConnections( const QStringList & connections );
QDomDocument saveOracleConnections( const QStringList & connections );
QDomDocument saveDb2Connections( const QStringList & connections );

void loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service );
void loadWFSConnections( const QDomDocument &doc, const QStringList &items );
void loadPgConnections( const QDomDocument &doc, const QStringList &items );
void loadMssqlConnections( const QDomDocument &doc, const QStringList &items );
void loadOracleConnections( const QDomDocument &doc, const QStringList &items );
void loadDb2Connections( const QDomDocument &doc, const QStringList &items );

QString mFileName;
Mode mDialogMode;
Expand Down
1 change: 1 addition & 0 deletions src/providers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ADD_SUBDIRECTORY(gpx)
ADD_SUBDIRECTORY(wfs)
ADD_SUBDIRECTORY(spatialite)
ADD_SUBDIRECTORY(virtual)
ADD_SUBDIRECTORY(db2)

IF (WITH_ORACLE)
ADD_SUBDIRECTORY(oracle)
Expand Down
58 changes: 58 additions & 0 deletions src/providers/db2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

########################################################
# Files

SET(DB2_SRCS
qgsdb2geometrycolumns.cpp
qgsdb2expressioncompiler.cpp
qgsdb2provider.cpp
qgsdb2dataitems.cpp
qgsdb2newconnection.cpp
qgsdb2tablemodel.cpp
qgsdb2featureiterator.cpp
qgsdb2sourceselect.cpp
)
SET(DB2_MOC_HDRS
qgsdb2provider.h
qgsdb2dataitems.h
qgsdb2newconnection.h
qgsdb2tablemodel.h
qgsdb2sourceselect.h
)

########################################################
# Build

QT4_WRAP_CPP(DB2_MOC_SRCS ${DB2_MOC_HDRS})

INCLUDE_DIRECTORIES(
.
../../core
../../core/auth
../../core/geometry
../../gui
../../gui/auth
../../ui
${CMAKE_CURRENT_BINARY_DIR}/../../ui
)

INCLUDE_DIRECTORIES(SYSTEM
${GEOS_INCLUDE_DIR}
${QT_INCLUDE_DIR}
)

ADD_LIBRARY (db2provider MODULE ${DB2_SRCS} ${DB2_HDRS} ${DB2_MOC_SRCS})

TARGET_LINK_LIBRARIES (db2provider
qgis_core
qgis_gui
${QT_QTSQL_LIBRARY}
)


########################################################
# Install

INSTALL(TARGETS db2provider
RUNTIME DESTINATION ${QGIS_PLUGIN_DIR}
LIBRARY DESTINATION ${QGIS_PLUGIN_DIR})
606 changes: 606 additions & 0 deletions src/providers/db2/qgsdb2dataitems.cpp

Large diffs are not rendered by default.

177 changes: 177 additions & 0 deletions src/providers/db2/qgsdb2dataitems.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/***************************************************************************
qgsdb2dataitems.h - Browser Panel object population
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#include "qgsdb2provider.h"
#include "qgsdb2sourceselect.h"
#include <qgsdataitem.h>

class QgsDb2RootItem;
class QgsDb2Connection;
class QgsDb2SchemaItem;
class QgsDb2LayerItem;

/**
* @class QgsDb2RootItem
* @brief Browser Panel DB2 root object.
*/
class QgsDb2RootItem : public QgsDataCollectionItem
{
Q_OBJECT

public:
QgsDb2RootItem( QgsDataItem* parent, QString name, QString path );
~QgsDb2RootItem();

/**
* Add saved connections as children.
*/
QVector<QgsDataItem*> createChildren() override;

virtual QWidget * paramWidget() override;

virtual QList<QAction*> actions() override;

public slots:
//void connectionsChanged();
void newConnection();
};

/**
* @class QgsDb2ConnectionItem
* @brief Browser Panel DB2 connection object (under root).
*/
class QgsDb2ConnectionItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsDb2ConnectionItem( QgsDataItem* parent, QString name, QString path );
~QgsDb2ConnectionItem();

static bool ConnInfoFromSettings( const QString connName,
QString &connInfo, QString &errorMsg );

static bool ConnInfoFromParameters(
const QString &service,
const QString &driver,
const QString &host,
const QString &port,
const QString &database,
const QString &username,
const QString &password,
const QString &authcfg,
QString &connInfo,
QString &errorMsg );
/**
* Fetch geometry column data from server and populate Browser Panel with
* schemas and layers.
*/
QVector<QgsDataItem*> createChildren() override;
virtual bool equal( const QgsDataItem *other ) override;

/**
* Add Refresh, Edit, and Delete actions for every QgsDb2ConnectionItem.
*/
virtual QList<QAction*> actions() override;

virtual bool acceptDrop() override { return true; }
virtual bool handleDrop( const QMimeData * data, Qt::DropAction action ) override;
bool handleDrop( const QMimeData * data, const QString& toSchema );
void refresh() override;

QString connInfo() const { return mConnInfo; }

signals:
void addGeometryColumn( QgsDb2LayerProperty );

public slots:
/**
* Refresh with saved connection data.
*/
void refreshConnection();

/**
* Show dialog to edit and save connection data.
*/
void editConnection();

/**
* Delete saved connection data and remove from Browser Panel.
*/
void deleteConnection();
//void setAllowGeometrylessTables( bool allow );

//void setLayerType( QgsDb2LayerProperty layerProperty );

private:
QString mConnInfo;
QString mService;
QString mHost;
QString mDriver;
QString mPort;
QString mDatabase;
QString mUsername;
QString mPassword;
QString mAuthcfg;
int mEnvironment;
bool mUseGeometryColumns;
bool mUseEstimatedMetadata;
bool mAllowGeometrylessTables;

void readConnectionSettings();
};

/**
* @class QgsDb2SchemaItem
* @brief Browser Panel DB2 schema object (under connections).
*/
class QgsDb2SchemaItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsDb2SchemaItem( QgsDataItem* parent, QString name, QString path );
~QgsDb2SchemaItem();

QVector<QgsDataItem*> createChildren() override;

QgsDb2LayerItem* addLayer( QgsDb2LayerProperty layerProperty, bool refresh );

void refresh() override {} // do not refresh directly
void addLayers( QgsDataItem* newLayers );
virtual bool acceptDrop() override { return true; }
virtual bool handleDrop( const QMimeData * data, Qt::DropAction action ) override;
};

/**
* @class QgsDb2LayerItem
* @brief Browser Panel DB2 layer object (under schemas).
*/
class QgsDb2LayerItem : public QgsLayerItem
{
Q_OBJECT

public:
QgsDb2LayerItem( QgsDataItem* parent, QString name, QString path, QgsLayerItem::LayerType layerType, QgsDb2LayerProperty layerProperties );
~QgsDb2LayerItem();

QString createUri();

QgsDb2LayerItem* createClone();

private:
QgsDb2LayerProperty mLayerProperty;
};

246 changes: 246 additions & 0 deletions src/providers/db2/qgsdb2expressioncompiler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/***************************************************************************
qgsdb2expressioncompiler.cpp - DB2 expression compiler
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
Adapted from MSSQL provider by Tamas Szekeres
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#include "qgsdb2expressioncompiler.h"

QgsDb2ExpressionCompiler::QgsDb2ExpressionCompiler( QgsDb2FeatureSource* source )
: QgsSqlExpressionCompiler( source->mFields
)
{

}

QString nodeType( const QgsExpression::Node* node )
{
QString opString = "?";
if ( node->nodeType() == QgsExpression::ntUnaryOperator ) opString = "ntUnaryOperator";
if ( node->nodeType() == QgsExpression::ntBinaryOperator ) opString = "ntBinaryOperator";
if ( node->nodeType() == QgsExpression::ntInOperator ) opString = "ntInOperator";
if ( node->nodeType() == QgsExpression::ntFunction ) opString = "ntFunction";
if ( node->nodeType() == QgsExpression::ntLiteral ) opString = "ntLiteral";
if ( node->nodeType() == QgsExpression::ntColumnRef ) opString = "ntColumnRef";
if ( node->nodeType() == QgsExpression::ntCondition ) opString = "ntCondition";
QString result = QString( "%1 - " ).arg( node->nodeType() ) + opString;
return result;

}

QString resultType( QgsSqlExpressionCompiler::Result result )
{
if ( result == QgsSqlExpressionCompiler::None ) return "None";
if ( result == QgsSqlExpressionCompiler::Complete ) return "Complete";
if ( result == QgsSqlExpressionCompiler::Partial ) return "Partial";
if ( result == QgsSqlExpressionCompiler::Fail ) return "Fail";
return "Other result";

}

QgsSqlExpressionCompiler::Result QgsDb2ExpressionCompiler::compileNode( const QgsExpression::Node* node, QString& result )
{
QgsDebugMsg( QString( "nodeType: %1" ).arg( nodeType( node ) ) );
if ( node->nodeType() == QgsExpression::ntColumnRef )
{
const QgsExpression::NodeColumnRef *n( static_cast<const QgsExpression::NodeColumnRef*>( node ) );
QgsDebugMsg( QString( "column ref node: " ) + n->dump() );
// TODO - consider escaped names - not sure how to handle
QString upperName = n->name().toUpper();
int idx = mFields.indexFromName( upperName );
QgsDebugMsg( QString( "%1 - %2" ).arg( idx ).arg( upperName ) );
if ( idx > -1 )
{
result = upperName;
QgsDebugMsg( "return Complete" );
return Complete;
}
QgsDebugMsg( "return Fail" );
return Fail;
}
// Seemed necessary in initial Python testing but can't identify failing case now
#if 0
if ( node->nodeType() == QgsExpression::ntLiteral )
{
const QgsExpression::NodeLiteral* n = static_cast<const QgsExpression::NodeLiteral*>( node );

bool ok = false;
if ( n->dump().toUpper() == "NULL" ) // expression compiler doesn't handle this correctly
{
result = "NULL";
ok = true;
}
else
{
result = quotedValue( n->value(), ok );
}
QgsDebugMsg( QString( "ok: %1; literal node: " ).arg( ok ) + n->value().toString() + "; result: " + result );
QgsDebugMsg( QString( "n->dump: " ) + n->dump() );
QgsDebugMsg( QString( "type: %1; typeName: %2" ).arg( n->value().type() ).arg( n->value().typeName() ) );
if ( ok )
{
QgsDebugMsg( "return Complete" );
return Complete;
}
else
{
QgsDebugMsg( "return Fail" );
return Fail;
}

}
#endif
if ( node->nodeType() == QgsExpression::ntUnaryOperator )
{
const QgsExpression::NodeUnaryOperator* n = static_cast<const QgsExpression::NodeUnaryOperator*>( node );
Result rr = Fail;
switch ( n->op() )
{
case QgsExpression::uoNot:
rr = compileNode( n->operand(), result );
if ( "NULL" == result.toUpper() )
{
result = "";
return Fail;
}

result = "NOT " + result;
QgsDebugMsg( QString( "NOT; result: %1; right: %2" ).arg( resultType( rr ) ).arg( result ) );
return rr;

case QgsExpression::uoMinus:
break;
}
}

if ( node->nodeType() == QgsExpression::ntBinaryOperator )
{
const QgsExpression::NodeBinaryOperator *bin( static_cast<const QgsExpression::NodeBinaryOperator*>( node ) );
QString left, right;

Result lr = compileNode( bin->opLeft(), left );
Result rr = compileNode( bin->opRight(), right );
Result compileResult;
QgsDebugMsg( "left: '" + left + "'; right: '" + right +
QString( "'; op: %1; lr: %2; rr: %3" ).arg( bin->op() ).arg( lr ).arg( rr ) );
if ( lr == Fail || rr == Fail )
return Fail;
// NULL can not appear on the left, only as part of IS NULL or IS NOT NULL
if ( "NULL" == left.toUpper() ) return Fail;
// NULL can only be on the right for IS and IS NOT
if ( "NULL" == right.toUpper() && ( bin->op() != QgsExpression::boIs && bin->op() != QgsExpression::boIsNot ) )
return Fail;

switch ( bin->op() )
{
case QgsExpression::boMod:
result = QString( "MOD(%1,%2)" ).arg( left, right );
compileResult = ( lr == Partial || rr == Partial ) ? Partial : Complete;
QgsDebugMsg( QString( "MOD compile status: %1" ).arg( compileResult ) + "; " + result );
return compileResult;

case QgsExpression::boPow:
result = QString( "power(%1,%2)" ).arg( left, right );
compileResult = ( lr == Partial || rr == Partial ) ? Partial : Complete;
QgsDebugMsg( QString( "POWER compile status: %1" ).arg( compileResult ) + "; " + result );
return compileResult;

case QgsExpression::boRegexp:
return Fail; //not supported, regexp syntax is too different to Qt

case QgsExpression::boConcat:
result = QString( "%1 || %2" ).arg( left, right );
compileResult = ( lr == Partial || rr == Partial ) ? Partial : Complete;
QgsDebugMsg( QString( "CONCAT compile status: %1" ).arg( compileResult ) + "; " + result );
return compileResult;

case QgsExpression::boILike:
QgsDebugMsg( "ILIKE is not supported by DB2" );
return Fail;
/*
result = QString( "%1 LIKE %2" ).arg( left, right );
compileResult = (lr == Partial || rr == Partial) ? Partial : Complete;
QgsDebugMsg(QString("ILIKE compile status: %1").arg(compileResult) + "; " + result);
return compileResult;
*/

case QgsExpression::boNotILike:
QgsDebugMsg( "NOT ILIKE is not supported by DB2" );
return Fail;
/*
result = QString( "%1 NOT LIKE %2" ).arg( left, right );
compileResult = (lr == Partial || rr == Partial) ? Partial : Complete;
QgsDebugMsg(QString("NOT ILIKE compile status: %1").arg(compileResult) + "; " + result);
return compileResult;
*/

// We only support IS NULL if the operand on the left is a column
case QgsExpression::boIs:
if ( "NULL" == right.toUpper() )
{
if ( bin->opLeft()->nodeType() != QgsExpression::ntColumnRef )
{
QgsDebugMsg( "Failing IS NULL with non-column on left: " + left );
return Fail;
}
}
break;
// We only support IS NULL if the operand on the left is a column
case QgsExpression::boIsNot:
if ( "NULL" == right.toUpper() )
{
if ( bin->opLeft()->nodeType() != QgsExpression::ntColumnRef )
{
QgsDebugMsg( "Failing IS NOT NULL with non-column on left: " + left );
return Fail;
}
}
break;

default:
break;
}
}

//fallback to default handling
QgsDebugMsg( QString( "fallback: %1 - " ).arg( nodeType( node ) ) );
QgsSqlExpressionCompiler::Result rc = QgsSqlExpressionCompiler::compileNode( node, result );
QgsDebugMsg( QString( "fallback: %1 - " ).arg( resultType( rc ) ) + result );
return rc;
}

QString QgsDb2ExpressionCompiler::quotedValue( const QVariant& value, bool& ok )
{
ok = true;
// Seemed necessary in initial Python testing but can't identify failing case now
#if 0
if ( value.isNull() )
{
//no NULL literal support
ok = false;
return QString();
}
#endif
switch ( value.type() )
{
case QVariant::Bool:
//no boolean literal support in db2, so fake it
return value.toBool() ? "(1=1)" : "(1=0)";

default:
QString result = QgsSqlExpressionCompiler::quotedValue( value, ok );
return QgsSqlExpressionCompiler::quotedValue( value, ok );
}
}
37 changes: 37 additions & 0 deletions src/providers/db2/qgsdb2expressioncompiler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/***************************************************************************
qgsdb2expressioncompiler.h - DB2 expression compiler
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2LEXPRESSIONCOMPILER_H
#define QGSDB2LEXPRESSIONCOMPILER_H

#include "qgssqlexpressioncompiler.h"
#include "qgsexpression.h"
#include "qgsdb2featureiterator.h"

class QgsDb2ExpressionCompiler : public QgsSqlExpressionCompiler
{
public:

explicit QgsDb2ExpressionCompiler( QgsDb2FeatureSource* source );

protected:
virtual Result compileNode( const QgsExpression::Node* node, QString& result ) override;
virtual QString quotedValue( const QVariant& value, bool& ok ) override;

};

#endif // QGSDB2EXPRESSIONCOMPILER_H
458 changes: 458 additions & 0 deletions src/providers/db2/qgsdb2featureiterator.cpp

Large diffs are not rendered by default.

110 changes: 110 additions & 0 deletions src/providers/db2/qgsdb2featureiterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/***************************************************************************
qgsdb2featureiterator.h - DB2 spatial feature processing
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2FEATUREITERATOR_H
#define QGSDB2FEATUREITERATOR_H

#include "qgsfeatureiterator.h"
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>

class QgsDb2Provider;

class QgsDb2FeatureSource : public QgsAbstractFeatureSource
{
public:
explicit QgsDb2FeatureSource( const QgsDb2Provider* p );
~QgsDb2FeatureSource();

virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ) override;

protected:
QgsFields mFields;
QString mFidColName;
long mSRId;

QString mGeometryColName;
QString mGeometryColType;

// current layer name
QString mSchemaName;
QString mTableName;

// server access
QString mConnInfo;

// SQL statement used to limit the features retrieved
QString mSqlWhereClause;

// Return True if this feature source has spatial attributes.
bool isSpatial() { return !mGeometryColName.isEmpty() || !mGeometryColType.isEmpty(); }

friend class QgsDb2FeatureIterator;
friend class QgsDb2ExpressionCompiler;
};

class QgsDb2FeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsDb2FeatureSource>
{
public:
QgsDb2FeatureIterator( QgsDb2FeatureSource* source, bool ownSource, const QgsFeatureRequest& request );

~QgsDb2FeatureIterator();

//! reset the iterator to the starting position
virtual bool rewind() override;

//! end of iterating: free the resources / lock
virtual bool close() override;

protected:
void BuildStatement( const QgsFeatureRequest& request );

//! fetch next feature, return true on success
virtual bool fetchFeature( QgsFeature& feature ) override;

//! fetch next feature filter expression
bool nextFeatureFilterExpression( QgsFeature& f ) override;

private:

virtual bool prepareOrderBy( const QList<QgsFeatureRequest::OrderByClause> &orderBys ) override;


// The current database
QSqlDatabase mDatabase;
QString mOrderByClause;

// The current sql query
QSqlQuery* mQuery;

// The current sql statement
QString mStatement;

// Field index of FID column
long mFidCol;

// List of attribute indices to fetch with nextFeature calls
QgsAttributeList mAttributesToFetch;

bool mExpressionCompiled;
bool mOrderByCompiled;

int mFetchCount;
};

#endif // QGSDB2FEATUREITERATOR_H
163 changes: 163 additions & 0 deletions src/providers/db2/qgsdb2geometrycolumns.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/***************************************************************************
qgsdb2geometrycolumns.cpp - Access DB2 geometry columns table
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/
#include "qgsdb2geometrycolumns.h"
#include "qgsdb2tablemodel.h" // needed for QgsDB2LayerProperty
#include "qgsdb2newconnection.h" // needed for ENV_ZOS, ENV_LUW
#include <QtSql>
#include <qgslogger.h>


QgsDb2GeometryColumns::QgsDb2GeometryColumns( const QSqlDatabase db )
: mDatabase( db )
{
QgsDebugMsg( "constructing" );
}
QgsDb2GeometryColumns::~QgsDb2GeometryColumns( )
{
mQuery.clear();
}


int QgsDb2GeometryColumns::open()
{
return open( QString(), QString() );
}

int QgsDb2GeometryColumns::open( const QString &schemaName, const QString &tableName )
{
QString queryExtents( "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, TYPE_NAME, "
"SRS_ID, SRS_NAME, MIN_X, MIN_Y, MAX_X, MAX_Y "
"FROM DB2GSE.ST_GEOMETRY_COLUMNS" );
QString queryNoExtents( "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, TYPE_NAME, "
"SRS_ID, SRS_NAME "
"FROM DB2GSE.ST_GEOMETRY_COLUMNS" );
mQuery = QSqlQuery( mDatabase );
int sqlcode = 0;
mEnvironment = ENV_LUW;

if ( !schemaName.isEmpty() && !tableName.isEmpty() )
{
QString whereClause = QString( " WHERE TABLE_SCHEMA = '%1' AND TABLE_NAME = '%2'" )
.arg( schemaName, tableName );
queryExtents += whereClause;
queryNoExtents += whereClause;
}
QgsDebugMsg( queryExtents );
// issue the sql query
if ( !mQuery.exec( queryExtents ) )
{
QgsDebugMsg( "ST_Geometry_Columns query failed: " + mDatabase.lastError().text() );
sqlcode = mQuery.lastError().number();
QgsDebugMsg( QString( "SQLCODE: %1" ).arg( sqlcode ) );
/* The MIN_X, MIN_Y, MAX_X, and MAX_Y columns are not available on z/OS (and LUW 9.5)
so SQLCODE -206 is returned when specifying non-existent columns. */
if ( mQuery.lastError().number() == -206 )
{
QgsDebugMsg( "Try query with no extents" );
mQuery.clear();

if ( !mQuery.exec( queryNoExtents ) )
{
QgsDebugMsg( QString( "SQLCODE: %1" ).arg( mQuery.lastError().number() ) );
}
else
{
QgsDebugMsg( "success; must be z/OS" );
mEnvironment = ENV_ZOS;
sqlcode = 0;
}
}
}
// QgsDebugMsg( QString( "sqlcode: %1" ).arg( sqlcode ) );

return sqlcode;
}

bool QgsDb2GeometryColumns::isActive()
{
return mQuery.isActive();
}

int QgsDb2GeometryColumns::db2Environment()
{
return mEnvironment;
}

bool QgsDb2GeometryColumns::populateLayerProperty( QgsDb2LayerProperty &layer )
{
if ( !mQuery.isActive() || !mQuery.next() )
{
return false;
}

layer.schemaName = mQuery.value( 0 ).toString().trimmed();
layer.tableName = mQuery.value( 1 ).toString().trimmed();
layer.geometryColName = mQuery.value( 2 ).toString().trimmed();
layer.type = mQuery.value( 3 ).toString();
if ( mQuery.value( 4 ).isNull() )
{
layer.srid = QString( "" );
layer.srsName = QString( "" );
}
else
{
layer.srid = mQuery.value( 4 ).toString();
layer.srsName = mQuery.value( 5 ).toString();
}
layer.extents = QString( "0 0 0 0" ); // no extents
if ( ENV_LUW == mEnvironment )
{
if ( !mQuery.value( 6 ).isNull() ) // Don't get values if null
{
layer.extents = QString(
mQuery.value( 6 ).toString() + ' ' +
mQuery.value( 7 ).toString() + ' ' +
mQuery.value( 8 ).toString() + ' ' +
mQuery.value( 9 ).toString() ).trimmed();
}
}
QgsDebugMsg( QString( "layer: %1.%2(%3) type='%4' srid='%5' srsName='%6'" )
.arg( layer.schemaName, layer.tableName, layer.geometryColName,
layer.type, layer.srid, layer.srsName ) );
QgsDebugMsg( "Extents: '" + layer.extents + "'" );

layer.pkCols = QStringList();

// Use the Qt functionality to get the primary key information
// to set the FID column.
// We can only use the primary key if it only has one column and
// the type is Integer or BigInt.
QString table = QString( "%1.%2" ).arg( layer.schemaName, layer.tableName );
QSqlIndex pk = mDatabase.primaryIndex( table );
if ( pk.count() == 1 )
{
QSqlField pkFld = pk.field( 0 );
QVariant::Type pkType = pkFld.type();
if (( pkType == QVariant::Int || pkType == QVariant::LongLong ) )
{
QString fidColName = pk.fieldName( 0 );
layer.pkCols.append( fidColName );
QgsDebugMsg( "pk is: " + fidColName );
}
}
else
{
QgsDebugMsg( "Warning: table primary key count is " + QString::number( pk.count() ) );
}
layer.pkColumnName = layer.pkCols.size() > 0 ? layer.pkCols.at( 0 ) : QString::null;
return true;
}
53 changes: 53 additions & 0 deletions src/providers/db2/qgsdb2geometrycolumns.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/***************************************************************************
qgsdb2geometrycolumns.h - Access DB2 geometry columns table
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2GEOMETRYCOLUMNS_H
#define QGSDB2GEOMETRYCOLUMNS_H

#include "qgsdb2tablemodel.h" // needed for QgsDB2LayerProperty
#include <QtSql>

/**
* @class QgsDb2GeometryColumns
* @brief Data provider for DB2 server.
*/

static const int ENV_LUW = 1, ENV_ZOS = 2;

class QgsDb2GeometryColumns
{
public:
QgsDb2GeometryColumns( const QSqlDatabase db );

~QgsDb2GeometryColumns();

bool isActive();
void close();
int open();
int open( const QString &schemaName, const QString &tableName );
bool populateLayerProperty( QgsDb2LayerProperty &layer );
int db2Environment();

private:

QSqlDatabase mDatabase;
QSqlQuery mQuery;
int mEnvironment;

};

#endif
199 changes: 199 additions & 0 deletions src/providers/db2/qgsdb2newconnection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/***************************************************************************
qgsdb2newconnection.cpp - new DB2 connection dialog
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
Adapted from MSSQL provider by Tamas Szekeres
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#include <QSettings>
#include <QInputDialog>
#include <QMessageBox>
#include <QLabel>
#include <qgslogger.h>
#include <qlistwidget.h>

#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlError>

#include "qgsdb2newconnection.h"
#include "qgsdb2dataitems.h"
#include "qgsdb2provider.h"
#include "qgscontexthelp.h"

QgsDb2NewConnection::QgsDb2NewConnection( QWidget *parent, const QString& connName, Qt::WindowFlags fl )
: QDialog( parent, fl ), mOriginalConnName( connName )
, mAuthConfigSelect( nullptr )
{
setupUi( this );

mAuthConfigSelect = new QgsAuthConfigSelect( this, "db2" );
tabAuthentication->insertTab( 1, mAuthConfigSelect, tr( "Configurations" ) );

if ( !connName.isEmpty() )
{
// populate the dialog with the information stored for the connection
// populate the fields with the stored setting parameters
QSettings settings;

QString key = "/DB2/connections/" + connName;
txtService->setText( settings.value( key + "/service" ).toString() );
txtHost->setText( settings.value( key + "/host" ).toString() );
txtPort->setText( settings.value( key + "/port" ).toString() );
txtDriver->setText( settings.value( key + "/driver" ).toString() );
txtDatabase->setText( settings.value( key + "/database" ).toString() );


if ( settings.value( key + "/saveUsername" ).toString() == "true" )
{
txtUsername->setText( settings.value( key + "/username" ).toString() );
chkStoreUsername->setChecked( true );
}

if ( settings.value( key + "/savePassword" ).toString() == "true" )
{
txtPassword->setText( settings.value( key + "/password" ).toString() );
chkStorePassword->setChecked( true );
}

QString authcfg = settings.value( key + "/authcfg" ).toString();
QgsDebugMsg( QString( "authcfg: %1" ).arg( authcfg ) );
mAuthConfigSelect->setConfigId( authcfg );
if ( !authcfg.isEmpty() )
{
tabAuthentication->setCurrentIndex( tabAuthentication->indexOf( mAuthConfigSelect ) );
}

txtName->setText( connName );
}
}

/** Autoconnected SLOTS **/
void QgsDb2NewConnection::accept()
{
QSettings settings;
QString baseKey = "/DB2/connections/";
settings.setValue( baseKey + "selected", txtName->text() );
bool hasAuthConfigID = !mAuthConfigSelect->configId().isEmpty();
QgsDebugMsg( QString( "hasAuthConfigID: %1" ).arg( hasAuthConfigID ) );
if ( !hasAuthConfigID && chkStorePassword->isChecked() &&
QMessageBox::question( this,
tr( "Saving passwords" ),
tr( "WARNING: You have opted to save your password. It will be stored in plain text in your project files and in your home directory on Unix-like systems, or in your user profile on Windows. If you do not want this to happen, please press the Cancel button.\n" ),
QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
{
return;
}

// warn if entry was renamed to an existing connection
if (( mOriginalConnName.isNull() || mOriginalConnName.compare( txtName->text(), Qt::CaseInsensitive ) != 0 ) &&
( settings.contains( baseKey + txtName->text() + "/service" ) ||
settings.contains( baseKey + txtName->text() + "/host" ) ) &&
QMessageBox::question( this,
tr( "Save connection" ),
tr( "Should the existing connection %1 be overwritten?" ).arg( txtName->text() ),
QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
{
return;
}

// on rename delete the original entry first
if ( !mOriginalConnName.isNull() && mOriginalConnName != txtName->text() )
{
settings.remove( baseKey + mOriginalConnName );
settings.sync();
}

baseKey += txtName->text();

settings.setValue( baseKey + "/service", txtService->text().trimmed() );
settings.setValue( baseKey + "/host", txtHost->text() );
settings.setValue( baseKey + "/port", txtPort->text() );
settings.setValue( baseKey + "/driver", txtDriver->text() );
settings.setValue( baseKey + "/database", txtDatabase->text() );
settings.setValue( baseKey + "/username", chkStoreUsername->isChecked() && !hasAuthConfigID ? txtUsername->text() : "" );
settings.setValue( baseKey + "/password", chkStorePassword->isChecked() && !hasAuthConfigID ? txtPassword->text() : "" );
settings.setValue( baseKey + "/saveUsername", chkStoreUsername->isChecked() && !hasAuthConfigID ? "true" : "false" );
settings.setValue( baseKey + "/savePassword", chkStorePassword->isChecked() && !hasAuthConfigID ? "true" : "false" );
settings.setValue( baseKey + "/authcfg", mAuthConfigSelect->configId() );

QDialog::accept();
}

void QgsDb2NewConnection::on_btnConnect_clicked()
{
QgsDebugMsg( "DB2: TestDatabase; button clicked" );
testConnection();
}

void QgsDb2NewConnection::on_btnListDatabase_clicked()
{
listDatabases();
}

void QgsDb2NewConnection::on_cb_trustedConnection_clicked()
{

}

/** End Autoconnected SLOTS **/

QgsDb2NewConnection::~QgsDb2NewConnection()
{

}

bool QgsDb2NewConnection::testConnection()
{
QSqlDatabase db;

QString authcfg;
QString connInfo;
QString errMsg;
bool rc = QgsDb2ConnectionItem::ConnInfoFromParameters(
txtService->text().trimmed(),
txtDriver->text().trimmed(),
txtHost->text().trimmed(),
txtPort->text().trimmed(),
txtDatabase->text().trimmed(),
txtUsername->text().trimmed(),
txtPassword->text().trimmed(),
authcfg,
connInfo, errMsg );

if ( !rc )
{
db2ConnectStatus -> setText( errMsg );
QgsDebugMsg( "errMsg: " + errMsg );
return false;
}

db = QgsDb2Provider::getDatabase( connInfo, errMsg );
if ( errMsg.isEmpty() )
{
QgsDebugMsg( "connection open succeeded " + connInfo );
db2ConnectStatus -> setText( "DB2 connection open succeeded" );
return true;
}
else
{
QgsDebugMsg( "connection open failed: " + errMsg );
db2ConnectStatus -> setText( "DB2 connection failed : " + errMsg );
return false;
}
}

void QgsDb2NewConnection::listDatabases()
{
QgsDebugMsg( "DB2 New Connection Dialogue : list database" );
}
56 changes: 56 additions & 0 deletions src/providers/db2/qgsdb2newconnection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/***************************************************************************
qgsdb2newconnection.h - new DB2 connection dialog
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2NEWCONNECTION_H
#define QGSDB2NEWCONNECTION_H
#include "ui_qgsdb2newconnectionbase.h"
#include "qgisgui.h"
#include "qgscontexthelp.h"
#include "qgsauthconfigselect.h"

/** \class QgsDb2NewConnection
* \brief Dialog to allow the user to configure and save connection
* information for an DB2 database
*/
class QgsDb2NewConnection : public QDialog, private Ui::QgsDb2NewConnectionBase
{
Q_OBJECT
public:
//! Constructor
QgsDb2NewConnection( QWidget *parent = 0, const QString& connName = QString::null, Qt::WindowFlags fl = QgisGui::ModalDialogFlags );

//! Destructor
~QgsDb2NewConnection();

//! Tests the connection using the parameters supplied
bool testConnection();
/**
* @brief List all databases found for the given server.
*/
void listDatabases();
public slots:
void accept() override;
void on_btnListDatabase_clicked();
void on_btnConnect_clicked();
void on_cb_trustedConnection_clicked();
void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); }
private:
QString mOriginalConnName; //store initial name to delete entry in case of rename
QgsAuthConfigSelect * mAuthConfigSelect;
};

#endif // QGSDB2NEWCONNECTION_H
1,757 changes: 1,757 additions & 0 deletions src/providers/db2/qgsdb2provider.cpp

Large diffs are not rendered by default.

201 changes: 201 additions & 0 deletions src/providers/db2/qgsdb2provider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/***************************************************************************
qgsdb2provider.h - Data provider for DB2 server
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2PROVIDER_H
#define QGSDB2PROVIDER_H

#include "qgsvectordataprovider.h"
#include "qgsvectorlayerimport.h"
#include <qgscoordinatereferencesystem.h>
#include "qgsgeometry.h"
#include <QtSql>

/**
* @class QgsDb2Provider
* @brief Data provider for DB2 server.
*/
class QgsDb2Provider : public QgsVectorDataProvider
{
Q_OBJECT

public:
explicit QgsDb2Provider( QString uri = QString() );

virtual ~QgsDb2Provider();

/**
* Returns a QSqlDatabase object that can connect to DB2 for LUW or z/OS.
*
* If service is provided, then username and password is required.
* If service is not provided, the remaining arguments are required.
*
* @param connInfo A string containing all connection information.
*/
static QSqlDatabase getDatabase( const QString &connInfo, QString &errMsg );

static bool openDatabase( QSqlDatabase db );

virtual QgsAbstractFeatureSource* featureSource() const override;

/**
* Get feature iterator.
* @return QgsFeatureIterator to iterate features.
*/
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request = QgsFeatureRequest() ) override;

/**
* Get feature type.
* @return int representing the feature type
*/
virtual QGis::WkbType geometryType() const override;

/**
* Number of features in the layer
* @return long containing number of features
*/
virtual long featureCount() const override;

/**
* Update the extent for this layer.
*/
void updateStatistics();

/**
* Return a map of indexes with field names for this layer.
* @return map of fields
*/
virtual const QgsFields &fields() const override;

virtual QgsCoordinateReferenceSystem crs() override;

/**
* Return the extent for this data layer.
*/
virtual QgsRectangle extent() override;

/**
* Returns true if this is a valid data source.
*/
virtual bool isValid() override;

/**
* Accessor for SQL WHERE clause used to limit dataset.
*/
QString subsetString() override;

/**
* Mutator for SQL WHERE clause used to limit dataset size.
*/
bool setSubsetString( const QString& theSQL, bool updateFeatureCount = true ) override;

virtual bool supportsSubsetString() override { return true; }

/** Return a provider name
Essentially just returns the provider key. Should be used to build file
dialogs so that providers can be shown with their supported types. Thus
if more than one provider supports a given format, the user is able to
select a specific provider to open that file.
*/
virtual QString name() const override;

/** Return description
Return a terse string describing what the provider is.
*/
virtual QString description() const override;

/** Returns a bitmask containing the supported capabilities
Note, some capabilities may change depending on whether
a spatial filter is active on this provider, so it may
be prudent to check this value per intended operation.
*/
virtual int capabilities() const override;

/** Writes a list of features to the database*/
virtual bool addFeatures( QgsFeatureList & flist ) override;

/** Deletes a feature*/
virtual bool deleteFeatures( const QgsFeatureIds & id ) override;

/** Changes attribute values of existing features */
virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override;

/** Changes existing geometries*/
virtual bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override;

/** Import a vector layer into the database */
static QgsVectorLayerImport::ImportError createEmptyLayer(
const QString& uri,
const QgsFields &fields,
QGis::WkbType wkbType,
const QgsCoordinateReferenceSystem *srs,
bool overwrite,
QMap<int, int> *oldToNewAttrIdxMap,
QString *errorMessage = nullptr,
const QMap<QString, QVariant> *options = nullptr
);

/** Convert a QgsField to work with DB2 */
static bool convertField( QgsField &field );

/** Convert a QgsField to work with DB2 */
static QString qgsFieldToDb2Field( QgsField field );

protected:
/** Loads fields from input file to member attributeFields */
QVariant::Type decodeSqlType( int typeId );
void loadMetadata();
void loadFields();

private:
static void db2WkbTypeAndDimension( QGis::WkbType wkbType, QString &geometryType, int &dim );
static QString db2TypeName( int typeId );

QgsFields mAttributeFields; //fields
QMap<int, QVariant> mDefaultValues;
QgsRectangle mExtent; //layer extent
bool mValid;
bool mUseEstimatedMetadata;
bool mSkipFailures;
long mNumberFeatures;
QString mFidColName;
QString mExtents;
long mSRId;
int mEnvironment;
QString mSrsName;
QString mGeometryColName, mGeometryColType;
QString mLastError; //string containing the last reported error message
QgsCoordinateReferenceSystem mCrs; //coordinate reference system
QGis::WkbType mWkbType;
QSqlQuery mQuery; //current SQL query
QString mConnInfo; // full connection information
QString mSchemaName, mTableName; //current layer schema/name
QString mSqlWhereClause; //SQL statement used to limit the features retrieved
QSqlDatabase mDatabase; //the database object
static int sConnectionId;

//sets the error messages
void setLastError( const QString& error )
{
mLastError = error;
}

friend class QgsDb2FeatureSource;
};

#endif
768 changes: 768 additions & 0 deletions src/providers/db2/qgsdb2sourceselect.cpp

Large diffs are not rendered by default.

191 changes: 191 additions & 0 deletions src/providers/db2/qgsdb2sourceselect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/***************************************************************************
qgsdb2sourceselect.h
dialog to select DB2 layer(s) and add to the map canvas
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2SOURCESELECT_H
#define QGSDB2SOURCESELECT_H

#include "ui_qgsdbsourceselectbase.h"
#include "qgisgui.h"
#include "qgsdbfilterproxymodel.h"
#include "qgsdb2tablemodel.h"
#include "qgscontexthelp.h"

#include <QMap>
#include <QPair>
#include <QIcon>
#include <QItemDelegate>
#include <QThread>

class QPushButton;
class QStringList;
class QgsGeomColumnTypeThread;
class QgisApp;

class QgsDb2SourceSelectDelegate : public QItemDelegate
{
Q_OBJECT

public:
explicit QgsDb2SourceSelectDelegate( QObject *parent = NULL )
: QItemDelegate( parent )
{}

QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override;
};

// A class that determines the geometry type of a given database
// schema.table.column, with the option of doing so in a separate
// thread.

class QgsDb2GeomColumnTypeThread : public QThread
{
Q_OBJECT
public:
QgsDb2GeomColumnTypeThread( QString connectionName, bool useEstimatedMetadata );

// These functions get the layer types and pass that information out
// by emitting the setLayerType() signal.
virtual void run() override;

signals:
void setLayerType( QgsDb2LayerProperty layerProperty );

public slots:
void addGeometryColumn( QgsDb2LayerProperty layerProperty );
void stop();

private:
QgsDb2GeomColumnTypeThread() {}

QString mConnectionName;
bool mUseEstimatedMetadata;
bool mStopped;
QList<QgsDb2LayerProperty> layerProperties;
};


/** \class QgsDb2SourceSelect
* \brief Dialog to create connections and add tables from Db2.
*
* This dialog allows the user to define and save connection information
* for Db2 databases. The user can then connect and add
* tables from the database to the map canvas.
*/
class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase
{
Q_OBJECT

public:

//! static function to delete a connection
static void deleteConnection( QString key );

//! Constructor
QgsDb2SourceSelect( QWidget *parent = 0, Qt::WindowFlags fl = QgisGui::ModalDialogFlags, bool managerMode = false, bool embeddedMode = false );
//! Destructor
~QgsDb2SourceSelect();
//! Populate the connection list combo box
void populateConnectionList();
//! String list containing the selected tables
QStringList selectedTables();
//! Connection info (database, host, user, password)
QString connectionInfo();

signals:
void addDatabaseLayers( QStringList const & layerPathList, QString const & providerKey );
void connectionsChanged();
void addGeometryColumn( QgsDb2LayerProperty );

public slots:
//! Determines the tables the user selected and closes the dialog
void addTables();
void buildQuery();

/** Connects to the database using the stored connection parameters.
* Once connected, available layers are displayed.
*/
void on_btnConnect_clicked();
void on_cbxAllowGeometrylessTables_stateChanged( int );
//! Opens the create connection dialog to build a new connection
void on_btnNew_clicked();
//! Opens a dialog to edit an existing connection
void on_btnEdit_clicked();
//! Deletes the selected connection
void on_btnDelete_clicked();
//! Saves the selected connections to file
void on_btnSave_clicked();
//! Loads the selected connections from file
void on_btnLoad_clicked();
void on_mSearchGroupBox_toggled( bool );
void on_mSearchTableEdit_textChanged( const QString & text );
void on_mSearchColumnComboBox_currentIndexChanged( const QString & text );
void on_mSearchModeComboBox_currentIndexChanged( const QString & text );
void setSql( const QModelIndex& index );
//! Store the selected database
void on_cmbConnections_activated( int );
void setLayerType( QgsDb2LayerProperty layerProperty );
void on_mTablesTreeView_clicked( const QModelIndex &index );
void on_mTablesTreeView_doubleClicked( const QModelIndex &index );
//!Sets a new regular expression to the model
void setSearchExpression( const QString& regexp );

void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); }

void columnThreadFinished();

private:
typedef QPair<QString, QString> geomPair;
typedef QList<geomPair> geomCol;

//! Connections manager mode
bool mManagerMode;

//! Embedded mode, without 'Close'
bool mEmbeddedMode;

// queue another query for the thread
void addSearchGeometryColumn( QString connectionName, QgsDb2LayerProperty layerProperty, bool estimateMetadata );

// Set the position of the database connection list to the last
// used one.
void setConnectionListPosition();
// Combine the schema, table and column data into a single string
// useful for display to the user
QString fullDescription( QString schema, QString table, QString column, QString type );
// The column labels
QStringList mColumnLabels;
// Our thread for doing long running queries
QgsDb2GeomColumnTypeThread* mColumnTypeThread;
QString mConnInfo;
QStringList mSelectedTables;
bool mUseEstimatedMetadata;
// Storage for the range of layer type icons
QMap<QString, QPair<QString, QIcon> > mLayerIcons;

//! Model that acts as datasource for mTableTreeWidget
QgsDb2TableModel mTableModel;
QgsDbFilterProxyModel mProxyModel;

QPushButton *mBuildQueryButton;
QPushButton *mAddButton;

void finishList();
};

#endif // QGSDb2SOURCESELECT_H
484 changes: 484 additions & 0 deletions src/providers/db2/qgsdb2tablemodel.cpp

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions src/providers/db2/qgsdb2tablemodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/***************************************************************************
qgsdb2tablemodel.h - description
--------------------------------------
Date : 2016-01-27
Copyright : (C) 2016 by David Adler
Shirley Xiao, David Nguyen
Email : dadler at adtechgeospatial.com
xshirley2012 at yahoo.com, davidng0123 at gmail.com
****************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/

#ifndef QGSDB2TABLEMODEL_H
#define QGSDB2TABLEMODEL_H

#include <QStandardItemModel>
#include <qgsdataitem.h>
#include "qgis.h"

/** Layer Property structure */
struct QgsDb2LayerProperty
{
QString type;
QString schemaName;
QString tableName;
QString geometryColName;
QStringList pkCols;
QString pkColumnName;
QString srid;
QString srsName;
QString sql;
QString extents;
};


class QIcon;

/** A model that holds the tables of a database in a hierarchy where the
schemas are the root elements that contain the individual tables as children.
The tables have the following columns: Type, Schema, Tablename, Geometry Column, Sql*/
class QgsDb2TableModel : public QStandardItemModel
{
Q_OBJECT
public:
QgsDb2TableModel();
~QgsDb2TableModel();

/** Adds entry for one database table to the model*/
void addTableEntry( const QgsDb2LayerProperty &property );

/** Sets an sql statement that belongs to a cell specified by a model index*/
void setSql( const QModelIndex& index, const QString& sql );

/** Sets one or more geometry types to a row. In case of several types, additional rows are inserted.
This is for tables where the type is dectected later by thread*/
void setGeometryTypesForTable( QgsDb2LayerProperty layerProperty );

/** Returns the number of tables in the model*/
int tableCount() const { return mTableCount; }

enum columns
{
dbtmSchema = 0,
dbtmTable,
dbtmType,
dbtmGeomCol,
dbtmSrid,
dbtmPkCol,
dbtmSelectAtId,
dbtmSql,
dbtmColumns
};

bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;

QString layerURI( const QModelIndex &index, const QString &connInfo, bool useEstimatedMetadata );

static QIcon iconForWkbType( QGis::WkbType type );

static QGis::WkbType wkbTypeFromDb2( QString dbType, int dim = 2 );

static QString displayStringForWkbType( QGis::WkbType type );

private:
/** Number of tables in the model*/
int mTableCount;
};
#endif
14 changes: 14 additions & 0 deletions src/ui/qgisapp.ui
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
<addaction name="mActionAddPgLayer"/>
<addaction name="mActionAddSpatiaLiteLayer"/>
<addaction name="mActionAddMssqlLayer"/>
<addaction name="mActionAddDb2Layer"/>
<addaction name="mActionAddOracleLayer"/>
<addaction name="mActionAddWmsLayer"/>
<addaction name="mActionAddLayerSeparator"/>
Expand Down Expand Up @@ -339,6 +340,7 @@
<addaction name="mActionAddPgLayer"/>
<addaction name="mActionAddSpatiaLiteLayer"/>
<addaction name="mActionAddMssqlLayer"/>
<addaction name="mActionAddDb2Layer"/>
<addaction name="mActionAddOracleLayer"/>
<addaction name="mActionAddWmsLayer"/>
<addaction name="mActionAddWcsLayer"/>
Expand Down Expand Up @@ -1337,6 +1339,18 @@
<string>Ctrl+Shift+M</string>
</property>
</action>
<action name="mActionAddDb2Layer">
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionAddDb2Layer.svg</normaloff>:/images/themes/default/mActionAddDb2Layer.svg</iconset>
</property>
<property name="text">
<string>Add DB2 Spatial Layer...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+2</string>
</property>
</action>
<action name="mActionAddOracleLayer">
<property name="icon">
<iconset resource="../../images/images.qrc">
Expand Down
358 changes: 358 additions & 0 deletions src/ui/qgsdb2newconnectionbase.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsDb2NewConnectionBase</class>
<widget class="QDialog" name="QgsDb2NewConnectionBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>749</width>
<height>582</height>
</rect>
</property>
<property name="windowTitle">
<string>Create a New DB2 connection</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>450</x>
<y>440</y>
<width>221</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>671</width>
<height>371</height>
</rect>
</property>
<property name="title">
<string>Connection Properties</string>
</property>
<widget class="QLabel" name="DB2HostLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>110</y>
<width>91</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>DB2 Host:</string>
</property>
</widget>
<widget class="QLabel" name="DB2port">
<property name="geometry">
<rect>
<x>20</x>
<y>150</y>
<width>91</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>DB2 Port:</string>
</property>
</widget>
<widget class="QLabel" name="database">
<property name="geometry">
<rect>
<x>20</x>
<y>180</y>
<width>91</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>Database:</string>
</property>
</widget>
<widget class="QLineEdit" name="txtPort">
<property name="geometry">
<rect>
<x>140</x>
<y>140</y>
<width>231</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="txtDatabase">
<property name="geometry">
<rect>
<x>140</x>
<y>180</y>
<width>231</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="txtHost">
<property name="geometry">
<rect>
<x>140</x>
<y>100</y>
<width>231</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="btnConnect">
<property name="geometry">
<rect>
<x>520</x>
<y>250</y>
<width>141</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>Test connection</string>
</property>
</widget>
<widget class="QLineEdit" name="txtService">
<property name="geometry">
<rect>
<x>560</x>
<y>70</y>
<width>111</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="DB2ServiceLabel">
<property name="geometry">
<rect>
<x>430</x>
<y>70</y>
<width>121</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>Service/DSN:</string>
</property>
</widget>
<widget class="QLineEdit" name="txtDriver">
<property name="geometry">
<rect>
<x>140</x>
<y>60</y>
<width>231</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="DB2ServiceLabel_2">
<property name="geometry">
<rect>
<x>20</x>
<y>70</y>
<width>91</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>Driver:</string>
</property>
</widget>
<widget class="QLineEdit" name="txtName">
<property name="geometry">
<rect>
<x>560</x>
<y>20</y>
<width>121</width>
<height>31</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="DB2ServiceLabel_3">
<property name="geometry">
<rect>
<x>390</x>
<y>20</y>
<width>161</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Connection Name:</string>
</property>
</widget>
<widget class="QTabWidget" name="tabAuthentication">
<property name="geometry">
<rect>
<x>20</x>
<y>230</y>
<width>417</width>
<height>131</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabBasic">
<attribute name="title">
<string>Authentication</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="TextLabel3">
<property name="text">
<string>Username</string>
</property>
<property name="buddy">
<cstring>txtUsername</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txtPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="chkStoreUsername">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="TextLabel3_2">
<property name="text">
<string>Password</string>
</property>
<property name="buddy">
<cstring>txtPassword</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtUsername"/>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="chkStorePassword">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QLabel" name="db2ConnectStatus">
<property name="geometry">
<rect>
<x>20</x>
<y>410</y>
<width>711</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>DB2 Connect Status: </string>
</property>
</widget>
</widget>
<tabstops>
<tabstop>txtName</tabstop>
<tabstop>txtService</tabstop>
<tabstop>txtDriver</tabstop>
<tabstop>txtHost</tabstop>
<tabstop>txtPort</tabstop>
<tabstop>txtDatabase</tabstop>
<tabstop>tabAuthentication</tabstop>
<tabstop>txtUsername</tabstop>
<tabstop>chkStoreUsername</tabstop>
<tabstop>txtPassword</tabstop>
<tabstop>chkStorePassword</tabstop>
<tabstop>btnConnect</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>btnConnect</sender>
<signal>released()</signal>
<receiver>QgsDb2NewConnectionBase</receiver>
<slot>on_btnConnect_clicked()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsDb2NewConnectionBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsDb2NewConnectionBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
29 changes: 28 additions & 1 deletion tests/src/core/testqgsdatasourceuri.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ void TestQgsDataSourceUri::checkparser_data()
QTest::addColumn<QString>( "dbname" );
QTest::addColumn<QString>( "host" );
QTest::addColumn<QString>( "port" );
QTest::addColumn<QString>( "driver" );
QTest::addColumn<QgsDataSourceURI::SSLmode>( "sslmode" );
QTest::addColumn<QString>( "sql" );
QTest::addColumn<QString>( "myparam" );


QTest::newRow( "oci" )
<< "host=myhost port=1234 user='myname' password='mypasswd' estimatedmetadata=true srid=1000003007 table=\"myschema\".\"mytable\" (GEOM) myparam='myvalue' sql="
<< "mytable" // table
Expand All @@ -64,6 +66,7 @@ void TestQgsDataSourceUri::checkparser_data()
<< "" // dbname
<< "myhost" // host
<< "1234" // port
<< "" // driver
<< QgsDataSourceURI::SSLprefer // sslmode
<< "" // sql
<< "myvalue" // myparam
Expand All @@ -84,6 +87,7 @@ void TestQgsDataSourceUri::checkparser_data()
<< "mydb" // dbname
<< "myhost" // host
<< "5432" // port
<< "" // driver
<< QgsDataSourceURI::SSLprefer // sslmode
<< "" // sql
<< "" // myparam
Expand All @@ -104,10 +108,32 @@ void TestQgsDataSourceUri::checkparser_data()
<< "mydb" // dbname
<< "myhost" // host
<< "5432" // port
<< "" // driver
<< QgsDataSourceURI::SSLprefer // sslmode
<< "" // sql
<< "" // myparam
;

QTest::newRow( "DB2" )
<< "host=localhost port=50000 dbname=OSTEST user='osuser' password='osuserpw' estimatedmetadata=true srid=4326 key=OBJECTID table=TEST.ZIPPOINT (GEOM) myparam='myvalue' driver='IBM DB2 ODBC DRIVER' sql="
<< "TEST.ZIPPOINT" // table
<< "GEOM" // geometrycolumn
<< "OBJECTID" // key
<< true // estimatedmetadata
<< "4326" // srid
<< QgsWKBTypes::Unknown // type
<< false // selectatid
<< "" // service
<< "osuser" // user
<< "osuserpw" // password
<< "OSTEST" // dbname
<< "localhost" // host
<< "50000" // port
<< "IBM DB2 ODBC DRIVER" // driver
<< QgsDataSourceURI::SSLprefer // sslmode
<< "" // sql
<< "myvalue" // myparam
;
}

void TestQgsDataSourceUri::checkparser()
Expand All @@ -126,6 +152,7 @@ void TestQgsDataSourceUri::checkparser()
QFETCH( QString, dbname );
QFETCH( QString, host );
QFETCH( QString, port );
QFETCH( QString, driver );
QFETCH( QgsDataSourceURI::SSLmode, sslmode );
QFETCH( QString, sql );
QFETCH( QString, myparam );
Expand All @@ -144,11 +171,11 @@ void TestQgsDataSourceUri::checkparser()
QCOMPARE( ds.database(), dbname );
QCOMPARE( ds.host(), host );
QCOMPARE( ds.port(), port );
QCOMPARE( ds.driver(), driver );
QCOMPARE( ds.sslMode(), sslmode );
QCOMPARE( ds.sql(), sql );
QCOMPARE( ds.param( "myparam" ), myparam );
}


QTEST_MAIN( TestQgsDataSourceUri )
#include "testqgsdatasourceuri.moc"
6 changes: 3 additions & 3 deletions tests/src/python/providertestbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,9 @@ def testGetFeaturesSubsetAttributes2(self):

for field_to_fetch in ['pk', 'cnt', 'name', 'name2']:
for f in self.provider.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_to_fetch], self.provider.fields())):
# Check that all other fields are NULL
for other_field in [field.name() for field in self.provider.fields() if field.name() != field_to_fetch]:
if other_field == 'pk':
# Check that all other fields are NULL and force name to lower-case
for other_field in [field.name() for field in self.provider.fields() if field.name().lower() != field_to_fetch]:
if other_field == 'pk' or other_field == 'PK':
# skip checking the primary key field, as it may be validly fetched by providers to use as feature id
continue
self.assertEqual(f[other_field], NULL, 'Value for field "{}" was present when it should not have been fetched by request'.format(other_field))
Expand Down
81 changes: 81 additions & 0 deletions tests/src/python/test_provider_db2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for the MS SQL provider.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'David Adler'
__date__ = '2016-03-01'
__copyright__ = 'Copyright 2016, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import qgis
import os
import sys
from qgis.core import NULL
from pprint import pprint
from qgis.core import (
QgsVectorLayer,
QgsFeatureRequest,
QgsFeature,
QgsProviderRegistry
)

from PyQt4.QtCore import (
QSettings,
QDate,
QTime,
QDateTime,
QVariant
)

from utilities import unitTestDataPath
from qgis.testing import (
start_app,
unittest
)
from providertestbase import ProviderTestCase

start_app()
TEST_DATA_DIR = unitTestDataPath()


class TestPyQgsDb2Provider(unittest.TestCase, ProviderTestCase):

@classmethod
def setUpClass(cls):
"""Run before all tests"""
cls.dbconn = u"dbname='ostest' driver='IBM DB2 ODBC DRIVER' host=dadler.dynalias.org port=50000 user='osuser' password='osuserpw'"
if 'QGIS_DB2_DB' in os.environ:
cls.dbconn = os.environ['QGIS_DB2TEST_DB']
# Create test layer
cls.vl = QgsVectorLayer(cls.dbconn + ' srid=4326 type=Point table="QGIS_TEST"."SOMEDATA" (GEOM) sql=', 'test', 'DB2')
assert(cls.vl.isValid())
cls.provider = cls.vl.dataProvider()
cls.poly_vl = QgsVectorLayer(
cls.dbconn + ' srid=4326 type=POLYGON table="QGIS_TEST"."SOME_POLY_DATA" (geom) sql=', 'test', 'DB2')
assert(cls.poly_vl.isValid())
cls.poly_provider = cls.poly_vl.dataProvider()

@classmethod
def tearDownClass(cls):
"""Run after all tests"""

def setUp(self):
print ("starting " + self._testMethodName)

def getSubsetString(self):
"""Individual providers may need to override this depending on their subset string formats"""
return 'cnt > 100 and cnt < 410'

def enableCompiler(self):
QSettings().setValue(u'/qgis/compileExpressions', True)

def disableCompiler(self):
QSettings().setValue(u'/qgis/compileExpressions', False)

if __name__ == '__main__':
unittest.main()
43 changes: 43 additions & 0 deletions tests/testdata/provider/testdata_db2.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
DROP TABLE qgis_test.someData;
DROP TABLE qgis_test.some_poly_data;
DROP TABLE qgis_test.date_times;

CREATE TABLE qgis_test.someData (
pk integer NOT NULL PRIMARY KEY,
cnt integer,
name varchar(32) DEFAULT 'qgis',
name2 varchar(32) DEFAULT 'qgis',
num_char char(1),
geom db2gse.st_point
);

CREATE TABLE qgis_test.some_poly_data (
pk integer NOT NULL PRIMARY KEY,
geom db2gse.st_polygon
);

CREATE TABLE qgis_test.date_times (
id integer NOT NULL PRIMARY KEY,
date_field date,
time_field time,
datetime_field timestamp
);

INSERT INTO qgis_test.someData (pk, cnt, name, name2, num_char, geom) VALUES
(5, -200, NULL, 'NuLl', '5', db2gse.st_point( 'Point(-71.123 78.23)', 4326 )),
(3, 300, 'Pear', 'PEaR', '3', NULL),
(1, 100, 'Orange', 'oranGe', '1', db2gse.st_point( 'Point(-70.332 66.33)', 4326 )),
(2, 200, 'Apple', 'Apple', '2', db2gse.st_point( 'Point(-68.2 70.8)', 4326 )),
(4, 400, 'Honey', 'Honey', '4', db2gse.st_point( 'Point(-65.32 78.3)', 4326 ))
;

INSERT INTO qgis_test.some_poly_data (pk, geom) VALUES
(1, db2gse.st_polygon('Polygon ((-69.0 81.4, -69.0 80.2, -73.7 80.2, -73.7 76.3, -74.9 76.3, -74.9 81.4, -69.0 81.4))', 4326 )),
(2, db2gse.st_polygon('Polygon ((-67.6 81.2, -66.3 81.2, -66.3 76.9, -67.6 76.9, -67.6 81.2))', 4326 )),
(3, db2gse.st_polygon('Polygon ((-68.4 75.8, -67.5 72.6, -68.6 73.7, -70.2 72.9, -68.4 75.8))', 4326 )),
(4, NULL)
;


INSERT INTO qgis_test.date_times (id, date_field, time_field, datetime_field ) VALUES
(1, '2004-03-04', '13:41:52', '2004-03-04 13:41:52' );