357 changes: 357 additions & 0 deletions src/providers/sqlanywhere/qgssqlanywherefeatureiterator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
#include "qgssqlanywherefeatureiterator.h"
#include "qgssqlanywhereprovider.h"

#include <qgsapplication.h>
#include <qgslogger.h>
#include <qgsmessagelog.h>

#include <QObject>

// provider:
// - mGeometryColumn
// - mAttributeFields
// - mConnRO
// - mQuotedTableName
// - mKeyColumn
// - mSrid
// - mSrsExtent
// - mSubsetString
// - geomColIdent()
// - isValid()
// - fields()
// - field()
// - ensureConnRO()
// - quotedIdentifier()
// - reportError()
// - tr()

QgsSqlAnywhereFeatureIterator::QgsSqlAnywhereFeatureIterator( QgsSqlAnywhereProvider* p, const QgsFeatureRequest & request )
: QgsAbstractFeatureIterator( request ), P( p )
, mStmt( NULL ), mStmtRect()
{

mClosed = false;
QString whereClause = P->getWhereClause();

if ( request.filterType() == QgsFeatureRequest::FilterRect && !P->mGeometryColumn.isNull() )
{
mStmtRect = mRequest.filterRect();
SaDebugMsg( "initial rectangle =" + mStmtRect.toString() );
mStmtRect = mStmtRect.intersect( &P->mSrsExtent );
SaDebugMsg( "rectangle clipped to SRS extent =" + mStmtRect.toString() );

whereClause += " AND " + whereClauseRect();
}
else if ( request.filterType() == QgsFeatureRequest::FilterFid )
{
whereClause += " AND " + whereClauseFid();
}

if ( !prepareStatement( whereClause ) )
{
mStmt = NULL;
mClosed = true;
return;
}
}


QgsSqlAnywhereFeatureIterator::~QgsSqlAnywhereFeatureIterator()
{
close();
}

bool
QgsSqlAnywhereFeatureIterator::nextFeature( QgsFeature& feature )
{
if ( mClosed )
return false;

feature.setValid( false );

if ( !P->isValid() )
{
SaDebugMsg( "Read attempt on an invalid SQL Anywhere data source" );
return false;
}

if ( mStmt == NULL || !mStmt->isValid() )
{
SaDebugMsg( "nextFeature() called with invalid cursor()" );
return false;
}

return nextFeature( feature, mStmt );
}


bool QgsSqlAnywhereFeatureIterator::nextFeature( QgsFeature& feature, SqlAnyStatement *stmt )
{
feature.setValid( false );

bool fetchGeometry = !( mRequest.flags() & QgsFeatureRequest::NoGeometry );
bool subsetAttributes = mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes;

if ( mClosed )
return false;

if ( !P->mConnRO || !P->mConnRO->isAlive() )
{
SaDebugMsg( "No database connection." );
return false;
}

bool ok;
int id;
a_sqlany_data_value geom;
unsigned char *geomBuf = NULL;


ok = ( stmt != NULL && stmt->fetchNext() );

// if no more rows...
if( !ok )
return false;

if( !fetchGeometry )
feature.setGeometryAndOwnership( 0, 0 );

int numAttributes = P->fields().count(); // also used later for sanity check

feature.initAttributes( numAttributes );
feature.setFields( &P->mAttributeFields ); // allow name-based attribute lookups

int i = 0;
int numcols = stmt->numCols();
int colidx = 0; // Refers to which column we're on in "feature" (the row)
for( i = 0; i < numcols; i++ )
{
if( i == 0 )
{
// first column always contains primary key
ok = stmt->getInt( i, id );
if( !ok ) break;
QgsDebugMsgLevel( QString( "pid=%1" ).arg( id ), 3 );
feature.setFeatureId( id );
}
else if( i == 1 && fetchGeometry )
{
// second column contains QKB geometry value
ok = stmt->getColumn( i, &geom );
if( !ok ) break;
QgsDebugMsgLevel( QString( "retrieved geometry column" ), 3 );
geomBuf = new unsigned char[ *geom.length + 1 ];
memset( geomBuf, '\0', *geom.length );
memcpy( geomBuf, geom.buffer, *geom.length );
feature.setGeometryAndOwnership( geomBuf, *geom.length + 1 );
}
else
{
if( i == 1 )
{
feature.setGeometryAndOwnership( 0, 0 ); // no geometry to fetch
}
int attrIndex = subsetAttributes ? mRequest.subsetOfAttributes()[colidx++] : colidx++;
QVariant val;
ok = stmt->getQVariant( i, val ); // ok may be false if value was NULL, but this is a valid return

// Sanity check before setting the attribute value
if( colidx-1 == i // First column is always pk, so colidx should be at least 1 behind
|| ( colidx-1 == i-1 && fetchGeometry ) // if fetchGeometry is true, colidx should be 2 behind
|| attrIndex >= numAttributes ) // index should always be less than the count
{
SaDebugMsg( QString("Error retrieving feature column %1 with attribute index %2").arg(i).arg(attrIndex) );
return false;
}
// So now this should not crash.
feature.setAttribute( attrIndex, val );
}

}

feature.setValid( true );
return true;
}


