QGIS/providers/postgres/qgspostgresprovider.cpp
g_j_m dd4cad1941 Fix for bug 1163483
git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@2971 c8812cc2-4d05-0410-92ff-de0c093fc19c
2005-03-15 20:16:11 +00:00

1950 lines
58 KiB
C++

/***************************************************************************
qgspostgresprovider.cpp - QGIS data provider for PostgreSQL/PostGIS layers
-------------------
begin : 2004/01/07
copyright : (C) 2004 by Gary E.Sherman
email : sherman at mrcc.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
/* $Id$ */
#include <fstream>
#include <iostream>
#include <qtextstream.h>
#include <qstringlist.h>
#include <qapplication.h>
#include <qmessagebox.h>
#include <qcursor.h>
#include <qobject.h>
#include <qregexp.h>
// for ntohl
#ifdef WIN32
#include <winsock.h>
#else
#include <netinet/in.h>
#endif
#include "../../src/qgis.h"
#include "../../src/qgsfeature.h"
#include "../../src/qgsfield.h"
#include "../../src/qgsrect.h"
#include "../../src/qgsprovidercountcalcevent.h"
#include "../../src/qgsproviderextentcalcevent.h"
#include "qgspostgresprovider.h"
#include "qgspostgrescountthread.h"
#include "qgspostgresextentthread.h"
#include "qgspostgisbox3d.h"
#ifdef WIN32
#define QGISEXTERN extern "C" __declspec( dllexport )
#else
#define QGISEXTERN extern "C"
#endif
QgsPostgresProvider::QgsPostgresProvider(QString uri):dataSourceUri(uri)
{
// assume this is a valid layer until we determine otherwise
valid = true;
/* OPEN LOG FILE */
// Make connection to the data source
// For postgres, the connection information is passed as a space delimited
// string:
// host=192.168.1.5 dbname=test port=5342 user=gsherman password=xxx table=tablename
std::cout << "Data source uri is " << uri << std::endl;
// Strip the table and sql statement name off and store them
int sqlStart = uri.find(" sql");
int tableStart = uri.find("table=");
#ifdef QGISDEBUG
qDebug( "****************************************");
qDebug( "**** Postgresql Layer Creation *****" );
qDebug( "****************************************");
qDebug( "URI: " + uri );
QString msg;
qDebug( "tableStart: " + msg.setNum(tableStart) );
qDebug( "sqlStart: " + msg.setNum(sqlStart));
#endif
tableName = uri.mid(tableStart + 6, sqlStart - tableStart -6);
if(sqlStart > -1)
{
sqlWhereClause = uri.mid(sqlStart + 5);
}
else
{
sqlWhereClause = QString::null;
}
QString connInfo = uri.left(uri.find("table="));
#ifdef QGISDEBUG
qDebug( "Table name is " + tableName);
qDebug( "SQL is " + sqlWhereClause );
qDebug( "Connection info is " + connInfo);
#endif
// calculate the schema if specified
mSchema = "";
if (tableName.find(".") > -1) {
mSchema = tableName.left(tableName.find("."));
}
geometryColumn = tableName.mid(tableName.find(" (") + 2);
geometryColumn.truncate(geometryColumn.length() - 1);
tableName = tableName.mid(tableName.find(".") + 1, tableName.find(" (") - (tableName.find(".") + 1));
/* populate the uri structure */
mUri.schema = mSchema;
mUri.table = tableName;
mUri.geometryColumn = geometryColumn;
mUri.sql = sqlWhereClause;
// parse the connection info
QStringList conParts = QStringList::split(" ", connInfo);
QStringList parm = QStringList::split("=", conParts[0]);
if(parm.size() == 2)
{
mUri.host = parm[1];
}
parm = QStringList::split("=", conParts[1]);
if(parm.size() == 2)
{
mUri.database = parm[1];
}
parm = QStringList::split("=", conParts[2]);
if(parm.size() == 2)
{
mUri.port = parm[1];
}
parm = QStringList::split("=", conParts[3]);
if(parm.size() == 2)
{
mUri.username = parm[1];
}
parm = QStringList::split("=", conParts[4]);
if(parm.size() == 2)
{
mUri.password = parm[1];
}
/* end uri structure */
#ifdef QGISDEBUG
std::cerr << "Geometry column is: " << geometryColumn << std::endl;
std::cerr << "Schema is: " + mSchema << std::endl;
std::cerr << "Table name is: " + tableName << std::endl;
#endif
//QString logFile = "./pg_provider_" + tableName + ".log";
//pLog.open((const char *)logFile);
#ifdef QGISDEBUG
std::cerr << "Opened log file for " << tableName << std::endl;
#endif
PGconn *pd = PQconnectdb((const char *) connInfo);
// check the connection status
if (PQstatus(pd) == CONNECTION_OK) {
/* Check to see if we have GEOS support and if not, warn the user about
the problems they will see :) */
#ifdef QGISDEBUG
std::cerr << "Checking for GEOS support" << std::endl;
#endif
if(!hasGEOS(pd)){
QApplication::restoreOverrideCursor();
QMessageBox::warning(0, "No GEOS Support!",
"Your PostGIS installation has no GEOS support.\nFeature selection and "
"identification will not work properly.\nPlease install PostGIS with "
"GEOS support (http://geos.refractions.net)");
QApplication::setOverrideCursor(Qt::waitCursor);
}
//--std::cout << "Connection to the database was successful\n";
// set the schema
PQexec(pd,(const char *)QString("set search_path = '%1','public'").arg(mSchema));
// store the connection for future use
connection = pd;
if (getGeometryDetails()) // gets srid and geometry type
{
deduceEndian();
calculateExtents();
getFeatureCount();
// selectSQL stores the select sql statement. This has to include
// each attribute plus the geometry column in binary form
selectSQL = "select ";
// Populate the field vector for this layer. The field vector contains
// field name, type, length, and precision (if numeric)
QString sql = "select * from " + tableName + " limit 1";
PGresult* result = PQexec(pd, (const char *) sql);
//--std::cout << "Field: Name, Type, Size, Modifier:" << std::endl;
for (int i = 0; i < PQnfields(result); i++)
{
QString fieldName = PQfname(result, i);
int fldtyp = PQftype(result, i);
QString typOid = QString().setNum(fldtyp);
int fieldModifier = PQfmod(result, i);
sql = "select typelem from pg_type where typelem = " + typOid + " and typlen = -1";
// //--std::cout << sql << std::endl;
PGresult *oidResult = PQexec(pd, (const char *) sql);
// get the oid of the "real" type
QString poid = PQgetvalue(oidResult, 0, PQfnumber(oidResult, "typelem"));
PQclear(oidResult);
sql = "select typname, typlen from pg_type where oid = " + poid;
// //--std::cout << sql << std::endl;
oidResult = PQexec(pd, (const char *) sql);
QString fieldType = PQgetvalue(oidResult, 0, 0);
QString fieldSize = PQgetvalue(oidResult, 0, 1);
PQclear(oidResult);
sql = "select oid from pg_class where relname = '" + tableName + "'";
PGresult *tresult= PQexec(pd, (const char *)sql);
QString tableoid = PQgetvalue(tresult, 0, 0);
PQclear(tresult);
sql = "select attnum from pg_attribute where attrelid = " + tableoid + " and attname = '" + fieldName + "'";
tresult = PQexec(pd, (const char *)sql);
QString attnum = PQgetvalue(tresult, 0, 0);
PQclear(tresult);
#ifdef QGISDEBUG
std::cerr << "Field: " << attnum << " maps to " << i << " " << fieldName << ", "
<< fieldType << " (" << fldtyp << "), " << fieldSize << ", "
<< fieldModifier << std::endl;
#endif
attributeFieldsIdMap[attnum.toInt()] = i;
attributeFields.push_back(QgsField(fieldName, fieldType, fieldSize.toInt(), fieldModifier));
// add to the select sql statement
if(i > 0)
selectSQL += ", ";
if (fieldType == "geometry")
selectSQL += "asbinary(" + geometryColumn + ",'" + endianString() + "') as qgs_feature_geometry";
else
selectSQL += fieldName;
}
PQclear(result);
// set the primary key
getPrimaryKey();
selectSQL += " from " + tableName;
//--std::cout << "selectSQL: " << (const char *)selectSQL << std::endl;
// Kick off the long running threads
std::cout << "QgsPostgresProvider: About to touch mExtentThread" << std::endl;
mExtentThread.setConnInfo( connInfo );
mExtentThread.setTableName( tableName );
mExtentThread.setSqlWhereClause( sqlWhereClause );
mExtentThread.setGeometryColumn( geometryColumn );
mExtentThread.setCallback( this );
std::cout << "QgsPostgresProvider: About to start mExtentThread" << std::endl;
mExtentThread.start();
std::cout << "QgsPostgresProvider: Main thread just dispatched mExtentThread" << std::endl;
std::cout << "QgsPostgresProvider: About to touch mCountThread" << std::endl;
mCountThread.setConnInfo( connInfo );
mCountThread.setTableName( tableName );
mCountThread.setSqlWhereClause( sqlWhereClause );
mCountThread.setGeometryColumn( geometryColumn );
mCountThread.setCallback( this );
std::cout << "QgsPostgresProvider: About to start mCountThread" << std::endl;
mCountThread.start();
std::cout << "QgsPostgresProvider: Main thread just dispatched mCountThread" << std::endl;
} else {
// the table is not a geometry table
numberFeatures = 0;
valid = false;
#ifdef QGISDEBUG
std::cerr << "Invalid Postgres layer" << std::endl;
#endif
}
// reset tableName to include schema
schemaTableName += mSchema + "." + tableName;
ready = false; // not ready to read yet cuz the cursor hasn't been created
} else {
valid = false;
//--std::cout << "Connection to database failed\n";
}
//create a boolean vector and set every entry to false
/* if (valid) {
selected = new std::vector < bool > (ogrLayer->GetFeatureCount(), false);
} else {
selected = 0;
} */
// tabledisplay=0;
//fill type names into lists
mNumericalTypes.push_back("double precision");
mNumericalTypes.push_back("int4");
mNumericalTypes.push_back("int8");
mNonNumericalTypes.push_back("text");
mNonNumericalTypes.push_back("varchar(30)");
}
QgsPostgresProvider::~QgsPostgresProvider()
{
std::cout << "QgsPostgresProvider: About to wait for mExtentThread" << std::endl;
mExtentThread.wait();
std::cout << "QgsPostgresProvider: Finished waiting for mExtentThread" << std::endl;
std::cout << "QgsPostgresProvider: About to wait for mCountThread" << std::endl;
mCountThread.wait();
std::cout << "QgsPostgresProvider: Finished waiting for mCountThread" << std::endl;
// Make sure all events from threads have been processed
// (otherwise they will get destroyed prematurely)
QApplication::sendPostedEvents(this, QGis::ProviderExtentCalcEvent);
QApplication::sendPostedEvents(this, QGis::ProviderCountCalcEvent);
PQfinish(connection);
std::cout << "QgsPostgresProvider: deconstructing." << std::endl;
//pLog.flush();
}
//TODO - we may not need this function - consider removing it from
// the dataprovider.h interface
/**
* Get the first feature resutling from a select operation
* @return QgsFeature
*/
//TODO - this function is a stub and always returns 0
QgsFeature *QgsPostgresProvider::getFirstFeature(bool fetchAttributes)
{
QgsFeature *f = 0;
if (valid) {
//--std::cout << "getting first feature\n";
f = new QgsFeature();
/* f->setGeometry(getGeometryPointer(feat));
if(fetchAttributes){
getFeatureAttributes(feat, f);
} */
}
return f;
}
bool QgsPostgresProvider::getNextFeature(QgsFeature &feature, bool fetchAttributes)
{
return true;
}
/**
* Get the next feature resutling from a select operation
* Return 0 if there are no features in the selection set
* @return QgsFeature
*/
QgsFeature *QgsPostgresProvider::getNextFeature(bool fetchAttributes)
{
QgsFeature *f = 0;
if (valid){
QString fetch = "fetch forward 1 from qgisf";
queryResult = PQexec(connection, (const char *)fetch);
// std::cerr << "Error: " << PQerrorMessage(connection) << std::endl;
// std::cerr << "Fetched " << PQntuples(queryResult) << "rows" << std::endl;
if(PQntuples(queryResult) == 0){
PQexec(connection, "end work");
ready = false;
return 0;
}
// //--std::cout <<"Raw value of the geometry field: " << PQgetvalue(queryResult,0,PQfnumber(queryResult,"qgs_feature_geometry")) << std::endl;
//--std::cout << "Length of oid is " << PQgetlength(queryResult,0, PQfnumber(queryResult,"oid")) << std::endl;
// get the value of the primary key based on type
int oid = *(int *)PQgetvalue(queryResult,0,PQfnumber(queryResult,primaryKey));
#ifdef QGISDEBUG
// std::cerr << "OID from database: " << oid << std::endl;
#endif
// oid is in big endian
// XXX If you're so sure about that, then why do you have to check to swap?
int *noid;
// We don't support primary keys that are not int4 so if
// the key is int8 we use the oid as the id instead.
// TODO Throw a warning to let the user know that the layer
// is not using a primary key and that performance will suffer
// XXX noid = &oid could probably be moved out of if statements since all
// XXX valid execution paths do that
if(primaryKeyType == "int8")
{
noid = &oid;
}
else
{
if(swapEndian)
{
// XXX I'm assuming swapping from big-endian, or network, byte order to little endian
#ifdef QGISDEBUG
// std::cerr << "swapping endian for oid" << std::endl;
#endif
// convert oid to opposite endian
// XXX "Opposite?" Umm, that's not enough information.
oid = ntohl(oid);
noid = &oid;
}
else
{
noid = &oid;
}
}
// noid contains the oid to be used in fetching attributes if
// fetchAttributes = true
#ifdef QGISDEBUG
// std::cerr << "Using OID: " << *noid << std::endl;
#endif
f = new QgsFeature(*noid);
if (fetchAttributes)
getFeatureAttributes(*noid, f);
int returnedLength = PQgetlength(queryResult,0, PQfnumber(queryResult,"qgs_feature_geometry"));
//--std::cerr << __FILE__ << ":" << __LINE__ << " Returned length is " << returnedLength << std::endl;
if(returnedLength > 0)
{
unsigned char *feature = new unsigned char[returnedLength + 1];
memset(feature, '\0', returnedLength + 1);
memcpy(feature, PQgetvalue(queryResult,0,PQfnumber(queryResult,"qgs_feature_geometry")), returnedLength);
#ifdef QGISDEBUG
// a bit verbose
//int wkbType = *((int *) (feature + 1));
//std::cout << "WKBtype is: " << wkbType << std::endl;
#endif
f->setGeometry(feature, returnedLength + 1);
}
else
{
//--std::cout <<"Couldn't get the feature geometry in binary form" << std::endl;
}
}
else
{
//--std::cout << "Read attempt on an invalid postgresql data source\n";
}
return f;
}
QgsFeature* QgsPostgresProvider::getNextFeature(std::list<int> const & attlist)
{
QgsFeature *f = 0;
if (valid)
{
QString fetch = "fetch forward 1 from qgisf";
queryResult = PQexec(connection, (const char *)fetch);
if(PQntuples(queryResult) == 0)
{
#ifdef QGISDEBUG
std::cerr << "Feature is null\n";
#endif
PQexec(connection, "end work");
ready = false;
return 0;
}
int *noid;
int oid = *(int *)PQgetvalue(queryResult,0,PQfnumber(queryResult,primaryKey));
#ifdef QGISDEBUG
// std::cerr << "Primary key type is " << primaryKeyType << std::endl;
#endif
// We don't support primary keys that are not int4 so if
// the key is int8 we use the oid as the id instead.
// TODO Throw a warning to let the user know that the layer
// is not using a primary key and that performance will suffer
if(primaryKeyType == "int8")
{
noid = &oid;
}
else
{
if(swapEndian)
{
// XXX I'm assuming swapping from big-endian, or network, byte order to little endian
#ifdef QGISDEBUG
//XXX TOO MUCH OUTPUT!!! qWarning("swapping endian for oid");
#endif
// convert oid to opposite endian
// XXX "Opposite?" Umm, that's not enough information.
oid = ntohl(oid);
noid = &oid;
}
else
{
noid = &oid;
}
}
f = new QgsFeature(*noid);
if(!attlist.empty())
{
getFeatureAttributes(*noid, f, attlist);
}
int returnedLength = PQgetlength(queryResult,0, PQfnumber(queryResult,"qgs_feature_geometry"));
if(returnedLength > 0)
{
unsigned char *feature = new unsigned char[returnedLength + 1];
memset(feature, '\0', returnedLength + 1);
memcpy(feature, PQgetvalue(queryResult,0,PQfnumber(queryResult,"qgs_feature_geometry")), returnedLength);
#ifdef QGISDEBUG
// Too verbose
//int wkbType = *((int *) (feature + 1));
//std::cout << "WKBtype is: " << wkbType << std::endl;
#endif
f->setGeometry(feature, returnedLength + 1);
}
else
{
//--std::cout <<"Couldn't get the feature geometry in binary form" << std::endl;
}
}
else
{
//--std::cout << "Read attempt on an invalid postgresql data source\n";
}
return f;
}
/**
* Select features based on a bounding rectangle. Features can be retrieved
* with calls to getFirstFeature and getNextFeature.
* @param mbr QgsRect containing the extent to use in selecting features
*/
void QgsPostgresProvider::select(QgsRect * rect, bool useIntersect)
{
// spatial query to select features
#ifdef QGISDEBUG
std::cerr << "Selection rectangle is " << *rect << std::endl;
std::cerr << "Selection polygon is " << rect->asPolygon() << std::endl;
#endif
QString declare = QString("declare qgisf binary cursor for select "
+ primaryKey
+ ",asbinary(%1,'%2') as qgs_feature_geometry from %3").arg(geometryColumn).arg(endianString()).arg(tableName);
#ifdef QGISDEBUG
std::cout << "Binary cursor: " << declare << std::endl;
#endif
if(useIntersect){
// declare += " where intersects(" + geometryColumn;
// declare += ", GeometryFromText('BOX3D(" + rect->stringRep();
// declare += ")'::box3d,";
// declare += srid;
// declare += "))";
// Contributed by #qgis irc "creeping"
// This version actually invokes PostGIS's use of spatial indexes
declare += " where " + geometryColumn;
declare += " && GeometryFromText('BOX3D(" + rect->stringRep();
declare += ")'::box3d,";
declare += srid;
declare += ")";
declare += " and intersects(" + geometryColumn;
declare += ", GeometryFromText('BOX3D(" + rect->stringRep();
declare += ")'::box3d,";
declare += srid;
declare += "))";
}else{
declare += " where " + geometryColumn;
declare += " && setsrid('BOX3D(" + rect->stringRep();
declare += ")'::box3d,";
declare += srid;
declare += ")";
}
if(sqlWhereClause.length() > 0)
{
declare += " and " + sqlWhereClause;
}
#ifdef QGISDEBUG
std::cerr << "Selecting features using: " << declare << std::endl;
#endif
// set up the cursor
if(ready){
PQexec(connection, "end work");
}
PQexec(connection,"begin work");
PQexec(connection, (const char *)declare);
}
/**
* Set the data source specification. This may be a path or database
* connection string
* @uri data source specification
*/
void QgsPostgresProvider::setDataSourceUri(QString uri)
{
dataSourceUri = uri;
}
/**
* Get the data source specification. This may be a path or database
* connection string
* @return data source specification
*/
QString QgsPostgresProvider::getDataSourceUri()
{
return dataSourceUri;
}
QgsDataSourceURI * QgsPostgresProvider::getURI()
{
return &mUri;
}
/**
* Identify features within the search radius specified by rect
* @param rect Bounding rectangle of search radius
* @return std::vector containing QgsFeature objects that intersect rect
*/
std::vector<QgsFeature>& QgsPostgresProvider::identify(QgsRect * rect)
{
features.clear();
// select the features
select(rect);
return features;
}
/* unsigned char * QgsPostgresProvider::getGeometryPointer(OGRFeature *fet){
// OGRGeometry *geom = fet->GetGeometryRef();
unsigned char *gPtr=0;
// get the wkb representation
gPtr = new unsigned char[geom->WkbSize()];
geom->exportToWkb((OGRwkbByteOrder) endian(), gPtr);
return gPtr;
} */
void QgsPostgresProvider::setExtent( QgsRect* newExtent )
{
layerExtent.setXmax( newExtent->xMax() );
layerExtent.setXmin( newExtent->xMin() );
layerExtent.setYmax( newExtent->yMax() );
layerExtent.setYmin( newExtent->yMin() );
}
// TODO - make this function return the real extent_
QgsRect *QgsPostgresProvider::extent()
{
return &layerExtent; //extent_->MinX, extent_->MinY, extent_->MaxX, extent_->MaxY);
}
/**
* Return the feature type
*/
int QgsPostgresProvider::geometryType() const
{
return geomType;
}
/**
* Return the feature type
*/
long QgsPostgresProvider::featureCount() const
{
return numberFeatures;
}
/**
* Return the number of fields
*/
int QgsPostgresProvider::fieldCount() const
{
return attributeFields.size();
}
/**
* Fetch attributes for a selected feature
*/
void QgsPostgresProvider::getFeatureAttributes(int key, QgsFeature *f){
QString sql = QString("select * from %1 where %2 = %3").arg(tableName).arg(primaryKey).arg(key);
#ifdef QGISDEBUG
// std::cerr << "getFeatureAttributes using: " << sql << std::endl;
#endif
PGresult *attr = PQexec(connection, (const char *)sql);
for (int i = 0; i < fieldCount(); i++) {
QString fld = PQfname(attr, i);
// Dont add the WKT representation of the geometry column to the identify
// results
if(fld != geometryColumn){
// Add the attribute to the feature
QString val = PQgetvalue(attr,0, i);
f->addAttribute(fld, val);
}
}
}
/**Fetch attributes with indices contained in attlist*/
void QgsPostgresProvider::getFeatureAttributes(int key,
QgsFeature *f,
std::list<int> const & attlist)
{
std::list<int>::const_iterator iter;
int i=-1;
for(iter=attlist.begin();iter!=attlist.end();++iter)
{
++i;
QString sql = QString("select %1 from %2 where %3 = %4")
.arg(fields()[*iter].name())
.arg(tableName)
.arg(primaryKey)
.arg(key);//todo: only query one attribute
PGresult *attr = PQexec(connection, (const char *)sql);
QString fld = PQfname(attr, 0);
// Dont add the WKT representation of the geometry column to the identify
// results
if(fld != geometryColumn)
{
// Add the attribute to the feature
QString val = PQgetvalue(attr,0, i);
//qWarning(val);
f->addAttribute(fld, val);
}
}
}
std::vector<QgsField> const & QgsPostgresProvider::fields() const
{
return attributeFields;
}
void QgsPostgresProvider::reset()
{
// reset the cursor to the first record
//--std::cout << "Resetting the cursor to the first record " << std::endl;
QString declare = QString("declare qgisf binary cursor for select " +
primaryKey +
",asbinary(%1,'%2') as qgs_feature_geometry from %3").arg(geometryColumn)
.arg(endianString()).arg(tableName);
if(sqlWhereClause.length() > 0)
{
declare += " where " + sqlWhereClause;
}
//--std::cout << "Selecting features using: " << declare << std::endl;
#ifdef QGISDEBUG
std::cerr << "Setting up binary cursor: " << declare << std::endl;
#endif
// set up the cursor
PQexec(connection,"end work");
PQexec(connection,"begin work");
PQexec(connection, (const char *)declare);
//--std::cout << "Error: " << PQerrorMessage(connection) << std::endl;
ready = true;
}
/* QString QgsPostgresProvider::getFieldTypeName(PGconn * pd, int oid)
{
QString typOid = QString().setNum(oid);
QString sql = "select typelem from pg_type where typelem = " + typOid + " and typlen = -1";
////--std::cout << sql << std::endl;
PGresult *result = PQexec(pd, (const char *) sql);
// get the oid of the "real" type
QString poid = PQgetvalue(result, 0, PQfnumber(result, "typelem"));
PQclear(result);
sql = "select typname, typlen from pg_type where oid = " + poid;
// //--std::cout << sql << std::endl;
result = PQexec(pd, (const char *) sql);
QString typeName = PQgetvalue(result, 0, 0);
QString typeLen = PQgetvalue(result, 0, 1);
PQclear(result);
typeName += "(" + typeLen + ")";
return typeName;
} */
/** @todo XXX Perhaps this should be promoted to QgsDataProvider? */
QString QgsPostgresProvider::endianString()
{
switch ( endian() )
{
case QgsDataProvider::NDR :
return QString("NDR");
break;
case QgsDataProvider::XDR :
return QString("XDR");
break;
default :
return QString("UNKNOWN");
}
}
QString QgsPostgresProvider::getPrimaryKey()
{
// check to see if there is a primary key on the relation
/*
Process to determine the fields used in a primary key for a table:
test=# select indkey from pg_index where indisprimary = 't'
and indrelid =
(select oid from pg_class where relname = 'earthquakes';
indkey
--------
1 5
(1 row)
Primary key is composed of fields 1 and 5
*/
QString sql = "select indkey from pg_index where indisprimary = 't' and "
"indrelid = (select oid from pg_class where relname = '"
+ tableName + "')";
#ifdef QGISDEBUG
std::cerr << "Getting primary key using '" << sql << "'" << std::endl;
#endif
PGresult *pk = PQexec(connection,(const char *)sql);
#ifdef QGISDEBUG
std::cout << "Got " << PQntuples(pk) << " rows." << std::endl;
#endif
// if we got no tuples we ain't go no pk :)
if( PQntuples(pk) == 0 )
{
#ifdef QGISDEBUG
std::cout << "Relation has no primary key -- "
<< "investigating alternatives" << std::endl;
#endif
// Two options here. If the relation is a table, see if there is
// an oid column that can be used instead.
// If the relation is a view try to find a suitable column to use as
// the primary key.
primaryKey = "";
sql = "select relkind from pg_class where relname = '"
+ tableName + "'";
PGresult* tableType = PQexec(connection, (const char*) sql);
QString type = PQgetvalue(tableType, 0, 0);
PQclear(tableType);
if (type == "r") // the relation is a table
{
#ifdef QGISDEBUG
std::cerr << "Relation is a table. Checking to see if it has an "
<< "oid column.\n";
#endif
sql = "select oid from " + tableName + " limit 1";
PGresult* oidPresent = PQexec(connection, (const char*)sql);
if (PQntuples(oidPresent) == 0)
{
valid = false;
QApplication::restoreOverrideCursor();
QMessageBox::warning(0, QObject::tr("No oid column in table"),
QObject::tr("The table has no primary key nor oid column. \n"
"Qgis requires that the table either has a primary key \n"
"or has a column containing the PostgreSQL oid.\n"
"For better performance the column should be indexed\n"));
QApplication::setOverrideCursor(Qt::waitCursor);
}
else
{
// Could warn the user here that performance will suffer if
// oid isn't indexed (and that they may want to add a
// primary key to the table)
primaryKey = "oid";
}
PQclear(oidPresent);
}
else if (type = "v") // the relation is a view
{
// Have a poke around the view to see if any of the columns
// could be used as the primary key.
// Get the select statement that defines the view
sql = "select definition from pg_views where viewname = '"
+ tableName + "'";
PGresult* def = PQexec(connection, (const char*)sql);
if (PQntuples(def) == 0)
qDebug("View " + tableName + " is not a view!");
QString viewDef = PQgetvalue(def, 0, 0);
PQclear(def);
table_cols cols;
// Find columns in the view that have unique constraints on the
// underlying table columns
findTableColumns(viewDef, cols);
// and choose one of them
primaryKey = chooseViewColumn(cols);
if (primaryKey.isEmpty())
{
valid = false;
QApplication::restoreOverrideCursor();
QMessageBox::warning(0, QObject::tr("No 'primary key' column in view"),
QObject::tr("The view has no column suitable for use as a "
"primary key.\n"
"Qgis requires that the view have a column that can be\n"
"used as a primary key. It should be of type int4 and contain\n"
"a unique value for each row of the table.\n"
"For better performance the column should be derived\n"
"from an indexed column"));
QApplication::setOverrideCursor(Qt::waitCursor);
}
}
else
qWarning("Unexpected relation type of '" + type + "'.");
}
else
{
// store the key column
QString keyString = PQgetvalue(pk,0,0);
QStringList columns = QStringList::split(" ", keyString);
if (columns.count() > 1)
{
valid = false;
QApplication::restoreOverrideCursor();
QMessageBox::warning(0, QObject::tr("No primary key column in table"),
QObject::tr("The table has a primary key that is composed of \n"
"more than one column. Qgis does not currently \n"
"support this."));
QApplication::setOverrideCursor(Qt::waitCursor);
//TODO concatenated key -- can we use this?
#ifdef QGISDEBUG
std::cerr << "Table has a concatenated primary key" << std::endl;
#endif
}
int primaryKeyIndex = attributeFieldsIdMap[columns[0].toInt()];
QgsField fld = attributeFields[primaryKeyIndex];
// if the primary key is 4-byte integer we use it
if (fld.type() == "int4")
{
primaryKey = fld.name();
primaryKeyType = fld.type();
}
else
{
// key is not a 4-byte int -- use the oid instead
primaryKey = "oid";
}
}
PQclear(pk);
#ifdef QGISDEBUG
std::cout << "Primary key is " << primaryKey << std::endl;
#endif
return primaryKey;
}
QString QgsPostgresProvider::chooseViewColumn(const table_cols& cols)
{
// For each relation name and column name need to see if it
// has unique constraints on it, or is a primary key (if not,
// it shouldn't be used). Should then be left with one or more
// entries in the map which can be used as the key.
QString sql, key;
table_cols suitable;
std::vector<table_cols::const_iterator> oids;
table_cols::const_iterator iter = cols.begin();
for (; iter != cols.end(); ++iter)
{
QString view_col = iter->first;
QString table_name = iter->second.first;
QString table_col = iter->second.second;
// This sql returns one or more rows if the column 'table_col' in
// table 'table_name' has one or more columns that satisfy the
// following conditions:
// 1) the column has data type of int4.
// 2) the column has a unique constraint or primary key constraint
// on it.
// 3) the constraint applies just to the column of interest (i.e.,
// it isn't a constraint over multiple columns.
sql = "select * from pg_constraint where conkey[1] = "
"(select attnum from pg_attribute where attname = '"
+ table_col + "' and attrelid = (select oid from "
"pg_class where relname = '" + table_name + "') and "
"atttypid = (select oid from pg_type where typname = 'int4')) and "
"conrelid = (select oid from pg_class where relname = '" +
table_name + "') and (contype = 'p' or contype = 'u') "
" and array_dims(conkey) = '[1:1]'";
#ifdef QGISDEBUG
std::cout << "Column " << view_col << " from "
<< table_name << "." << table_col;
#endif
PGresult* result = PQexec(connection, (const char*)sql);
if (PQntuples(result) == 1)
suitable[view_col] = iter->second;
#ifdef QGISDEBUG
if (PQntuples(result) == 1)
std::cout << " is suitable.\n";
else
std::cout << " is not suitable.\n";
#endif
PQclear(result);
if (table_col == "oid")
oids.push_back(iter);
}
// 'oid' columns in tables don't have a constraint on them, but
// they are useful to consider, so add them in if not already
// here.
for (int i = 0; i < oids.size(); ++i)
{
if (suitable.find(oids[i]->first) == suitable.end())
{
suitable[oids[i]->first] = oids[i]->second;
#ifdef QGISDEBUG
std::cout << "Adding column " << oids[i]->first
<< " as it may be suitable." << std::endl;
#endif
}
}
// If there is more than one suitable column pick one that is
// indexed, else pick one called 'oid' if it exists, else
// pick the first one. If there are none we return an empty string.
if (suitable.size() == 1)
{
key = suitable.begin()->first;
#ifdef QGISDEBUG
std::cout << "Picked column " << key
<< " as it is the only one that was suitable.\n";
#endif
}
else if (suitable.size() > 1)
{
// Search for one with an index
table_cols::const_iterator i = suitable.begin();
for (; i != suitable.end(); ++i)
{
sql = "select * from pg_index where indrelid = (select oid "
"from pg_class where relname = '" + i->second.first +
"') and indkey[0] = "
"(select attnum from pg_attribute where attrelid = "
"(select oid from pg_class where relname = '" +
i->second.first + "') and attname = '" +
i->second.second + "')";
PGresult* result = PQexec(connection, (const char*)sql);
if (PQntuples(result) > 0)
{ // Got one. Use it.
key = i->first;
#ifdef QGISDEBUG
std::cout << "Picked column '" << key
<< "' because it has an index.\n";
#endif
break;
}
PQclear(result);
}
if (key.isEmpty())
{
// If none have indices, choose one that is called 'oid' (if it
// exists). This is legacy support and could be removed in
// future.
i = suitable.find("oid");
if (i != suitable.end())
{
key = i->first;
#ifdef QGISDEBUG
std::cout << "Picked column " << key
<< " as it is probably the postgresql object id "
<< " column (which contains unique values) and there are no"
<< " columns with indices to choose from\n.";
#endif
}
else // else choose the first one in the container
{
key = suitable.begin()->first;
#ifdef QGISDEBUG
std::cout << "Picked column " << key
<< " as it was the first suitable column found"
<< " and there are no"
<< " columns with indices to choose from\n.";
#endif
}
}
}
// XXX Remove when findTableColumns() is enhanced.
//
// Temporary hack to choose a column called 'oid' to
// allow for none of the above working due to the
// findTableColumns() function currently not detecting
// underlying tables if a view refers to a second view.
if (key == "" && cols.find("oid") != cols.end())
key = "oid";
return key;
}
// Finds out the underlying tables and columns for each column in the
// view defined by the given select statement.
void QgsPostgresProvider::findTableColumns(QString select_cmd,
table_cols& cols)
{
QRegExp col_regexp("SELECT *(.+) *FROM");
col_regexp.setCaseSensitive(false);
if (col_regexp.search(select_cmd) > -1)
{
#ifdef QGISDEBUG
std::cerr << "Column definitions are: " << col_regexp.cap(1) << std::endl;
#endif
// Now split the columns definitions on commas to get at actual
// column definitions. The complication is that if column
// definitions involve SQL functions there may be commas inside
// braces. This precludes using something simple like
// QStringList::split() and so the splitting is done the laborous
// way.
QStringList fields;
QString cmd = col_regexp.cap(1);
QString current_item;
bool in_braces = false;
for (int i = 0; i < cmd.length(); ++i)
{
// new item, store preceeding one if it's got something in it
if (cmd[i] == ',' && !in_braces && !current_item.isEmpty())
{
fields.push_back(current_item);
current_item = "";
}
else
current_item += cmd[i];
if (cmd[i] == '(')
in_braces = true;
else if (cmd[i] == ')' && in_braces)
in_braces = false;
}
// Grab the last item if worthwhile
if (!current_item.isEmpty())
fields.push_back(current_item);
// Pick apart the fields to determine the table name, table
// column name, and view column name
for (QStringList::iterator i = fields.begin(); i != fields.end(); ++i)
{
QString f = (*i).simplifyWhiteSpace();
QString view_col_name = "", table_col_name = "",
table_name = "";
// if the view column name is renamed, there will be the
// 'AS' command, so split on that if present.
QRegExp rename_regexp("(.+) AS (.+)");
if (rename_regexp.search(f) > -1)
{
view_col_name = rename_regexp.cap(2);
table_col_name = rename_regexp.cap(1);
}
else
table_col_name = f;
// Now extract the table name from the column
// name. It appears that the sql that we get from
// postgresql always has the full form of 'relation.column'
if (table_col_name.contains('.'))
{
QStringList tt = QStringList::split('.', table_col_name);
table_name = tt[0];
table_col_name = tt[1];
}
else
std::cerr << "The view column definition '" << f
<< "' is not in relation.column form." << std::endl;
// If there was no 'AS', the view column name is the same as
// the column name from the underlying table.
if (view_col_name == "")
view_col_name = table_col_name;
#ifdef QGISDEBUG
std::cerr << "View column '" << view_col_name << "' comes from "
<< table_name << "." << table_col_name << std::endl;
#endif
// If there are braces () in the table_col_name this probably
// indicates that some sql function is being used to transform
// the underlying column. This is probably not
// suitable so exclude such columns from the result.
if (!table_name.contains('('))
cols[view_col_name] =
std::make_pair<QString, QString>(table_name, table_col_name);
else
{
#ifdef QGISDEBUG
std::cerr << "The definition for view column '"
<< view_col_name << "' contains "
"an open bracket in the definition which probably means that "
"a postgreSQL function is being used to derive the column and "
"hence that it is unsuitable for use as a key into the "
"table." << std::endl;
#endif
}
}
}
else
std::cerr << "Couldn't extract view column definitions from '"
<< select_cmd << "'." << std::endl;
// Now ensure that the tables and columns that we found are tables,
// and not views.
/*
// work in progress
// A cache for our results. The key is the table name.
typedef std::map<QString, table_cols> cache_type;
cache_type cache;
static table_cols::iterator iter = cols.begin();
for (; iter != cols.end(); ++iter)
{
// Is it a view?
// Is it in the cache?
cache_type::const_iterator i = cache.find(iter->second.first);
if (i != cache.end())
{
// Store results
iter->second = i->second;
}
else
{
// Get view definition
// Call recursively
table_cols temp_cols;
findTableColumns(VIEW_DEF, temp_cols);
// Store results
iter->second = temp_cols[iter->second.first].second;
}
}
*/
}
// Returns the minimum value of an attribute
QString QgsPostgresProvider::minValue(int position){
// get the field name
QgsField fld = attributeFields[position];
QString sql;
if(sqlWhereClause.isEmpty())
{
sql = QString("select min(%1) from %2").arg(fld.name()).arg(tableName);
}
else
{
sql = QString("select min(%1) from %2").arg(fld.name()).arg(tableName)+" where "+sqlWhereClause;
}
PGresult *rmin = PQexec(connection,(const char *)sql);
QString minValue = PQgetvalue(rmin,0,0);
PQclear(rmin);
return minValue;
}
// Returns the maximum value of an attribute
QString QgsPostgresProvider::maxValue(int position){
// get the field name
QgsField fld = attributeFields[position];
QString sql;
if(sqlWhereClause.isEmpty())
{
sql = QString("select max(%1) from %2").arg(fld.name()).arg(tableName);
}
else
{
sql = QString("select max(%1) from %2").arg(fld.name()).arg(tableName)+" where "+sqlWhereClause;
}
PGresult *rmax = PQexec(connection,(const char *)sql);
QString maxValue = PQgetvalue(rmax,0,0);
PQclear(rmax);
return maxValue;
}
bool QgsPostgresProvider::isValid(){
return valid;
}
bool QgsPostgresProvider::addFeature(QgsFeature* f)
{
if(f)
{
QString insert("INSERT INTO ");
insert+=tableName;
insert+="(";
//add the name of the geometry column to the insert statement
insert+=geometryColumn;//first the geometry
//add the names of the other fields to the insert
std::vector<QgsFeatureAttribute> attributevec=f->attributeMap();
for(std::vector<QgsFeatureAttribute>::iterator it=attributevec.begin();it!=attributevec.end();++it)
{
QString fieldname=it->fieldName();
if(fieldname!=geometryColumn)
{
insert+=",";
insert+=fieldname;
}
}
insert+=") VALUES (GeomFromWKB('";
//add the wkb geometry to the insert statement
unsigned char* geom=f->getGeometry();
for(int i=0;i<f->getGeometrySize();++i)
{
QString hex=QString::number((int)geom[i],16).upper();
if(hex.length()==1)
{
hex="0"+hex;
}
insert+=hex;
}
insert+="',-1)";
//add the field values to the insert statement
for(std::vector<QgsFeatureAttribute>::iterator it=attributevec.begin();it!=attributevec.end();++it)
{
if(it->fieldName()!=geometryColumn)
{
QString fieldvalue=it->fieldValue();
bool charactertype=false;
insert+=",";
//add quotes if the field is a characted type
if(fieldvalue!="NULL")
{
for(std::vector<QgsField>::iterator iter=attributeFields.begin();iter!=attributeFields.end();++iter)
{
if(iter->name()==it->fieldName())
{
if(iter->type().contains("char",false)>0||iter->type()=="text")
{
charactertype=true;
}
}
}
}
if(charactertype)
{
insert+="'";
}
insert+=fieldvalue;
if(charactertype)
{
insert+="'";
}
}
}
insert+=")";
#ifdef QGISDEBUG
qWarning("insert statement is: "+insert);
#endif
//send INSERT statement and do error handling
PGresult* result=PQexec(connection, (const char *)insert);
if(result==0)
{
QMessageBox::information(0,"INSERT error","An error occured during feature insertion",QMessageBox::Ok);
return false;
}
ExecStatusType message=PQresultStatus(result);
if(message==PGRES_FATAL_ERROR)
{
QMessageBox::information(0,"INSERT error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
return false;
}
return true;
}
return false;
}
QString QgsPostgresProvider::getDefaultValue(const QString& attr, QgsFeature* f)
{
return "NULL";
}
bool QgsPostgresProvider::deleteFeature(int id)
{
QString sql("DELETE FROM "+tableName+" WHERE "+primaryKey+" = "+QString::number(id));
#ifdef QGISDEBUG
qWarning("delete sql: "+sql);
#endif
//send DELETE statement and do error handling
PGresult* result=PQexec(connection, (const char *)sql);
if(result==0)
{
QMessageBox::information(0,"DELETE error","An error occured during deletion from disk",QMessageBox::Ok);
return false;
}
ExecStatusType message=PQresultStatus(result);
if(message==PGRES_FATAL_ERROR)
{
QMessageBox::information(0,"DELETE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
return false;
}
return true;
}
/**
* Check to see if GEOS is available
*/
bool QgsPostgresProvider::hasGEOS(PGconn *connection){
// make sure info is up to date for the current connection
postgisVersion(connection);
// get geos capability
return geosAvailable;
}
/* Functions for determining available features in postGIS */
QString QgsPostgresProvider::postgisVersion(PGconn *connection){
PGresult *result = PQexec(connection, "select postgis_version()");
postgisVersionInfo = PQgetvalue(result,0,0);
#ifdef QGISDEBUG
std::cerr << "PostGIS version info: " << postgisVersionInfo << std::endl;
#endif
// assume no capabilities
geosAvailable = false;
gistAvailable = false;
projAvailable = false;
// parse out the capabilities and store them
QStringList postgisParts = QStringList::split(" ", postgisVersionInfo);
QStringList geos = postgisParts.grep("GEOS");
if(geos.size() == 1){
geosAvailable = (geos[0].find("=1") > -1);
}
QStringList gist = postgisParts.grep("STATS");
if(gist.size() == 1){
gistAvailable = (geos[0].find("=1") > -1);
}
QStringList proj = postgisParts.grep("PROJ");
if(proj.size() == 1){
projAvailable = (proj[0].find("=1") > -1);
}
return postgisVersionInfo;
}
bool QgsPostgresProvider::addFeatures(std::list<QgsFeature*> const flist)
{
bool returnvalue=true;
for(std::list<QgsFeature*>::const_iterator it=flist.begin();it!=flist.end();++it)
{
if(!addFeature(*it))
{
returnvalue=false;
}
}
return returnvalue;
}
bool QgsPostgresProvider::deleteFeatures(std::list<int> const & id)
{
bool returnvalue=true;
for(std::list<int>::const_iterator it=id.begin();it!=id.end();++it)
{
if(!deleteFeature(*it))
{
returnvalue=false;
}
}
return returnvalue;
}
bool QgsPostgresProvider::addAttributes(std::map<QString,QString> const & name)
{
bool returnvalue=true;
PQexec(connection,"BEGIN");
for(std::map<QString,QString>::const_iterator iter=name.begin();iter!=name.end();++iter)
{
QString sql="ALTER TABLE "+tableName+" ADD COLUMN "+(*iter).first+" "+(*iter).second;
#ifdef QGISDEBUG
qWarning(sql);
#endif
//send sql statement and do error handling
PGresult* result=PQexec(connection, (const char *)sql);
if(result==0)
{
returnvalue=false;
ExecStatusType message=PQresultStatus(result);
if(message==PGRES_FATAL_ERROR)
{
QMessageBox::information(0,"ALTER TABLE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
}
}
}
PQexec(connection,"COMMIT");
reset();
return returnvalue;
}
bool QgsPostgresProvider::deleteAttributes(std::set<QString> const & name)
{
bool returnvalue=true;
PQexec(connection,"BEGIN");
for(std::set<QString>::const_iterator iter=name.begin();iter!=name.end();++iter)
{
QString sql="ALTER TABLE "+tableName+" DROP COLUMN "+(*iter);
#ifdef QGISDEBUG
qWarning(sql);
#endif
//send sql statement and do error handling
PGresult* result=PQexec(connection, (const char *)sql);
if(result==0)
{
returnvalue=false;
ExecStatusType message=PQresultStatus(result);
if(message==PGRES_FATAL_ERROR)
{
QMessageBox::information(0,"ALTER TABLE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
}
}
else
{
//delete the attribute from attributeFields
for(std::vector<QgsField>::iterator it=attributeFields.begin();it!=attributeFields.end();++it)
{
if((*it).name()==(*iter))
{
attributeFields.erase(it);
break;
}
}
}
}
PQexec(connection,"COMMIT");
reset();
return returnvalue;
}
bool QgsPostgresProvider::changeAttributeValues(std::map<int,std::map<QString,QString> > const & attr_map)
{
bool returnvalue=true;
PQexec(connection,"BEGIN");
for(std::map<int,std::map<QString,QString> >::const_iterator iter=attr_map.begin();iter!=attr_map.end();++iter)
{
for(std::map<QString,QString>::const_iterator siter=(*iter).second.begin();siter!=(*iter).second.end();++siter)
{
QString value=(*siter).second;
//find out, if value contains letters and quote if yes
bool text=false;
for(int i=0;i<value.length();++i)
{
if(value[i].isLetter())
{
text=true;
}
}
if(text)
{
value.prepend("'");
value.append("'");
}
QString sql="UPDATE "+tableName+" SET "+(*siter).first+"="+value+" WHERE " +primaryKey+"="+QString::number((*iter).first);
#ifdef QGISDEBUG
qWarning(sql);
#endif
//send sql statement and do error handling
PGresult* result=PQexec(connection, (const char *)sql);
if(result==0)
{
returnvalue=false;
ExecStatusType message=PQresultStatus(result);
if(message==PGRES_FATAL_ERROR)
{
QMessageBox::information(0,"UPDATE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
}
}
}
}
PQexec(connection,"COMMIT");
reset();
return returnvalue;
}
bool QgsPostgresProvider::supportsSaveAsShapefile() const
{
return false;
}
int QgsPostgresProvider::capabilities() const
{
return ( QgsVectorDataProvider::AddFeatures |
QgsVectorDataProvider::DeleteFeatures |
QgsVectorDataProvider::ChangeAttributeValues |
QgsVectorDataProvider::AddAttributes |
QgsVectorDataProvider::DeleteAttributes );
}
void QgsPostgresProvider::setSubsetString(QString theSQL)
{
sqlWhereClause=theSQL;
// need to recalculate the number of features...
getFeatureCount();
calculateExtents();
}
long QgsPostgresProvider::getFeatureCount()
{
// get total number of features
// First get an approximate count; then delegate to
// a thread the task of getting the full count.
QString sql = "select reltuples from pg_catalog.pg_class where relname = '" +
tableName + "'";
std::cerr << "QgsPostgresProvider: Running SQL: " <<
sql << std::endl;
//QString sql = "select count(*) from " + tableName;
//if(sqlWhereClause.length() > 0)
//{
// sql += " where " + sqlWhereClause;
//}
PGresult *result = PQexec(connection, (const char *) sql);
#ifdef QGISDEBUG
std::cerr << "QgsPostgresProvider: Approximate Number of features as text: " <<
PQgetvalue(result, 0, 0) << std::endl;
#endif
numberFeatures = QString(PQgetvalue(result, 0, 0)).toLong();
PQclear(result);
#ifdef QGISDEBUG
std::cerr << "QgsPostgresProvider: Approximate Number of features: " <<
numberFeatures << std::endl;
#endif
return numberFeatures;
}
// TODO: use the estimateExtents procedure of PostGIS and PostgreSQL 8
// This tip thanks to #qgis irc nick "creeping"
void QgsPostgresProvider::calculateExtents()
{
// get the approximate extent by retreiving the bounding box
// of the first few items with a geometry
QString sql = "select box3d(" + geometryColumn + ") from " + tableName +
" where ";
if(sqlWhereClause.length() > 0)
{
sql += "(" + sqlWhereClause + ") and ";
}
sql += "not IsEmpty(" + geometryColumn + ") limit 5";
#if WASTE_TIME
sql = "select xmax(extent(" + geometryColumn + ")) as xmax,"
"xmin(extent(" + geometryColumn + ")) as xmin,"
"ymax(extent(" + geometryColumn + ")) as ymax," "ymin(extent(" + geometryColumn + ")) as ymin" " from " + tableName;
#endif
#ifdef QGISDEBUG
qDebug("QgsPostgresProvider::calculateExtents - Getting approximate extent using: '" + sql + "'");
#endif
PGresult *result = PQexec(connection, (const char *) sql);
// TODO: Guard against the result having no rows
for (int i = 0; i < PQntuples(result); i++)
{
std::string box3d = PQgetvalue(result, i, 0);
if (0 == i)
{
// create the initial extent
layerExtent = QgsPostGisBox3d(box3d);
}
else
{
// extend the initial extent
QgsPostGisBox3d b = QgsPostGisBox3d(box3d);
layerExtent.combineExtentWith( &b );
}
std::cout << "QgsPostgresProvider: After row " << i << ", extent is: "
<< layerExtent.xMin() << ", " << layerExtent.yMin() <<
" " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl;
}
#ifdef QGISDEBUG
QString xMsg;
QTextOStream(&xMsg).precision(18);
QTextOStream(&xMsg).width(18);
QTextOStream(&xMsg) << "QgsPostgresProvider: Set extents to: " << layerExtent.
xMin() << ", " << layerExtent.yMin() << " " << layerExtent.xMax() << ", " << layerExtent.yMax();
std::cerr << xMsg << std::endl;
#endif
std::cout << "QgsPostgresProvider: Set limit 5 extents to: "
<< layerExtent.xMin() << ", " << layerExtent.yMin() <<
" " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl;
// clear query result
PQclear(result);
}
/**
* Event sink for events from threads
*/
void QgsPostgresProvider::customEvent( QCustomEvent * e )
{
std::cout << "QgsPostgresProvider: received a custom event " << e->type() << std::endl;
switch ( e->type() )
{
case (QEvent::Type) QGis::ProviderExtentCalcEvent:
std::cout << "QgsPostgresProvider: extent has been calculated" << std::endl;
// Collect the new extent from the event and set this layer's
// extent with it.
setExtent( (QgsRect*) e->data() );
std::cout << "QgsPostgresProvider: new extent has been saved" << std::endl;
std::cout << "QgsPostgresProvider: Set extent to: "
<< layerExtent.xMin() << ", " << layerExtent.yMin() <<
" " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl;
std::cout << "QgsPostgresProvider: emitting fullExtentCalculated()" << std::endl;
emit fullExtentCalculated();
// TODO: Only uncomment this when the overview map canvas has been subclassed
// from the QgsMapCanvas
// std::cout << "QgsPostgresProvider: emitting repaintRequested()" << std::endl;
// emit repaintRequested();
break;
case (QEvent::Type) QGis::ProviderCountCalcEvent:
std::cout << "QgsPostgresProvider: count has been calculated" << std::endl;
QgsProviderCountCalcEvent* e1 = (QgsProviderCountCalcEvent*) e;
numberFeatures = e1->numberFeatures();
std::cout << "QgsPostgresProvider: count is " << numberFeatures << std::endl;
break;
}
std::cout << "QgsPostgresProvider: Finished processing custom event " << e->type() << std::endl;
}
bool QgsPostgresProvider::deduceEndian()
{
// need to store the PostgreSQL endian format used in binary cursors
// since it appears that starting with
// version 7.4, binary cursors return data in XDR whereas previous versions
// return data in the endian of the server
QString firstOid = "select oid from " + tableName + " limit 1";
PGresult * oidResult = PQexec(connection, firstOid);
// get the int value from a "normal" select
QString oidValue = PQgetvalue(oidResult,0,0);
#ifdef QGISDEBUG
std::cerr << "Creating binary cursor" << std::endl;
#endif
// get the same value using a binary cursor
PQexec(connection,"begin work");
QString oidDeclare = QString("declare oidcursor binary cursor for select oid from %1 where oid = %2").arg(tableName).arg(oidValue);
// set up the cursor
PQexec(connection, (const char *)oidDeclare);
QString fetch = "fetch forward 1 from oidcursor";
#ifdef QGISDEBUG
std::cerr << "Fetching a record and attempting to get check endian-ness" << std::endl;
#endif
PGresult *fResult = PQexec(connection, (const char *)fetch);
PQexec(connection, "end work");
if(PQntuples(fResult) > 0){
// get the oid value from the binary cursor
int oid = *(int *)PQgetvalue(fResult,0,0);
//--std::cout << "Got oid of " << oid << " from the binary cursor" << std::endl;
//--std::cout << "First oid is " << oidValue << std::endl;
// compare the two oid values to determine if we need to do an endian swap
if(oid == oidValue.toInt()){
swapEndian = false;
}else{
swapEndian = true;
}
PQclear(fResult);
}
return swapEndian;
}
bool QgsPostgresProvider::getGeometryDetails()
{
QString sql = "select f_geometry_column,type,srid from geometry_columns"
" where f_table_name='" + tableName + "' and f_geometry_column = '" +
geometryColumn + "' and f_table_schema = '" + mSchema + "'";
#ifdef QGISDEBUG
std::cerr << "Getting geometry column: " + sql << std::endl;
#endif
QString fType;
valid = false;
PGresult *result = PQexec(connection, (const char *) sql);
if (PQntuples(result) > 0)
{
valid = true;
#ifdef QGISDEBUG
std::cout << "geometry column query returned "
<< PQntuples(result) << std::endl;
std::cout << "column number of srid is "
<< PQfnumber(result, "srid") << std::endl;
#endif
srid = PQgetvalue(result, 0, PQfnumber(result, "srid"));
fType = PQgetvalue(result, 0, PQfnumber(result, "type"));
if (fType == "POINT" || fType == "MULTIPOINT")
geomType = QGis::WKBPoint;
else if (fType == "LINESTRING" || fType == "MULTILINESTRING")
geomType = QGis::WKBLineString;
else if (fType == "POLYGON" || fType == "MULTIPOLYGON")
geomType = QGis::WKBPolygon;
PQclear(result);
}
else
{
// Didn't find what we need in the geometry_columns table, so
// get stuff from the relevant column instead. This may (will?)
// fail if there is no data in the relevant table.
PQclear(result);
sql = "select "
"srid(" + geometryColumn + "), "
"geometrytype(" + geometryColumn + ") from " +
tableName + " limit 1";
result = PQexec(connection, (const char*) sql);
if (PQntuples(result) > 0)
{
valid = true;
srid = PQgetvalue(result, 0, PQfnumber(result, "srid"));
fType = PQgetvalue(result, 0, PQfnumber(result, "geometrytype"));
if (fType == "POINT" || fType == "MULTIPOINT")
geomType = QGis::WKBPoint;
else if (fType == "LINESTRING" || fType == "MULTILINESTRING")
geomType = QGis::WKBLineString;
else if (fType == "POLYGON" || fType == "MULTIPOLYGON")
geomType = QGis::WKBPolygon;
}
PQclear(result);
}
#ifdef QGISDEBUG
std::cout << "SRID is " << srid << '\n'
<< "type is " << fType << '\n'
<< "Feature type is " << geomType << '\n'
<< "Feature type name is "
<< QGis::qgisFeatureTypes[geomType] << std::endl;
#endif
return valid;
}
/**
* Class factory to return a pointer to a newly created
* QgsPostgresProvider object
*/
QGISEXTERN QgsPostgresProvider * classFactory(const char *uri)
{
return new QgsPostgresProvider(uri);
}
/** Required key function (used to map the plugin to a data store type)
*/
QGISEXTERN QString providerKey(){
return QString("postgres");
}
/**
* Required description function
*/
QGISEXTERN QString description(){
return QString("PostgreSQL/PostGIS data provider");
}
/**
* Required isProvider function. Used to determine if this shared library
* is a data provider plugin
*/
QGISEXTERN bool isProvider(){
return true;
}