mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-22 00:14:55 -05:00
1779 lines
51 KiB
C++
1779 lines
51 KiB
C++
/***************************************************************************
|
|
qgsdb2provider.cpp - 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
|
|
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 "qgsdb2provider.h"
|
|
#include "qgsdb2dataitems.h"
|
|
#include "qgsdb2featureiterator.h"
|
|
#include "qgsdb2geometrycolumns.h"
|
|
#include "qgscoordinatereferencesystem.h"
|
|
#include "qgslogger.h"
|
|
#include "qgscredentials.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgssettings.h"
|
|
#include <QThread>
|
|
#include <QSqlRecord>
|
|
#include <QSqlField>
|
|
|
|
const QString QgsDb2Provider::DB2_PROVIDER_KEY = QStringLiteral( "DB2" );
|
|
const QString QgsDb2Provider::DB2_PROVIDER_DESCRIPTION = QStringLiteral( "DB2 Spatial Extender provider" );
|
|
|
|
int QgsDb2Provider::sConnectionId = 0;
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
|
QMutex QgsDb2Provider::sMutex { QMutex::Recursive };
|
|
#else
|
|
QRecursiveMutex QgsDb2Provider::sMutex;
|
|
#endif
|
|
|
|
QgsDb2Provider::QgsDb2Provider( const QString &uri, const ProviderOptions &options,
|
|
QgsDataProvider::ReadFlags flags )
|
|
: QgsVectorDataProvider( uri, options, flags )
|
|
, mEnvironment( ENV_LUW )
|
|
{
|
|
QgsDebugMsg( "uri: " + uri );
|
|
QgsDataSourceUri anUri = QgsDataSourceUri( uri );
|
|
if ( !anUri.srid().isEmpty() )
|
|
mSRId = anUri.srid().toInt();
|
|
else
|
|
mSRId = -1;
|
|
|
|
if ( 0 != anUri.wkbType() )
|
|
{
|
|
mWkbType = anUri.wkbType();
|
|
}
|
|
QgsDebugMsg( QStringLiteral( "mWkbType: %1" ).arg( mWkbType ) );
|
|
QgsDebugMsg( QStringLiteral( "new mWkbType: %1" ).arg( anUri.wkbType() ) );
|
|
|
|
mValid = true;
|
|
mSkipFailures = false;
|
|
int dim; // Not used
|
|
db2WkbTypeAndDimension( mWkbType, mGeometryColType, dim ); // Get DB2 geometry type name
|
|
|
|
mFidColName = anUri.keyColumn().toUpper();
|
|
QgsDebugMsg( "mFidColName " + mFidColName );
|
|
mExtents = anUri.param( QStringLiteral( "extents" ) );
|
|
QgsDebugMsg( "mExtents " + mExtents );
|
|
|
|
mUseEstimatedMetadata = anUri.useEstimatedMetadata();
|
|
if ( mReadFlags & QgsDataProvider::FlagTrustDataSource )
|
|
{
|
|
mUseEstimatedMetadata = true;
|
|
}
|
|
QgsDebugMsg( QStringLiteral( "mUseEstimatedMetadata: '%1'" ).arg( mUseEstimatedMetadata ) );
|
|
mSqlWhereClause = anUri.sql();
|
|
QString errMsg;
|
|
mDatabase = getDatabase( uri, errMsg );
|
|
mConnInfo = anUri.connectionInfo();
|
|
QgsCoordinateReferenceSystem layerCrs = crs();
|
|
QgsDebugMsg( "CRS: " + layerCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) );
|
|
|
|
if ( !errMsg.isEmpty() )
|
|
{
|
|
setLastError( errMsg );
|
|
QgsDebugMsg( mLastError );
|
|
mValid = false;
|
|
return;
|
|
}
|
|
|
|
// Create a query for default connection
|
|
mQuery = QSqlQuery( mDatabase );
|
|
|
|
mSchemaName = anUri.schema();
|
|
|
|
mTableName = anUri.table().toUpper();
|
|
QStringList sl = mTableName.split( '.' );
|
|
if ( sl.length() == 2 ) // Never seems to be the case
|
|
{
|
|
mSchemaName = sl[0];
|
|
mTableName = sl[1];
|
|
}
|
|
if ( mSchemaName.isEmpty() )
|
|
{
|
|
mSchemaName = anUri.username().toUpper();
|
|
}
|
|
|
|
QgsDebugMsg( "mSchemaName: '" + mSchemaName + "; mTableName: '" + mTableName );
|
|
|
|
if ( !anUri.geometryColumn().isEmpty() )
|
|
mGeometryColName = anUri.geometryColumn().toUpper();
|
|
|
|
loadFields();
|
|
updateStatistics();
|
|
|
|
if ( mGeometryColName.isEmpty() )
|
|
{
|
|
// table contains no geometries
|
|
mWkbType = QgsWkbTypes::NoGeometry;
|
|
mSRId = 0;
|
|
}
|
|
|
|
//fill type names into sets
|
|
setNativeTypes( QList< NativeType >()
|
|
// integer types
|
|
<< QgsVectorDataProvider::NativeType( tr( "8 Bytes integer" ), QStringLiteral( "bigint" ), QVariant::Int )
|
|
<< QgsVectorDataProvider::NativeType( tr( "4 Bytes integer" ), QStringLiteral( "integer" ), QVariant::Int )
|
|
<< QgsVectorDataProvider::NativeType( tr( "2 Bytes integer" ), QStringLiteral( "smallint" ), QVariant::Int )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), QStringLiteral( "numeric" ), QVariant::Double, 1, 31, 0, 31 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), QStringLiteral( "decimal" ), QVariant::Double, 1, 31, 0, 31 )
|
|
|
|
// floating point
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "real" ), QVariant::Double )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), QStringLiteral( "double" ), QVariant::Double )
|
|
|
|
// date/time types
|
|
<< QgsVectorDataProvider::NativeType( tr( "Date" ), QStringLiteral( "date" ), QVariant::Date, -1, -1, -1, -1 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Time" ), QStringLiteral( "time" ), QVariant::Time, -1, -1, -1, -1 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Date & Time" ), QStringLiteral( "datetime" ), QVariant::DateTime, -1, -1, -1, -1 )
|
|
|
|
// string types
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), QStringLiteral( "char" ), QVariant::String, 1, 254 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, variable length (varchar)" ), QStringLiteral( "varchar" ), QVariant::String, 1, 32704 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, variable length large object (clob)" ), QStringLiteral( "clob" ), QVariant::String, 1, 2147483647 )
|
|
//DBCLOB is for 1073741824 double-byte characters, data length should be the same as CLOB (2147483647)?
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, variable length large object (dbclob)" ), QStringLiteral( "dbclob" ), QVariant::String, 1, 1073741824 )
|
|
);
|
|
}
|
|
|
|
QgsDb2Provider::~QgsDb2Provider()
|
|
{
|
|
if ( mDatabase.isOpen() )
|
|
mDatabase.close();
|
|
}
|
|
|
|
QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errMsg )
|
|
{
|
|
QSqlDatabase db;
|
|
QString service;
|
|
QString driver;
|
|
QString host;
|
|
QString databaseName;
|
|
QString port;
|
|
QString userName;
|
|
QString password;
|
|
QString connectionName;
|
|
QString connectionString;
|
|
|
|
QgsDataSourceUri uri( connInfo );
|
|
// Fill in the password if authentication is used
|
|
QString expandedConnectionInfo = uri.connectionInfo( true );
|
|
QgsDebugMsg( "expanded connInfo: " + expandedConnectionInfo );
|
|
QgsDataSourceUri uriExpanded( expandedConnectionInfo );
|
|
|
|
userName = uriExpanded.username();
|
|
password = uriExpanded.password();
|
|
service = uriExpanded.service();
|
|
databaseName = uriExpanded.database();
|
|
host = uriExpanded.host();
|
|
port = uriExpanded.port();
|
|
driver = uriExpanded.driver();
|
|
QgsDebugMsg( QStringLiteral( "driver: '%1'; host: '%2'; databaseName: '%3'" ).arg( driver, host, databaseName ) );
|
|
if ( service.isEmpty() )
|
|
{
|
|
if ( driver.isEmpty() || host.isEmpty() || databaseName.isEmpty() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "service not provided, a required argument is empty." ) );
|
|
return db;
|
|
}
|
|
connectionName = databaseName + ".";
|
|
}
|
|
else
|
|
{
|
|
connectionName = service;
|
|
}
|
|
QgsDebugMsg( "connectionName: " + connectionName );
|
|
|
|
// Starting with Qt 5.11, sharing the same connection between threads is not allowed.
|
|
// We use a dedicated connection for each thread requiring access to the database,
|
|
// using the thread address as connection name.
|
|
const QString threadSafeConnectionName = dbConnectionName( connectionName );
|
|
QgsDebugMsg( "threadSafeConnectionName: " + threadSafeConnectionName );
|
|
|
|
// while everything we use from QSqlDatabase here is thread safe, we need to ensure
|
|
// that the connection cleanup on thread finalization happens in a predictable order
|
|
QMutexLocker locker( &sMutex );
|
|
|
|
/* if new database connection */
|
|
if ( !QSqlDatabase::contains( threadSafeConnectionName ) )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "new connection. create new QODBC mapping" ) );
|
|
db = QSqlDatabase::addDatabase( QStringLiteral( "QODBC3" ), threadSafeConnectionName );
|
|
db.setConnectOptions( QStringLiteral( "SQL_ATTR_CONNECTION_POOLING=SQL_CP_ONE_PER_HENV" ) );
|
|
|
|
// for background threads, remove database when current thread finishes
|
|
if ( QThread::currentThread() != QCoreApplication::instance()->thread() )
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "Scheduled auth db remove on thread close" ), 2 );
|
|
|
|
// IMPORTANT - we use a direct connection here, because the database removal must happen immediately
|
|
// when the thread finishes, and we cannot let this get queued on the main thread's event loop.
|
|
// Otherwise, the QSqlDatabase's private data's thread gets reset immediately the QThread::finished,
|
|
// and a subsequent call to QSqlDatabase::database with the same thread address (yep it happens, actually a lot)
|
|
// triggers a condition in QSqlDatabase which detects the nullptr private thread data and returns an invalid database instead.
|
|
// QSqlDatabase::removeDatabase is thread safe, so this is ok to do.
|
|
QObject::connect( QThread::currentThread(), &QThread::finished, QThread::currentThread(), [connectionName]
|
|
{
|
|
QMutexLocker locker( &sMutex );
|
|
QSqlDatabase::removeDatabase( connectionName );
|
|
}, Qt::DirectConnection );
|
|
}
|
|
}
|
|
else /* if existing database connection */
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "found existing connection, use the existing one" ) );
|
|
db = QSqlDatabase::database( threadSafeConnectionName );
|
|
}
|
|
locker.unlock();
|
|
|
|
db.setHostName( host );
|
|
db.setPort( port.toInt() );
|
|
bool connected = false;
|
|
int i = 0;
|
|
QgsCredentials::instance()->lock();
|
|
while ( !connected && i < 3 )
|
|
{
|
|
i++;
|
|
// Don't prompt if this is the first time and we have both userName and password
|
|
// This is needed for Python or any non-GUI process
|
|
if ( userName.isEmpty() || password.isEmpty() || ( !connected && i > 1 ) )
|
|
{
|
|
bool ok = QgsCredentials::instance()->get( databaseName, userName,
|
|
password, errMsg );
|
|
if ( !ok )
|
|
{
|
|
errMsg = QStringLiteral( "Cancel clicked" );
|
|
QgsDebugMsg( errMsg );
|
|
QgsCredentials::instance()->unlock();
|
|
break;
|
|
}
|
|
}
|
|
|
|
db.setUserName( userName );
|
|
db.setPassword( password );
|
|
|
|
/* start building connection string */
|
|
if ( service.isEmpty() )
|
|
{
|
|
connectionString = QString( "Driver={%1};Hostname=%2;Port=%3;"
|
|
"Protocol=TCPIP;Database=%4;Uid=%5;Pwd=%6;" )
|
|
.arg( driver,
|
|
host )
|
|
.arg( db.port() )
|
|
.arg( databaseName,
|
|
userName,
|
|
password );
|
|
}
|
|
else
|
|
{
|
|
connectionString = service;
|
|
}
|
|
QgsDebugMsg( "ODBC connection string: " + connectionString );
|
|
|
|
db.setDatabaseName( connectionString ); //for QODBC driver, the name can be a DSN or connection string
|
|
if ( db.open() )
|
|
{
|
|
connected = true;
|
|
errMsg.clear();
|
|
}
|
|
else
|
|
{
|
|
errMsg = db.lastError().text();
|
|
QgsDebugMsg( "DB not open" + errMsg );
|
|
}
|
|
}
|
|
if ( connected )
|
|
{
|
|
QgsCredentials::instance()->put( databaseName, userName, password );
|
|
}
|
|
QgsCredentials::instance()->unlock();
|
|
|
|
return db;
|
|
}
|
|
|
|
bool QgsDb2Provider::openDatabase( QSqlDatabase db )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "openDatabase" ) );
|
|
if ( !db.isOpen() )
|
|
{
|
|
if ( !db.open() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Database could not be opened." ) );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// loadFields() gets the type from the field record
|
|
void QgsDb2Provider::loadFields()
|
|
{
|
|
mAttributeFields.clear();
|
|
//mDefaultValues.clear();
|
|
QString table = QStringLiteral( "%1.%2" ).arg( mSchemaName, mTableName );
|
|
|
|
// Use the Qt functionality to get the fields and their definitions.
|
|
QSqlRecord r = mDatabase.record( table );
|
|
int fieldCount = r.count();
|
|
|
|
for ( int i = 0; i < fieldCount; i++ )
|
|
{
|
|
QSqlField f = r.field( i );
|
|
int typeID = f.typeID(); // seems to be DB2 numeric type id (standard?)
|
|
QString sqlTypeName = db2TypeName( typeID );
|
|
QVariant::Type sqlType = f.type();
|
|
QgsDebugMsg( QStringLiteral( "name: %1; length: %2; sqlTypeID: %3; sqlTypeName: %4" )
|
|
.arg( f.name() ).arg( f.length() ).arg( QString::number( typeID ), sqlTypeName ) );
|
|
if ( f.name() == mGeometryColName ) continue; // Got this with uri, just skip
|
|
if ( sqlType == QVariant::String )
|
|
{
|
|
mAttributeFields.append(
|
|
QgsField(
|
|
f.name(),
|
|
sqlType,
|
|
sqlTypeName,
|
|
f.length()
|
|
) );
|
|
}
|
|
else if ( sqlType == QVariant::Double )
|
|
{
|
|
mAttributeFields.append(
|
|
QgsField(
|
|
f.name(),
|
|
sqlType,
|
|
sqlTypeName,
|
|
f.length(),
|
|
f.precision()
|
|
) );
|
|
}
|
|
else
|
|
{
|
|
mAttributeFields.append(
|
|
QgsField(
|
|
f.name(),
|
|
sqlType,
|
|
sqlTypeName
|
|
) );
|
|
}
|
|
|
|
if ( !f.defaultValue().isNull() )
|
|
{
|
|
mDefaultValues.insert( i, f.defaultValue() );
|
|
}
|
|
// Hack to get primary key since the primaryIndex function above doesn't work
|
|
// on z/OS. Pick first integer column.
|
|
if ( mFidColName.isEmpty() &&
|
|
( sqlType == QVariant::LongLong || sqlType == QVariant::Int ) )
|
|
{
|
|
mFidColName = f.name();
|
|
}
|
|
}
|
|
|
|
if ( !mFidColName.isEmpty() )
|
|
{
|
|
mFidColIdx = mAttributeFields.indexFromName( mFidColName );
|
|
if ( mFidColIdx >= 0 )
|
|
{
|
|
// primary key has not null, unique constraints
|
|
QgsFieldConstraints constraints = mAttributeFields.at( mFidColIdx ).constraints();
|
|
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider );
|
|
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider );
|
|
mAttributeFields[ mFidColIdx ].setConstraints( constraints );
|
|
}
|
|
}
|
|
}
|
|
|
|
QVariant::Type QgsDb2Provider::decodeSqlType( int typeId )
|
|
{
|
|
QVariant::Type type = QVariant::Invalid;
|
|
switch ( typeId )
|
|
{
|
|
case -5: //BIGINT
|
|
type = QVariant::LongLong;
|
|
break;
|
|
|
|
case -3: //VARBINARY
|
|
type = QVariant::ByteArray;
|
|
break;
|
|
|
|
case 1: //CHAR
|
|
case 12: //VARCHAR
|
|
type = QVariant::String;
|
|
break;
|
|
|
|
case 4: //INTEGER
|
|
type = QVariant::Int;
|
|
break;
|
|
|
|
case 3: //NUMERIC and DECIMAL
|
|
case 7: //REAL
|
|
case 8: //DOUBLE
|
|
type = QVariant::Double;
|
|
break;
|
|
|
|
case 9: //DATE
|
|
type = QVariant::String; // don't know why it doesn't like QVariant::Date
|
|
break;
|
|
|
|
case 10: //TIME
|
|
type = QVariant::Time;
|
|
break;
|
|
|
|
case 11: //TIMESTAMP
|
|
type = QVariant::String; // don't know why it doesn't like QVariant::DateTime
|
|
break;
|
|
|
|
default:
|
|
// Everything else just dumped as a string.
|
|
type = QVariant::String;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
// Return the DB2 type name for the type numeric value
|
|
QString QgsDb2Provider::db2TypeName( int typeId )
|
|
{
|
|
QString typeName;
|
|
switch ( typeId )
|
|
{
|
|
case -3: //VARBINARY
|
|
typeName = QStringLiteral( "VARBINARY" ); // also for spatial types
|
|
break;
|
|
|
|
case 1: //CHAR
|
|
typeName = QStringLiteral( "CHAR" );
|
|
break;
|
|
|
|
case 12: //VARCHAR
|
|
typeName = QStringLiteral( "VARCHAR" );
|
|
break;
|
|
|
|
case 4: //INTEGER
|
|
typeName = QStringLiteral( "INTEGER" );
|
|
break;
|
|
|
|
case -5: //BIGINT
|
|
typeName = QStringLiteral( "BIGINT" );
|
|
break;
|
|
|
|
case 3: //NUMERIC and DECIMAL
|
|
typeName = QStringLiteral( "DECIMAL" );
|
|
break;
|
|
|
|
case 7: //REAL
|
|
typeName = QStringLiteral( "REAL" );
|
|
break;
|
|
|
|
case 8: //DOUBLE
|
|
typeName = QStringLiteral( "DOUBLE" );
|
|
break;
|
|
|
|
case 9: //DATE
|
|
typeName = QStringLiteral( "DATE" );
|
|
break;
|
|
|
|
case 10: //TIME
|
|
typeName = QStringLiteral( "TIME" );
|
|
break;
|
|
|
|
case 11: //TIMESTAMP
|
|
typeName = QStringLiteral( "TIMESTAMP" );
|
|
break;
|
|
|
|
default:
|
|
typeName = QStringLiteral( "UNKNOWN" );
|
|
}
|
|
|
|
return typeName;
|
|
}
|
|
|
|
QgsAbstractFeatureSource *QgsDb2Provider::featureSource() const
|
|
{
|
|
return new QgsDb2FeatureSource( this );
|
|
}
|
|
|
|
QgsFeatureIterator QgsDb2Provider::getFeatures( const QgsFeatureRequest &request ) const
|
|
{
|
|
if ( !mValid )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Read attempt on an invalid db2 data source" ) );
|
|
return QgsFeatureIterator();
|
|
}
|
|
|
|
return QgsFeatureIterator( new QgsDb2FeatureIterator( new QgsDb2FeatureSource( this ), true, request ) );
|
|
}
|
|
|
|
QgsWkbTypes::Type QgsDb2Provider::wkbType() const
|
|
{
|
|
return mWkbType;
|
|
}
|
|
|
|
long long QgsDb2Provider::featureCount() const
|
|
{
|
|
// Return the count that we get from the subset.
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
return mNumberFeatures;
|
|
|
|
// On LUW, this could be selected from syscat.tables but I'm not sure if this
|
|
// is actually correct if RUNSTATS hasn't been done.
|
|
// On z/OS, we don't have access to the system catalog.
|
|
// Use count(*) as the easiest approach
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
|
|
QString sql = QStringLiteral( "SELECT COUNT(*) FROM %1.%2" );
|
|
QString statement = QString( sql ).arg( mSchemaName, mTableName );
|
|
QgsDebugMsg( statement );
|
|
if ( query.exec( statement ) && query.next() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "count: %1" ).arg( query.value( 0 ).toInt() ) );
|
|
return query.value( 0 ).toInt();
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Failed" ) );
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
QgsFields QgsDb2Provider::fields() const
|
|
{
|
|
return mAttributeFields;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsDb2Provider::crs() const
|
|
{
|
|
if ( !mCrs.isValid() && mSRId > 0 )
|
|
{
|
|
// try to load crs from the database tables as a fallback
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
bool execOk = query.exec( QStringLiteral( "SELECT DEFINITION FROM DB2GSE.ST_SPATIAL_REFERENCE_SYSTEMS WHERE SRS_ID = %1" ).arg( QString::number( mSRId ) ) );
|
|
if ( execOk && query.isActive() )
|
|
{
|
|
if ( query.next() )
|
|
{
|
|
mCrs = QgsCoordinateReferenceSystem::fromWkt( query.value( 0 ).toString() );
|
|
if ( mCrs.isValid() )
|
|
return mCrs;
|
|
}
|
|
}
|
|
}
|
|
return mCrs;
|
|
}
|
|
|
|
// update the extent for this layer
|
|
void QgsDb2Provider::updateStatistics() const
|
|
{
|
|
// get features to calculate the statistics
|
|
QString statement;
|
|
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
|
|
statement = QStringLiteral( "SELECT MIN(DB2GSE.ST_MINX(%1)), MIN(DB2GSE.ST_MINY(%1)), MAX(DB2GSE.ST_MAXX(%1)), MAX(DB2GSE.ST_MAXY(%1))" ).arg( mGeometryColName );
|
|
|
|
statement += QStringLiteral( " FROM %1.%2" ).arg( mSchemaName, mTableName );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
statement += " WHERE (" + mSqlWhereClause + ")";
|
|
}
|
|
QgsDebugMsg( statement );
|
|
|
|
if ( !query.exec( statement ) )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
}
|
|
|
|
if ( !query.isActive() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( query.next() )
|
|
{
|
|
mExtent.setXMinimum( query.value( 0 ).toDouble() );
|
|
mExtent.setYMinimum( query.value( 1 ).toDouble() );
|
|
mExtent.setXMaximum( query.value( 2 ).toDouble() );
|
|
mExtent.setYMaximum( query.value( 3 ).toDouble() );
|
|
QgsDebugMsg( QStringLiteral( "after setting; mExtent: %1" ).arg( mExtent.toString() ) );
|
|
}
|
|
|
|
QgsDebugMsg( QStringLiteral( "mSRId: %1" ).arg( mSRId ) );
|
|
QgsDb2GeometryColumns gc( mDatabase );
|
|
QString rc = gc.open( mSchemaName, mTableName ); // returns SQLCODE if failure
|
|
if ( rc.isEmpty() || rc == QLatin1String( "0" ) )
|
|
{
|
|
mEnvironment = gc.db2Environment();
|
|
if ( -1 == mSRId )
|
|
{
|
|
QgsDb2LayerProperty layer;
|
|
gc.populateLayerProperty( layer );
|
|
if ( !layer.srid.isEmpty() )
|
|
{
|
|
mSRId = layer.srid.toInt();
|
|
mSrsName = layer.srsName;
|
|
}
|
|
mGeometryColType = layer.type;
|
|
QgsDebugMsg( QStringLiteral( "srs_id: %1; srs_name: %2; mGeometryColType: %3" )
|
|
.arg( mSRId ).arg( mSrsName, mGeometryColType ) );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Couldn't get srid from geometry columns" ) );
|
|
}
|
|
|
|
// Try to get the srid from the data if srid isn't already set
|
|
QgsDebugMsg( QStringLiteral( "mSRId: %1" ).arg( mSRId ) );
|
|
if ( -1 == mSRId )
|
|
{
|
|
query.clear();
|
|
statement = QStringLiteral( "SELECT DB2GSE.ST_SRID(%1) FROM %2.%3 FETCH FIRST ROW ONLY" )
|
|
.arg( mGeometryColName, mSchemaName, mTableName );
|
|
|
|
QgsDebugMsg( statement );
|
|
|
|
if ( !query.exec( statement ) || !query.isActive() )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
}
|
|
|
|
if ( query.next() )
|
|
{
|
|
mSRId = query.value( 0 ).toInt();
|
|
QgsDebugMsg( QStringLiteral( "srid from data: %1" ).arg( mSRId ) );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Couldn't get srid from data" ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsRectangle QgsDb2Provider::extent() const
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "entering; mExtent: %1" ).arg( mExtent.toString() ) );
|
|
if ( mExtent.isEmpty() )
|
|
updateStatistics();
|
|
return mExtent;
|
|
}
|
|
|
|
bool QgsDb2Provider::isValid() const
|
|
{
|
|
return true; //DB2 only has valid geometries
|
|
}
|
|
|
|
QString QgsDb2Provider::subsetString() const
|
|
{
|
|
return mSqlWhereClause;
|
|
}
|
|
|
|
bool QgsDb2Provider::setSubsetString( const QString &theSQL, bool )
|
|
{
|
|
QString prevWhere = mSqlWhereClause;
|
|
QgsDebugMsg( theSQL );
|
|
mSqlWhereClause = theSQL.trimmed();
|
|
|
|
QString sql = QStringLiteral( "SELECT COUNT(*) FROM " );
|
|
|
|
sql += QStringLiteral( "%1.%2" ).arg( mSchemaName, mTableName );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
sql += QStringLiteral( " WHERE %1" ).arg( mSqlWhereClause );
|
|
}
|
|
|
|
if ( !openDatabase( mDatabase ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
QgsDebugMsg( sql );
|
|
if ( !query.exec( sql ) )
|
|
{
|
|
pushError( query.lastError().text() );
|
|
mSqlWhereClause = prevWhere;
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
|
|
if ( query.isActive() && query.next() )
|
|
{
|
|
mNumberFeatures = query.value( 0 ).toInt();
|
|
QgsDebugMsg( QStringLiteral( "count: %1" ).arg( mNumberFeatures ) );
|
|
}
|
|
else
|
|
{
|
|
pushError( query.lastError().text() );
|
|
mSqlWhereClause = prevWhere;
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
|
|
QgsDataSourceUri anUri = QgsDataSourceUri( dataSourceUri() );
|
|
anUri.setSql( mSqlWhereClause );
|
|
|
|
setDataSourceUri( anUri.uri() );
|
|
|
|
mExtent.setMinimal();
|
|
|
|
emit dataChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
void QgsDb2Provider::db2WkbTypeAndDimension( QgsWkbTypes::Type wkbType, QString &geometryType, int &dim )
|
|
{
|
|
if ( QgsWkbTypes::hasZ( wkbType ) )
|
|
dim = 3;
|
|
|
|
QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( wkbType );
|
|
|
|
if ( flatType == QgsWkbTypes::Point )
|
|
geometryType = QStringLiteral( "POINT" );
|
|
else if ( flatType == QgsWkbTypes::LineString )
|
|
geometryType = QStringLiteral( "LINESTRING" );
|
|
else if ( flatType == QgsWkbTypes::Polygon )
|
|
geometryType = QStringLiteral( "POLYGON" );
|
|
else if ( flatType == QgsWkbTypes::MultiPoint )
|
|
geometryType = QStringLiteral( "MULTIPOINT" );
|
|
else if ( flatType == QgsWkbTypes::MultiLineString )
|
|
geometryType = QStringLiteral( "MULTILINESTRING" );
|
|
else if ( flatType == QgsWkbTypes::MultiPolygon )
|
|
geometryType = QStringLiteral( "MULTIPOLYGON" );
|
|
else if ( flatType == QgsWkbTypes::Unknown )
|
|
geometryType = QStringLiteral( "GEOMETRY" );
|
|
else
|
|
dim = 0;
|
|
}
|
|
|
|
bool QgsDb2Provider::deleteFeatures( const QgsFeatureIds &id )
|
|
{
|
|
if ( mFidColName.isEmpty() )
|
|
return false;
|
|
|
|
QString featureIds;
|
|
for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it )
|
|
{
|
|
if ( featureIds.isEmpty() )
|
|
featureIds = FID_TO_STRING( *it );
|
|
else
|
|
featureIds += ',' + FID_TO_STRING( *it );
|
|
}
|
|
|
|
if ( !mDatabase.isOpen() )
|
|
{
|
|
QString errMsg;
|
|
mDatabase = getDatabase( mConnInfo, errMsg );
|
|
if ( !errMsg.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
QString statement;
|
|
statement = QStringLiteral( "DELETE FROM %1.%2 WHERE %3 IN (%4)" ).arg( mSchemaName,
|
|
mTableName, mFidColName, featureIds );
|
|
QgsDebugMsg( statement );
|
|
if ( !query.exec( statement ) )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool QgsDb2Provider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Entering" ) );
|
|
if ( attr_map.isEmpty() )
|
|
return true;
|
|
|
|
if ( mFidColName.isEmpty() )
|
|
return false;
|
|
|
|
for ( QgsChangedAttributesMap::const_iterator it = attr_map.begin(); it != attr_map.end(); ++it )
|
|
{
|
|
QgsFeatureId fid = it.key();
|
|
|
|
// skip added features
|
|
if ( FID_IS_NEW( fid ) )
|
|
continue;
|
|
|
|
const QgsAttributeMap &attrs = it.value();
|
|
if ( attrs.isEmpty() )
|
|
continue;
|
|
|
|
QString statement = QStringLiteral( "UPDATE %1.%2 SET " ).arg( mSchemaName, mTableName );
|
|
|
|
bool first = true;
|
|
if ( !mDatabase.isOpen() )
|
|
{
|
|
QString errMsg;
|
|
mDatabase = getDatabase( mConnInfo, errMsg );
|
|
if ( !errMsg.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
|
|
for ( QgsAttributeMap::const_iterator it2 = attrs.begin(); it2 != attrs.end(); ++it2 )
|
|
{
|
|
QgsField fld = mAttributeFields.at( it2.key() );
|
|
|
|
if ( fld.typeName().endsWith( QLatin1String( " identity" ), Qt::CaseInsensitive ) )
|
|
continue; // skip identity field
|
|
|
|
if ( fld.name().isEmpty() )
|
|
continue; // invalid
|
|
|
|
if ( !first )
|
|
statement += ',';
|
|
else
|
|
first = false;
|
|
|
|
statement += QStringLiteral( "%1=?" ).arg( fld.name() );
|
|
}
|
|
|
|
if ( first )
|
|
return true; // no fields have been changed
|
|
|
|
// set attribute filter
|
|
statement += QStringLiteral( " WHERE %1=%2" ).arg( mFidColName, FID_TO_STRING( fid ) );
|
|
|
|
// use prepared statement to prevent from sql injection
|
|
if ( !query.prepare( statement ) )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
QgsDebugMsg( statement );
|
|
for ( QgsAttributeMap::const_iterator it2 = attrs.begin(); it2 != attrs.end(); ++it2 )
|
|
{
|
|
QgsField fld = mAttributeFields.at( it2.key() );
|
|
|
|
if ( fld.name().isEmpty() )
|
|
continue; // invalid
|
|
|
|
QVariant::Type type = fld.type();
|
|
if ( it2->isNull() || !it2->isValid() )
|
|
{
|
|
// binding null values
|
|
if ( type == QVariant::Date || type == QVariant::DateTime )
|
|
query.addBindValue( QVariant( QVariant::String ) );
|
|
else
|
|
query.addBindValue( QVariant( type ) );
|
|
}
|
|
else if ( type == QVariant::Int )
|
|
{
|
|
// binding an INTEGER value
|
|
query.addBindValue( it2->toInt() );
|
|
}
|
|
else if ( type == QVariant::Double )
|
|
{
|
|
// binding a DOUBLE value
|
|
query.addBindValue( it2->toDouble() );
|
|
}
|
|
else if ( type == QVariant::String )
|
|
{
|
|
// binding a TEXT value
|
|
query.addBindValue( it2->toString() );
|
|
}
|
|
else if ( type == QVariant::DateTime )
|
|
{
|
|
// binding a DATETIME value
|
|
query.addBindValue( it2->toDateTime().toString( Qt::ISODate ) );
|
|
}
|
|
else if ( type == QVariant::Date )
|
|
{
|
|
// binding a DATE value
|
|
query.addBindValue( it2->toDate().toString( Qt::ISODate ) );
|
|
}
|
|
else if ( type == QVariant::Time )
|
|
{
|
|
// binding a TIME value
|
|
query.addBindValue( it2->toTime().toString( Qt::ISODate ) );
|
|
}
|
|
else
|
|
{
|
|
query.addBindValue( *it2 );
|
|
}
|
|
}
|
|
|
|
if ( !query.exec() )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QgsDb2Provider::addFeatures( QgsFeatureList &flist, Flags flags )
|
|
{
|
|
QgsDebugMsg( "mGeometryColType: " + mGeometryColType );
|
|
int writeCount = 0;
|
|
bool copyOperation = false;
|
|
|
|
if ( !mDatabase.isOpen() )
|
|
{
|
|
QString errMsg;
|
|
mDatabase = getDatabase( mConnInfo, errMsg );
|
|
if ( !errMsg.isEmpty() )
|
|
{
|
|
QgsDebugMsg( "getDatabase failed: " + errMsg );
|
|
return false;
|
|
}
|
|
}
|
|
if ( !mDatabase.transaction() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "transaction failed" ) );
|
|
return false;
|
|
}
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
QSqlQuery queryFid = QSqlQuery( mDatabase );
|
|
queryFid.setForwardOnly( true );
|
|
|
|
QgsFeature it = flist.at( 0 );
|
|
QString statement;
|
|
QString values;
|
|
statement = QStringLiteral( "INSERT INTO %1.%2 (" ).arg( mSchemaName, mTableName );
|
|
|
|
bool first = true;
|
|
|
|
// Get the first geometry and its wkbType as when we are doing drag/drop,
|
|
// the wkbType is not passed to the DB2 provider from QgsVectorLayerExporter
|
|
// Can't figure out how to resolved "unreferenced" wkbType compile message
|
|
// Don't really do anything with it at this point
|
|
#if 0
|
|
QgsGeometry *geom = it.geometry();
|
|
QgsWkbTypes::Type wkbType = geom->wkbType();
|
|
QgsDebugMsg( QStringLiteral( "wkbType: %1" ).arg( wkbType ) );
|
|
QgsDebugMsg( QStringLiteral( "mWkbType: %1" ).arg( mWkbType ) );
|
|
#endif
|
|
|
|
QgsAttributes attrs = it.attributes();
|
|
QgsDebugMsg( QStringLiteral( "attrs.count: %1" ).arg( attrs.count() ) );
|
|
QgsDebugMsg( QStringLiteral( "fields.count: %1" ).arg( mAttributeFields.count() ) );
|
|
if ( mAttributeFields.count() == ( attrs.count() + 1 ) )
|
|
{
|
|
copyOperation = true; // FID is first field but no attribute in attrs
|
|
}
|
|
else if ( mAttributeFields.count() != attrs.count() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Count mismatch - failing" ) );
|
|
return false;
|
|
}
|
|
|
|
|
|
if ( attrs.count() != mAttributeFields.count() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "field counts don't match" ) );
|
|
// return false;
|
|
}
|
|
|
|
for ( int i = 0; i < mAttributeFields.count(); ++i )
|
|
{
|
|
QgsField fld = mAttributeFields.at( i );
|
|
QgsDebugMsg( QStringLiteral( "i: %1; got field: %2" ).arg( i ).arg( fld.name() ) );
|
|
|
|
if ( fld.name().isEmpty() )
|
|
continue; // invalid
|
|
|
|
if ( mFidColName == fld.name() )
|
|
continue; // skip identity field
|
|
|
|
// if ( mDefaultValues.contains( i ) && mDefaultValues[i] == attrs.at( i ) )
|
|
// continue; // skip fields having default values
|
|
|
|
if ( !first )
|
|
{
|
|
statement += ',';
|
|
values += ',';
|
|
}
|
|
else
|
|
first = false;
|
|
|
|
statement += fld.name();
|
|
values += '?';
|
|
}
|
|
|
|
// append geometry column name
|
|
if ( !mGeometryColName.isEmpty() )
|
|
{
|
|
if ( !first )
|
|
{
|
|
statement += ',';
|
|
values += ',';
|
|
}
|
|
|
|
statement += mGeometryColName;
|
|
|
|
values += QStringLiteral( "db2gse.%1(CAST (%2 AS BLOB(2M)),%3)" )
|
|
.arg( mGeometryColType,
|
|
QStringLiteral( "?" ),
|
|
QString::number( mSRId ) );
|
|
}
|
|
|
|
QgsDebugMsg( statement );
|
|
QgsDebugMsg( values );
|
|
statement += ") VALUES (" + values + ')';
|
|
QgsDebugMsg( statement );
|
|
|
|
QgsDebugMsg( QStringLiteral( "Prepare statement" ) );
|
|
// use prepared statement to prevent from sql injection
|
|
if ( !query.prepare( statement ) )
|
|
{
|
|
QString msg = query.lastError().text();
|
|
QgsDebugMsg( msg );
|
|
pushError( msg );
|
|
return false;
|
|
}
|
|
|
|
|
|
for ( QgsFeatureList::iterator it = flist.begin(); it != flist.end(); ++it )
|
|
{
|
|
attrs = it->attributes();
|
|
|
|
int fieldIdx = 0;
|
|
if ( copyOperation )
|
|
{
|
|
fieldIdx = 1; // skip first (FID) field if copying from shapefile
|
|
}
|
|
int bindIdx = 0;
|
|
for ( int i = 0; i < attrs.count(); i++ )
|
|
{
|
|
QgsField fld = mAttributeFields.at( fieldIdx++ );
|
|
if ( fld.name().isEmpty() )
|
|
continue; // invalid
|
|
|
|
if ( mFidColName == fld.name() )
|
|
continue; // skip identity field
|
|
|
|
// if ( mDefaultValues.contains( i ) && mDefaultValues[i] == attrs.at( i ) )
|
|
// continue; // skip fields having default values
|
|
|
|
QVariant::Type type = fld.type();
|
|
if ( attrs.at( i ).isNull() || !attrs.at( i ).isValid() )
|
|
{
|
|
// binding null values
|
|
if ( type == QVariant::Date || type == QVariant::DateTime )
|
|
query.bindValue( bindIdx, QVariant( QVariant::String ) );
|
|
else
|
|
query.bindValue( bindIdx, QVariant( type ) );
|
|
}
|
|
else if ( type == QVariant::Int )
|
|
{
|
|
// binding an INTEGER value
|
|
query.bindValue( bindIdx, attrs.at( i ).toInt() );
|
|
}
|
|
else if ( type == QVariant::Double )
|
|
{
|
|
// binding a DOUBLE value
|
|
query.bindValue( bindIdx, attrs.at( i ).toDouble() );
|
|
}
|
|
else if ( type == QVariant::String )
|
|
{
|
|
// binding a TEXT value
|
|
query.bindValue( bindIdx, attrs.at( i ).toString() );
|
|
}
|
|
else if ( type == QVariant::Time )
|
|
{
|
|
// binding a TIME value
|
|
query.bindValue( bindIdx, attrs.at( i ).toTime().toString( Qt::ISODate ) );
|
|
}
|
|
else if ( type == QVariant::Date )
|
|
{
|
|
// binding a DATE value
|
|
query.bindValue( bindIdx, attrs.at( i ).toDate().toString( Qt::ISODate ) );
|
|
}
|
|
else if ( type == QVariant::DateTime )
|
|
{
|
|
// binding a DATETIME value
|
|
query.bindValue( bindIdx, attrs.at( i ).toDateTime().toString( Qt::ISODate ) );
|
|
}
|
|
else
|
|
{
|
|
query.bindValue( bindIdx, attrs.at( i ) );
|
|
}
|
|
|
|
#if 0
|
|
QgsDebugMsg( QStringLiteral( "bound i: %1; name: %2; value: %3; bindIdx: %4" ).
|
|
arg( i ).arg( fld.name() ).arg( attrs.at( i ).toString() ).arg( bindIdx ) );
|
|
#endif
|
|
bindIdx++;
|
|
}
|
|
|
|
if ( !mGeometryColName.isEmpty() )
|
|
{
|
|
QgsGeometry geom = it->geometry();
|
|
|
|
QByteArray bytea = geom.asWkb();
|
|
query.bindValue( bindIdx, bytea, QSql::In | QSql::Binary );
|
|
}
|
|
|
|
#if 0
|
|
// Show bound values
|
|
QList<QVariant> list = query.boundValues().values();
|
|
|
|
for ( int i = 0; i < list.size(); ++i )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "i: %1; value: %2; type: %3" )
|
|
.arg( i ).arg( list.at( i ).toString().toLatin1().data() ).arg( list.at( i ).typeName() ) );
|
|
}
|
|
#endif
|
|
if ( !query.exec() )
|
|
{
|
|
QString msg = query.lastError().text();
|
|
QgsDebugMsg( msg );
|
|
if ( !mSkipFailures )
|
|
{
|
|
pushError( msg );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( !( flags & QgsFeatureSink::FastInsert ) )
|
|
{
|
|
statement = QString( "select IDENTITY_VAL_LOCAL() AS IDENTITY "
|
|
"FROM SYSIBM.SYSDUMMY1" );
|
|
// QgsDebugMsg( statement );
|
|
if ( !queryFid.exec( statement ) )
|
|
{
|
|
QString msg = query.lastError().text();
|
|
QgsDebugMsg( msg );
|
|
if ( !mSkipFailures )
|
|
{
|
|
pushError( msg );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( !queryFid.next() )
|
|
{
|
|
QString msg = query.lastError().text();
|
|
QgsDebugMsg( msg );
|
|
if ( !mSkipFailures )
|
|
{
|
|
pushError( msg );
|
|
return false;
|
|
}
|
|
}
|
|
it->setId( queryFid.value( 0 ).toLongLong() );
|
|
}
|
|
writeCount++;
|
|
// QgsDebugMsg( QStringLiteral( "count: %1; featureId: %2" ).arg( writeCount ).arg( queryFid.value( 0 ).toLongLong() ) );
|
|
}
|
|
bool commitStatus = mDatabase.commit();
|
|
QgsDebugMsg( QStringLiteral( "commitStatus: %1; write count: %2; featureId: %3" )
|
|
.arg( commitStatus ).arg( writeCount ).arg( queryFid.value( 0 ).toLongLong() ) );
|
|
if ( !commitStatus )
|
|
{
|
|
pushError( QStringLiteral( "Commit of new features failed" ) );
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QgsVectorDataProvider::Capabilities QgsDb2Provider::capabilities() const
|
|
{
|
|
QgsVectorDataProvider::Capabilities cap = AddFeatures;
|
|
bool hasGeom = false;
|
|
if ( !mGeometryColName.isEmpty() )
|
|
{
|
|
hasGeom = true;
|
|
// cap |= CreateSpatialIndex;
|
|
}
|
|
|
|
if ( mFidColName.isEmpty() )
|
|
return cap;
|
|
else
|
|
{
|
|
if ( hasGeom )
|
|
cap |= ChangeGeometries;
|
|
|
|
return cap | DeleteFeatures | ChangeAttributeValues |
|
|
QgsVectorDataProvider::SelectAtId;
|
|
}
|
|
}
|
|
|
|
bool QgsDb2Provider::changeGeometryValues( const QgsGeometryMap &geometry_map )
|
|
{
|
|
if ( geometry_map.isEmpty() )
|
|
return true;
|
|
|
|
if ( mFidColName.isEmpty() )
|
|
return false;
|
|
|
|
for ( QgsGeometryMap::const_iterator it = geometry_map.constBegin(); it != geometry_map.constEnd(); ++it )
|
|
{
|
|
QgsFeatureId fid = it.key();
|
|
// skip added features
|
|
if ( FID_IS_NEW( fid ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QString statement;
|
|
statement = QStringLiteral( "UPDATE %1.%2 SET %3 = " )
|
|
.arg( mSchemaName, mTableName, mGeometryColName );
|
|
|
|
if ( !mDatabase.isOpen() )
|
|
{
|
|
QString errMsg;
|
|
mDatabase = getDatabase( mConnInfo, errMsg );
|
|
if ( !errMsg.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
QSqlQuery query = QSqlQuery( mDatabase );
|
|
query.setForwardOnly( true );
|
|
|
|
statement += QStringLiteral( "db2gse.%1(CAST (%2 AS BLOB(2M)),%3)" )
|
|
.arg( mGeometryColType,
|
|
QStringLiteral( "?" ),
|
|
QString::number( mSRId ) );
|
|
|
|
// set attribute filter
|
|
statement += QStringLiteral( " WHERE %1=%2" ).arg( mFidColName, FID_TO_STRING( fid ) );
|
|
QgsDebugMsg( statement );
|
|
if ( !query.prepare( statement ) )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
|
|
// add geometry param
|
|
QByteArray bytea = it->asWkb();
|
|
query.addBindValue( bytea, QSql::In | QSql::Binary );
|
|
|
|
if ( !query.exec() )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Qgis::VectorExportResult QgsDb2Provider::createEmptyLayer( const QString &uri,
|
|
const QgsFields &fields,
|
|
QgsWkbTypes::Type wkbType,
|
|
const QgsCoordinateReferenceSystem &srs,
|
|
bool overwrite,
|
|
QMap<int, int> *oldToNewAttrIdxMap,
|
|
QString *errorMessage )
|
|
{
|
|
// populate members from the uri structure
|
|
QgsDataSourceUri dsUri( uri );
|
|
|
|
QString connInfo = dsUri.connectionInfo();
|
|
QString errMsg;
|
|
QString srsName;
|
|
QgsDebugMsg( "uri: " + uri );
|
|
|
|
// connect to database
|
|
QSqlDatabase db = QgsDb2Provider::getDatabase( connInfo, errMsg );
|
|
|
|
if ( !errMsg.isEmpty() )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = errMsg;
|
|
return Qgis::VectorExportResult::ErrorConnectionFailed;
|
|
}
|
|
|
|
// Get the SRS name using srid, needed to register the spatial column
|
|
// srs->posgisSrid() seems to return the authority id which is
|
|
// most often the EPSG id. Hopefully DB2 has defined an SRS using this
|
|
// value as the srid / srs_id. If not, we are out of luck.
|
|
QgsDebugMsg( "srs: " + srs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) );
|
|
long srid = srs.postgisSrid();
|
|
QgsDebugMsg( QStringLiteral( "srid: %1" ).arg( srid ) );
|
|
if ( srid >= 0 )
|
|
{
|
|
QSqlQuery query( db );
|
|
QString statement = QStringLiteral( "SELECT srs_name FROM db2gse.st_spatial_reference_systems where srs_id=%1" )
|
|
.arg( srid );
|
|
QgsDebugMsg( statement );
|
|
|
|
if ( !query.exec( statement ) || !query.isActive() )
|
|
{
|
|
QgsDebugMsg( query.lastError().text() );
|
|
}
|
|
|
|
if ( query.next() )
|
|
{
|
|
srsName = query.value( 0 ).toString();
|
|
QgsDebugMsg( QStringLiteral( "srs_name: %1" ).arg( srsName ) );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Couldn't get srs_name from db2gse.st_spatial_reference_systems" ) );
|
|
}
|
|
}
|
|
|
|
QString schemaName = dsUri.schema().toUpper();
|
|
QString tableName = dsUri.table().toUpper();
|
|
QString fullName;
|
|
|
|
if ( schemaName.isEmpty() )
|
|
{
|
|
schemaName = dsUri.username().toUpper(); // set schema to user name
|
|
}
|
|
fullName = schemaName + "." + tableName;
|
|
|
|
QString geometryColumn = dsUri.geometryColumn().toUpper();
|
|
QString primaryKey = dsUri.keyColumn().toUpper();
|
|
QString primaryKeyType;
|
|
|
|
// TODO - this is a bad hack to cope with shapefiles.
|
|
// The wkbType from the shapefile header is usually a multi-type
|
|
// even if all the data is a single-type. If we create the column as
|
|
// a multi-type, the insert will fail if the actual data is a single-type
|
|
// due to type mismatch.
|
|
// We could potentially defer adding the spatial column until addFeatures is
|
|
// called the first time, but QgsVectorLayerExporter doesn't pass the CRS/srid
|
|
// information to the DB2 provider and we need this information to register
|
|
// the spatial column.
|
|
// This hack is problematic because the drag/drop will fail if the
|
|
// actual data is a multi-type which is possible with a shapefile or
|
|
// other data source.
|
|
QgsWkbTypes::Type wkbTypeSingle;
|
|
wkbTypeSingle = QgsWkbTypes::singleType( wkbType );
|
|
if ( wkbType != QgsWkbTypes::NoGeometry && geometryColumn.isEmpty() )
|
|
geometryColumn = QStringLiteral( "GEOM" );
|
|
|
|
if ( primaryKey.isEmpty() )
|
|
primaryKey = QStringLiteral( "QGS_FID" );
|
|
|
|
// get the pk's name and type
|
|
// if no pk name was passed, define the new pk field name
|
|
int fieldCount = fields.size();
|
|
if ( primaryKey.isEmpty() )
|
|
{
|
|
int index = 0;
|
|
QString pk = primaryKey = QStringLiteral( "QGS_FID" );
|
|
for ( int i = 0; i < fieldCount; ++i )
|
|
{
|
|
if ( fields.at( i ).name() == primaryKey )
|
|
{
|
|
// it already exists, try again with a new name
|
|
primaryKey = QStringLiteral( "%1_%2" ).arg( pk ).arg( index++ );
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// search for the passed field
|
|
for ( int i = 0; i < fieldCount; ++i )
|
|
{
|
|
if ( fields.at( i ).name() == primaryKey )
|
|
{
|
|
// found, get the field type
|
|
QgsField fld = fields.at( i );
|
|
if ( convertField( fld ) )
|
|
{
|
|
primaryKeyType = fld.typeName();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
QgsDebugMsg( "primaryKeyType: '" + primaryKeyType + "'" );
|
|
|
|
QString sql;
|
|
QSqlQuery q = QSqlQuery( db );
|
|
q.setForwardOnly( true );
|
|
|
|
// get wkb type and dimension
|
|
QString geometryType;
|
|
int dim = 2;
|
|
db2WkbTypeAndDimension( wkbTypeSingle, geometryType, dim );
|
|
QgsDebugMsg( QStringLiteral( "wkbTypeSingle: %1; geometryType: %2" ).arg( wkbTypeSingle ).arg( geometryType ) );
|
|
if ( overwrite )
|
|
{
|
|
// remove the old table with the same name
|
|
sql = "DROP TABLE " + fullName;
|
|
if ( !q.exec( sql ) )
|
|
{
|
|
if ( q.lastError().nativeErrorCode() != QLatin1String( "-206" ) ) // -206 is "not found" just ignore
|
|
{
|
|
QString lastError = q.lastError().text();
|
|
QgsDebugMsg( lastError );
|
|
if ( errorMessage )
|
|
{
|
|
*errorMessage = lastError;
|
|
}
|
|
return Qgis::VectorExportResult::ErrorCreatingLayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add fields to the layer
|
|
if ( oldToNewAttrIdxMap )
|
|
oldToNewAttrIdxMap->clear();
|
|
QString attr2Create;
|
|
if ( fields.size() > 0 )
|
|
{
|
|
int offset = 0;
|
|
|
|
// get the list of fields
|
|
QgsDebugMsg( "PrimaryKey: '" + primaryKey + "'" );
|
|
for ( int i = 0; i < fieldCount; ++i )
|
|
{
|
|
QgsField fld = fields.field( i );
|
|
QgsDebugMsg( QStringLiteral( "i: %1; fldIdx: %2; offset: %3" )
|
|
.arg( i ).arg( fields.lookupField( fld.name() ) ).arg( offset ) );
|
|
|
|
if ( oldToNewAttrIdxMap && fld.name() == primaryKey )
|
|
{
|
|
oldToNewAttrIdxMap->insert( i, 0 );
|
|
continue;
|
|
}
|
|
|
|
if ( fld.name() == geometryColumn )
|
|
{
|
|
// Found a field with the same name of the geometry column. Skip it!
|
|
continue;
|
|
}
|
|
QString db2Field = qgsFieldToDb2Field( fld );
|
|
|
|
if ( db2Field.isEmpty() )
|
|
{
|
|
if ( errorMessage )
|
|
{
|
|
*errorMessage = QObject::tr( "Unsupported type for field %1" ).arg( fld.name() );
|
|
}
|
|
return Qgis::VectorExportResult::ErrorAttributeTypeUnsupported;
|
|
}
|
|
|
|
if ( oldToNewAttrIdxMap )
|
|
{
|
|
oldToNewAttrIdxMap->insert( fields.lookupField( fld.name() ), offset++ );
|
|
}
|
|
attr2Create += ',' + db2Field.toUpper();
|
|
}
|
|
QgsDebugMsg( attr2Create );
|
|
if ( !geometryColumn.isEmpty() )
|
|
{
|
|
sql = QString( // need to set specific geometry type
|
|
"CREATE TABLE %1(%2 BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY, "
|
|
"%3 DB2GSE.%4 %5) " )
|
|
.arg( fullName,
|
|
primaryKey,
|
|
geometryColumn,
|
|
geometryType,
|
|
attr2Create );
|
|
}
|
|
else
|
|
{
|
|
//geometryless table
|
|
sql = QStringLiteral( // need to set specific geometry type
|
|
"CREATE TABLE %1.%2(%3 INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS %4) " )
|
|
.arg( schemaName,
|
|
tableName,
|
|
primaryKey,
|
|
attr2Create );
|
|
}
|
|
QgsDebugMsg( sql );
|
|
if ( !q.exec( sql ) )
|
|
{
|
|
QString lastError = q.lastError().text();
|
|
QgsDebugMsg( lastError );
|
|
if ( errorMessage )
|
|
{
|
|
*errorMessage = lastError;
|
|
}
|
|
return Qgis::VectorExportResult::ErrorCreatingLayer;
|
|
}
|
|
|
|
|
|
if ( !geometryColumn.isEmpty() )
|
|
{
|
|
int computeExtents = 0;
|
|
int msgCode = 0;
|
|
int outCode;
|
|
int outMsg;
|
|
QVariant msgText( " " );
|
|
QSqlQuery query( db );
|
|
int db2Environment = ENV_LUW;
|
|
|
|
// get the environment
|
|
QgsDb2GeometryColumns gc( db );
|
|
QString rc = gc.open( schemaName, tableName ); // returns SQLCODE if failure
|
|
if ( rc.isEmpty() || rc == QLatin1String( "0" ) )
|
|
{
|
|
db2Environment = gc.db2Environment();
|
|
}
|
|
if ( ENV_LUW == db2Environment )
|
|
{
|
|
sql = QStringLiteral( "CALL DB2GSE.ST_Register_Spatial_Column(?, ?, ?, ?, ?, ?, ?)" );
|
|
outCode = 5;
|
|
outMsg = 6;
|
|
}
|
|
else // z/OS doesn't support 'computeExtents' parameter and has different schema
|
|
{
|
|
sql = QStringLiteral( "CALL SYSPROC.ST_Register_Spatial_Column(?, ?, ?, ?, ?, ?)" );
|
|
outCode = 4;
|
|
outMsg = 5;
|
|
}
|
|
query.prepare( sql );
|
|
query.bindValue( 0, schemaName );
|
|
query.bindValue( 1, tableName );
|
|
query.bindValue( 2, geometryColumn );
|
|
query.bindValue( 3, srsName );
|
|
if ( ENV_LUW == db2Environment )
|
|
{
|
|
query.bindValue( 4, computeExtents );
|
|
}
|
|
|
|
query.bindValue( outCode, msgCode, QSql::Out );
|
|
query.bindValue( outMsg, msgText, QSql::Out );
|
|
|
|
if ( !query.exec() )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "error: %1; sql: %2" ).arg( query.lastError().text(), query.lastQuery() ) );
|
|
}
|
|
else
|
|
{
|
|
msgCode = query.boundValue( outCode ).toInt();
|
|
msgText = query.boundValue( outMsg ).toString(); // never gets a value...
|
|
if ( 0 != msgCode )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Register failed with code: %1; text: '%2'" ).arg( msgCode ).arg( msgText.toString() ) );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "Register successful" ) );
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// Show bound values
|
|
QList<QVariant> list = query.boundValues().values();
|
|
for ( int i = 0; i < list.size(); ++i )
|
|
{
|
|
QgsDebugMsg( QStringLiteral( "i: %1; value: %2; type: %3" )
|
|
.arg( i ).arg( list.at( i ).toString().toLatin1().data(), list.at( i ).typeName() ) );
|
|
}
|
|
#endif
|
|
}
|
|
// clear any resources hold by the query
|
|
q.clear();
|
|
q.setForwardOnly( true );
|
|
|
|
}
|
|
QgsDebugMsg( QStringLiteral( "successfully created empty layer" ) );
|
|
return Qgis::VectorExportResult::Success;
|
|
}
|
|
|
|
QString QgsDb2Provider::qgsFieldToDb2Field( const QgsField &field )
|
|
{
|
|
QString result;
|
|
switch ( field.type() )
|
|
{
|
|
case QVariant::LongLong:
|
|
result = QStringLiteral( "BIGINT" );
|
|
break;
|
|
|
|
case QVariant::DateTime:
|
|
result = QStringLiteral( "TIMESTAMP" );
|
|
break;
|
|
|
|
case QVariant::Date:
|
|
result = QStringLiteral( "DATE" );
|
|
break;
|
|
|
|
case QVariant::Time:
|
|
result = QStringLiteral( "TIME" );
|
|
break;
|
|
|
|
case QVariant::String:
|
|
result = QStringLiteral( "VARCHAR(%1)" ).arg( field.length() );
|
|
break;
|
|
|
|
case QVariant::Int:
|
|
result = QStringLiteral( "INTEGER" );
|
|
break;
|
|
|
|
case QVariant::Double:
|
|
if ( field.length() <= 0 || field.precision() <= 0 )
|
|
{
|
|
result = QStringLiteral( "DOUBLE" );
|
|
}
|
|
else
|
|
{
|
|
result = QStringLiteral( "DECIMAL(%1,%2)" ).arg( field.length(), field.precision() );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if ( !result.isEmpty() )
|
|
{
|
|
result = field.name() + ' ' + result;
|
|
}
|
|
return result;
|
|
}
|
|
bool QgsDb2Provider::convertField( QgsField &field )
|
|
{
|
|
QString fieldType = QStringLiteral( "VARCHAR" ); //default to string
|
|
int fieldSize = field.length();
|
|
int fieldPrec = field.precision();
|
|
switch ( field.type() )
|
|
{
|
|
case QVariant::LongLong:
|
|
fieldType = QStringLiteral( "BIGINT" );
|
|
fieldSize = -1;
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::DateTime:
|
|
fieldType = QStringLiteral( "TIMESTAMP" );
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::Date:
|
|
fieldType = QStringLiteral( "DATE" );
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::Time:
|
|
fieldType = QStringLiteral( "TIME" );
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::String:
|
|
fieldType = QStringLiteral( "VARCHAR" );
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::Int:
|
|
fieldType = QStringLiteral( "INTEGER" );
|
|
fieldSize = -1;
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::Double:
|
|
if ( fieldSize <= 0 || fieldPrec <= 0 )
|
|
{
|
|
fieldType = QStringLiteral( "DOUBLE" );
|
|
fieldSize = -1;
|
|
fieldPrec = 0;
|
|
}
|
|
else
|
|
{
|
|
fieldType = QStringLiteral( "DECIMAL" );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
field.setTypeName( fieldType );
|
|
field.setLength( fieldSize );
|
|
field.setPrecision( fieldPrec );
|
|
return true;
|
|
}
|
|
|
|
|
|
QString QgsDb2Provider::name() const
|
|
{
|
|
return DB2_PROVIDER_KEY;
|
|
}
|
|
|
|
QString QgsDb2Provider::description() const
|
|
{
|
|
return DB2_PROVIDER_DESCRIPTION;
|
|
}
|
|
|
|
QgsAttributeList QgsDb2Provider::pkAttributeIndexes() const
|
|
{
|
|
QgsAttributeList list;
|
|
if ( mFidColIdx >= 0 )
|
|
list << mFidColIdx;
|
|
return list;
|
|
}
|
|
|
|
QgsDb2Provider *QgsDb2ProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
|
|
{
|
|
return new QgsDb2Provider( uri, options, flags );
|
|
}
|
|
|
|
QgsDb2ProviderMetadata::QgsDb2ProviderMetadata()
|
|
: QgsProviderMetadata( QgsDb2Provider::DB2_PROVIDER_KEY, QgsDb2Provider::DB2_PROVIDER_DESCRIPTION )
|
|
{
|
|
|
|
}
|
|
|
|
QList< QgsDataItemProvider * > QgsDb2ProviderMetadata::dataItemProviders() const
|
|
{
|
|
QList<QgsDataItemProvider *> providers;
|
|
QgsSettings settings;
|
|
if ( settings.value( QStringLiteral( "showDeprecated" ), false, QgsSettings::Providers ).toBool() )
|
|
{
|
|
providers << new QgsDb2DataItemProvider;
|
|
}
|
|
return providers;
|
|
}
|
|
|
|
Qgis::VectorExportResult QgsDb2ProviderMetadata::createEmptyLayer(
|
|
const QString &uri,
|
|
const QgsFields &fields,
|
|
QgsWkbTypes::Type wkbType,
|
|
const QgsCoordinateReferenceSystem &srs,
|
|
bool overwrite,
|
|
QMap<int, int> &oldToNewAttrIdxMap,
|
|
QString &errorMessage,
|
|
const QMap<QString, QVariant> * )
|
|
{
|
|
return QgsDb2Provider::createEmptyLayer(
|
|
uri, fields, wkbType, srs, overwrite,
|
|
&oldToNewAttrIdxMap, &errorMessage
|
|
);
|
|
}
|
|
|
|
QString QgsDb2Provider::dbConnectionName( const QString &name )
|
|
{
|
|
// Starting with Qt 5.11, sharing the same connection between threads is not allowed.
|
|
// We use a dedicated connection for each thread requiring access to the database,
|
|
// using the thread address as connection name.
|
|
return QStringLiteral( "%1:0x%2" ).arg( name ).arg( reinterpret_cast<quintptr>( QThread::currentThread() ), 2 * QT_POINTER_SIZE, 16, QLatin1Char( '0' ) );
|
|
}
|
|
|
|
|
|
QGISEXTERN QgsProviderMetadata *providerMetadataFactory()
|
|
{
|
|
return new QgsDb2ProviderMetadata();
|
|
}
|