~ubuntu-branches/ubuntu/hardy/qgis/hardy

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): William Grant
  • Date: 2007-05-06 13:42:32 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20070506134232-pyli6t388w5asd8x
Tags: 0.8.0-3ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
  - debian/rules, debian/qgis.install, debian/qgis.dirs debian/qgis.desktop:
    Add and install .desktop.
* debian/qgis.desktop: Remove Applications category; it's not real.
* Modify Maintainer value to match Debian-Maintainer-Field Spec

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
  qgspostgresprovider.cpp  -  QGIS data provider for PostgreSQL/PostGIS layers
 
3
                             -------------------
 
4
    begin                : 2004/01/07
 
5
    copyright            : (C) 2004 by Gary E.Sherman
 
6
    email                : sherman at mrcc.com
 
7
 ***************************************************************************/
 
8
 
 
9
/***************************************************************************
 
10
 *                                                                         *
 
11
 *   This program is free software; you can redistribute it and/or modify  *
 
12
 *   it under the terms of the GNU General Public License as published by  *
 
13
 *   the Free Software Foundation; either version 2 of the License, or     *
 
14
 *   (at your option) any later version.                                   *
 
15
 *                                                                         *
 
16
 ***************************************************************************/
 
17
 
 
18
/* $Id: qgspostgresprovider.cpp 6321 2006-12-27 18:11:47Z mhugent $ */
 
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
 
34
#ifdef WIN32
 
35
#include <winsock.h>
 
36
#else
 
37
#include <netinet/in.h>
 
38
#endif
 
39
 
 
40
#include <qgis.h>
 
41
#include <qgsfeature.h>
 
42
#include <qgsfield.h>
 
43
#include <qgsrect.h>
 
44
#include <qgsmessageviewer.h>
 
45
 
 
46
#include "qgsprovidercountcalcevent.h"
 
47
#include "qgsproviderextentcalcevent.h"
 
48
 
 
49
#include "qgspostgresprovider.h"
 
50
 
 
51
#include "qgspostgrescountthread.h"
 
52
#include "qgspostgresextentthread.h"
 
53
 
 
54
#include "qgspostgisbox3d.h"
 
55
#include "qgslogger.h"
 
56
 
 
57
#ifdef WIN32
 
58
#define QGISEXTERN extern "C" __declspec( dllexport )
 
59
#else
 
60
#define QGISEXTERN extern "C"
 
61
#endif
 
62
 
 
63
const QString POSTGRES_KEY = "postgres";
 
64
const QString POSTGRES_DESCRIPTION = "PostgreSQL/PostGIS data provider";
 
65
 
 
66
 
 
67
QgsPostgresProvider::QgsPostgresProvider(QString const & uri)
 
68
  :  QgsVectorDataProvider(uri),
 
69
  geomType(QGis::WKBUnknown),
 
70
  gotPostgisVersion(FALSE)
 
71
{
 
72
  // assume this is a valid layer until we determine otherwise
 
73
  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];
 
143
 
 
144
  // 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.
 
335
#ifndef QGISDEBUG
 
336
      PQexec(connection, "set client_min_messages to error");
 
337
#endif
 
338
 
 
339
      // Kick off the long running threads
 
340
 
 
341
#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");
 
351
 
 
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");
 
361
#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 {
 
374
    valid = false;
 
375
    //QgsDebugMsg("Connection to database failed");
 
376
  }
 
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())
 
386
  {
 
387
    valid = false;
 
388
  }
 
389
 
 
390
  // Close the database connection if the layer isn't going to be loaded.
 
391
  if (!valid)
 
392
    PQfinish(connection);
 
393
}
 
394
 
 
395
QgsPostgresProvider::~QgsPostgresProvider()
 
396
{
 
397
#ifdef POSTGRESQL_THREADS
 
398
  QgsDebugMsg("QgsPostgresProvider: About to wait for mExtentThread");
 
399
 
 
400
  mExtentThread.wait();
 
401
 
 
402
  QgsDebugMsg("QgsPostgresProvider: Finished waiting for mExtentThread");
 
403
 
 
404
  QgsDebugMsg("QgsPostgresProvider: About to wait for mCountThread");
 
405
 
 
406
  mCountThread.wait();
 
407
 
 
408
  QgsDebugMsg("QgsPostgresProvider: Finished waiting for mCountThread");
 
409
 
 
410
  // Make sure all events from threads have been processed
 
411
  // (otherwise they will get destroyed prematurely)
 
412
  QApplication::sendPostedEvents(this, QGis::ProviderExtentCalcEvent);
 
413
  QApplication::sendPostedEvents(this, QGis::ProviderCountCalcEvent);
 
414
#endif
 
415
  PQfinish(connection);
 
416
 
 
417
  QgsDebugMsg("QgsPostgresProvider: deconstructing.");
 
418
 
 
419
  //pLog.flush();
 
420
}
 