bool QgsSqlAnywhereFeatureIterator::rewind()
{
if ( mClosed )
return false;

mStmt->reset();
mStmt->execute();
return true;
}

bool QgsSqlAnywhereFeatureIterator::close()
{
if ( mClosed )
return false;

if ( mStmt )
{
delete mStmt;
mStmt = NULL;
}

mClosed = true;
return true;
}

bool
QgsSqlAnywhereFeatureIterator::prepareStatement( QString whereClause )
// adapted from QgsSpatialLiteFeatureIterator::prepareStatement
{
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) && P->mGeometryColumn.isNull() )
return false;

if( !P->ensureConnRO() )
{
SaDebugMsg( "No read-only database connection." );
return false;
}

QString sql = QString( "SELECT %1" ).arg( quotedPrimaryKey() ); // Column 0 is primary key

// Column 1 is geometry, if applicable
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
{
sql += QString( ", %1 .ST_AsBinary('WKB(Version=1.1;endian=%2)') " )
.arg( P->mGeometryColumn )
.arg( QgsApplication::endian() == QgsApplication::XDR ? "xdr" : "ndr" );
}

// Add the rest of the columns
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
const QgsAttributeList& fetchAttributes = mRequest.subsetOfAttributes();
for ( QgsAttributeList::const_iterator it = fetchAttributes.constBegin(); it != fetchAttributes.constEnd(); ++it )
{
QString name = P->field( *it ).name();
if( !name.isEmpty() /*&& name != P->mKeyColumn*/ ) {
sql += "," + P->quotedIdentifier( name );
}
}
}
else
{
// fetch all attributes
for ( int idx = 0; idx < P->mAttributeFields.count(); ++idx )
{
QString name = P->mAttributeFields[idx].name();
if( !name.isEmpty() /*&& name != P->mKeyColumn*/ ) {
sql += "," + P->quotedIdentifier( name );
}
}
}

sql += QString( " FROM %1 " ).arg( P->mQuotedTableName );

if ( !whereClause.isEmpty() )
sql += QString( " WHERE %1 OPTIONS(FORCE OPTIMIZATION)" ).arg( whereClause );


if ( mStmt )
delete mStmt;

mStmt = P->mConnRO->prepare( sql );
if( !mStmt->isValid() )
{
// (Error msg will be printed using SaDebugMsg in prepare())
rewind();
return false;
}

