~ubuntu-branches/ubuntu/wily/qgis/wily

« back to all changes in this revision

Viewing changes to src/providers/postgres/qgspostgresprovider.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Johan Van de Wauw
  • Date: 2010-07-11 20:23:24 UTC
  • mfrom: (3.1.4 squeeze)
  • Revision ID: james.westby@ubuntu.com-20100711202324-5ktghxa7hracohmr
Tags: 1.4.0+12730-3ubuntu1
* Merge from Debian unstable (LP: #540941).
* Fix compilation issues with QT 4.7
* Add build-depends on libqt4-webkit-dev 

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
 *                                                                         *
16
16
 ***************************************************************************/
17
17
 
18
 
/* $Id: qgspostgresprovider.cpp 6837 2007-03-27 17:51:29Z homann $ */
19
 
 
20
 
#include <fstream>
21
 
#include <iostream>
22
 
#include <cassert>
23
 
 
24
 
#include <QStringList>
25
 
#include <QApplication>
26
 
#include <QMessageBox>
27
 
#include <QEvent>
28
 
#include <QCustomEvent>
29
 
#include <QTextOStream>
30
 
#include <QRegExp>
31
 
#include <QMap>
32
 
 
33
 
// for ntohl
 
18
/* $Id$ */
 
19
 
 
20
// for htonl
34
21
#ifdef WIN32
35
22
#include <winsock.h>
36
23
#else
38
25
#endif
39
26
 
40
27
#include <qgis.h>
 
28
#include <qgsapplication.h>
41
29
#include <qgsfeature.h>
42
30
#include <qgsfield.h>
43
 
#include <qgsrect.h>
44
 
#include <qgsmessageviewer.h>
 
31
#include <qgsgeometry.h>
 
32
#include <qgsmessageoutput.h>
 
33
#include <qgsrectangle.h>
 
34
#include <qgscoordinatereferencesystem.h>
45
35
 
46
36
#include "qgsprovidercountcalcevent.h"
47
37
#include "qgsproviderextentcalcevent.h"
54
44
#include "qgspostgisbox3d.h"
55
45
#include "qgslogger.h"
56
46
 
57
 
#ifdef WIN32
58
 
#define QGISEXTERN extern "C" __declspec( dllexport )
59
 
#else
60
 
#define QGISEXTERN extern "C"
61
 
#endif
62
 
 
63
47
const QString POSTGRES_KEY = "postgres";
64
48
const QString POSTGRES_DESCRIPTION = "PostgreSQL/PostGIS data provider";
65
49
 
 
50
QMap<QString, QgsPostgresProvider::Conn *> QgsPostgresProvider::Conn::connectionsRO;
 
51
QMap<QString, QgsPostgresProvider::Conn *> QgsPostgresProvider::Conn::connectionsRW;
 
52
int QgsPostgresProvider::providerIds = 0;
66
53
 
67
 
QgsPostgresProvider::QgsPostgresProvider(QString const & uri)
68
 
  :  QgsVectorDataProvider(uri),
69
 
  geomType(QGis::WKBUnknown),
70
 
  gotPostgisVersion(FALSE)
 
54
QgsPostgresProvider::QgsPostgresProvider( QString const & uri )
 
55
    : QgsVectorDataProvider( uri ),
 
56
    mFetching( false ),
 
57
    geomType( QGis::WKBUnknown ),
 
58
    mFeatureQueueSize( 200 ),
 
59
    mPrimaryKeyDefault( QString::null )
71
60
{
72
61
  // assume this is a valid layer until we determine otherwise
73
62
  valid = true;
74
 
  /* OPEN LOG FILE */
75
 
 
76
 
  // Make connection to the data source
77
 
  // For postgres, the connection information is passed as a space delimited
78
 
  // string:
79
 
  //  host=192.168.1.5 dbname=test port=5342 user=gsherman password=xxx table=tablename
80
 
 
81
 
  // A little bit of backwards compability. At r6193 the schema/table
82
 
  // names became fully quoted, but with the problem that earlier
83
 
  // project files then didn't load. This bit of code puts in the
84
 
  // quotes that are now required.
85
 
  QString uriModified = uri;
86
 
  int start = uriModified.indexOf("table=\"");
87
 
  if (start == -1)
88
 
    {
89
 
      // Need to put in some "'s
90
 
      start = uriModified.indexOf("table=");
91
 
      uriModified.insert(start+6, '"');
92
 
      int start_dot = uriModified.indexOf('.', start+7);
93
 
      if (start_dot != -1)
94
 
        {
95
 
          uriModified.insert(start_dot, '"');
96
 
          uriModified.insert(start_dot+2, '"');
97
 
        }
98
 
      // and one at the end
99
 
      int end = uriModified.indexOf(' ',start);
100
 
      if (end != -1)
101
 
        uriModified.insert(end, '"');
102
 
    }
103
 
 
104
 
  // Strip the table and sql statement name off and store them
105
 
  int sqlStart = uriModified.find(" sql");
106
 
  int tableStart = uriModified.find("table=");
107
 
#ifdef QGISDEBUG
108
 
  qDebug(  "****************************************");
109
 
  qDebug(  "****   Postgresql Layer Creation   *****" );
110
 
  qDebug(  "****************************************");
111
 
  qDebug(  (const char*)(QString("URI: ") + uriModified).toLocal8Bit().data() );
112
 
  QString msg;
113
 
 
114
 
  qDebug(  "tableStart: " + msg.setNum(tableStart) );
115
 
  qDebug(  "sqlStart: " + msg.setNum(sqlStart));
116
 
#endif 
117
 
  mTableName = uriModified.mid(tableStart + 6, sqlStart - tableStart -6);
118
 
 
119
 
  if(sqlStart > -1)
120
 
  { 
121
 
    sqlWhereClause = uriModified.mid(sqlStart + 5);
122
 
  }
123
 
  else
124
 
  {
125
 
    sqlWhereClause = QString::null;
126
 
  }
127
 
  QString connInfo = uriModified.left(uriModified.find("table="));
128
 
#ifdef QGISDEBUG
129
 
  qDebug( (const char*)(QString("Table name is ") + mTableName).toLocal8Bit().data());
130
 
  qDebug( (const char*)(QString("SQL is ") + sqlWhereClause).toLocal8Bit().data() );
131
 
  qDebug( "Connection info is " + connInfo);
132
 
#endif
133
 
 
134
 
  // Pick up some stuff from the uriModified: basically two bits of text
135
 
  // inside double quote marks, separated by a .
136
 
  QRegExp reg("\"(.+)\"\\.\"(.+)\".+\\((.+)\\)");
137
 
  reg.indexIn(mTableName);
138
 
  QStringList stuff = reg.capturedTexts();
139
 
 
140
 
  mSchemaName = stuff[1];
141
 
  mTableName = stuff[2];
142
 
  geometryColumn = stuff[3];
 
63
 
 
64
  providerId = providerIds++;
 
65
 
 
66
  QgsDebugMsg( "Postgresql Layer Creation" );
 
67
  QgsDebugMsg( "URI: " + uri );
 
68
 
 
69
  mUri = QgsDataSourceURI( uri );
 
70
 
 
71
  /* populate members from the uri structure */
 
72
  mSchemaName = mUri.schema();
 
73
  mTableName = mUri.table();
 
74
  geometryColumn = mUri.geometryColumn();
 
75
  sqlWhereClause = mUri.sql();
 
76
  primaryKey = mUri.keyColumn();
143
77
 
144
78
  // Keep a schema qualified table name for convenience later on.
145
 
  if (mSchemaName.length() > 0)
146
 
    mSchemaTableName = "\"" + mSchemaName + "\".\"" + mTableName + "\"";
147
 
  else
148
 
    mSchemaTableName = "\"" + mTableName + "\"";
149
 
 
150
 
  /* populate the uri structure */
151
 
  mUri.schema = mSchemaName;
152
 
  mUri.table = mTableName;
153
 
  mUri.geometryColumn = geometryColumn;
154
 
  mUri.sql = sqlWhereClause;
155
 
  // parse the connection info
156
 
  QStringList conParts = QStringList::split(" ", connInfo);
157
 
  QStringList parm = QStringList::split("=", conParts[0]);
158
 
  if(parm.size() == 2)
159
 
  {
160
 
    mUri.host = parm[1];
161
 
  }
162
 
  parm = QStringList::split("=", conParts[1]);
163
 
  if(parm.size() == 2)
164
 
  {
165
 
    mUri.database = parm[1];
166
 
  }
167
 
  parm = QStringList::split("=", conParts[2]);
168
 
  if(parm.size() == 2)
169
 
  {
170
 
    mUri.port = parm[1];
171
 
  }
172
 
 
173
 
  parm = QStringList::split("=", conParts[3]);
174
 
  if(parm.size() == 2)
175
 
  {
176
 
    mUri.username = parm[1];
177
 
  }
178
 
 
179
 
  // The password can have '=' and ' ' characters in it, so we can't
180
 
  // use the split on '=' and ' ' technique - use indexOf()
181
 
  // instead.
182
 
  QString key="password='";
183
 
  int i = connInfo.indexOf(key);
184
 
  if (i != -1)
185
 
    {
186
 
      QString password = connInfo.mid(i+key.length());
187
 
      // Now walk through the string till we find a ' character, but
188
 
      // need to allow for an escaped ' character (which will be the
189
 
      // \' character pair).
190
 
      int n = 0;
191
 
      bool escaped = false;
192
 
      while (n < password.length() && (password[n] != '\'' || escaped))
193
 
        {
194
 
          if (password[n] == '\\')
195
 
            escaped = true;
196
 
          else
197
 
            escaped = false;
198
 
          n++;
199
 
        }
200
 
      // The -1 is to remove the trailing ' character
201
 
      mUri.password = password.left(n);
202
 
    }
203
 
  else
204
 
  /* end uri structure */
205
 
 
206
 
  QgsDebugMsg("Geometry column is: " + geometryColumn);
207
 
  QgsDebugMsg("Schema is: " + mSchemaName);
208
 
  QgsDebugMsg("Table name is: " + mTableName);
209
 
 
210
 
  //QString logFile = "./pg_provider_" + mTableName + ".log";
211
 
  //pLog.open((const char *)logFile);
212
 
 
213
 
  QgsDebugMsg("Opened log file for " + mTableName);
214
 
 
215
 
  PGconn *pd = PQconnectdb((const char *) connInfo);
216
 
  // check the connection status
217
 
  if (PQstatus(pd) == CONNECTION_OK)
218
 
  {
219
 
    // store the connection for future use
220
 
    connection = pd;
221
 
 
222
 
    //set client encoding to unicode because QString uses UTF-8 anyway
223
 
    QgsDebugMsg("setting client encoding to UNICODE");
224
 
 
225
 
    int errcode=PQsetClientEncoding(connection, "UNICODE");
226
 
#ifdef QGISDEBUG
227
 
    if(errcode==0)
228
 
    {
229
 
        qWarning("encoding successfully set");
230
 
    }
231
 
    else if(errcode==-1)
232
 
    {
233
 
        qWarning("error in setting encoding");
234
 
    }
235
 
    else
236
 
    {
237
 
        qWarning("undefined return value from encoding setting");
238
 
    }
239
 
#endif
240
 
 
241
 
    QgsDebugMsg("Checking for select permission on the relation");
242
 
 
243
 
    // Check that we can read from the table (i.e., we have
244
 
    // select permission).
245
 
    QString sql = "select * from " + mSchemaTableName + " limit 1";
246
 
    PGresult* testAccess = PQexec(pd, (const char*)(sql.utf8()));
247
 
    if (PQresultStatus(testAccess) != PGRES_TUPLES_OK)
248
 
    {
249
 
      showMessageBox(tr("Unable to access relation"),
250
 
          tr("Unable to access the ") + mSchemaTableName + 
251
 
          tr(" relation.\nThe error message from the database was:\n") +
252
 
          QString(PQresultErrorMessage(testAccess)) + ".\n" + 
253
 
          "SQL: " + sql);
254
 
      PQclear(testAccess);
255
 
      valid = false;
256
 
      return;
257
 
    }
258
 
    PQclear(testAccess);
259
 
 
260
 
    /* Check to see if we have GEOS support and if not, warn the user about
261
 
       the problems they will see :) */
262
 
    QgsDebugMsg("Checking for GEOS support");
263
 
 
264
 
    if(!hasGEOS(pd))
265
 
    {
266
 
      showMessageBox(tr("No GEOS Support!"),
267
 
                     tr("Your PostGIS installation has no GEOS support.\n"
268
 
                        "Feature selection and identification will not "
269
 
                        "work properly.\nPlease install PostGIS with " 
270
 
                        "GEOS support (http://geos.refractions.net)"));
271
 
    }
272
 
    //QgsDebugMsg("Connection to the database was successful");
273
 
 
274
 
    if (getGeometryDetails()) // gets srid and geometry type
275
 
    {
276
 
      deduceEndian();
277
 
      calculateExtents();
278
 
      getFeatureCount();
279
 
 
280
 
      // Populate the field vector for this layer. The field vector contains
281
 
      // field name, type, length, and precision (if numeric)
282
 
      sql = "select * from " + mSchemaTableName + " limit 1";
283
 
 
284
 
      PGresult* result = PQexec(pd, (const char *) (sql.utf8()));
285
 
      //QgsDebugMsg("Field: Name, Type, Size, Modifier:");
286
 
      for (int i = 0; i < PQnfields(result); i++)
287
 
      {
288
 
        QString fieldName = PQfname(result, i);
289
 
        int fldtyp = PQftype(result, i);
290
 
        QString typOid = QString().setNum(fldtyp);
291
 
        int fieldModifier = PQfmod(result, i);
292
 
 
293
 
        sql = "select typelem from pg_type where typelem = " + typOid + " and typlen = -1";
294
 
        //QgsDebugMsg(sql);
295
 
        PGresult *oidResult = PQexec(pd, (const char *) sql);
296
 
        // get the oid of the "real" type
297
 
        QString poid = PQgetvalue(oidResult, 0, PQfnumber(oidResult, "typelem"));
298
 
        PQclear(oidResult);
299
 
 
300
 
        sql = "select typname, typlen from pg_type where oid = " + poid;
301
 
        //QgsDebugMsg(sql);
302
 
        oidResult = PQexec(pd, (const char *) sql);
303
 
        QString fieldType = PQgetvalue(oidResult, 0, 0);
304
 
        QString fieldSize = PQgetvalue(oidResult, 0, 1);
305
 
        PQclear(oidResult);
306
 
 
307
 
        sql = "select oid from pg_class where relname = '" + mTableName + "' and relnamespace = ("
308
 
          "select oid from pg_namespace where nspname = '" + mSchemaName + "')";
309
 
        PGresult *tresult= PQexec(pd, (const char *)(sql.utf8()));
310
 
        QString tableoid = PQgetvalue(tresult, 0, 0);
311
 
        PQclear(tresult);
312
 
 
313
 
        sql = "select attnum from pg_attribute where attrelid = " + tableoid + " and attname = '" + fieldName + "'";
314
 
        tresult = PQexec(pd, (const char *)(sql.utf8()));
315
 
        QString attnum = PQgetvalue(tresult, 0, 0);
316
 
        PQclear(tresult);
317
 
 
318
 
        QgsDebugMsg("Field: " + attnum + " maps to " + QString::number(i) + " " + fieldName + ", " +  
319
 
                    fieldType + " (" + QString::number(fldtyp) + "),  " + fieldSize + ", " + QString::number(fieldModifier));
320
 
 
321
 
        attributeFieldsIdMap[attnum.toInt()] = i;
322
 
 
323
 
        if(fieldName!=geometryColumn)
324
 
        {
325
 
          attributeFields.push_back(QgsField(fieldName, fieldType, fieldSize.toInt(), fieldModifier));
326
 
        }
327
 
      }
328
 
      PQclear(result);
329
 
 
330
 
      // set the primary key
331
 
      getPrimaryKey();
332
 
  
333
 
      // Set the postgresql message level so that we don't get the
334
 
      // 'there is no transaction in progress' warning.
 
79
  mSchemaTableName = mUri.quotedTablename();
 
80
 
 
81
  QgsDebugMsg( "Table name is " + mTableName );
 
82
  QgsDebugMsg( "SQL is " + sqlWhereClause );
 
83
  QgsDebugMsg( "Connection info is " + mUri.connectionInfo() );
 
84
 
 
85
  QgsDebugMsg( "Geometry column is: " + geometryColumn );
 
86
  QgsDebugMsg( "Schema is: " + mSchemaName );
 
87
  QgsDebugMsg( "Table name is: " + mTableName );
 
88
 
 
89
  connectionRW = NULL;
 
90
  connectionRO = Conn::connectDb( mUri.connectionInfo(), true );
 
91
  if ( connectionRO == NULL )
 
92
  {
 
93
    valid = false;
 
94
    return;
 
95
  }
 
96
 
 
97
  QgsDebugMsg( "Checking for permissions on the relation" );
 
98
 
 
99
  // Check that we can read from the table (i.e., we have
 
100
  // select permission).
 
101
  QString sql = QString( "select * from %1 limit 1" ).arg( mSchemaTableName );
 
102
  Result testAccess = connectionRO->PQexec( sql );
 
103
  if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
 
104
  {
 
105
    showMessageBox( tr( "Unable to access relation" ),
 
106
                    tr( "Unable to access the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
 
107
                    .arg( mSchemaTableName )
 
108
                    .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
 
109
                    .arg( sql ) );
 
110
    valid = false;
 
111
    disconnectDb();
 
112
    return;
 
113
  }
 
114
 
 
115
  sql = QString( "SELECT "
 
116
                 "has_table_privilege(%1,'DELETE'),"
 
117
                 "has_table_privilege(%1,'UPDATE'),"
 
118
                 "has_table_privilege(%1,'INSERT'),"
 
119
                 "current_schema()" )
 
120
        .arg( quotedValue( mSchemaTableName ) );
 
121
 
 
122
  testAccess = connectionRO->PQexec( sql );
 
123
  if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
 
124
  {
 
125
    showMessageBox( tr( "Unable to access relation" ),
 
126
                    tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
 
127
                    .arg( mSchemaTableName )
 
128
                    .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
 
129
                    .arg( sql ) );
 
130
    valid = false;
 
131
    disconnectDb();
 
132
    return;
 
133
  }
 
134
 
 
135
  // postgres has fast access to features at id (thanks to primary key / unique index)
 
136
  // the latter flag is here just for compatibility
 
137
  enabledCapabilities = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId;
 
138
 
 
139
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 0 ) ) == "t" )
 
140
  {
 
141
    // DELETE
 
142
    enabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
 
143
  }
 
144
 
 
145
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 1 ) ) == "t" )
 
146
  {
 
147
    // UPDATE
 
148
    enabledCapabilities |= QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::ChangeAttributeValues;
 
149
  }
 
150
 
 
151
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 2 ) ) == "t" )
 
152
  {
 
153
    // INSERT
 
154
    enabledCapabilities |= QgsVectorDataProvider::AddFeatures;
 
155
  }
 
156
 
 
157
  mCurrentSchema = QString::fromUtf8( PQgetvalue( testAccess, 0, 3 ) );
 
158
  if ( mCurrentSchema == mSchemaName )
 
159
  {
 
160
    mUri.clearSchema();
 
161
  }
 
162
 
 
163
  if ( mSchemaName == "" )
 
164
    mSchemaName = mCurrentSchema;
 
165
 
 
166
  sql = QString( "SELECT 1 FROM pg_class,pg_namespace WHERE "
 
167
                 "pg_class.relnamespace=pg_namespace.oid AND "
 
168
                 "pg_get_userbyid(relowner)=current_user AND "
 
169
                 "relname=%1 AND nspname=%2" )
 
170
        .arg( quotedValue( mTableName ) )
 
171
        .arg( quotedValue( mSchemaName ) );
 
172
  testAccess = connectionRO->PQexec( sql );
 
173
  if ( PQresultStatus( testAccess ) == PGRES_TUPLES_OK && PQntuples( testAccess ) == 1 )
 
174
  {
 
175
    enabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes;
 
176
  }
 
177
 
 
178
  if ( !getGeometryDetails() ) // gets srid and geometry type
 
179
  {
 
180
    // the table is not a geometry table
 
181
    featuresCounted = 0;
 
182
    valid = false;
 
183
 
 
184
    QgsDebugMsg( "Invalid Postgres layer" );
 
185
    disconnectDb();
 
186
    return;
 
187
  }
 
188
 
 
189
  deduceEndian();
 
190
  calculateExtents();
 
191
  getFeatureCount();
 
192
 
 
193
  // set the primary key
 
194
  getPrimaryKey();
 
195
 
 
196
  // load the field list
 
197
  loadFields();
 
198
 
 
199
  // Set the postgresql message level so that we don't get the
 
200
  // 'there is no transaction in progress' warning.
335
201
#ifndef QGISDEBUG
336
 
      PQexec(connection, "set client_min_messages to error");
 
202
  connectionRO->PQexecNR( "set client_min_messages to error" );
337
203
#endif
338
204
 
339
 
      // Kick off the long running threads
 
205
  // Kick off the long running threads
340
206
 
341
207
#ifdef POSTGRESQL_THREADS
342
 
      QgsDebugMsg("QgsPostgresProvider: About to touch mExtentThread");
343
 
      mExtentThread.setConnInfo( connInfo );
344
 
      mExtentThread.setTableName( mTableName );
345
 
      mExtentThread.setSqlWhereClause( sqlWhereClause );
346
 
      mExtentThread.setGeometryColumn( geometryColumn );
347
 
      mExtentThread.setCallback( this );
348
 
      QgsDebugMsg("QgsPostgresProvider: About to start mExtentThread");
349
 
      mExtentThread.start();
350
 
      QgsDebugMsg("QgsPostgresProvider: Main thread just dispatched mExtentThread");
 
208
  QgsDebugMsg( "About to touch mExtentThread" );
 
209
  mExtentThread.setConnInfo( mUri.connectionInfo );
 
210
  mExtentThread.setTableName( mTableName );
 
211
  mExtentThread.setSqlWhereClause( sqlWhereClause );
 
212
  mExtentThread.setGeometryColumn( geometryColumn );
 
213
  mExtentThread.setCallback( this );
 
214
  QgsDebugMsg( "About to start mExtentThread" );
 
215
  mExtentThread.start();
 
216
  QgsDebugMsg( "Main thread just dispatched mExtentThread" );
351
217
 
352
 
      QgsDebugMsg("QgsPostgresProvider: About to touch mCountThread");
353
 
      mCountThread.setConnInfo( connInfo );
354
 
      mCountThread.setTableName( mTableName );
355
 
      mCountThread.setSqlWhereClause( sqlWhereClause );
356
 
      mCountThread.setGeometryColumn( geometryColumn );
357
 
      mCountThread.setCallback( this );
358
 
      QgsDebugMsg("QgsPostgresProvider: About to start mCountThread");
359
 
      mCountThread.start();
360
 
      QgsDebugMsg("QgsPostgresProvider: Main thread just dispatched mCountThread");
 
218
  QgsDebugMsg( "About to touch mCountThread" );
 
219
  mCountThread.setConnInfo( mUri.connectionInfo );
 
220
  mCountThread.setTableName( mTableName );
 
221
  mCountThread.setSqlWhereClause( sqlWhereClause );
 
222
  mCountThread.setGeometryColumn( geometryColumn );
 
223
  mCountThread.setCallback( this );
 
224
  QgsDebugMsg( "About to start mCountThread" );
 
225
  mCountThread.start();
 
226
  QgsDebugMsg( "Main thread just dispatched mCountThread" );
361
227
#endif
362
 
    } 
363
 
    else 
364
 
    {
365
 
      // the table is not a geometry table
366
 
      numberFeatures = 0;
367
 
      valid = false;
368
 
      QgsDebugMsg("Invalid Postgres layer");
369
 
    }
370
 
 
371
 
    ready = false; // not ready to read yet cuz the cursor hasn't been created
372
 
 
373
 
  } else {
 
228
 
 
229
  //fill type names into sets
 
230
  mNativeTypes
 
231
  // integer types
 
232
  << QgsVectorDataProvider::NativeType( tr( "Whole number (smallint - 16bit)" ), "int2", QVariant::Int )
 
233
  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer - 32bit)" ), "int4", QVariant::Int )
 
234
  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer - 64bit)" ), "int8", QVariant::LongLong )
 
235
  << QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), "numeric", QVariant::LongLong, 1, 20, 0, 20 )
 
236
  << QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), "decimal", QVariant::LongLong, 1, 20, 0, 20 )
 
237
 
 
238
  // floating point
 
239
  << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), "real", QVariant::Double )
 
240
  << QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), "double precision", QVariant::Double )
 
241
 
 
242
  // string types
 
243
  << QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), "char", QVariant::String, 1, 255 )
 
244
  << QgsVectorDataProvider::NativeType( tr( "Text, limited variable length (varchar)" ), "varchar", QVariant::String, 1, 255 )
 
245
  << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (text)" ), "text", QVariant::String )
 
246
  ;
 
247
 
 
248
  if ( primaryKey.isEmpty() )
 
249
  {
374
250
    valid = false;
375
 
    //QgsDebugMsg("Connection to database failed");
376
251
  }
377
 
 
378
 
  //fill type names into lists
379
 
  mNumericalTypes.push_back("double precision");
380
 
  mNumericalTypes.push_back("int4");
381
 
  mNumericalTypes.push_back("int8");
382
 
  mNonNumericalTypes.push_back("text");
383
 
  mNonNumericalTypes.push_back("varchar(30)");
384
 
 
385
 
  if (primaryKey.isEmpty())
 
252
  else
386
253
  {
387
 
    valid = false;
 
254
    mUri.setKeyColumn( primaryKey );
 
255
    setDataSourceUri( mUri.uri() );
388
256
  }
389
257
 
390
258
  // Close the database connection if the layer isn't going to be loaded.
391
 
  if (!valid)
392
 
    PQfinish(connection);
 
259
  if ( !valid )
 
260
    disconnectDb();
393
261
}
394
262
 
395
263
QgsPostgresProvider::~QgsPostgresProvider()
396
264
{
397
265
#ifdef POSTGRESQL_THREADS
398
 
  QgsDebugMsg("QgsPostgresProvider: About to wait for mExtentThread");
 
266
  QgsDebugMsg( "About to wait for mExtentThread" );
399
267
 
400
268
  mExtentThread.wait();
401
269
 
402
 
  QgsDebugMsg("QgsPostgresProvider: Finished waiting for mExtentThread");
 
270
  QgsDebugMsg( "Finished waiting for mExtentThread" );
403
271
 
404
 
  QgsDebugMsg("QgsPostgresProvider: About to wait for mCountThread");
 
272
  QgsDebugMsg( "About to wait for mCountThread" );
405
273
 
406
274
  mCountThread.wait();
407
275
 
408
 
  QgsDebugMsg("QgsPostgresProvider: Finished waiting for mCountThread");
 
276
  QgsDebugMsg( "Finished waiting for mCountThread" );
409
277
 
410
278
  // Make sure all events from threads have been processed
411
279
  // (otherwise they will get destroyed prematurely)
412
 
  QApplication::sendPostedEvents(this, QGis::ProviderExtentCalcEvent);
413
 
  QApplication::sendPostedEvents(this, QGis::ProviderCountCalcEvent);
 
280
  QApplication::sendPostedEvents( this, QGis::ProviderExtentCalcEvent );
 
281
  QApplication::sendPostedEvents( this, QGis::ProviderCountCalcEvent );
414
282
#endif
415
 
  PQfinish(connection);
416
 
 
417
 
  QgsDebugMsg("QgsPostgresProvider: deconstructing.");
 
283
 
 
284
  disconnectDb();
 
285
 
 
286
  QgsDebugMsg( "deconstructing." );
418
287
 
419
288
  //pLog.flush();
420
289
}
421
290
 
422
 
QString QgsPostgresProvider::storageType()
 
291
QgsPostgresProvider::Conn *QgsPostgresProvider::Conn::connectDb( const QString & conninfo, bool readonly )
 
292
{
 
293
  QMap<QString, QgsPostgresProvider::Conn *> &connections =
 
294
    readonly ? QgsPostgresProvider::Conn::connectionsRO : QgsPostgresProvider::Conn::connectionsRW;
 
295
 
 
296
  if ( connections.contains( conninfo ) )
 
297
  {
 
298
    QgsDebugMsg( QString( "Using cached connection for %1" ).arg( conninfo ) );
 
299
    connections[conninfo]->ref++;
 
300
    return connections[conninfo];
 
301
  }
 
302
 
 
303
  QgsDebugMsg( QString( "New postgres connection for " ) + conninfo );
 
304
 
 
305
  PGconn *pd = PQconnectdb( conninfo.toLocal8Bit() );  // use what is set based on locale; after connecting, use Utf8
 
306
  // check the connection status
 
307
  if ( PQstatus( pd ) != CONNECTION_OK )
 
308
  {
 
309
    QgsDebugMsg( "Connection to database failed" );
 
310
    return NULL;
 
311
  }
 
312
 
 
313
  //set client encoding to unicode because QString uses UTF-8 anyway
 
314
  QgsDebugMsg( "setting client encoding to UNICODE" );
 
315
 
 
316
  int errcode = PQsetClientEncoding( pd, QString( "UNICODE" ).toLocal8Bit() );
 
317
 
 
318
  if ( errcode == 0 )
 
319
  {
 
320
    QgsDebugMsg( "encoding successfully set" );
 
321
  }
 
322
  else if ( errcode == -1 )
 
323
  {
 
324
    QgsDebugMsg( "error in setting encoding" );
 
325
  }
 
326
  else
 
327
  {
 
328
    QgsDebugMsg( "undefined return value from encoding setting" );
 
329
  }
 
330
 
 
331
  QgsDebugMsg( "Connection to the database was successful" );
 
332
 
 
333
  Conn *conn = new Conn( pd );
 
334
 
 
335
  /* Check to see if we have working PostGIS support */
 
336
  if ( conn->postgisVersion().isNull() )
 
337
  {
 
338
    showMessageBox( tr( "No PostGIS Support!" ),
 
339
                    tr( "Your database has no working PostGIS support.\n" ) );
 
340
    conn->PQfinish();
 
341
    delete conn;
 
342
    return NULL;
 
343
  }
 
344
 
 
345
  connections.insert( conninfo, conn );
 
346
 
 
347
  /* Check to see if we have GEOS support and if not, warn the user about
 
348
     the problems they will see :) */
 
349
  QgsDebugMsg( "Checking for GEOS support" );
 
350
 
 
351
  if ( !conn->hasGEOS() )
 
352
  {
 
353
    showMessageBox( tr( "No GEOS Support!" ),
 
354
                    tr( "Your PostGIS installation has no GEOS support.\n"
 
355
                        "Feature selection and identification will not "
 
356
                        "work properly.\nPlease install PostGIS with "
 
357
                        "GEOS support (http://geos.refractions.net)" ) );
 
358
  }
 
359
 
 
360
 
 
361
 
 
362
  return conn;
 
363
}
 