421
 
 
422
QString QgsPostgresProvider::storageType()
 
423
{
 
424
  return "PostgreSQL database with PostGIS extension";
 
425
}
 
426
 
 
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);
 
515
  }
 
516
  else
 
517
  {
 
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
        
 
595
      } // 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() );
 
719
}
 
720
 
 
721
// TODO - make this function return the real extent_
 
722
QgsRect *QgsPostgresProvider::extent()
 
723
{
 
724
  return &layerExtent;      //extent_->MinX, extent_->MinY, extent_->MaxX, extent_->MaxY);
 
725
}
 
726
 
 
727
/** 
 
728
 * Return the feature type
 
729
 */
 
730
int QgsPostgresProvider::geometryType() const
 
731
{
 
732
  return geomType;
 
733
}
 
734
 
 
735
/** 
 
736
 * Return the feature type
 
737
 */
 
738
long QgsPostgresProvider::featureCount() const
 
739
{
 
740
  return numberFeatures;
 
741
}
 
742
 
 
743
/**
 
744
 * Return the number of fields
 
745
 */
 
746
int QgsPostgresProvider::fieldCount() const
 
747
{
 
748
  return attributeFields.size();
 
749
}
 
750
 
 
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
 
864
{
 
865
  return attributeFields;
 
866
}
 
867
 
 
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)
 
877
  {
 
878
    declare += " where " + sqlWhereClause;
 
879
  }
 
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
  mFeatureQueue.empty();
 
894
 
 
895
}
 
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
 
 
916
/** @todo XXX Perhaps this should be promoted to QgsDataProvider? */
 
917
QString QgsPostgresProvider::endianString()
 
918
{
 
919
  switch ( endian() )
 
920
  {
 
921
    case QgsDataProvider::NDR : 
 
922
      return QString("NDR");
 
923
      break;
 
924
    case QgsDataProvider::XDR : 
 
925
      return QString("XDR");
 
926
      break;
 
927
    default :
 
928
      return QString("UNKNOWN");
 
929
  }
 
930
}
 
931
 
 
932
QString QgsPostgresProvider::getPrimaryKey()
 
933
{
 
934
  // check to see if there is an unique index on the relation, which
 
935
  // can be used as a key into the table. Primary keys are always
 
936
  // unique indices, so we catch them as well.
 
937
 
 
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");
 
947
 
 
948
  QStringList log;
 
949
 
 
950
  // if we got no tuples we ain't got no unique index :)
 
951
  if (PQntuples(pk) == 0)
 
952
  {
 
953
    QgsDebugMsg("Relation has no unique index -- investigating alternatives");
 
954
 
 
955
    // Two options here. If the relation is a table, see if there is
 
956
    // an oid column that can be used instead.
 
957
    // If the relation is a view try to find a suitable column to use as
 
958
    // the primary key.
 
959
 
 
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
 
970
    {
 
971
      QgsDebugMsg("Relation is a table. Checking to see if it has an oid column.");
 
972
 
 
973
      // If there is an oid on the table, use that instead,
 
974
      // 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)
 
982
      {
 
983
        // Could warn the user here that performance will suffer if
 
984
        // oid isn't indexed (and that they may want to add a
 
985
        // 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);
 
998
    }
 
999
    else if (type == "v") // the relation is a view
 
1000
    {
 
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);
 
1010
    }
 
1011
    else
 
1012
      qWarning("Unexpected relation type of '" + type + "'.");
 
1013
  }
 
1014
  else // have some unique indices on the table. Now choose one...
 
1015
  {
 
1016
    // choose which (if more than one) unique index to use
 
1017
    std::vector<std::pair<QString, QString> > suitableKeyColumns;
 
1018
    for (int i = 0; i < PQntuples(pk); ++i)
 
1019
    {
 
1020
      QString col = PQgetvalue(pk, i, 0);
 
1021
      QStringList columns = QStringList::split(" ", col);
 
1022
      if (columns.count() == 1)
 
1023
      {
 
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);
 
1046
      }
 
1047
      else
 
1048
      {
 
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);
 
1056
        QString colNames;
 
1057
        int numCols = PQntuples(types);
 
1058
        for (int j = 0; j < numCols; ++j)
 
1059
        {
 
1060
          if (j == numCols-1)
 
1061
            colNames += tr("and ");
 
1062
          colNames += "'" + QString(PQgetvalue(types, j, 0)) 
 
1063
            + (j < numCols-2 ? "', " : "' ");
 
1064
        }
 
1065
 
 
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"));
 
1069
      }
 
1070
    }
 
