mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-17 00:04:02 -04:00
capability and changeFeaturss method to allows joint geometry and attribute updates. Currently only implemented in the postgres provider
3107 lines
89 KiB
C++
3107 lines
89 KiB
C++
/***************************************************************************
|
|
qgsoracleprovider.cpp - QGIS data provider for Oracle layers
|
|
-------------------
|
|
begin : August 2012
|
|
copyright : (C) 2012 by Juergen E. Fischer
|
|
email : jef at norbit dot de
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgsfeature.h"
|
|
#include "qgsfield.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgsmessageoutput.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgsrectangle.h"
|
|
#include "qgscoordinatereferencesystem.h"
|
|
#include "qgsvectorlayerimport.h"
|
|
#include "qgslogger.h"
|
|
|
|
#include "qgsprovidercountcalcevent.h"
|
|
#include "qgsproviderextentcalcevent.h"
|
|
|
|
#include "qgsoracleprovider.h"
|
|
#include "qgsoracletablemodel.h"
|
|
#include "qgsoraclesourceselect.h"
|
|
#include "qgsoracledataitems.h"
|
|
#include "qgsoraclefeatureiterator.h"
|
|
#include "qgsoracleconnpool.h"
|
|
|
|
#include <QSqlRecord>
|
|
#include <QSqlField>
|
|
|
|
#include "ocispatial/wkbptr.h"
|
|
|
|
const QString ORACLE_KEY = "oracle";
|
|
const QString ORACLE_DESCRIPTION = "Oracle data provider";
|
|
|
|
QgsOracleProvider::QgsOracleProvider( QString const & uri )
|
|
: QgsVectorDataProvider( uri )
|
|
, mValid( false )
|
|
, mPrimaryKeyType( pktUnknown )
|
|
, mDetectedGeomType( QGis::WKBUnknown )
|
|
, mRequestedGeomType( QGis::WKBUnknown )
|
|
, mHasSpatialIndex( false )
|
|
, mSpatialIndexName( QString::null )
|
|
, mShared( new QgsOracleSharedData )
|
|
{
|
|
static int geomMetaType = -1;
|
|
if ( geomMetaType < 0 )
|
|
geomMetaType = qRegisterMetaType<QOCISpatialGeometry>();
|
|
|
|
QgsDebugMsg( QString( "URI: %1 " ).arg( uri ) );
|
|
|
|
mUri = QgsDataSourceURI( uri );
|
|
|
|
// populate members from the uri structure
|
|
mOwnerName = mUri.schema();
|
|
mTableName = mUri.table();
|
|
mGeometryColumn = mUri.geometryColumn();
|
|
mSqlWhereClause = mUri.sql();
|
|
mSrid = mUri.srid().toInt();
|
|
mRequestedGeomType = mUri.wkbType();
|
|
mUseEstimatedMetadata = mUri.useEstimatedMetadata();
|
|
|
|
mConnection = QgsOracleConnPool::instance()->acquireConnection( mUri.connectionInfo() );
|
|
if ( !mConnection )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( mOwnerName.isEmpty() && mTableName.startsWith( "(" ) && mTableName.endsWith( ")" ) )
|
|
{
|
|
mIsQuery = true;
|
|
mQuery = mTableName;
|
|
mTableName = "";
|
|
}
|
|
else
|
|
{
|
|
mIsQuery = false;
|
|
|
|
if ( mOwnerName.isEmpty() )
|
|
{
|
|
mOwnerName = mConnection->currentUser();
|
|
}
|
|
|
|
if ( !mOwnerName.isEmpty() )
|
|
{
|
|
mQuery += quotedIdentifier( mOwnerName ) + ".";
|
|
}
|
|
|
|
if ( !mTableName.isEmpty() )
|
|
{
|
|
mQuery += quotedIdentifier( mTableName );
|
|
}
|
|
}
|
|
|
|
QgsDebugMsg( QString( "Connection info is %1" ).arg( mUri.connectionInfo() ) );
|
|
QgsDebugMsg( QString( "Geometry column is: %1" ).arg( mGeometryColumn ) );
|
|
QgsDebugMsg( QString( "Owner is: %1" ).arg( mOwnerName ) );
|
|
QgsDebugMsg( QString( "Table name is: %1" ).arg( mTableName ) );
|
|
QgsDebugMsg( QString( "Query is: %1" ).arg( mQuery ) );
|
|
QgsDebugMsg( QString( "Where clause is: %1" ).arg( mSqlWhereClause ) );
|
|
QgsDebugMsg( QString( "SRID is: %1" ).arg( mSrid ) );
|
|
|
|
// no table/query passed, the provider could be used to get tables
|
|
if ( mQuery.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !hasSufficientPermsAndCapabilities() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// get geometry details
|
|
if ( !getGeometryDetails() ) // gets srid, geometry and data type
|
|
{
|
|
// the table is not a geometry table
|
|
mValid = false;
|
|
disconnectDb();
|
|
return;
|
|
}
|
|
|
|
mLayerExtent.setMinimal();
|
|
mFeaturesCounted = -1;
|
|
|
|
// set the primary key
|
|
if ( !determinePrimaryKey() )
|
|
{
|
|
mValid = false;
|
|
disconnectDb();
|
|
return;
|
|
}
|
|
|
|
//fill type names into sets
|
|
mNativeTypes
|
|
// integer types
|
|
<< QgsVectorDataProvider::NativeType( tr( "Whole number" ), "number(10,0)", QVariant::Int )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Whole big number" ), "number(20,0)", QVariant::LongLong )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), "number", QVariant::Double, 1, 38, 0, 38 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), "double precision", QVariant::Double )
|
|
|
|
// floating point
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), "binary_float", QVariant::Double )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), "binary_double", QVariant::Double )
|
|
|
|
// string types
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), "CHAR", QVariant::String, 1, 255 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, limited variable length (varchar2)" ), "VARCHAR2", QVariant::String, 1, 255 )
|
|
<< QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (long)" ), "LONG", QVariant::String )
|
|
;
|
|
|
|
QString key;
|
|
switch ( mPrimaryKeyType )
|
|
{
|
|
case pktRowId:
|
|
key = "ROWID";
|
|
break;
|
|
|
|
case pktInt:
|
|
case pktFidMap:
|
|
{
|
|
Q_ASSERT( mPrimaryKeyType != pktInt || mPrimaryKeyAttrs.size() == 1 );
|
|
|
|
QString delim;
|
|
Q_FOREACH ( int idx, mPrimaryKeyAttrs )
|
|
{
|
|
Q_ASSERT( idx >= 0 && idx < mAttributeFields.size() );
|
|
key += delim + mAttributeFields[ idx ].name();
|
|
delim = ",";
|
|
}
|
|
}
|
|
break;
|
|
case pktUnknown:
|
|
mValid = false;
|
|
break;
|
|
}
|
|
|
|
if ( mValid )
|
|
{
|
|
mUri.setKeyColumn( key );
|
|
setDataSourceUri( mUri.uri() );
|
|
}
|
|
else
|
|
{
|
|
disconnectDb();
|
|
}
|
|
}
|
|
|
|
QgsOracleProvider::~QgsOracleProvider()
|
|
{
|
|
QgsDebugMsg( "deconstructing." );
|
|
|
|
disconnectDb();
|
|
}
|
|
|
|
QgsAbstractFeatureSource *QgsOracleProvider::featureSource() const
|
|
{
|
|
return new QgsOracleFeatureSource( this );
|
|
}
|
|
|
|
void QgsOracleProvider::disconnectDb()
|
|
{
|
|
if ( mConnection )
|
|
QgsOracleConnPool::instance()->releaseConnection( mConnection );
|
|
mConnection = 0;
|
|
}
|
|
|
|
bool QgsOracleProvider::exec( QSqlQuery &qry, QString sql )
|
|
{
|
|
QgsDebugMsgLevel( QString( "SQL: %1" ).arg( sql ), 4 );
|
|
|
|
qry.setForwardOnly( true );
|
|
|
|
bool res = qry.exec( sql );
|
|
if ( !res )
|
|
{
|
|
QgsDebugMsg( QString( "SQL: %1\nERROR: %2" )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ) );
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
QString QgsOracleProvider::storageType() const
|
|
{
|
|
return "Oracle database with locator/spatial extension";
|
|
}
|
|
|
|
static bool operator<( const QVariant &a, const QVariant &b )
|
|
{
|
|
if ( a.isNull() || b.isNull() )
|
|
return false;
|
|
|
|
if ( a.type() == b.type() )
|
|
{
|
|
switch ( a.type() )
|
|
{
|
|
case QVariant::Int:
|
|
case QVariant::Char:
|
|
return a.toInt() < b.toInt();
|
|
|
|
case QVariant::Double:
|
|
return a.toDouble() < b.toDouble();
|
|
|
|
case QVariant::LongLong:
|
|
return a.toLongLong() < b.toLongLong();
|
|
|
|
case QVariant::List:
|
|
{
|
|
QList<QVariant> al = a.toList();
|
|
QList<QVariant> bl = b.toList();
|
|
|
|
int i, n = qMin( al.size(), bl.size() );
|
|
for ( i = 0; i < n && al[i] == bl[i]; i++ )
|
|
;
|
|
|
|
if ( i == n )
|
|
return al.size() < bl.size();
|
|
else
|
|
return al[i] < bl[i];
|
|
}
|
|
break;
|
|
|
|
case QVariant::StringList:
|
|
{
|
|
QStringList al = a.toStringList();
|
|
QStringList bl = b.toStringList();
|
|
|
|
int i, n = qMin( al.size(), bl.size() );
|
|
for ( i = 0; i < n && al[i] == bl[i]; i++ )
|
|
;
|
|
|
|
if ( i == n )
|
|
return al.size() < bl.size();
|
|
else
|
|
return al[i] < bl[i];
|
|
}
|
|
break;
|
|
|
|
case QVariant::Date:
|
|
return a.toDate() < b.toDate();
|
|
|
|
case QVariant::Time:
|
|
return a.toTime() < b.toTime();
|
|
|
|
case QVariant::DateTime:
|
|
return a.toDateTime() < b.toDateTime();
|
|
|
|
case QVariant::Bool:
|
|
return a.toBool() < b.toBool();
|
|
|
|
case QVariant::UInt:
|
|
return a.toUInt() < b.toUInt();
|
|
|
|
case QVariant::ULongLong:
|
|
return a.toULongLong() < b.toULongLong();
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return a.canConvert( QVariant::String ) && b.canConvert( QVariant::String ) && a.toString() < b.toString();
|
|
}
|
|
|
|
|
|
QString QgsOracleProvider::pkParamWhereClause() const
|
|
{
|
|
QString whereClause;
|
|
|
|
switch ( mPrimaryKeyType )
|
|
{
|
|
case pktInt:
|
|
case pktFidMap:
|
|
{
|
|
Q_ASSERT( mPrimaryKeyAttrs.size() >= 1 );
|
|
|
|
QString delim = "";
|
|
for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ )
|
|
{
|
|
int idx = mPrimaryKeyAttrs[i];
|
|
const QgsField &fld = field( idx );
|
|
|
|
whereClause += delim + QString( "%1=?" ).arg( mConnection->fieldExpression( fld ) );
|
|
delim = " AND ";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case pktRowId:
|
|
return "ROWID=?";
|
|
break;
|
|
|
|
case pktUnknown:
|
|
Q_ASSERT( !"FAILURE: Primary key unknown" );
|
|
whereClause = "NULL IS NOT NULL";
|
|
break;
|
|
}
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
if ( !whereClause.isEmpty() )
|
|
whereClause += " AND ";
|
|
|
|
whereClause += "(" + mSqlWhereClause + ")";
|
|
}
|
|
|
|
return whereClause;
|
|
}
|
|
|
|
void QgsOracleProvider::appendPkParams( QgsFeatureId fid, QSqlQuery &qry ) const
|
|
{
|
|
switch ( mPrimaryKeyType )
|
|
{
|
|
case pktInt:
|
|
QgsDebugMsgLevel( QString( "addBindValue pk %1" ).arg( FID_TO_STRING( fid ) ), 4 );
|
|
qry.addBindValue( FID_TO_STRING( fid ) );
|
|
break;
|
|
|
|
case pktRowId:
|
|
case pktFidMap:
|
|
{
|
|
QVariant pkValsVariant = mShared->lookupKey( fid );
|
|
if ( !pkValsVariant.isNull() )
|
|
{
|
|
Q_FOREACH ( const QVariant &v, pkValsVariant.toList() )
|
|
{
|
|
QgsDebugMsgLevel( QString( "addBindValue pk %1" ).arg( FID_TO_STRING( fid ) ), 4 );
|
|
qry.addBindValue( v );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QString( "key values for fid %1 not found." ).arg( fid ) );
|
|
for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ )
|
|
{
|
|
QgsDebugMsgLevel( QString( "addBindValue pk NULL" ).arg( fid ), 4 );
|
|
qry.addBindValue( QVariant() );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case pktUnknown:
|
|
QgsDebugMsg( "Unknown key type" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
QString QgsOracleUtils::whereClause( QgsFeatureId featureId, const QgsFields& fields, QgsOraclePrimaryKeyType primaryKeyType, const QList<int>& primaryKeyAttrs, QSharedPointer<QgsOracleSharedData> sharedData )
|
|
{
|
|
QString whereClause;
|
|
|
|
switch ( primaryKeyType )
|
|
{
|
|
case pktInt:
|
|
Q_ASSERT( primaryKeyAttrs.size() == 1 );
|
|
whereClause = QString( "%1=%2" ).arg( QgsOracleConn::quotedIdentifier( fields[ primaryKeyAttrs[0] ].name() ) ).arg( featureId );
|
|
break;
|
|
|
|
case pktRowId:
|
|
case pktFidMap:
|
|
{
|
|
QVariant pkValsVariant = sharedData->lookupKey( featureId );
|
|
if ( !pkValsVariant.isNull() )
|
|
{
|
|
QList<QVariant> pkVals = pkValsVariant.toList();
|
|
|
|
if ( primaryKeyType == pktFidMap )
|
|
{
|
|
Q_ASSERT( pkVals.size() == primaryKeyAttrs.size() );
|
|
|
|
QString delim = "";
|
|
for ( int i = 0; i < primaryKeyAttrs.size(); i++ )
|
|
{
|
|
int idx = primaryKeyAttrs[i];
|
|
const QgsField &fld = fields[ idx ];
|
|
|
|
whereClause += delim + QString( "%1=%2" ).arg( QgsOracleConn::fieldExpression( fld ) ).arg( QgsOracleConn::quotedValue( pkVals[i], fld.type() ) );
|
|
delim = " AND ";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
whereClause += QString( "ROWID=%1" ).arg( QgsOracleConn::quotedValue( pkVals[0].toString() ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QString( "FAILURE: Key values for feature %1 not found." ).arg( featureId ) );
|
|
whereClause = "NULL IS NOT NULL";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case pktUnknown:
|
|
Q_ASSERT( !"FAILURE: Primary key unknown" );
|
|
whereClause = "NULL IS NOT NULL";
|
|
break;
|
|
}
|
|
|
|
return whereClause;
|
|
}
|
|
|
|
QString QgsOracleUtils::whereClause( QgsFeatureIds featureIds, const QgsFields &fields, QgsOraclePrimaryKeyType primaryKeyType, const QList<int> &primaryKeyAttrs, QSharedPointer<QgsOracleSharedData> sharedData )
|
|
{
|
|
QStringList whereClauses;
|
|
Q_FOREACH ( const QgsFeatureId featureId, featureIds )
|
|
{
|
|
whereClauses << whereClause( featureId, fields, primaryKeyType, primaryKeyAttrs, sharedData );
|
|
}
|
|
return whereClauses.isEmpty() ? "" : whereClauses.join( " OR " ).prepend( "(" ).append( ")" );
|
|
}
|
|
|
|
QString QgsOracleUtils::andWhereClauses( const QString& c1, const QString& c2 )
|
|
{
|
|
if ( c1.isEmpty() )
|
|
return c2;
|
|
if ( c2.isEmpty() )
|
|
return c1;
|
|
|
|
return QString( "(%1) AND (%2)" ).arg( c1 ).arg( c2 );
|
|
}
|
|
|
|
QString QgsOracleProvider::whereClause( QgsFeatureId featureId ) const
|
|
{
|
|
return QgsOracleUtils::whereClause( featureId, mAttributeFields, mPrimaryKeyType, mPrimaryKeyAttrs, mShared );
|
|
}
|
|
|
|
void QgsOracleProvider::setExtent( QgsRectangle& newExtent )
|
|
{
|
|
mLayerExtent.setXMaximum( newExtent.xMaximum() );
|
|
mLayerExtent.setXMinimum( newExtent.xMinimum() );
|
|
mLayerExtent.setYMaximum( newExtent.yMaximum() );
|
|
mLayerExtent.setYMinimum( newExtent.yMinimum() );
|
|
}
|
|
|
|
/**
|
|
* Return the feature type
|
|
*/
|
|
QGis::WkbType QgsOracleProvider::geometryType() const
|
|
{
|
|
return mRequestedGeomType != QGis::WKBUnknown ? mRequestedGeomType : mDetectedGeomType;
|
|
}
|
|
|
|
const QgsField &QgsOracleProvider::field( int index ) const
|
|
{
|
|
if ( index < 0 || index >= mAttributeFields.size() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "FAILURE: Field %1 not found." ).arg( index ), tr( "Oracle" ) );
|
|
throw OracleFieldNotFound();
|
|
}
|
|
|
|
return mAttributeFields[ index ];
|
|
}
|
|
|
|
QgsFeatureIterator QgsOracleProvider::getFeatures( const QgsFeatureRequest& request )
|
|
{
|
|
if ( !mValid )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Read attempt on an invalid oracle data source" ), tr( "Oracle" ) );
|
|
return QgsFeatureIterator();
|
|
}
|
|
|
|
return QgsFeatureIterator( new QgsOracleFeatureIterator( new QgsOracleFeatureSource( this ), true, request ) );
|
|
}
|
|
|
|
/**
|
|
* Return the number of fields
|
|
*/
|
|
uint QgsOracleProvider::fieldCount() const
|
|
{
|
|
return mAttributeFields.size();
|
|
}
|
|
|
|
const QgsFields &QgsOracleProvider::fields() const
|
|
{
|
|
return mAttributeFields;
|
|
}
|
|
|
|
QString QgsOracleProvider::dataComment() const
|
|
{
|
|
return mDataComment;
|
|
}
|
|
|
|
bool QgsOracleProvider::loadFields()
|
|
{
|
|
mAttributeFields.clear();
|
|
mDefaultValues.clear();
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
QMap<QString, QString> comments;
|
|
QMap<QString, QString> types;
|
|
QMap<QString, QVariant> defvalues;
|
|
|
|
if ( !mIsQuery )
|
|
{
|
|
QgsDebugMsg( QString( "Loading fields for table %1" ).arg( mTableName ) );
|
|
|
|
if ( exec( qry, QString( "SELECT comments FROM all_tab_comments WHERE owner=%1 AND table_name=%2" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
mDataComment = qry.value( 0 ).toString();
|
|
else if ( exec( qry, QString( "SELECT comments FROM all_mview_comments WHERE owner=%1 AND mview_name=%2" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
mDataComment = qry.value( 0 ).toString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Loading comment for table %1.%2 failed [%3]" )
|
|
.arg( mOwnerName )
|
|
.arg( mTableName )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=%1 AND t.table_name=%2 AND t.column_name<>%3" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) ) ) )
|
|
{
|
|
while ( qry.next() )
|
|
{
|
|
comments.insert( qry.value( 0 ).toString(), qry.value( 1 ).toString() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Loading comment for columns of table %1.%2 failed [%3]" ).arg( mOwnerName ).arg( mTableName ).arg( qry.lastError().text() ), tr( "Oracle" ) );
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
if ( exec( qry, QString( "SELECT"
|
|
" t.column_name"
|
|
",CASE WHEN t.data_type_owner IS NULL THEN t.data_type ELSE t.data_type_owner||'.'||t.data_type END"
|
|
",t.data_precision"
|
|
",t.data_scale"
|
|
",t.char_length"
|
|
",t.char_used"
|
|
",t.data_default"
|
|
" FROM all_tab_columns t"
|
|
" WHERE t.owner=%1 AND t.table_name=%2%3"
|
|
" ORDER BY t.column_id" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( mGeometryColumn.isEmpty() ? "" : QString( " AND t.column_name<>%1 " ).arg( quotedValue( mGeometryColumn ) ) )
|
|
) )
|
|
{
|
|
while ( qry.next() )
|
|
{
|
|
QString name = qry.value( 0 ).toString();
|
|
QString type = qry.value( 1 ).toString();
|
|
int prec = qry.value( 2 ).toInt();
|
|
int scale = qry.value( 3 ).toInt();
|
|
int clength = qry.value( 4 ).toInt();
|
|
bool cused = qry.value( 5 ).toString() == "C";
|
|
QVariant defValue = qry.value( 6 );
|
|
|
|
if ( type == "CHAR" || type == "VARCHAR2" || type == "VARCHAR" )
|
|
{
|
|
types.insert( name, QString( "%1(%2 %3)" ).arg( type ).arg( clength ).arg( cused ? "CHAR" : "BYTE" ) );
|
|
}
|
|
else if ( type == "NCHAR" || type == "NVARCHAR2" || type == "NVARCHAR" )
|
|
{
|
|
types.insert( name, QString( "%1(%2)" ).arg( type ).arg( clength ) );
|
|
}
|
|
else if ( type == "NUMBER" )
|
|
{
|
|
if ( scale == 0 )
|
|
{
|
|
types.insert( name, QString( "%1(%2)" ).arg( type ).arg( prec ) );
|
|
}
|
|
else
|
|
{
|
|
types.insert( name, QString( "%1(%2,%3)" ).arg( type ).arg( prec ).arg( scale ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
types.insert( name, type );
|
|
}
|
|
|
|
defvalues.insert( name, defValue );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Loading field types for table %1.%2 failed [%3]" )
|
|
.arg( mOwnerName )
|
|
.arg( mTableName )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
|
|
if ( !mGeometryColumn.isEmpty() )
|
|
{
|
|
if ( exec( qry, QString( "SELECT i.index_name,i.domidx_opstatus"
|
|
" FROM all_indexes i"
|
|
" JOIN all_ind_columns c ON i.owner=c.index_owner AND i.index_name=c.index_name AND c.column_name=%3"
|
|
" WHERE i.table_owner=%1 AND i.table_name=%2 AND i.ityp_owner='MDSYS' AND i.ityp_name='SPATIAL_INDEX'" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
{
|
|
mSpatialIndexName = qry.value( 0 ).toString();
|
|
if ( qry.value( 1 ).toString() != "VALID" )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Invalid spatial index %1 on column %2.%3.%4 found - expect poor performance." )
|
|
.arg( mSpatialIndexName )
|
|
.arg( mOwnerName )
|
|
.arg( mTableName )
|
|
.arg( mGeometryColumn ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( QString( "Valid spatial index %1 found" ).arg( mSpatialIndexName ) );
|
|
mHasSpatialIndex = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Probing for spatial index on column %1.%2.%3 failed [%4]" )
|
|
.arg( mOwnerName )
|
|
.arg( mTableName )
|
|
.arg( mGeometryColumn )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
|
|
if ( !mHasSpatialIndex )
|
|
{
|
|
mHasSpatialIndex = qry.exec( QString( "SELECT %2 FROM %1 WHERE sdo_filter(%2,mdsys.sdo_geometry(2003,%3,NULL,mdsys.sdo_elem_info_array(1,1003,3),mdsys.sdo_ordinate_array(-1,-1,1,1)))='TRUE'" )
|
|
.arg( mQuery )
|
|
.arg( quotedIdentifier( mGeometryColumn ) )
|
|
.arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) ) );
|
|
if ( !mHasSpatialIndex )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "No spatial index on column %1.%2.%3 found - expect poor performance." )
|
|
.arg( mOwnerName )
|
|
.arg( mTableName )
|
|
.arg( mGeometryColumn ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
mEnabledCapabilities |= QgsVectorDataProvider::CreateSpatialIndex;
|
|
}
|
|
|
|
if ( !exec( qry, QString( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Retrieving fields from '%1' failed [%2]" ).arg( mQuery ).arg( qry.lastError().text() ), tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
QSqlRecord record = qry.record();
|
|
|
|
for ( int i = 0; i < record.count(); i++ )
|
|
{
|
|
QSqlField field = record.field( i );
|
|
|
|
if ( field.name() == mGeometryColumn )
|
|
continue;
|
|
|
|
if ( !mIsQuery && !types.contains( field.name() ) )
|
|
continue;
|
|
|
|
mAttributeFields.append( QgsField( field.name(), field.type(), types.value( field.name() ), field.length(), field.precision(), comments.value( field.name() ) ) );
|
|
mDefaultValues.append( defvalues.value( field.name(), QVariant() ) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsOracleProvider::hasSufficientPermsAndCapabilities()
|
|
{
|
|
QgsDebugMsg( "Checking for permissions on the relation" );
|
|
|
|
mEnabledCapabilities = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId;
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
if ( !mIsQuery )
|
|
{
|
|
if ( mConnection->currentUser() == mOwnerName )
|
|
{
|
|
// full set of privileges for the owner
|
|
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures
|
|
| QgsVectorDataProvider::ChangeAttributeValues
|
|
| QgsVectorDataProvider::AddFeatures
|
|
| QgsVectorDataProvider::AddAttributes
|
|
| QgsVectorDataProvider::DeleteAttributes
|
|
| QgsVectorDataProvider::ChangeGeometries
|
|
;
|
|
}
|
|
else if ( exec( qry, QString( "SELECT privilege FROM all_tab_privs WHERE table_schema=%1 AND table_name=%2 AND privilege IN ('DELETE','UPDATE','INSERT','ALTER TABLE')" )
|
|
.arg( quotedValue( mOwnerName ) ).arg( quotedValue( mTableName ) ) ) )
|
|
{
|
|
// check grants
|
|
while ( qry.next() )
|
|
{
|
|
QString priv = qry.value( 0 ).toString();
|
|
|
|
if ( priv == "DELETE" )
|
|
{
|
|
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
|
|
}
|
|
else if ( priv == "UPDATE" )
|
|
{
|
|
mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
|
|
}
|
|
else if ( priv == "INSERT" )
|
|
{
|
|
mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures;
|
|
}
|
|
else if ( priv == "ALTER TABLE" )
|
|
{
|
|
mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes;
|
|
}
|
|
}
|
|
|
|
if ( !mGeometryColumn.isNull() )
|
|
{
|
|
if ( exec( qry, QString( "SELECT 1 FROM all_col_privs WHERE table_schema=%1 AND table_name=%2 AND column_name=%3 AND privilege='UPDATE'" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries;
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to determine geometry column access privileges for column %1.%2.\nThe error message from the database was:\n%3.\nSQL: %4" )
|
|
.arg( mQuery )
|
|
.arg( mGeometryColumn )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the table %1.\nThe error message from the database was:\n%2.\nSQL: %3" )
|
|
.arg( mQuery )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Check if the sql is a select query
|
|
if ( !mQuery.startsWith( "(" ) && !mQuery.endsWith( ")" ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "The custom query is not a select query." ), tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
if ( !exec( qry, QString( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsOracleProvider::determinePrimaryKey()
|
|
{
|
|
if ( !loadFields() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check to see if there is an unique index on the relation, which
|
|
// can be used as a key into the table. Primary keys are always
|
|
// unique indices, so we catch them as well.
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
if ( !mIsQuery )
|
|
{
|
|
|
|
if ( !exec( qry, QString( "SELECT column_name"
|
|
" FROM all_ind_columns a"
|
|
" JOIN all_constraints b ON a.index_name=constraint_name AND a.index_owner=b.owner"
|
|
" WHERE b.constraint_type='P' AND b.owner=%1 AND b.table_name=%2" )
|
|
.arg( quotedValue( mOwnerName ) ).arg( quotedValue( mTableName ) ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
bool isInt = true;
|
|
|
|
while ( qry.next() )
|
|
{
|
|
QString name = qry.value( 0 ).toString();
|
|
|
|
int idx = mAttributeFields.indexFromName( name );
|
|
if ( idx < 0 )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Primary key field %1 not found in %2" ).arg( name ).arg( mQuery ), tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
const QgsField &fld = mAttributeFields.at( idx );
|
|
|
|
if ( isInt &&
|
|
fld.type() != QVariant::Int &&
|
|
fld.type() != QVariant::LongLong &&
|
|
!( fld.type() == QVariant::Double && fld.precision() == 0 ) )
|
|
isInt = false;
|
|
|
|
mPrimaryKeyAttrs << idx;
|
|
}
|
|
|
|
if ( mPrimaryKeyAttrs.size() > 0 )
|
|
{
|
|
mPrimaryKeyType = ( mPrimaryKeyAttrs.size() == 1 && isInt ) ? pktInt : pktFidMap;
|
|
}
|
|
else if ( !exec( qry, QString( "SELECT 1 FROM all_tables WHERE owner=%1 AND table_name=%2" ).arg( quotedValue( mOwnerName ) ).arg( quotedValue( mTableName ) ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
}
|
|
else if ( qry.next() )
|
|
{
|
|
// is table
|
|
mPrimaryKeyType = pktRowId;
|
|
}
|
|
else
|
|
{
|
|
QString primaryKey = mUri.keyColumn();
|
|
mPrimaryKeyType = pktUnknown;
|
|
|
|
if ( !primaryKey.isEmpty() )
|
|
{
|
|
int idx = fieldNameIndex( primaryKey );
|
|
|
|
if ( idx >= 0 )
|
|
{
|
|
const QgsField &fld = mAttributeFields.at( idx );
|
|
|
|
if ( mUseEstimatedMetadata || uniqueData( mQuery, primaryKey ) )
|
|
{
|
|
mPrimaryKeyType = ( fld.type() == QVariant::Int || fld.type() == QVariant::LongLong || ( fld.type() == QVariant::Double && fld.precision() == 0 ) ) ? pktInt : pktFidMap;
|
|
mPrimaryKeyAttrs << idx;
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Primary key field '%1' for view not unique." ).arg( primaryKey ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Key field '%1' for view not found." ).arg( primaryKey ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "No key field for view given." ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QString primaryKey = mUri.keyColumn();
|
|
int idx = fieldNameIndex( mUri.keyColumn() );
|
|
|
|
if ( idx >= 0 && (
|
|
mAttributeFields[idx].type() == QVariant::Int ||
|
|
mAttributeFields[idx].type() == QVariant::LongLong ||
|
|
mAttributeFields[idx].type() == QVariant::Double
|
|
) )
|
|
{
|
|
if ( mUseEstimatedMetadata || uniqueData( mQuery, primaryKey ) )
|
|
{
|
|
mPrimaryKeyType = pktInt;
|
|
mPrimaryKeyAttrs << idx;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "No key field for query given." ), tr( "Oracle" ) );
|
|
mPrimaryKeyType = pktUnknown;
|
|
}
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
mValid = mPrimaryKeyType != pktUnknown;
|
|
|
|
return mValid;
|
|
}
|
|
|
|
bool QgsOracleProvider::uniqueData( QString query, QString colName )
|
|
{
|
|
Q_UNUSED( query );
|
|
// Check to see if the given column contains unique data
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
QString table = mQuery;
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
table += " WHERE " + mSqlWhereClause;
|
|
}
|
|
|
|
QString sql = QString( "SELECT (SELECT count(distinct %1) FROM %2)-(SELECT count(%1) FROM %2) FROM dual" )
|
|
.arg( quotedIdentifier( colName ) )
|
|
.arg( mQuery );
|
|
|
|
if ( !exec( qry, sql ) || !qry.next() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
return qry.value( 0 ).toInt() == 0;
|
|
}
|
|
|
|
// Returns the minimum value of an attribute
|
|
QVariant QgsOracleProvider::minimumValue( int index )
|
|
{
|
|
try
|
|
{
|
|
// get the field name
|
|
const QgsField &fld = field( index );
|
|
QString sql = QString( "SELECT min(%1) FROM %2" )
|
|
.arg( quotedIdentifier( fld.name() ) )
|
|
.arg( mQuery );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
sql += QString( " WHERE %1" ).arg( mSqlWhereClause );
|
|
}
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
if ( !exec( qry, sql ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
return QVariant( QString::null );
|
|
}
|
|
|
|
if ( qry.next() )
|
|
{
|
|
return qry.value( 0 );
|
|
}
|
|
}
|
|
catch ( OracleFieldNotFound )
|
|
{
|
|
;
|
|
}
|
|
return QVariant( QString::null );
|
|
}
|
|
|
|
// Returns the list of unique values of an attribute
|
|
void QgsOracleProvider::uniqueValues( int index, QList<QVariant> &uniqueValues, int limit )
|
|
{
|
|
uniqueValues.clear();
|
|
|
|
try
|
|
{
|
|
// get the field name
|
|
const QgsField &fld = field( index );
|
|
QString sql = QString( "SELECT DISTINCT %1 FROM %2" )
|
|
.arg( quotedIdentifier( fld.name() ) )
|
|
.arg( mQuery );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
sql += QString( " WHERE %1" ).arg( mSqlWhereClause );
|
|
}
|
|
|
|
sql += QString( " ORDER BY %1" )
|
|
.arg( quotedIdentifier( fld.name() ) );
|
|
|
|
if ( limit >= 0 )
|
|
{
|
|
sql = QString( "SELECT * FROM (%1) WHERE rownum<=%2" ).arg( sql ).arg( limit );
|
|
}
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
if ( !exec( qry, sql ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
return;
|
|
}
|
|
|
|
while ( qry.next() )
|
|
{
|
|
uniqueValues.append( qry.value( 0 ) );
|
|
}
|
|
}
|
|
catch ( OracleFieldNotFound )
|
|
{
|
|
}
|
|
}
|
|
|
|
// Returns the maximum value of an attribute
|
|
QVariant QgsOracleProvider::maximumValue( int index )
|
|
{
|
|
try
|
|
{
|
|
// get the field name
|
|
const QgsField &fld = field( index );
|
|
QString sql = QString( "SELECT max(%1) FROM %2" )
|
|
.arg( quotedIdentifier( fld.name() ) )
|
|
.arg( mQuery );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
sql += QString( " WHERE %1" ).arg( mSqlWhereClause );
|
|
}
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
if ( !exec( qry, sql ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
return QVariant( QString::null );
|
|
}
|
|
|
|
if ( qry.next() )
|
|
{
|
|
return qry.value( 0 );
|
|
}
|
|
}
|
|
catch ( OracleFieldNotFound )
|
|
{
|
|
;
|
|
}
|
|
|
|
return QVariant( QString::null );
|
|
}
|
|
|
|
|
|
bool QgsOracleProvider::isValid()
|
|
{
|
|
return mValid;
|
|
}
|
|
|
|
QVariant QgsOracleProvider::defaultValue( int fieldId )
|
|
{
|
|
return mDefaultValues.value( fieldId, QVariant() );
|
|
}
|
|
|
|
QString QgsOracleProvider::paramValue( QString fieldValue, const QString &defaultValue ) const
|
|
{
|
|
if ( fieldValue.isNull() )
|
|
{
|
|
return QString::null;
|
|
}
|
|
else if ( fieldValue == defaultValue && !defaultValue.isNull() )
|
|
{
|
|
QSqlQuery qry( *mConnection );
|
|
if ( !exec( qry, QString( "SELECT %1 FROM dual" ).arg( defaultValue ) ) || !qry.next() )
|
|
{
|
|
throw OracleException( tr( "Evaluation of default value failed" ), qry );
|
|
}
|
|
|
|
return qry.value( 0 ).toString();
|
|
}
|
|
else
|
|
{
|
|
return fieldValue;
|
|
}
|
|
}
|
|
|
|
|
|
bool QgsOracleProvider::addFeatures( QgsFeatureList &flist )
|
|
{
|
|
if ( flist.size() == 0 )
|
|
return true;
|
|
|
|
if ( mIsQuery )
|
|
return false;
|
|
|
|
bool returnvalue = true;
|
|
|
|
|
|
QSqlDatabase db( *mConnection );
|
|
|
|
try
|
|
{
|
|
QSqlQuery ins( db ), getfid( db );
|
|
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
// Prepare the INSERT statement
|
|
QString insert = QString( "INSERT INTO %1(" ).arg( mQuery );
|
|
QString values = ") VALUES (";
|
|
QString delim = "";
|
|
|
|
QStringList defaultValues;
|
|
QList<int> fieldId;
|
|
|
|
if ( !mGeometryColumn.isNull() )
|
|
{
|
|
insert += quotedIdentifier( mGeometryColumn );
|
|
values += "?";
|
|
delim = ",";
|
|
}
|
|
|
|
if ( mPrimaryKeyType == pktInt || mPrimaryKeyType == pktFidMap )
|
|
{
|
|
QString keys, kdelim = "";
|
|
|
|
Q_FOREACH ( int idx, mPrimaryKeyAttrs )
|
|
{
|
|
const QgsField &fld = field( idx );
|
|
insert += delim + quotedIdentifier( fld.name() );
|
|
keys += kdelim + quotedIdentifier( fld.name() );
|
|
values += delim + "?";
|
|
delim = ",";
|
|
kdelim = ",";
|
|
fieldId << idx;
|
|
defaultValues << defaultValue( idx ).toString();
|
|
}
|
|
|
|
if ( !getfid.prepare( QString( "SELECT %1 FROM %2 WHERE ROWID=?" ).arg( keys ).arg( mQuery ) ) )
|
|
{
|
|
throw OracleException( tr( "Could not prepare get feature id statement" ), getfid );
|
|
}
|
|
}
|
|
|
|
QgsAttributes attributevec = flist[0].attributes();
|
|
|
|
// look for unique attribute values to place in statement instead of passing as parameter
|
|
// e.g. for defaults
|
|
for ( int idx = 0; idx < qMin( attributevec.size(), mAttributeFields.size() ); ++idx )
|
|
{
|
|
QVariant v = attributevec[idx];
|
|
if ( !v.isValid() )
|
|
continue;
|
|
|
|
if ( fieldId.contains( idx ) )
|
|
continue;
|
|
|
|
const QgsField &fld = mAttributeFields[idx];
|
|
|
|
QgsDebugMsg( "Checking field against: " + fld.name() );
|
|
|
|
if ( fld.name().isEmpty() || fld.name() == mGeometryColumn )
|
|
continue;
|
|
|
|
int i;
|
|
for ( i = 1; i < flist.size(); i++ )
|
|
{
|
|
QgsAttributes attributevec = flist[i].attributes();
|
|
QVariant v2 = attributevec[idx];
|
|
|
|
if ( !v2.isValid() )
|
|
break;
|
|
|
|
if ( v2 != v )
|
|
break;
|
|
}
|
|
|
|
insert += delim + quotedIdentifier( fld.name() );
|
|
|
|
QString defVal = defaultValue( idx ).toString();
|
|
|
|
if ( i == flist.size() )
|
|
{
|
|
if ( v == defVal )
|
|
{
|
|
if ( defVal.isNull() )
|
|
{
|
|
values += delim + "NULL";
|
|
}
|
|
else
|
|
{
|
|
values += delim + defVal;
|
|
}
|
|
}
|
|
else if ( fld.typeName().endsWith( ".SDO_GEOMETRY" ) )
|
|
{
|
|
values += delim + "?";
|
|
}
|
|
else
|
|
{
|
|
values += delim + quotedValue( v, mAttributeFields[idx].type() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// value is not unique => add parameter
|
|
values += delim + "?";
|
|
defaultValues.append( defVal );
|
|
fieldId.append( idx );
|
|
}
|
|
|
|
delim = ",";
|
|
}
|
|
|
|
insert += values + ")";
|
|
|
|
QgsDebugMsgLevel( QString( "SQL prepare: %1" ).arg( insert ), 4 );
|
|
if ( !ins.prepare( insert ) )
|
|
{
|
|
throw OracleException( tr( "Could not prepare insert statement" ), ins );
|
|
}
|
|
|
|
for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); ++features )
|
|
{
|
|
QgsAttributes attributevec = features->attributes();
|
|
|
|
QgsDebugMsg( QString( "insert feature %1" ).arg( features->id() ) );
|
|
|
|
if ( !mGeometryColumn.isNull() )
|
|
{
|
|
appendGeomParam( features->geometry(), ins );
|
|
}
|
|
|
|
for ( int i = 0; i < fieldId.size(); i++ )
|
|
{
|
|
QVariant value = attributevec[ fieldId[i] ];
|
|
|
|
QString v;
|
|
if ( !value.isValid() )
|
|
{
|
|
const QgsField &fld = field( fieldId[i] );
|
|
v = paramValue( defaultValues[i], defaultValues[i] );
|
|
features->setAttribute( fieldId[i], convertValue( fld.type(), v ) );
|
|
}
|
|
else
|
|
{
|
|
v = paramValue( value.toString(), defaultValues[i] );
|
|
|
|
if ( v != value.toString() )
|
|
{
|
|
const QgsField &fld = field( fieldId[i] );
|
|
features->setAttribute( fieldId[i], convertValue( fld.type(), v ) );
|
|
}
|
|
}
|
|
|
|
QgsDebugMsgLevel( QString( "addBindValue: %1" ).arg( v ), 4 );
|
|
ins.addBindValue( v );
|
|
}
|
|
|
|
if ( !ins.exec() )
|
|
throw OracleException( tr( "Could not insert feature %1" ).arg( features->id() ), ins );
|
|
|
|
if ( mPrimaryKeyType == pktRowId )
|
|
{
|
|
features->setFeatureId( mShared->lookupFid( QList<QVariant>() << QVariant( ins.lastInsertId() ) ) );
|
|
QgsDebugMsgLevel( QString( "new fid=%1" ).arg( features->id() ), 4 );
|
|
}
|
|
else if ( mPrimaryKeyType == pktInt || mPrimaryKeyType == pktFidMap )
|
|
{
|
|
getfid.addBindValue( QVariant( ins.lastInsertId() ) );
|
|
if ( !getfid.exec() || !getfid.next() )
|
|
throw OracleException( tr( "Could not retrieve feature id %1" ).arg( features->id() ), getfid );
|
|
|
|
int col = 0;
|
|
Q_FOREACH ( int idx, mPrimaryKeyAttrs )
|
|
{
|
|
const QgsField &fld = field( idx );
|
|
|
|
QVariant v = getfid.value( col++ );
|
|
if ( v.type() != fld.type() )
|
|
v = QgsVectorDataProvider::convertValue( fld.type(), v.toString() );
|
|
features->setAttribute( idx, v );
|
|
}
|
|
}
|
|
}
|
|
|
|
ins.finish();
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
throw OracleException( tr( "Could not commit transaction" ), db );
|
|
}
|
|
|
|
// update feature ids
|
|
if ( mPrimaryKeyType == pktInt || mPrimaryKeyType == pktFidMap )
|
|
{
|
|
for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); ++features )
|
|
{
|
|
QgsAttributes attributevec = features->attributes();
|
|
|
|
if ( mPrimaryKeyType == pktInt )
|
|
{
|
|
features->setFeatureId( STRING_TO_FID( attributevec[ mPrimaryKeyAttrs[0] ] ) );
|
|
}
|
|
else
|
|
{
|
|
QList<QVariant> primaryKeyVals;
|
|
|
|
Q_FOREACH ( int idx, mPrimaryKeyAttrs )
|
|
{
|
|
primaryKeyVals << attributevec[ idx ];
|
|
}
|
|
|
|
features->setFeatureId( mShared->lookupFid( QVariant( primaryKeyVals ) ) );
|
|
}
|
|
QgsDebugMsgLevel( QString( "new fid=%1" ).arg( features->id() ), 4 );
|
|
}
|
|
}
|
|
|
|
mFeaturesCounted += flist.size();
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
QgsDebugMsg( QString( "Oracle error: %1" ).arg( e.errorMessage() ) );
|
|
pushError( tr( "Oracle error while adding features: %1" ).arg( e.errorMessage() ) );
|
|
if ( !db.rollback() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
}
|
|
returnvalue = false;
|
|
}
|
|
|
|
return returnvalue;
|
|
}
|
|
|
|
bool QgsOracleProvider::deleteFeatures( const QgsFeatureIds & id )
|
|
{
|
|
bool returnvalue = true;
|
|
|
|
if ( mIsQuery )
|
|
return false;
|
|
|
|
QSqlDatabase db( *mConnection );
|
|
|
|
try
|
|
{
|
|
QSqlQuery qry( db );
|
|
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it )
|
|
{
|
|
QString sql = QString( "DELETE FROM %1 WHERE %2" )
|
|
.arg( mQuery ).arg( whereClause( *it ) );
|
|
QgsDebugMsg( "delete sql: " + sql );
|
|
|
|
if ( !exec( qry, sql ) )
|
|
throw OracleException( tr( "Deletion of feature %1 failed" ).arg( *it ), qry );
|
|
|
|
mShared->removeFid( *it );
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
throw OracleException( tr( "Could not commit transaction" ), db );
|
|
}
|
|
|
|
mFeaturesCounted -= id.size();
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
pushError( tr( "Oracle error while deleting features: %1" ).arg( e.errorMessage() ) );
|
|
if ( !db.rollback() )
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
returnvalue = false;
|
|
}
|
|
|
|
return returnvalue;
|
|
}
|
|
|
|
bool QgsOracleProvider::addAttributes( const QList<QgsField> &attributes )
|
|
{
|
|
bool returnvalue = true;
|
|
|
|
if ( mIsQuery )
|
|
return false;
|
|
|
|
QSqlDatabase db( *mConnection );
|
|
|
|
try
|
|
{
|
|
QSqlQuery qry( db );
|
|
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
|
|
{
|
|
QString type = iter->typeName().toLower();
|
|
if ( type == "char" || type == "varchar2" )
|
|
{
|
|
type = QString( "%1(%2 char)" ).arg( type ).arg( iter->length() );
|
|
}
|
|
else if ( type == "number" )
|
|
{
|
|
if ( iter->precision() > 0 )
|
|
{
|
|
type = QString( "%1(%2,%3)" ).arg( type ).arg( iter->length() ).arg( iter->precision() );
|
|
}
|
|
else
|
|
{
|
|
type = QString( "%1(%2,%3)" ).arg( type ).arg( iter->length() );
|
|
}
|
|
}
|
|
|
|
QString sql = QString( "ALTER TABLE %1 ADD %2 %3" )
|
|
.arg( mQuery )
|
|
.arg( quotedIdentifier( iter->name() ) )
|
|
.arg( type );
|
|
QgsDebugMsg( sql );
|
|
|
|
if ( !exec( qry, sql ) )
|
|
throw OracleException( tr( "Adding attribute %1 failed" ).arg( iter->name() ), qry );
|
|
|
|
if ( !iter->comment().isEmpty() )
|
|
{
|
|
sql = QString( "COMMENT ON COLUMN %1.%2 IS %3" )
|
|
.arg( mQuery )
|
|
.arg( quotedIdentifier( iter->name() ) )
|
|
.arg( quotedValue( iter->comment() ) );
|
|
if ( !exec( qry, sql ) )
|
|
throw OracleException( tr( "Setting comment on %1 failed" ).arg( iter->name() ), qry );
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
}
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
throw OracleException( tr( "Could not commit transaction" ), db );
|
|
}
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
pushError( tr( "Oracle error while adding attributes: %1" ).arg( e.errorMessage() ) );
|
|
if ( !db.rollback() )
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
returnvalue = false;
|
|
}
|
|
|
|
if ( !loadFields() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not reload fields." ), tr( "Oracle" ) );
|
|
}
|
|
|
|
return returnvalue;
|
|
}
|
|
|
|
bool QgsOracleProvider::deleteAttributes( const QgsAttributeIds& ids )
|
|
{
|
|
bool returnvalue = true;
|
|
|
|
if ( mIsQuery )
|
|
return false;
|
|
|
|
QSqlDatabase db( *mConnection );
|
|
|
|
try
|
|
{
|
|
QSqlQuery qry( db );
|
|
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
QList<int> idsList = ids.values();
|
|
qSort( idsList.begin(), idsList.end(), qGreater<int>() );
|
|
|
|
Q_FOREACH ( int id, idsList )
|
|
{
|
|
const QgsField &fld = mAttributeFields.at( id );
|
|
|
|
QString sql = QString( "ALTER TABLE %1 DROP COLUMN %2" )
|
|
.arg( mQuery )
|
|
.arg( quotedIdentifier( fld.name() ) );
|
|
|
|
//send sql statement and do error handling
|
|
if ( !exec( qry, sql ) )
|
|
throw OracleException( tr( "Dropping column %1 failed" ).arg( fld.name() ), qry );
|
|
|
|
//delete the attribute from mAttributeFields
|
|
mAttributeFields.remove( id );
|
|
mDefaultValues.removeAt( id );
|
|
}
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
throw OracleException( tr( "Could not commit transaction" ), db );
|
|
}
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
pushError( tr( "Oracle error while deleting attributes: %1" ).arg( e.errorMessage() ) );
|
|
if ( !db.rollback() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
}
|
|
returnvalue = false;
|
|
}
|
|
|
|
if ( !loadFields() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not reload fields." ), tr( "Oracle" ) );
|
|
}
|
|
|
|
return returnvalue;
|
|
}
|
|
|
|
bool QgsOracleProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
|
|
{
|
|
bool returnvalue = true;
|
|
|
|
if ( mIsQuery )
|
|
return false;
|
|
|
|
if ( attr_map.isEmpty() )
|
|
return true;
|
|
|
|
QSqlDatabase db( *mConnection );
|
|
|
|
try
|
|
{
|
|
QSqlQuery qry( db );
|
|
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
// cycle through the features
|
|
for ( QgsChangedAttributesMap::const_iterator iter = attr_map.begin(); iter != attr_map.end(); ++iter )
|
|
{
|
|
QgsFeatureId fid = iter.key();
|
|
|
|
// skip added features
|
|
if ( FID_IS_NEW( fid ) )
|
|
continue;
|
|
|
|
const QgsAttributeMap& attrs = iter.value();
|
|
if ( attrs.isEmpty() )
|
|
continue;
|
|
|
|
QString sql = QString( "UPDATE %1 SET " ).arg( mQuery );
|
|
|
|
bool pkChanged = false;
|
|
QList<int> geometryParams;
|
|
|
|
// cycle through the changed attributes of the feature
|
|
QString delim;
|
|
for ( QgsAttributeMap::const_iterator siter = attrs.begin(); siter != attrs.end(); ++siter )
|
|
{
|
|
try
|
|
{
|
|
const QgsField &fld = field( siter.key() );
|
|
|
|
pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() );
|
|
|
|
sql += delim + QString( "%1=" ).arg( quotedIdentifier( fld.name() ) );
|
|
delim = ",";
|
|
|
|
if ( fld.typeName().endsWith( ".SDO_GEOMETRY" ) )
|
|
{
|
|
if ( mConnection->hasSpatial() )
|
|
sql += QString( "SDO_UTIL.FROM_WKTGEOMETRY(%1)" ).arg( quotedValue( siter->toString() ) );
|
|
else
|
|
{
|
|
geometryParams << siter.key();
|
|
sql += "?";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sql += quotedValue( *siter, fld.type() );
|
|
}
|
|
}
|
|
catch ( OracleFieldNotFound )
|
|
{
|
|
// Field was missing - shouldn't happen
|
|
}
|
|
}
|
|
|
|
sql += QString( " WHERE %1" ).arg( whereClause( fid ) );
|
|
|
|
if ( !qry.prepare( sql ) )
|
|
{
|
|
throw OracleException( tr( "Could not prepare update statement." ), qry );
|
|
}
|
|
|
|
Q_FOREACH ( int idx, geometryParams )
|
|
{
|
|
QgsGeometry *g;
|
|
if ( !attrs[idx].isNull() )
|
|
{
|
|
g = QgsGeometry::fromWkt( attrs[ idx ].toString() );
|
|
}
|
|
else
|
|
{
|
|
g = new QgsGeometry();
|
|
}
|
|
|
|
appendGeomParam( g, qry );
|
|
|
|
delete g;
|
|
}
|
|
|
|
if ( !qry.exec() )
|
|
throw OracleException( tr( "Update of feature %1 failed" ).arg( iter.key() ), qry );
|
|
|
|
qry.finish();
|
|
|
|
// update feature id map if key was changed
|
|
if ( pkChanged && mPrimaryKeyType == pktFidMap )
|
|
{
|
|
QVariant v = mShared->removeFid( fid );
|
|
|
|
QList<QVariant> k = v.toList();
|
|
|
|
for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ )
|
|
{
|
|
int idx = mPrimaryKeyAttrs[i];
|
|
if ( !attrs.contains( idx ) )
|
|
continue;
|
|
|
|
k[i] = attrs[ idx ];
|
|
}
|
|
|
|
mShared->insertFid( fid, k );
|
|
}
|
|
}
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
throw OracleException( tr( "Could not commit transaction" ), db );
|
|
}
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
pushError( tr( "Oracle error while changing attributes: %1" ).arg( e.errorMessage() ) );
|
|
if ( !db.rollback() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
}
|
|
returnvalue = false;
|
|
}
|
|
|
|
return returnvalue;
|
|
}
|
|
|
|
void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry ) const
|
|
{
|
|
QOCISpatialGeometry g;
|
|
|
|
wkbPtr ptr;
|
|
ptr.ucPtr = geom ? ( unsigned char * ) geom->asWkb() : 0;
|
|
g.isNull = !ptr.ucPtr;
|
|
g.gtype = -1;
|
|
g.srid = mSrid < 1 ? -1 : mSrid;
|
|
|
|
if ( !g.isNull )
|
|
{
|
|
ptr.ucPtr++; // skip endianness
|
|
|
|
g.eleminfo.clear();
|
|
g.ordinates.clear();
|
|
|
|
QString expr;
|
|
int iOrdinate = 1;
|
|
QGis::WkbType type = ( QGis::WkbType ) * ptr.iPtr++;
|
|
int dim = 2;
|
|
|
|
switch ( type )
|
|
{
|
|
case QGis::WKBPoint25D:
|
|
dim = 3;
|
|
case QGis::WKBPoint:
|
|
g.srid = mSrid;
|
|
g.gtype = SDO_GTYPE( dim, gtPoint );
|
|
g.x = *ptr.dPtr++;
|
|
g.y = *ptr.dPtr++;
|
|
g.z = dim == 3 ? *ptr.dPtr++ : 0.0;
|
|
break;
|
|
|
|
case QGis::WKBLineString25D:
|
|
case QGis::WKBMultiLineString25D:
|
|
dim = 3;
|
|
case QGis::WKBLineString:
|
|
case QGis::WKBMultiLineString:
|
|
{
|
|
g.gtype = SDO_GTYPE( dim, gtLine );
|
|
int nLines = 1;
|
|
if ( type == QGis::WKBMultiLineString25D || type == QGis::WKBMultiLineString )
|
|
{
|
|
g.gtype = SDO_GTYPE( dim, gtMultiLine );
|
|
nLines = *ptr.iPtr++;
|
|
ptr.ucPtr++; // Skip endianness of first linestring
|
|
ptr.iPtr++; // Skip type of first linestring
|
|
}
|
|
|
|
for ( int iLine = 0; iLine < nLines; iLine++ )
|
|
{
|
|
g.eleminfo << iOrdinate << 2 << 1;
|
|
|
|
for ( int i = 0, n = *ptr.iPtr++; i < n; i++ )
|
|
{
|
|
g.ordinates << *ptr.dPtr++;
|
|
g.ordinates << *ptr.dPtr++;
|
|
if ( dim == 3 )
|
|
g.ordinates << *ptr.dPtr++;
|
|
iOrdinate += dim;
|
|
}
|
|
|
|
ptr.ucPtr++; // Skip endianness of next linestring
|
|
ptr.iPtr++; // Skip type of next linestring
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QGis::WKBPolygon25D:
|
|
case QGis::WKBMultiPolygon25D:
|
|
dim = 3;
|
|
case QGis::WKBPolygon:
|
|
case QGis::WKBMultiPolygon:
|
|
{
|
|
g.gtype = SDO_GTYPE( dim, gtPolygon );
|
|
int nPolygons = 1;
|
|
if ( type == QGis::WKBMultiPolygon25D || type == QGis::WKBMultiPolygon )
|
|
{
|
|
g.gtype = SDO_GTYPE( dim, gtMultiPolygon );
|
|
nPolygons = *ptr.iPtr++;
|
|
|
|
ptr.ucPtr++; // Skip endianness of first polygon
|
|
ptr.iPtr++; // Skip type of first polygon
|
|
}
|
|
|
|
for ( int iPolygon = 0; iPolygon < nPolygons; iPolygon++ )
|
|
{
|
|
for ( int iRing = 0, nRings = *ptr.iPtr++; iRing < nRings; iRing++ )
|
|
{
|
|
g.eleminfo << iOrdinate << ( iRing == 0 ? 1003 : 2003 ) << 1;
|
|
|
|
// TODO: verify ring orientation
|
|
for ( int i = 0, n = *ptr.iPtr++; i < n; i++ )
|
|
{
|
|
g.ordinates << *ptr.dPtr++;
|
|
g.ordinates << *ptr.dPtr++;
|
|
if ( dim == 3 )
|
|
g.ordinates << *ptr.dPtr++;
|
|
iOrdinate += dim;
|
|
}
|
|
}
|
|
|
|
ptr.ucPtr++; // Skip endianness of next polygon
|
|
ptr.iPtr++; // Skip type of next polygon
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QGis::WKBMultiPoint25D:
|
|
dim = 3;
|
|
case QGis::WKBMultiPoint:
|
|
{
|
|
g.gtype = SDO_GTYPE( dim, gtMultiPoint );
|
|
int n = *ptr.iPtr++;
|
|
|
|
g.eleminfo << 1 << 1 << n;
|
|
|
|
for ( int i = 0; i < n; i++ )
|
|
{
|
|
ptr.ucPtr++; // Skip endianness of point
|
|
ptr.iPtr++; // Skip type of point
|
|
|
|
g.ordinates << *ptr.dPtr++;
|
|
g.ordinates << *ptr.dPtr++;
|
|
if ( dim == 3 )
|
|
g.ordinates << *ptr.dPtr++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QGis::WKBUnknown:
|
|
case QGis::WKBNoGeometry:
|
|
g.isNull = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QgsDebugMsgLevel( QString( "addBindValue geometry: isNull=%1 gtype=%2 srid=%3 p=%4,%5,%6 eleminfo=%7 ordinates=%8" )
|
|
.arg( g.isNull )
|
|
.arg( g.gtype )
|
|
.arg( g.srid )
|
|
.arg( g.x ).arg( g.y ).arg( g.z )
|
|
.arg( g.eleminfo.size() )
|
|
.arg( g.ordinates.size() )
|
|
, 4 );
|
|
qry.addBindValue( QVariant::fromValue( g ) );
|
|
}
|
|
|
|
bool QgsOracleProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
|
|
{
|
|
QgsDebugMsg( "entering." );
|
|
|
|
if ( mIsQuery || mGeometryColumn.isNull() )
|
|
return false;
|
|
|
|
QSqlDatabase db( *mConnection );
|
|
|
|
bool returnvalue = true;
|
|
|
|
try
|
|
{
|
|
QSqlQuery qry( db );
|
|
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
QString update = QString( "UPDATE %1 SET %2=? WHERE %3" )
|
|
.arg( mQuery )
|
|
.arg( quotedIdentifier( mGeometryColumn ) )
|
|
.arg( pkParamWhereClause() );
|
|
QgsDebugMsgLevel( QString( "SQL prepare: %1" ).arg( update ), 4 );
|
|
if ( !qry.prepare( update ) )
|
|
{
|
|
throw OracleException( tr( "Could not prepare update statement." ), qry );
|
|
}
|
|
|
|
for ( QgsGeometryMap::const_iterator iter = geometry_map.constBegin();
|
|
iter != geometry_map.constEnd();
|
|
++iter )
|
|
{
|
|
appendGeomParam( &iter.value(), qry );
|
|
appendPkParams( iter.key(), qry );
|
|
|
|
if ( !qry.exec() )
|
|
throw OracleException( tr( "Update of feature %1 failed" ).arg( iter.key() ), qry );
|
|
}
|
|
|
|
qry.finish();
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
throw OracleException( tr( "Could not commit transaction" ), db );
|
|
}
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
pushError( tr( "Oracle error while changing geometry values: %1" ).arg( e.errorMessage() ) );
|
|
if ( !db.rollback() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
}
|
|
returnvalue = false;
|
|
}
|
|
|
|
QgsDebugMsg( "exiting." );
|
|
|
|
return returnvalue;
|
|
}
|
|
|
|
int QgsOracleProvider::capabilities() const
|
|
{
|
|
return mEnabledCapabilities;
|
|
}
|
|
|
|
bool QgsOracleProvider::setSubsetString( const QString& theSQL, bool updateFeatureCount )
|
|
{
|
|
QString prevWhere = mSqlWhereClause;
|
|
|
|
mSqlWhereClause = theSQL.trimmed();
|
|
|
|
QString sql = QString( "SELECT * FROM %1 WHERE " ).arg( mQuery );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
sql += "(" + mSqlWhereClause + ") AND ";
|
|
}
|
|
|
|
sql += "1=0";
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
if ( !exec( qry, sql ) )
|
|
{
|
|
pushError( qry.lastError().text() );
|
|
mSqlWhereClause = prevWhere;
|
|
qry.finish();
|
|
return false;
|
|
}
|
|
qry.finish();
|
|
|
|
if ( mPrimaryKeyType == pktInt && !uniqueData( mQuery, mAttributeFields[ mPrimaryKeyAttrs[0] ].name() ) )
|
|
{
|
|
mSqlWhereClause = prevWhere;
|
|
return false;
|
|
}
|
|
|
|
// Update datasource uri too
|
|
mUri.setSql( theSQL );
|
|
// Update yet another copy of the uri. Why are there 3 copies of the
|
|
// uri? Perhaps this needs some rationalisation.....
|
|
setDataSourceUri( mUri.uri() );
|
|
|
|
if ( updateFeatureCount )
|
|
{
|
|
mFeaturesCounted = -1;
|
|
}
|
|
mLayerExtent.setMinimal();
|
|
|
|
emit dataChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return the feature count
|
|
*/
|
|
long QgsOracleProvider::featureCount() const
|
|
{
|
|
if ( mFeaturesCounted >= 0 )
|
|
return mFeaturesCounted;
|
|
|
|
// get total number of features
|
|
QString sql;
|
|
|
|
// use estimated metadata even when there is a where clause,
|
|
// although we get an incorrect feature count for the subset
|
|
// - but make huge dataset usable.
|
|
if ( !mIsQuery && mUseEstimatedMetadata )
|
|
{
|
|
sql = QString( "SELECT num_rows FROM all_tables WHERE owner=%1 AND table_name=%2" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) );
|
|
}
|
|
else
|
|
{
|
|
sql = QString( "SELECT count(*) FROM %1" ).arg( mQuery );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
sql += " WHERE " + mSqlWhereClause;
|
|
}
|
|
}
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
if ( exec( qry, sql ) && qry.next() )
|
|
{
|
|
mFeaturesCounted = qry.value( 0 ).toInt();
|
|
}
|
|
qry.finish();
|
|
|
|
QgsDebugMsg( "number of features: " + QString::number( mFeaturesCounted ) );
|
|
|
|
return mFeaturesCounted;
|
|
}
|
|
|
|
QgsRectangle QgsOracleProvider::extent()
|
|
{
|
|
if ( mGeometryColumn.isNull() )
|
|
return QgsRectangle();
|
|
|
|
if ( mLayerExtent.isEmpty() )
|
|
{
|
|
QString sql;
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
if ( mUseEstimatedMetadata )
|
|
{
|
|
if ( exec( qry, QString( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=%1 AND table_name=%2 AND column_name=%3 AND sdo_dimname='X'" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) ) ) && qry.next() )
|
|
{
|
|
mLayerExtent.setXMinimum( qry.value( 0 ).toDouble() );
|
|
mLayerExtent.setXMaximum( qry.value( 1 ).toDouble() );
|
|
|
|
if ( exec( qry, QString( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=%1 AND table_name=%2 AND column_name=%3 AND sdo_dimname='Y'" )
|
|
.arg( quotedValue( mOwnerName ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) ) ) && qry.next() )
|
|
{
|
|
mLayerExtent.setYMinimum( qry.value( 0 ).toDouble() );
|
|
mLayerExtent.setYMaximum( qry.value( 1 ).toDouble() );
|
|
return mLayerExtent;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
if ( mHasSpatialIndex && ( mUseEstimatedMetadata || mSqlWhereClause.isEmpty() ) )
|
|
{
|
|
sql = QString( "SELECT SDO_TUNE.EXTENT_OF(%1,%2) FROM dual" )
|
|
.arg( quotedValue( QString( "%1.%2" ).arg( mOwnerName ).arg( mTableName ) ) )
|
|
.arg( quotedValue( mGeometryColumn ) );
|
|
|
|
ok = exec( qry, sql );
|
|
}
|
|
|
|
if ( !ok )
|
|
{
|
|
sql = QString( "SELECT SDO_AGGR_MBR(%1) FROM %2" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( mQuery );
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
sql += QString( " WHERE %1" ).arg( mSqlWhereClause );
|
|
|
|
ok = exec( qry, sql );
|
|
}
|
|
|
|
if ( ok && qry.next() )
|
|
{
|
|
QByteArray *ba = static_cast<QByteArray*>( qry.value( 0 ).data() );
|
|
unsigned char *copy = new unsigned char[ba->size()];
|
|
memcpy( copy, ba->constData(), ba->size() );
|
|
QgsGeometry g;
|
|
g.fromWkb( copy, ba->size() ); // take ownership
|
|
mLayerExtent = g.boundingBox();
|
|
QgsDebugMsg( "extent: " + mLayerExtent.toString() );
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not retrieve extents: %1\nSQL: %2" ).arg( qry.lastError().text() ).arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
|
|
return mLayerExtent;
|
|
}
|
|
|
|
void QgsOracleProvider::updateExtents()
|
|
{
|
|
mLayerExtent.setMinimal();
|
|
}
|
|
|
|
bool QgsOracleProvider::getGeometryDetails()
|
|
{
|
|
if ( mGeometryColumn.isNull() )
|
|
{
|
|
mDetectedGeomType = QGis::WKBNoGeometry;
|
|
mValid = true;
|
|
return true;
|
|
}
|
|
|
|
QString ownerName = mOwnerName;
|
|
QString tableName = mTableName;
|
|
QString geomCol = mGeometryColumn;
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
if ( mIsQuery )
|
|
{
|
|
if ( !exec( qry, QString( "SELECT %1 FROM %2 WHERE 1=0" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( mQuery ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not execute query.\nThe error message from the database was:\n%1.\nSQL: %2" )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
mValid = false;
|
|
return false;
|
|
}
|
|
|
|
ownerName = "";
|
|
tableName = mQuery;
|
|
}
|
|
|
|
int detectedSrid = -1;
|
|
QGis::WkbType detectedType = QGis::WKBUnknown;
|
|
mHasSpatialIndex = false;
|
|
|
|
if ( mIsQuery )
|
|
{
|
|
detectedSrid = mSrid;
|
|
detectedType = mRequestedGeomType;
|
|
}
|
|
|
|
if ( !ownerName.isEmpty() )
|
|
{
|
|
if ( exec( qry, QString( "SELECT srid FROM mdsys.all_sdo_geom_metadata WHERE owner=%1 AND table_name=%2 AND column_name=%3" )
|
|
.arg( quotedValue( ownerName ) )
|
|
.arg( quotedValue( tableName ) )
|
|
.arg( quotedValue( geomCol ) ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
{
|
|
detectedSrid = qry.value( 0 ).toInt();
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not retrieve SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" )
|
|
.arg( mQuery )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not determine SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" )
|
|
.arg( mQuery )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
}
|
|
|
|
if ( exec( qry, QString( mUseEstimatedMetadata
|
|
? "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE rownum<1000) WHERE rownum<=2"
|
|
: "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
{
|
|
detectedType = QgsOracleConn::wkbTypeFromDatabase( qry.value( 0 ).toInt() );
|
|
if ( qry.next() )
|
|
{
|
|
detectedType = QGis::WKBUnknown;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
detectedType = QGis::WKBUnknown;
|
|
QgsMessageLog::logMessage( tr( "%1 has no valid geometry types.\nSQL: %2" )
|
|
.arg( mQuery )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not determine geometry type of %1.\nThe error message from the database was:\n%2.\nSQL: %3" )
|
|
.arg( mQuery )
|
|
.arg( qry.lastError().text() )
|
|
.arg( qry.lastQuery() ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
|
|
if ( detectedType == QGis::WKBUnknown || detectedSrid <= 0 )
|
|
{
|
|
QgsOracleLayerProperty layerProperty;
|
|
|
|
if ( !mIsQuery )
|
|
{
|
|
layerProperty.ownerName = ownerName;
|
|
layerProperty.tableName = tableName;
|
|
layerProperty.geometryColName = mGeometryColumn;
|
|
layerProperty.types << detectedType;
|
|
layerProperty.srids << detectedSrid;
|
|
|
|
QString delim = "";
|
|
|
|
if ( !mSqlWhereClause.isEmpty() )
|
|
{
|
|
layerProperty.sql += delim + "(" + mSqlWhereClause + ")";
|
|
delim = " AND ";
|
|
}
|
|
|
|
mConnection->retrieveLayerTypes( layerProperty, mUseEstimatedMetadata, false );
|
|
|
|
Q_ASSERT( layerProperty.types.size() == layerProperty.srids.size() );
|
|
}
|
|
|
|
if ( layerProperty.types.isEmpty() )
|
|
{
|
|
// no data - so take what's requested
|
|
if ( mRequestedGeomType == QGis::WKBUnknown )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Geometry type and srid for empty column %1 of %2 undefined." ).arg( mGeometryColumn ).arg( mQuery ) );
|
|
}
|
|
|
|
detectedType = QGis::WKBUnknown;
|
|
detectedSrid = -1;
|
|
}
|
|
else
|
|
{
|
|
// requested type && srid is available
|
|
if ( mRequestedGeomType == QGis::WKBUnknown || layerProperty.types.contains( mRequestedGeomType ) )
|
|
{
|
|
if ( layerProperty.size() == 1 )
|
|
{
|
|
// only what we requested is available
|
|
detectedType = layerProperty.types.at( 0 );
|
|
detectedSrid = layerProperty.srids.at( 0 );
|
|
}
|
|
else
|
|
{
|
|
// we need to filter
|
|
detectedType = QGis::WKBUnknown;
|
|
detectedSrid = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// geometry type undetermined or not unrequested
|
|
QgsMessageLog::logMessage( tr( "Feature type or srid for %1 of %2 could not be determined or was not requested." ).arg( mGeometryColumn ).arg( mQuery ) );
|
|
detectedType = QGis::WKBUnknown;
|
|
detectedSrid = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
mDetectedGeomType = detectedType;
|
|
if ( detectedSrid != -1 )
|
|
mSrid = detectedSrid;
|
|
|
|
QgsDebugMsg( QString( "Detected Oracle SRID is %1" ).arg( mSrid ) );
|
|
QgsDebugMsg( QString( "Detected type is %1" ).arg( mDetectedGeomType ) );
|
|
QgsDebugMsg( QString( "Requested type is %1" ).arg( mRequestedGeomType ) );
|
|
|
|
mValid = ( mDetectedGeomType != QGis::WKBUnknown || mRequestedGeomType != QGis::WKBUnknown );
|
|
|
|
if ( !mValid )
|
|
return false;
|
|
|
|
|
|
// store whether the geometry includes measure value
|
|
if ( detectedType == QGis::WKBPoint25D || detectedType == QGis::WKBMultiPoint25D ||
|
|
detectedType == QGis::WKBLineString25D || detectedType == QGis::WKBMultiLineString25D ||
|
|
detectedType == QGis::WKBPolygon25D || detectedType == QGis::WKBMultiPolygon25D )
|
|
{
|
|
// explicitly disable adding new features and editing of geometries
|
|
// as this would lead to corruption of measures
|
|
QgsMessageLog::logMessage( tr( "Editing and adding disabled for 2D+ layer (%1; %2)" ).arg( mGeometryColumn ).arg( mQuery ) );
|
|
mEnabledCapabilities &= ~( QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::AddFeatures );
|
|
}
|
|
|
|
QgsDebugMsg( QString( "Feature type name is %1" ).arg( QGis::featureType( geometryType() ) ) );
|
|
|
|
return mValid;
|
|
}
|
|
|
|
bool QgsOracleProvider::createSpatialIndex()
|
|
{
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
if ( !crs().geographicFlag() )
|
|
{
|
|
// TODO: make precision configurable
|
|
QgsRectangle r( extent() );
|
|
if ( !exec( qry, QString( "UPDATE mdsys.user_sdo_geom_metadata SET diminfo=mdsys.sdo_dim_array("
|
|
"mdsys.sdo_dim_element('X', %1, %2, 0.001),"
|
|
"mdsys.sdo_dim_element('Y', %3, %4, 0.001)"
|
|
") WHERE table_name=%5 AND column_name=%6" )
|
|
.arg( qgsDoubleToString( r.xMinimum() ) ).arg( qgsDoubleToString( r.xMaximum() ) )
|
|
.arg( qgsDoubleToString( r.yMinimum() ) ).arg( qgsDoubleToString( r.yMaximum() ) )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) ) )
|
|
)
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not update metadata for %1.%2.\nSQL:%3\nError: %4" )
|
|
.arg( mTableName )
|
|
.arg( mGeometryColumn )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
if ( qry.numRowsAffected() == 0 )
|
|
{
|
|
if ( !exec( qry, QString( "INSERT INTO mdsys.user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (%1,%2,%3,mdsys.sdo_dim_array("
|
|
"mdsys.sdo_dim_element('X', %4, %5, 0.001),"
|
|
"mdsys.sdo_dim_element('Y', %6, %7, 0.001)"
|
|
"))" )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) )
|
|
.arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) )
|
|
.arg( qgsDoubleToString( r.xMinimum() ) ).arg( qgsDoubleToString( r.xMaximum() ) )
|
|
.arg( qgsDoubleToString( r.yMinimum() ) ).arg( qgsDoubleToString( r.yMaximum() ) )
|
|
) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not insert metadata for %1.%2.\nSQL:%3\nError: %4" )
|
|
.arg( quotedValue( mTableName ) )
|
|
.arg( quotedValue( mGeometryColumn ) )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( "geographic CRS" );
|
|
}
|
|
|
|
if ( !mHasSpatialIndex )
|
|
{
|
|
int n = 0;
|
|
if ( exec( qry, QString( "SELECT coalesce(substr(max(index_name),10),'0') FROM all_indexes WHERE index_name LIKE 'QGIS_IDX_%' ESCAPE '#' ORDER BY index_name" ) ) &&
|
|
qry.next() )
|
|
{
|
|
n = qry.value( 0 ).toInt() + 1;
|
|
}
|
|
|
|
if ( !exec( qry, QString( "CREATE INDEX QGIS_IDX_%1 ON %2.%3(%4) INDEXTYPE IS MDSYS.SPATIAL_INDEX PARALLEL" )
|
|
.arg( n, 10, 10, QChar( '0' ) )
|
|
.arg( quotedIdentifier( mOwnerName ) )
|
|
.arg( quotedIdentifier( mTableName ) )
|
|
.arg( quotedIdentifier( mGeometryColumn ) ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Creation spatial index failed.\nSQL:%1\nError: %2" )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
|
|
mSpatialIndexName = QString( "QGIS_IDX_%1" ).arg( n, 10, 10, QChar( '0' ) );
|
|
}
|
|
else
|
|
{
|
|
if ( !exec( qry, QString( "ALTER INDEX %1 REBUILD" ).arg( mSpatialIndexName ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Rebuild of spatial index failed.\nSQL:%1\nError: %2" )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsOracleProvider::convertField( QgsField &field )
|
|
{
|
|
QString fieldType = "VARCHAR2(2047)"; //default to string
|
|
int fieldSize = field.length();
|
|
int fieldPrec = field.precision();
|
|
switch ( field.type() )
|
|
{
|
|
case QVariant::LongLong:
|
|
fieldType = "NUMBER(20,0)";
|
|
fieldSize = -1;
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::DateTime:
|
|
case QVariant::Time:
|
|
case QVariant::String:
|
|
fieldType = "VARCHAR2(2047)";
|
|
fieldPrec = -1;
|
|
break;
|
|
|
|
case QVariant::Date:
|
|
fieldType = "DATE";
|
|
fieldPrec = -1;
|
|
break;
|
|
|
|
case QVariant::Int:
|
|
fieldType = "NUMBER(10,0)";
|
|
fieldSize = -1;
|
|
fieldPrec = 0;
|
|
break;
|
|
|
|
case QVariant::Double:
|
|
if ( fieldSize <= 0 || fieldPrec <= 0 )
|
|
{
|
|
fieldType = "BINARY_DOUBLE";
|
|
fieldSize = -1;
|
|
fieldPrec = -1;
|
|
}
|
|
else
|
|
{
|
|
fieldType = QString( "NUMBER(%1,%2)" ).arg( fieldSize ).arg( fieldPrec );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
field.setTypeName( fieldType );
|
|
field.setLength( fieldSize );
|
|
field.setPrecision( fieldPrec );
|
|
return true;
|
|
}
|
|
|
|
QgsVectorLayerImport::ImportError QgsOracleProvider::createEmptyLayer(
|
|
const QString& uri,
|
|
const QgsFields &fields,
|
|
QGis::WkbType wkbType,
|
|
const QgsCoordinateReferenceSystem *srs,
|
|
bool overwrite,
|
|
QMap<int, int> *oldToNewAttrIdxMap,
|
|
QString *errorMessage,
|
|
const QMap<QString, QVariant> *options )
|
|
{
|
|
Q_UNUSED( wkbType );
|
|
Q_UNUSED( options );
|
|
|
|
// populate members from the uri structure
|
|
QgsDataSourceURI dsUri( uri );
|
|
QString ownerName = dsUri.schema();
|
|
|
|
QgsDebugMsg( QString( "Connection info is: %1" ).arg( dsUri.connectionInfo() ) );
|
|
|
|
// create the table
|
|
QgsOracleConn *conn = QgsOracleConn::connectDb( dsUri );
|
|
if ( !conn )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Connection to database failed" );
|
|
return QgsVectorLayerImport::ErrConnectionFailed;
|
|
}
|
|
|
|
if ( ownerName.isEmpty() )
|
|
{
|
|
ownerName = conn->currentUser();
|
|
}
|
|
|
|
if ( ownerName.isEmpty() )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "No owner name found" );
|
|
return QgsVectorLayerImport::ErrInvalidLayer;
|
|
}
|
|
|
|
QString tableName = dsUri.table();
|
|
QString geometryColumn = dsUri.geometryColumn();
|
|
|
|
QString primaryKey = dsUri.keyColumn();
|
|
QString primaryKeyType;
|
|
|
|
QString ownerTableName = quotedIdentifier( ownerName ) + "." + quotedIdentifier( tableName );
|
|
|
|
QgsDebugMsg( QString( "Geometry column is: %1" ).arg( geometryColumn ) );
|
|
QgsDebugMsg( QString( "Owner is: %1" ).arg( ownerName ) );
|
|
QgsDebugMsg( QString( "Table name is: %1" ).arg( tableName ) );
|
|
|
|
// get the pk's name and type
|
|
|
|
// if no pk name was passed, define the new pk field name
|
|
if ( primaryKey.isEmpty() )
|
|
{
|
|
int index = 0;
|
|
QString pk = primaryKey = "id";
|
|
while ( fields.indexFromName( primaryKey ) >= 0 )
|
|
{
|
|
primaryKey = QString( "%1_%2" ).arg( pk ).arg( index++ );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int idx = fields.indexFromName( primaryKey );
|
|
if ( idx >= 0 )
|
|
{
|
|
QgsField fld = fields[idx];
|
|
if ( convertField( fld ) )
|
|
{
|
|
primaryKeyType = fld.typeName();
|
|
}
|
|
}
|
|
}
|
|
|
|
QSqlDatabase db( *conn );
|
|
QSqlQuery qry( db );
|
|
bool created = false;
|
|
try
|
|
{
|
|
if ( !db.transaction() )
|
|
{
|
|
throw OracleException( tr( "Could not start transaction" ), db );
|
|
}
|
|
|
|
if ( !exec( qry, QString( "SELECT 1 FROM all_tables WHERE owner=%1 AND table_name=%2" )
|
|
.arg( quotedValue( ownerName ) )
|
|
.arg( quotedValue( tableName ) )
|
|
) )
|
|
{
|
|
throw OracleException( tr( "Could not determine table existence." ), qry );
|
|
}
|
|
|
|
bool exists = qry.next();
|
|
|
|
if ( exists )
|
|
{
|
|
if ( overwrite )
|
|
{
|
|
// delete the table if exists, then re-create it
|
|
if ( !exec( qry, QString( "DROP TABLE %1" ).arg( ownerTableName ) ) )
|
|
{
|
|
throw OracleException( tr( "Table %1 could not be dropped." ).arg( ownerTableName ), qry );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw OracleException( tr( "Table %1 already exists." ).arg( ownerTableName ), qry );
|
|
}
|
|
}
|
|
|
|
QString sql = QString( "CREATE TABLE %1(" ).arg( ownerTableName );
|
|
QString delim;
|
|
|
|
if ( !primaryKey.isEmpty() && !primaryKeyType.isEmpty() )
|
|
{
|
|
sql += QString( "%1 %2 PRIMARY KEY" ).arg( quotedIdentifier( primaryKey ) ).arg( primaryKeyType );
|
|
delim = ",";
|
|
}
|
|
|
|
// create geometry column
|
|
sql += QString( "%1%2 MDSYS.SDO_GEOMETRY)" ).arg( delim ).arg( quotedIdentifier( geometryColumn ) );
|
|
delim = ",";
|
|
|
|
if ( !exec( qry, sql ) )
|
|
{
|
|
throw OracleException( tr( "Table creation failed." ), qry );
|
|
}
|
|
|
|
created = true;
|
|
|
|
// TODO: make precision configurable
|
|
QString diminfo;
|
|
if ( srs->geographicFlag() )
|
|
{
|
|
diminfo = "mdsys.sdo_dim_array("
|
|
"mdsys.sdo_dim_element('Longitude', -180, 180, 0.001),"
|
|
"mdsys.sdo_dim_element('Latitude', -90, 90, 0.001)"
|
|
")";
|
|
}
|
|
else
|
|
{
|
|
diminfo = "mdsys.sdo_dim_array("
|
|
"mdsys.sdo_dim_element('X', NULL, NULL, 0.001),"
|
|
"mdsys.sdo_dim_element('Y', NULL, NULL, 0.001)"
|
|
")";
|
|
}
|
|
|
|
int srid = 0;
|
|
QStringList parts = srs->authid().split( ":" );
|
|
if ( parts.size() == 2 )
|
|
{
|
|
// apparently some EPSG codes don't have the auth_name setup in cs_srs
|
|
if ( !exec( qry, QString( "SELECT srid FROM mdsys.cs_srs WHERE coalesce(auth_name,'EPSG')=%1 AND auth_srid=%2" )
|
|
.arg( quotedValue( parts[0] ) )
|
|
.arg( quotedValue( parts[1] ) ) ) )
|
|
{
|
|
throw OracleException( tr( "Could not lookup authid %1:%2" ).arg( parts[0] ).arg( parts[1] ), qry );
|
|
}
|
|
|
|
if ( qry.next() )
|
|
{
|
|
srid = qry.value( 0 ).toInt();
|
|
}
|
|
}
|
|
|
|
if ( srid == 0 )
|
|
{
|
|
QgsDebugMsg( QString( "%1:%2 not found in mdsys.cs_srs - trying WKT" ).arg( parts[0] ).arg( parts[1] ) );
|
|
|
|
QString wkt = srs->toWkt();
|
|
if ( !exec( qry, QString( "SELECT srid FROM mdsys.cs_srs WHERE wktext=%1" ).arg( quotedValue( wkt ) ) ) )
|
|
{
|
|
throw OracleException( tr( "Could not lookup WKT." ), qry );
|
|
}
|
|
|
|
if ( qry.next() )
|
|
{
|
|
srid = qry.value( 0 ).toInt();
|
|
}
|
|
else
|
|
{
|
|
if ( !exec( qry, "SELECT max(srid)+1 FROM sdo_coord_ref_system" ) || !qry.next() )
|
|
{
|
|
throw OracleException( tr( "Could not determine new srid." ), qry );
|
|
}
|
|
|
|
srid = qry.value( 0 ).toInt();
|
|
|
|
QString sql;
|
|
|
|
if ( !exec( qry, QString( "INSERT"
|
|
" INTO sdo_coord_ref_system(srid,coord_ref_sys_name,coord_ref_sys_kind,legacy_wktext,is_valid,is_legacy,information_source)"
|
|
" VALUES (%1,%2,%3,%4,'TRUE','TRUE','GDAL/OGR via QGIS')" )
|
|
.arg( srid )
|
|
.arg( quotedValue( srs->description() ) )
|
|
.arg( quotedValue( srs->geographicFlag() ? "GEOGRAPHIC2D" : "PROJECTED" ) )
|
|
.arg( quotedValue( wkt ) ) ) )
|
|
{
|
|
throw OracleException( tr( "CRS not found and could not be created." ), qry );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !exec( qry, QString( "INSERT INTO mdsys.user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (%1,%2,%3,%4)" )
|
|
.arg( quotedValue( tableName.toUpper() ) )
|
|
.arg( quotedValue( geometryColumn.toUpper() ) )
|
|
.arg( srid )
|
|
.arg( diminfo ) ) )
|
|
{
|
|
throw OracleException( tr( "Could not insert metadata." ), qry );
|
|
}
|
|
|
|
if ( !db.commit() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
catch ( OracleException &e )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Creation of data source %1 failed: \n%2" )
|
|
.arg( ownerTableName )
|
|
.arg( e.errorMessage() );
|
|
|
|
if ( db.rollback() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) );
|
|
}
|
|
|
|
if ( created )
|
|
{
|
|
if ( !exec( qry, QString( "DROP TABLE %1" ).arg( ownerTableName ) ) )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Drop created table %1 failed.\nSQL:%2\nError: %3" )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
|
|
conn->disconnect();
|
|
|
|
return QgsVectorLayerImport::ErrCreateLayer;
|
|
}
|
|
|
|
conn->disconnect();
|
|
|
|
QgsDebugMsg( QString( "layer %1 created" ).arg( ownerTableName ) );
|
|
|
|
// use the provider to edit the table
|
|
dsUri.setDataSource( ownerName, tableName, geometryColumn, QString(), primaryKey );
|
|
QgsOracleProvider *provider = new QgsOracleProvider( dsUri.uri() );
|
|
if ( !provider->isValid() )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Loading of the layer %1 failed" ).arg( ownerTableName );
|
|
|
|
delete provider;
|
|
return QgsVectorLayerImport::ErrInvalidLayer;
|
|
}
|
|
|
|
QgsDebugMsg( "layer loaded" );
|
|
|
|
// add fields to the layer
|
|
if ( oldToNewAttrIdxMap )
|
|
oldToNewAttrIdxMap->clear();
|
|
|
|
if ( fields.size() > 0 )
|
|
{
|
|
int offset = 0;
|
|
|
|
QSet<QString> names;
|
|
QList<QgsField> launderedFields;
|
|
for ( int i = 0; i < fields.size(); i++ )
|
|
{
|
|
QgsField fld = fields[i];
|
|
|
|
QString name = fld.name().left( 30 ).toUpper();
|
|
|
|
if ( names.contains( name ) )
|
|
{
|
|
int j;
|
|
int n;
|
|
for ( j = 1, n = 10; j < 3; j++, n *= 10 )
|
|
{
|
|
int k;
|
|
for ( k = 0; k < n && names.contains( name ); k++ )
|
|
{
|
|
name = QString( "%1%2" ).arg( name.left( 30 - j ) ).arg( k, j, 10, QChar( '0' ) );
|
|
}
|
|
|
|
if ( k < n )
|
|
break;
|
|
}
|
|
|
|
if ( j == 3 )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Field name clash found (%1 not remappable)" ).arg( fld.name() );
|
|
|
|
delete provider;
|
|
return QgsVectorLayerImport::ErrAttributeTypeUnsupported;
|
|
}
|
|
}
|
|
|
|
if ( fld.name() == geometryColumn )
|
|
geometryColumn = name;
|
|
|
|
fld.setName( name );
|
|
launderedFields << fld;
|
|
names << name;
|
|
}
|
|
|
|
// get the list of fields
|
|
QList<QgsField> flist;
|
|
for ( int i = 0; i < launderedFields.size(); i++ )
|
|
{
|
|
QgsField fld = launderedFields[i];
|
|
|
|
if ( fld.name() == primaryKey )
|
|
{
|
|
oldToNewAttrIdxMap->insert( i, 0 );
|
|
continue;
|
|
}
|
|
|
|
if ( fld.name() == geometryColumn )
|
|
{
|
|
QgsDebugMsg( "Found a field with the same name of the geometry column. Skip it!" );
|
|
continue;
|
|
}
|
|
|
|
if ( !convertField( fld ) )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Unsupported type for field %1" ).arg( fld.name() );
|
|
|
|
delete provider;
|
|
return QgsVectorLayerImport::ErrAttributeTypeUnsupported;
|
|
}
|
|
|
|
QgsDebugMsg( QString( "Field #%1 name %2 type %3 typename %4 width %5 precision %6" )
|
|
.arg( i )
|
|
.arg( fld.name() ).arg( QVariant::typeToName( fld.type() ) ).arg( fld.typeName() )
|
|
.arg( fld.length() ).arg( fld.precision() ) );
|
|
|
|
flist.append( fld );
|
|
if ( oldToNewAttrIdxMap )
|
|
oldToNewAttrIdxMap->insert( i, offset++ );
|
|
}
|
|
|
|
if ( !provider->addAttributes( flist ) )
|
|
{
|
|
if ( errorMessage )
|
|
*errorMessage = QObject::tr( "Creation of fields failed" );
|
|
|
|
delete provider;
|
|
return QgsVectorLayerImport::ErrAttributeCreationFailed;
|
|
}
|
|
|
|
QgsDebugMsg( "Done creating fields" );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( "No fields created." );
|
|
}
|
|
|
|
delete provider;
|
|
|
|
return QgsVectorLayerImport::NoError;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsOracleProvider::crs()
|
|
{
|
|
QgsCoordinateReferenceSystem srs;
|
|
|
|
QSqlQuery qry( *mConnection );
|
|
|
|
// apparently some EPSG codes don't have the auth_name setup in cs_srs
|
|
if ( exec( qry, QString( "SELECT coalesce(auth_name,'EPSG'),auth_srid,wktext FROM mdsys.cs_srs WHERE srid=%1" ).arg( mSrid ) ) )
|
|
{
|
|
if ( qry.next() )
|
|
{
|
|
if ( qry.value( 0 ).toString() == "EPSG" )
|
|
{
|
|
srs.createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( qry.value( 1 ).toString() ) );
|
|
}
|
|
else
|
|
{
|
|
srs.createFromWkt( qry.value( 2 ).toString() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Oracle SRID %1 not found." ).arg( mSrid ), tr( "Oracle" ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Lookup of Oracle SRID %1 failed.\nSQL:%2\nError:%3" )
|
|
.arg( mSrid )
|
|
.arg( qry.lastQuery() )
|
|
.arg( qry.lastError().text() ),
|
|
tr( "Oracle" ) );
|
|
}
|
|
|
|
return srs;
|
|
}
|
|
|
|
QString QgsOracleProvider::subsetString()
|
|
{
|
|
return mSqlWhereClause;
|
|
}
|
|
|
|
QString QgsOracleProvider::getTableName()
|
|
{
|
|
return mTableName;
|
|
}
|
|
|
|
size_t QgsOracleProvider::layerCount() const
|
|
{
|
|
return 1; // XXX need to return actual number of layers
|
|
} // QgsOracleProvider::layerCount()
|
|
|
|
|
|
QString QgsOracleProvider::name() const
|
|
{
|
|
return ORACLE_KEY;
|
|
} // QgsOracleProvider::name()
|
|
|
|
QString QgsOracleProvider::description() const
|
|
{
|
|
return ORACLE_DESCRIPTION;
|
|
} // QgsOracleProvider::description()
|
|
|
|
/**
|
|
* Class factory to return a pointer to a newly created
|
|
* QgsOracleProvider object
|
|
*/
|
|
QGISEXTERN QgsOracleProvider * classFactory( const QString *uri )
|
|
{
|
|
return new QgsOracleProvider( *uri );
|
|
}
|
|
/** Required key function (used to map the plugin to a data store type)
|
|
*/
|
|
QGISEXTERN QString providerKey()
|
|
{
|
|
return QSqlDatabase::isDriverAvailable( "QOCISPATIAL" ) ? ORACLE_KEY : 0;
|
|
}
|
|
/**
|
|
* Required description function
|
|
*/
|
|
QGISEXTERN QString description()
|
|
{
|
|
return QSqlDatabase::isDriverAvailable( "QOCISPATIAL" ) ? ORACLE_DESCRIPTION : 0;
|
|
}
|
|
/**
|
|
* Required isProvider function. Used to determine if this shared library
|
|
* is a data provider plugin
|
|
*/
|
|
QGISEXTERN bool isProvider()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QGISEXTERN QgsOracleSourceSelect *selectWidget( QWidget *parent, Qt::WindowFlags fl )
|
|
{
|
|
return new QgsOracleSourceSelect( parent, fl );
|
|
}
|
|
|
|
QGISEXTERN int dataCapabilities()
|
|
{
|
|
return QgsDataProvider::Database;
|
|
}
|
|
|
|
QGISEXTERN QgsDataItem *dataItem( QString thePath, QgsDataItem *parentItem )
|
|
{
|
|
Q_UNUSED( thePath );
|
|
return new QgsOracleRootItem( parentItem, "Oracle", "oracle:" );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
QGISEXTERN QgsVectorLayerImport::ImportError createEmptyLayer(
|
|
const QString& uri,
|
|
const QgsFields &fields,
|
|
QGis::WkbType wkbType,
|
|
const QgsCoordinateReferenceSystem *srs,
|
|
bool overwrite,
|
|
QMap<int, int> *oldToNewAttrIdxMap,
|
|
QString *errorMessage,
|
|
const QMap<QString, QVariant> *options )
|
|
{
|
|
return QgsOracleProvider::createEmptyLayer(
|
|
uri, fields, wkbType, srs, overwrite,
|
|
oldToNewAttrIdxMap, errorMessage, options
|
|
);
|
|
}
|
|
|
|
QGISEXTERN bool deleteLayer( const QString& uri, QString& errCause )
|
|
{
|
|
QgsDebugMsg( "deleting layer " + uri );
|
|
|
|
QgsDataSourceURI dsUri( uri );
|
|
QString ownerName = dsUri.schema();
|
|
QString tableName = dsUri.table();
|
|
QString geometryCol = dsUri.geometryColumn();
|
|
|
|
QgsOracleConn* conn = QgsOracleConn::connectDb( dsUri );
|
|
if ( !conn )
|
|
{
|
|
errCause = QObject::tr( "Connection to database failed" );
|
|
return false;
|
|
}
|
|
|
|
if ( ownerName != conn->currentUser() )
|
|
{
|
|
errCause = QObject::tr( "%1 not owner of the table %2." )
|
|
.arg( ownerName )
|
|
.arg( tableName );
|
|
conn->disconnect();
|
|
return false;
|
|
}
|
|
|
|
QSqlQuery qry( *conn );
|
|
|
|
// check the geometry column count
|
|
if ( !QgsOracleProvider::exec( qry, QString( "SELECT count(*)"
|
|
" FROM user_tab_columns"
|
|
" WHERE table_name=%1 AND data_type='SDO_GEOMETRY' AND data_type_owner='MDSYS'" )
|
|
.arg( QgsOracleConn::quotedValue( tableName ) ) )
|
|
|| !qry.next() )
|
|
{
|
|
errCause = QObject::tr( "Unable to determine number of geometry columns of layer %1.%2: \n%3" )
|
|
.arg( ownerName )
|
|
.arg( tableName )
|
|
.arg( qry.lastError().text() );
|
|
conn->disconnect();
|
|
return false;
|
|
}
|
|
|
|
int count = qry.value( 0 ).toInt();
|
|
|
|
QString dropTable;
|
|
QString cleanView;
|
|
if ( !geometryCol.isEmpty() && count > 1 )
|
|
{
|
|
// the table has more geometry columns, drop just the geometry column
|
|
dropTable = QString( "ALTER TABLE %1 DROP COLUMN %2" )
|
|
.arg( QgsOracleConn::quotedIdentifier( tableName ) )
|
|
.arg( QgsOracleConn::quotedIdentifier( geometryCol ) );
|
|
cleanView = QString( "DELETE FROM mdsys.user_sdo_geom_metadata WHERE table_name=%1 AND column_name=%2" )
|
|
.arg( QgsOracleConn::quotedValue( tableName ) )
|
|
.arg( QgsOracleConn::quotedValue( geometryCol ) );
|
|
}
|
|
else
|
|
{
|
|
// drop the table
|
|
dropTable = QString( "DROP TABLE %1" )
|
|
.arg( QgsOracleConn::quotedIdentifier( tableName ) );
|
|
cleanView = QString( "DELETE FROM mdsys.user_sdo_geom_metadata WHERE table_name=%1" )
|
|
.arg( QgsOracleConn::quotedValue( tableName ) );
|
|
}
|
|
|
|
if ( !QgsOracleProvider::exec( qry, dropTable ) )
|
|
{
|
|
errCause = QObject::tr( "Unable to delete layer %1.%2: \n%3" )
|
|
.arg( ownerName )
|
|
.arg( tableName )
|
|
.arg( qry.lastError().text() );
|
|
conn->disconnect();
|
|
return false;
|
|
}
|
|
|
|
if ( !QgsOracleProvider::exec( qry, cleanView ) )
|
|
{
|
|
errCause = QObject::tr( "Unable to clean metadata %1.%2: \n%3" )
|
|
.arg( ownerName )
|
|
.arg( tableName )
|
|
.arg( qry.lastError().text() );
|
|
conn->disconnect();
|
|
return false;
|
|
}
|
|
|
|
conn->disconnect();
|
|
return true;
|
|
}
|
|
|
|
// ----------
|
|
|
|
|
|
QgsOracleSharedData::QgsOracleSharedData()
|
|
: mFidCounter( 0 )
|
|
{
|
|
}
|
|
|
|
QgsFeatureId QgsOracleSharedData::lookupFid( const QVariant &v )
|
|
{
|
|
QMutexLocker locker( &mMutex );
|
|
|
|
QMap<QVariant, QgsFeatureId>::const_iterator it = mKeyToFid.find( v );
|
|
|
|
if ( it != mKeyToFid.constEnd() )
|
|
{
|
|
return it.value();
|
|
}
|
|
|
|
mFidToKey.insert( ++mFidCounter, v );
|
|
mKeyToFid.insert( v, mFidCounter );
|
|
|
|
return mFidCounter;
|
|
}
|
|
|
|
QVariant QgsOracleSharedData::removeFid( QgsFeatureId fid )
|
|
{
|
|
QMutexLocker locker( &mMutex );
|
|
|
|
QVariant v = mFidToKey[ fid ];
|
|
mFidToKey.remove( fid );
|
|
mKeyToFid.remove( v );
|
|
return v;
|
|
}
|
|
|
|
void QgsOracleSharedData::insertFid( QgsFeatureId fid, const QVariant& k )
|
|
{
|
|
QMutexLocker locker( &mMutex );
|
|
|
|
mFidToKey.insert( fid, k );
|
|
mKeyToFid.insert( k, fid );
|
|
}
|
|
|
|
QVariant QgsOracleSharedData::lookupKey( QgsFeatureId featureId )
|
|
{
|
|
QMutexLocker locker( &mMutex );
|
|
|
|
QMap<QgsFeatureId, QVariant>::const_iterator it = mFidToKey.find( featureId );
|
|
if ( it != mFidToKey.constEnd() )
|
|
return it.value();
|
|
return QVariant();
|
|
}
|
|
|
|
// vim: set sw=2 :
|