// bind parameters if necessary
if ( !mStmtRect.isEmpty() )
{
double xmin = mStmtRect.xMinimum();
double ymin = mStmtRect.yMinimum();
double xmax = mStmtRect.xMaximum();
double ymax = mStmtRect.yMaximum();

a_sqlany_bind_param xminParam;
a_sqlany_bind_param yminParam;
a_sqlany_bind_param xmaxParam;
a_sqlany_bind_param ymaxParam;

size_t xminLen = sizeof( double );
size_t yminLen = sizeof( double );
size_t xmaxLen = sizeof( double );
size_t ymaxLen = sizeof( double );

if ( !mStmt->describe_bind_param( 0, xminParam )
|| !mStmt->describe_bind_param( 1, yminParam )
|| !mStmt->describe_bind_param( 2, xmaxParam )
|| !mStmt->describe_bind_param( 3, ymaxParam ) )
{
P->reportError( P->tr( "Error describing bind parameters" ), mStmt );
return false;
}

xminParam.value.buffer = ( char * ) & xmin;
yminParam.value.buffer = ( char * ) & ymin;
xmaxParam.value.buffer = ( char * ) & xmax;
ymaxParam.value.buffer = ( char * ) & ymax;

xminParam.value.length = &xminLen;
yminParam.value.length = &yminLen;
xmaxParam.value.length = &xmaxLen;
ymaxParam.value.length = &ymaxLen;

xminParam.value.type = A_DOUBLE;
yminParam.value.type = A_DOUBLE;
xmaxParam.value.type = A_DOUBLE;
ymaxParam.value.type = A_DOUBLE;

if ( !mStmt->bind_param( 0, xminParam )
|| !mStmt->bind_param( 1, yminParam )
|| !mStmt->bind_param( 2, xmaxParam )
|| !mStmt->bind_param( 3, ymaxParam ) )
{
P->reportError( P->tr( "Error binding parameters" ), mStmt );
return false;
}
}

// Execute the statement
if( !mStmt->execute() )
{
// (Error msg will be printed using SaDebugMsg in execute())
rewind();
return false;
}

return true;
}

QString
QgsSqlAnywhereFeatureIterator::whereClauseRect() const
{
bool exactIntersect = ( mRequest.flags() & QgsFeatureRequest::ExactIntersect );

QString whereClause;
whereClause += QString( "%1 .%2 ( new ST_Polygon( "
"new ST_Point( ?, ?, %3), "
"new ST_Point( ?, ?, %3 ) ) ) = 1 " )
.arg( P->geomColIdent() )
.arg(( exactIntersect ? "ST_Intersects" : "ST_IntersectsFilter" ) )
.arg( P->mSrid );
// Note: IntersectsFilter is the less expensive estimate

return whereClause;
}

QString QgsSqlAnywhereFeatureIterator::quotedPrimaryKey() const
{
return P->quotedIdentifier( P->mKeyColumn );
}

QString QgsSqlAnywhereFeatureIterator::whereClauseFid() const
{
return QString( "%1=%2" ).arg( quotedPrimaryKey() ).arg( mRequest.filterFid() );
}



47 changes: 47 additions & 0 deletions src/providers/sqlanywhere/qgssqlanywherefeatureiterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef QGSSQLANYWHEREFEATUREITERATOR_H
#define QGSSQLANYWHEREFEATUREITERATOR_H

#include "qgsfeatureiterator.h"
#include "sqlanystatement.h"

class QgsSqlAnywhereProvider;
class QgsSqlAnywhereResult;

class QgsSqlAnywhereFeatureIterator : public QgsAbstractFeatureIterator
{
public:
QgsSqlAnywhereFeatureIterator( QgsSqlAnywhereProvider* p, const QgsFeatureRequest & request );

~QgsSqlAnywhereFeatureIterator();

//! fetch next feature, return true on success
virtual bool nextFeature( QgsFeature& feature );
bool nextFeature( QgsFeature& feature, SqlAnyStatement *stmt );

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

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

protected:
QgsSqlAnywhereProvider* P;

private:
bool prepareStatement( QString whereClause );

QString whereClauseRect() const;

QString quotedPrimaryKey() const;
QString whereClauseFid() const;

/**
* Statement handle for fetching of features by bounding rectangle
*/
SqlAnyStatement *mStmt;

QgsRectangle mStmtRect;

};

#endif // QGSSQLANYWHEREFEATUREITERATOR_H
371 changes: 31 additions & 340 deletions src/providers/sqlanywhere/qgssqlanywhereprovider.cpp

Large diffs are not rendered by default.