1071
 
 
1072
    // suitableKeyColumns now contains the name of columns (and their
 
1073
    // data type) that
 
1074
    // are suitable for use as a key into the table. If there is
 
1075
    // more than one we need to choose one. For the moment, just
 
1076
    // choose the first in the list.
 
1077
 
 
1078
    if (suitableKeyColumns.size() > 0)
 
1079
    {
 
1080
      primaryKey = suitableKeyColumns[0].first;
 
1081
      primaryKeyType = suitableKeyColumns[0].second;
 
1082
    }
 
1083
    else
 
1084
    {
 
1085
      // If there is an oid on the table, use that instead,
 
1086
      // 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);
 
1092
 
 
1093
      if (PQntuples(oidCheck) != 0)
 
1094
      {
 
1095
        primaryKey = "oid";
 
1096
        primaryKeyType = "int4";
 
1097
      }
 
1098
      else
 
1099
      {
 
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");
 
1103
      }
 
1104
      PQclear(oidCheck);
 
1105
    }
 
1106
 
 
1107
    // Either primaryKey has been set by the above code, or it
 
1108
    // hasn't. If not, present some info to the user to give them some
 
1109
    // idea of why not.
 
1110
    if (primaryKey.isEmpty())
 
1111
    {
 
1112
      // Give some info to the user about why things didn't work out.
 
1113
      valid = false;
 
1114
      showMessageBox(tr("Unable to find a key column"), log);
 
1115
    }
 
1116
  }
 
1117
  PQclear(pk);
 
1118
 
 
1119
#ifdef QGISDEBUG
 
1120
  if (primaryKey.length() > 0)
 
1121
    {
 
1122
      QgsDebugMsg("Qgis row key is " + primaryKey);
 
1123
    }
 
1124
  else
 
1125
    {
 
1126
      QgsDebugMsg("Qgis row key was not set.");
 
1127
    }
 
1128
#endif
 
1129
 
 
1130
  return primaryKey;
 
1131
}
 
1132
 
 
1133
// Given the table and column that each column in the view refers to,
 
1134
// choose one. Prefers column with an index on them, but will
 
1135
// otherwise choose something suitable.
 
1136
 
 
1137
QString QgsPostgresProvider::chooseViewColumn(const tableCols& cols)
 
