1 /***************************************************************************
2 qgspostgresconn.cpp - connection class to PostgreSQL/PostGIS
3 -------------------
4 begin : 2011/01/28
5 copyright : (C) 2011 by Juergen E. Fischer
6 email : jef at norbit dot de
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 #include "qgspostgresconn.h"
19 #include "qgsauthmanager.h"
20 #include "qgslogger.h"
21 #include "qgsdatasourceuri.h"
22 #include "qgsmessagelog.h"
23 #include "qgscredentials.h"
24 #include "qgsfields.h"
25 #include "qgspgtablemodel.h"
26 #include "qgsproviderregistry.h"
27 #include "qgsvectordataprovider.h"
28 #include "qgswkbtypes.h"
29 #include "qgssettings.h"
30 #include "qgsjsonutils.h"
31 #include "qgspostgresstringutils.h"
32 #include "qgspostgresconnpool.h"
33
34 #include <QApplication>
35 #include <QStringList>
36 #include <QThread>
37
38 #include <climits>
39
40 #include <nlohmann/json.hpp>
41
42 // for htonl
43 #ifdef Q_OS_WIN
44 #include <winsock.h>
45 #else
46 #include <netinet/in.h>
47 #endif
48
49 const int PG_DEFAULT_TIMEOUT = 30;
50
~QgsPostgresResult()51 QgsPostgresResult::~QgsPostgresResult()
52 {
53 if ( mRes )
54 ::PQclear( mRes );
55 mRes = nullptr;
56 }
57
operator =(PGresult * result)58 QgsPostgresResult &QgsPostgresResult::operator=( PGresult *result )
59 {
60 if ( mRes )
61 ::PQclear( mRes );
62 mRes = result;
63 return *this;
64 }
65
operator =(const QgsPostgresResult & src)66 QgsPostgresResult &QgsPostgresResult::operator=( const QgsPostgresResult &src )
67 {
68 if ( mRes )
69 ::PQclear( mRes );
70 mRes = src.result();
71 return *this;
72 }
73
PQresultStatus()74 ExecStatusType QgsPostgresResult::PQresultStatus()
75 {
76 return mRes ? ::PQresultStatus( mRes ) : PGRES_FATAL_ERROR;
77 }
78
PQresultErrorMessage()79 QString QgsPostgresResult::PQresultErrorMessage()
80 {
81 return mRes ? QString::fromUtf8( ::PQresultErrorMessage( mRes ) ) : QObject::tr( "no result buffer" );
82 }
83
PQntuples()84 int QgsPostgresResult::PQntuples()
85 {
86 Q_ASSERT( mRes );
87 return ::PQntuples( mRes );
88 }
89
PQgetvalue(int row,int col)90 QString QgsPostgresResult::PQgetvalue( int row, int col )
91 {
92 Q_ASSERT( mRes );
93 return PQgetisnull( row, col )
94 ? QString()
95 : QString::fromUtf8( ::PQgetvalue( mRes, row, col ) );
96 }
97
PQgetisnull(int row,int col)98 bool QgsPostgresResult::PQgetisnull( int row, int col )
99 {
100 Q_ASSERT( mRes );
101 return ::PQgetisnull( mRes, row, col );
102 }
103
PQnfields()104 int QgsPostgresResult::PQnfields()
105 {
106 Q_ASSERT( mRes );
107 return ::PQnfields( mRes );
108 }
109
PQfname(int col)110 QString QgsPostgresResult::PQfname( int col )
111 {
112 Q_ASSERT( mRes );
113 return QString::fromUtf8( ::PQfname( mRes, col ) );
114 }
115
PQftable(int col)116 Oid QgsPostgresResult::PQftable( int col )
117 {
118 Q_ASSERT( mRes );
119 return ::PQftable( mRes, col );
120 }
121
PQftablecol(int col)122 int QgsPostgresResult::PQftablecol( int col )
123 {
124 Q_ASSERT( mRes );
125 return ::PQftablecol( mRes, col );
126 }
127
PQftype(int col)128 Oid QgsPostgresResult::PQftype( int col )
129 {
130 Q_ASSERT( mRes );
131 return ::PQftype( mRes, col );
132 }
133
PQfmod(int col)134 int QgsPostgresResult::PQfmod( int col )
135 {
136 Q_ASSERT( mRes );
137 return ::PQfmod( mRes, col );
138 }
139
PQoidValue()140 Oid QgsPostgresResult::PQoidValue()
141 {
142 Q_ASSERT( mRes );
143 return ::PQoidValue( mRes );
144 }
145
QgsPoolPostgresConn(const QString & connInfo)146 QgsPoolPostgresConn::QgsPoolPostgresConn( const QString &connInfo )
147 : mPgConn( QgsPostgresConnPool::instance()->acquireConnection( connInfo ) )
148 {
149 }
150
~QgsPoolPostgresConn()151 QgsPoolPostgresConn::~QgsPoolPostgresConn()
152 {
153 if ( mPgConn )
154 QgsPostgresConnPool::instance()->releaseConnection( mPgConn );
155 }
156
157
158 QMap<QString, QgsPostgresConn *> QgsPostgresConn::sConnectionsRO;
159 QMap<QString, QgsPostgresConn *> QgsPostgresConn::sConnectionsRW;
160
161 const int QgsPostgresConn::GEOM_TYPE_SELECT_LIMIT = 100;
162
connectDb(const QString & conninfo,bool readonly,bool shared,bool transaction)163 QgsPostgresConn *QgsPostgresConn::connectDb( const QString &conninfo, bool readonly, bool shared, bool transaction )
164 {
165 QMap<QString, QgsPostgresConn *> &connections =
166 readonly ? QgsPostgresConn::sConnectionsRO : QgsPostgresConn::sConnectionsRW;
167
168 // This is called from may places where shared parameter cannot be forced to false (QgsVectorLayerExporter)
169 // and which is run in a different thread (drag and drop in browser)
170 if ( QApplication::instance()->thread() != QThread::currentThread() )
171 {
172 shared = false;
173 }
174
175 if ( shared )
176 {
177 // sharing connection between threads is not safe
178 // See https://github.com/qgis/QGIS/issues/21205
179 Q_ASSERT( QApplication::instance()->thread() == QThread::currentThread() );
180
181 if ( connections.contains( conninfo ) )
182 {
183 QgsDebugMsgLevel( QStringLiteral( "Using cached connection for %1" ).arg( conninfo ), 2 );
184 connections[conninfo]->mRef++;
185 return connections[conninfo];
186 }
187 }
188
189 QgsPostgresConn *conn = new QgsPostgresConn( conninfo, readonly, shared, transaction );
190
191 if ( conn->mRef == 0 )
192 {
193 delete conn;
194 return nullptr;
195 }
196
197 if ( shared )
198 {
199 connections.insert( conninfo, conn );
200 }
201
202 return conn;
203 }
204
noticeProcessor(void * arg,const char * message)205 static void noticeProcessor( void *arg, const char *message )
206 {
207 Q_UNUSED( arg )
208 QString msg( QString::fromUtf8( message ) );
209 msg.chop( 1 );
210 QgsMessageLog::logMessage( QObject::tr( "NOTICE: %1" ).arg( msg ), QObject::tr( "PostGIS" ) );
211 }
212
QgsPostgresConn(const QString & conninfo,bool readOnly,bool shared,bool transaction)213 QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool shared, bool transaction )
214 : mRef( 1 )
215 , mOpenCursors( 0 )
216 , mConnInfo( conninfo )
217 , mGeosAvailable( false )
218 , mProjAvailable( false )
219 , mTopologyAvailable( false )
220 , mGotPostgisVersion( false )
221 , mPostgresqlVersion( 0 )
222 , mPostgisVersionMajor( 0 )
223 , mPostgisVersionMinor( 0 )
224 , mPointcloudAvailable( false )
225 , mRasterAvailable( false )
226 , mUseWkbHex( false )
227 , mReadOnly( readOnly )
228 , mSwapEndian( false )
229 , mNextCursorId( 0 )
230 , mShared( shared )
231 , mTransaction( transaction )
232 {
233
234 QgsDebugMsgLevel( QStringLiteral( "New PostgreSQL connection for " ) + conninfo, 2 );
235
236 // expand connectionInfo
237 QgsDataSourceUri uri( conninfo );
238 QString expandedConnectionInfo = uri.connectionInfo( true );
239
240 auto addDefaultTimeoutAndClientEncoding = []( QString & connectString )
241 {
242 if ( !connectString.contains( QStringLiteral( "connect_timeout=" ) ) )
243 {
244 // add default timeout
245 QgsSettings settings;
246 int timeout = settings.value( QStringLiteral( "PostgreSQL/default_timeout" ), PG_DEFAULT_TIMEOUT, QgsSettings::Providers ).toInt();
247 connectString += QStringLiteral( " connect_timeout=%1" ).arg( timeout );
248 }
249
250 connectString += QLatin1String( " client_encoding='UTF-8'" );
251 };
252 addDefaultTimeoutAndClientEncoding( expandedConnectionInfo );
253
254 mConn = PQconnectdb( expandedConnectionInfo.toUtf8() );
255
256 // remove temporary cert/key/CA if they exist
257 QgsDataSourceUri expandedUri( expandedConnectionInfo );
258 QStringList parameters;
259 parameters << QStringLiteral( "sslcert" ) << QStringLiteral( "sslkey" ) << QStringLiteral( "sslrootcert" );
260 const auto constParameters = parameters;
261 for ( const QString ¶m : constParameters )
262 {
263 if ( expandedUri.hasParam( param ) )
264 {
265 QString fileName = expandedUri.param( param );
266 fileName.remove( QStringLiteral( "'" ) );
267 QFile file( fileName );
268 // set minimal permission to allow removing on Win.
269 // On linux and Mac if file is set with QFile::ReadUser
270 // does not create problem removing certs
271 if ( !file.setPermissions( QFile::WriteOwner ) )
272 {
273 QString errorMsg = tr( "Cannot set WriteOwner permission to cert: %0 to allow removing it" ).arg( file.fileName() );
274 PQfinish();
275 QgsMessageLog::logMessage( tr( "Client security failure" ) + '\n' + errorMsg, tr( "PostGIS" ) );
276 mRef = 0;
277 return;
278 }
279 if ( !file.remove() )
280 {
281 QString errorMsg = tr( "Cannot remove cert: %0" ).arg( file.fileName() );
282 PQfinish();
283 QgsMessageLog::logMessage( tr( "Client security failure" ) + '\n' + errorMsg, tr( "PostGIS" ) );
284 mRef = 0;
285 return;
286 }
287 }
288 }
289
290 // check the connection status
291 if ( PQstatus() != CONNECTION_OK )
292 {
293 QString username = uri.username();
294 QString password = uri.password();
295
296 QgsCredentials::instance()->lock();
297
298 int i = 0;
299 while ( PQstatus() != CONNECTION_OK && i < 5 )
300 {
301 ++i;
302 bool ok = QgsCredentials::instance()->get( conninfo, username, password, PQerrorMessage() );
303 if ( !ok )
304 {
305 break;
306 }
307
308 PQfinish();
309
310 if ( !username.isEmpty() )
311 uri.setUsername( username );
312
313 if ( !password.isEmpty() )
314 uri.setPassword( password );
315
316 QgsDebugMsgLevel( "Connecting to " + uri.connectionInfo( false ), 2 );
317 QString connectString = uri.connectionInfo();
318 addDefaultTimeoutAndClientEncoding( connectString );
319 mConn = PQconnectdb( connectString.toUtf8() );
320 }
321
322 if ( PQstatus() == CONNECTION_OK )
323 QgsCredentials::instance()->put( conninfo, username, password );
324
325 QgsCredentials::instance()->unlock();
326 }
327
328 if ( PQstatus() != CONNECTION_OK )
329 {
330 QString errorMsg = PQerrorMessage();
331 PQfinish();
332 QgsMessageLog::logMessage( tr( "Connection to database failed" ) + '\n' + errorMsg, tr( "PostGIS" ) );
333 mRef = 0;
334 return;
335 }
336
337 QgsDebugMsgLevel( QStringLiteral( "Connection to the database was successful" ), 2 );
338
339 deduceEndian();
340
341 /* Check to see if we have working PostGIS support */
342 if ( !postgisVersion().isNull() )
343 {
344 /* Check to see if we have GEOS support and if not, warn the user about
345 the problems they will see :) */
346 QgsDebugMsgLevel( QStringLiteral( "Checking for GEOS support" ), 3 );
347
348 if ( !hasGEOS() )
349 {
350 QgsMessageLog::logMessage( tr( "Your PostGIS installation has no GEOS support. Feature selection and identification will not work properly. Please install PostGIS with GEOS support (http://geos.refractions.net)" ), tr( "PostGIS" ) );
351 }
352 else
353 {
354 QgsDebugMsgLevel( QStringLiteral( "GEOS support available!" ), 3 );
355 }
356 }
357
358 if ( mPostgresqlVersion >= 90000 )
359 {
360 PQexecNR( QStringLiteral( "SET application_name='QGIS'" ) );
361 PQexecNR( QStringLiteral( "SET extra_float_digits=3" ) );
362 }
363
364 PQsetNoticeProcessor( mConn, noticeProcessor, nullptr );
365 }
366
~QgsPostgresConn()367 QgsPostgresConn::~QgsPostgresConn()
368 {
369 Q_ASSERT( mRef == 0 );
370 if ( mConn )
371 ::PQfinish( mConn );
372 mConn = nullptr;
373 }
374
ref()375 void QgsPostgresConn::ref()
376 {
377 QMutexLocker locker( &mLock );
378 ++mRef;
379 }
380
unref()381 void QgsPostgresConn::unref()
382 {
383 QMutexLocker locker( &mLock );
384 if ( --mRef > 0 )
385 return;
386
387 if ( mShared )
388 {
389 QMap<QString, QgsPostgresConn *> &connections = mReadOnly ? sConnectionsRO : sConnectionsRW;
390
391 QString key = connections.key( this, QString() );
392
393 Q_ASSERT( !key.isNull() );
394 connections.remove( key );
395 }
396
397 // to avoid destroying locked mutex
398 locker.unlock();
399 delete this;
400 }
401
402 /* private */
supportedSpatialTypes() const403 QStringList QgsPostgresConn::supportedSpatialTypes() const
404 {
405 QStringList supportedSpatialTypes;
406
407 supportedSpatialTypes << quotedValue( "geometry" )
408 << quotedValue( "geography" );
409
410 if ( hasPointcloud() )
411 {
412 supportedSpatialTypes << quotedValue( "pcpatch" );
413 supportedSpatialTypes << quotedValue( "pcpoint" );
414 }
415
416 if ( hasRaster() )
417 supportedSpatialTypes << quotedValue( "raster" );
418
419 if ( hasTopology() )
420 supportedSpatialTypes << quotedValue( "topogeometry" );
421
422 return supportedSpatialTypes;
423 }
424
425 /* private */
426 // TODO: deprecate this function
addColumnInfo(QgsPostgresLayerProperty & layerProperty,const QString & schemaName,const QString & viewName,bool fetchPkCandidates)427 void QgsPostgresConn::addColumnInfo( QgsPostgresLayerProperty &layerProperty, const QString &schemaName, const QString &viewName, bool fetchPkCandidates )
428 {
429 // TODO: optimize this query when pk candidates aren't needed
430 // could use array_agg() and count()
431 // array output would look like this: "{One,tWo}"
432 QString sql = QStringLiteral( "SELECT attname, CASE WHEN typname in (%1) THEN 1 ELSE null END AS isSpatial FROM pg_attribute JOIN pg_type ON atttypid=pg_type.oid WHERE attrelid=regclass('%2.%3') AND NOT attisdropped AND attnum>0 ORDER BY attnum" )
433 .arg( supportedSpatialTypes().join( ',' ) )
434 .arg( quotedIdentifier( schemaName ),
435 quotedIdentifier( viewName ) );
436 QgsDebugMsgLevel( "getting column info: " + sql, 2 );
437 QgsPostgresResult colRes( PQexec( sql ) );
438
439 layerProperty.pkCols.clear();
440 layerProperty.nSpCols = 0;
441
442 if ( colRes.PQresultStatus() == PGRES_TUPLES_OK )
443 {
444 for ( int i = 0; i < colRes.PQntuples(); i++ )
445 {
446 if ( fetchPkCandidates )
447 {
448 layerProperty.pkCols << colRes.PQgetvalue( i, 0 );
449 }
450
451 if ( colRes.PQgetisnull( i, 1 ) == 0 )
452 {
453 ++layerProperty.nSpCols;
454 }
455 }
456 }
457 else
458 {
459 QgsMessageLog::logMessage( tr( "SQL: %1\nresult: %2\nerror: %3\n" ).arg( sql ).arg( colRes.PQresultStatus() ).arg( colRes.PQresultErrorMessage() ), tr( "PostGIS" ) );
460 }
461
462 }
463
getTableInfo(bool searchGeometryColumnsOnly,bool searchPublicOnly,bool allowGeometrylessTables,const QString & schema)464 bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema )
465 {
466 QMutexLocker locker( &mLock );
467 int nColumns = 0;
468 int foundInTables = 0;
469 QgsPostgresResult result;
470 QString query;
471
472 mLayersSupported.clear();
473
474 for ( int i = SctGeometry; i <= SctRaster; ++i )
475 {
476 QString sql, tableName, schemaName, columnName, typeName, sridName, gtableName, dimName;
477
478 if ( i == SctGeometry )
479 {
480 tableName = QStringLiteral( "l.f_table_name" );
481 schemaName = QStringLiteral( "l.f_table_schema" );
482 columnName = QStringLiteral( "l.f_geometry_column" );
483 typeName = QStringLiteral( "upper(l.type)" );
484 sridName = QStringLiteral( "l.srid" );
485 dimName = QStringLiteral( "l.coord_dimension" );
486 gtableName = QStringLiteral( "geometry_columns" );
487 }
488 // Geography since postgis 1.5
489 else if ( i == SctGeography
490 && ( mPostgisVersionMajor >= 2
491 || ( mPostgisVersionMajor == 1 && mPostgisVersionMinor >= 5 ) ) )
492 {
493 tableName = QStringLiteral( "l.f_table_name" );
494 schemaName = QStringLiteral( "l.f_table_schema" );
495 columnName = QStringLiteral( "l.f_geography_column" );
496 typeName = QStringLiteral( "upper(l.type)" );
497 sridName = QStringLiteral( "l.srid" );
498 dimName = QStringLiteral( "2" );
499 gtableName = QStringLiteral( "geography_columns" );
500 }
501 else if ( i == SctTopoGeometry )
502 {
503 if ( !hasTopology() )
504 continue;
505
506 schemaName = QStringLiteral( "l.schema_name" );
507 tableName = QStringLiteral( "l.table_name" );
508 columnName = QStringLiteral( "l.feature_column" );
509 typeName = "CASE "
510 "WHEN l.feature_type = 1 THEN 'MULTIPOINT' "
511 "WHEN l.feature_type = 2 THEN 'MULTILINESTRING' "
512 "WHEN l.feature_type = 3 THEN 'MULTIPOLYGON' "
513 "WHEN l.feature_type = 4 THEN 'GEOMETRYCOLLECTION' "
514 "END AS type";
515 sridName = QStringLiteral( "(SELECT srid FROM topology.topology t WHERE l.topology_id=t.id)" );
516 dimName = QStringLiteral( "2" );
517 gtableName = QStringLiteral( "topology.layer" );
518 }
519 else if ( i == SctPcPatch )
520 {
521 if ( !hasPointcloud() )
522 continue;
523
524 tableName = QStringLiteral( "l.\"table\"" );
525 schemaName = QStringLiteral( "l.\"schema\"" );
526 columnName = QStringLiteral( "l.\"column\"" );
527 typeName = QStringLiteral( "'POLYGON'" );
528 sridName = QStringLiteral( "l.srid" );
529 dimName = QStringLiteral( "2" );
530 gtableName = QStringLiteral( "pointcloud_columns" );
531 }
532 else if ( i == SctRaster )
533 {
534 if ( !hasRaster() )
535 continue;
536
537 tableName = QStringLiteral( "l.\"r_table_name\"" );
538 schemaName = QStringLiteral( "l.\"r_table_schema\"" );
539 columnName = QStringLiteral( "l.\"r_raster_column\"" );
540 typeName = QStringLiteral( "'RASTER'" );
541 sridName = QStringLiteral( "l.srid" );
542 dimName = QStringLiteral( "2" );
543 gtableName = QStringLiteral( "raster_columns" );
544 }
545 else
546 {
547 QgsMessageLog::logMessage( tr( "Unsupported spatial column type %1" )
548 .arg( displayStringForGeomType( ( QgsPostgresGeometryColumnType )i ) ) );
549 continue;
550 }
551
552 // The following query returns only tables that exist and the user has SELECT privilege on.
553 // Can't use regclass here because table must exist, else error occurs.
554 sql = QString( "SELECT %1,%2,%3,%4,%5,%6,c.relkind,obj_description(c.oid),"
555 "%10, "
556 "count(CASE WHEN t.typname IN (%9) THEN 1 ELSE NULL END) "
557 ", %8 "
558 " FROM %7 l,pg_class c,pg_namespace n,pg_attribute a,pg_type t"
559 " WHERE c.relname=%1"
560 " AND %2=n.nspname"
561 " AND NOT a.attisdropped"
562 " AND a.attrelid=c.oid"
563 " AND a.atttypid=t.oid"
564 " AND a.attnum>0"
565 " AND n.oid=c.relnamespace"
566 " AND has_schema_privilege(n.nspname,'usage')"
567 " AND has_table_privilege(c.oid,'select')" // user has select privilege
568 )
569 .arg( tableName, schemaName, columnName, typeName, sridName, dimName, gtableName )
570 .arg( i )
571 .arg( supportedSpatialTypes().join( ',' ) )
572 .arg( mPostgresqlVersion >= 90000 ? "array_agg(a.attname ORDER BY a.attnum)" : "(SELECT array_agg(attname) FROM (SELECT unnest(array_agg(a.attname)) AS attname ORDER BY unnest(array_agg(a.attnum))) AS attname)" )
573 ;
574
575 if ( searchPublicOnly )
576 sql += QLatin1String( " AND n.nspname='public'" );
577
578 if ( !schema.isEmpty() )
579 sql += QStringLiteral( " AND %1='%2'" ).arg( schemaName, schema );
580
581 sql += QString( " GROUP BY 1,2,3,4,5,6,7,c.oid,11" );
582
583 foundInTables |= 1 << i;
584
585 if ( ! query.isEmpty() )
586 query += " UNION ";
587
588 query += sql;
589 }
590
591 query += QLatin1String( " ORDER BY 2,1,3" );
592
593
594 QgsDebugMsgLevel( "getting table info from layer registries: " + query, 2 );
595 result = PQexec( query, true );
596 // NOTE: we intentionally continue if the query fails
597 // (for example because PostGIS is not installed)
598 for ( int idx = 0; idx < result.PQntuples(); idx++ )
599 {
600 QString tableName = result.PQgetvalue( idx, 0 );
601 QString schemaName = result.PQgetvalue( idx, 1 );
602 QString column = result.PQgetvalue( idx, 2 );
603 QString type = result.PQgetvalue( idx, 3 );
604 QString ssrid = result.PQgetvalue( idx, 4 );
605 int dim = result.PQgetvalue( idx, 5 ).toInt();
606 QString relkind = result.PQgetvalue( idx, 6 );
607 bool isView = relkind == QLatin1String( "v" ) || relkind == QLatin1String( "m" );
608 bool isMaterializedView = relkind == QLatin1String( "m" );
609 bool isForeignTable = relkind == QLatin1String( "f" );
610 bool isRaster = type == QLatin1String( "RASTER" );
611 QString comment = result.PQgetvalue( idx, 7 );
612 QString attributes = result.PQgetvalue( idx, 8 );
613 int nSpCols = result.PQgetvalue( idx, 9 ).toInt();
614 QgsPostgresGeometryColumnType columnType = SctNone;
615
616 int columnTypeInt = result.PQgetvalue( idx, 10 ).toInt();
617 if ( columnTypeInt == SctGeometry )
618 columnType = SctGeometry;
619 else if ( columnTypeInt == SctGeography )
620 columnType = SctGeography;
621 else if ( columnTypeInt == SctTopoGeometry )
622 columnType = SctTopoGeometry;
623 else if ( columnTypeInt == SctPcPatch )
624 columnType = SctPcPatch;
625 else if ( columnTypeInt == SctRaster )
626 columnType = SctRaster;
627 else
628 {
629 QgsDebugMsg( QStringLiteral( "Unhandled columnType index %1" )
630 . arg( columnTypeInt ) );
631 }
632
633 int srid = ssrid.isEmpty() ? std::numeric_limits<int>::min() : ssrid.toInt();
634
635 if ( ! isRaster && majorVersion() >= 2 && srid == 0 )
636 {
637 // 0 doesn't constraint => detect
638 srid = std::numeric_limits<int>::min();
639 }
640
641 #if 0
642 QgsDebugMsgLevel( QStringLiteral( "%1 : %2.%3.%4: %5 %6 %7 %8" )
643 .arg( gtableName )
644 .arg( schemaName ).arg( tableName ).arg( column )
645 .arg( type )
646 .arg( srid )
647 .arg( relkind )
648 .arg( dim ), 2 );
649 #endif
650
651 QgsPostgresLayerProperty layerProperty;
652 layerProperty.schemaName = schemaName;
653 layerProperty.tableName = tableName;
654 layerProperty.geometryColName = column;
655 layerProperty.geometryColType = columnType;
656 if ( dim == 3 && !type.endsWith( 'M' ) )
657 type += QLatin1Char( 'Z' );
658 else if ( dim == 4 )
659 type += QLatin1String( "ZM" );
660 layerProperty.types = QList<QgsWkbTypes::Type>() << ( QgsPostgresConn::wkbTypeFromPostgis( type ) );
661 layerProperty.srids = QList<int>() << srid;
662 layerProperty.sql.clear();
663 layerProperty.relKind = relkind;
664 layerProperty.isView = isView;
665 layerProperty.isForeignTable = isForeignTable;
666 layerProperty.isRaster = isRaster;
667 layerProperty.isMaterializedView = isMaterializedView;
668 layerProperty.tableComment = comment;
669 layerProperty.nSpCols = nSpCols;
670 if ( isView || isForeignTable )
671 {
672 // TODO: use std::transform
673 for ( const auto &a : QgsPostgresStringUtils::parseArray( attributes ) )
674 {
675 layerProperty.pkCols << a.toString();
676 }
677 }
678
679 if ( isView && layerProperty.pkCols.empty() )
680 {
681 //QgsDebugMsgLevel( QStringLiteral( "no key columns found." ), 2 );
682 continue;
683 }
684
685 mLayersSupported << layerProperty;
686 nColumns++;
687 }
688
689 //search for geometry columns in tables that are not in the geometry_columns metatable
690 if ( !searchGeometryColumnsOnly )
691 {
692 // Now have a look for spatial columns that aren't in the geometry_columns table.
693 QString sql = QStringLiteral( "SELECT"
694 " c.relname"
695 ",n.nspname"
696 ",a.attname"
697 ",c.relkind"
698 ",CASE WHEN t.typname IN (%1) THEN t.typname ELSE b.typname END AS coltype"
699 ",obj_description(c.oid)"
700 " FROM pg_attribute a"
701 " JOIN pg_class c ON c.oid=a.attrelid"
702 " JOIN pg_namespace n ON n.oid=c.relnamespace"
703 " JOIN pg_type t ON t.oid=a.atttypid"
704 " LEFT JOIN pg_type b ON b.oid=t.typbasetype"
705 " WHERE c.relkind IN ('v','r','m','p','f')"
706 " AND has_schema_privilege( n.nspname, 'usage' )"
707 " AND has_table_privilege( c.oid, 'select' )"
708 " AND (t.typname IN (%1) OR b.typname IN (%1))" )
709 .arg( supportedSpatialTypes().join( ',' ) );
710
711 // user has select privilege
712 if ( searchPublicOnly )
713 sql += QLatin1String( " AND n.nspname='public'" );
714
715 if ( !schema.isEmpty() )
716 sql += QStringLiteral( " AND n.nspname='%2'" ).arg( schema );
717
718 // skip columns of which we already derived information from the metadata tables
719 if ( nColumns > 0 )
720 {
721 if ( foundInTables & ( 1 << SctGeometry ) )
722 {
723 sql += QLatin1String( " AND NOT EXISTS (SELECT 1 FROM geometry_columns WHERE n.nspname=f_table_schema AND c.relname=f_table_name AND a.attname=f_geometry_column)" );
724 }
725
726 if ( foundInTables & ( 1 << SctGeography ) )
727 {
728 sql += QLatin1String( " AND NOT EXISTS (SELECT 1 FROM geography_columns WHERE n.nspname=f_table_schema AND c.relname=f_table_name AND a.attname=f_geography_column)" );
729 }
730
731 if ( foundInTables & ( 1 << SctPcPatch ) )
732 {
733 sql += QLatin1String( " AND NOT EXISTS (SELECT 1 FROM pointcloud_columns WHERE n.nspname=\"schema\" AND c.relname=\"table\" AND a.attname=\"column\")" );
734 }
735
736 if ( foundInTables & ( 1 << SctRaster ) )
737 {
738 sql += QLatin1String( " AND NOT EXISTS (SELECT 1 FROM raster_columns WHERE n.nspname=\"r_table_schema\" AND c.relname=\"r_table_name\" AND a.attname=\"r_raster_column\")" );
739 }
740
741 if ( foundInTables & ( 1 << SctTopoGeometry ) )
742 {
743 sql += QLatin1String( " AND NOT EXISTS (SELECT 1 FROM topology.layer WHERE n.nspname=\"schema_name\" AND c.relname=\"table_name\" AND a.attname=\"feature_column\")" );
744 }
745 }
746
747 QgsDebugMsgLevel( "getting spatial table info from pg_catalog: " + sql, 2 );
748
749 result = PQexec( sql );
750
751 if ( result.PQresultStatus() != PGRES_TUPLES_OK )
752 {
753 QgsMessageLog::logMessage( tr( "Database connection was successful, but the accessible tables could not be determined. The error message from the database was:\n%1\n" )
754 .arg( result.PQresultErrorMessage() ),
755 tr( "PostGIS" ) );
756 PQexecNR( QStringLiteral( "COMMIT" ) );
757 return false;
758 }
759
760 for ( int i = 0; i < result.PQntuples(); i++ )
761 {
762 // Have the column name, schema name and the table name. The concept of a
763 // catalog doesn't exist in PostgreSQL so we ignore that, but we
764 // do need to get the geometry type.
765
766 QString tableName = result.PQgetvalue( i, 0 ); // relname
767 QString schemaName = result.PQgetvalue( i, 1 ); // nspname
768 QString column = result.PQgetvalue( i, 2 ); // attname
769 QString relkind = result.PQgetvalue( i, 3 ); // relation kind
770 QString coltype = result.PQgetvalue( i, 4 ); // column type
771 bool isView = relkind == QLatin1String( "v" ) || relkind == QLatin1String( "m" );
772 bool isMaterializedView = relkind == QLatin1String( "m" );
773 bool isForeignTable = relkind == QLatin1String( "f" );
774 QString comment = result.PQgetvalue( i, 5 ); // table comment
775
776 QgsPostgresLayerProperty layerProperty;
777 layerProperty.types = QList<QgsWkbTypes::Type>() << QgsWkbTypes::Unknown;
778 layerProperty.srids = QList<int>() << std::numeric_limits<int>::min();
779 layerProperty.schemaName = schemaName;
780 layerProperty.tableName = tableName;
781 layerProperty.geometryColName = column;
782 layerProperty.relKind = relkind;
783 layerProperty.isView = isView;
784 layerProperty.isForeignTable = isForeignTable;
785 layerProperty.isRaster = coltype == QLatin1String( "raster" );
786 layerProperty.isMaterializedView = isMaterializedView;
787 layerProperty.tableComment = comment;
788 if ( coltype == QLatin1String( "geometry" ) )
789 {
790 layerProperty.geometryColType = SctGeometry;
791 }
792 else if ( coltype == QLatin1String( "geography" ) )
793 {
794 layerProperty.geometryColType = SctGeography;
795 }
796 else if ( coltype == QLatin1String( "topogeometry" ) )
797 {
798 layerProperty.geometryColType = SctTopoGeometry;
799 }
800 else if ( coltype == QLatin1String( "pcpatch" ) ||
801 coltype == QLatin1String( "pcpoint" ) )
802 {
803 layerProperty.geometryColType = SctPcPatch;
804 }
805 else if ( coltype == QLatin1String( "raster" ) )
806 {
807 layerProperty.geometryColType = SctRaster;
808 }
809 else
810 {
811 Q_ASSERT( !"Unknown geometry type" );
812 }
813
814 // TODO: use knowledge from already executed query to count
815 // spatial fields and list attribute names...
816 addColumnInfo( layerProperty, schemaName, tableName, isView || isForeignTable );
817
818 if ( isView && layerProperty.pkCols.empty() )
819 {
820 //QgsDebugMsgLevel( QStringLiteral( "no key columns found." ), 2 );
821 continue;
822 }
823
824 mLayersSupported << layerProperty;
825 nColumns++;
826 }
827 }
828
829 if ( allowGeometrylessTables )
830 {
831 QString sql = QStringLiteral( "SELECT "
832 "pg_class.relname"
833 ",pg_namespace.nspname"
834 ",pg_class.relkind"
835 ",obj_description(pg_class.oid)"
836 ",%1"
837 " FROM "
838 " pg_class"
839 ",pg_namespace"
840 ",pg_attribute a"
841 " WHERE pg_namespace.oid=pg_class.relnamespace"
842 " AND has_schema_privilege(pg_namespace.nspname,'usage')"
843 " AND has_table_privilege(pg_class.oid,'select')"
844 " AND pg_class.relkind IN ('v','r','m','p','f')"
845 " AND pg_class.oid = a.attrelid"
846 " AND NOT a.attisdropped"
847 " AND a.attnum > 0" )
848 .arg( mPostgresqlVersion >= 90000 ? "array_agg(a.attname ORDER BY a.attnum)" : "(SELECT array_agg(attname) FROM (SELECT unnest(array_agg(a.attname)) AS attname ORDER BY unnest(array_agg(a.attnum))) AS attname)" );
849
850 // user has select privilege
851 if ( searchPublicOnly )
852 sql += QLatin1String( " AND pg_namespace.nspname='public'" );
853
854 if ( !schema.isEmpty() )
855 sql += QStringLiteral( " AND pg_namespace.nspname='%2'" ).arg( schema );
856
857 sql += QLatin1String( " GROUP BY 1,2,3,4" );
858
859 QgsDebugMsgLevel( "getting non-spatial table info: " + sql, 2 );
860
861 result = PQexec( sql );
862
863 if ( result.PQresultStatus() != PGRES_TUPLES_OK )
864 {
865 QgsMessageLog::logMessage( tr( "Database connection was successful, but the accessible tables could not be determined.\nThe error message from the database was:\n%1" )
866 .arg( result.PQresultErrorMessage() ),
867 tr( "PostGIS" ) );
868 return false;
869 }
870
871 for ( int i = 0; i < result.PQntuples(); i++ )
872 {
873 QString table = result.PQgetvalue( i, 0 ); // relname
874 QString schema = result.PQgetvalue( i, 1 ); // nspname
875 QString relkind = result.PQgetvalue( i, 2 ); // relation kind
876 QString comment = result.PQgetvalue( i, 3 ); // table comment
877 QString attributes = result.PQgetvalue( i, 4 ); // attributes array
878 bool isView = relkind == QLatin1String( "v" ) || relkind == QLatin1String( "m" );
879 bool isMaterializedView = relkind == QLatin1String( "m" );
880 bool isForeignTable = relkind == QLatin1String( "f" );
881
882 QgsPostgresLayerProperty layerProperty;
883 layerProperty.types = QList<QgsWkbTypes::Type>() << QgsWkbTypes::NoGeometry;
884 layerProperty.srids = QList<int>() << std::numeric_limits<int>::min();
885 layerProperty.schemaName = schema;
886 layerProperty.tableName = table;
887 layerProperty.geometryColName = QString();
888 layerProperty.geometryColType = SctNone;
889 layerProperty.nSpCols = 0;
890 layerProperty.relKind = relkind;
891 layerProperty.isView = isView;
892 layerProperty.isForeignTable = isForeignTable;
893 layerProperty.isRaster = false;
894 layerProperty.isMaterializedView = isMaterializedView;
895 layerProperty.tableComment = comment;
896
897 //check if we've already added this layer in some form
898 bool alreadyFound = false;
899 const auto constMLayersSupported = mLayersSupported;
900 for ( const QgsPostgresLayerProperty &foundLayer : constMLayersSupported )
901 {
902 if ( foundLayer.schemaName == schema && foundLayer.tableName == table )
903 {
904 //already found this table
905 alreadyFound = true;
906 break;
907 }
908 }
909 if ( alreadyFound )
910 continue;
911
912 if ( isView || isForeignTable )
913 {
914 // TODO: use std::transform
915 for ( const auto &a : QgsPostgresStringUtils::parseArray( attributes ) )
916 {
917 layerProperty.pkCols << a.toString();
918 }
919 }
920
921 mLayersSupported << layerProperty;
922 nColumns++;
923 }
924 }
925
926 if ( nColumns == 0 && schema.isEmpty() )
927 {
928 QgsMessageLog::logMessage( tr( "Database connection was successful, but the accessible tables could not be determined." ), tr( "PostGIS" ) );
929 }
930
931 return true;
932 }
933
supportedLayers(QVector<QgsPostgresLayerProperty> & layers,bool searchGeometryColumnsOnly,bool searchPublicOnly,bool allowGeometrylessTables,const QString & schema)934 bool QgsPostgresConn::supportedLayers( QVector<QgsPostgresLayerProperty> &layers, bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema )
935 {
936 QMutexLocker locker( &mLock );
937
938 // Get the list of supported tables
939 if ( !getTableInfo( searchGeometryColumnsOnly, searchPublicOnly, allowGeometrylessTables, schema ) )
940 {
941 QgsMessageLog::logMessage( tr( "Unable to get list of spatially enabled tables from the database" ), tr( "PostGIS" ) );
942 return false;
943 }
944
945 layers = mLayersSupported;
946
947 return true;
948 }
949
getSchemas(QList<QgsPostgresSchemaProperty> & schemas)950 bool QgsPostgresConn::getSchemas( QList<QgsPostgresSchemaProperty> &schemas )
951 {
952 schemas.clear();
953 QgsPostgresResult result;
954
955 QString sql = QStringLiteral( "SELECT nspname, pg_get_userbyid(nspowner), pg_catalog.obj_description(oid) FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema' ORDER BY nspname" );
956
957 result = PQexec( sql, true );
958 if ( result.PQresultStatus() != PGRES_TUPLES_OK )
959 {
960 PQexecNR( QStringLiteral( "COMMIT" ) );
961 return false;
962 }
963
964 for ( int idx = 0; idx < result.PQntuples(); idx++ )
965 {
966 QgsPostgresSchemaProperty schema;
967 schema.name = result.PQgetvalue( idx, 0 );
968 schema.owner = result.PQgetvalue( idx, 1 );
969 schema.description = result.PQgetvalue( idx, 2 );
970 schemas << schema;
971 }
972 return true;
973 }
974
975 /**
976 * Check to see if GEOS is available
977 */
hasGEOS() const978 bool QgsPostgresConn::hasGEOS() const
979 {
980 // make sure info is up to date for the current connection
981 postgisVersion();
982 return mGeosAvailable;
983 }
984
985 /**
986 * Check to see if topology is available
987 */
hasTopology() const988 bool QgsPostgresConn::hasTopology() const
989 {
990 // make sure info is up to date for the current connection
991 postgisVersion();
992 return mTopologyAvailable;
993 }
994
995 /**
996 * Check to see if pointcloud is available
997 */
hasPointcloud() const998 bool QgsPostgresConn::hasPointcloud() const
999 {
1000 // make sure info is up to date for the current connection
1001 postgisVersion();
1002 return mPointcloudAvailable;
1003 }
1004
1005 /**
1006 * Check to see if raster is available
1007 */
hasRaster() const1008 bool QgsPostgresConn::hasRaster() const
1009 {
1010 // make sure info is up to date for the current connection
1011 postgisVersion();
1012 return mRasterAvailable;
1013 }
1014 /* Functions for determining available features in postGIS */
postgisVersion() const1015 QString QgsPostgresConn::postgisVersion() const
1016 {
1017 QMutexLocker locker( &mLock );
1018 if ( mGotPostgisVersion )
1019 return mPostgisVersionInfo;
1020
1021 mPostgresqlVersion = PQserverVersion( mConn );
1022
1023 QgsPostgresResult result( PQexec( QStringLiteral( "SELECT postgis_version()" ), false ) );
1024 if ( result.PQntuples() != 1 )
1025 {
1026 QgsMessageLog::logMessage( tr( "No PostGIS support in the database." ), tr( "PostGIS" ) );
1027 mGotPostgisVersion = true;
1028 return QString();
1029 }
1030
1031 mPostgisVersionInfo = result.PQgetvalue( 0, 0 );
1032
1033 QgsDebugMsgLevel( "PostGIS version info: " + mPostgisVersionInfo, 2 );
1034
1035 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
1036 QStringList postgisParts = mPostgisVersionInfo.split( ' ', QString::SkipEmptyParts );
1037
1038 // Get major and minor version
1039 QStringList postgisVersionParts = postgisParts[0].split( '.', QString::SkipEmptyParts );
1040 #else
1041 QStringList postgisParts = mPostgisVersionInfo.split( ' ', Qt::SkipEmptyParts );
1042
1043 // Get major and minor version
1044 QStringList postgisVersionParts = postgisParts[0].split( '.', Qt::SkipEmptyParts );
1045 #endif
1046 if ( postgisVersionParts.size() < 2 )
1047 {
1048 QgsMessageLog::logMessage( tr( "Could not parse postgis version string '%1'" ).arg( mPostgisVersionInfo ), tr( "PostGIS" ) );
1049 return QString();
1050 }
1051
1052 mPostgisVersionMajor = postgisVersionParts[0].toInt();
1053 mPostgisVersionMinor = postgisVersionParts[1].toInt();
1054
1055 mUseWkbHex = mPostgisVersionMajor < 1;
1056
1057 // apparently PostGIS 1.5.2 doesn't report capabilities in postgis_version() anymore
1058 if ( mPostgisVersionMajor > 1 || ( mPostgisVersionMajor == 1 && mPostgisVersionMinor >= 5 ) )
1059 {
1060 result = PQexec( QStringLiteral( "SELECT postgis_geos_version(), postgis_proj_version()" ) );
1061 mGeosAvailable = result.PQntuples() == 1 && !result.PQgetisnull( 0, 0 );
1062 mProjAvailable = result.PQntuples() == 1 && !result.PQgetisnull( 0, 1 );
1063 QgsDebugMsgLevel( QStringLiteral( "geos:%1 proj:%2" )
1064 .arg( mGeosAvailable ? result.PQgetvalue( 0, 0 ) : "none" )
1065 .arg( mProjAvailable ? result.PQgetvalue( 0, 1 ) : "none" ), 2 );
1066 }
1067 else
1068 {
1069 // assume no capabilities
1070 mGeosAvailable = false;
1071
1072 // parse out the capabilities and store them
1073 QStringList geos = postgisParts.filter( QStringLiteral( "GEOS" ) );
1074 if ( geos.size() == 1 )
1075 {
1076 mGeosAvailable = ( geos[0].indexOf( QLatin1String( "=1" ) ) > -1 );
1077 }
1078 }
1079
1080 // checking for topology support
1081 QgsDebugMsgLevel( QStringLiteral( "Checking for topology support" ), 2 );
1082 mTopologyAvailable = false;
1083 if ( mPostgisVersionMajor > 1 )
1084 {
1085 QgsPostgresResult result(
1086 PQexec(
1087 QStringLiteral(
1088 "SELECT has_schema_privilege(n.oid, 'usage')"
1089 " AND has_table_privilege(t.oid, 'select')"
1090 " AND has_table_privilege(l.oid, 'select')"
1091 " FROM pg_namespace n, pg_class t, pg_class l"
1092 " WHERE n.nspname = 'topology'"
1093 " AND t.relnamespace = n.oid"
1094 " AND l.relnamespace = n.oid"
1095 " AND t.relname = 'topology'"
1096 " AND l.relname = 'layer'"
1097 ) ) );
1098 if ( result.PQntuples() >= 1 && result.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) )
1099 {
1100 mTopologyAvailable = true;
1101 }
1102 }
1103
1104 if ( mTopologyAvailable )
1105 {
1106 QgsDebugMsgLevel( QStringLiteral( "Topology support available :)" ), 2 );
1107 }
1108 else
1109 {
1110 QgsDebugMsgLevel( QStringLiteral( "Topology support not available :(" ), 2 );
1111 }
1112
1113 mGotPostgisVersion = true;
1114
1115 if ( mPostgresqlVersion >= 90000 )
1116 {
1117 QgsDebugMsgLevel( QStringLiteral( "Checking for pointcloud support" ), 2 );
1118 result = PQexec( QStringLiteral(
1119 "SELECT has_table_privilege(c.oid, 'select')"
1120 " AND has_table_privilege(f.oid, 'select')"
1121 " FROM pg_class c, pg_class f, pg_namespace n, pg_extension e"
1122 " WHERE c.relnamespace = n.oid"
1123 " AND c.relname = 'pointcloud_columns'"
1124 " AND f.relnamespace = n.oid"
1125 " AND f.relname = 'pointcloud_formats'"
1126 " AND n.oid = e.extnamespace"
1127 " AND e.extname = 'pointcloud'"
1128 ), false );
1129 if ( result.PQntuples() >= 1 && result.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) )
1130 {
1131 mPointcloudAvailable = true;
1132 QgsDebugMsgLevel( QStringLiteral( "Pointcloud support available!" ), 2 );
1133 }
1134 }
1135
1136 QgsDebugMsgLevel( QStringLiteral( "Checking for raster support" ), 2 );
1137 if ( mPostgisVersionMajor >= 2 )
1138 {
1139 result = PQexec( QStringLiteral(
1140 "SELECT has_table_privilege(c.oid, 'select')"
1141 " FROM pg_class c, pg_namespace n, pg_type t"
1142 " WHERE c.relnamespace = n.oid"
1143 " AND n.oid = t.typnamespace"
1144 " AND c.relname = 'raster_columns'"
1145 " AND t.typname = 'raster'"
1146 ), false );
1147 if ( result.PQntuples() >= 1 && result.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) )
1148 {
1149 mRasterAvailable = true;
1150 QgsDebugMsgLevel( QStringLiteral( "Raster support available!" ), 2 );
1151 }
1152 }
1153
1154 return mPostgisVersionInfo;
1155 }
1156
quotedIdentifier(const QString & ident)1157 QString QgsPostgresConn::quotedIdentifier( const QString &ident )
1158 {
1159 QString result = ident;
1160 result.replace( '"', QLatin1String( "\"\"" ) );
1161 return result.prepend( '\"' ).append( '\"' );
1162 }
1163
quotedString(const QString & v)1164 static QString quotedString( const QString &v )
1165 {
1166 QString result = v;
1167 result.replace( '\'', QLatin1String( "''" ) );
1168 if ( result.contains( '\\' ) )
1169 return result.replace( '\\', QLatin1String( "\\\\" ) ).prepend( "E'" ).append( '\'' );
1170 else
1171 return result.prepend( '\'' ).append( '\'' );
1172 }
1173
doubleQuotedMapValue(const QString & v)1174 static QString doubleQuotedMapValue( const QString &v )
1175 {
1176 QString result = v;
1177 return "\"" + result.replace( '\\', QLatin1String( "\\\\\\\\" ) ).replace( '\"', QLatin1String( "\\\\\"" ) ).replace( '\'', QLatin1String( "\\'" ) ) + "\"";
1178 }
1179
quotedMap(const QVariantMap & map)1180 static QString quotedMap( const QVariantMap &map )
1181 {
1182 QString ret;
1183 for ( QVariantMap::const_iterator i = map.constBegin(); i != map.constEnd(); ++i )
1184 {
1185 if ( !ret.isEmpty() )
1186 {
1187 ret += QLatin1Char( ',' );
1188 }
1189 ret.append( doubleQuotedMapValue( i.key() ) + "=>" +
1190 doubleQuotedMapValue( i.value().toString() ) );
1191 }
1192 return "E'" + ret + "'::hstore";
1193 }
1194
quotedList(const QVariantList & list)1195 static QString quotedList( const QVariantList &list )
1196 {
1197 QString ret;
1198 for ( QVariantList::const_iterator i = list.constBegin(); i != list.constEnd(); ++i )
1199 {
1200 if ( !ret.isEmpty() )
1201 {
1202 ret += QLatin1Char( ',' );
1203 }
1204
1205 QString inner = i->toString();
1206 if ( inner.startsWith( '{' ) || i->type() == QVariant::Int || i->type() == QVariant::LongLong )
1207 {
1208 ret.append( inner );
1209 }
1210 else
1211 {
1212 ret.append( doubleQuotedMapValue( i->toString() ) );
1213 }
1214 }
1215 return "E'{" + ret + "}'";
1216 }
1217
quotedValue(const QVariant & value)1218 QString QgsPostgresConn::quotedValue( const QVariant &value )
1219 {
1220 if ( value.isNull() )
1221 return QStringLiteral( "NULL" );
1222
1223 switch ( value.type() )
1224 {
1225 case QVariant::Int:
1226 case QVariant::LongLong:
1227 return value.toString();
1228
1229 case QVariant::DateTime:
1230 return quotedString( value.toDateTime().toString( Qt::ISODateWithMs ) );
1231
1232 case QVariant::Bool:
1233 return value.toBool() ? "TRUE" : "FALSE";
1234
1235 case QVariant::Map:
1236 return quotedMap( value.toMap() );
1237
1238 case QVariant::StringList:
1239 case QVariant::List:
1240 return quotedList( value.toList() );
1241
1242 case QVariant::Double:
1243 case QVariant::String:
1244 default:
1245 return quotedString( value.toString() );
1246 }
1247 }
1248
quotedJsonValue(const QVariant & value)1249 QString QgsPostgresConn::quotedJsonValue( const QVariant &value )
1250 {
1251 if ( value.isNull() || !value.isValid() )
1252 return QStringLiteral( "null" );
1253 // where json is a string literal just construct it from that rather than dump
1254 if ( value.type() == QVariant::String )
1255 {
1256 QString valueStr = value.toString();
1257 if ( valueStr.at( 0 ) == '\"' && valueStr.at( valueStr.size() - 1 ) == '\"' )
1258 {
1259 return quotedString( value.toString() );
1260 }
1261 }
1262 const auto j = QgsJsonUtils::jsonFromVariant( value );
1263 return quotedString( QString::fromStdString( j.dump() ) );
1264 }
1265
PQexec(const QString & query,bool logError,bool retry) const1266 PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool retry ) const
1267 {
1268 QMutexLocker locker( &mLock );
1269
1270 QgsDebugMsgLevel( QStringLiteral( "Executing SQL: %1" ).arg( query ), 3 );
1271
1272 PGresult *res = ::PQexec( mConn, query.toUtf8() );
1273
1274 // libpq may return a non null ptr with conn status not OK so we need to check for it to allow a retry below
1275 if ( res && PQstatus() == CONNECTION_OK )
1276 {
1277 int errorStatus = PQresultStatus( res );
1278 if ( errorStatus != PGRES_COMMAND_OK && errorStatus != PGRES_TUPLES_OK )
1279 {
1280 if ( logError )
1281 {
1282 QgsMessageLog::logMessage( tr( "Erroneous query: %1 returned %2 [%3]" )
1283 .arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ),
1284 tr( "PostGIS" ) );
1285 }
1286 else
1287 {
1288 QgsDebugMsg( QStringLiteral( "Not logged erroneous query: %1 returned %2 [%3]" )
1289 .arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ) );
1290 }
1291 }
1292 return res;
1293 }
1294 if ( PQstatus() != CONNECTION_OK )
1295 {
1296 if ( logError )
1297 {
1298 QgsMessageLog::logMessage( tr( "Connection error: %1 returned %2 [%3]" )
1299 .arg( query ).arg( PQstatus() ).arg( PQerrorMessage() ),
1300 tr( "PostGIS" ) );
1301 }
1302 else
1303 {
1304 QgsDebugMsg( QStringLiteral( "Connection error: %1 returned %2 [%3]" )
1305 .arg( query ).arg( PQstatus() ).arg( PQerrorMessage() ) );
1306 }
1307 }
1308 else
1309 {
1310 if ( logError )
1311 {
1312 QgsMessageLog::logMessage( tr( "Query failed: %1\nError: no result buffer" ).arg( query ), tr( "PostGIS" ) );
1313 }
1314 else
1315 {
1316 QgsDebugMsg( QStringLiteral( "Not logged query failed: %1\nError: no result buffer" ).arg( query ) );
1317 }
1318 }
1319
1320 if ( retry )
1321 {
1322 QgsMessageLog::logMessage( tr( "resetting bad connection." ), tr( "PostGIS" ) );
1323 ::PQreset( mConn );
1324 res = PQexec( query, logError, false );
1325 if ( PQstatus() == CONNECTION_OK )
1326 {
1327 if ( res )
1328 {
1329 QgsMessageLog::logMessage( tr( "retry after reset succeeded." ), tr( "PostGIS" ) );
1330 return res;
1331 }
1332 else
1333 {
1334 QgsMessageLog::logMessage( tr( "retry after reset failed again." ), tr( "PostGIS" ) );
1335 return nullptr;
1336 }
1337 }
1338 else
1339 {
1340 QgsMessageLog::logMessage( tr( "connection still bad after reset." ), tr( "PostGIS" ) );
1341 }
1342 }
1343 else
1344 {
1345 QgsMessageLog::logMessage( tr( "bad connection, not retrying." ), tr( "PostGIS" ) );
1346 }
1347 return nullptr;
1348
1349 }
1350
PQCancel()1351 int QgsPostgresConn::PQCancel()
1352 {
1353 // No locker: this is supposed to be thread safe
1354 int result = 0;
1355 auto cancel = ::PQgetCancel( mConn ) ;
1356 if ( cancel )
1357 {
1358 char errbuf[255];
1359 result = ::PQcancel( cancel, errbuf, 255 );
1360 if ( ! result )
1361 QgsDebugMsgLevel( QStringLiteral( "Error canceling the query:" ).arg( errbuf ), 3 );
1362 }
1363 ::PQfreeCancel( cancel );
1364 return result;
1365 }
1366
openCursor(const QString & cursorName,const QString & sql)1367 bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql )
1368 {
1369 QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
1370 QString preStr;
1371
1372 if ( mOpenCursors++ == 0 && !mTransaction )
1373 {
1374 QgsDebugMsgLevel( QStringLiteral( "Starting read-only transaction: %1" ).arg( mPostgresqlVersion ), 4 );
1375 if ( mPostgresqlVersion >= 80000 )
1376 preStr = QStringLiteral( "BEGIN READ ONLY;" );
1377 else
1378 preStr = QStringLiteral( "BEGIN;" );
1379 }
1380 QgsDebugMsgLevel( QStringLiteral( "Binary cursor %1 for %2" ).arg( cursorName, sql ), 3 );
1381 return PQexecNR( QStringLiteral( "%1DECLARE %2 BINARY CURSOR%3 FOR %4" ).
1382 arg( preStr, cursorName, !mTransaction ? QString() : QStringLiteral( " WITH HOLD" ), sql ) );
1383 }
1384
closeCursor(const QString & cursorName)1385 bool QgsPostgresConn::closeCursor( const QString &cursorName )
1386 {
1387 QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
1388 QString postStr;
1389
1390 if ( --mOpenCursors == 0 && !mTransaction )
1391 {
1392 QgsDebugMsgLevel( QStringLiteral( "Committing read-only transaction" ), 4 );
1393 postStr = QStringLiteral( ";COMMIT" );
1394 }
1395
1396 if ( !PQexecNR( QStringLiteral( "CLOSE %1%2" ).arg( cursorName, postStr ) ) )
1397 return false;
1398
1399 return true;
1400 }
1401
uniqueCursorName()1402 QString QgsPostgresConn::uniqueCursorName()
1403 {
1404 QMutexLocker locker( &mLock ); // to protect access to mNextCursorId
1405 return QStringLiteral( "qgis_%1" ).arg( ++mNextCursorId );
1406 }
1407
PQexecNR(const QString & query)1408 bool QgsPostgresConn::PQexecNR( const QString &query )
1409 {
1410 QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
1411
1412 QgsPostgresResult res( PQexec( query, false ) );
1413
1414 ExecStatusType errorStatus = res.PQresultStatus();
1415 if ( errorStatus == PGRES_COMMAND_OK )
1416 return true;
1417
1418 QgsMessageLog::logMessage( tr( "Query: %1 returned %2 [%3]" )
1419 .arg( query )
1420 .arg( errorStatus )
1421 .arg( res.PQresultErrorMessage() ),
1422 tr( "PostGIS" ) );
1423
1424 if ( mOpenCursors )
1425 {
1426 QgsMessageLog::logMessage( tr( "%1 cursor states lost.\nSQL: %2\nResult: %3 (%4)" )
1427 .arg( mOpenCursors ).arg( query ).arg( errorStatus )
1428 .arg( res.PQresultErrorMessage() ), tr( "PostGIS" ) );
1429 mOpenCursors = 0;
1430 }
1431
1432 if ( PQstatus() == CONNECTION_OK )
1433 {
1434 PQexecNR( QStringLiteral( "ROLLBACK" ) );
1435 }
1436
1437 return false;
1438 }
1439
PQgetResult()1440 PGresult *QgsPostgresConn::PQgetResult()
1441 {
1442 return ::PQgetResult( mConn );
1443 }
1444
PQprepare(const QString & stmtName,const QString & query,int nParams,const Oid * paramTypes)1445 PGresult *QgsPostgresConn::PQprepare( const QString &stmtName, const QString &query, int nParams, const Oid *paramTypes )
1446 {
1447 QMutexLocker locker( &mLock );
1448
1449 return ::PQprepare( mConn, stmtName.toUtf8(), query.toUtf8(), nParams, paramTypes );
1450 }
1451
PQexecPrepared(const QString & stmtName,const QStringList & params)1452 PGresult *QgsPostgresConn::PQexecPrepared( const QString &stmtName, const QStringList ¶ms )
1453 {
1454 QMutexLocker locker( &mLock );
1455
1456 const char **param = new const char *[ params.size()];
1457 QList<QByteArray> qparam;
1458
1459 qparam.reserve( params.size() );
1460 for ( int i = 0; i < params.size(); i++ )
1461 {
1462 qparam << params[i].toUtf8();
1463
1464 if ( params[i].isNull() )
1465 param[i] = nullptr;
1466 else
1467 param[i] = qparam[i];
1468 }
1469
1470 PGresult *res = ::PQexecPrepared( mConn, stmtName.toUtf8(), params.size(), param, nullptr, nullptr, 0 );
1471
1472 delete [] param;
1473
1474 return res;
1475 }
1476
PQfinish()1477 void QgsPostgresConn::PQfinish()
1478 {
1479 QMutexLocker locker( &mLock );
1480
1481 Q_ASSERT( mConn );
1482 ::PQfinish( mConn );
1483 mConn = nullptr;
1484 }
1485
PQstatus() const1486 int QgsPostgresConn::PQstatus() const
1487 {
1488 QMutexLocker locker( &mLock );
1489
1490 Q_ASSERT( mConn );
1491 return ::PQstatus( mConn );
1492 }
1493
PQerrorMessage() const1494 QString QgsPostgresConn::PQerrorMessage() const
1495 {
1496 QMutexLocker locker( &mLock );
1497
1498 Q_ASSERT( mConn );
1499 return QString::fromUtf8( ::PQerrorMessage( mConn ) );
1500 }
1501
PQsendQuery(const QString & query)1502 int QgsPostgresConn::PQsendQuery( const QString &query )
1503 {
1504 QMutexLocker locker( &mLock );
1505 Q_ASSERT( mConn );
1506 return ::PQsendQuery( mConn, query.toUtf8() );
1507 }
1508
begin()1509 bool QgsPostgresConn::begin()
1510 {
1511 QMutexLocker locker( &mLock );
1512 if ( mTransaction )
1513 {
1514 return PQexecNR( QStringLiteral( "SAVEPOINT transaction_savepoint" ) );
1515 }
1516 else
1517 {
1518 return PQexecNR( QStringLiteral( "BEGIN" ) );
1519 }
1520 }
1521
commit()1522 bool QgsPostgresConn::commit()
1523 {
1524 QMutexLocker locker( &mLock );
1525 if ( mTransaction )
1526 {
1527 return PQexecNR( QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) );
1528 }
1529 else
1530 {
1531 return PQexecNR( QStringLiteral( "COMMIT" ) );
1532 }
1533 }
1534
rollback()1535 bool QgsPostgresConn::rollback()
1536 {
1537 QMutexLocker locker( &mLock );
1538 if ( mTransaction )
1539 {
1540 return PQexecNR( QStringLiteral( "ROLLBACK TO SAVEPOINT transaction_savepoint" ) )
1541 && PQexecNR( QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) );
1542 }
1543 else
1544 {
1545 return PQexecNR( QStringLiteral( "ROLLBACK" ) );
1546 }
1547 }
1548
getBinaryInt(QgsPostgresResult & queryResult,int row,int col)1549 qint64 QgsPostgresConn::getBinaryInt( QgsPostgresResult &queryResult, int row, int col )
1550 {
1551 QMutexLocker locker( &mLock );
1552 quint64 oid;
1553 char *p = PQgetvalue( queryResult.result(), row, col );
1554 size_t s = PQgetlength( queryResult.result(), row, col );
1555
1556 #ifdef QGISDEBUG
1557 if ( QgsLogger::debugLevel() >= 4 )
1558 {
1559 QString buf;
1560 for ( size_t i = 0; i < s; i++ )
1561 {
1562 buf += QStringLiteral( "%1 " ).arg( *( unsigned char * )( p + i ), 0, 16, QLatin1Char( ' ' ) );
1563 }
1564
1565 QgsDebugMsgLevel( QStringLiteral( "int in hex:%1" ).arg( buf ), 2 );
1566 }
1567 #endif
1568
1569 switch ( s )
1570 {
1571 case 2:
1572 oid = *( quint16 * )p;
1573 if ( mSwapEndian )
1574 oid = ntohs( oid );
1575 /* cast to signed 16bit
1576 * See https://github.com/qgis/QGIS/issues/22258 */
1577 oid = ( qint16 )oid;
1578 break;
1579
1580 case 6:
1581 {
1582 quint64 block = *( quint32 * ) p;
1583 quint64 offset = *( quint16 * )( p + sizeof( quint32 ) );
1584
1585 if ( mSwapEndian )
1586 {
1587 block = ntohl( block );
1588 offset = ntohs( offset );
1589 }
1590
1591 oid = ( block << 16 ) + offset;
1592 }
1593 break;
1594
1595 case 8:
1596 {
1597 quint32 oid0 = *( quint32 * ) p;
1598 quint32 oid1 = *( quint32 * )( p + sizeof( quint32 ) );
1599
1600 if ( mSwapEndian )
1601 {
1602 QgsDebugMsgLevel( QStringLiteral( "swap oid0:%1 oid1:%2" ).arg( oid0 ).arg( oid1 ), 4 );
1603 oid0 = ntohl( oid0 );
1604 oid1 = ntohl( oid1 );
1605 }
1606
1607 QgsDebugMsgLevel( QStringLiteral( "oid0:%1 oid1:%2" ).arg( oid0 ).arg( oid1 ), 4 );
1608 oid = oid0;
1609 QgsDebugMsgLevel( QStringLiteral( "oid:%1" ).arg( oid ), 4 );
1610 oid <<= 32;
1611 QgsDebugMsgLevel( QStringLiteral( "oid:%1" ).arg( oid ), 4 );
1612 oid |= oid1;
1613 QgsDebugMsgLevel( QStringLiteral( "oid:%1" ).arg( oid ), 4 );
1614 }
1615 break;
1616
1617 default:
1618 QgsDebugMsg( QStringLiteral( "unexpected size %1" ).arg( s ) );
1619 //intentional fall-through
1620 FALLTHROUGH
1621 case 4:
1622 oid = *( quint32 * )p;
1623 if ( mSwapEndian )
1624 oid = ntohl( oid );
1625 /* cast to signed 32bit
1626 * See https://github.com/qgis/QGIS/issues/22258 */
1627 oid = ( qint32 )oid;
1628 break;
1629 }
1630
1631 return oid;
1632 }
1633
fieldExpressionForWhereClause(const QgsField & fld,QVariant::Type valueType,QString expr)1634 QString QgsPostgresConn::fieldExpressionForWhereClause( const QgsField &fld, QVariant::Type valueType, QString expr )
1635 {
1636 QString out;
1637 const QString &type = fld.typeName();
1638
1639 if ( type == QLatin1String( "timestamp" ) || type == QLatin1String( "time" ) || type == QLatin1String( "date" ) )
1640 {
1641 out = expr.arg( quotedIdentifier( fld.name() ) );
1642 // if field and value havev incompatible types, rollback to text cast
1643 if ( valueType != QVariant::LastType && valueType != QVariant::DateTime && valueType != QVariant::Date && valueType != QVariant::Time )
1644 {
1645 out = out + "::text";
1646 }
1647 }
1648
1649 else if ( type == QLatin1String( "int8" ) || type == QLatin1String( "serial8" ) //
1650 || type == QLatin1String( "int2" ) || type == QLatin1String( "int4" ) || type == QLatin1String( "oid" ) || type == QLatin1String( "serial" ) //
1651 || type == QLatin1String( "real" ) || type == QLatin1String( "double precision" ) || type == QLatin1String( "float4" ) || type == QLatin1String( "float8" ) //
1652 || type == QLatin1String( "numeric" ) )
1653 {
1654 out = expr.arg( quotedIdentifier( fld.name() ) );
1655 // if field and value havev incompatible types, rollback to text cast
1656 if ( valueType != QVariant::LastType && valueType != QVariant::Int && valueType != QVariant::LongLong && valueType != QVariant::Double )
1657 {
1658 out = out + "::text";
1659 }
1660 }
1661
1662 else
1663 {
1664 out = fieldExpression( fld, expr ); // same as fieldExpression by default
1665 }
1666
1667 return out;
1668 }
1669
fieldExpression(const QgsField & fld,QString expr)1670 QString QgsPostgresConn::fieldExpression( const QgsField &fld, QString expr )
1671 {
1672 const QString &type = fld.typeName();
1673 expr = expr.arg( quotedIdentifier( fld.name() ) );
1674 if ( type == QLatin1String( "money" ) )
1675 {
1676 return QStringLiteral( "cash_out(%1)::text" ).arg( expr );
1677 }
1678 else if ( type.startsWith( '_' ) )
1679 {
1680 //TODO: add native support for arrays
1681 return QStringLiteral( "array_out(%1)::text" ).arg( expr );
1682 }
1683 else if ( type == QLatin1String( "bool" ) )
1684 {
1685 return QStringLiteral( "boolout(%1)::text" ).arg( expr );
1686 }
1687 else if ( type == QLatin1String( "geometry" ) )
1688 {
1689 return QStringLiteral( "%1(%2)" )
1690 .arg( majorVersion() < 2 ? "asewkt" : "st_asewkt",
1691 expr );
1692 }
1693 else if ( type == QLatin1String( "geography" ) )
1694 {
1695 return QStringLiteral( "st_astext(%1)" ).arg( expr );
1696 }
1697 else if ( type == QLatin1String( "int8" ) )
1698 {
1699 return expr;
1700 }
1701 //TODO: add support for hstore
1702 //TODO: add support for json/jsonb
1703 else
1704 {
1705 return expr + "::text";
1706 }
1707 }
1708
nativeTypes()1709 QList<QgsVectorDataProvider::NativeType> QgsPostgresConn::nativeTypes()
1710 {
1711 QList<QgsVectorDataProvider::NativeType> types;
1712
1713 types // integer types
1714 << QgsVectorDataProvider::NativeType( tr( "Whole number (smallint - 16bit)" ), QStringLiteral( "int2" ), QVariant::Int, -1, -1, 0, 0 )
1715 << QgsVectorDataProvider::NativeType( tr( "Whole number (integer - 32bit)" ), QStringLiteral( "int4" ), QVariant::Int, -1, -1, 0, 0 )
1716 << QgsVectorDataProvider::NativeType( tr( "Whole number (integer - 64bit)" ), QStringLiteral( "int8" ), QVariant::LongLong, -1, -1, 0, 0 )
1717 << QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), QStringLiteral( "numeric" ), QVariant::Double, 1, 20, 0, 20 )
1718 << QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), QStringLiteral( "decimal" ), QVariant::Double, 1, 20, 0, 20 )
1719
1720 // floating point
1721 << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "real" ), QVariant::Double, -1, -1, -1, -1 )
1722 << QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), QStringLiteral( "double precision" ), QVariant::Double, -1, -1, -1, -1 )
1723
1724 // string types
1725 << QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), QStringLiteral( "char" ), QVariant::String, 1, 255, -1, -1 )
1726 << QgsVectorDataProvider::NativeType( tr( "Text, limited variable length (varchar)" ), QStringLiteral( "varchar" ), QVariant::String, 1, 255, -1, -1 )
1727 << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (text)" ), QStringLiteral( "text" ), QVariant::String, -1, -1, -1, -1 )
1728 << QgsVectorDataProvider::NativeType( tr( "Text, case-insensitive unlimited length (citext)" ), QStringLiteral( "citext" ), QVariant::String, -1, -1, -1, -1 )
1729
1730 // date type
1731 << QgsVectorDataProvider::NativeType( tr( "Date" ), QStringLiteral( "date" ), QVariant::Date, -1, -1, -1, -1 )
1732 << QgsVectorDataProvider::NativeType( tr( "Time" ), QStringLiteral( "time" ), QVariant::Time, -1, -1, -1, -1 )
1733 << QgsVectorDataProvider::NativeType( tr( "Date & Time" ), QStringLiteral( "timestamp without time zone" ), QVariant::DateTime, -1, -1, -1, -1 )
1734
1735 // complex types
1736 << QgsVectorDataProvider::NativeType( tr( "Map (hstore)" ), QStringLiteral( "hstore" ), QVariant::Map, -1, -1, -1, -1, QVariant::String )
1737 << QgsVectorDataProvider::NativeType( tr( "Array of number (integer - 32bit)" ), QStringLiteral( "int4[]" ), QVariant::List, -1, -1, -1, -1, QVariant::Int )
1738 << QgsVectorDataProvider::NativeType( tr( "Array of number (integer - 64bit)" ), QStringLiteral( "int8[]" ), QVariant::List, -1, -1, -1, -1, QVariant::LongLong )
1739 << QgsVectorDataProvider::NativeType( tr( "Array of number (double)" ), QStringLiteral( "double precision[]" ), QVariant::List, -1, -1, -1, -1, QVariant::Double )
1740 << QgsVectorDataProvider::NativeType( tr( "Array of text" ), QStringLiteral( "text[]" ), QVariant::StringList, -1, -1, -1, -1, QVariant::String )
1741
1742 // boolean
1743 << QgsVectorDataProvider::NativeType( tr( "Boolean" ), QStringLiteral( "bool" ), QVariant::Bool, -1, -1, -1, -1 )
1744
1745 // binary (bytea)
1746 << QgsVectorDataProvider::NativeType( tr( "Binary object (bytea)" ), QStringLiteral( "bytea" ), QVariant::ByteArray, -1, -1, -1, -1 )
1747 ;
1748
1749 if ( pgVersion() >= 90200 )
1750 {
1751 types << QgsVectorDataProvider::NativeType( tr( "JSON (json)" ), QStringLiteral( "json" ), QVariant::Map, -1, -1, -1, -1, QVariant::String );
1752
1753 if ( pgVersion() >= 90400 )
1754 {
1755 types << QgsVectorDataProvider::NativeType( tr( "JSON (jsonb)" ), QStringLiteral( "jsonb" ), QVariant::Map, -1, -1, -1, -1, QVariant::String );
1756 }
1757 }
1758 return types;
1759 }
1760
deduceEndian()1761 void QgsPostgresConn::deduceEndian()
1762 {
1763 QMutexLocker locker( &mLock );
1764 // need to store the PostgreSQL endian format used in binary cursors
1765 // since it appears that starting with
1766 // version 7.4, binary cursors return data in XDR whereas previous versions
1767 // return data in the endian of the server
1768
1769 QgsPostgresResult resOID;
1770 int queryCounter = 0;
1771 int errorCounter = 0;
1772 int oidStatus = 0;
1773 int oidSelectSet = 1 << 0;
1774 int oidBinaryCursorSet = 1 << 1;
1775 qint64 oidSelect = 0;
1776 qint64 oidBinaryCursor = 0;
1777
1778 if ( 0 == PQsendQuery( QStringLiteral(
1779 "SELECT regclass('pg_class')::oid AS oidselect;"
1780 "BEGIN;"
1781 "DECLARE oidcursor BINARY CURSOR FOR SELECT regclass('pg_class')::oid AS oidbinarycursor;"
1782 "FETCH FORWARD 1 FROM oidcursor;"
1783 "CLOSE oidcursor;"
1784 "COMMIT;" ) ) )
1785 QgsDebugMsgLevel( QStringLiteral( "PQsendQuery(...) error %1" ).arg( PQerrorMessage() ), 2 );
1786
1787 for ( ;; )
1788 {
1789 // PQgetResult() must be called repeatedly until it returns a null pointer
1790 resOID = PQgetResult();
1791
1792 if ( resOID.result() == nullptr )
1793 break;
1794
1795 queryCounter++;
1796 if ( resOID.PQresultStatus() == PGRES_FATAL_ERROR )
1797 {
1798 errorCounter++;
1799 QgsDebugMsgLevel( QStringLiteral( "QUERY #%1 PGRES_FATAL_ERROR %2" )
1800 .arg( queryCounter )
1801 .arg( PQerrorMessage().trimmed() ), 2 );
1802 continue;
1803 }
1804
1805 if ( resOID.PQresultStatus() == PGRES_TUPLES_OK && resOID.PQnfields() && resOID.PQntuples() )
1806 {
1807 if ( resOID.PQfname( 0 ) == QLatin1String( "oidselect" ) )
1808 {
1809 oidSelect = resOID.PQgetvalue( 0, 0 ).toLongLong();
1810 oidStatus |= oidSelectSet;
1811 }
1812 if ( resOID.PQfname( 0 ) == QLatin1String( "oidbinarycursor" ) )
1813 {
1814 oidBinaryCursor = getBinaryInt( resOID, 0, 0 );
1815 oidStatus |= oidBinaryCursorSet;
1816 }
1817 }
1818 }
1819
1820 if ( errorCounter == 0 && oidStatus == ( oidSelectSet | oidBinaryCursorSet ) )
1821 {
1822 mSwapEndian = mSwapEndian == ( oidSelect == oidBinaryCursor );
1823 return;
1824 }
1825
1826 QgsDebugMsgLevel( QStringLiteral( "Back to old deduceEndian(): PQstatus() - %1, queryCounter = %2, errorCounter = %3" )
1827 .arg( PQstatus() )
1828 .arg( queryCounter )
1829 .arg( errorCounter ), 2 );
1830
1831 QgsPostgresResult res( PQexec( QStringLiteral( "select regclass('pg_class')::oid" ) ) );
1832 QString oidValue = res.PQgetvalue( 0, 0 );
1833
1834 QgsDebugMsgLevel( QStringLiteral( "Creating binary cursor" ), 2 );
1835
1836 // get the same value using a binary cursor
1837 openCursor( QStringLiteral( "oidcursor" ), QStringLiteral( "select regclass('pg_class')::oid" ) );
1838
1839 QgsDebugMsgLevel( QStringLiteral( "Fetching a record and attempting to get check endian-ness" ), 2 );
1840
1841 res = PQexec( QStringLiteral( "fetch forward 1 from oidcursor" ) );
1842
1843 mSwapEndian = true;
1844 if ( res.PQntuples() > 0 )
1845 {
1846 // get the oid value from the binary cursor
1847 qint64 oid = getBinaryInt( res, 0, 0 );
1848
1849 QgsDebugMsgLevel( QStringLiteral( "Got oid of %1 from the binary cursor" ).arg( oid ), 2 );
1850 QgsDebugMsgLevel( QStringLiteral( "First oid is %1" ).arg( oidValue ), 2 );
1851
1852 // compare the two oid values to determine if we need to do an endian swap
1853 if ( oid != oidValue.toLongLong() )
1854 mSwapEndian = false;
1855 }
1856
1857 closeCursor( QStringLiteral( "oidcursor" ) );
1858 }
1859
retrieveLayerTypes(QgsPostgresLayerProperty & layerProperty,bool useEstimatedMetadata)1860 void QgsPostgresConn::retrieveLayerTypes( QgsPostgresLayerProperty &layerProperty, bool useEstimatedMetadata )
1861 {
1862 QVector<QgsPostgresLayerProperty *> vect;
1863 vect << &layerProperty;
1864 retrieveLayerTypes( vect, useEstimatedMetadata );
1865 }
1866
retrieveLayerTypes(QVector<QgsPostgresLayerProperty * > & layerProperties,bool useEstimatedMetadata)1867 void QgsPostgresConn::retrieveLayerTypes( QVector<QgsPostgresLayerProperty *> &layerProperties, bool useEstimatedMetadata )
1868 {
1869 QString table;
1870 QString query;
1871
1872 // Limit table row scan if useEstimatedMetadata
1873 const QString tableScanLimit { useEstimatedMetadata ? QStringLiteral( " LIMIT %1" ).arg( GEOM_TYPE_SELECT_LIMIT ) : QString() };
1874
1875 int i = 0;
1876 for ( auto *layerPropertyPtr : layerProperties )
1877 {
1878 QgsPostgresLayerProperty &layerProperty = *layerPropertyPtr;
1879
1880 if ( i++ )
1881 query += " UNION ";
1882
1883 if ( !layerProperty.schemaName.isEmpty() )
1884 {
1885 table = QStringLiteral( "%1.%2" )
1886 .arg( quotedIdentifier( layerProperty.schemaName ),
1887 quotedIdentifier( layerProperty.tableName ) );
1888 }
1889 else
1890 {
1891 // Query
1892 table = layerProperty.tableName;
1893 }
1894
1895 if ( layerProperty.geometryColName.isEmpty() )
1896 continue;
1897
1898 if ( layerProperty.isRaster )
1899 {
1900 QString sql;
1901
1902 int srid = layerProperty.srids.value( 0, std::numeric_limits<int>::min() );
1903 // SRID is already known
1904 if ( srid != std::numeric_limits<int>::min() )
1905 {
1906 sql += QStringLiteral( "SELECT %1, array_agg( '%2:RASTER:-1'::text )" )
1907 .arg( i - 1 )
1908 .arg( srid );
1909 }
1910 else
1911 {
1912 if ( useEstimatedMetadata )
1913 {
1914 sql = QStringLiteral( "SELECT %1, "
1915 "array_agg(srid || ':RASTER:-1') "
1916 "FROM raster_columns "
1917 "WHERE r_raster_column = %2 AND r_table_schema = %3 AND r_table_name = %4" )
1918 .arg( i - 1 )
1919 .arg( quotedValue( layerProperty.geometryColName ) )
1920 .arg( quotedValue( layerProperty.schemaName ) )
1921 .arg( quotedValue( layerProperty.tableName ) );
1922 }
1923 else
1924 {
1925 sql = QStringLiteral( "SELECT %1, "
1926 "array_agg(DISTINCT st_srid(%2) || ':RASTER:-1') "
1927 "FROM %3 "
1928 "%2 IS NOT NULL "
1929 "%4" // SQL clause
1930 "%5" )
1931 .arg( i - 1 )
1932 .arg( quotedIdentifier( layerProperty.geometryColName ) )
1933 .arg( table )
1934 .arg( layerProperty.sql.isEmpty() ? QString() : QStringLiteral( " AND %1" ).arg( layerProperty.sql ) )
1935 .arg( tableScanLimit );
1936 }
1937 }
1938
1939 QgsDebugMsgLevel( "Raster srids query: " + sql, 2 );
1940 query += sql;
1941 }
1942 else // vectors
1943 {
1944 // our estimation ignores that a where clause might restrict the feature type or srid
1945 if ( useEstimatedMetadata )
1946 {
1947 table = QStringLiteral( "(SELECT %1 FROM %2 WHERE %3%1 IS NOT NULL%4) AS t" )
1948 .arg( quotedIdentifier( layerProperty.geometryColName ),
1949 table,
1950 layerProperty.sql.isEmpty() ? QString() : QStringLiteral( " (%1) AND " ).arg( layerProperty.sql ) )
1951 .arg( tableScanLimit );
1952 }
1953 else if ( !layerProperty.sql.isEmpty() )
1954 {
1955 table += QStringLiteral( " WHERE %1" ).arg( layerProperty.sql );
1956 }
1957
1958 QString sql = QStringLiteral( "SELECT %1, " ).arg( i - 1 );
1959
1960 bool castToGeometry = layerProperty.geometryColType == SctGeography ||
1961 layerProperty.geometryColType == SctPcPatch;
1962
1963 sql += QLatin1String( "array_agg(DISTINCT " );
1964
1965 int srid = layerProperty.srids.value( 0, std::numeric_limits<int>::min() );
1966 if ( srid == std::numeric_limits<int>::min() )
1967 {
1968 sql += QStringLiteral( "%1(%2%3)::text" )
1969 .arg( majorVersion() < 2 ? "srid" : "st_srid",
1970 quotedIdentifier( layerProperty.geometryColName ),
1971 castToGeometry ? "::geometry" : "" );
1972 }
1973 else
1974 {
1975 sql += QStringLiteral( "%1::text" )
1976 .arg( QString::number( srid ) );
1977 }
1978
1979 sql += " || ':' || ";
1980
1981 QgsWkbTypes::Type type = layerProperty.types.value( 0, QgsWkbTypes::Unknown );
1982 if ( type == QgsWkbTypes::Unknown )
1983 {
1984 // Note that we would like to apply a "LIMIT GEOM_TYPE_SELECT_LIMIT"
1985 // here, so that the previous "array_agg(DISTINCT" does not scan the
1986 // full table. However SQL does not allow that.
1987 // So we have to do a subselect on the table to add the LIMIT,
1988 // see comment in the following code.
1989 sql += QStringLiteral( "UPPER(geometrytype(%1%2)) || ':' || ST_Zmflag(%1%2)" )
1990 .arg( quotedIdentifier( layerProperty.geometryColName ),
1991 castToGeometry ? "::geometry" : "" );
1992 }
1993 else
1994 {
1995 sql += QStringLiteral( "%1::text || ':-1'" )
1996 .arg( quotedValue( QgsPostgresConn::postgisWkbTypeName( type ) ) );
1997 }
1998
1999
2000 sql += QLatin1String( ") " );
2001
2002 if ( type == QgsWkbTypes::Unknown )
2003 {
2004 // Subselect to limit the "array_agg(DISTINCT", see previous comment.
2005 sql += QStringLiteral( " FROM (SELECT %1 FROM %2%3) AS _unused" )
2006 .arg( quotedIdentifier( layerProperty.geometryColName ) )
2007 .arg( table )
2008 .arg( tableScanLimit );
2009 }
2010 else
2011 {
2012 sql += " FROM " + table;
2013 }
2014
2015 QgsDebugMsgLevel( "Geometry types,srids and dims query: " + sql, 2 );
2016
2017 query += sql;
2018 }
2019 }
2020
2021 QgsDebugMsgLevel( "Layer types,srids and dims query: " + query, 3 );
2022
2023 QgsPostgresResult res( PQexec( query ) );
2024 if ( res.PQresultStatus() != PGRES_TUPLES_OK )
2025 {
2026 // TODO: print some error here ?
2027 return;
2028 }
2029
2030 for ( int i = 0; i < res.PQntuples(); i++ )
2031 {
2032 int idx = res.PQgetvalue( i, 0 ).toInt();
2033 auto srids_and_types = QgsPostgresStringUtils::parseArray( res.PQgetvalue( i, 1 ) );
2034 QgsPostgresLayerProperty &layerProperty = *layerProperties[idx];
2035
2036 QgsDebugMsgLevel( QStringLiteral(
2037 "Layer %1.%2.%3 has %4 srid/type combinations"
2038 )
2039 .arg( layerProperty.schemaName,
2040 layerProperty.tableName,
2041 layerProperty.geometryColName )
2042 .arg( srids_and_types.length() )
2043 , 3
2044 );
2045
2046 /* Gather found types */
2047 QList< std::pair<QgsWkbTypes::Type, int> > foundCombinations;
2048 for ( const auto &sridAndTypeVariant : srids_and_types )
2049 {
2050 QString sridAndTypeString = sridAndTypeVariant.toString();
2051
2052 QgsDebugMsgLevel( QStringLiteral(
2053 "Analyzing layer's %1.%2.%3 sridAndType %4"
2054 " against %6 found combinations"
2055 )
2056 .arg( layerProperty.schemaName,
2057 layerProperty.tableName,
2058 layerProperty.geometryColName )
2059 .arg( sridAndTypeString )
2060 .arg( foundCombinations.length() )
2061 , 3
2062 );
2063
2064 if ( sridAndTypeString == "NULL" )
2065 continue;
2066
2067 const QStringList sridAndType = sridAndTypeString.split( ':' );
2068 Q_ASSERT( sridAndType.size() == 3 );
2069 const int srid = sridAndType[0].toInt();
2070 QString typeString = sridAndType[1];
2071 const int zmFlags = sridAndType[2].toInt();
2072
2073 switch ( zmFlags )
2074 {
2075 case 1:
2076 typeString.append( 'M' );
2077 break;
2078 case 2:
2079 typeString.append( 'Z' );
2080 break;
2081 case 3:
2082 typeString.append( QStringLiteral( "ZM" ) );
2083 break;
2084 default:
2085 case 0:
2086 case -1:
2087 break;
2088 }
2089
2090 auto type = QgsPostgresConn::wkbTypeFromPostgis( typeString );
2091 auto flatType = QgsWkbTypes::flatType( type );
2092 auto multiType = QgsWkbTypes::multiType( flatType );
2093 auto curveType = QgsWkbTypes::curveType( flatType );
2094 auto multiCurveType = QgsWkbTypes::multiType( curveType );
2095
2096 // if both multi and single types exists, go for the multi type,
2097 // so that st_multi can be applied if necessary.
2098
2099 // if both flat and curve types exists, go for the curve type,
2100 // so that st_multi can be applied if necessary.
2101
2102 int j;
2103 for ( j = 0; j < foundCombinations.length(); j++ )
2104 {
2105 auto foundPair = foundCombinations.at( j );
2106 if ( foundPair.second != srid )
2107 continue; // srid must match
2108
2109 auto knownType = foundPair.first;
2110 if ( type == knownType )
2111 break; // found
2112
2113 auto knownMultiType = QgsWkbTypes::multiType( knownType );
2114 auto knownCurveType = QgsWkbTypes::curveType( knownType );
2115 auto knownMultiCurveType = QgsWkbTypes::multiType( knownCurveType );
2116
2117 if ( multiCurveType == knownMultiCurveType )
2118 {
2119 QgsDebugMsgLevel( QStringLiteral(
2120 "Upgrading type[%1] of layer %2.%3.%4 "
2121 "to multiCurved type %5" )
2122 .arg( j )
2123 .arg( layerProperty.schemaName,
2124 layerProperty.tableName,
2125 layerProperty.geometryColName )
2126 .arg( multiCurveType ), 3
2127 );
2128 foundCombinations[j].first = multiCurveType;
2129 break;
2130 }
2131 else if ( multiType == knownMultiType )
2132 {
2133 QgsDebugMsgLevel( QStringLiteral(
2134 "Upgrading type[%1] of layer %2.%3.%4 "
2135 "to multi type %5" )
2136 .arg( j )
2137 .arg( layerProperty.schemaName,
2138 layerProperty.tableName,
2139 layerProperty.geometryColName )
2140 .arg( multiType ), 3
2141 );
2142 foundCombinations[j].first = multiType;
2143 break;
2144 }
2145 else if ( curveType == knownCurveType )
2146 {
2147 QgsDebugMsgLevel( QStringLiteral(
2148 "Upgrading type[%1] of layer %2.%3.%4 "
2149 "to curved type %5" )
2150 .arg( j )
2151 .arg( layerProperty.schemaName,
2152 layerProperty.tableName,
2153 layerProperty.geometryColName )
2154 .arg( multiType ), 3
2155 );
2156 foundCombinations[j].first = curveType;
2157 break;
2158 }
2159 }
2160
2161 if ( j < foundCombinations.length() )
2162 {
2163 QgsDebugMsgLevel( QStringLiteral(
2164 "Pre-existing compatible combination %1/%2 "
2165 "found for layer %3.%4.%5 "
2166 )
2167 .arg( j ) .arg( foundCombinations.length() )
2168 .arg( layerProperty.schemaName,
2169 layerProperty.tableName,
2170 layerProperty.geometryColName ), 3
2171 );
2172 continue; // already found
2173 }
2174
2175 QgsDebugMsgLevel( QStringLiteral(
2176 "Setting typeSridCombination[%1] of layer %2.%3.%4 "
2177 "to srid %5 and type %6" )
2178 .arg( j )
2179 .arg( layerProperty.schemaName,
2180 layerProperty.tableName,
2181 layerProperty.geometryColName )
2182 .arg( srid )
2183 .arg( type ), 3
2184 );
2185
2186 foundCombinations << std::make_pair( type, srid );
2187 }
2188
2189 QgsDebugMsgLevel( QStringLiteral(
2190 "Completed scan of %1 srid/type combinations "
2191 "for layer of layer %2.%3.%4 " )
2192 .arg( srids_and_types.length() )
2193 .arg( layerProperty.schemaName,
2194 layerProperty.tableName,
2195 layerProperty.geometryColName ), 2
2196 );
2197
2198 /* Rewrite srids and types to match found combinations
2199 * of srids and types */
2200 layerProperty.srids.clear();
2201 layerProperty.types.clear();
2202 for ( const auto &comb : foundCombinations )
2203 {
2204 layerProperty.types << comb.first;
2205 layerProperty.srids << comb.second;
2206 }
2207 QgsDebugMsgLevel( QStringLiteral(
2208 "Final layer %1.%2.%3 types: %4" )
2209 .arg( layerProperty.schemaName,
2210 layerProperty.tableName,
2211 layerProperty.geometryColName )
2212 .arg( layerProperty.types.length() ), 2
2213 );
2214 QgsDebugMsgLevel( QStringLiteral(
2215 "Final layer %1.%2.%3 srids: %4" )
2216 .arg( layerProperty.schemaName,
2217 layerProperty.tableName,
2218 layerProperty.geometryColName )
2219 .arg( layerProperty.srids.length() ), 2
2220 );
2221 }
2222 }
2223
postgisWkbType(QgsWkbTypes::Type wkbType,QString & geometryType,int & dim)2224 void QgsPostgresConn::postgisWkbType( QgsWkbTypes::Type wkbType, QString &geometryType, int &dim )
2225 {
2226 dim = 2;
2227 QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( wkbType );
2228 switch ( flatType )
2229 {
2230 case QgsWkbTypes::Point:
2231 geometryType = QStringLiteral( "POINT" );
2232 break;
2233
2234 case QgsWkbTypes::LineString:
2235 geometryType = QStringLiteral( "LINESTRING" );
2236 break;
2237
2238 case QgsWkbTypes::Polygon:
2239 geometryType = QStringLiteral( "POLYGON" );
2240 break;
2241
2242 case QgsWkbTypes::MultiPoint:
2243 geometryType = QStringLiteral( "MULTIPOINT" );
2244 break;
2245
2246 case QgsWkbTypes::MultiLineString:
2247 geometryType = QStringLiteral( "MULTILINESTRING" );
2248 break;
2249
2250 case QgsWkbTypes::MultiPolygon:
2251 geometryType = QStringLiteral( "MULTIPOLYGON" );
2252 break;
2253
2254 case QgsWkbTypes::CircularString:
2255 geometryType = QStringLiteral( "CIRCULARSTRING" );
2256 break;
2257
2258 case QgsWkbTypes::CompoundCurve:
2259 geometryType = QStringLiteral( "COMPOUNDCURVE" );
2260 break;
2261
2262 case QgsWkbTypes::CurvePolygon:
2263 geometryType = QStringLiteral( "CURVEPOLYGON" );
2264 break;
2265
2266 case QgsWkbTypes::MultiCurve:
2267 geometryType = QStringLiteral( "MULTICURVE" );
2268 break;
2269
2270 case QgsWkbTypes::MultiSurface:
2271 geometryType = QStringLiteral( "MULTISURFACE" );
2272 break;
2273
2274 case QgsWkbTypes::Unknown:
2275 geometryType = QStringLiteral( "GEOMETRY" );
2276 break;
2277
2278 case QgsWkbTypes::NoGeometry:
2279 default:
2280 dim = 0;
2281 break;
2282 }
2283
2284 if ( QgsWkbTypes::hasZ( wkbType ) && QgsWkbTypes::hasM( wkbType ) )
2285 {
2286 geometryType += QLatin1String( "ZM" );
2287 dim = 4;
2288 }
2289 else if ( QgsWkbTypes::hasZ( wkbType ) )
2290 {
2291 geometryType += QLatin1Char( 'Z' );
2292 dim = 3;
2293 }
2294 else if ( QgsWkbTypes::hasM( wkbType ) )
2295 {
2296 geometryType += QLatin1Char( 'M' );
2297 dim = 3;
2298 }
2299 else if ( wkbType >= QgsWkbTypes::Point25D && wkbType <= QgsWkbTypes::MultiPolygon25D )
2300 {
2301 dim = 3;
2302 }
2303 }
2304
postgisWkbTypeName(QgsWkbTypes::Type wkbType)2305 QString QgsPostgresConn::postgisWkbTypeName( QgsWkbTypes::Type wkbType )
2306 {
2307 QString geometryType;
2308 int dim;
2309
2310 postgisWkbType( wkbType, geometryType, dim );
2311
2312 return geometryType;
2313 }
2314
postgisTypeFilter(QString geomCol,QgsWkbTypes::Type wkbType,bool castToGeometry)2315 QString QgsPostgresConn::postgisTypeFilter( QString geomCol, QgsWkbTypes::Type wkbType, bool castToGeometry )
2316 {
2317 geomCol = quotedIdentifier( geomCol );
2318 if ( castToGeometry )
2319 geomCol += QLatin1String( "::geometry" );
2320
2321 QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( wkbType );
2322 switch ( geomType )
2323 {
2324 case QgsWkbTypes::PointGeometry:
2325 return QStringLiteral( "upper(geometrytype(%1)) IN ('POINT','POINTZ','POINTM','POINTZM','MULTIPOINT','MULTIPOINTZ','MULTIPOINTM','MULTIPOINTZM')" ).arg( geomCol );
2326 case QgsWkbTypes::LineGeometry:
2327 return QStringLiteral( "upper(geometrytype(%1)) IN ('LINESTRING','LINESTRINGZ','LINESTRINGM','LINESTRINGZM','CIRCULARSTRING','CIRCULARSTRINGZ','CIRCULARSTRINGM','CIRCULARSTRINGZM','COMPOUNDCURVE','COMPOUNDCURVEZ','COMPOUNDCURVEM','COMPOUNDCURVEZM','MULTILINESTRING','MULTILINESTRINGZ','MULTILINESTRINGM','MULTILINESTRINGZM','MULTICURVE','MULTICURVEZ','MULTICURVEM','MULTICURVEZM')" ).arg( geomCol );
2328 case QgsWkbTypes::PolygonGeometry:
2329 return QStringLiteral( "upper(geometrytype(%1)) IN ('POLYGON','POLYGONZ','POLYGONM','POLYGONZM','CURVEPOLYGON','CURVEPOLYGONZ','CURVEPOLYGONM','CURVEPOLYGONZM','MULTIPOLYGON','MULTIPOLYGONZ','MULTIPOLYGONM','MULTIPOLYGONZM','MULTIPOLYGONM','MULTISURFACE','MULTISURFACEZ','MULTISURFACEM','MULTISURFACEZM','POLYHEDRALSURFACE','TIN')" ).arg( geomCol );
2330 case QgsWkbTypes::NullGeometry:
2331 return QStringLiteral( "geometrytype(%1) IS NULL" ).arg( geomCol );
2332 default: //unknown geometry
2333 return QString();
2334 }
2335 }
2336
postgisWkbTypeDim(QgsWkbTypes::Type wkbType)2337 int QgsPostgresConn::postgisWkbTypeDim( QgsWkbTypes::Type wkbType )
2338 {
2339 QString geometryType;
2340 int dim;
2341
2342 postgisWkbType( wkbType, geometryType, dim );
2343
2344 return dim;
2345 }
2346
wkbTypeFromPostgis(const QString & type)2347 QgsWkbTypes::Type QgsPostgresConn::wkbTypeFromPostgis( const QString &type )
2348 {
2349 // Polyhedral surfaces and TIN are stored in PostGIS as geometry collections
2350 // of Polygons and Triangles.
2351 // So, since QGIS does not natively support PS and TIN, but we would like to open them if possible,
2352 // we consider them as multipolygons. WKB will be converted by the feature iterator
2353 if ( ( type == QLatin1String( "POLYHEDRALSURFACE" ) ) || ( type == QLatin1String( "TIN" ) ) )
2354 {
2355 return QgsWkbTypes::MultiPolygon;
2356 }
2357 else if ( ( type == QLatin1String( "POLYHEDRALSURFACEZ" ) ) || ( type == QLatin1String( "TINZ" ) ) )
2358 {
2359 return QgsWkbTypes::MultiPolygonZ;
2360 }
2361 else if ( ( type == QLatin1String( "POLYHEDRALSURFACEM" ) ) || ( type == QLatin1String( "TINM" ) ) )
2362 {
2363 return QgsWkbTypes::MultiPolygonM;
2364 }
2365 else if ( ( type == QLatin1String( "POLYHEDRALSURFACEZM" ) ) || ( type == QLatin1String( "TINZM" ) ) )
2366 {
2367 return QgsWkbTypes::MultiPolygonZM;
2368 }
2369 else if ( type == QLatin1String( "TRIANGLE" ) )
2370 {
2371 return QgsWkbTypes::Polygon;
2372 }
2373 else if ( type == QLatin1String( "TRIANGLEZ" ) )
2374 {
2375 return QgsWkbTypes::PolygonZ;
2376 }
2377 else if ( type == QLatin1String( "TRIANGLEM" ) )
2378 {
2379 return QgsWkbTypes::PolygonM;
2380 }
2381 else if ( type == QLatin1String( "TRIANGLEZM" ) )
2382 {
2383 return QgsWkbTypes::PolygonZM;
2384 }
2385 return QgsWkbTypes::parseType( type );
2386 }
2387
wkbTypeFromOgcWkbType(unsigned int wkbType)2388 QgsWkbTypes::Type QgsPostgresConn::wkbTypeFromOgcWkbType( unsigned int wkbType )
2389 {
2390 // PolyhedralSurface => MultiPolygon
2391 if ( wkbType % 1000 == 15 )
2392 return ( QgsWkbTypes::Type )( wkbType / 1000 * 1000 + QgsWkbTypes::MultiPolygon );
2393 // TIN => MultiPolygon
2394 if ( wkbType % 1000 == 16 )
2395 return ( QgsWkbTypes::Type )( wkbType / 1000 * 1000 + QgsWkbTypes::MultiPolygon );
2396 // Triangle => Polygon
2397 if ( wkbType % 1000 == 17 )
2398 return ( QgsWkbTypes::Type )( wkbType / 1000 * 1000 + QgsWkbTypes::Polygon );
2399 return ( QgsWkbTypes::Type ) wkbType;
2400 }
2401
displayStringForWkbType(QgsWkbTypes::Type type)2402 QString QgsPostgresConn::displayStringForWkbType( QgsWkbTypes::Type type )
2403 {
2404 return QgsWkbTypes::displayString( QgsWkbTypes::Type( type ) );
2405 }
2406
displayStringForGeomType(QgsPostgresGeometryColumnType type)2407 QString QgsPostgresConn::displayStringForGeomType( QgsPostgresGeometryColumnType type )
2408 {
2409 switch ( type )
2410 {
2411 case SctNone:
2412 return tr( "None" );
2413 case SctGeometry:
2414 return tr( "Geometry" );
2415 case SctGeography:
2416 return tr( "Geography" );
2417 case SctTopoGeometry:
2418 return tr( "TopoGeometry" );
2419 case SctPcPatch:
2420 return tr( "PcPatch" );
2421 case SctRaster:
2422 return tr( "Raster" );
2423 }
2424
2425 Q_ASSERT( !"unexpected geometry column type" );
2426 return QString();
2427 }
2428
wkbTypeFromGeomType(QgsWkbTypes::GeometryType geomType)2429 QgsWkbTypes::Type QgsPostgresConn::wkbTypeFromGeomType( QgsWkbTypes::GeometryType geomType )
2430 {
2431 switch ( geomType )
2432 {
2433 case QgsWkbTypes::PointGeometry:
2434 return QgsWkbTypes::Point;
2435 case QgsWkbTypes::LineGeometry:
2436 return QgsWkbTypes::LineString;
2437 case QgsWkbTypes::PolygonGeometry:
2438 return QgsWkbTypes::Polygon;
2439 case QgsWkbTypes::NullGeometry:
2440 return QgsWkbTypes::NoGeometry;
2441 case QgsWkbTypes::UnknownGeometry:
2442 return QgsWkbTypes::Unknown;
2443 }
2444
2445 Q_ASSERT( !"unexpected geomType" );
2446 return QgsWkbTypes::Unknown;
2447 }
2448
connectionList()2449 QStringList QgsPostgresConn::connectionList()
2450 {
2451 QgsSettings settings;
2452 settings.beginGroup( QStringLiteral( "PostgreSQL/connections" ) );
2453 return settings.childGroups();
2454 }
2455
selectedConnection()2456 QString QgsPostgresConn::selectedConnection()
2457 {
2458 QgsSettings settings;
2459 return settings.value( QStringLiteral( "PostgreSQL/connections/selected" ) ).toString();
2460 }
2461
setSelectedConnection(const QString & name)2462 void QgsPostgresConn::setSelectedConnection( const QString &name )
2463 {
2464 QgsSettings settings;
2465 return settings.setValue( QStringLiteral( "PostgreSQL/connections/selected" ), name );
2466 }
2467
connUri(const QString & connName)2468 QgsDataSourceUri QgsPostgresConn::connUri( const QString &connName )
2469 {
2470 QgsDebugMsgLevel( "theConnName = " + connName, 2 );
2471
2472 QgsSettings settings;
2473
2474 QString key = "/PostgreSQL/connections/" + connName;
2475
2476 QString service = settings.value( key + "/service" ).toString();
2477 QString host = settings.value( key + "/host" ).toString();
2478 QString port = settings.value( key + "/port" ).toString();
2479 if ( port.length() == 0 )
2480 {
2481 port = QStringLiteral( "5432" );
2482 }
2483 QString database = settings.value( key + "/database" ).toString();
2484
2485 bool estimatedMetadata = useEstimatedMetadata( connName );
2486 QgsDataSourceUri::SslMode sslmode = settings.enumValue( key + "/sslmode", QgsDataSourceUri::SslPrefer );
2487
2488 QString username;
2489 QString password;
2490 if ( settings.value( key + "/saveUsername" ).toString() == QLatin1String( "true" ) )
2491 {
2492 username = settings.value( key + "/username" ).toString();
2493 }
2494
2495 if ( settings.value( key + "/savePassword" ).toString() == QLatin1String( "true" ) )
2496 {
2497 password = settings.value( key + "/password" ).toString();
2498 }
2499
2500 // Old save setting
2501 if ( settings.contains( key + "/save" ) )
2502 {
2503 username = settings.value( key + "/username" ).toString();
2504
2505 if ( settings.value( key + "/save" ).toString() == QLatin1String( "true" ) )
2506 {
2507 password = settings.value( key + "/password" ).toString();
2508 }
2509 }
2510
2511 QString authcfg = settings.value( key + "/authcfg" ).toString();
2512
2513 QgsDataSourceUri uri;
2514 if ( !service.isEmpty() )
2515 {
2516 uri.setConnection( service, database, username, password, sslmode, authcfg );
2517 }
2518 else
2519 {
2520 uri.setConnection( host, port, database, username, password, sslmode, authcfg );
2521 }
2522 uri.setUseEstimatedMetadata( estimatedMetadata );
2523
2524 return uri;
2525 }
2526
publicSchemaOnly(const QString & connName)2527 bool QgsPostgresConn::publicSchemaOnly( const QString &connName )
2528 {
2529 QgsSettings settings;
2530 return settings.value( "/PostgreSQL/connections/" + connName + "/publicOnly", false ).toBool();
2531 }
2532
geometryColumnsOnly(const QString & connName)2533 bool QgsPostgresConn::geometryColumnsOnly( const QString &connName )
2534 {
2535 QgsSettings settings;
2536
2537 return settings.value( "/PostgreSQL/connections/" + connName + "/geometryColumnsOnly", false ).toBool();
2538 }
2539
dontResolveType(const QString & connName)2540 bool QgsPostgresConn::dontResolveType( const QString &connName )
2541 {
2542 QgsSettings settings;
2543
2544 return settings.value( "/PostgreSQL/connections/" + connName + "/dontResolveType", false ).toBool();
2545 }
2546
useEstimatedMetadata(const QString & connName)2547 bool QgsPostgresConn::useEstimatedMetadata( const QString &connName )
2548 {
2549 QgsSettings settings;
2550
2551 return settings.value( "/PostgreSQL/connections/" + connName + "/estimatedMetadata", false ).toBool();
2552 }
2553
2554
allowGeometrylessTables(const QString & connName)2555 bool QgsPostgresConn::allowGeometrylessTables( const QString &connName )
2556 {
2557 QgsSettings settings;
2558 return settings.value( "/PostgreSQL/connections/" + connName + "/allowGeometrylessTables", false ).toBool();
2559 }
2560
allowProjectsInDatabase(const QString & connName)2561 bool QgsPostgresConn::allowProjectsInDatabase( const QString &connName )
2562 {
2563 QgsSettings settings;
2564 return settings.value( "/PostgreSQL/connections/" + connName + "/projectsInDatabase", false ).toBool();
2565 }
2566
deleteConnection(const QString & connName)2567 void QgsPostgresConn::deleteConnection( const QString &connName )
2568 {
2569 QgsSettings settings;
2570
2571 QString key = "/PostgreSQL/connections/" + connName;
2572 settings.remove( key + "/service" );
2573 settings.remove( key + "/host" );
2574 settings.remove( key + "/port" );
2575 settings.remove( key + "/database" );
2576 settings.remove( key + "/username" );
2577 settings.remove( key + "/password" );
2578 settings.remove( key + "/sslmode" );
2579 settings.remove( key + "/publicOnly" );
2580 settings.remove( key + "/geometryColumnsOnly" );
2581 settings.remove( key + "/allowGeometrylessTables" );
2582 settings.remove( key + "/estimatedMetadata" );
2583 settings.remove( key + "/saveUsername" );
2584 settings.remove( key + "/savePassword" );
2585 settings.remove( key + "/save" );
2586 settings.remove( key + "/authcfg" );
2587 settings.remove( key + "/keys" );
2588 settings.remove( key );
2589 }
2590
cancel()2591 bool QgsPostgresConn::cancel()
2592 {
2593 QMutexLocker locker( &mLock );
2594 PGcancel *c = ::PQgetCancel( mConn );
2595 if ( !c )
2596 {
2597 QgsMessageLog::logMessage( tr( "Query could not be canceled [%1]" ).arg( tr( "PQgetCancel failed" ) ),
2598 tr( "PostGIS" ) );
2599 return false;
2600 }
2601
2602 char errbuf[256];
2603 int res = ::PQcancel( c, errbuf, sizeof errbuf );
2604 ::PQfreeCancel( c );
2605
2606 if ( !res )
2607 QgsMessageLog::logMessage( tr( "Query could not be canceled [%1]" ).arg( errbuf ), tr( "PostGIS" ) );
2608
2609 return res == 0;
2610 }
2611
currentDatabase() const2612 QString QgsPostgresConn::currentDatabase() const
2613 {
2614 QMutexLocker locker( &mLock );
2615 QString database;
2616 QString sql = "SELECT current_database()";
2617 QgsPostgresResult res( PQexec( sql ) );
2618
2619 if ( res.PQresultStatus() == PGRES_TUPLES_OK )
2620 {
2621 database = res.PQgetvalue( 0, 0 );
2622 }
2623 else
2624 {
2625 QgsMessageLog::logMessage( tr( "SQL: %1\nresult: %2\nerror: %3\n" ).arg( sql ).arg( res.PQresultStatus() ).arg( res.PQresultErrorMessage() ), tr( "PostGIS" ) );
2626 }
2627
2628 return database;
2629 }
2630