364
 
 
365
void QgsPostgresProvider::disconnectDb()
 
366
{
 
367
  if ( mFetching )
 
368
  {
 
369
    connectionRO->closeCursor( QString( "qgisf%1" ).arg( providerId ) );
 
370
    mFetching = false;
 
371
  }
 
372
 
 
373
  if ( connectionRO )
 
374
  {
 
375
    Conn::disconnectRO( connectionRO );
 
376
  }
 
377
 
 
378
  if ( connectionRW )
 
379
  {
 
380
    Conn::disconnectRW( connectionRW );
 
381
  }
 
382
}
 
383
 
 
384
void QgsPostgresProvider::Conn::disconnectRW( Conn *&connection )
 
385
{
 
386
  disconnect( connectionsRW, connection );
 
387
}
 
388
 
 
389
void QgsPostgresProvider::Conn::disconnectRO( Conn *&connection )
 
390
{
 
391
  disconnect( connectionsRO, connection );
 
392
}
 
393
 
 
394
void QgsPostgresProvider::Conn::disconnect( QMap<QString, Conn *>& connections, Conn *&conn )
 
395
{
 
396
  QMap<QString, Conn *>::iterator i;
 
397
  for ( i = connections.begin(); i != connections.end() && i.value() != conn; i++ )
 
398
    ;
 
399
 
 
400
  assert( i.value() == conn );
 
401
  assert( i.value()->ref > 0 );
 
402
 
 
403
  if ( --i.value()->ref == 0 )
 
404
  {
 
405
    i.value()->PQfinish();
 
406
    delete i.value();
 
407
    connections.remove( i.key() );
 
408
  }
 
409
 
 
410
  conn = NULL;
 
411
}
 
412
 
 
413
QString QgsPostgresProvider::storageType() const
423
414
{
424
415
  return "PostgreSQL database with PostGIS extension";
425
416
}
426
417
 
427
 
//TODO - we may not need this function - consider removing it from
428
 
//       the dataprovider.h interface
429
 
/**
430
 
 * Get the first feature resutling from a select operation
431
 
 * @return QgsFeature
432
 
 */
433
 
//TODO - this function is a stub and always returns 0
434
 
QgsFeature *QgsPostgresProvider::getFirstFeature(bool fetchAttributes)
435
 
{
436
 
  QgsFeature *f = 0;
437
 
  if (valid) {
438
 
    //QgsDebugMsg("getting first feature");
439
 
 
440
 
    f = new QgsFeature();
441
 
    /*  f->setGeometry(getGeometryPointer(feat));
442
 
        if(fetchAttributes){
443
 
        getFeatureAttributes(feat, f);
444
 
        } */
445
 
  }
446
 
  return f;
447
 
}
448
 
 
449
 
bool QgsPostgresProvider::getNextFeature(QgsFeature &feature, bool fetchAttributes)
450
 
{
451
 
  return true;
452
 
}
453
 
 
454
 
/**
455
 
 * Get the next feature resutling from a select operation
456
 
 * Return 0 if there are no features in the selection set
457
 
 * @return QgsFeature
458
 
 */
459
 
QgsFeature *QgsPostgresProvider::getNextFeature(bool fetchAttributes)
460
 