1138
{
 
1139
  // For each relation name and column name need to see if it
 
1140
  // has unique constraints on it, or is a primary key (if not,
 
1141
  // it shouldn't be used). Should then be left with one or more
 
1142
  // entries in the map which can be used as the key.
 
1143
 
 
1144
  QString sql, key;
 
1145
  QStringList log;
 
1146
  tableCols suitable;
 
1147
  // Cache of relation oid's
 
1148
  std::map<QString, QString> relOid;
 
1149
 
 
1150
  std::vector<tableCols::const_iterator> oids;
 
1151
  tableCols::const_iterator iter = cols.begin();
 
1152
  for (; iter != cols.end(); ++iter)
 
1153
  {
 
1154
    QString viewCol   = iter->first;
 
1155
    QString schemaName = iter->second.schema;
 
1156
    QString tableName = iter->second.relation;
 
1157
    QString tableCol  = iter->second.column;
 
1158
    QString colType   = iter->second.type;
 
1159
 
 
1160
    // Get the oid from pg_class for the given schema.relation for use
 
1161
    // 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()));
 
1166
    QString rel_oid;
 
1167
    if (PQntuples(result) == 1)
 
1168
    {
 
1169
      rel_oid = PQgetvalue(result, 0, 0);
 
1170
      // Keep the rel_oid for use later one.
 
1171
      relOid[viewCol] = rel_oid;
 
1172
    }
 
1173
    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 
 
1181
    // table 'tableName' and schema 'schemaName' has one or more
 
1182
    // columns that satisfy the following conditions:
 
1183
    // 1) the column has data type of int4.
 
1184
    // 2) the column has a unique constraint or primary key constraint
 
1185
    //    on it.
 
1186
    // 3) the constraint applies just to the column of interest (i.e.,
 
1187
    //    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]'";
 
1194
 
 
1195
    result = PQexec(connection, (const char*)(sql.utf8()));
 
1196
    if (PQntuples(result) == 1 && colType == "int4")
 
1197
      suitable[viewCol] = iter->second;
 
1198
 
 
1199
    QString details = "'" + viewCol + "'" + tr(" derives from ") 
 
1200
      + "'" + schemaName + "." + tableName + "." + tableCol + "' ";
 
1201
 
 
1202
    if (PQntuples(result) == 1 && colType == "int4")
 
1203
    {
 
1204
      details += tr("and is suitable.");
 
1205
    }
 
1206
    else
 
1207
    {
 
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)");
 
1212
      else
 
1213
        details += tr(" and does not have a suitable constraint)");
 
1214
    }
 
1215
 
 
1216
    log << details;
 
1217
 
 
1218
    PQclear(result);
 
1219
    if (tableCol == "oid")
 
1220
      oids.push_back(iter);
 
1221
  }
 
1222
 
 
1223
  // 'oid' columns in tables don't have a constraint on them, but
 
1224
  // they are useful to consider, so add them in if not already
 
1225
  // here.
 
1226
  for (int i = 0; i < oids.size(); ++i)
 
1227
  {
 
1228
    if (suitable.find(oids[i]->first) == suitable.end())
 
1229
    {
 
1230
      suitable[oids[i]->first] = oids[i]->second;
 
1231
      QgsDebugMsg("Adding column " + oids[i]->first + " as it may be suitable.");
 
1232
    }
 
1233
  }
 
1234
 
 
1235
  // Now have a map containing all of the columns in the view that
 
1236
  // might be suitable for use as the key to the table. Need to choose
 
1237
  // one thus:
 
1238
  //
 
1239
  // If there is more than one suitable column pick one that is
 
1240
  // indexed, else pick one called 'oid' if it exists, else
 
1241
  // pick the first one. If there are none we return an empty string. 
 
1242
 
 
1243
  // Search for one with an index
 
1244
  tableCols::const_iterator i = suitable.begin();
 
1245
  for (; i != suitable.end(); ++i)
 
1246
  {
 
1247
    // Get the relation oid from our cache.
 
1248
    QString rel_oid = relOid[i->first];
 
1249
    // 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()));
 
1254
 
 
1255
    if (PQntuples(result) > 0 && uniqueData(mSchemaName, mTableName, i->first))
 
1256
    { // Got one. Use it.
 
1257
      key = i->first;
 
1258
      QgsDebugMsg("Picked column '" + key + "' because it has an index.");
 
1259
      break;
 
1260
    }
 
1261
    PQclear(result);
 
1262
  }
 
1263
 
 
1264
  if (key.isEmpty())
 
1265
  {
 
1266
    // If none have indices, choose one that is called 'oid' (if it
 
1267
    // 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))
 
1271
    {
 
1272
      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");
 
1274
    }
 
1275
    // else choose the first one in the container that has unique data
 
1276
    else
 
1277
    {
 
1278
      tableCols::const_iterator i = suitable.begin();
 
1279
      for (; i != suitable.end(); ++i)
 
1280
      {
 
1281
        if (uniqueData(mSchemaName, mTableName, i->first))
 
1282
        {
 
1283
          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.");
 
1285
          break;
 
1286
        }
 
1287
        else
 
1288
        {
 
1289
          log << QString(tr("Note: ") + "'" + i->first + "' "
 
1290
                         + tr("initially appeared suitable but does not "
 
1291
                              "contain unique data, so is not suitable.\n"));
 
1292
        }
 
1293
      }
 
1294
    }
 
1295
  }
 
1296
 
 
1297
  if (key.isEmpty())
 