58 changes: 6 additions & 52 deletions src/providers/sqlanywhere/qgssqlanywhereprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,8 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
/** Get the QgsCoordinateReferenceSystem for this layer */
QgsCoordinateReferenceSystem crs() { return mCrs; }

/** Gets the feature at the given feature ID
* @param featureId of feature to retrieve
* @param feature which will receive data from the provider
* @param fetchGeometry true if the feature geometry should be fetched
* @param fetchAttributes list of attributes which should be fetched
*/
virtual bool featureAtId( QgsFeatureId featureId,
QgsFeature & feature, bool fetchGeometry = true, QgsAttributeList fetchAttributes = QgsAttributeList() );
/** Query for features specified in request. **/
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request );

/** Accessor for sql where clause used to limit dataset */
virtual QString subsetString();
Expand All @@ -79,23 +73,6 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
virtual bool setSubsetString( QString theSQL, bool updateFeatureCount = true );
virtual bool supportsSubsetString() { return true; }

/** Select features based on a bounding rectangle. Features can be retrieved with calls to nextFeature.
* @param fetchAttributes list of attributes which should be fetched
* @param rect spatial filter
* @param fetchGeometry true if the feature geometry should be fetched
* @param useIntersect true if an accurate intersection test should be used,
* false if a test based on bounding box is sufficient
*/
virtual void select( QgsAttributeList fetchAttributes = QgsAttributeList(),
QgsRectangle rect = QgsRectangle(), bool fetchGeometry = true, bool useIntersect = false );

/**
* Get the next feature resulting from a select operation.
* @param feature feature which will receive data from the provider
* @return true when there was a feature to fetch, false when end was hit
*/
virtual bool nextFeature( QgsFeature & feature );

/** Get the feature type. This corresponds to
* WKBPoint,
* WKBLineString,
Expand Down Expand Up @@ -126,12 +103,7 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
* Get the field information for the layer
* @return vector of QgsField objects
*/
const QgsFieldMap & fields() const { return mAttributeFields; }

/**
* Restart reading features from previous select operation
*/
void rewind();
const QgsFields & fields() const { return mAttributeFields; }

/** Returns the minimum value of an attribute
* @param index the index of the attribute */
Expand Down Expand Up @@ -277,16 +249,12 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
* internal utility functions used to handle common database tasks
*/
void closeDb();
void closeCursors() { closeConnROCursors(); }
void closeConnROCursors();
void closeConnRO();
void closeConnRW();
QString quotedIdentifier( QString id ) const;
QString quotedValue( QString value ) const;
QString getWhereClause() const { return mSubsetString.isEmpty() ? "1=1 " : "( " + mSubsetString + ") "; }
bool hasUniqueData( QString colName );
QString makeSelectSql( QString whereClause ) const;
bool nextFeature( QgsFeature & feature, SqlAnyStatement *stmt );
QString geomSampleSet();
QString geomColIdent() const { return quotedIdentifier( mGeometryColumn ) + mGeometryProjStr; }

Expand All @@ -309,7 +277,7 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
/**
* map of field index number to field type
*/
QgsFieldMap mAttributeFields;
QgsFields mAttributeFields;
/**
* map of field index number to field default value
*/
Expand Down Expand Up @@ -399,22 +367,6 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
*/
long mNumberFeatures;

/**
* Statement handle for fetching of features by bounding rectangle
*/
SqlAnyStatement *mStmt;
QgsAttributeList mStmtAttributesToFetch;
bool mStmtFetchGeom;
QgsRectangle mStmtRect;
bool mStmtUseIntersect;

/**
* Statement handle for fetching of features by ID
*/
SqlAnyStatement *mIdStmt;
QgsAttributeList mIdStmtAttributesToFetch;
bool mIdStmtFetchGeom;

/**
* Read-only connection to SQL Anywhere database
*/
Expand All @@ -424,6 +376,8 @@ class QgsSqlAnywhereProvider: public QgsVectorDataProvider
*/
SqlAnyConnection *mConnRW;

friend class QgsSqlAnywhereFeatureIterator;

}; // class QgsSqlAnywhereProvider


Expand Down