{
461
 
  QgsFeature *f = 0;
462
 
  
463
 
  int row = 0;  // TODO: Make this useful
464
 
 
465
 
  if (valid){
466
 
    QString fetch = "fetch forward 1 from qgisf";
467
 
    queryResult = PQexec(connection, (const char *)fetch);
468
 
    //    std::cerr << "Error: " << PQerrorMessage(connection) << std::endl;
469
 
    //   std::cerr << "Fetched " << PQntuples(queryResult) << "rows" << std::endl;
470
 
    if(PQntuples(queryResult) == 0){
471
 
      if (ready)
472
 
        PQexec(connection, "end work");
473
 
      ready = false;
474
 
      return 0;
475
 
    } 
476
 
    // QgsDebugMsg("Raw value of the geometry field: " + PQgetvalue(queryResult,0,PQfnumber(queryResult,"qgs_feature_geometry")));
477
 
    //QgsDebugMsg("Length of oid is " + PQgetlength(queryResult,0, PQfnumber(queryResult,"oid")));
478
 
 
479
 
    // get the value of the primary key based on type
480
 
 
481
 
    int oid = *(int *)PQgetvalue(queryResult, row, PQfnumber(queryResult,"\""+primaryKey+"\""));
482
 
    // QgsDebugMsg("OID from database: " + QString::number(oid)); 
483
 
 
484
 
    if (swapEndian)
485
 
      oid = ntohl(oid); // convert oid to opposite endian
486
 
 
487
 
    // oid is the key to be used in fetching attributes if 
488
 
    // fetchAttributes = true
489
 
    //QgsDebugMsg("Using OID: " + QString::number(oid));
490
 
 
491
 
    f = new QgsFeature(oid);
492
 
    if (fetchAttributes)
493
 
      getFeatureAttributes(oid, row, f);
494
 
 
495
 
    int returnedLength = PQgetlength(queryResult, row, PQfnumber(queryResult,"qgs_feature_geometry"));
496
 
    //--std::cerr << __FILE__ << ":" << __LINE__ << " Returned length is " << returnedLength << std::endl;
497
 
    if(returnedLength > 0)
498
 
    {
499
 
      unsigned char *feature = new unsigned char[returnedLength + 1];
500
 
      memset(feature, '\0', returnedLength + 1);
501
 
      memcpy(feature, PQgetvalue(queryResult, row, PQfnumber(queryResult,"qgs_feature_geometry")), returnedLength);
502
 
#ifdef QGISDEBUG
503
 
      // a bit verbose
504
 
      //int wkbType = *((int *) (feature + 1));
505
 
      //QgsDebugMsg("WKBtype is: " + QString::number(wkbType));
506
 
#endif
507
 
      f->setGeometryAndOwnership(feature, returnedLength + 1);
508
 
    }
509
 
    else
510
 
    {
511
 
      //QgsDebugMsg("Couldn't get the feature geometry in binary form");
512
 
    }
513
 
    
514
 
    PQclear(queryResult);
 
418
QString QgsPostgresProvider::fieldExpression( const QgsField &fld ) const
 
419
{
 
420
  const QString &type = fld.typeName();
 
421
  if ( type == "money" )
 
422
  {
 
423
    return QString( "cash_out(%1)" ).arg( quotedIdentifier( fld.name() ) );
 
424
  }
 
425
  else if ( type.startsWith( "_" ) )
 
426
  {
 
427
    return QString( "array_out(%1)" ).arg( quotedIdentifier( fld.name() ) );
 
428
  }
 
429
  else if ( type == "bool" )
 
430
  {
 
431
    return QString( "boolout(%1)" ).arg( quotedIdentifier( fld.name() ) );
 
432
  }
 
433
  else if ( type == "geometry" )
 
434
  {
 
435
    return QString( "asewkt(%1)" ).arg( quotedIdentifier( fld.name() ) );
515
436
  }
516
437
  else
517
438
  {
518
 
    //QgsDebugMsg("Read attempt on an invalid postgresql data source");
519
 
  }
520
 
  return f;
521
 
}
522
 
 
523
 
// // TODO: Remove completely (see morb_au)
524
 
// QgsFeature* QgsPostgresProvider::getNextFeature(std::list<int> const & attlist)
525
 
// {
526
 
//   return getNextFeature(attlist, 1);
527
 
// }
528
 
 
529
 
QgsFeature* QgsPostgresProvider::getNextFeature(std::list<int> const & attlist, int featureQueueSize)
530
 
{
531
 
  QgsFeature *f = 0;
532
 
  if (valid)
533
 
  {
534
 
  
535
 
    // Top up our queue if it is empty
536
 
    if (mFeatureQueue.empty())
537
 
    {
538
 
    
539
 
      if (featureQueueSize < 1)
540
 
      {
541
 
        featureQueueSize = 1;
542
 
      }
543
 
 
544
 
      QString fetch = QString("fetch forward %1 from qgisf")
545
 
                         .arg(featureQueueSize);
546
 
                         
547
 
      queryResult = PQexec(connection, (const char *)fetch);
548
 
      
549
 
      int rows = PQntuples(queryResult);
550
 
 
551
 
      if (rows == 0)
552
 
      {
553
 
        QgsDebugMsg("End of features."); 
554
 
        if (ready)
555
 
          PQexec(connection, "end work");
556
 
        ready = false;
557
 
        return 0;
558
 
      }
559
 
      
560
 
      for (int row = 0; row < rows; row++)
561
 
      {
562
 
        int oid = *(int *)PQgetvalue(queryResult, row, PQfnumber(queryResult,"\""+primaryKey+"\""));
563
 
        //QgsDebugMsg("Primary key type is " + primaryKeyType); 
564
 
        if (swapEndian)
565
 
          oid = ntohl(oid); // convert oid to opposite endian
566
 
 
567
 
        f = new QgsFeature(oid);
568
 
        if(!attlist.empty())
569
 
        {
570
 
          getFeatureAttributes(oid, row, f, attlist);
571
 
        }
572
 
        int returnedLength = PQgetlength(queryResult, row, PQfnumber(queryResult,"qgs_feature_geometry")); 
573
 
        if(returnedLength > 0)
574
 
        {
575
 
          unsigned char *feature = new unsigned char[returnedLength + 1];
576
 
          memset(feature, '\0', returnedLength + 1);
577
 
          memcpy(feature, PQgetvalue(queryResult, row, PQfnumber(queryResult,"qgs_feature_geometry")), returnedLength); 
578
 
    
579
 
          // Too verbose
580
 
          //int wkbType = *((int *) (feature + 1));
581
 
          //QgsDebugMsg("WKBtype is: " + QString::number(wkbType));
582
 
    
583
 
          f->setGeometryAndOwnership(feature, returnedLength + 1);
584
 
    
585
 
        }
586
 
        else
587
 
        {
588
 
          //QgsDebugMsg("Couldn't get the feature geometry in binary form");
589
 
        }
590
 
 
591
 
//      QgsDebugMsg("QgsPostgresProvider::getNextFeature: pushing " + QString::number(f->featureId())); 
592
 
  
593
 
        mFeatureQueue.push(f);
594
 
        
 
439
    return quotedIdentifier( fld.name() ) + "::text";
 
440
  }
 
441
}
 
442
 
 
443
bool QgsPostgresProvider::declareCursor(
 
444
  const QString &cursorName,
 
445
  const QgsAttributeList &fetchAttributes,
 
446
  bool fetchGeometry,
 
447
  QString whereClause )
 
448
{
 
449
  try
 
450
  {
 
451
    QString query = QString( "select %1" ).arg( quotedIdentifier( primaryKey ) );
 
452
 
 
453
    if ( fetchGeometry )
 
454
    {
 
455
      query += QString( ",asbinary(%1,'%2')" )
 
456
               .arg( quotedIdentifier( geometryColumn ) )
 
457
               .arg( endianString() );
 
458
    }
 
459
 
 
460
    for ( QgsAttributeList::const_iterator it = fetchAttributes.constBegin(); it != fetchAttributes.constEnd(); ++it )
 
461
    {
 
462
      const QgsField &fld = field( *it );
 
463
 
 
464
      if ( fld.name() == primaryKey )
 
465
        continue;
 
466
 
 
467
      query += "," + fieldExpression( fld );
 
468
    }
 
469
 
 
470
    query += " from " + mSchemaTableName;
 
471
 
 
472
    if ( !whereClause.isEmpty() )
 
473
      query += QString( " where %1" ).arg( whereClause );
 
474
 
 
475
    return connectionRO->openCursor( cursorName, query );
 
476
  }
 
477
  catch ( PGFieldNotFound )
 
478
  {
 
479
    return false;
 
480
  }
 
481
}
 
482
 
 
483
bool QgsPostgresProvider::getFeature( PGresult *queryResult, int row, bool fetchGeometry,
 
484
                                      QgsFeature &feature,
 
485
                                      const QgsAttributeList &fetchAttributes )
 
486
{
 
487
  try
 
488
  {
 
489
    int oid;
 
490
 
 
491
    if ( primaryKeyType != "tid" )
 
492
    {
 
493
      oid = *( int * )PQgetvalue( queryResult, row, 0 );
 
494
      if ( swapEndian )
 
495
        oid = ntohl( oid ); // convert oid to opposite endian
 
496
    }
 
497
    else if ( PQgetlength( queryResult, row, 0 ) == 6 )
 
498
    {
 
499
      char *data = PQgetvalue( queryResult, row, 0 );
 
500
      int block = *( int * )data;
 
501
      int offset = *( short * )( data + sizeof( int ) );
 
502
 
 
503
      if ( swapEndian )
 
504
      {
 
505
        block = ntohl( block );
 
506
        offset = ntohs( offset );
 
507
      }
 
508
 
 
509
      if ( block > 0xffff )
 
510
      {
 
511
        QgsDebugMsg( QString( "block number %1 exceeds 16 bit" ).arg( block ) );
 
512
        return false;
 
513
      }
 
514
 
 
515
      oid = ( block << 16 ) + offset;
 
516
    }
 
517
    else
 
518
    {
 
519
      QgsDebugMsg( QString( "expecting 6 bytes for tid (found %1 bytes)" ).arg( PQgetlength( queryResult, row, 0 ) ) );
 
520
      return false;
 
521
    }
 
522
 
 
523
    feature.setFeatureId( oid );
 
524
 
 
525
    int col;  // first attribute column after geometry
 
526
 
 
527
    if ( fetchGeometry )
 
528
    {
 
529
      int returnedLength = PQgetlength( queryResult, row, 1 );
 
530
      if ( returnedLength > 0 )
 
531
      {
 
532
        unsigned char *featureGeom = new unsigned char[returnedLength + 1];
 
533
        memset( featureGeom, '\0', returnedLength + 1 );
 
534
        memcpy( featureGeom, PQgetvalue( queryResult, row, 1 ), returnedLength );
 
535
        feature.setGeometryAndOwnership( featureGeom, returnedLength + 1 );
 
536
      }
 
537
      else
 
538
      {
 
539
        feature.setGeometryAndOwnership( 0, 0 );
 
540
        QgsDebugMsg( "Couldn't get the feature geometry in binary form" );
 
541
      }
 
542
 
 
543
      col = 2;
 
544
    }
 
545
    else
 
546
    {
 
547
      col = 1;
 
548
    }
 
549
 
 
550
    // iterate attributes
 
551
    for ( QgsAttributeList::const_iterator it = fetchAttributes.constBegin(); it != fetchAttributes.constEnd(); it++ )
 
552
    {
 
553
      const QgsField &fld = field( *it );
 
554
 
 
555
      if ( fld.name() == primaryKey )
 
556
      {
 
557
        // primary key was already processed
 
558
        feature.addAttribute( *it, convertValue( fld.type(), QString::number( oid ) ) );
 
559
        continue;
 
560
      }
 
561
 
 
562
      if ( !PQgetisnull( queryResult, row, col ) )
 
563
      {
 
564
        feature.addAttribute( *it, convertValue( fld.type(), QString::fromUtf8( PQgetvalue( queryResult, row, col ) ) ) );
 
565
      }
 
566
      else
 
567
      {
 
568
        feature.addAttribute( *it, QVariant( QString::null ) );
 
569
      }
 
570
 
 
571
      col++;
 
572
    }
 
573
 
 
574
    return true;
 
575
  }
 
576
  catch ( PGFieldNotFound )
 
577
  {
 
578
    return false;
 
579
  }
 
580
}
 
581
 
 
582
void QgsPostgresProvider::select( QgsAttributeList fetchAttributes, QgsRectangle rect, bool fetchGeometry, bool useIntersect )
 
583
{
 
584
  QString cursorName = QString( "qgisf%1" ).arg( providerId );
 
585
 
 
586
  if ( mFetching )
 
587
  {
 
588
    connectionRO->closeCursor( cursorName );
 
589
    mFetching = false;
 
590
 
 
591
    while ( !mFeatureQueue.empty() )
 
592
    {
 
593
      mFeatureQueue.pop();
 
594
    }
 
595
  }
 
596
 
 
597
  QString whereClause;
 
598
 
 
599
  if ( !rect.isEmpty() )
 
600
  {
 
601
    if ( useIntersect )
 
602
    {
 
603
      // Contributed by #qgis irc "creeping"
 
604
      // This version actually invokes PostGIS's use of spatial indexes
 
605
      whereClause = QString( "%1 && setsrid('BOX3D(%2)'::box3d,%3) and intersects(%1,setsrid('BOX3D(%2)'::box3d,%3))" )
 
606
                    .arg( quotedIdentifier( geometryColumn ) )
 
607
                    .arg( rect.asWktCoordinates() )
 
608
                    .arg( srid );
 
609
    }
 
610
    else
 
611
    {
 
612
      whereClause = QString( "%1 && setsrid('BOX3D(%2)'::box3d,%3)" )
 
613
                    .arg( quotedIdentifier( geometryColumn ) )
 
614
                    .arg( rect.asWktCoordinates() )
 
615
                    .arg( srid );
 
616
    }
 
617
  }
 
618
 
 
619
  if ( !sqlWhereClause.isEmpty() )
 
620
  {
 
621
    if ( !whereClause.isEmpty() )
 
622
      whereClause += " and ";
 
623
 
 
624
    whereClause += "(" + sqlWhereClause + ")";
 
625
  }
 
626
 
 
627
  mFetchGeom = fetchGeometry;
 
628
  mAttributesToFetch = fetchAttributes;
 
629
  if ( !declareCursor( cursorName, fetchAttributes, fetchGeometry, whereClause ) )
 
630
    return;
 
631
 
 
632
  mFetching = true;
 
633
}
 
634
 
 
635
bool QgsPostgresProvider::nextFeature( QgsFeature& feature )
 
636
{
 
637
  feature.setValid( false );
 
638
  QString cursorName = QString( "qgisf%1" ).arg( providerId );
 
639
 
 
640
  if ( !valid )
 
641
  {
 
642
    QgsDebugMsg( "Read attempt on an invalid postgresql data source" );
 
643
    return false;
 
644
  }
 
645
 
 
646
  if ( mFeatureQueue.empty() )
 
647
  {
 
648
    QString fetch = QString( "fetch forward %1 from %2" ).arg( mFeatureQueueSize ).arg( cursorName );
 
649
    if ( connectionRO->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously
 
650
    {
 
651
      QgsDebugMsg( "PQsendQuery failed (1)" );
 
652
    }
 
653
 
 
654
    Result queryResult;
 
655
    while (( queryResult = connectionRO->PQgetResult() ) )
 
656
    {
 
657
      int rows = PQntuples( queryResult );
 
658
      if ( rows == 0 )
 
659
        continue;
 
660
 
 
661
      for ( int row = 0; row < rows; row++ )
 
662
      {
 
663
        mFeatureQueue.push( QgsFeature() );
 
664
        getFeature( queryResult, row, mFetchGeom, mFeatureQueue.back(), mAttributesToFetch );
595
665
      } // for each row in queue
596
 
      
597
 
//      QgsDebugMsg("QgsPostgresProvider::getNextFeature: retrieved batch of features."); 
598
 
 
599
 
            
600
 
      PQclear(queryResult);
601
 
      
602
 
    } // if new queue is required
603
 
    
604
 
    // Now return the next feature from the queue
605
 
    
606
 
    f = mFeatureQueue.front();
607
 
    mFeatureQueue.pop();
608
 
    
609
 
  }
610
 
  else 
611
 
  {
612
 
    //QgsDebugMsg("Read attempt on an invalid postgresql data source");
613
 
  }
614
 
//QgsDebugMsg("QgsPostgresProvider::getNextFeature: returning " + QString::number(f->featureId())); 
615
 
 
616
 
 
617
 
  return f;   
618
 
}
619
 
 
620
 
/**
621
 
 * Select features based on a bounding rectangle. Features can be retrieved
622
 
 * with calls to getFirstFeature and getNextFeature.
623
 
 * @param mbr QgsRect containing the extent to use in selecting features
624
 
 */
625
 
void QgsPostgresProvider::select(QgsRect * rect, bool useIntersect)
626
 
{
627
 
  // spatial query to select features
628
 
  QgsDebugMsg("Selection polygon is " + rect->asPolygon());
629
 
 
630
 
  QString declare = QString("declare qgisf binary cursor for select \""
631
 
      + primaryKey  
632
 
      + "\",asbinary(\"%1\",'%2') as qgs_feature_geometry from %3").arg(geometryColumn).arg(endianString()).arg(mSchemaTableName);
633
 
 
634
 
  QgsDebugMsg("Binary cursor: " + declare); 
635
 
 
636
 
  if(useIntersect){
637
 
    //    declare += " where intersects(" + geometryColumn;
638
 
    //    declare += ", GeometryFromText('BOX3D(" + rect->asWKTCoords();
639
 
    //    declare += ")'::box3d,";
640
 
    //    declare += srid;
641
 
    //    declare += "))";
642
 
 
643
 
    // Contributed by #qgis irc "creeping"
644
 
    // This version actually invokes PostGIS's use of spatial indexes
645
 
    declare += " where " + geometryColumn;
646
 
    declare += " && setsrid('BOX3D(" + rect->asWKTCoords();
647
 
    declare += ")'::box3d,";
648
 
    declare += srid;
649
 
    declare += ")";
650
 
    declare += " and intersects(" + geometryColumn;
651
 
    declare += ", setsrid('BOX3D(" + rect->asWKTCoords();
652
 
    declare += ")'::box3d,";
653
 
    declare += srid;
654
 
    declare += "))";
655
 
  }else{
656
 
    declare += " where " + geometryColumn;
657
 
    declare += " && setsrid('BOX3D(" + rect->asWKTCoords();
658
 
    declare += ")'::box3d,";
659
 
    declare += srid;
660
 
    declare += ")";
661
 
  }
662
 
  if(sqlWhereClause.length() > 0)
663
 
  {
664
 
    declare += " and (" + sqlWhereClause + ")";
665
 
  }
666
 
 
667
 
  QgsDebugMsg("Selecting features using: " + declare);
668
 
 
669
 
  // set up the cursor
670
 
  if(ready){
671
 
    PQexec(connection, "end work");
672
 
  }
673
 
  PQexec(connection,"begin work");
674
 
  ready = true;
675
 
  PQexec(connection, (const char *)(declare.utf8()));
676
 
  
677
 
  // TODO - see if this deallocates member features
678
 
  mFeatureQueue.empty();
679
 
}
680
 
 
681
 
 
682
 
QgsDataSourceURI * QgsPostgresProvider::getURI()
683
 
{
684
 
  return &mUri;
685
 
}
686
 
 
687
 
 
688
 
/**
689
 
 * Identify features within the search radius specified by rect
690
 
 * @param rect Bounding rectangle of search radius
691
 
 * @return std::vector containing QgsFeature objects that intersect rect
692
 
 */
693
 
std::vector<QgsFeature>& QgsPostgresProvider::identify(QgsRect * rect)
694
 
{
695
 
  features.clear();
696
 
  // select the features
697
 
  select(rect);
698
 
 
699
 
  return features;
700
 
}
701
 
 
702
 
/* unsigned char * QgsPostgresProvider::getGeometryPointer(OGRFeature *fet){
703
 
//  OGRGeometry *geom = fet->GetGeometryRef();
704
 
unsigned char *gPtr=0;
705
 
// get the wkb representation
706
 
gPtr = new unsigned char[geom->WkbSize()];
707
 
 
708
 
geom->exportToWkb((OGRwkbByteOrder) endian(), gPtr);
709
 
return gPtr;
710
 
 
711
 
} */
712
 
 
713
 
void QgsPostgresProvider::setExtent( QgsRect* newExtent )
714
 
{
715
 
  layerExtent.setXmax( newExtent->xMax() );
716
 
  layerExtent.setXmin( newExtent->xMin() );
717
 
  layerExtent.setYmax( newExtent->yMax() );
718
 
  layerExtent.setYmin( newExtent->yMin() );
 
666
    }
 
667
  }
 
668
 
 
669
  if ( mFeatureQueue.empty() )
 
670
  {
 
671
    QgsDebugMsg( "End of features" );
 
672
    connectionRO->closeCursor( cursorName );
 
673
    mFetching = false;
 
674
    return false;
 
675
  }
 
676
 
 
677
  // Now return the next feature from the queue
 
678
  if ( mFetchGeom )
 
679
  {
 
680
    QgsGeometry* featureGeom = mFeatureQueue.front().geometryAndOwnership();
 
681
    feature.setGeometry( featureGeom );
 
682
  }
 
683
  else
 
684
  {
 
685
    feature.setGeometryAndOwnership( 0, 0 );
 
686
  }
 
687
  feature.setFeatureId( mFeatureQueue.front().id() );
 
688
  feature.setAttributeMap( mFeatureQueue.front().attributeMap() );
 
689
 
 
690
  mFeatureQueue.pop();
 
691
 
 
692
  feature.setValid( true );
 
693
  return true;
 
694
}
 
695
 
 
696
QString QgsPostgresProvider::whereClause( int featureId ) const
 
697
{
 
698
  QString whereClause;
 
699
 
 
700
  if ( primaryKeyType != "tid" )
 
701
  {
 
702
    whereClause = QString( "%1=%2" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId );
 
703
  }
 
704
  else
 
705
  {
 
706
    whereClause = QString( "%1='(%2,%3)'" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId >> 16 ).arg( featureId & 0xffff );
 
707
  }
 
708
 
 
709
  if ( !sqlWhereClause.isEmpty() )
 
710
  {
 
711
    if ( !whereClause.isEmpty() )
 
712
      whereClause += " and ";
 
713
 
 
714
    whereClause += "(" + sqlWhereClause + ")";
 
715
  }
 
716
 
 
717
  return whereClause;
 
718
}
 
719
 
 
720
bool QgsPostgresProvider::featureAtId( int featureId, QgsFeature& feature, bool fetchGeometry, QgsAttributeList fetchAttributes )
 
721
{
 
722
  QString cursorName = QString( "qgisfid%1" ).arg( providerId );
 
723
 
 
724
  if ( !declareCursor( cursorName, fetchAttributes, fetchGeometry, whereClause( featureId ) ) )
 
725
    return false;
 
726
 
 
727
  Result queryResult = connectionRO->PQexec( QString( "fetch forward 1 from %1" ).arg( cursorName ) );
 
728
  if ( queryResult == 0 )
 
729
    return false;
 
730
 
 
731
  int rows = PQntuples( queryResult );
 
732
  if ( rows == 0 )
 
733
  {
 
734
    QgsDebugMsg( QString( "feature %1 not found" ).arg( featureId ) );
 
735
    connectionRO->closeCursor( cursorName );
 
736
    return false;
 
737
  }
 
738
  else if ( rows != 1 )
 
739
  {
 
740
    QgsDebugMsg( QString( "found %1 features instead of just one." ).arg( rows ) );
 
741
  }
 
742
 
 
743
  bool gotit = getFeature( queryResult, 0, fetchGeometry, feature, fetchAttributes );
 
744
 
 
745
  connectionRO->closeCursor( cursorName );
 
746
 
 
747
  return gotit;
 
748
}
 
749
 
 
750
 
 
751
QgsDataSourceURI& QgsPostgresProvider::getURI()
 
752
{
 
753
  return mUri;
 
754
}
 
755
 
 
756
void QgsPostgresProvider::setExtent( QgsRectangle& newExtent )
 
757
{
 
758
  layerExtent.setXMaximum( newExtent.xMaximum() );
 
759
  layerExtent.setXMinimum( newExtent.xMinimum() );
 
760
  layerExtent.setYMaximum( newExtent.yMaximum() );
 
761
  layerExtent.setYMinimum( newExtent.yMinimum() );
719
762
}
720
763
 
721
764
// TODO - make this function return the real extent_
722
 
QgsRect *QgsPostgresProvider::extent()
 
765
QgsRectangle QgsPostgresProvider::extent()
723
766
{
724
 
  return &layerExtent;      //extent_->MinX, extent_->MinY, extent_->MaxX, extent_->MaxY);
 
767
  return layerExtent;      //extent_->MinX, extent_->MinY, extent_->MaxX, extent_->MaxY);
725
768
}
726
769
 
727
 
/** 
 
770
/**
728
771
 * Return the feature type
729
772
 */
730
 
int QgsPostgresProvider::geometryType() const
 
773
QGis::WkbType QgsPostgresProvider::geometryType() const
731
774
{
732
775
  return geomType;
733
776
}
734
777
 
735
 
/** 
 
778
/**
736
779
 * Return the feature type
737
780
 */
738
781
long QgsPostgresProvider::featureCount() const
739
782
{
740
 
  return numberFeatures;
 
783
  return featuresCounted;
 
784
}
 
785
 
 
786
const QgsField &QgsPostgresProvider::field( int index ) const
 
787
{
 
788
  QgsFieldMap::const_iterator it = attributeFields.find( index );
 
789
 
 
790
  if ( it == attributeFields.constEnd() )
 
791
  {
 
792
    QgsDebugMsg( "Field " + QString::number( index ) + " not found." );
 
793
    throw PGFieldNotFound();
 
794
  }
 
795
 
 
796
  return it.value();
741
797
}
742
798
 
743
799
/**
744
800
 * Return the number of fields
745
801
 */
746
 
int QgsPostgresProvider::fieldCount() const
 
802
uint QgsPostgresProvider::fieldCount() const
747
803
{
748
804
  return attributeFields.size();
749
805
}
750
806
 
751
 
/**
752
 
 * Fetch attributes for a selected feature
753
 
 */
754
 
void QgsPostgresProvider::getFeatureAttributes(int key, int &row, QgsFeature *f) {
755
 
 
756
 
  QString sql = QString("select * from %1 where \"%2\" = %3").arg(mSchemaTableName).arg(primaryKey).arg(key);
757
 
  QgsDebugMsg("QgsPostgresProvider::getFeatureAttributes using: " + sql); 
758
 
 
759
 
  PGresult *attr = PQexec(connection, (const char *)(sql.utf8()));
760
 
 
761
 
  for (int i = 0; i < PQnfields(attr); i++) {
762
 
    QString fld = PQfname(attr, i);
763
 
    // Dont add the WKT representation of the geometry column to the identify
764
 
    // results
765
 
    if(fld != geometryColumn){
766
 
      // Add the attribute to the feature
767
 
      //QString val = mEncoding->toUnicode(PQgetvalue(attr,0, i));
768
 
        QString val = QString::fromUtf8 (PQgetvalue(attr, row, i));
769
 
      f->addAttribute(fld, val);
770
 
    }
771
 
  }
772
 
  PQclear(attr);
773
 
774
 
 
775
 
/**Fetch attributes with indices contained in attlist*/
776
 
void QgsPostgresProvider::getFeatureAttributes(int key, int &row,
777
 
    QgsFeature *f, 
778
 
    std::list<int> const & attlist)
779
 
{
780
 
  std::list<int>::const_iterator iter;
781
 
  for(iter=attlist.begin();iter!=attlist.end();++iter)
782
 
  {
783
 
    QString sql = QString("select \"%1\" from %2 where \"%3\" = %4")
784
 
      .arg(fields()[*iter].name())
785
 
      .arg(mSchemaTableName)
786
 
      .arg(primaryKey)
787
 
      .arg(key);//todo: only query one attribute
788
 
 
789
 
    PGresult *attr = PQexec(connection, (const char *)(sql.utf8()));
790
 
    QString fld = PQfname(attr, 0);
791
 
 
792
 
    // Dont add the WKT representation of the geometry column to the identify
793
 
    // results
794
 
    if(fld != geometryColumn)
795
 
    {
796
 
      // Add the attribute to the feature
797
 
        QString val = QString::fromUtf8(PQgetvalue(attr, 0, 0));
798
 
      f->addAttribute(fld, val);
799
 
    }
800
 
    PQclear(attr);
801
 
  }
802
 
}
803
 
 
804
 
 
805
 
void QgsPostgresProvider::getFeatureGeometry(int key, QgsFeature *f)
806
 
{
807
 
  if (!valid)
808
 
  {
809
 
    return;
810
 
  }
811
 
 
812
 
  QString cursor = QString("declare qgisf binary cursor for "
813
 
                           "select asbinary(\"%1\",'%2') from %3 where \"%4\" = %5")
814
 
                   .arg(geometryColumn)
815
 
                   .arg(endianString())
816
 
                   .arg(mSchemaTableName)
817
 
                   .arg(primaryKey)
818
 
                   .arg(key);
819
 
 
820
 
  QgsDebugMsg("QgsPostgresProvider::getFeatureGeometry using: " + cursor); 
821
 
 
822
 
  if (ready)
823
 
    PQexec(connection, "end work");
824
 
  PQexec(connection, "begin work");
825
 
  ready = true;
826
 
  PQexec(connection, (const char *)(cursor.utf8()));
827
 
 
828
 
  QString fetch = "fetch forward 1 from qgisf";
829
 
  PGresult *geomResult = PQexec(connection, (const char *)fetch);
830
 
 
831
 
  if (PQntuples(geomResult) == 0)
832
 
  {
833
 
    // Nothing found - therefore nothing to change
834
 
    PQexec(connection,"end work");
835
 
    ready = false;
836
 
    PQclear(geomResult);
837
 
    return;
838
 
  }
839
 
 
840
 
  int row = 0;
841
 
 
842
 
  int returnedLength = PQgetlength(geomResult, row, 0);
843
 
 
844
 
  if(returnedLength > 0)
845
 
  {
846
 
      unsigned char *wkbgeom = new unsigned char[returnedLength];
847
 
      memcpy(wkbgeom, PQgetvalue(geomResult, row, 0), returnedLength);
848
 
      f->setGeometryAndOwnership(wkbgeom, returnedLength);
849
 
  }
850
 
  else
851
 
  {
852
 
    //QgsDebugMsg("Couldn't get the feature geometry in binary form");
853
 
  }
854
 
 
855
 
  PQclear(geomResult);
856
 
 
857
 
  if (ready)
858
 
    PQexec(connection,"end work");
859
 
  ready = false;
860
 
}
861
 
 
862
 
 
863
 
std::vector<QgsField> const & QgsPostgresProvider::fields() const
 
807
const QgsFieldMap & QgsPostgresProvider::fields() const
864
808
{
865
809
  return attributeFields;
866
810
}
867
811
 
868
 
void QgsPostgresProvider::reset()
869
 
{
870
 
  // reset the cursor to the first record
871
 
  //QgsDebugMsg("Resetting the cursor to the first record ");
872
 
  QString declare = QString("declare qgisf binary cursor for select \"" +
873
 
      primaryKey + 
874
 
      "\",asbinary(\"%1\",'%2') as qgs_feature_geometry from %3").arg(geometryColumn)
875
 
    .arg(endianString()).arg(mSchemaTableName);
876
 
  if(sqlWhereClause.length() > 0)
 
812
QString QgsPostgresProvider::dataComment() const
 
813
{
 
814
  return mDataComment;
 
815
}
 
816
 
 
817
void QgsPostgresProvider::rewind()
 
818
{
 
819
  if ( mFetching )
877
820
  {
878
 
    declare += " where " + sqlWhereClause;
 
821
    //move cursor to first record
 
822
    connectionRO->PQexecNR( QString( "move 0 in qgisf%1" ).arg( providerId ) );
879
823
  }
880
 
  //QgsDebugMsg("Selecting features using: " + declare);
881
 
  QgsDebugMsg("Setting up binary cursor: " + declare);
882
 
 
883
 
  // set up the cursor
884
 
  if (ready)
885
 
    PQexec(connection,"end work");
886
 
 
887
 
  PQexec(connection,"begin work");
888
 
  ready = true;
889
 
  PQexec(connection, (const char *)(declare.utf8()));
890
 
  //QgsDebugMsg("Error: " + PQerrorMessage(connection));
891
 
  
892
 
  // TODO - see if this deallocates member features
893
824
  mFeatureQueue.empty();
894
 
 
 
825
  loadFields();
895
826
}
896
 
/* QString QgsPostgresProvider::getFieldTypeName(PGconn * pd, int oid)
897
 
   {
898
 
   QString typOid = QString().setNum(oid);
899
 
   QString sql = "select typelem from pg_type where typelem = " + typOid + " and typlen = -1";
900
 
////QgsDebugMsg(sql);
901
 
PGresult *result = PQexec(pd, (const char *) sql);
902
 
// get the oid of the "real" type
903
 
QString poid = PQgetvalue(result, 0, PQfnumber(result, "typelem"));
904
 
PQclear(result);
905
 
sql = "select typname, typlen from pg_type where oid = " + poid;
906
 
// QgsDebugMsg(sql);
907
 
result = PQexec(pd, (const char *) sql);
908
 
 
909
 
QString typeName = PQgetvalue(result, 0, 0);
910
 
QString typeLen = PQgetvalue(result, 0, 1);
911
 
PQclear(result);
912
 
typeName += "(" + typeLen + ")";
913
 
return typeName;
914
 
} */
915
827
 
916
828
/** @todo XXX Perhaps this should be promoted to QgsDataProvider? */
917
829
QString QgsPostgresProvider::endianString()
918
830
{
919
 
  switch ( endian() )
 
831
  switch ( QgsApplication::endian() )
920
832
  {
921
 
    case QgsDataProvider::NDR : 
922
 
      return QString("NDR");
 
833
    case QgsApplication::NDR:
 
834
      return QString( "NDR" );
923
835
      break;
924
 
    case QgsDataProvider::XDR : 
925
 
      return QString("XDR");
 
836
    case QgsApplication::XDR:
 
837
      return QString( "XDR" );
926
838
      break;
927
839
    default :
928
 
      return QString("UNKNOWN");
 
840
      return QString( "Unknown" );
 
841
  }
 
842
}
 
843
 
 
844
void QgsPostgresProvider::loadFields()
 
845
{
 
846
  QgsDebugMsg( "Loading fields for table " + mTableName );
 
847
 
 
848
  // Get the relation oid for use in later queries
 
849
  QString sql = QString( "SELECT regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
 
850
  Result tresult = connectionRO->PQexec( sql );
 
851
  QString tableoid = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
 
852
 
 
853
  // Get the table description
 
854
  sql = QString( "SELECT description FROM pg_description WHERE objoid=%1 AND objsubid=0" ).arg( tableoid );
 
855
  tresult = connectionRO->PQexec( sql );
 
856
  if ( PQntuples( tresult ) > 0 )
 
857
    mDataComment = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
 
858
 
 
859
  // Populate the field vector for this layer. The field vector contains
 
860
  // field name, type, length, and precision (if numeric)
 
861
  sql = QString( "select * from %1 limit 0" ).arg( mSchemaTableName );
 
862
 
 
863
  Result result = connectionRO->PQexec( sql );
 
864
 
 
865
  // The queries inside this loop could possibly be combined into one
 
866
  // single query - this would make the code run faster.
 
867
 
 
868
  attributeFields.clear();
 
869
  for ( int i = 0; i < PQnfields( result ); i++ )
 
870
  {
 
871
    QString fieldName = QString::fromUtf8( PQfname( result, i ) );
 
872
    if ( fieldName == geometryColumn )
 
873
      continue;
 
874
 
 
875
    int fldtyp = PQftype( result, i );
 
876
    QString typOid = QString().setNum( fldtyp );
 
877
    int fieldModifier = PQfmod( result, i );
 
878
    QString fieldComment( "" );
 
879
 
 
880
    sql = QString( "SELECT typname,typtype,typelem,typlen FROM pg_type WHERE oid=%1" ).arg( typOid );
 
881
    // just oid; needs more work to support array type
 
882
    //      "oid = (SELECT Distinct typelem FROM pg_type WHERE "  //needs DISTINCT to guard against 2 or more rows on int2
 
883
    //      "typelem = " + typOid + " AND typlen = -1)";
 
884
 
 
885
    Result oidResult = connectionRO->PQexec( sql );
 
886
    QString fieldTypeName = QString::fromUtf8( PQgetvalue( oidResult, 0, 0 ) );
 
887
    QString fieldTType = QString::fromUtf8( PQgetvalue( oidResult, 0, 1 ) );
 
888
    QString fieldElem = QString::fromUtf8( PQgetvalue( oidResult, 0, 2 ) );
 
889
    int fieldSize = QString::fromUtf8( PQgetvalue( oidResult, 0, 3 ) ).toInt();
 
890
 
 
891
    sql = QString( "SELECT attnum FROM pg_attribute WHERE attrelid=%1 AND attname=%2" )
 
892
          .arg( tableoid ).arg( quotedValue( fieldName ) );
 
893
 
 
894
    Result tresult = connectionRO->PQexec( sql );
 
895
    QString attnum = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
 
896
 
 
897
    sql = QString( "SELECT description FROM pg_description WHERE objoid=%1 AND objsubid=%2" )
 
898
          .arg( tableoid ).arg( attnum );
 
899
 
 
900
    tresult = connectionRO->PQexec( sql );
 
901
    if ( PQntuples( tresult ) > 0 )
 
902
      fieldComment = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
 
903
 
 
904
    QVariant::Type fieldType;
 
905
 
 
906
    if ( fieldTType == "b" )
 
907
    {
 
908
      bool isArray = fieldTypeName.startsWith( "_" );
 
909
 
 
910
      if ( isArray )
 
911
        fieldTypeName = fieldTypeName.mid( 1 );
 
912
 
 
913
      if ( fieldTypeName == "int8" )
 
914
      {
 
915
        fieldType = QVariant::LongLong;
 
916
        fieldSize = -1;
 
917
      }
 
918
      else if ( fieldTypeName.startsWith( "int" ) ||
 
919
                fieldTypeName == "serial" )
 
920
      {
 
921
        fieldType = QVariant::Int;
 
922
        fieldSize = -1;
 
923
      }
 
924
      else if ( fieldTypeName == "real" ||
 
925
                fieldTypeName == "double precision" ||
 
926
                fieldTypeName.startsWith( "float" ) ||
 
927
                fieldTypeName == "numeric" )
 
928
      {
 
929
        fieldType = QVariant::Double;
 
930
        fieldSize = -1;
 
931
      }
 
932
      else if ( fieldTypeName == "text" ||
 
933
                fieldTypeName == "bpchar" ||
 
934
                fieldTypeName == "varchar" ||
 
935
                fieldTypeName == "bool" ||
 
936
                fieldTypeName == "geometry" ||
 
937
                fieldTypeName == "money" ||
 
938
                fieldTypeName.startsWith( "time" ) ||
 
939
                fieldTypeName.startsWith( "date" ) )
 
940
      {
 
941
        fieldType = QVariant::String;
 
942
        fieldSize = -1;
 
943
      }
 
944
      else if ( fieldTypeName == "char" )
 
945
      {
 
946
        fieldType = QVariant::String;
 
947
      }
 
948
      else
 
949
      {
 
950
        QgsDebugMsg( "Field " + fieldName + " ignored, because of unsupported type " + fieldTypeName );
 
951
        continue;
 
952
      }
 
953
 
 
954
      if ( isArray )
 
955
      {
 
956
        fieldTypeName = "_" + fieldTypeName;
 
957
        fieldType = QVariant::String;
 
958
        fieldSize = -1;
 
959
      }
 
960
    }
 
961
    else if ( fieldTType == "e" )
 
962
    {
 
963
      // enum
 
964
      fieldType = QVariant::String;
 
965
      fieldSize = -1;
 
966
    }
 
967
    else
 
968
    {
 
969
      QgsDebugMsg( "Field " + fieldName + " ignored, because of unsupported type type " + fieldTType );
 
970
      continue;
 
971
    }
 
972
 
 
973
    attributeFields.insert( i, QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldModifier, fieldComment ) );
929
974
  }
930
975
}
931
976
 
935
980
  // can be used as a key into the table. Primary keys are always
936
981
  // unique indices, so we catch them as well.
937
982
 
938
 
  QString sql = "select indkey from pg_index where indisunique = 't' and "
939
 
    "indrelid = (select oid from pg_class where relname = '"
940
 
    + mTableName + "' and relnamespace = (select oid from pg_namespace where "
941
 
    "nspname = '" + mSchemaName + "'))";
942
 
 
943
 
  QgsDebugMsg("Getting unique index using '" + sql);;
944
 
  PGresult *pk = executeDbCommand(connection, sql);
945
 
 
946
 
  QgsDebugMsg("Got " + QString::number(PQntuples(pk)) + " rows");
 
983
  QString sql = QString( "select indkey from pg_index where indisunique and indrelid=regclass(%1)::oid and indpred is null" )
 
984
                .arg( quotedValue( mSchemaTableName ) );
 
985
 
 
986
  QgsDebugMsg( "Getting unique index using '" + sql + "'" );
 
987
 
 
988
  Result pk = connectionRO->PQexec( sql );
 
989
 
 
990
  QgsDebugMsg( "Got " + QString::number( PQntuples( pk ) ) + " rows." );
947
991
 
948
992
  QStringList log;
949
993
 
950
994
  // if we got no tuples we ain't got no unique index :)
951
 
  if (PQntuples(pk) == 0)
 
995
  if ( PQntuples( pk ) == 0 )
952
996
  {
953
 
    QgsDebugMsg("Relation has no unique index -- investigating alternatives");
 
997
    QgsDebugMsg( "Relation has no unique index -- investigating alternatives" );
954
998
 
955
999
    // Two options here. If the relation is a table, see if there is
956
1000
    // an oid column that can be used instead.
957
1001
    // If the relation is a view try to find a suitable column to use as
958
1002
    // the primary key.
959
1003
 
960
 
    sql = "select relkind from pg_class where relname = '" + mTableName + 
961
 
      "' and relnamespace = (select oid from pg_namespace where "
962
 
      "nspname = '" + mSchemaName + "')";
963
 
    PGresult* tableType = executeDbCommand(connection, sql);
964
 
    QString type = PQgetvalue(tableType, 0, 0);
965
 
    PQclear(tableType);
966
 
 
967
 
    primaryKey = "";
968
 
 
969
 
    if (type == "r") // the relation is a table
 
1004
    sql = QString( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" )
 
1005
          .arg( quotedValue( mSchemaTableName ) );
 
1006
    Result tableType = connectionRO->PQexec( sql );
 
1007
    QString type = QString::fromUtf8( PQgetvalue( tableType, 0, 0 ) );
 
1008
 
 
1009
    if ( type == "r" ) // the relation is a table
970
1010
    {
971
 
      QgsDebugMsg("Relation is a table. Checking to see if it has an oid column.");
 
1011
      QgsDebugMsg( "Relation is a table. Checking to see if it has an oid column." );
 
1012
 
 
1013
      primaryKey = "";
972
1014
 
973
1015
      // If there is an oid on the table, use that instead,
974
1016
      // otherwise give up
975
 
      sql = "select attname from pg_attribute where attname = 'oid' and "
976
 
        "attrelid = (select oid from pg_class where relname = '" +
977
 
        mTableName + "' and relnamespace = (select oid from pg_namespace "
978
 
        "where nspname = '" + mSchemaName + "'))";
979
 
      PGresult* oidCheck = executeDbCommand(connection, sql);
980
 
 
981
 
      if (PQntuples(oidCheck) != 0)
 
1017
      sql = QString( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" )
 
1018
            .arg( quotedValue( mSchemaTableName ) );
 
1019
 
 
1020
      Result oidCheck = connectionRO->PQexec( sql );
 
1021
 
 
1022
      if ( PQntuples( oidCheck ) != 0 )
982
1023
      {
983
1024
        // Could warn the user here that performance will suffer if
984
1025
        // oid isn't indexed (and that they may want to add a
985
1026
        // primary key to the table)
986
 
        primaryKey = "oid";
987
 
        primaryKeyType = "int4";
988
 
      }
989
 
      else
990
 
      {
991
 
        showMessageBox(tr("No suitable key column in table"),
992
 
            tr("The table has no column suitable for use as a key.\n\n"
993
 
               "Qgis requires that the table either has a column of type\n"
994
 
               "int4 with a unique constraint on it (which includes the\n"
995
 
               "primary key) or has a PostgreSQL oid column.\n"));
996
 
      }
997
 
      PQclear(oidCheck);
 
1027
        primaryKey = "oid";
 
1028
        primaryKeyType = "int4";
 
1029
      }
 
1030
      else
 
1031
      {
 
1032
        sql = QString( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" )
 
1033
              .arg( quotedValue( mSchemaTableName ) );
 
1034
 
 
1035
        Result ctidCheck = connectionRO->PQexec( sql );
 
1036
 
 
1037
        if ( PQntuples( ctidCheck ) == 1 )
 
1038
        {
 
1039
          sql = QString( "SELECT max(substring(ctid::text from E'\\\\((\\\\d+),\\\\d+\\\\)')::integer) from %1" )
 
1040
                .arg( mSchemaTableName );
 
1041
 
 
1042
          Result ctidCheck = connectionRO->PQexec( sql );
 
1043
          if ( PQntuples( ctidCheck ) == 1 )
 
1044
          {
 
1045
            int id = QString( PQgetvalue( ctidCheck, 0, 0 ) ).toInt();
 
1046
 
 
1047
            if ( id < 0x10000 )
 
1048
            {
 
1049
              // fallback to ctid
 
1050
              primaryKey = "ctid";
 
1051
              primaryKeyType = "tid";
 
1052
            }
 
1053
          }
 
1054
        }
 
1055
      }
 
1056
 
 
1057
      if ( primaryKey.isEmpty() )
 
1058
      {
 
1059
        showMessageBox( tr( "No suitable key column in table" ),
 
1060
                        tr( "The table has no column suitable for use as a key.\n\n"
 
1061
                            "Qgis requires that the table either has a column of type\n"
 
1062
                            "int4 with a unique constraint on it (which includes the\n"
 
1063
                            "primary key), has a PostgreSQL oid column or has a ctid\n"
 
1064
                            "column with a 16bit block number.\n" ) );
 
1065
      }
 
1066
      else
 
1067
      {
 
1068
        mPrimaryKeyDefault = defaultValue( primaryKey ).toString();
 
1069
        if ( mPrimaryKeyDefault.isNull() )
 
1070
        {
 
1071
          mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
 
1072
                               .arg( quotedIdentifier( primaryKey ) )
 
1073
                               .arg( quotedIdentifier( mSchemaName ) )
 
1074
                               .arg( quotedIdentifier( mTableName ) );
 
1075
        }
 
1076
      }
998
1077
    }
999
 
    else if (type == "v") // the relation is a view
 
1078
    else if ( type == "v" ) // the relation is a view
1000
1079
    {
1001
 
      // Have a poke around the view to see if any of the columns
1002
 
      // could be used as the primary key.
1003
 
      tableCols cols;
1004
 
      // Given a schema.view, populate the cols variable with the
1005
 
      // schema.table.column's that underly the view columns.
1006
 
      findColumns(cols);
1007
 
      // From the view columns, choose one for which the underlying
1008
 
      // column is suitable for use as a key into the view.
1009
 
      primaryKey = chooseViewColumn(cols);
 
1080
      if ( !primaryKey.isEmpty() )
 
1081
      {
 
1082
        // check last used candidate
 
1083
        sql = QString( "select pg_type.typname from pg_attribute,pg_type where atttypid=pg_type.oid and attname=%1 and attrelid=regclass(%2)" )
 
1084
              .arg( quotedValue( primaryKey ) ).arg( quotedValue( mSchemaTableName ) );
 
1085
 
 
1086
        QgsDebugMsg( "checking candidate: " + sql );
 
1087
 
 
1088
        Result result = connectionRO->PQexec( sql );
 
1089
 
 
1090
        QString type;
 
1091
        if ( PQresultStatus( result ) == PGRES_TUPLES_OK &&
 
1092
             PQntuples( result ) == 1 )
 
1093
        {
 
1094
          type = PQgetvalue( result, 0, 0 );
 
1095
        }
 
1096
 
 
1097
        // mPrimaryKeyDefault stays null and is retrieved later on demand
 
1098
 
 
1099
        if (( type != "int4" && type != "oid" ) ||
 
1100
            !uniqueData( mSchemaName, mTableName, primaryKey ) )
 
1101
        {
 
1102
          primaryKey = "";
 
1103
        }
 
1104
      }
 
1105
 
 
1106
      if ( primaryKey.isEmpty() )
 
1107
      {
 
1108
        parseView();
 
1109
      }
1010
1110
    }
1011
1111
    else
1012
 
      qWarning("Unexpected relation type of '" + type + "'.");
 
1112
      QgsDebugMsg( "Unexpected relation type of '" + type + "'." );
1013
1113
  }
1014
1114
  else // have some unique indices on the table. Now choose one...
1015
1115
  {
1016
1116
    // choose which (if more than one) unique index to use
1017
1117
    std::vector<std::pair<QString, QString> > suitableKeyColumns;
1018
 
    for (int i = 0; i < PQntuples(pk); ++i)
 
1118
    for ( int i = 0; i < PQntuples( pk ); ++i )
1019
1119
    {
1020
 
      QString col = PQgetvalue(pk, i, 0);
1021
 
      QStringList columns = QStringList::split(" ", col);
1022
 
      if (columns.count() == 1)
 
1120
      QString col = QString::fromUtf8( PQgetvalue( pk, i, 0 ) );
 
1121
      QStringList columns = col.split( " ", QString::SkipEmptyParts );
 
1122
      if ( columns.count() == 1 )
1023
1123
      {
1024
 
        // Get the column name and data type
1025
 
        sql = "select attname, pg_type.typname from pg_attribute, pg_type where "
1026
 
          "atttypid = pg_type.oid and attnum = " +
1027
 
          col + " and attrelid = (select oid from pg_class where " +
1028
 
          "relname = '" + mTableName + "' and relnamespace = (select oid "
1029
 
          "from pg_namespace where nspname = '" + mSchemaName + "'))";
1030
 
        PGresult* types = executeDbCommand(connection, sql);
1031
 
 
1032
 
        assert(PQntuples(types) > 0); // should never happen
1033
 
 
1034
 
        QString columnName = PQgetvalue(types, 0, 0);
1035
 
        QString columnType = PQgetvalue(types, 0, 1);
1036
 
 
1037
 
        if (columnType != "int4")
1038
 
          log.append(tr("The unique index on column") + 
1039
 
                     " '" + columnName + "' " +
1040
 
                     tr("is unsuitable because Qgis does not currently support"
1041
 
                        " non-int4 type columns as a key into the table.\n"));
1042
 
        else
1043
 
          suitableKeyColumns.push_back(std::make_pair(columnName, columnType));
1044
 
          
1045
 
        PQclear(types);
 
1124
        // Get the column name and data type
 
1125
        sql = QString( "select attname,pg_type.typname from pg_attribute,pg_type where atttypid=pg_type.oid and attnum=%1 and attrelid=regclass(%2)" )
 
1126
              .arg( col ).arg( quotedValue( mSchemaTableName ) );
 
1127
        Result types = connectionRO->PQexec( sql );
 
1128
 
 
1129
        if ( PQntuples( types ) > 0 )
 
1130
        {
 
1131
          QString columnName = QString::fromUtf8( PQgetvalue( types, 0, 0 ) );
 
1132
          QString columnType = QString::fromUtf8( PQgetvalue( types, 0, 1 ) );
 
1133
 
 
1134
          if ( columnType != "int4" )
 
1135
            log.append( tr( "The unique index on column '%1' is unsuitable because Qgis does not currently "
 
1136
                            "support non-int4 type columns as a key into the table.\n" ).arg( columnName ) );
 
1137
          else
 
1138
            suitableKeyColumns.push_back( std::make_pair( columnName, columnType ) );
 
1139
        }
 
1140
        else
 
1141
        {
 
1142
          //QgsDebugMsg( QString("name and type of %3. column of %1.%2 not found").arg(mSchemaName).arg(mTables).arg(col) );
 
1143
        }
1046
1144
      }
1047
1145
      else
1048
1146
      {
1049
 
        sql = "select attname from pg_attribute, pg_type where "
1050
 
          "atttypid = pg_type.oid and attnum in (" +
1051
 
          col.replace(" ", ",") 
1052
 
          + ") and attrelid = (select oid from pg_class where " +
1053
 
          "relname = '" + mTableName + "' and relnamespace = (select oid "
1054
 
          "from pg_namespace where nspname = '" + mSchemaName + "'))";
1055
 
        PGresult* types = executeDbCommand(connection, sql);
 
1147
        sql = QString( "select attname from pg_attribute, pg_type where atttypid=pg_type.oid and attnum in (%1) and attrelid=regclass(%2)::oid" )
 
1148
              .arg( col.replace( " ", "," ) )
 
1149
              .arg( quotedValue( mSchemaTableName ) );
 
1150
 
 
1151
        Result types = connectionRO->PQexec( sql );
1056
1152
        QString colNames;
1057
 
        int numCols = PQntuples(types);
1058
 
        for (int j = 0; j < numCols; ++j)
 
1153
        int numCols = PQntuples( types );
 
1154
        for ( int j = 0; j < numCols; ++j )
1059
1155
        {
1060
 
          if (j == numCols-1)
1061
 
            colNames += tr("and ");
1062
 
          colNames += "'" + QString(PQgetvalue(types, j, 0)) 
1063
 
            + (j < numCols-2 ? "', " : "' ");
 
1156
          if ( j == numCols - 1 )
 
1157
            colNames += tr( "and " );
 
1158
          colNames += quotedValue( QString::fromUtf8( PQgetvalue( types, j, 0 ) ) );
 
1159
          if ( j < numCols - 2 )
 
1160
            colNames += ",";
1064
1161
        }
1065
1162
 
1066
 
        log.append(tr("The unique index based on columns ") + colNames + 
1067
 
                   tr(" is unsuitable because Qgis does not currently support"
1068
 
                      " multiple columns as a key into the table.\n"));
 
1163
        log.append( tr( "The unique index based on columns %1 is unsuitable because Qgis does not currently "
 
1164
                        "support multiple columns as a key into the table.\n" ).arg( colNames ) );
1069
1165
      }
1070
1166
    }
1071
1167
 
1075
1171
    // more than one we need to choose one. For the moment, just
1076
1172
    // choose the first in the list.
1077
1173
 
1078
 
    if (suitableKeyColumns.size() > 0)
 
1174
    if ( suitableKeyColumns.size() > 0 )
1079
1175
    {
1080
1176
      primaryKey = suitableKeyColumns[0].first;
1081
1177
      primaryKeyType = suitableKeyColumns[0].second;
1084
1180
    {
1085
1181
      // If there is an oid on the table, use that instead,
1086
1182
      // otherwise give up
1087
 
      sql = "select attname from pg_attribute where attname = 'oid' and "
1088
 
        "attrelid = (select oid from pg_class where relname = '" +
1089
 
        mTableName + "' and relnamespace = (select oid from pg_namespace "
1090
 
        "where nspname = '" + mSchemaName + "'))";
1091
 
      PGresult* oidCheck = executeDbCommand(connection, sql);
 
1183
      sql = QString( "select attname from pg_attribute where attname='oid' and attrelid=regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
 
1184
      Result oidCheck = connectionRO->PQexec( sql );
1092
1185
 
1093
 
      if (PQntuples(oidCheck) != 0)
 
1186
      if ( PQntuples( oidCheck ) != 0 )
1094
1187
      {
1095
 
        primaryKey = "oid";
1096
 
        primaryKeyType = "int4";
 
1188
        primaryKey = "oid";
 
1189
        primaryKeyType = "int4";
1097
1190
      }
1098
1191
      else
1099
1192
      {
1100
 
        log.prepend("There were no columns in the table that were suitable "
1101
 
                   "as a qgis key into the table (either a column with a "
1102
 
                   "unique index and type int4 or a PostgreSQL oid column.\n");
 
1193
        log.prepend( "There were no columns in the table that were suitable "
 
1194
                     "as a qgis key into the table (either a column with a "
 
1195
                     "unique index and type int4 or a PostgreSQL oid column.\n" );
1103
1196
      }
1104
 
      PQclear(oidCheck);
1105
1197
    }
1106
1198
 
1107
1199
    // Either primaryKey has been set by the above code, or it
1108
1200
    // hasn't. If not, present some info to the user to give them some
1109
1201
    // idea of why not.
1110
 
    if (primaryKey.isEmpty())
 
1202
    if ( primaryKey.isEmpty() )
1111
1203
    {
1112
1204
      // Give some info to the user about why things didn't work out.
1113
1205
      valid = false;
1114
 
      showMessageBox(tr("Unable to find a key column"), log);
 
1206
      showMessageBox( tr( "Unable to find a key column" ), log );
 
1207
    }
 
1208
    else
 
1209
    {
 
1210
      mPrimaryKeyDefault = defaultValue( primaryKey ).toString();
 
1211
      if ( mPrimaryKeyDefault.isNull() )
 
1212
      {
 
1213
        mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
 
1214
                             .arg( quotedIdentifier( primaryKey ) )
 
1215
                             .arg( quotedIdentifier( mSchemaName ) )
 
1216
                             .arg( quotedIdentifier( mTableName ) );
 
1217
      }
1115
1218
    }
1116
1219
  }
1117
 
  PQclear(pk);
1118
1220
 
1119
 
#ifdef QGISDEBUG
1120
 
  if (primaryKey.length() > 0)
1121
 
    {
1122
 
      QgsDebugMsg("Qgis row key is " + primaryKey);
1123
 
    }
 
1221
  if ( !primaryKey.isNull() )
 
1222
  {
 
1223
    QgsDebugMsg( "Qgis row key is " + primaryKey );
 
1224
  }
1124
1225
  else
1125
 
    {
1126
 
      QgsDebugMsg("Qgis row key was not set.");
1127
 
    }
1128
 
#endif
 
1226
  {
 
1227
    QgsDebugMsg( "Qgis row key was not set." );
 
1228
  }
1129
1229
 
1130
1230
  return primaryKey;
1131
1231
}
1132
1232
 
 
1233
void QgsPostgresProvider::parseView()
 
1234
{
 
1235
  // Have a poke around the view to see if any of the columns
 
1236
  // could be used as the primary key.
 
1237
  tableCols cols;
 
1238
 
 
1239
  // Given a schema.view, populate the cols variable with the
 
1240
  // schema.table.column's that underly the view columns.
 
1241
  findColumns( cols );
 
1242
 
 
1243
  // pick the primary key, if we don't have one yet
 
1244
  if ( primaryKey.isEmpty() )
 
1245
  {
 
1246
    // From the view columns, choose one for which the underlying
 
1247
    // column is suitable for use as a key into the view.
 
1248
    primaryKey = chooseViewColumn( cols );
 
1249
  }
 
1250
 
 
1251
  tableCols::const_iterator it = cols.find( primaryKey );
 
1252
  if ( it != cols.end() )
 
1253
  {
 
1254
    mPrimaryKeyDefault = defaultValue( it->second.column, it->second.relation, it->second.schema ).toString();
 
1255
    if ( mPrimaryKeyDefault.isNull() )
 
1256
    {
 
1257
      mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
 
1258
                           .arg( quotedIdentifier( it->second.column ) )
 
1259
                           .arg( quotedIdentifier( it->second.schema ) )
 
1260
                           .arg( quotedIdentifier( it->second.relation ) );
 
1261
    }
 
1262
  }
 
1263
  else
 
1264
  {
 
1265
    mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
 
1266
                         .arg( quotedIdentifier( primaryKey ) )
 
1267
                         .arg( quotedIdentifier( mSchemaName ) )
 
1268
                         .arg( quotedIdentifier( mTableName ) );
 
1269
  }
 
1270
}
 
1271
 
 
1272
QString QgsPostgresProvider::primaryKeyDefault()
 
1273
{
 
1274
  if ( mPrimaryKeyDefault.isNull() )
 
1275
    parseView();
 
1276
 
 
1277
  return mPrimaryKeyDefault;
 
1278
}
 
1279
 
1133
1280
// Given the table and column that each column in the view refers to,
1134
1281
// choose one. Prefers column with an index on them, but will
1135
1282
// otherwise choose something suitable.
1136
1283
 
1137
 
QString QgsPostgresProvider::chooseViewColumn(const tableCols& cols)
 
1284
QString QgsPostgresProvider::chooseViewColumn( const tableCols &cols )
1138
1285
{
1139
1286
  // For each relation name and column name need to see if it
1140
1287
  // has unique constraints on it, or is a primary key (if not,
1149
1296
 
1150
1297
  std::vector<tableCols::const_iterator> oids;
1151
1298
  tableCols::const_iterator iter = cols.begin();
1152
 
  for (; iter != cols.end(); ++iter)
 
1299
  for ( ; iter != cols.end(); ++iter )
1153
1300
  {
1154
1301
    QString viewCol   = iter->first;
1155
1302
    QString schemaName = iter->second.schema;
1159
1306
 
1160
1307
    // Get the oid from pg_class for the given schema.relation for use
1161
1308
    // in subsequent queries.
1162
 
    sql = "select oid from pg_class where relname = '" + tableName +
1163
 
      "' and relnamespace = (select oid from pg_namespace where "
1164
 
      " nspname = '" + schemaName + "')";
1165
 
    PGresult* result = PQexec(connection, (const char*)(sql.utf8()));
 
1309
    sql = QString( "select regclass(%1)::oid" ).arg( quotedValue( quotedIdentifier( schemaName ) + "." + quotedIdentifier( tableName ) ) );
 
1310
    Result result = connectionRO->PQexec( sql );
1166
1311
    QString rel_oid;
1167
 
    if (PQntuples(result) == 1)
 
1312
    if ( PQntuples( result ) == 1 )
1168
1313
    {
1169
 
      rel_oid = PQgetvalue(result, 0, 0);
 
1314
      rel_oid = PQgetvalue( result, 0, 0 );
1170
1315
      // Keep the rel_oid for use later one.
1171
1316
      relOid[viewCol] = rel_oid;
1172
1317
    }
1173
1318
    else
1174
 
      {
1175
 
        QgsDebugMsg("Relation " + schemaName + "." + tableName + " doesn't exist in the pg_class table. This shouldn't happen and is odd.");
1176
 
        assert(0);
1177
 
      }
1178
 
    PQclear(result);
1179
 
      
1180
 
    // This sql returns one or more rows if the column 'tableCol' in 
 
1319
    {
 
1320
      QgsDebugMsg( "Relation " + schemaName + "." + tableName +
 
1321
                   " doesn't exist in the pg_class table."
 
1322
                   "This shouldn't happen and is odd." );
 
1323
      continue;
 
1324
    }
 
1325
 
 
1326
    // This sql returns one or more rows if the column 'tableCol' in
1181
1327
    // table 'tableName' and schema 'schemaName' has one or more
1182
1328
    // columns that satisfy the following conditions:
1183
1329
    // 1) the column has data type of int4.
1185
1331
    //    on it.
1186
1332
    // 3) the constraint applies just to the column of interest (i.e.,
1187
1333
    //    it isn't a constraint over multiple columns.
1188
 
    sql = "select * from pg_constraint where conkey[1] = "
1189
 
      "(select attnum from pg_attribute where attname = '" + tableCol + "' "
1190
 
      "and attrelid = " + rel_oid + ")"
1191
 
      "and conrelid = " + rel_oid + " "
1192
 
      "and (contype = 'p' or contype = 'u') "
1193
 
      "and array_dims(conkey) = '[1:1]'";
 
1334
    sql = QString( "select * from pg_constraint where "
 
1335
                   "conkey[1]=(select attnum from pg_attribute where attname=%1 and attrelid=%2) "
 
1336
                   "and conrelid=%2 and (contype='p' or contype='u') "
 
1337
                   "and array_dims(conkey)='[1:1]'" ).arg( quotedValue( tableCol ) ).arg( rel_oid );
1194
1338
 
1195
 
    result = PQexec(connection, (const char*)(sql.utf8()));
1196
 
    if (PQntuples(result) == 1 && colType == "int4")
 
1339
    result = connectionRO->PQexec( sql );
 
1340
    if ( PQntuples( result ) == 1 && colType == "int4" )
1197
1341
      suitable[viewCol] = iter->second;
1198
1342
 
1199
 
    QString details = "'" + viewCol + "'" + tr(" derives from ") 
1200
 
      + "'" + schemaName + "." + tableName + "." + tableCol + "' ";
 
1343
    QString details = tr( "'%1' derives from '%2.%3.%4' " ).arg( viewCol ).arg( schemaName ).arg( tableName ).arg( tableCol );
1201
1344
 
1202
 
    if (PQntuples(result) == 1 && colType == "int4")
 
1345
    if ( PQntuples( result ) == 1 && colType == "int4" )
1203
1346
    {
1204
 
      details += tr("and is suitable.");
 
1347
      details += tr( "and is suitable." );
1205
1348
    }
1206
1349
    else
1207
1350
    {
1208
 
      details += tr("and is not suitable ");
1209
 
      details += "(" + tr("type is ") + colType;
1210
 
      if (PQntuples(result) == 1)
1211
 
        details += tr(" and has a suitable constraint)");
 
1351
      details += tr( "and is not suitable (type is %1)" ).arg( colType );
 
1352
      if ( PQntuples( result ) == 1 )
 
1353
        details += tr( " and has a suitable constraint)" );
1212
1354
      else
1213
 
        details += tr(" and does not have a suitable constraint)");
 
1355
        details += tr( " and does not have a suitable constraint)" );
1214
1356
    }
1215
1357
 
1216
1358
    log << details;
1217
1359
 
1218
 
    PQclear(result);
1219
 
    if (tableCol == "oid")
1220
 
      oids.push_back(iter);
 
1360
    if ( tableCol == "oid" )
 
1361
      oids.push_back( iter );
1221
1362
  }
1222
1363
 
1223
1364
  // 'oid' columns in tables don't have a constraint on them, but
1224
1365
  // they are useful to consider, so add them in if not already
1225
1366
  // here.
1226
 
  for (int i = 0; i < oids.size(); ++i)
 
1367
  for ( uint i = 0; i < oids.size(); ++i )
1227
1368
  {
1228
 
    if (suitable.find(oids[i]->first) == suitable.end())
 
1369
    if ( suitable.find( oids[i]->first ) == suitable.end() )
1229
1370
    {
1230
1371
      suitable[oids[i]->first] = oids[i]->second;
1231
 
      QgsDebugMsg("Adding column " + oids[i]->first + " as it may be suitable.");
 
1372
 
 
1373
      QgsDebugMsg( "Adding column " + oids[i]->first + " as it may be suitable." );
1232
1374
    }
1233
1375
  }
1234
1376
 
1238
1380
  //
1239
1381
  // If there is more than one suitable column pick one that is
1240
1382
  // indexed, else pick one called 'oid' if it exists, else
1241
 
  // pick the first one. If there are none we return an empty string. 
 
1383
  // pick the first one. If there are none we return an empty string.
1242
1384
 
1243
1385
  // Search for one with an index
1244
1386
  tableCols::const_iterator i = suitable.begin();
1245
 
  for (; i != suitable.end(); ++i)
 
1387
  for ( ; i != suitable.end(); ++i )
1246
1388
  {
1247
1389
    // Get the relation oid from our cache.
1248
1390
    QString rel_oid = relOid[i->first];
1249
1391
    // And see if the column has an index
1250
 
    sql = "select * from pg_index where indrelid = " + rel_oid +
1251
 
      " and indkey[0] = (select attnum from pg_attribute where "
1252
 
      "attrelid = " +   rel_oid + " and attname = '" + i->second.column + "')";
1253
 
    PGresult* result = PQexec(connection, (const char*)(sql.utf8()));
 
1392
    sql = QString( "select * from pg_index where indrelid=%1 and indkey[0]=(select attnum from pg_attribute where attrelid=%1 and attname=%2)" )
 
1393
          .arg( rel_oid )
 
1394
          .arg( quotedValue( i->second.column ) );
 
1395
    Result result = connectionRO->PQexec( sql );
1254
1396
 
1255
 
    if (PQntuples(result) > 0 && uniqueData(mSchemaName, mTableName, i->first))
1256
 
    { // Got one. Use it.
 
1397
    if ( PQntuples( result ) > 0 && uniqueData( mSchemaName, mTableName, i->first ) )
 
1398
    {
 
1399
      // Got one. Use it.
1257
1400
      key = i->first;
1258
 
      QgsDebugMsg("Picked column '" + key + "' because it has an index.");
 
1401
      QgsDebugMsg( "Picked column '" + key + "' because it has an index." );
1259
1402
      break;
1260
1403
    }
1261
 
    PQclear(result);
1262
1404
  }
1263
1405
 
1264
 
  if (key.isEmpty())
 
1406
  if ( key.isEmpty() )
1265
1407
  {
1266
1408
    // If none have indices, choose one that is called 'oid' (if it
1267
1409
    // exists). This is legacy support and could be removed in
1268
 
    // future. 
1269
 
    i = suitable.find("oid");
1270
 
    if (i != suitable.end() && uniqueData(mSchemaName, mTableName, i->first))
 
1410
    // future.
 
1411
    i = suitable.find( "oid" );
 
1412
    if ( i != suitable.end() && uniqueData( mSchemaName, mTableName, i->first ) )
1271
1413
    {
1272
1414
      key = i->first;
1273
 
      QgsDebugMsg("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");
 
1415
 
 
1416
      QgsDebugMsg( "Picked column " + key +
 
1417
                   " as it is probably the postgresql object id "
 
1418
                   " column (which contains unique values) and there are no"
 
1419
                   " columns with indices to choose from." );
1274
1420
    }
1275
1421
    // else choose the first one in the container that has unique data
1276
1422
    else
1277
1423
    {
1278
1424
      tableCols::const_iterator i = suitable.begin();
1279
 
      for (; i != suitable.end(); ++i)
 
1425
      for ( ; i != suitable.end(); ++i )
1280
1426
      {
1281
 
        if (uniqueData(mSchemaName, mTableName, i->first))
 
1427
        if ( uniqueData( mSchemaName, mTableName, i->first ) )
1282
1428
        {
1283
1429
          key = i->first;
1284
 
          QgsDebugMsg("Picked column " + key + " as it was the first suitable column found with unique data and were are no columns with indices to choose from.");
 
1430
 
 
1431
          QgsDebugMsg( "Picked column " + key +
 
1432
                       " as it was the first suitable column found"
 
1433
                       " with unique data and were are no"
 
1434
                       " columns with indices to choose from" );
1285
1435
          break;
1286
1436
        }
1287
1437
        else
1288
1438
        {
1289
 
          log << QString(tr("Note: ") + "'" + i->first + "' "
1290
 
                         + tr("initially appeared suitable but does not "
1291
 
                              "contain unique data, so is not suitable.\n"));
 
1439
          log << tr( "Note: '%1' initially appeared suitable"
 
1440
                     " but does not contain unique data, so is not suitable.\n" )
 
1441
          .arg( i->first );
1292
1442
        }
1293
1443
      }
1294
1444
    }
1295
1445
  }
1296
1446
 
1297
 
  if (key.isEmpty())
 
1447
  if ( key.isEmpty() )
1298
1448
  {
1299
1449
    valid = false;
1300
 
    // Successive prepends means that the text appears in the dialog
1301
 
    // box in the reverse order to that seen here.
1302
 
    log.prepend(tr("The view you selected has the following columns, none "
1303
 
                   "of which satisfy the above conditions:"));
1304
 
    log.prepend(tr("Qgis requires that the view has a column that can be used "
1305
 
                   "as a unique key. Such a column should be derived from "
1306
 
                   "a table column of type int4 and be a primary key, "
1307
 
                   "have a unique constraint on it, or be a PostgreSQL "
1308
 
                   "oid column. To improve "
1309
 
                   "performance the column should also be indexed.\n"));
1310
 
    log.prepend(tr("The view ") + "'" + mSchemaName + '.' + mTableName + "' " +
1311
 
                tr("has no column suitable for use as a unique key.\n"));
1312
 
    showMessageBox(tr("No suitable key column in view"), log);
 
1450
    log.prepend( tr( "The view '%1.%2' has no column suitable for use as a unique key.\n"
 
1451
                     "Qgis requires that the view has a column that can be used "
 
1452
                     "as a unique key. Such a column should be derived from "
 
1453
                     "a table column of type int4 and be a primary key, "
 
1454
                     "have a unique constraint on it, or be a PostgreSQL "
 
1455
                     "oid column. To improve performance the column should also be indexed.\n"
 
1456
                     "The view you selected has the following columns, none "
 
1457
                     "of which satisfy the above conditions:" ).arg( mSchemaName ).arg( mTableName ) );
 
1458
    showMessageBox( tr( "No suitable key column in view" ), log );
1313
1459
  }
1314
1460
 
1315
1461
  return key;
1316
1462
}
1317
1463
 
1318
 
bool QgsPostgresProvider::uniqueData(QString schemaName, 
1319
 
                                     QString tableName, QString colName)
 
1464
bool QgsPostgresProvider::uniqueData( QString schemaName,
 
1465
                                      QString tableName, QString colName )
1320
1466
{
1321
1467
  // Check to see if the given column contains unique data
1322
1468
 
1323
1469
  bool isUnique = false;
1324
1470
 
1325
 
  QString sql = "select count(distinct \"" + colName + "\") = count(\"" +
1326
 
      colName + "\") from \"" + schemaName + "\".\"" + tableName + "\"";
1327
 
 
1328
 
  PGresult* unique = PQexec(connection, (const char*) (sql.utf8()));
1329
 
 
1330
 
  if (PQntuples(unique) == 1)
1331
 
      if (strncmp(PQgetvalue(unique, 0, 0),"t", 1) == 0)
1332
 
        isUnique = true;
1333
 
 
1334
 
    PQclear(unique);
 
1471
  QString sql = QString( "select count(distinct %1)=count(%1) from %2.%3" )
 
1472
                .arg( quotedIdentifier( colName ) )
 
1473
                .arg( quotedIdentifier( schemaName ) )
 
1474
                .arg( quotedIdentifier( tableName ) );
 
1475
 
 
1476
  if ( !sqlWhereClause.isEmpty() )
 
1477
  {
 
1478
    sql += " where " + sqlWhereClause;
 
1479
  }
 
1480
 
 
1481
  Result unique = connectionRO->PQexec( sql );
 
1482
 
 
1483
  if ( PQntuples( unique ) == 1 && QString::fromUtf8( PQgetvalue( unique, 0, 0 ) ).startsWith( "t" ) )
 
1484
    isUnique = true;
1335
1485
 
1336
1486
  return isUnique;
1337
1487
}
1338
1488
 
1339
 
// This function will return in the cols variable the 
 
1489
int QgsPostgresProvider::SRCFromViewColumn( const QString& ns, const QString& relname, const QString& attname_table, const QString& attname_view, const QString& viewDefinition, SRC& result ) const
 
1490
{
 
1491
  QString newViewDefSql = QString( "SELECT definition FROM pg_views WHERE schemaname=%1 AND viewname=%2" )
 
1492
                          .arg( quotedValue( ns ) ).arg( quotedValue( relname ) );
 
1493
  Result newViewDefResult = connectionRO->PQexec( newViewDefSql );
 
1494
  int numEntries = PQntuples( newViewDefResult );
 
1495
 
 
1496
  if ( numEntries > 0 ) //relation is a view
 
1497
  {
 
1498
    QString newViewDefinition( QString::fromUtf8( PQgetvalue( newViewDefResult, 0, 0 ) ) );
 
1499
 
 
1500
    QString newAttNameView = attname_table;
 
1501
    QString newAttNameTable = attname_table;
 
1502
 
 
1503
    //find out the attribute name of the underlying table/view
 
1504
    if ( newViewDefinition.contains( " AS " ) )
 
1505
    {
 
1506
      QRegExp s( "(\\w+)" + QString( " AS " ) + QRegExp::escape( attname_table ) );
 
1507
      if ( s.indexIn( newViewDefinition ) != -1 )
 
1508
      {
 
1509
        newAttNameTable = s.cap( 1 );
 
1510
      }
 
1511
    }
 
1512
 
 
1513
    QString viewColumnSql =
 
1514
      QString( "SELECT "
 
1515
               "table_schema,"
 
1516
               "table_name,"
 
1517
               "column_name"
 
1518
               " FROM "
 
1519
               "("
 
1520
               "SELECT DISTINCT "
 
1521
               "current_database()::information_schema.sql_identifier AS view_catalog,"
 
1522
               "nv.nspname::information_schema.sql_identifier AS view_schema,"
 
1523
               "v.relname::information_schema.sql_identifier AS view_name,"
 
1524
               "current_database()::information_schema.sql_identifier AS table_catalog,"
 
1525
               "nt.nspname::information_schema.sql_identifier AS table_schema,"
 
1526
               "t.relname::information_schema.sql_identifier AS table_name,"
 
1527
               "a.attname::information_schema.sql_identifier AS column_name"
 
1528
               " FROM "
 
1529
               "pg_namespace nv,"
 
1530
               "pg_class v,"
 
1531
               "pg_depend dv,"
 
1532
               "pg_depend dt,"
 
1533
               "pg_class t,"
 
1534
               "pg_namespace nt,"
 
1535
               "pg_attribute a"
 
1536
               " WHERE "
 
1537
               "nv.oid=v.relnamespace AND "
 
1538
               "v.relkind='v'::\"char\" AND "
 
1539
               "v.oid=dv.refobjid AND "
 
1540
               "dv.refclassid='pg_class'::regclass::oid AND "
 
1541
               "dv.classid='pg_rewrite'::regclass::oid AND "
 
1542
               "dv.deptype='i'::\"char\" AND "
 
1543
               "dv.objid = dt.objid AND "
 
1544
               "dv.refobjid<>dt.refobjid AND "
 
1545
               "dt.classid='pg_rewrite'::regclass::oid AND "
 
1546
               "dt.refclassid='pg_class'::regclass::oid AND "
 
1547
               "dt.refobjid=t.oid AND "
 
1548
               "t.relnamespace = nt.oid AND "
 
1549
               "(t.relkind=ANY (ARRAY['r'::\"char\", 'v'::\"char\"])) AND "
 
1550
               "t.oid=a.attrelid AND "
 
1551
               "dt.refobjsubid=a.attnum"
 
1552
               " ORDER BY "
 
1553
               "current_database()::information_schema.sql_identifier,"
 
1554
               "nv.nspname::information_schema.sql_identifier,"
 
1555
               "v.relname::information_schema.sql_identifier,"
 
1556
               "current_database()::information_schema.sql_identifier,"
 
1557
               "nt.nspname::information_schema.sql_identifier,"
 
1558
               "t.relname::information_schema.sql_identifier,"
 
1559
               "a.attname::information_schema.sql_identifier"
 
1560
               ") x"
 
1561
               " WHERE "
 
1562
               "view_schema=%1 AND "
 
1563
               "view_name=%2 AND "
 
1564
               "column_name=%3" )
 
1565
      .arg( quotedValue( ns ) )
 
1566
      .arg( quotedValue( relname ) )
 
1567
      .arg( quotedValue( newAttNameTable ) );
 
1568
 
 
1569
    Result viewColumnResult = connectionRO->PQexec( viewColumnSql );
 
1570
    if ( PQntuples( viewColumnResult ) > 0 )
 
1571
    {
 
1572
      QString newTableSchema = QString::fromUtf8( PQgetvalue( viewColumnResult, 0, 0 ) );
 
1573
      QString newTableName = QString::fromUtf8( PQgetvalue( viewColumnResult, 0, 1 ) );
 
1574
      int retvalue = SRCFromViewColumn( newTableSchema, newTableName, newAttNameTable, newAttNameView, newViewDefinition, result );
 
1575
      return retvalue;
 
1576
    }
 
1577
    else
 
1578
    {
 
1579
      return 1;
 
1580
    }
 
1581
 
 
1582
  }
 
1583
 
 
1584
  //relation is table, we just have to add the type
 
1585
  QString typeSql = QString( "SELECT "
 
1586
                             "pg_type.typname"
 
1587
                             " FROM "
 
1588
                             "pg_attribute,"
 
1589
                             "pg_class,"
 
1590
                             "pg_namespace,"
 
1591
                             "pg_type"
 
1592
                             " WHERE "
 
1593
                             "pg_class.relname=%1 AND "
 
1594
                             "pg_namespace.nspname=%2 AND "
 
1595
                             "pg_attribute.attname=%3 AND "
 
1596
                             "pg_attribute.attrelid=pg_class.oid AND "
 
1597
                             "pg_class.relnamespace=pg_namespace.oid AND "
 
1598
                             "pg_attribute.atttypid=pg_type.oid" )
 
1599
                    .arg( quotedValue( relname ) )
 
1600
                    .arg( quotedValue( ns ) )
 
1601
                    .arg( quotedValue( attname_table ) );
 
1602
 
 
1603
  Result typeSqlResult = connectionRO->PQexec( typeSql );
 
1604
  if ( PQntuples( typeSqlResult ) < 1 )
 
1605
  {
 
1606
    return 1;
 
1607
  }
 
1608
  QString type = QString::fromUtf8( PQgetvalue( typeSqlResult, 0, 0 ) );
 
1609
 
 
1610
  result.schema = ns;
 
1611
  result.relation = relname;
 
1612
  result.column = attname_table;
 
1613
  result.type = type;
 
1614
  return 0;
 
1615
}
 
1616
 
 
1617
// This function will return in the cols variable the
1340
1618
// underlying view and columns for each column in
1341
1619
// mSchemaName.mTableName.
1342
1620
 
1343
 
void QgsPostgresProvider::findColumns(tableCols& cols)
 
1621
void QgsPostgresProvider::findColumns( tableCols& cols )
1344
1622
{
1345
 
  // This sql is derived from the one that defines the view
1346
 
  // 'information_schema.view_column_usage' in PostgreSQL, with a few
1347
 
  // mods to suit our purposes. 
1348
 
    QString sql = ""
1349
 
        "SELECT DISTINCT "
1350
 
        "       nv.nspname AS view_schema, "
1351
 
        "       v.relname AS view_name, "
1352
 
        "       a.attname AS view_column_name, "
1353
 
        "       nt.nspname AS table_schema, "
1354
 
        "       t.relname AS table_name, "
1355
 
        "       a.attname AS column_name, "
1356
 
        "       t.relkind as table_type, "
1357
 
        "       typ.typname as column_type "
1358
 
        "FROM "
1359
 
        "       pg_namespace nv, "
1360
 
        "       pg_class v, "
1361
 
        "       pg_depend dv,"
1362
 
        "       pg_depend dt, "
1363
 
        "       pg_class t, "
1364
 
        "       pg_namespace nt, "
1365
 
        "       pg_attribute a,"
1366
 
        "       pg_user u, "
1367
 
        "       pg_type typ "
1368
 
        "WHERE "
1369
 
        "       nv.oid = v.relnamespace AND "
1370
 
        "       v.relkind = 'v'::\"char\" AND "
1371
 
        "       v.oid = dv.refobjid AND "
1372
 
        "       dv.refclassid = 'pg_class'::regclass::oid AND "
1373
 
        "       dv.classid = 'pg_rewrite'::regclass::oid AND "
1374
 
        "       dv.deptype = 'i'::\"char\" AND "
1375
 
        "       dv.objid = dt.objid AND "
1376
 
        "       dv.refobjid <> dt.refobjid AND "
1377
 
        "       dt.classid = 'pg_rewrite'::regclass::oid AND "
1378
 
        "       dt.refclassid = 'pg_class'::regclass::oid AND "
1379
 
        "       dt.refobjid = t.oid AND "
1380
 
        "       t.relnamespace = nt.oid AND "
1381
 
        "       (t.relkind = 'r'::\"char\" OR t.relkind = 'v'::\"char\") AND "
1382
 
        "       t.oid = a.attrelid AND "
1383
 
        "       dt.refobjsubid = a.attnum AND "
1384
 
        "       nv.nspname NOT IN ('pg_catalog', 'information_schema' ) AND "
1385
 
        "       a.atttypid = typ.oid";
1386
 
 
1387
 
  // A structure to store the results of the above sql.
1388
 
  typedef std::map<QString, TT> columnRelationsType;
1389
 
  columnRelationsType columnRelations;
1390
 
 
1391
 
  // A structure to cache the query results that return the view
1392
 
  // definition.
1393
 
  typedef QMap<QString, QString> viewDefCache;
1394
 
  viewDefCache viewDefs;
1395
 
 
1396
 
  PGresult* result = PQexec(connection, (const char*)(sql.utf8()));
1397
 
  // Store the results of the query for convenient access 
1398
 
  for (int i = 0; i < PQntuples(result); ++i)
1399
 
  {
1400
 
    TT temp;
1401
 
    temp.view_schema      = PQgetvalue(result, i, 0);
1402
 
    temp.view_name        = PQgetvalue(result, i, 1);
1403
 
    temp.view_column_name = PQgetvalue(result, i, 2);
1404
 
    temp.table_schema     = PQgetvalue(result, i, 3);
1405
 
    temp.table_name       = PQgetvalue(result, i, 4);
1406
 
    temp.column_name      = PQgetvalue(result, i, 5);
1407
 
    temp.table_type       = PQgetvalue(result, i, 6);
1408
 
    temp.column_type      = PQgetvalue(result, i, 7);
1409
 
 
1410
 
    // BUT, the above SQL doesn't always give the correct value for the view
1411
 
    // column name (that's because that information isn't available directly
1412
 
    // from the database), mainly when the view column name has been renamed
1413
 
    // using 'AS'. To fix this we need to look in the view definition and
1414
 
    // adjust the view column name if necessary.
1415
 
 
1416
 
    QString viewQuery = "SELECT definition FROM pg_views "
1417
 
      "WHERE schemaname = '" + temp.view_schema + "' AND "
1418
 
      "viewname = '" + temp.view_name + "'";
1419
 
 
1420
 
    // Maintain a cache of the above SQL.
1421
 
    QString viewDef;
1422
 
    if (!viewDefs.contains(viewQuery))
1423
 
    {
1424
 
      PGresult* r = PQexec(connection, (const char*)(viewQuery.utf8()));
1425
 
      if (PQntuples(r) > 0)
1426
 
        viewDef = PQgetvalue(r, 0, 0);
1427
 
      else
1428
 
        QgsDebugMsg("Failed to get view definition for " + temp.view_schema + "." + temp.view_name);
1429
 
      viewDefs[viewQuery] = viewDef;
1430
 
    }
1431
 
 
1432
 
    viewDef = viewDefs.value(viewQuery);
1433
 
 
1434
 
    // Now pick the view definiton apart, looking for
1435
 
    // temp.column_name to the left of an 'AS'.
1436
 
 
1437
 
    // This regular expression needs more testing. Since the view
1438
 
    // definition comes from postgresql and has been 'standardised', we
1439
 
    // don't need to deal with everything that the user could put in a view
1440
 
    // definition. Does the regexp have to deal with the schema??
1441
 
    if (!viewDef.isEmpty())
1442
 
    {
1443
 
      QRegExp s(".* \"?" + QRegExp::escape(temp.table_name) + 
1444
 
                "\"?\\.\"?" + QRegExp::escape(temp.column_name) + 
1445
 
                "\"? AS \"?(\\w+)\"?,* .*");
1446
 
 
1447
 
#ifdef QGSIDEBUG
1448
 
      std::cerr <<__FILE__<<__LINE__ << ' ' << viewQuery.toLocal8Bit().data() << '\n'
1449
 
                << viewDef.toLocal8Bit().data() << '\n'
1450
 
                << s.pattern().toLocal8Bit().data() << '\n';
1451
 
#endif
1452
 
 
1453
 
      if (s.indexIn(viewDef) != -1)
1454
 
        {
1455
 
          temp.view_column_name = s.cap(1);
1456
 
          //std::cerr<<__FILE__<<__LINE__<<' '<<temp.view_column_name.toLocal8Bit().data()<<'\n';
1457
 
        }
1458
 
    }
1459
 
 
1460
 
    QgsDebugMsg(temp.view_schema + "." 
1461
 
                + temp.view_name + "." 
1462
 
                + temp.view_column_name + " <- " 
1463
 
                + temp.table_schema + "." 
1464
 
                + temp.table_name + "." 
1465
 
                + temp.column_name + " is a '" 
1466
 
                + temp.table_type + "' of type " 
1467
 
                + temp.column_type);
1468
 
 
1469
 
    columnRelations[temp.view_schema + '.' +
1470
 
                    temp.view_name + '.' +
1471
 
                    temp.view_column_name] = temp;
1472
 
  }
1473
 
  PQclear(result);
1474
 
 
1475
 
  // Loop over all columns in the view in question. 
1476
 
 
1477
 
  sql = "SELECT pg_namespace.nspname || '.' || "
1478
 
    "pg_class.relname || '.' || pg_attribute.attname "
1479
 
    "FROM pg_attribute, pg_class, pg_namespace "
1480
 
    "WHERE pg_class.relname = '" + mTableName + "' "
1481
 
    "AND pg_namespace.nspname = '" + mSchemaName + "' "
1482
 
    "AND pg_attribute.attrelid = pg_class.oid "
1483
 
    "AND pg_class.relnamespace = pg_namespace.oid";
1484
 
 
1485
 
  result = PQexec(connection, (const char*)(sql.utf8()));
1486
 
 
1487
 
  // Loop over the columns in mSchemaName.mTableName and find out the
1488
 
  // underlying schema, table, and column name.
1489
 
  for (int i = 0; i < PQntuples(result); ++i)
1490
 
  {
1491
 
    columnRelationsType::const_iterator 
1492
 
      ii = columnRelations.find(PQgetvalue(result, i, 0));
1493
 
    columnRelationsType::const_iterator start_iter = ii;
1494
 
 
1495
 
    if (ii == columnRelations.end())
1496
 
      continue;
1497
 
 
1498
 
    int count = 0;
1499
 
    const int max_loops = 100;
1500
 
 
1501
 
    while (ii->second.table_type != "r" && count < max_loops)
1502
 
    {
1503
 
      QgsDebugMsg("Searching for the column that " + ii->second.table_schema  + "."
1504
 
                  + ii->second.table_name + "." + ii->second.column_name + " refers to.");
1505
 
 
1506
 
      columnRelationsType::const_iterator 
1507
 
        jj = columnRelations.find(QString(ii->second.table_schema + '.' +
1508
 
                                          ii->second.table_name + '.' +
1509
 
                                          ii->second.column_name));
1510
 
 
1511
 
      if (jj == columnRelations.end())
 
1623
  QString viewColumnSql =
 
1624
    QString( "SELECT "
 
1625
             "table_schema,"
 
1626
             "table_name,"
 
1627
             "column_name"
 
1628
             " FROM "
 
1629
             "("
 
1630
             "SELECT DISTINCT "
 
1631
             "current_database() AS view_catalog,"
 
1632
             "nv.nspname AS view_schema,"
 
1633
             "v.relname AS view_name,"
 
1634
             "current_database() AS table_catalog,"
 
1635
             "nt.nspname AS table_schema,"
 
1636
             "t.relname AS table_name,"
 
1637
             "a.attname AS column_name"
 
1638
             " FROM "
 
1639
             "pg_namespace nv,"
 
1640
             "pg_class v,"
 
1641
             "pg_depend dv,"
 
1642
             "pg_depend dt,"
 
1643
             "pg_class t,"
 
1644
             "pg_namespace nt,"
 
1645
             "pg_attribute a"
 
1646
             " WHERE "
 
1647
             "nv.oid=v.relnamespace AND "
 
1648
             "v.relkind='v'::\"char\" AND "
 
1649
             "v.oid=dv.refobjid AND "
 
1650
             "dv.refclassid='pg_class'::regclass::oid AND "
 
1651
             "dv.classid='pg_rewrite'::regclass::oid AND "
 
1652
             "dv.deptype='i'::\"char\" AND "
 
1653
             "dv.objid=dt.objid AND "
 
1654
             "dv.refobjid<>dt.refobjid AND "
 
1655
             "dt.classid='pg_rewrite'::regclass::oid AND "
 
1656
             "dt.refclassid='pg_class'::regclass::oid AND "
 
1657
             "dt.refobjid=t.oid AND "
 
1658
             "t.relnamespace=nt.oid AND "
 
1659
             "(t.relkind = ANY (ARRAY['r'::\"char\",'v'::\"char\"])) AND "
 
1660
             "t.oid=a.attrelid AND "
 
1661
             "dt.refobjsubid=a.attnum"
 
1662
             " ORDER BY "
 
1663
             "current_database(),"
 
1664
             "nv.nspname,"
 
1665
             "v.relname,"
 
1666
             "current_database(),"
 
1667
             "nt.nspname,"
 
1668
             "t.relname,"
 
1669
             "a.attname"
 
1670
             ") x"
 
1671
             " WHERE "
 
1672
             "view_schema=%1 AND view_name=%2" )
 
1673
    .arg( quotedValue( mSchemaName ) )
 
1674
    .arg( quotedValue( mTableName ) );
 
1675
 
 
1676
  if ( !primaryKey.isEmpty() )
 
1677
  {
 
1678
    viewColumnSql += QString( " AND column_name=%1" ).arg( quotedValue( primaryKey ) );
 
1679
  }
 
1680
 
 
1681
  Result viewColumnResult = connectionRO->PQexec( viewColumnSql );
 
1682
 
 
1683
  //find out view definition
 
1684
  QString viewDefSql = QString( "SELECT definition FROM pg_views WHERE schemaname=%1 AND viewname=%2" )
 
1685
                       .arg( quotedValue( mSchemaName ) )
 
1686
                       .arg( quotedValue( mTableName ) );
 
1687
  Result viewDefResult = connectionRO->PQexec( viewDefSql );
 
1688
  if ( PQntuples( viewDefResult ) < 1 )
 
1689
  {
 
1690
    return;
 
1691
  }
 
1692
 
 
1693
  QString viewDefinition( QString::fromUtf8( PQgetvalue( viewDefResult, 0, 0 ) ) );
 
1694
 
 
1695
  QString ns, relname, attname_table, attname_view;
 
1696
  SRC columnInformation;
 
1697
 
 
1698
  for ( int i = 0; i < PQntuples( viewColumnResult ); ++i )
 
1699
  {
 
1700
    ns = QString::fromUtf8( PQgetvalue( viewColumnResult, i, 0 ) );
 
1701
    relname = QString::fromUtf8( PQgetvalue( viewColumnResult, i, 1 ) );
 
1702
    attname_table = QString::fromUtf8( PQgetvalue( viewColumnResult, i, 2 ) );
 
1703
 
 
1704
    //find out original attribute name
 
1705
    attname_view = attname_table;
 
1706
 
 
1707
    //examine if the column name has been renamed in the view with AS
 
1708
    if ( viewDefinition.contains( " AS " ) )
 
1709
    {
 
1710
      // This regular expression needs more testing. Since the view
 
1711
      // definition comes from postgresql and has been 'standardised', we
 
1712
      // don't need to deal with everything that the user could put in a view
 
1713
      // definition. Does the regexp have to deal with the schema??
 
1714
 
 
1715
      QRegExp s( ".* \"?" + QRegExp::escape( relname ) +
 
1716
                 "\"?\\.\"?" + QRegExp::escape( attname_table ) +
 
1717
                 "\"? AS \"?(\\w+)\"?,* .*" );
 
1718
 
 
1719
      QgsDebugMsg( viewDefinition + "\n" + s.pattern() );
 
1720
 
 
1721
      if ( s.indexIn( viewDefinition ) != -1 )
1512
1722
      {
1513
 
        QgsDebugMsg("WARNING: Failed to find the column that " + ii->second.table_schema + "."
1514
 
                    + ii->second.table_name + "." + ii->second.column_name + " refers to.");
1515
 
      break;
 
1723
        attname_view = s.cap( 1 );
 
1724
        QgsDebugMsg( QString( "original view column name was: %1" ).arg( attname_view ) );
1516
1725
      }
1517
 
 
1518
 
      ii = jj;
1519
 
      ++count;
1520
 
    }
1521
 
 
1522
 
    if (count >= max_loops)
1523
 
    {
1524
 
      QgsDebugMsg("  Search for the underlying table.column for view column "
1525
 
                  + ii->second.table_schema + "." 
1526
 
                  + ii->second.table_name + "."
1527
 
                  + ii->second.column_name + " failed: exceeded maximum "
1528
 
                  + "interation limit (" + QString::number(max_loops) + ").");
1529
 
 
1530
 
      cols[ii->second.view_column_name] = SRC("","","","");
1531
 
    }
1532
 
    else if (ii != columnRelations.end())
1533
 
    {
1534
 
      cols[start_iter->second.view_column_name] = 
1535
 
        SRC(ii->second.table_schema, 
1536
 
            ii->second.table_name, 
1537
 
            ii->second.column_name, 
1538
 
            ii->second.column_type);
1539
 
 
1540
 
      QgsDebugMsg(QString(PQgetvalue(result, i, 0)) + QString(" derives from ") + QString(ii->second.table_schema) + "."
1541
 
                + ii->second.table_name + "." + ii->second.column_name);
1542
 
    }
 
1726
    }
 
1727
 
 
1728
    SRCFromViewColumn( ns, relname, attname_table, attname_view, viewDefinition, columnInformation );
 
1729
    cols.insert( std::make_pair( attname_view, columnInformation ) );
 
1730
    QgsDebugMsg( "Inserting into cols (for key " + attname_view + " ): " + columnInformation.schema + "." + columnInformation.relation + "." + columnInformation.column + "." + columnInformation.type );
1543
1731
  }
1544
 
  PQclear(result);
1545
1732
}
1546
1733
 
1547
1734
// Returns the minimum value of an attribute
1548
 
QString QgsPostgresProvider::minValue(int position){
1549
 
  // get the field name 
1550
 
  QgsField fld = attributeFields[position];
1551
 
  QString sql;
1552
 
  if(sqlWhereClause.isEmpty())
1553
 
  {
1554
 
    sql = QString("select min(\"%1\") from %2").arg(fld.name()).arg(mSchemaTableName);
1555
 
  }
1556
 
  else
1557
 
  {
1558
 
    sql = QString("select min(\"%1\") from %2").arg(fld.name()).arg(mSchemaTableName)+" where "+sqlWhereClause;
1559
 
  }
1560
 
  PGresult *rmin = PQexec(connection,(const char *)(sql.utf8()));
1561
 
  QString minValue = PQgetvalue(rmin,0,0);
1562
 
  PQclear(rmin);
1563
 
  return minValue;
 
1735
QVariant QgsPostgresProvider::minimumValue( int index )
 
1736
{
 
1737
  try
 
1738
  {
 
1739
    // get the field name
 
1740
    const QgsField &fld = field( index );
 
1741
    QString sql;
 
1742
    if ( sqlWhereClause.isEmpty() )
 
1743
    {
 
1744
      sql = QString( "select min(%1) from %2" )
 
1745
            .arg( quotedIdentifier( fld.name() ) )
 
1746
            .arg( mSchemaTableName );
 
1747
    }
 
1748
    else
 
1749
    {
 
1750
      sql = QString( "select min(%1) from %2 where %3" )
 
1751
            .arg( quotedIdentifier( fld.name() ) )
 
1752
            .arg( mSchemaTableName )
 
1753
            .arg( sqlWhereClause );
 
1754
    }
 
1755
    Result rmin = connectionRO->PQexec( sql );
 
1756
    return convertValue( fld.type(), QString::fromUtf8( PQgetvalue( rmin, 0, 0 ) ) );
 
1757
  }
 
1758
  catch ( PGFieldNotFound )
 
1759
  {
 
1760
    return QVariant( QString::null );
 
1761
  }
 
1762
}
 
1763
 
 
1764
// Returns the list of unique values of an attribute
 
1765
void QgsPostgresProvider::uniqueValues( int index, QList<QVariant> &uniqueValues, int limit )
 
1766
{
 
1767
  uniqueValues.clear();
 
1768
 
 
1769
  try
 
1770
  {
 
1771
    // get the field name
 
1772
    const QgsField &fld = field( index );
 
1773
    QString sql;
 
1774
    if ( sqlWhereClause.isEmpty() )
 
1775
    {
 
1776
      sql = QString( "select distinct %1 from %2 order by %1" )
 
1777
            .arg( quotedIdentifier( fld.name() ) )
 
1778
            .arg( mSchemaTableName );
 
1779
    }
 
1780
    else
 
1781
    {
 
1782
      sql = QString( "select distinct %1 from %2 where %3 order by %1" )
 
1783
            .arg( quotedIdentifier( fld.name() ) )
 
1784
            .arg( mSchemaTableName )
 
1785
            .arg( sqlWhereClause );
 
1786
    }
 
1787
 
 
1788
    if ( limit >= 0 )
 
1789
    {
 
1790
      sql += QString( " LIMIT %1" ).arg( limit );
 
1791
    }
 
1792
 
 
1793
    Result res = connectionRO->PQexec( sql );
 
1794
    if ( PQresultStatus( res ) == PGRES_TUPLES_OK )
 
1795
    {
 
1796
      for ( int i = 0; i < PQntuples( res ); i++ )
 
1797
        uniqueValues.append( convertValue( fld.type(), QString::fromUtf8( PQgetvalue( res, i, 0 ) ) ) );
 
1798
    }
 
1799
  }
 
1800
  catch ( PGFieldNotFound )
 
1801
  {
 
1802
  }
 
1803
}
 
1804
 
 
1805
void QgsPostgresProvider::enumValues( int index, QStringList& enumList )
 
1806
{
 
1807
  enumList.clear();
 
1808
 
 
1809
  QString typeName;
 
1810
  //find out type of index
 
1811
  QgsFieldMap::const_iterator f_it = attributeFields.find( index );
 
1812
  if ( f_it != attributeFields.constEnd() )
 
1813
  {
 
1814
    typeName = f_it.value().typeName();
 
1815
  }
 
1816
  else
 
1817
  {
 
1818
    return;
 
1819
  }
 
1820
 
 
1821
  //is type an enum?
 
1822
  QString typeSql = QString( "SELECT typtype FROM pg_type where typname = %1" ).arg( quotedValue( typeName ) );
 
1823
  Result typeRes = connectionRO->PQexec( typeSql );
 
1824
  if ( PQresultStatus( typeRes ) != PGRES_TUPLES_OK || PQntuples( typeRes ) < 1 )
 
1825
  {
 
1826
    return;
 
1827
  }
 
1828
 
 
1829
 
 
1830
  QString typtype = PQgetvalue( typeRes, 0, 0 );
 
1831
  if ( typtype.compare( "e", Qt::CaseInsensitive ) == 0 )
 
1832
  {
 
1833
    //try to read enum_range of attribute
 
1834
    if ( !parseEnumRange( enumList, f_it->name() ) )
 
1835
    {
 
1836
      enumList.clear();
 
1837
    }
 
1838
  }
 
1839
  else
 
1840
  {
 
1841
    //is there a domain check constraint for the attribute?
 
1842
    if ( !parseDomainCheckConstraint( enumList, f_it->name() ) )
 
1843
    {
 
1844
      enumList.clear();
 
1845
    }
 
1846
  }
 
1847
}
 
1848
 
 
1849
bool QgsPostgresProvider::parseEnumRange( QStringList& enumValues, const QString& attributeName ) const
 
1850
{
 
1851
  enumValues.clear();
 
1852
  QString enumRangeSql = QString( "SELECT enum_range(%1) from %2 limit 1" ).arg( quotedIdentifier( attributeName ) ).arg( mSchemaTableName );
 
1853
  Result enumRangeRes = connectionRO->PQexec( enumRangeSql );
 
1854
  if ( PQresultStatus( enumRangeRes ) == PGRES_TUPLES_OK && PQntuples( enumRangeRes ) > 0 )
 
1855
  {
 
1856
    QString enumRangeString = PQgetvalue( enumRangeRes, 0, 0 );
 
1857
    //strip away the brackets at begin and end
 
1858
    enumRangeString.chop( 1 );
 
1859
    enumRangeString.remove( 0, 1 );
 
1860
    QStringList rangeSplit = enumRangeString.split( "," );
 
1861
    QStringList::const_iterator range_it = rangeSplit.constBegin();
 
1862
    for ( ; range_it != rangeSplit.constEnd(); ++range_it )
 
1863
    {
 
1864
      QString currentEnumValue = *range_it;
 
1865
      //remove quotes from begin and end of the value
 
1866
      if ( currentEnumValue.startsWith( "'" ) || currentEnumValue.startsWith( "\"" ) )
 
1867
      {
 
1868
        currentEnumValue.remove( 0, 1 );
 
1869
      }
 
1870
      if ( currentEnumValue.endsWith( "'" ) || currentEnumValue.endsWith( "\"" ) )
 
1871
      {
 
1872
        currentEnumValue.chop( 1 );
 
1873
      }
 
1874
      enumValues << currentEnumValue;
 
1875
    }
 
1876
    return true;
 
1877
  }
 
1878
  return false;
 
1879
}
 
1880
 
 
1881
bool QgsPostgresProvider::parseDomainCheckConstraint( QStringList& enumValues, const QString& attributeName ) const
 
1882
{
 
1883
  enumValues.clear();
 
1884
 
 
1885
  //is it a domain type with a check constraint?
 
1886
  QString domainSql = QString( "SELECT domain_name from information_schema.columns where table_name = %1 and column_name = %2" ).arg( quotedValue( mTableName ) ).arg( quotedValue( attributeName ) );
 
1887
  Result domainResult = connectionRO->PQexec( domainSql );
 
1888
  if ( PQresultStatus( domainResult ) == PGRES_TUPLES_OK && PQntuples( domainResult ) > 0 )
 
1889
  {
 
1890
    //a domain type
 
1891
    QString domainCheckDefinitionSql = QString( "SELECT consrc FROM pg_constraint where conname = (SELECT constraint_name FROM information_schema.domain_constraints WHERE domain_name = %1)" ).arg( quotedValue( PQgetvalue( domainResult, 0, 0 ) ) );
 
1892
    Result domainCheckRes = connectionRO->PQexec( domainCheckDefinitionSql );
 
1893
    if ( PQresultStatus( domainCheckRes ) == PGRES_TUPLES_OK && PQntuples( domainCheckRes ) > 0 )
 
1894
    {
 
1895
      QString checkDefinition = QString::fromUtf8( PQgetvalue( domainCheckRes, 0, 0 ) );
 
1896
 
 
1897
      //we assume that the constraint is of the following form:
 
1898
      //(VALUE = ANY (ARRAY['a'::text, 'b'::text, 'c'::text, 'd'::text]))
 
1899
      //normally, postgresql creates that if the contstraint has been specified as 'VALUE in ('a', 'b', 'c', 'd')
 
1900
 
 
1901
      //todo: ANY must occure before ARRAY
 
1902
      int anyPos = checkDefinition.indexOf( "VALUE = ANY" );
 
1903
      int arrayPosition = checkDefinition.lastIndexOf( "ARRAY[" );
 
1904
      int closingBracketPos = checkDefinition.indexOf( "]", arrayPosition + 6 );
 
1905
 
 
1906
      if ( anyPos == -1 || anyPos >= arrayPosition )
 
1907
      {
 
1908
        return false; //constraint has not the required format
 
1909
      }
 
1910
 
 
1911
      if ( arrayPosition != -1 )
 
1912
      {
 
1913
        QString valueList = checkDefinition.mid( arrayPosition + 6, closingBracketPos );
 
1914
        QStringList commaSeparation = valueList.split( ",", QString::SkipEmptyParts );
 
1915
        QStringList::const_iterator cIt = commaSeparation.constBegin();
 
1916
        for ( ; cIt != commaSeparation.constEnd(); ++cIt )
 
1917
        {
 
1918
          //get string between ''
 
1919
          int beginQuotePos = cIt->indexOf( "'" );
 
1920
          int endQuotePos = cIt->lastIndexOf( "'" );
 
1921
          if ( beginQuotePos != -1 && ( endQuotePos - beginQuotePos ) > 1 )
 
1922
          {
 
1923
            enumValues << cIt->mid( beginQuotePos + 1, endQuotePos - beginQuotePos - 1 );
 
1924
          }
 
1925
        }
 
1926
      }
 
1927
      return true;
 
1928
    }
 
1929
  }
 
1930
  return false;
1564
1931
}
1565
1932
 
1566
1933
// Returns the maximum value of an attribute
1567
 
 
1568
 
QString QgsPostgresProvider::maxValue(int position){
1569
 
  // get the field name 
1570
 
  QgsField fld = attributeFields[position];
1571
 
  QString sql;
1572
 
  if(sqlWhereClause.isEmpty())
1573
 
  {
1574
 
    sql = QString("select max(\"%1\") from %2").arg(fld.name()).arg(mSchemaTableName);
1575
 
  }
1576
 
  else
1577
 
  {
1578
 
    sql = QString("select max(\"%1\") from %2").arg(fld.name()).arg(mSchemaTableName)+" where "+sqlWhereClause;
1579
 
  } 
1580
 
  PGresult *rmax = PQexec(connection,(const char *)(sql.utf8()));
1581
 
  QString maxValue = PQgetvalue(rmax,0,0);
1582
 
  PQclear(rmax);
1583
 
  return maxValue;
1584
 
}
1585
 
 
1586
 
 
1587
 
int QgsPostgresProvider::maxPrimaryKeyValue()
1588
 
{
1589
 
  QString sql;
1590
 
 
1591
 
  sql = QString("select max(\"%1\") from %2")
1592
 
           .arg(primaryKey)
1593
 
           .arg(mSchemaTableName);
1594
 
 
1595
 
  PGresult *rmax = PQexec(connection,(const char *)(sql.utf8()));
1596
 
  QString maxValue = PQgetvalue(rmax,0,0);
1597
 
  PQclear(rmax);
1598
 
 
1599
 
  return maxValue.toInt();
1600
 
}
1601
 
 
1602
 
 
1603
 
bool QgsPostgresProvider::isValid(){
 
1934
QVariant QgsPostgresProvider::maximumValue( int index )
 
1935
{
 
1936
  try
 
1937
  {
 
1938
    // get the field name
 
1939
    const QgsField &fld = field( index );
 
1940
    QString sql;
 
1941
    if ( sqlWhereClause.isEmpty() )
 
1942
    {
 
1943
      sql = QString( "select max(%1) from %2" )
 
1944
            .arg( quotedIdentifier( fld.name() ) )
 
1945
            .arg( mSchemaTableName );
 
1946
    }
 
1947
    else
 
1948
    {
 
1949
      sql = QString( "select max(%1) from %2 where %3" )
 
1950
            .arg( quotedIdentifier( fld.name() ) )
 
1951
            .arg( mSchemaTableName )
 
1952
            .arg( sqlWhereClause );
 
1953
    }
 
1954
    Result rmax = connectionRO->PQexec( sql );
 
1955
    return convertValue( fld.type(), QString::fromUtf8( PQgetvalue( rmax, 0, 0 ) ) );
 
1956
  }
 
1957
  catch ( PGFieldNotFound )
 
1958
  {
 
1959
    return QVariant( QString::null );
 
1960
  }
 
1961
}
 
1962
 
 
1963
 
 
1964
bool QgsPostgresProvider::isValid()
 
1965
{
1604
1966
  return valid;
1605
1967
}
1606
1968
 
1607
 
bool QgsPostgresProvider::addFeature(QgsFeature* f, int primaryKeyHighWater)
1608
 
{
1609
 
  QgsDebugMsg("QgsPostgresProvider::addFeature: Entering.");
1610
 
  
1611
 
  if (f)
1612
 
  {
1613
 
    // Determine which insertion method to use for WKB
1614
 
    // PostGIS 1.0+ uses BYTEA
1615
 
    // earlier versions use HEX
1616
 
    bool useWkbHex(FALSE);
1617
 
 
1618
 
    if (!gotPostgisVersion)
1619
 
    {
1620
 
      postgisVersion(connection);
1621
 
    }
1622
 
 
1623
 
    QgsDebugMsg("QgsPostgresProvider::addFeature: PostGIS version is  major: "  + QString::number(postgisVersionMajor) + ", minor: " + QString::number(postgisVersionMinor) + ".");
1624
 
 
1625
 
    if (postgisVersionMajor < 1)
1626
 
    {
1627
 
      useWkbHex = TRUE;
1628
 
    }
1629
 
 
1630
 
    // Start building insert string
1631
 
    QString insert("INSERT INTO ");
1632
 
    insert+=mSchemaTableName;
1633
 
    insert+=" (";
1634
 
 
1635
 
    // add the name of the geometry column to the insert statement
1636
 
    insert += "\"" + geometryColumn;
1637
 
 
1638
 
    // add the name of the primary key column to the insert statement
1639
 
    insert += "\",\"";
1640
 
    insert += primaryKey + "\"";
1641
 
 
1642
 
    QgsDebugMsg("QgsPostgresProvider::addFeature: Constructing insert SQL, currently at: " + insert);
1643
 
  
1644
 
    
1645
 
    
1646
 
    //add the names of the other fields to the insert
1647
 
    std::vector<QgsFeatureAttribute> attributevec=f->attributeMap();
1648
 
    
1649
 
    QgsDebugMsg("QgsPostgresProvider::addFeature: Got attribute map.");
1650
 
    
1651
 
    for(std::vector<QgsFeatureAttribute>::iterator it=attributevec.begin();it!=attributevec.end();++it)
1652
 
    {
1653
 
      QString fieldname=it->fieldName();
1654
 
      QgsDebugMsg("QgsPostgresProvider::addFeature: Checking field against: " + fieldname + ".");
1655
 
 
1656
 
            
1657
 
      //TODO: Check if field exists in this layer
1658
 
      // (Sometimes features will have fields that are not part of this layer since
1659
 
      // they have been pasted from other layers with a different field map)
1660
 
      bool fieldInLayer = FALSE;
1661
 
      
1662
 
      for (std::vector<QgsField>::iterator iter  = attributeFields.begin();
1663
 
                                           iter != attributeFields.end();
1664
 
                                         ++iter)
1665
 
      {
1666
 
        if ( iter->name() == it->fieldName() )
1667
 
        {
1668
 
          fieldInLayer = TRUE;
1669
 
          break;
1670
 
        }
1671
 
      }                                         
1672
 
      
1673
 
      if (
1674
 
           (fieldname != geometryColumn) &&
1675
 
           (fieldname != primaryKey) &&
1676
 
           (!(it->fieldValue().isEmpty())) && 
1677
 
           (fieldInLayer)
1678
 
         )
1679
 
      {
1680
 
        insert+=",\"";
1681
 
        insert+=fieldname +"\"";
1682
 
      }
1683
 
    }
1684
 
 
1685
 
    insert+=") VALUES (GeomFromWKB('";
1686
 
 
1687
 
    // Add the WKB geometry to the INSERT statement
1688
 
    unsigned char* geom = f->getGeometry();
1689
 
    for (int i=0; i < f->getGeometrySize(); ++i)
1690
 
    {
1691
 
      if (useWkbHex)
1692
 
      {
1693
 
        // PostGIS < 1.0 wants hex
1694
 
        QString hex = QString::number((int) geom[i], 16).upper();
1695
 
 
1696
 
        if (hex.length() == 1)
1697
 
        {
1698
 
          hex = "0" + hex;
1699
 
        }
1700
 
 
1701
 
        insert += hex;
1702
 
      }
1703
 
      else
1704
 
      {
1705
 
        // Postgis 1.0 wants bytea
1706
 
        QString oct = QString::number((int) geom[i], 8);
1707
 
 
1708
 
        if(oct.length()==3)
1709
 
        {
1710
 
            oct="\\\\"+oct;
1711
 
        }
1712
 
        else if(oct.length()==1)
1713
 
        {
1714
 
            oct="\\\\00"+oct;
1715
 
        }
1716
 
        else if(oct.length()==2)
1717
 
        {
1718
 
            oct="\\\\0"+oct; 
1719
 
        }
1720
 
 
1721
 
        insert += oct;
1722
 
      }
1723
 
    }
1724
 
 
1725
 
    if (useWkbHex)
1726
 
    {
1727
 
      insert += "',"+srid+")";
1728
 
    }
1729
 
    else
1730
 
    {
1731
 
      insert += "::bytea',"+srid+")";
1732
 
    }
1733
 
 
1734
 
    //add the primary key value to the insert statement
1735
 
    insert += ",";
1736
 
    insert += QString::number(primaryKeyHighWater);
1737
 
 
1738
 
    //add the field values to the insert statement
1739
 
    for(std::vector<QgsFeatureAttribute>::iterator it=attributevec.begin();it!=attributevec.end();++it)
1740
 
    {
1741
 
      QString fieldname=it->fieldName();
1742
 
      QgsDebugMsg("QgsPostgresProvider::addFeature: Checking field name " + fieldname);
1743
 
      
1744
 
      //TODO: Check if field exists in this layer
1745
 
      // (Sometimes features will have fields that are not part of this layer since
1746
 
      // they have been pasted from other layers with a different field map)
1747
 
      bool fieldInLayer = FALSE;
1748
 
      
1749
 
      for (std::vector<QgsField>::iterator iter  = attributeFields.begin();
1750
 
                                           iter != attributeFields.end();
1751
 
                                         ++iter)
1752
 
      {
1753
 
        if ( iter->name() == it->fieldName() )
1754
 
        {
1755
 
          fieldInLayer = TRUE;
1756
 
          break;
1757
 
        }
1758
 
      }                                         
1759
 
      
1760
 
      if (
1761
 
           (fieldname != geometryColumn) &&
1762
 
           (fieldname != primaryKey) &&
1763
 
           (!(it->fieldValue().isEmpty())) && 
1764
 
           (fieldInLayer)
1765
 
         )
1766
 
      {
1767
 
        QString fieldvalue = it->fieldValue();
1768
 
        bool charactertype=false;
1769
 
        insert+=",";
1770
 
 
1771
 
        QgsDebugMsg("QgsPostgresProvider::addFeature: Field is in layer with value " + fieldvalue);
1772
 
        
1773
 
        //add quotes if the field is a character or date type and not
1774
 
        //the postgres provided default value
1775
 
      if(fieldvalue != "NULL" && fieldvalue != getDefaultValue(it->fieldName(), f))
1776
 
        {
1777
 
          for(std::vector<QgsField>::iterator iter=attributeFields.begin();iter!=attributeFields.end();++iter)
1778
 
          {
1779
 
            if(iter->name()==it->fieldName())
1780
 
            {
1781
 
              if (
1782
 
                  iter->type().contains("char",false) > 0 || 
1783
 
                  iter->type() == "text"                  ||
1784
 
                  iter->type() == "date"                  ||
1785
 
                  iter->type() == "interval"              ||
1786
 
                  iter->type().contains("time",false) > 0      // includes time and timestamp
1787
 
                 )
1788
 
              {
1789
 
                charactertype=true;
1790
 
                break; // no need to continue with this loop
1791
 
              }
1792
 
            }
1793
 
          }
1794
 
        }
1795
 
 
1796
 
        if(charactertype)
1797
 
        {
1798
 
          insert+="'";
1799
 
        }
1800
 
        
1801
 
        // important: escape quotes in field value
1802
 
        fieldvalue.replace("'", "''");
1803
 
        
1804
 
        insert+=fieldvalue;
1805
 
        if(charactertype)
1806
 
        {
1807
 
          insert+="'";
1808
 
        }
1809
 
      }
1810
 
    }
1811
 
 
1812
 
    insert+=")";
1813
 
    QgsDebugMsg("insert statement is: " +insert);
1814
 
 
1815
 
    //send INSERT statement and do error handling
1816
 
    PGresult* result=PQexec(connection, (const char *)(insert.utf8()));
1817
 
    if(result==0)
1818
 
    {
1819
 
      QMessageBox::information(0,tr("INSERT error"),tr("An error occured during feature insertion"),QMessageBox::Ok);
1820
 
      return false;
1821
 
    }
1822
 
    ExecStatusType message=PQresultStatus(result);
1823
 
    if(message==PGRES_FATAL_ERROR)
1824
 
    {
1825
 
      // Use QgsMessage viewer here instead of a QMessageBox because
1826
 
      // we want to include the offending SQL, which may be quite
1827
 
      // long, and the QMessageBox doesn't wrap text, etc.
1828
 
      
1829
 
      QString sqlDetails = PQresultErrorMessage(result);
1830
 
      sqlDetails += tr("The sql was:\n\n") + insert;
1831
 
      QgsMessageViewer viewer;
1832
 
      viewer.setWindowTitle(tr("SQL error"));
1833
 
      viewer.setMessageAsPlainText(sqlDetails);
1834
 
      viewer.exec();
1835
 
 
1836
 
      //      QMessageBox::information(0,tr("INSERT error"), sqlDetails,QMessageBox::Ok);
1837
 
      return false;
1838
 
    }
1839
 
    
1840
 
    QgsDebugMsg("QgsPostgresProvider::addFeature: Exiting with true.");
1841
 
    return true;
1842
 
  }
1843
 
  
1844
 
  QgsDebugMsg("QgsPostgresProvider::addFeature: Exiting with false.");
1845
 
  
1846
 
  return false;
1847
 
}
1848
 
 
1849
 
QString QgsPostgresProvider::getDefaultValue(const QString& attr, QgsFeature* f)
1850
 
{
 
1969
QVariant QgsPostgresProvider::defaultValue( QString fieldName, QString tableName, QString schemaName )
 
1970
{
 
1971
  if ( schemaName.isNull() )
 
1972
    schemaName = mSchemaName;
 
1973
  if ( tableName.isNull() )
 
1974
    tableName = mTableName;
 
1975
 
1851
1976
  // Get the default column value from the Postgres information
1852
1977
  // schema. If there is no default we return an empty string.
1853
1978
 
1854
1979
  // Maintaining a cache of the results of this query would be quite
1855
1980
  // simple and if this query is called lots, could save some time.
1856
1981
 
1857
 
  QString sql("SELECT column_default FROM "
1858
 
              "information_schema.columns WHERE "
1859
 
              "column_default IS NOT NULL AND "
1860
 
              "table_schema = '" + mSchemaName + "' AND "
1861
 
              "table_name = '" + mTableName + "' AND "
1862
 
              "column_name = '" + attr + "'");
1863
 
 
1864
 
  QString defaultValue("");
1865
 
 
1866
 
  PGresult* result = PQexec(connection, (const char*)(sql.utf8()));
1867
 
 
1868
 
  if (PQntuples(result) == 1)
1869
 
    defaultValue = PQgetvalue(result, 0, 0);
1870
 
 
1871
 
  PQclear(result);
 
1982
  QString sql( "SELECT column_default FROM"
 
1983
               " information_schema.columns WHERE"
 
1984
               " column_default IS NOT NULL"
 
1985
               " AND table_schema = " + quotedValue( schemaName ) +
 
1986
               " AND table_name = " + quotedValue( tableName ) +
 
1987
               " AND column_name = " + quotedValue( fieldName ) );
 
1988
 
 
1989
  QVariant defaultValue( QString::null );
 
1990
 
 
1991
  Result result = connectionRO->PQexec( sql );
 
1992
 
 
1993
  if ( PQntuples( result ) == 1 && !PQgetisnull( result, 0, 0 ) )
 
1994
    defaultValue = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
1872
1995
 
1873
1996
  return defaultValue;
1874
1997
}
1875
1998
 
1876
 
bool QgsPostgresProvider::deleteFeature(int id)
 
1999
QVariant QgsPostgresProvider::defaultValue( int fieldId )
1877
2000
{
1878
 
  QString sql("DELETE FROM "+mSchemaTableName+" WHERE \""+primaryKey+"\" = "+QString::number(id));
1879
 
  QgsDebugMsg("delete sql: " + sql);
1880
 
 
1881
 
  //send DELETE statement and do error handling
1882
 
  PGresult* result=PQexec(connection, (const char *)(sql.utf8()));
1883
 
  if(result==0)
1884
 
  {
1885
 
    QMessageBox::information(0,tr("DELETE error"),tr("An error occured during deletion from disk"),QMessageBox::Ok);
1886
 
    return false;
1887
 
  }
1888
 
  ExecStatusType message=PQresultStatus(result);
1889
 
  if(message==PGRES_FATAL_ERROR)
1890
 
  {
1891
 
    QMessageBox::information(0,tr("DELETE error"),QString(PQresultErrorMessage(result)),QMessageBox::Ok);
1892
 
    return false;
1893
 
  }
1894
 
 
1895
 
  return true;
 
2001
  try
 
2002
  {
 
2003
    return defaultValue( field( fieldId ).name() );
 
2004
  }
 
2005
  catch ( PGFieldNotFound )
 
2006
  {
 
2007
    return QVariant( QString::null );
 
2008
  }
1896
2009
}
1897
2010
 
1898
2011
/**
1899
2012
 * Check to see if GEOS is available
1900
2013
 */
1901
 
bool QgsPostgresProvider::hasGEOS(PGconn *connection){
 
2014
bool QgsPostgresProvider::Conn::hasGEOS()
 
2015
{
1902
2016
  // make sure info is up to date for the current connection
1903
 
  postgisVersion(connection);
 
2017
  postgisVersion();
1904
2018
  // get geos capability
1905
2019
  return geosAvailable;
1906
2020
}
1907
2021
 
1908
2022
/* Functions for determining available features in postGIS */
1909
 
QString QgsPostgresProvider::postgisVersion(PGconn *connection)
 
2023
QString QgsPostgresProvider::Conn::postgisVersion()
1910
2024
{
1911
 
  PGresult *result = PQexec(connection, "select postgis_version()");
1912
 
  postgisVersionInfo = PQgetvalue(result,0,0);
1913
 
  QgsDebugMsg("PostGIS version info: " + postgisVersionInfo);
1914
 
  PQclear(result);
1915
 
 
1916
 
  QStringList postgisParts = QStringList::split(" ", postgisVersionInfo);
 
2025
  Result result = PQexec( "select postgis_version()" );
 
2026
  if ( PQntuples( result ) != 1 )
 
2027
  {
 
2028
    QgsDebugMsg( "Retrieval of postgis version failed" );
 
2029
    return QString::null;
 
2030
  }
 
2031
 
 
2032
  postgisVersionInfo = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
 
2033
 
 
2034
  QgsDebugMsg( "PostGIS version info: " + postgisVersionInfo );
 
2035
 
 
2036
  QStringList postgisParts = postgisVersionInfo.split( " ", QString::SkipEmptyParts );
1917
2037
 
1918
2038
  // Get major and minor version
1919
 
  QStringList postgisVersionParts = QStringList::split(".", postgisParts[0]);
 
2039
  QStringList postgisVersionParts = postgisParts[0].split( ".", QString::SkipEmptyParts );
 
2040
  if ( postgisVersionParts.size() < 2 )
 
2041
  {
 
2042
    QgsDebugMsg( "Could not parse postgis version" );
 
2043
    return QString::null;
 
2044
  }
1920
2045
 
1921
2046
  postgisVersionMajor = postgisVersionParts[0].toInt();
1922
2047
  postgisVersionMinor = postgisVersionParts[1].toInt();
1927
2052
  projAvailable = false;
1928
2053
 
1929
2054
  // parse out the capabilities and store them
1930
 
  QStringList geos = postgisParts.grep("GEOS");
1931
 
  if(geos.size() == 1){
1932
 
    geosAvailable = (geos[0].find("=1") > -1);  
1933
 
  }
1934
 
  QStringList gist = postgisParts.grep("STATS");
1935
 
  if(gist.size() == 1){
1936
 
    gistAvailable = (geos[0].find("=1") > -1);
1937
 
  }
1938
 
  QStringList proj = postgisParts.grep("PROJ");
1939
 
  if(proj.size() == 1){
1940
 
    projAvailable = (proj[0].find("=1") > -1);
1941
 
  }
1942
 
 
1943
 
  gotPostgisVersion = TRUE;
 
2055
  QStringList geos = postgisParts.filter( "GEOS" );
 
2056
  if ( geos.size() == 1 )
 
2057
  {
 
2058
    geosAvailable = ( geos[0].indexOf( "=1" ) > -1 );
 
2059
  }
 
2060
  QStringList gist = postgisParts.filter( "STATS" );
 
2061
  if ( gist.size() == 1 )
 
2062
  {
 
2063
    gistAvailable = ( geos[0].indexOf( "=1" ) > -1 );
 
2064
  }
 
2065
  QStringList proj = postgisParts.filter( "PROJ" );
 
2066
  if ( proj.size() == 1 )
 
2067
  {
 
2068
    projAvailable = ( proj[0].indexOf( "=1" ) > -1 );
 
2069
  }
 
2070
 
 
2071
  mUseWkbHex = postgisVersionMajor < 1;
 
2072
 
 
2073
  gotPostgisVersion = true;
1944
2074
 
1945
2075
  return postgisVersionInfo;
1946
2076
}
1947
2077
 
1948
 
bool QgsPostgresProvider::addFeatures(std::list<QgsFeature*> const flist)
1949
 
{
1950
 
  bool returnvalue=true;
1951
 
  PQexec(connection,"BEGIN");
1952
 
 
1953
 
  int primaryKeyHighWater = maxPrimaryKeyValue();
1954
 
 
1955
 
  for(std::list<QgsFeature*>::const_iterator it=flist.begin();it!=flist.end();++it)
1956
 
  {
1957
 
    primaryKeyHighWater++;
1958
 
    if(!addFeature(*it, primaryKeyHighWater))
1959
 
    {
1960
 
      returnvalue=false;
1961
 
      // TODO: exit loop here?
1962
 
    }
1963
 
  }
1964
 
  PQexec(connection,"COMMIT");
1965
 
  reset();
1966
 
  return returnvalue;
1967
 
}
1968
 
 
1969
 
bool QgsPostgresProvider::deleteFeatures(std::list<int> const & id)
1970
 
{
1971
 
  bool returnvalue=true;
1972
 
  PQexec(connection,"BEGIN");
1973
 
  for(std::list<int>::const_iterator it=id.begin();it!=id.end();++it)
1974
 
  {
1975
 
    if(!deleteFeature(*it))
1976
 
    {
1977
 
      returnvalue=false;
1978
 
    }
1979
 
  }
1980
 
  PQexec(connection,"COMMIT");
1981
 
  reset();
1982
 
  return returnvalue;
1983
 
}
1984
 
 
1985
 
bool QgsPostgresProvider::addAttributes(std::map<QString,QString> const & name)
1986
 
{
1987
 
  bool returnvalue=true;
1988
 
  PQexec(connection,"BEGIN");
1989
 
  for(std::map<QString,QString>::const_iterator iter=name.begin();iter!=name.end();++iter)
1990
 
  {
1991
 
    QString sql="ALTER TABLE "+mSchemaTableName+" ADD COLUMN "+(*iter).first+" "+(*iter).second;
1992
 
    QgsDebugMsg(sql);
1993
 
 
1994
 
    //send sql statement and do error handling
1995
 
    PGresult* result=PQexec(connection, (const char *)(sql.utf8()));
1996
 
    if(result==0)
1997
 
    {
1998
 
      returnvalue=false;
1999
 
      ExecStatusType message=PQresultStatus(result);
2000
 
      if(message==PGRES_FATAL_ERROR)
2001
 
      {
2002
 
        QMessageBox::information(0,"ALTER TABLE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
2003
 
      } 
2004
 
    }
2005
 
  }
2006
 
  PQexec(connection,"COMMIT");
2007
 
  reset();
2008
 
  return returnvalue;
2009
 
}
2010
 
 
2011
 
bool QgsPostgresProvider::deleteAttributes(std::set<QString> const & name)
2012
 
{
2013
 
  bool returnvalue=true;
2014
 
  PQexec(connection,"BEGIN");
2015
 
  for(std::set<QString>::const_iterator iter=name.begin();iter!=name.end();++iter)
2016
 
  {
2017
 
    QString sql="ALTER TABLE "+mSchemaTableName+" DROP COLUMN "+(*iter);
2018
 
    QgsDebugMsg(sql);
2019
 
 
2020
 
    //send sql statement and do error handling
2021
 
    PGresult* result=PQexec(connection, (const char *)(sql.utf8()));
2022
 
    if(result==0)
2023
 
    {
2024
 
      returnvalue=false;
2025
 
      ExecStatusType message=PQresultStatus(result);
2026
 
      if(message==PGRES_FATAL_ERROR)
2027
 
      {
2028
 
        QMessageBox::information(0,"ALTER TABLE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
2029
 
      }
2030
 
    }
2031
 
    else
2032
 
    {
 
2078
QByteArray QgsPostgresProvider::paramValue( QString fieldValue, const QString &defaultValue ) const
 
2079
{
 
2080
  if ( fieldValue.isNull() )
 
2081
    return QByteArray( 0 );  // QByteArray(0).isNull() is true
 
2082
 
 
2083
  if ( fieldValue == defaultValue && !defaultValue.isNull() )
 
2084
  {
 
2085
    PGresult *result = connectionRW->PQexec( QString( "select %1" ).arg( defaultValue ) );
 
2086
    if ( PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2087
      throw PGException( result );
 
2088
 
 
2089
    if ( PQgetisnull( result, 0, 0 ) )
 
2090
    {
 
2091
      PQclear( result );
 
2092
      return QByteArray( 0 );  // QByteArray(0).isNull() is true
 
2093
    }
 
2094
    else
 
2095
    {
 
2096
      QString val = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
 
2097
      PQclear( result );
 
2098
      return val.toUtf8();
 
2099
    }
 
2100
  }
 
2101
 
 
2102
  return fieldValue.toUtf8();
 
2103
}
 
2104
 
 
2105
bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
 
2106
{
 
2107
  if ( flist.size() == 0 )
 
2108
    return true;
 
2109
 
 
2110
  if ( !connectRW() )
 
2111
    return false;
 
2112
 
 
2113
  bool returnvalue = true;
 
2114
 
 
2115
  try
 
2116
  {
 
2117
    connectionRW->PQexecNR( "BEGIN" );
 
2118
 
 
2119
    // Prepare the INSERT statement
 
2120
    QString insert = QString( "INSERT INTO %1(%2" )
 
2121
                     .arg( mSchemaTableName )
 
2122
                     .arg( quotedIdentifier( geometryColumn ) ),
 
2123
                     values = QString( ") VALUES (GeomFromWKB($1%1,%2)" )
 
2124
                              .arg( connectionRW->useWkbHex() ? "" : "::bytea" )
 
2125
                              .arg( srid );
 
2126
 
 
2127
    int offset;
 
2128
    if ( primaryKeyType != "tid" )
 
2129
    {
 
2130
      insert += "," + quotedIdentifier( primaryKey );
 
2131
      values += ",$2";
 
2132
      offset = 3;
 
2133
    }
 
2134
    else
 
2135
    {
 
2136
      offset = 2;
 
2137
    }
 
2138
 
 
2139
    const QgsAttributeMap &attributevec = flist[0].attributeMap();
 
2140
 
 
2141
    QStringList defaultValues;
 
2142
    QList<int> fieldId;
 
2143
 
 
2144
    // look for unique attribute values to place in statement instead of passing as parameter
 
2145
    // e.g. for defaults
 
2146
    for ( QgsAttributeMap::const_iterator it = attributevec.begin(); it != attributevec.end(); it++ )
 
2147
    {
 
2148
      QgsFieldMap::const_iterator fit = attributeFields.find( it.key() );
 
2149
      if ( fit == attributeFields.end() )
 
2150
        continue;
 
2151
 
 
2152
      QString fieldname = fit->name();
 
2153
 
 
2154
      QgsDebugMsg( "Checking field against: " + fieldname );
 
2155
 
 
2156
      if ( fieldname.isEmpty() || fieldname == geometryColumn || fieldname == primaryKey )
 
2157
        continue;
 
2158
 
 
2159
      int i;
 
2160
      for ( i = 1; i < flist.size(); i++ )
 
2161
      {
 
2162
        const QgsAttributeMap &attributevec = flist[i].attributeMap();
 
2163
 
 
2164
        QgsAttributeMap::const_iterator thisit = attributevec.find( it.key() );
 
2165
        if ( thisit == attributevec.end() )
 
2166
          break;
 
2167
 
 
2168
        if ( *thisit != *it )
 
2169
          break;
 
2170
      }
 
2171
 
 
2172
      insert += "," + quotedIdentifier( fieldname );
 
2173
 
 
2174
      QString defVal = defaultValue( it.key() ).toString();
 
2175
 
 
2176
      if ( i == flist.size() )
 
2177
      {
 
2178
        if ( *it == defVal )
 
2179
        {
 
2180
          if ( defVal.isNull() )
 
2181
          {
 
2182
            values += ",NULL";
 
2183
          }
 
2184
          else
 
2185
          {
 
2186
            values += "," + defVal;
 
2187
          }
 
2188
        }
 
2189
        else if ( fit->typeName() == "geometry" )
 
2190
        {
 
2191
          values += QString( ",geomfromewkt(%1)" ).arg( quotedValue( it->toString() ) );
 
2192
        }
 
2193
        else
 
2194
        {
 
2195
          values += "," + quotedValue( it->toString() );
 
2196
        }
 
2197
      }
 
2198
      else
 
2199
      {
 
2200
        // value is not unique => add parameter
 
2201
        if ( fit->typeName() == "geometry" )
 
2202
        {
 
2203
          values += QString( ",geomfromewkt($%1)" ).arg( defaultValues.size() + offset );
 
2204
        }
 
2205
        else
 
2206
        {
 
2207
          values += QString( ",$%1" ).arg( defaultValues.size() + offset );
 
2208
        }
 
2209
        defaultValues.append( defVal );
 
2210
        fieldId.append( it.key() );
 
2211
      }
 
2212
    }
 
2213
 
 
2214
    insert += values + ")";
 
2215
 
 
2216
    QgsDebugMsg( QString( "prepare addfeatures: %1" ).arg( insert ) );
 
2217
    PGresult *stmt = connectionRW->PQprepare( "addfeatures", insert, fieldId.size() + offset - 1, NULL );
 
2218
    if ( stmt == 0 || PQresultStatus( stmt ) == PGRES_FATAL_ERROR )
 
2219
      throw PGException( stmt );
 
2220
    PQclear( stmt );
 
2221
 
 
2222
    QList<int> newIds;
 
2223
 
 
2224
    for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); features++ )
 
2225
    {
 
2226
      const QgsAttributeMap &attributevec = features->attributeMap();
 
2227
 
 
2228
      QString geomParam;
 
2229
      appendGeomString( features->geometry(), geomParam );
 
2230
 
 
2231
      QStringList params;
 
2232
      params << geomParam;
 
2233
 
 
2234
      if ( primaryKeyType != "tid" )
 
2235
      {
 
2236
        int id = paramValue( primaryKeyDefault(), primaryKeyDefault() ).toInt();
 
2237
        params << QString::number( id );
 
2238
        newIds << id;
 
2239
      }
 
2240
 
 
2241
      for ( int i = 0; i < fieldId.size(); i++ )
 
2242
        params << paramValue( attributevec[ fieldId[i] ].toString(), defaultValues[i] );
 
2243
 
 
2244
      PGresult *result = connectionRW->PQexecPrepared( "addfeatures", params );
 
2245
      if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2246
        throw PGException( result );
 
2247
      PQclear( result );
 
2248
    }
 
2249
 
 
2250
    if ( flist.size() == newIds.size() )
 
2251
      for ( int i = 0; i < flist.size(); i++ )
 
2252
        flist[i].setFeatureId( newIds[i] );
 
2253
 
 
2254
    connectionRW->PQexecNR( "DEALLOCATE addfeatures" );
 
2255
    connectionRW->PQexecNR( "COMMIT" );
 
2256
 
 
2257
    featuresCounted += flist.size();
 
2258
  }
 
2259
  catch ( PGException &e )
 
2260
  {
 
2261
    e.showErrorMessage( tr( "Error while adding features" ) );
 
2262
    connectionRW->PQexecNR( "ROLLBACK" );
 
2263
    connectionRW->PQexecNR( "DEALLOCATE addfeatures" );
 
2264
    returnvalue = false;
 
2265
  }
 
2266
 
 
2267
  rewind();
 
2268
  return returnvalue;
 
2269
}
 
2270
 
 
2271
bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds & id )
 
2272
{
 
2273
  bool returnvalue = true;
 
2274
 
 
2275
  if ( !connectRW() )
 
2276
    return false;
 
2277
 
 
2278
  try
 
2279
  {
 
2280
    connectionRW->PQexecNR( "BEGIN" );
 
2281
 
 
2282
    for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it )
 
2283
    {
 
2284
      QString sql = QString( "DELETE FROM %1 WHERE %2" )
 
2285
                    .arg( mSchemaTableName ).arg( whereClause( *it ) );
 
2286
      QgsDebugMsg( "delete sql: " + sql );
 
2287
 
 
2288
      //send DELETE statement and do error handling
 
2289
      PGresult *result = connectionRW->PQexec( sql );
 
2290
      if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2291
        throw PGException( result );
 
2292
      PQclear( result );
 
2293
    }
 
2294
 
 
2295
    connectionRW->PQexecNR( "COMMIT" );
 
2296
 
 
2297
    featuresCounted -= id.size();
 
2298
  }
 
2299
  catch ( PGException &e )
 
2300
  {
 
2301
    e.showErrorMessage( tr( "Error while deleting features" ) );
 
2302
    connectionRW->PQexecNR( "ROLLBACK" );
 
2303
    returnvalue = false;
 
2304
  }
 
2305
  rewind();
 
2306
  return returnvalue;
 
2307
}
 
2308
 
 
2309
bool QgsPostgresProvider::addAttributes( const QList<QgsField> &attributes )
 
2310
{
 
2311
  bool returnvalue = true;
 
2312
 
 
2313
  if ( !connectRW() )
 
2314
    return false;
 
2315
 
 
2316
  try
 
2317
  {
 
2318
    connectionRW->PQexecNR( "BEGIN" );
 
2319
 
 
2320
    for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
 
2321
    {
 
2322
      QString type = iter->typeName();
 
2323
      if ( type == "char" || type == "varchar" )
 
2324
      {
 
2325
        type = QString( "%1(%2)" ).arg( type ).arg( iter->length() );
 
2326
      }
 
2327
      else if ( type == "numeric" || type == "decimal" )
 
2328
      {
 
2329
        type = QString( "%1(%2,%3)" ).arg( type ).arg( iter->length() ).arg( iter->precision() );
 
2330
      }
 
2331
 
 
2332
      QString sql = QString( "ALTER TABLE %1 ADD COLUMN %2 %3" )
 
2333
                    .arg( mSchemaTableName )
 
2334
                    .arg( quotedIdentifier( iter->name() ) )
 
2335
                    .arg( type );
 
2336
      QgsDebugMsg( sql );
 
2337
 
 
2338
      //send sql statement and do error handling
 
2339
      PGresult *result = connectionRW->PQexec( sql );
 
2340
      if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2341
        throw PGException( result );
 
2342
      PQclear( result );
 
2343
 
 
2344
      if ( !iter->comment().isEmpty() )
 
2345
      {
 
2346
        sql = QString( "COMMENT ON COLUMN %1.%2 IS %3" )
 
2347
              .arg( mSchemaTableName )
 
2348
              .arg( quotedIdentifier( iter->name() ) )
 
2349
              .arg( quotedValue( iter->comment() ) );
 
2350
        result = connectionRW->PQexec( sql );
 
2351
        if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2352
          throw PGException( result );
 
2353
        PQclear( result );
 
2354
      }
 
2355
    }
 
2356
 
 
2357
    connectionRW->PQexecNR( "COMMIT" );
 
2358
  }
 
2359
  catch ( PGException &e )
 
2360
  {
 
2361
    e.showErrorMessage( tr( "Error while adding attributes" ) );
 
2362
    connectionRW->PQexecNR( "ROLLBACK" );
 
2363
    returnvalue = false;
 
2364
  }
 
2365
 
 
2366
  rewind();
 
2367
  return returnvalue;
 
2368
}
 
2369
 
 
2370
bool QgsPostgresProvider::deleteAttributes( const QgsAttributeIds& ids )
 
2371
{
 
2372
  bool returnvalue = true;
 
2373
 
 
2374
  if ( !connectRW() )
 
2375
    return false;
 
2376
 
 
2377
  try
 
2378
  {
 
2379
    connectionRW->PQexecNR( "BEGIN" );
 
2380
 
 
2381
    for ( QgsAttributeIds::const_iterator iter = ids.begin(); iter != ids.end(); ++iter )
 
2382
    {
 
2383
      QgsFieldMap::const_iterator field_it = attributeFields.find( *iter );
 
2384
      if ( field_it == attributeFields.constEnd() )
 
2385
        continue;
 
2386
 
 
2387
      QString column = field_it->name();
 
2388
      QString sql = QString( "ALTER TABLE %1 DROP COLUMN %2" )
 
2389
                    .arg( mSchemaTableName )
 
2390
                    .arg( quotedIdentifier( column ) );
 
2391
 
 
2392
      //send sql statement and do error handling
 
2393
      PGresult *result = connectionRW->PQexec( sql );
 
2394
      if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2395
        throw PGException( result );
 
2396
      PQclear( result );
 
2397
 
2033
2398
      //delete the attribute from attributeFields
2034
 
      for(std::vector<QgsField>::iterator it=attributeFields.begin();it!=attributeFields.end();++it)
2035
 
      {
2036
 
        if((*it).name()==(*iter))
2037
 
        {
2038
 
          attributeFields.erase(it);
2039
 
          break;
2040
 
        }
2041
 
      }
2042
 
    }
2043
 
  }
2044
 
  PQexec(connection,"COMMIT");
2045
 
  reset();
2046
 
  return returnvalue;
2047
 
}
2048
 
 
2049
 
bool QgsPostgresProvider::changeAttributeValues(std::map<int,std::map<QString,QString> > const & attr_map)
2050
 
{
2051
 
  bool returnvalue=true; 
2052
 
  PQexec(connection,"BEGIN");
2053
 
 
2054
 
  for(std::map<int,std::map<QString,QString> >::const_iterator iter=attr_map.begin();iter!=attr_map.end();++iter)
2055
 
  {
2056
 
    for(std::map<QString,QString>::const_iterator siter=(*iter).second.begin();siter!=(*iter).second.end();++siter)
2057
 
    {
2058
 
      QString val = (*siter).second;
2059
 
      
2060
 
      // escape quotes
2061
 
      val.replace("'", "''");
2062
 
      
2063
 
      QString sql="UPDATE "+mSchemaTableName+" SET "+(*siter).first+"='"+val+"' WHERE \"" +primaryKey+"\"="+QString::number((*iter).first);
2064
 
      QgsDebugMsg(sql);
2065
 
 
2066
 
      // s end sql statement and do error handling
2067
 
      // TODO: Make all error handling like this one
2068
 
      PGresult* result=PQexec(connection, (const char *)(sql.utf8()));
2069
 
      if (result==0)
2070
 
      {
2071
 
        QMessageBox::critical(0, tr("PostGIS error"), 
2072
 
                                 tr("An error occured contacting the PostgreSQL database"),
2073
 
                                 QMessageBox::Ok,
2074
 
                                 Qt::NoButton);
2075
 
        return false;
2076
 
      }
2077
 
      ExecStatusType message=PQresultStatus(result);
2078
 
      if(message==PGRES_FATAL_ERROR)
2079
 
      {
2080
 
        QMessageBox::information(0, tr("PostGIS error"), 
2081
 
                                 tr("The PostgreSQL database returned: ")
2082
 
                                   + QString(PQresultErrorMessage(result))
2083
 
                                   + "\n"
2084
 
                                   + tr("When trying: ")
2085
 
                                   + sql,
2086
 
                                 QMessageBox::Ok,
2087
 
                                 Qt::NoButton);
2088
 
        return false;
2089
 
      }
2090
 
 
2091
 
    }
2092
 
  }
2093
 
  PQexec(connection,"COMMIT");
2094
 
  reset();
2095
 
  return returnvalue;
2096
 
}
2097
 
 
2098
 
bool QgsPostgresProvider::changeGeometryValues(std::map<int, QgsGeometry> & geometry_map)
2099
 
{
2100
 
  QgsDebugMsg("QgsPostgresProvider::changeGeometryValues: entering.");
2101
 
 
2102
 
  bool returnvalue = true;
2103
 
 
2104
 
  // Determine which insertion method to use for WKB
2105
 
  // PostGIS 1.0+ uses BYTEA
2106
 
  // earlier versions use HEX
2107
 
  bool useWkbHex(FALSE);
2108
 
 
2109
 
  if (!gotPostgisVersion)
2110
 
  {
2111
 
    postgisVersion(connection);
2112
 
  }
2113
 
 
2114
 
  QgsDebugMsg("QgsPostgresProvider::addFeature: PostGIS version is major: "  + QString::number(postgisVersionMajor) + ", minor: " + QString::number(postgisVersionMinor) + ".");
2115
 
 
2116
 
  if (postgisVersionMajor < 1)
2117
 
  {
2118
 
    useWkbHex = TRUE;
2119
 
  }
2120
 
 
2121
 
  // Start the PostGIS transaction
2122
 
 
2123
 
  PQexec(connection,"BEGIN");
2124
 
 
2125
 
  for(std::map<int, QgsGeometry>::const_iterator iter  = geometry_map.begin();
2126
 
                                                 iter != geometry_map.end();
2127
 
                                               ++iter)
2128
 
  {
2129
 
 
2130
 
    QgsDebugMsg("QgsPostgresProvider::changeGeometryValues: iterating over the map of changed geometries...");
2131
 
    
2132
 
    if (iter->second.wkbBuffer())
2133
 
    {
2134
 
      QgsDebugMsg("QgsPostgresProvider::changeGeometryValues: iterating over feature id " + iter->first);
2135
 
    
2136
 
      QString sql = "UPDATE "+ mSchemaTableName +" SET \"" + 
2137
 
                    geometryColumn + "\"=";
2138
 
 
2139
 
      sql += "GeomFromWKB('";
2140
 
 
2141
 
      // Add the WKB geometry to the UPDATE statement
2142
 
      unsigned char* geom = iter->second.wkbBuffer();
2143
 
      for (int i=0; i < iter->second.wkbSize(); ++i)
2144
 
      {
2145
 
        if (useWkbHex)
2146
 
        {
2147
 
          // PostGIS < 1.0 wants hex
2148
 
          QString hex = QString::number((int) geom[i], 16).upper();
2149
 
 
2150
 
          if (hex.length() == 1)
2151
 
          {
2152
 
            hex = "0" + hex;
2153
 
          }
2154
 
 
2155
 
          sql += hex;
 
2399
      attributeFields.remove( *iter );
 
2400
    }
 
2401
 
 
2402
    connectionRW->PQexecNR( "COMMIT" );
 
2403
  }
 
2404
  catch ( PGException &e )
 
2405
  {
 
2406
    e.showErrorMessage( tr( "Error while deleting attributes" ) );
 
2407
    connectionRW->PQexecNR( "ROLLBACK" );
 
2408
    returnvalue = false;
 
2409
  }
 
2410
 
 
2411
  rewind();
 
2412
  return returnvalue;
 
2413
}
 
2414
 
 
2415
bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap & attr_map )
 
2416
{
 
2417
  bool returnvalue = true;
 
2418
 
 
2419
  if ( !connectRW() )
 
2420
    return false;
 
2421
 
 
2422
  try
 
2423
  {
 
2424
    connectionRW->PQexecNR( "BEGIN" );
 
2425
 
 
2426
    // cycle through the features
 
2427
    for ( QgsChangedAttributesMap::const_iterator iter = attr_map.begin(); iter != attr_map.end(); ++iter )
 
2428
    {
 
2429
      int fid = iter.key();
 
2430
 
 
2431
      // skip added features
 
2432
      if ( fid < 0 )
 
2433
        continue;
 
2434
 
 
2435
      QString sql = QString( "UPDATE %1 SET " ).arg( mSchemaTableName );
 
2436
      bool first = true;
 
2437
 
 
2438
      const QgsAttributeMap& attrs = iter.value();
 
2439
 
 
2440
      // cycle through the changed attributes of the feature
 
2441
      for ( QgsAttributeMap::const_iterator siter = attrs.begin(); siter != attrs.end(); ++siter )
 
2442
      {
 
2443
        try
 
2444
        {
 
2445
          QgsField fld = field( siter.key() );
 
2446
 
 
2447
          if ( !first )
 
2448
            sql += ",";
 
2449
          else
 
2450
            first = false;
 
2451
 
 
2452
          sql += QString( fld.typeName() != "geometry" ? "%1=%2" : "%1=geomfromewkt(%2)" )
 
2453
                 .arg( quotedIdentifier( fld.name() ) )
 
2454
                 .arg( quotedValue( siter->toString() ) );
 
2455
        }
 
2456
        catch ( PGFieldNotFound )
 
2457
        {
 
2458
          // Field was missing - shouldn't happen
 
2459
        }
 
2460
      }
 
2461
 
 
2462
      sql += QString( " WHERE %1" ).arg( whereClause( fid ) );
 
2463
 
 
2464
      PGresult *result = connectionRW->PQexec( sql );
 
2465
      if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2466
        throw PGException( result );
 
2467
      PQclear( result );
 
2468
    }
 
2469
 
 
2470
    connectionRW->PQexecNR( "COMMIT" );
 
2471
  }
 
2472
  catch ( PGException &e )
 
2473
  {
 
2474
    e.showErrorMessage( tr( "Error while changing attributes" ) );
 
2475
    connectionRW->PQexecNR( "ROLLBACK" );
 
2476
    returnvalue = false;
 
2477
  }
 
2478
 
 
2479
  rewind();
 
2480
 
 
2481
  return returnvalue;
 
2482
}
 
2483
 
 
2484
void QgsPostgresProvider::appendGeomString( QgsGeometry *geom, QString &geomString ) const
 
2485
{
 
2486
  unsigned char *buf = geom->asWkb();
 
2487
  for ( uint i = 0; i < geom->wkbSize(); ++i )
 
2488
  {
 
2489
    if ( connectionRW->useWkbHex() )
 
2490
      geomString += QString( "%1" ).arg(( int ) buf[i], 2, 16, QChar( '0' ) );
 
2491
    else
 
2492
      geomString += QString( "\\%1" ).arg(( int ) buf[i], 3, 8, QChar( '0' ) );
 
2493
  }
 
2494
}
 
2495
 
 
2496
bool QgsPostgresProvider::changeGeometryValues( QgsGeometryMap & geometry_map )
 
2497
{
 
2498
  QgsDebugMsg( "entering." );
 
2499
 
 
2500
  if ( !connectRW() )
 
2501
    return false;
 
2502
 
 
2503
  bool returnvalue = true;
 
2504
 
 
2505
  try
 
2506
  {
 
2507
    // Start the PostGIS transaction
 
2508
    connectionRW->PQexecNR( "BEGIN" );
 
2509
 
 
2510
    QString update = QString( "UPDATE %1 SET %2=GeomFromWKB($1%3,%4) WHERE %5=$2" )
 
2511
                     .arg( mSchemaTableName )
 
2512
                     .arg( quotedIdentifier( geometryColumn ) )
 
2513
                     .arg( connectionRW->useWkbHex() ? "" : "::bytea" )
 
2514
                     .arg( srid )
 
2515
                     .arg( quotedIdentifier( primaryKey ) );
 
2516
 
 
2517
    PGresult *stmt = connectionRW->PQprepare( "updatefeatures", update, 2, NULL );
 
2518
    if ( stmt == 0 || PQresultStatus( stmt ) == PGRES_FATAL_ERROR )
 
2519
      throw PGException( stmt );
 
2520
    PQclear( stmt );
 
2521
 
 
2522
    for ( QgsGeometryMap::iterator iter  = geometry_map.begin();
 
2523
          iter != geometry_map.end();
 
2524
          ++iter )
 
2525
    {
 
2526
 
 
2527
      QgsDebugMsg( "iterating over the map of changed geometries..." );
 
2528
 
 
2529
      if ( iter->asWkb() )
 
2530
      {
 
2531
        QgsDebugMsg( "iterating over feature id " + QString::number( iter.key() ) );
 
2532
 
 
2533
        QString geomParam;
 
2534
        appendGeomString( &*iter, geomParam );
 
2535
 
 
2536
        QStringList params;
 
2537
        params << geomParam;
 
2538
        if ( primaryKeyType != "tid" )
 
2539
        {
 
2540
          params << QString( "%1" ).arg( iter.key() );
2156
2541
        }
2157
2542
        else
2158
2543
        {
2159
 
          // Postgis 1.0 wants bytea
2160
 
          QString oct = QString::number((int) geom[i], 8);
2161
 
 
2162
 
          if(oct.length()==3)
2163
 
          {
2164
 
              oct="\\\\"+oct;
2165
 
          }
2166
 
          else if(oct.length()==1)
2167
 
          {
2168
 
              oct="\\\\00"+oct;
2169
 
          }
2170
 
          else if(oct.length()==2)
2171
 
          {
2172
 
              oct="\\\\0"+oct; 
2173
 
          }
2174
 
 
2175
 
          sql += oct;
 
2544
          params << QString( "(%1,%2)" ).arg( iter.key() >> 16 ).arg( iter.key() & 0xffff );
2176
2545
        }
2177
 
      }
2178
 
 
2179
 
      if (useWkbHex)
2180
 
      {
2181
 
        sql += "',"+srid+")";
2182
 
      }
2183
 
      else
2184
 
      {
2185
 
        sql += "::bytea',"+srid+")";
2186
 
      }
2187
 
 
2188
 
      sql += " WHERE \"" +primaryKey+"\"="+QString::number(iter->first);
2189
 
 
2190
 
      QgsDebugMsg("QgsPostgresProvider::changeGeometryValues: Updating with '" + sql + "'.");
2191
 
    
2192
 
      // send sql statement and do error handling
2193
 
      // TODO: Make all error handling like this one
2194
 
      PGresult* result=PQexec(connection, (const char *)(sql.utf8()));
2195
 
      if (result==0)
2196
 
      {
2197
 
        QMessageBox::critical(0, tr("PostGIS error"), 
2198
 
                                 tr("An error occured contacting the PostgreSQL database"),
2199
 
                                 QMessageBox::Ok,
2200
 
                                 Qt::NoButton);
2201
 
        return false;
2202
 
      }
2203
 
      ExecStatusType message=PQresultStatus(result);
2204
 
      if(message==PGRES_FATAL_ERROR)
2205
 
      {
2206
 
        QMessageBox::information(0, tr("PostGIS error"), 
2207
 
                                 tr("The PostgreSQL database returned: ")
2208
 
                                   + QString(PQresultErrorMessage(result))
2209
 
                                   + "\n"
2210
 
                                   + tr("When trying: ")
2211
 
                                   + sql,
2212
 
                                 QMessageBox::Ok,
2213
 
                                 Qt::NoButton);
2214
 
        return false;
2215
 
      }
2216
 
                       
2217
 
    } // if (*iter)
2218
 
 
2219
 
/*  
2220
 
  
2221
 
    if(f)
 
2546
 
 
2547
        PGresult *result = connectionRW->PQexecPrepared( "updatefeatures", params );
 
2548
        if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
 
2549
          throw PGException( result );
 
2550
        PQclear( result );
 
2551
      } // if (*iter)
 
2552
 
 
2553
    } // for each feature
 
2554
 
 
2555
    connectionRW->PQexecNR( "DEALLOCATE updatefeatures" );
 
2556
    connectionRW->PQexecNR( "COMMIT" );
 
2557
  }
 
2558
  catch ( PGException &e )
2222
2559
  {
2223
 
    QString insert("INSERT INTO \"");
2224
 
    insert+=tableName;
2225
 
    insert+="\" (";
2226
 
 
2227
 
    //add the name of the geometry column to the insert statement
2228
 
    insert+=geometryColumn;//first the geometry
2229
 
 
2230
 
    //add the names of the other fields to the insert
2231
 
    std::vector<QgsFeatureAttribute> attributevec=f->attributeMap();
2232
 
    for(std::vector<QgsFeatureAttribute>::iterator it=attributevec.begin();it!=attributevec.end();++it)
2233
 
    {
2234
 
      QString fieldname=it->fieldName();
2235
 
      if(fieldname!=geometryColumn)
2236
 
      {
2237
 
        insert+=",";
2238
 
        insert+=fieldname;
2239
 
      }
2240
 
    }
2241
 
 
2242
 
    insert+=") VALUES (GeomFromWKB('";
2243
 
 
2244
 
    //add the wkb geometry to the insert statement
2245
 
    unsigned char* geom=f->getGeometry();
2246
 
    for(int i=0;i<f->getGeometrySize();++i)
2247
 
    {
2248
 
      QString hex=QString::number((int)geom[i],16).upper();
2249
 
      if(hex.length()==1)
2250
 
      {
2251
 
        hex="0"+hex;
2252
 
      }
2253
 
      insert+=hex;
2254
 
    }
2255
 
    insert+="',"+srid+")";
2256
 
 
2257
 
    //add the field values to the insert statement
2258
 
    for(std::vector<QgsFeatureAttribute>::iterator it=attributevec.begin();it!=attributevec.end();++it)
2259
 
    {
2260
 
      if(it->fieldName()!=geometryColumn)
2261
 
      {
2262
 
        QString fieldvalue=it->fieldValue();
2263
 
        bool charactertype=false;
2264
 
        insert+=",";
2265
 
 
2266
 
        //add quotes if the field is a characted type
2267
 
        if(fieldvalue!="NULL")
2268
 
        {
2269
 
          for(std::vector<QgsField>::iterator iter=attributeFields.begin();iter!=attributeFields.end();++iter)
2270
 
          {
2271
 
            if(iter->name()==it->fieldName())
2272
 
            {
2273
 
              if(iter->type().contains("char",false)>0||iter->type()=="text")
2274
 
              {
2275
 
                charactertype=true;
2276
 
              }
2277
 
            }
2278
 
          }
2279
 
        }
2280
 
 
2281
 
        if(charactertype)
2282
 
        {
2283
 
          insert+="'";
2284
 
        }
2285
 
        insert+=fieldvalue;
2286
 
        if(charactertype)
2287
 
        {
2288
 
          insert+="'";
2289
 
        }
2290
 
      }
2291
 
    }
2292
 
 
2293
 
    insert+=")";
2294
 
    QgsDebugMsg("insert statement is: "+insert);
2295
 
 
2296
 
    //send INSERT statement and do error handling
2297
 
    PGresult* result=PQexec(connection, (const char *)(insert.utf8()));
2298
 
    if(result==0)
2299
 
    {
2300
 
      QMessageBox::information(0,"INSERT error","An error occured during feature insertion",QMessageBox::Ok);
2301
 
      return false;
2302
 
    }
2303
 
    ExecStatusType message=PQresultStatus(result);
2304
 
    if(message==PGRES_FATAL_ERROR)
2305
 
    {
2306
 
      QMessageBox::information(0,"INSERT error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
2307
 
      return false;
2308
 
    }
2309
 
    return true;
2310
 
  }
2311
 
  return false;
2312
 
 
2313
 
  
2314
 
  
2315
 
  
2316
 
  
2317
 
  
2318
 
  
2319
 
  
2320
 
  
2321
 
    for(std::map<QString,QString>::const_iterator siter=(*iter).second.begin();siter!=(*iter).second.end();++siter)
2322
 
    {
2323
 
      QString value=(*siter).second;
2324
 
 
2325
 
      //find out, if value contains letters and quote if yes
2326
 
      bool text=false;
2327
 
      for(int i=0;i<value.length();++i)
2328
 
      {
2329
 
        if(value[i].isLetter())
2330
 
        {
2331
 
          text=true;
2332
 
        }
2333
 
      }
2334
 
      if(text)
2335
 
      {
2336
 
        value.prepend("'");
2337
 
        value.append("'");
2338
 
      }
2339
 
 
2340
 
      QString sql="UPDATE \""+tableName+"\" SET "+(*siter).first+"="+value+" WHERE " +primaryKey+"="+QString::number((*iter).first);
2341
 
      QgsDebugMsg(sql);
2342
 
 
2343
 
      //send sql statement and do error handling
2344
 
      PGresult* result=PQexec(connection, (const char *)(sql.utf8()));
2345
 
      if(result==0)
2346
 
      {
2347
 
        returnvalue=false;
2348
 
        ExecStatusType message=PQresultStatus(result);
2349
 
        if(message==PGRES_FATAL_ERROR)
2350
 
        {
2351
 
          QMessageBox::information(0,"UPDATE error",QString(PQresultErrorMessage(result)),QMessageBox::Ok);
2352
 
        }
2353
 
      }
2354
 
    }
2355
 
  }
2356
 
*/
2357
 
  } // for each feature
2358
 
  
2359
 
  PQexec(connection,"COMMIT");
2360
 
  
2361
 
  // TODO: Reset Geometry dirty if commit was OK
2362
 
  
2363
 
  reset();
2364
 
  
2365
 
  QgsDebugMsg("QgsPostgresProvider::changeGeometryValues: exiting.");
 
2560
    e.showErrorMessage( tr( "Error while changing geometry values" ) );
 
2561
    connectionRW->PQexecNR( "ROLLBACK" );
 
2562
    connectionRW->PQexecNR( "DEALLOCATE updatefeatures" );
 
2563
    returnvalue = false;
 
2564
  }
 
2565
 
 
2566
  rewind();
 
2567
 
 
2568
  QgsDebugMsg( "exiting." );
 
2569
 
2366
2570
  return returnvalue;
2367
2571
}
2368
2572
 
2369
 
 
2370
 
bool QgsPostgresProvider::supportsSaveAsShapefile() const
 
2573
QgsAttributeList QgsPostgresProvider::attributeIndexes()
2371
2574
{
2372
 
  return false;
 
2575
  QgsAttributeList attributes;
 
2576
  for ( QgsFieldMap::const_iterator it = attributeFields.constBegin(); it != attributeFields.constEnd(); ++it )
 
2577
  {
 
2578
    attributes.push_back( it.key() );
 
2579
  }
 
2580
  return attributes;
2373
2581
}
2374
2582
 
 
2583
 
2375
2584
int QgsPostgresProvider::capabilities() const
2376
2585
{
2377
 
  return (
2378
 
           QgsVectorDataProvider::AddFeatures |
2379
 
           QgsVectorDataProvider::DeleteFeatures |
2380
 
           QgsVectorDataProvider::ChangeAttributeValues |
2381
 
           QgsVectorDataProvider::AddAttributes |
2382
 
           QgsVectorDataProvider::DeleteAttributes |
2383
 
           QgsVectorDataProvider::ChangeGeometries |
2384
 
           QgsVectorDataProvider::SelectGeometryAtId
2385
 
         );
 
2586
  return enabledCapabilities;
2386
2587
}
2387
2588
 
2388
 
void QgsPostgresProvider::setSubsetString(QString theSQL)
 
2589
bool QgsPostgresProvider::setSubsetString( QString theSQL )
2389
2590
{
2390
 
  sqlWhereClause=theSQL;
 
2591
  QString prevWhere = sqlWhereClause;
 
2592
 
 
2593
  sqlWhereClause = theSQL;
 
2594
 
 
2595
  if ( !uniqueData( mSchemaName, mTableName, primaryKey ) )
 
2596
  {
 
2597
    sqlWhereClause = prevWhere;
 
2598
    return false;
 
2599
  }
 
2600
 
2391
2601
  // Update datasource uri too
2392
 
  mUri.sql=theSQL;
 
2602
  mUri.setSql( theSQL );
2393
2603
  // Update yet another copy of the uri. Why are there 3 copies of the
2394
2604
  // uri? Perhaps this needs some rationalisation.....
2395
 
  setDataSourceUri(mUri.text());
 
2605
  setDataSourceUri( mUri.uri() );
2396
2606
 
2397
2607
  // need to recalculate the number of features...
2398
2608
  getFeatureCount();
2399
2609
  calculateExtents();
2400
2610
 
 
2611
  return true;
2401
2612
}
2402
2613
 
2403
2614
long QgsPostgresProvider::getFeatureCount()
2408
2619
  // a thread the task of getting the full count.
2409
2620
 
2410
2621
#ifdef POSTGRESQL_THREADS
2411
 
  QString sql = "select reltuples from pg_catalog.pg_class where relname = '" + 
2412
 
    tableName + "'";
2413
 
 
2414
 
  QgsDebugMsg("QgsPostgresProvider: Running SQL: " + sql);        
2415
 
#else                 
2416
 
  QString sql = "select count(*) from " + mSchemaTableName + "";
2417
 
 
2418
 
  if(sqlWhereClause.length() > 0)
 
2622
  QString sql = QString( "select reltuples from pg_catalog.pg_class where relname=%1" ).arg( quotedValue( tableName ) );
 
2623
  QgsDebugMsg( "Running SQL: " + sql );
 
2624
#else
 
2625
  QString sql = QString( "select count(*) from %1" ).arg( mSchemaTableName );
 
2626
 
 
2627
  if ( sqlWhereClause.length() > 0 )
2419
2628
  {
2420
2629
    sql += " where " + sqlWhereClause;
2421
2630
  }
2422
2631
#endif
2423
2632
 
2424
 
  PGresult *result = PQexec(connection, (const char *) (sql.utf8()));
2425
 
 
2426
 
  QgsDebugMsg("QgsPostgresProvider: Approximate Number of features as text: " + QString(PQgetvalue(result, 0, 0)));
2427
 
 
2428
 
  numberFeatures = QString(PQgetvalue(result, 0, 0)).toLong();
2429
 
  PQclear(result);
2430
 
  QgsDebugMsg("QgsPostgresProvider: Approximate Number of features: " + QString::number(numberFeatures));
2431
 
 
2432
 
  return numberFeatures;
 
2633
  Result result = connectionRO->PQexec( sql );
 
2634
 
 
2635
  QgsDebugMsg( "Approximate Number of features as text: " +
 
2636
               QString::fromUtf8( PQgetvalue( result, 0, 0 ) ) );
 
2637
 
 
2638
  featuresCounted = QString::fromUtf8( PQgetvalue( result, 0, 0 ) ).toLong();
 
2639
 
 
2640
  QgsDebugMsg( "Approximate Number of features: " + QString::number( featuresCounted ) );
 
2641
 
 
2642
  return featuresCounted;
2433
2643
}
2434
2644
 
2435
2645
// TODO: use the estimateExtents procedure of PostGIS and PostgreSQL 8
2437
2647
void QgsPostgresProvider::calculateExtents()
2438
2648
{
2439
2649
#ifdef POSTGRESQL_THREADS
2440
 
  // get the approximate extent by retreiving the bounding box
 
2650
  // get the approximate extent by retrieving the bounding box
2441
2651
  // of the first few items with a geometry
2442
2652
 
2443
 
  QString sql = "select box3d(" + geometryColumn + ") from " 
2444
 
    + mSchemaTableName + " where ";
 
2653
  QString sql = QString( "select box3d(%1) from %2 where " )
 
2654
                .arg( quotedIdentifier( geometryColumn ) )
 
2655
                .arg( mSchemaTableName );
2445
2656
 
2446
 
  if(sqlWhereClause.length() > 0)
 
2657
  if ( sqlWhereClause.length() > 0 )
2447
2658
  {
2448
 
    sql += "(" + sqlWhereClause + ") and ";
 
2659
    sql += QString( "(%1) and " ).arg( sqlWhereClause );
2449
2660
  }
2450
2661
 
2451
 
  sql += "not IsEmpty(" + geometryColumn + ") limit 5";
2452
 
 
 
2662
  sql += QString( "not IsEmpty(%1) limit 5" ).arg( quotedIdentifier( geometryColumn ) );
2453
2663
 
2454
2664
#if WASTE_TIME
2455
 
  sql = "select xmax(extent(\"" + geometryColumn + "\")) as xmax,"
2456
 
    "xmin(extent(\"" + geometryColumn + "\")) as xmin,"
2457
 
    "ymax(extent(\"" + geometryColumn + "\")) as ymax," 
2458
 
    "ymin(extent(\"" + geometryColumn + "\")) as ymin" 
2459
 
    " from " + mSchemaTableName;
 
2665
  sql = QString( "select "
 
2666
                 "xmax(extent(%1)) as xmax,"
 
2667
                 "xmin(extent(%1)) as xmin,"
 
2668
                 "ymax(extent(%1)) as ymax,"
 
2669
                 "ymin(extent(%1)) as ymin"
 
2670
                 " from %2" ).arg( quotedIdentifier( geometryColumn ) ).arg( mSchemaTableName );
2460
2671
#endif
2461
 
 
2462
 
  QgsDebugMsg((const char*)(QString("QgsPostgresProvider::calculateExtents - Getting approximate extent using: '") + sql + "'").toLocal8Bit().data());
2463
 
 
2464
 
  PGresult *result = PQexec(connection, (const char *) (sql.utf8()));
 
2672
 
 
2673
  QgsDebugMsg( "Getting approximate extent using: '" + sql + "'" );
 
2674
 
 
2675
  Result result = connectionRO->PQexec( sql );
2465
2676
 
2466
2677
  // TODO: Guard against the result having no rows
2467
 
 
2468
 
  for (int i = 0; i < PQntuples(result); i++)
 
2678
  for ( int i = 0; i < PQntuples( result ); i++ )
2469
2679
  {
2470
 
    std::string box3d = PQgetvalue(result, i, 0);
 
2680
    QString box3d = PQgetvalue( result, i, 0 );
2471
2681
 
2472
 
    if (0 == i)
 
2682
    if ( 0 == i )
2473
2683
    {
2474
2684
      // create the initial extent
2475
 
      layerExtent = QgsPostGisBox3d(box3d);
 
2685
      layerExtent = QgsPostGisBox3d( box3d );
2476
2686
    }
2477
2687
    else
2478
2688
    {
2479
2689
      // extend the initial extent
2480
 
      QgsPostGisBox3d b = QgsPostGisBox3d(box3d);
2481
 
 
 
2690
      QgsPostGisBox3d b = QgsPostGisBox3d( box3d );
2482
2691
      layerExtent.combineExtentWith( &b );
2483
2692
    }
2484
2693
 
2485
 
    QgsDebugMsg("QgsPostgresProvider: After row " + QString::number(i) + ", extent is: " + 
2486
 
                QString::number(layerExtent.xMin()) + ", " + QString::number(layerExtent.yMin()) +
2487
 
                " " + QString::number(layerExtent.xMax()) + ", " + QString::number(layerExtent.yMax()));
2488
 
 
 
2694
    QgsDebugMsg( QString( "After row %1, extent is %2" ).arg( i ).arg( layerExtent.toString() ) );
2489
2695
  }
2490
2696
 
2491
 
#ifdef QGISDEBUG
2492
 
  QString xMsg;
2493
 
  QTextOStream(&xMsg).precision(18);
2494
 
  QTextOStream(&xMsg).width(18);
2495
 
  QTextOStream(&xMsg) << "QgsPostgresProvider: Set extents to: " << layerExtent.
2496
 
    xMin() << ", " << layerExtent.yMin() << " " << layerExtent.xMax() << ", " << layerExtent.yMax();
2497
 
  QgsDebugMsg(xMsg);
2498
 
#endif
2499
 
 
2500
 
  QgsDebugMsg("QgsPostgresProvider: Set limit 5 extents to: " + QString::number(layerExtent.xMin()) + \
2501
 
              ", " + QString::number(layerExtent.yMin()) + " " + QString::number(layerExtent.xMax()) + \
2502
 
              ", " + QString::number(layerExtent.yMax()));
2503
 
 
2504
 
  // clear query result
2505
 
  PQclear(result);
2506
 
 
2507
2697
#else // non-postgresql threads version
 
2698
  QString sql;
 
2699
  Result result;
 
2700
  QString ext;
2508
2701
 
2509
2702
  // get the extents
2510
 
 
2511
 
  QString sql = "select extent(\"" + geometryColumn + "\") from " + 
2512
 
    mSchemaTableName;
2513
 
  if(sqlWhereClause.length() > 0)
2514
 
  {
2515
 
    sql += " where " + sqlWhereClause;
 
2703
  if ( sqlWhereClause.isEmpty() )
 
2704
  {
 
2705
    result = connectionRO->PQexec( QString( "select estimated_extent(%1,%2,%3)" )
 
2706
                                   .arg( quotedValue( mSchemaName ) )
 
2707
                                   .arg( quotedValue( mTableName ) )
 
2708
                                   .arg( quotedValue( geometryColumn ) ) );
 
2709
    if ( PQntuples( result ) == 1 )
 
2710
      ext = PQgetvalue( result, 0, 0 );
 
2711
  }
 
2712
 
 
2713
  if ( ext.isEmpty() )
 
2714
  {
 
2715
    sql = QString( "select extent(%1) from %2" )
 
2716
          .arg( quotedIdentifier( geometryColumn ) )
 
2717
          .arg( mSchemaTableName );
 
2718
 
 
2719
    if ( !sqlWhereClause.isEmpty() )
 
2720
      sql += QString( "where %1" ).arg( sqlWhereClause );
 
2721
 
 
2722
    result = connectionRO->PQexec( sql );
 
2723
    if ( PQntuples( result ) == 1 )
 
2724
      ext = PQgetvalue( result, 0, 0 );
2516
2725
  }
2517
2726
 
2518
2727
#if WASTE_TIME
2519
 
  sql = "select xmax(extent(\"" + geometryColumn + "\")) as xmax,"
2520
 
    "xmin(extent(\"" + geometryColumn + "\")) as xmin,"
2521
 
    "ymax(extent(\"" + geometryColumn + "\")) as ymax," 
2522
 
    "ymin(extent(\"" + geometryColumn + "\")) as ymin" 
2523
 
    " from " + mSchemaTableName;
2524
 
#endif
2525
 
 
2526
 
  QgsDebugMsg((const char*)(QString("+++++++++QgsPostgresProvider::calculateExtents -  Getting extents using schema.table: ") + sql).toLocal8Bit().data());
2527
 
 
2528
 
  PGresult *result = PQexec(connection, (const char *) (sql.utf8()));
2529
 
  Q_ASSERT(PQntuples(result) == 1);
2530
 
  std::string box3d = PQgetvalue(result, 0, 0);
2531
 
 
2532
 
  if (box3d != "")
2533
 
  {
2534
 
    std::string s;
2535
 
 
2536
 
    box3d = box3d.substr(box3d.find_first_of("(")+1);
2537
 
    box3d = box3d.substr(box3d.find_first_not_of(" "));
2538
 
    s = box3d.substr(0, box3d.find_first_of(" "));
2539
 
    double minx = strtod(s.c_str(), NULL);
2540
 
 
2541
 
    box3d = box3d.substr(box3d.find_first_of(" ")+1);
2542
 
    s = box3d.substr(0, box3d.find_first_of(" "));
2543
 
    double miny = strtod(s.c_str(), NULL);
2544
 
 
2545
 
    box3d = box3d.substr(box3d.find_first_of(",")+1);
2546
 
    box3d = box3d.substr(box3d.find_first_not_of(" "));
2547
 
    s = box3d.substr(0, box3d.find_first_of(" "));
2548
 
    double maxx = strtod(s.c_str(), NULL);
2549
 
 
2550
 
    box3d = box3d.substr(box3d.find_first_of(" ")+1);
2551
 
    s = box3d.substr(0, box3d.find_first_of(" "));
2552
 
    double maxy = strtod(s.c_str(), NULL);
2553
 
 
2554
 
    layerExtent.setXmax(maxx);
2555
 
    layerExtent.setXmin(minx);
2556
 
    layerExtent.setYmax(maxy);
2557
 
    layerExtent.setYmin(miny);
 
2728
  sql = QString( "select "
 
2729
                 "xmax(extent(%1)) as xmax,"
 
2730
                 "xmin(extent(%1)) as xmin,"
 
2731
                 "ymax(extent(%1)) as ymax,"
 
2732
                 "ymin(extent(%1)) as ymin"
 
2733
                 " from %2" ).arg( quotedIdentifier( geometryColumn ) ).arg( mSchemaTableName );
 
2734
#endif
 
2735
 
 
2736
  QgsDebugMsg( "Getting extents using schema.table: " + sql );
 
2737
 
 
2738
  QRegExp rx( "\\((.+) (.+),(.+) (.+)\\)" );
 
2739
  if ( ext.contains( rx ) )
 
2740
  {
 
2741
    QStringList ex = rx.capturedTexts();
 
2742
 
 
2743
    layerExtent.setXMinimum( ex[1].toDouble() );
 
2744
    layerExtent.setYMinimum( ex[2].toDouble() );
 
2745
    layerExtent.setXMaximum( ex[3].toDouble() );
 
2746
    layerExtent.setYMaximum( ex[4].toDouble() );
 
2747
  }
 
2748
  else
 
2749
  {
 
2750
    QgsDebugMsg( "extents query failed" );
 
2751
  }
 
2752
#endif
 
2753
 
 
2754
#if 0
2558
2755
#ifdef QGISDEBUG
2559
 
    QString xMsg;
2560
 
    QTextOStream(&xMsg).precision(18);
2561
 
    QTextOStream(&xMsg).width(18);
2562
 
    QTextOStream(&xMsg) << "Set extents to: " << layerExtent.
2563
 
      xMin() << ", " << layerExtent.yMin() << " " << layerExtent.xMax() << ", " << layerExtent.yMax();
2564
 
    QgsDebugMsg(xMsg);
2565
 
#endif
2566
 
    // clear query result
2567
 
    PQclear(result);
2568
 
  }
2569
 
 
2570
 
 
2571
 
#endif
2572
 
 
 
2756
  QString xMsg;
 
2757
  QTextOStream( &xMsg ).precision( 18 );
 
2758
  QTextOStream( &xMsg ).width( 18 );
 
2759
  QTextOStream( &xMsg ) << "QgsPostgresProvider: Set extents to: "
 
2760
  << layerExtent.xMinimum() << ", "
 
2761
  << layerExtent.yMinimum() << " "
 
2762
  << layerExtent.xMaximum() << ", "
 
2763
  << layerExtent.yMaximum();
 
2764
  QgsDebugMsg( xMsg );
 
2765
#endif
 
2766
#endif
 
2767
  QgsDebugMsg( "Set extents to: " + layerExtent.toString() );
2573
2768
}
2574
2769
 
2575
2770
/**
2576
2771
 * Event sink for events from threads
2577
2772
 */
2578
 
void QgsPostgresProvider::customEvent( QCustomEvent * e )
 
2773
void QgsPostgresProvider::customEvent( QEvent * e )
2579
2774
{
2580
 
  QgsDebugMsg("QgsPostgresProvider: received a custom event " + e->type());
 
2775
  QgsDebugMsg( "received a custom event " + QString::number( e->type() ) );
2581
2776
 
2582
 
  switch ( e->type() )
 
2777
  switch (( int ) e->type() )
2583
2778
  {
2584
 
    case (QEvent::Type) QGis::ProviderExtentCalcEvent:
 
2779
    case QGis::ProviderExtentCalcEvent:
2585
2780
 
2586
 
      QgsDebugMsg("QgsPostgresProvider: extent has been calculated");
 
2781
      QgsDebugMsg( "extent has been calculated" );
2587
2782
 
2588
2783
      // Collect the new extent from the event and set this layer's
2589
2784
      // extent with it.
2590
2785
 
2591
 
      setExtent( (QgsRect*) e->data() );
2592
 
 
2593
 
 
2594
 
      QgsDebugMsg("QgsPostgresProvider: new extent has been saved");
2595
 
 
2596
 
      QgsDebugMsg("QgsPostgresProvider: Set extent to: " + QString::number(layerExtent.xMin()) + ", " + 
2597
 
                  QString::number(layerExtent.yMin()) + " " + QString::number(layerExtent.xMax()) + ", " + 
2598
 
                  QString::number(layerExtent.yMax()));
2599
 
 
2600
 
      QgsDebugMsg("QgsPostgresProvider: emitting fullExtentCalculated()");
2601
 
 
2602
 
#ifndef WIN32 //temporary hack for native win build
 
2786
      {
 
2787
        QgsRectangle* r = (( QgsProviderExtentCalcEvent* ) e )->layerExtent();
 
2788
        setExtent( *r );
 
2789
      }
 
2790
 
 
2791
      QgsDebugMsg( "new extent has been saved" );
 
2792
 
 
2793
      QgsDebugMsg( "Set extent to: " + layerExtent.toString() );
 
2794
 
 
2795
      QgsDebugMsg( "emitting fullExtentCalculated()" );
 
2796
 
2603
2797
      emit fullExtentCalculated();
2604
 
#endif
2605
2798
 
2606
2799
      // TODO: Only uncomment this when the overview map canvas has been subclassed
2607
2800
      // from the QgsMapCanvas
2608
2801
 
2609
 
      //        QgsDebugMsg("QgsPostgresProvider: emitting repaintRequested()");
 
2802
      //        QgsDebugMsg("emitting repaintRequested()");
2610
2803
      //        emit repaintRequested();
2611
2804
 
2612
2805
      break;
2613
2806
 
2614
 
    case (QEvent::Type) QGis::ProviderCountCalcEvent:
2615
 
 
2616
 
      QgsDebugMsg("QgsPostgresProvider: count has been calculated");
2617
 
 
2618
 
      QgsProviderCountCalcEvent* e1 = (QgsProviderCountCalcEvent*) e;
2619
 
 
2620
 
      numberFeatures = e1->numberFeatures();
2621
 
 
2622
 
      QgsDebugMsg("QgsPostgresProvider: count is " + QString::number(numberFeatures));
2623
 
 
 
2807
    case QGis::ProviderCountCalcEvent:
 
2808
 
 
2809
      QgsDebugMsg( "count has been calculated" );
 
2810
 
 
2811
      featuresCounted = (( QgsProviderCountCalcEvent* ) e )->featuresCounted();
 
2812
 
 
2813
      QgsDebugMsg( "count is " + QString::number( featuresCounted ) );
 
2814
 
 
2815
      break;
 
2816
 
 
2817
    default:
 
2818
      // do nothing
2624
2819
      break;
2625
2820
  }
2626
2821
 
2627
 
  QgsDebugMsg("QgsPostgresProvider: Finished processing custom event " + QString::number(e->type()));
 
2822
  QgsDebugMsg( "Finished processing custom event " + QString::number( e->type() ) );
2628
2823
 
2629
2824
}
2630
2825
 
2636
2831
  // version 7.4, binary cursors return data in XDR whereas previous versions
2637
2832
  // return data in the endian of the server
2638
2833
 
2639
 
  QString firstOid = "select oid from pg_class where relname = '" + 
2640
 
    mTableName + "' and relnamespace = (select oid from pg_namespace where nspname = '"
2641
 
    + mSchemaName + "')";
2642
 
  PGresult * oidResult = PQexec(connection, (const char*)(firstOid.utf8()));
 
2834
  QString firstOid = QString( "select regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
 
2835
  Result oidResult = connectionRO->PQexec( firstOid );
2643
2836
  // get the int value from a "normal" select
2644
 
  QString oidValue = PQgetvalue(oidResult,0,0);
2645
 
  PQclear(oidResult);
2646
 
  QgsDebugMsg("Creating binary cursor");
 
2837
  QString oidValue = QString::fromUtf8( PQgetvalue( oidResult, 0, 0 ) );
 
2838
 
 
2839
  QgsDebugMsg( "Creating binary cursor" );
2647
2840
 
2648
2841
  // get the same value using a binary cursor
2649
 
 
2650
 
  PQexec(connection,"begin work");
2651
 
  QString oidDeclare = QString("declare oidcursor binary cursor for select oid from pg_class where relname = '%1' and relnamespace = (select oid from pg_namespace where nspname = '%2')").arg(mTableName).arg(mSchemaName);
2652
 
  // set up the cursor
2653
 
  PQexec(connection, (const char *)oidDeclare);
2654
 
  QString fetch = "fetch forward 1 from oidcursor";
2655
 
 
2656
 
  QgsDebugMsg("Fetching a record and attempting to get check endian-ness");
2657
 
 
2658
 
  PGresult *fResult = PQexec(connection, (const char *)fetch);
2659
 
  PQexec(connection, "end work");
 
2842
  connectionRO->openCursor( "oidcursor", QString( "select regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) ) );
 
2843
 
 
2844
  QgsDebugMsg( "Fetching a record and attempting to get check endian-ness" );
 
2845
 
 
2846
  Result fResult = connectionRO->PQexec( "fetch forward 1 from oidcursor" );
2660
2847
  swapEndian = true;
2661
 
  if(PQntuples(fResult) > 0){
 
2848
  if ( PQntuples( fResult ) > 0 )
 
2849
  {
2662
2850
    // get the oid value from the binary cursor
2663
 
    int oid = *(int *)PQgetvalue(fResult,0,0);
2664
 
 
2665
 
    //QgsDebugMsg("Got oid of " << oid << " from the binary cursor");
2666
 
    //QgsDebugMsg("First oid is " QString::number(oidValue));
 
2851
    int oid = *( int * )PQgetvalue( fResult, 0, 0 );
 
2852
 
 
2853
    //--std::cout << "Got oid of " << oid << " from the binary cursor" << std::endl;
 
2854
 
 
2855
    //--std::cout << "First oid is " << oidValue << std::endl;
 
2856
 
2667
2857
    // compare the two oid values to determine if we need to do an endian swap
2668
 
    if(oid == oidValue.toInt())
 
2858
    if ( oid == oidValue.toInt() )
2669
2859
      swapEndian = false;
2670
 
 
2671
 
    PQclear(fResult);
2672
2860
  }
 
2861
  connectionRO->closeCursor( "oidcursor" );
2673
2862
  return swapEndian;
2674
2863
}
2675
2864
 
2676
2865
bool QgsPostgresProvider::getGeometryDetails()
2677
2866
{
2678
 
  QString fType("");
 
2867
  QString fType( "" );
2679
2868
  srid = "";
2680
2869
  valid = false;
2681
2870
  QStringList log;
2682
2871
 
2683
 
  QString sql = "select f_geometry_column,type,srid from geometry_columns"
2684
 
    " where f_table_name='" + mTableName + "' and f_geometry_column = '" + 
2685
 
    geometryColumn + "' and f_table_schema = '" + mSchemaName + "'";
2686
 
 
2687
 
  QgsDebugMsg("Getting geometry column: " + sql);
2688
 
 
2689
 
  PGresult *result = executeDbCommand(connection, sql);
2690
 
  QgsDebugMsg("geometry column query returned " + PQntuples(result));
2691
 
  QgsDebugMsg("column number of srid is " + QString::number(PQfnumber(result, "srid")));
2692
 
 
2693
 
  if (PQntuples(result) > 0)
 
2872
  QString sql = QString( "select type,srid from geometry_columns"
 
2873
                         " where f_table_name=%1 and f_geometry_column=%2 and f_table_schema=%3" )
 
2874
                .arg( quotedValue( mTableName ) )
 
2875
                .arg( quotedValue( geometryColumn ) )
 
2876
                .arg( quotedValue( mSchemaName ) );
 
2877
 
 
2878
  QgsDebugMsg( "Getting geometry column: " + sql );
 
2879
 
 
2880
  Result result = connectionRO->PQexec( sql );
 
2881
 
 
2882
  QgsDebugMsg( "geometry column query returned " + QString::number( PQntuples( result ) ) );
 
2883
 
 
2884
  if ( PQntuples( result ) > 0 )
2694
2885
  {
2695
 
    srid = PQgetvalue(result, 0, PQfnumber(result, "srid"));
2696
 
    fType = PQgetvalue(result, 0, PQfnumber(result, "type"));
2697
 
    PQclear(result);
 
2886
    fType = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
 
2887
    srid = QString::fromUtf8( PQgetvalue( result, 0, 1 ) );
2698
2888
  }
2699
2889
  else
2700
2890
  {
2701
2891
    // Didn't find what we need in the geometry_columns table, so
2702
 
    // get stuff from the relevant column instead. This may (will?) 
 
2892
    // get stuff from the relevant column instead. This may (will?)
2703
2893
    // fail if there is no data in the relevant table.
2704
 
    PQclear(result); // for the query just before the if() statement
2705
 
    sql = "select "
2706
 
      "srid(\""         + geometryColumn + "\"), "
2707
 
      "geometrytype(\"" + geometryColumn + "\") from " + 
2708
 
      mSchemaTableName + " limit 1";
2709
 
 
2710
 
    result = executeDbCommand(connection, sql);
2711
 
 
2712
 
    if (PQntuples(result) > 0)
2713
 
    {
2714
 
      srid = PQgetvalue(result, 0, PQfnumber(result, "srid"));
2715
 
      fType = PQgetvalue(result, 0, PQfnumber(result, "geometrytype"));
2716
 
    }
2717
 
    PQclear(result);
 
2894
    sql = QString( "select srid(%1),geometrytype(%1) from %2" )
 
2895
          .arg( quotedIdentifier( geometryColumn ) )
 
2896
          .arg( mSchemaTableName );
 
2897
 
 
2898
    //it is possible that the where clause restricts the feature type
 
2899
    if ( !sqlWhereClause.isEmpty() )
 
2900
    {
 
2901
      sql += " WHERE " + sqlWhereClause;
 
2902
    }
 
2903
 
 
2904
    sql += " limit 1";
 
2905
 
 
2906
    result = connectionRO->PQexec( sql );
 
2907
 
 
2908
    if ( PQntuples( result ) > 0 )
 
2909
    {
 
2910
      srid = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
 
2911
      fType = QString::fromUtf8( PQgetvalue( result, 0, 1 ) );
 
2912
    }
2718
2913
  }
2719
2914
 
2720
 
  if (!srid.isEmpty() && !fType.isEmpty())
 
2915
  if ( !srid.isEmpty() && !fType.isEmpty() )
2721
2916
  {
2722
2917
    valid = true;
2723
 
    if (fType == "POINT")
2724
 
      {
2725
 
        geomType = QGis::WKBPoint;
2726
 
      }
2727
 
    else if(fType == "MULTIPOINT")
2728
 
      {
2729
 
        geomType = QGis::WKBMultiPoint;
2730
 
      }
2731
 
    else if(fType == "LINESTRING")
2732
 
      {
2733
 
        geomType = QGis::WKBLineString;
2734
 
      }
2735
 
    else if(fType == "MULTILINESTRING")
2736
 
      {
2737
 
        geomType = QGis::WKBMultiLineString;
2738
 
      }
2739
 
    else if (fType == "POLYGON")
2740
 
      {
2741
 
        geomType = QGis::WKBPolygon;
2742
 
      }
2743
 
    else if(fType == "MULTIPOLYGON")
2744
 
      {
2745
 
        geomType = QGis::WKBMultiPolygon;
2746
 
      }
 
2918
    if ( fType == "GEOMETRY" )
 
2919
    {
 
2920
      // check to see if there is a unique geometry type
 
2921
      sql = QString( "select distinct "
 
2922
                     "case"
 
2923
                     " when geometrytype(%1) IN ('POINT','MULTIPOINT') THEN 'POINT'"
 
2924
                     " when geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'"
 
2925
                     " when geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'"
 
2926
                     " end "
 
2927
                     "from %2" ).arg( quotedIdentifier( geometryColumn ) ).arg( mSchemaTableName );
 
2928
      if ( mUri.sql() != "" )
 
2929
        sql += " where " + mUri.sql();
 
2930
 
 
2931
      result = connectionRO->PQexec( sql );
 
2932
 
 
2933
      if ( PQntuples( result ) == 1 )
 
2934
      {
 
2935
        fType = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
 
2936
      }
 
2937
    }
 
2938
    if ( fType == "POINT" || fType == "POINTM" )
 
2939
    {
 
2940
      geomType = QGis::WKBPoint;
 
2941
    }
 
2942
    else if ( fType == "MULTIPOINT" || fType == "MULTIPOINTM" )
 
2943
    {
 
2944
      geomType = QGis::WKBMultiPoint;
 
2945
    }
 
2946
    else if ( fType == "LINESTRING" || fType == "LINESTRINGM" )
 
2947
    {
 
2948
      geomType = QGis::WKBLineString;
 
2949
    }
 
2950
    else if ( fType == "MULTILINESTRING" || fType == "MULTILINESTRINGM" )
 
2951
    {
 
2952
      geomType = QGis::WKBMultiLineString;
 
2953
    }
 
2954
    else if ( fType == "POLYGON" || fType == "POLYGONM" )
 
2955
    {
 
2956
      geomType = QGis::WKBPolygon;
 
2957
    }
 
2958
    else if ( fType == "MULTIPOLYGON" || fType == "MULTIPOLYGONM" )
 
2959
    {
 
2960
      geomType = QGis::WKBMultiPolygon;
 
2961
    }
2747
2962
    else
2748
2963
    {
2749
 
      showMessageBox(tr("Unknown geometry type"), 
2750
 
        tr("Column ") + geometryColumn + tr(" in ") +
2751
 
           mSchemaTableName + tr(" has a geometry type of ") +
2752
 
           fType + tr(", which Qgis does not currently support."));
 
2964
      showMessageBox( tr( "Unknown geometry type" ),
 
2965
                      tr( "Column %1 in %2 has a geometry type of %3, which Qgis does not currently support." )
 
2966
                      .arg( geometryColumn ).arg( mSchemaTableName ).arg( fType ) );
2753
2967
      valid = false;
2754
2968
    }
2755
2969
  }
2756
2970
  else // something went wrong...
2757
2971
  {
2758
 
    log.prepend(tr("Qgis was unable to determine the type and srid of "
2759
 
                   "column " + geometryColumn + tr(" in ") +
2760
 
                   mSchemaTableName + 
2761
 
                   tr(". The database communication log was:\n")));
2762
 
    showMessageBox(tr("Unable to get feature type and srid"), log);
2763
 
  }
2764
 
 
2765
 
 
2766
 
  if (valid)
2767
 
    {
2768
 
      QgsDebugMsg("SRID is " + srid);
2769
 
      QgsDebugMsg("type is " + fType);
2770
 
      QgsDebugMsg("Feature type is " + geomType);
2771
 
      QgsDebugMsg(QString("Feature type name is ") + QGis::qgisFeatureTypes[geomType]);
2772
 
    }
 
2972
    log.prepend( tr( "Qgis was unable to determine the type and srid of column %1 in %2. The database communication log was:\n%3" )
 
2973
                 .arg( geometryColumn )
 
2974
                 .arg( mSchemaTableName )
 
2975
                 .arg( QString::fromUtf8( PQresultErrorMessage( result ) ) ) );
 
2976
    showMessageBox( tr( "Unable to get feature type and srid" ), log );
 
2977
  }
 
2978
 
 
2979
  // store whether the geometry includes measure value
 
2980
  if ( fType == "POINTM" || fType == "MULTIPOINTM" ||
 
2981
       fType == "LINESTRINGM" || fType == "MULTILINESTRINGM" ||
 
2982
       fType == "POLYGONM"  || fType == "MULTIPOLYGONM" )
 
2983
  {
 
2984
    // explicitly disable adding new features and editing of geometries
 
2985
    // as this would lead to corruption of measures
 
2986
    enabledCapabilities &= ~( QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::AddFeatures );
 
2987
  }
 
2988
 
 
2989
 
 
2990
  if ( valid )
 
2991
  {
 
2992
    QgsDebugMsg( "SRID is " + srid );
 
2993
    QgsDebugMsg( "type is " + fType );
 
2994
    QgsDebugMsg( "Feature type is " + QString::number( geomType ) );
 
2995
    QgsDebugMsg( "Feature type name is " + QString( QGis::qgisFeatureTypes[geomType] ) );
 
2996
  }
2773
2997
  else
2774
 
    {
2775
 
      QgsDebugMsg("Failed to get geometry details for Postgres layer.");
2776
 
    }
 
2998
  {
 
2999
    QgsDebugMsg( "Failed to get geometry details for Postgres layer." );
 
3000
  }
 
3001
 
2777
3002
  return valid;
2778
3003
}
2779
3004
 
2780
 
PGresult* QgsPostgresProvider::executeDbCommand(PGconn* connection, 
2781
 
                                                const QString& sql)
2782
 
{
2783
 
  PGresult *result = PQexec(connection, (const char *) (sql.utf8()));
2784
 
 
2785
 
  QgsDebugMsg("Executed SQL: " + sql);
2786
 
  if (PQresultStatus(result) == PGRES_TUPLES_OK)
2787
 
    {
2788
 
      QgsDebugMsg("Command was successful.");
2789
 
    }
 
3005
QString QgsPostgresProvider::quotedIdentifier( QString ident ) const
 
3006
{
 
3007
  ident.replace( '"', "\"\"" );
 
3008
  return ident.prepend( "\"" ).append( "\"" );
 
3009
}
 
3010
 
 
3011
QString QgsPostgresProvider::quotedValue( QString value ) const
 
3012
{
 
3013
  if ( value.isNull() )
 
3014
    return "NULL";
 
3015
 
 
3016
  // FIXME: use PQescapeStringConn
 
3017
  value.replace( "'", "''" );
 
3018
  return value.prepend( "'" ).append( "'" );
 
3019
}
 
3020
 
 
3021
PGresult *QgsPostgresProvider::Conn::PQexec( QString query )
 
3022
{
 
3023
  QgsDebugMsgLevel( QString( "Executing SQL: %1" ).arg( query ), 3 );
 
3024
  PGresult *res = ::PQexec( conn, query.toUtf8() );
 
3025
 
 
3026
#ifdef QGISDEBUG
 
3027
  if ( res )
 
3028
  {
 
3029
    int errorStatus = PQresultStatus( res );
 
3030
    if ( errorStatus != PGRES_COMMAND_OK && errorStatus != PGRES_TUPLES_OK )
 
3031
    {
 
3032
      QString err = QString( "Errornous query: %1 returned %2 [%3]" )
 
3033
                    .arg( query )
 
3034
                    .arg( errorStatus )
 
3035
                    .arg( PQresultErrorMessage( res ) );
 
3036
      QgsDebugMsgLevel( err, 3 );
 
3037
    }
 
3038
  }
 
3039
#endif
 
3040
 
 
3041
  return res;
 
3042
}
 
3043
 
 
3044
bool QgsPostgresProvider::Conn::openCursor( QString cursorName, QString sql )
 
3045
{
 
3046
  if ( openCursors++ == 0 )
 
3047
  {
 
3048
    QgsDebugMsg( "Starting read-only transaction" );
 
3049
    PQexecNR( "BEGIN READ ONLY" );
 
3050
  }
 
3051
  QgsDebugMsgLevel( QString( "Binary cursor %1 for %2" ).arg( cursorName ).arg( sql ), 3 );
 
3052
  return PQexecNR( QString( "declare %1 binary cursor for %2" ).arg( cursorName ).arg( sql ) );
 
3053
}
 
3054
 
 
3055
bool QgsPostgresProvider::Conn::closeCursor( QString cursorName )
 
3056
{
 
3057
  bool res = PQexecNR( QString( "CLOSE %1" ).arg( cursorName ) );
 
3058
 
 
3059
  if ( --openCursors == 0 )
 
3060
  {
 
3061
    QgsDebugMsg( "Committing read-only transaction" );
 
3062
    PQexecNR( "COMMIT" );
 
3063
  }
 
3064
 
 
3065
  return res;
 
3066
}
 
3067
 
 
3068
bool QgsPostgresProvider::Conn::PQexecNR( QString query )
 
3069
{
 
3070
  Result res = ::PQexec( conn, query.toUtf8() );
 
3071
  if ( res )
 
3072
  {
 
3073
    int errorStatus = PQresultStatus( res );
 
3074
    if ( errorStatus != PGRES_COMMAND_OK )
 
3075
    {
 
3076
#ifdef QGISDEBUG
 
3077
      QString err = QString( "Query: %1 returned %2 [%3]" )
 
3078
                    .arg( query )
 
3079
                    .arg( errorStatus )
 
3080
                    .arg( PQresultErrorMessage( res ) );
 
3081
      QgsDebugMsgLevel( err, 3 );
 
3082
#endif
 
3083
      if ( openCursors )
 
3084
      {
 
3085
        PQexecNR( "ROLLBACK" );
 
3086
        QgsDebugMsg( QString( "Re-starting read-only transaction after errornous statement - state of %1 cursors lost" ).arg( openCursors ) );
 
3087
        PQexecNR( "BEGIN READ ONLY" );
 
3088
      }
 
3089
    }
 
3090
    return errorStatus == PGRES_COMMAND_OK;
 
3091
  }
2790
3092
  else
2791
 
    {
2792
 
      QgsDebugMsg("Command was unsuccessful. The error message was: " + QString(PQresultErrorMessage(result)));
2793
 
    }
2794
 
  return result;
2795
 
}
2796
 
 
2797
 
void QgsPostgresProvider::showMessageBox(const QString& title, 
2798
 
                                         const QString& text)
2799
 
{
2800
 
  QgsMessageViewer* message = new QgsMessageViewer();
2801
 
  message->setCaption(title);
2802
 
  message->setMessageAsPlainText(text);
2803
 
  message->exec(); // modal
2804
 
}
2805
 
 
2806
 
void QgsPostgresProvider::showMessageBox(const QString& title, 
2807
 
                                         const QStringList& text)
2808
 
{
2809
 
  showMessageBox(title, text.join("\n"));
2810
 
}
2811
 
 
2812
 
int QgsPostgresProvider::getSrid()
2813
 
{
2814
 
  return srid.toInt();
2815
 
}
2816
 
 
 
3093
  {
 
3094
    QgsDebugMsgLevel( QString( "Query: %1 returned no result buffer" ).arg( query ), 3 );
 
3095
  }
 
3096
  return false;
 
3097
}
 
3098
 
 
3099
PGresult *QgsPostgresProvider::Conn::PQgetResult()
 
3100
{
 
3101
  return ::PQgetResult( conn );
 
3102
}
 
3103
 
 
3104
PGresult *QgsPostgresProvider::Conn::PQprepare( QString stmtName, QString query, int nParams, const Oid *paramTypes )
 
3105
{
 
3106
  return ::PQprepare( conn, stmtName.toUtf8(), query.toUtf8(), nParams, paramTypes );
 
3107
}
 
3108
 
 
3109
PGresult *QgsPostgresProvider::Conn::PQexecPrepared( QString stmtName, const QStringList &params )
 
3110
{
 
3111
  const char **param = new const char *[ params.size()];
 
3112
  QList<QByteArray> qparam;
 
3113
 
 
3114
  for ( int i = 0; i < params.size(); i++ )
 
3115
  {
 
3116
    qparam << params[i].toUtf8();
 
3117
 
 
3118
    if ( params[i].isNull() )
 
3119
      param[i] = 0;
 
3120
    else
 
3121
      param[i] = qparam[i];
 
3122
  }
 
3123
 
 
3124
  PGresult *res = ::PQexecPrepared( conn, stmtName.toUtf8(), params.size(), param, NULL, NULL, 0 );
 
3125
 
 
3126
  delete [] param;
 
3127
 
 
3128
  return res;
 
3129
}
 
3130
 
 
3131
void QgsPostgresProvider::Conn::PQfinish()
 
3132
{
 
3133
  ::PQfinish( conn );
 
3134
}
 
3135
 
 
3136
int QgsPostgresProvider::Conn::PQsendQuery( QString query )
 
3137
{
 
3138
  return ::PQsendQuery( conn, query.toUtf8() );
 
3139
}
 
3140
 
 
3141
void QgsPostgresProvider::showMessageBox( const QString& title, const QString& text )
 
3142
{
 
3143
  QgsMessageOutput* message = QgsMessageOutput::createMessageOutput();
 
3144
  message->setTitle( title );
 
3145
  message->setMessage( text, QgsMessageOutput::MessageText );
 
3146
  message->showMessage();
 
3147
}
 
3148
 
 
3149
void QgsPostgresProvider::showMessageBox( const QString& title, const QStringList& text )
 
3150
{
 
3151
  showMessageBox( title, text.join( "\n" ) );
 
3152
}
 
3153
 
 
3154
 
 
3155
QgsCoordinateReferenceSystem QgsPostgresProvider::crs()
 
3156
{
 
3157
  QgsCoordinateReferenceSystem srs;
 
3158
  srs.createFromSrid( srid.toInt() );
 
3159
  return srs;
 
3160
}
 
3161
 
 
3162
QString QgsPostgresProvider::subsetString()
 
3163
{
 
3164
  return sqlWhereClause;
 
3165
}
 
3166
 
 
3167
PGconn * QgsPostgresProvider::pgConnection()
 
3168
{
 
3169
  connectRW();
 
3170
  return connectionRW->pgConnection();
 
3171
}
 
3172
 
 
3173
QString QgsPostgresProvider::getTableName()
 
3174
{
 
3175
  return mTableName;
 
3176
}
2817
3177
 
2818
3178
 
2819
3179
size_t QgsPostgresProvider::layerCount() const
2820
3180
{
2821
 
    return 1;                   // XXX need to return actual number of layers
 
3181
  return 1;                   // XXX need to return actual number of layers
2822
3182
} // QgsPostgresProvider::layerCount()
2823
3183
 
2824
3184
 
2825
3185
 
2826
3186
QString  QgsPostgresProvider::name() const
2827
3187
{
2828
 
    return POSTGRES_KEY;
 
3188
  return POSTGRES_KEY;
2829
3189
} //  QgsPostgresProvider::name()
2830
3190
 
2831
3191
 
2832
3192
 
2833
3193
QString  QgsPostgresProvider::description() const
2834
3194
{
2835
 
    return POSTGRES_DESCRIPTION;
 
3195
  return POSTGRES_DESCRIPTION;
2836
3196
} //  QgsPostgresProvider::description()
2837
3197
 
2838
 
 
2839
 
 
2840
 
 
2841
3198
/**
2842
 
 * Class factory to return a pointer to a newly created 
 
3199
 * Class factory to return a pointer to a newly created
2843
3200
 * QgsPostgresProvider object
2844
3201
 */
2845
 
QGISEXTERN QgsPostgresProvider * classFactory(const QString *uri)
 
3202
QGISEXTERN QgsPostgresProvider * classFactory( const QString *uri )
2846
3203
{
2847
 
  return new QgsPostgresProvider(*uri);
 
3204
  return new QgsPostgresProvider( *uri );
2848
3205
}
2849
3206
/** Required key function (used to map the plugin to a data store type)
2850
3207
*/
2853
3210
  return  POSTGRES_KEY;
2854
3211
}
2855
3212
/**
2856
 
 * Required description function 
 
3213
 * Required description function
2857
3214
 */
2858
3215
QGISEXTERN QString description()
2859
3216
{
2860
 
    return POSTGRES_DESCRIPTION;
2861
 
 
3217
  return POSTGRES_DESCRIPTION;
 
3218
}
2862
3219
/**
2863
3220
 * Required isProvider function. Used to determine if this shared library
2864
3221
 * is a data provider plugin
2865
3222
 */
2866
 
QGISEXTERN bool isProvider(){
 
3223
QGISEXTERN bool isProvider()
 
3224
{
2867
3225
  return true;
2868
3226
}
2869