1298
  {
 
1299
    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);
 
1313
  }
 
1314
 
 
1315
  return key;
 
1316
}
 
1317
 
 
1318
bool QgsPostgresProvider::uniqueData(QString schemaName, 
 
1319
                                     QString tableName, QString colName)
 
1320
{
 
1321
  // Check to see if the given column contains unique data
 
1322
 
 
1323
  bool isUnique = false;
 
1324
 
 
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);
 
1335
 
 
1336
  return isUnique;
 
1337
}
 
1338
 
 
1339
// This function will return in the cols variable the 
 
1340
// underlying view and columns for each column in
 
1341
// mSchemaName.mTableName.
 
1342
 
 
1343
void QgsPostgresProvider::findColumns(tableCols& cols)
 
1344
{
 
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())
 
1512
      {
 
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;
 
1516
      }
 
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
    }
 
1543
  }
 
1544
  PQclear(result);
 
1545
}
 
1546
 
 
1547
// 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;
 
1564
}
 
1565
 
 
1566
// 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(){
 
1604
  return valid;
 
1605
}
 
1606
 
 
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
{
 
1851
  // Get the default column value from the Postgres information
 
1852
  // schema. If there is no default we return an empty string.
 
1853
 
 
1854
  // Maintaining a cache of the results of this query would be quite
 
1855
  // simple and if this query is called lots, could save some time.
 
1856
 
 
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);
 
1872
 
 
1873
  return defaultValue;
 
1874
}
 
1875
 
 
1876
bool QgsPostgresProvider::deleteFeature(int id)
 
1877
{
 
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;
 
1896
}
 
1897
 
 
1898
/**
 
1899
 * Check to see if GEOS is available
 
1900
 */
 
1901
bool QgsPostgresProvider::hasGEOS(PGconn *connection){
 
1902
  // make sure info is up to date for the current connection
 
1903
  postgisVersion(connection);
 
1904
  // get geos capability
 
1905
  return geosAvailable;
 
1906
}
 
1907
 
 
1908
/* Functions for determining available features in postGIS */
 
1909
QString QgsPostgresProvider::postgisVersion(PGconn *connection)
 
1910
{
 
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);
 
1917
 
 
1918
  // Get major and minor version
 
1919
  QStringList postgisVersionParts = QStringList::split(".", postgisParts[0]);
 
1920
 
 
1921
  postgisVersionMajor = postgisVersionParts[0].toInt();
 
1922
  postgisVersionMinor = postgisVersionParts[1].toInt();
 
1923
 
 
1924
  // assume no capabilities
 
1925
  geosAvailable = false;
 
1926
  gistAvailable = false;
 
1927
  projAvailable = false;
 
1928
 
 
1929
  // 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;
 
1944
 
 
1945
  return postgisVersionInfo;
 
1946
}
 
1947
 
 
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
    {
 
2033
      //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 databse"),
 
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 databse 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;
 
2156
        }
 
2157
        else
 
2158
        {
 
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;
 
2176
        }
 
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 databse"),
 
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 databse 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)
 
2222
  {
 
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.");
 
2366
  return returnvalue;
 
2367
}
 
2368
 
 
2369
 
 
2370
bool QgsPostgresProvider::supportsSaveAsShapefile() const
 
2371
{
 
2372
  return false;
 
2373
}
 
2374
 
 
2375
int QgsPostgresProvider::capabilities() const
 
2376
{
 
2377
  return (
 
2378
           QgsVectorDataProvider::AddFeatures |
 
2379
           QgsVectorDataProvider::DeleteFeatures |
 
2380
           QgsVectorDataProvider::ChangeAttributeValues |
 
2381
           QgsVectorDataProvider::AddAttributes |
 
2382
           QgsVectorDataProvider::DeleteAttributes |
 
2383
           QgsVectorDataProvider::ChangeGeometries |
 
2384
           QgsVectorDataProvider::SelectGeometryAtId
 
2385
         );
 
2386
}
 
2387
 
 
2388
void QgsPostgresProvider::setSubsetString(QString theSQL)
 
2389
{
 
2390
  sqlWhereClause=theSQL;
 
2391
  // Update datasource uri too
 
2392
  mUri.sql=theSQL;
 
2393
  // Update yet another copy of the uri. Why are there 3 copies of the
 
2394
  // uri? Perhaps this needs some rationalisation.....
 
2395
  setDataSourceUri(mUri.text());
 
2396
 
 
2397
  // need to recalculate the number of features...
 
2398
  getFeatureCount();
 
2399
  calculateExtents();
 
2400
 
 
2401
}
 
2402
 
 
2403
long QgsPostgresProvider::getFeatureCount()
 
2404
{
 
2405
  // get total number of features
 
2406
 
 
2407
  // First get an approximate count; then delegate to
 
2408
  // a thread the task of getting the full count.
 
2409
 
 
2410
#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)
 
2419
  {
 
2420
    sql += " where " + sqlWhereClause;
 
2421
  }
 
2422
#endif
 
2423
 
 
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;
 
2433
}
 
2434
 
 
2435
// TODO: use the estimateExtents procedure of PostGIS and PostgreSQL 8
 
2436
// This tip thanks to #qgis irc nick "creeping"
 
2437
void QgsPostgresProvider::calculateExtents()
 
2438
{
 
2439
#ifdef POSTGRESQL_THREADS
 
2440
  // get the approximate extent by retreiving the bounding box
 
2441
  // of the first few items with a geometry
 
2442
 
 
2443
  QString sql = "select box3d(" + geometryColumn + ") from " 
 
2444
    + mSchemaTableName + " where ";
 
2445
 
 
2446
  if(sqlWhereClause.length() > 0)
 
2447
  {
 
2448
    sql += "(" + sqlWhereClause + ") and ";
 
2449
  }
 
2450
 
 
2451
  sql += "not IsEmpty(" + geometryColumn + ") limit 5";
 
2452
 
 
2453
 
 
2454
#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;
 
2460
#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()));
 
2465
 
 
2466
  // TODO: Guard against the result having no rows
 
2467
 
 
2468
  for (int i = 0; i < PQntuples(result); i++)
 
2469
  {
 
2470
    std::string box3d = PQgetvalue(result, i, 0);
 
2471
 
 
2472
    if (0 == i)
 
2473
    {
 
2474
      // create the initial extent
 
2475
      layerExtent = QgsPostGisBox3d(box3d);
 
2476
    }
 
2477
    else
 
2478
    {
 
2479
      // extend the initial extent
 
2480
      QgsPostGisBox3d b = QgsPostGisBox3d(box3d);
 
2481
 
 
2482
      layerExtent.combineExtentWith( &b );
 
2483
    }
 
2484
 
 
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
 
 
2489
  }
 
2490
 
 
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
#else // non-postgresql threads version
 
2508
 
 
2509
  // get the extents
 
2510
 
 
2511
  QString sql = "select extent(\"" + geometryColumn + "\") from " + 
 
2512
    mSchemaTableName;
 
2513
  if(sqlWhereClause.length() > 0)
 
2514
  {
 
2515
    sql += " where " + sqlWhereClause;
 
2516
  }
 
2517
 
 
2518
#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);
 
2558
#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
 
 
2573
}
 
2574
 
 
2575
/**
 
2576
 * Event sink for events from threads
 
2577
 */
 
2578
void QgsPostgresProvider::customEvent( QCustomEvent * e )
 
2579
{
 
2580
  QgsDebugMsg("QgsPostgresProvider: received a custom event " + e->type());
 
2581
 
 
2582
  switch ( e->type() )
 
2583
  {
 
2584
    case (QEvent::Type) QGis::ProviderExtentCalcEvent:
 
2585
 
 
2586
      QgsDebugMsg("QgsPostgresProvider: extent has been calculated");
 
2587
 
 
2588
      // Collect the new extent from the event and set this layer's
 
2589
      // extent with it.
 
2590
 
 
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
 
2603
      emit fullExtentCalculated();
 
2604
#endif
 
2605
 
 
2606
      // TODO: Only uncomment this when the overview map canvas has been subclassed
 
2607
      // from the QgsMapCanvas
 
2608
 
 
2609
      //        QgsDebugMsg("QgsPostgresProvider: emitting repaintRequested()");
 
2610
      //        emit repaintRequested();
 
2611
 
 
2612
      break;
 
2613
 
 
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
 
 
2624
      break;
 
2625
  }
 
2626
 
 
2627
  QgsDebugMsg("QgsPostgresProvider: Finished processing custom event " + QString::number(e->type()));
 
2628
 
 
2629
}
 
2630
 
 
2631
 
 
2632
bool QgsPostgresProvider::deduceEndian()
 
2633
{
 
2634
  // need to store the PostgreSQL endian format used in binary cursors
 
2635
  // since it appears that starting with
 
2636
  // version 7.4, binary cursors return data in XDR whereas previous versions
 
2637
  // return data in the endian of the server
 
2638
 
 
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()));
 
2643
  // get the int value from a "normal" select
 
2644
  QString oidValue = PQgetvalue(oidResult,0,0);
 
2645
  PQclear(oidResult);
 
2646
  QgsDebugMsg("Creating binary cursor");
 
2647
 
 
2648
  // 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");
 
2660
  swapEndian = true;
 
2661
  if(PQntuples(fResult) > 0){
 
2662
    // 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));
 
2667
    // compare the two oid values to determine if we need to do an endian swap
 
2668
    if(oid == oidValue.toInt())
 
2669
      swapEndian = false;
 
2670
 
 
2671
    PQclear(fResult);
 
2672
  }
 
2673
  return swapEndian;
 
2674
}
 
2675
 
 
2676
bool QgsPostgresProvider::getGeometryDetails()
 
2677
{
 
2678
  QString fType("");
 
2679
  srid = "";
 
2680
  valid = false;
 
2681
  QStringList log;
 
2682
 
 
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)
 
2694
  {
 
2695
    srid = PQgetvalue(result, 0, PQfnumber(result, "srid"));
 
2696
    fType = PQgetvalue(result, 0, PQfnumber(result, "type"));
 
2697
    PQclear(result);
 
2698
  }
 
2699
  else
 
2700
  {
 
2701
    // Didn't find what we need in the geometry_columns table, so
 
2702
    // get stuff from the relevant column instead. This may (will?) 
 
2703
    // 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);
 
2718
  }
 
2719
 
 
2720
  if (!srid.isEmpty() && !fType.isEmpty())
 
2721
  {
 
2722
    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
      }
 
2747
    else
 
2748
    {
 
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."));
 
2753
      valid = false;
 
2754
    }
 
2755
  }
 
2756
  else // something went wrong...
 
2757
  {
 
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
    }
 
2773
  else
 
2774
    {
 
2775
      QgsDebugMsg("Failed to get geometry details for Postgres layer.");
 
2776
    }
 
2777
  return valid;
 
2778
}
 
2779
 
 
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
    }
 
2790
  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
 
 
2817
 
 
2818
 
 
2819
size_t QgsPostgresProvider::layerCount() const
 
2820
{
 
2821
    return 1;                   // XXX need to return actual number of layers
 
2822
} // QgsPostgresProvider::layerCount()
 
2823
 
 
2824
 
 
2825
 
 
2826
QString  QgsPostgresProvider::name() const
 
2827
{
 
2828
    return POSTGRES_KEY;
 
2829
} //  QgsPostgresProvider::name()
 
2830
 
 
2831
 
 
2832
 
 
2833
QString  QgsPostgresProvider::description() const
 
2834
{
 
2835
    return POSTGRES_DESCRIPTION;
 
2836
} //  QgsPostgresProvider::description()
 
2837
 
 
2838
 
 
2839
 
 
2840
 
 
2841
/**
 
2842
 * Class factory to return a pointer to a newly created 
 
2843
 * QgsPostgresProvider object
 
2844
 */
 
2845
QGISEXTERN QgsPostgresProvider * classFactory(const QString *uri)
 
2846
{
 
2847
  return new QgsPostgresProvider(*uri);
 
2848
}
 
2849
/** Required key function (used to map the plugin to a data store type)
 
2850
*/
 
2851
QGISEXTERN QString providerKey()
 
2852
{
 
2853
  return  POSTGRES_KEY;
 
2854
}
 
2855
/**
 
2856
 * Required description function 
 
2857
 */
 
2858
QGISEXTERN QString description()
 
2859
{
 
2860
    return POSTGRES_DESCRIPTION;
 
2861
 
2862
/**
 
2863
 * Required isProvider function. Used to determine if this shared library
 
2864
 * is a data provider plugin
 
2865
 */
 
2866
QGISEXTERN bool isProvider(){
 
2867
  return true;
 
2868
}
 
2869