1 /***************************************************************************
2   qgspostgresproviderconnection.cpp - QgsPostgresProviderConnection
3 
4  ---------------------
5  begin                : 2.8.2019
6  copyright            : (C) 2019 by Alessandro Pasotti
7  email                : elpaso at itopen dot it
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 #include "qgspostgresproviderconnection.h"
17 #include "qgspostgresconn.h"
18 #include "qgspostgresconnpool.h"
19 #include "qgssettings.h"
20 #include "qgspostgresprovider.h"
21 #include "qgsexception.h"
22 #include "qgsapplication.h"
23 
24 extern "C"
25 {
26 #include <libpq-fe.h>
27 }
28 
QgsPostgresProviderConnection(const QString & name)29 QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &name )
30   : QgsAbstractDatabaseProviderConnection( name )
31 {
32   mProviderKey = QStringLiteral( "postgres" );
33   // Remove the sql and table empty parts
34   const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=""\s*)raw" };
35   setUri( QgsPostgresConn::connUri( name ).uri().replace( removePartsRe, QString() ) );
36   setDefaultCapabilities();
37 }
38 
QgsPostgresProviderConnection(const QString & uri,const QVariantMap & configuration)39 QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &uri, const QVariantMap &configuration ):
40   QgsAbstractDatabaseProviderConnection( QgsDataSourceUri( uri ).connectionInfo( false ), configuration )
41 {
42   mProviderKey = QStringLiteral( "postgres" );
43   setDefaultCapabilities();
44 }
45 
setDefaultCapabilities()46 void QgsPostgresProviderConnection::setDefaultCapabilities()
47 {
48   // TODO: we might check at this point if the user actually has the privileges and return
49   //       properly filtered capabilities instead of all of them
50   mCapabilities =
51   {
52     Capability::DropVectorTable,
53     Capability::DropRasterTable,
54     Capability::CreateVectorTable,
55     Capability::RenameSchema,
56     Capability::DropSchema,
57     Capability::CreateSchema,
58     Capability::RenameVectorTable,
59     Capability::RenameRasterTable,
60     Capability::Vacuum,
61     Capability::ExecuteSql,
62     Capability::SqlLayers,
63     //Capability::Transaction,
64     Capability::Tables,
65     Capability::Schemas,
66     Capability::Spatial,
67     Capability::TableExists,
68     Capability::CreateSpatialIndex,
69     Capability::SpatialIndexExists,
70     Capability::DeleteSpatialIndex,
71     Capability::DeleteField,
72     Capability::DeleteFieldCascade,
73     Capability::AddField
74   };
75   mGeometryColumnCapabilities =
76   {
77     GeometryColumnCapability::Z,
78     GeometryColumnCapability::M,
79     GeometryColumnCapability::SinglePart,
80     GeometryColumnCapability::Curves
81   };
82 }
83 
dropTablePrivate(const QString & schema,const QString & name) const84 void QgsPostgresProviderConnection::dropTablePrivate( const QString &schema, const QString &name ) const
85 {
86   executeSqlPrivate( QStringLiteral( "DROP TABLE %1.%2" )
87                      .arg( QgsPostgresConn::quotedIdentifier( schema ) )
88                      .arg( QgsPostgresConn::quotedIdentifier( name ) ) );
89 }
90 
createVectorTable(const QString & schema,const QString & name,const QgsFields & fields,QgsWkbTypes::Type wkbType,const QgsCoordinateReferenceSystem & srs,bool overwrite,const QMap<QString,QVariant> * options) const91 void QgsPostgresProviderConnection::createVectorTable( const QString &schema,
92     const QString &name,
93     const QgsFields &fields,
94     QgsWkbTypes::Type wkbType,
95     const QgsCoordinateReferenceSystem &srs,
96     bool overwrite,
97     const QMap<QString,
98     QVariant> *options ) const
99 {
100 
101   checkCapability( Capability::CreateVectorTable );
102 
103   QgsDataSourceUri newUri { uri() };
104   newUri.setSchema( schema );
105   newUri.setTable( name );
106   // Set geometry column if it's not aspatial
107   if ( wkbType != QgsWkbTypes::Type::Unknown &&  wkbType != QgsWkbTypes::Type::NoGeometry )
108   {
109     newUri.setGeometryColumn( options->value( QStringLiteral( "geometryColumn" ), QStringLiteral( "geom" ) ).toString() );
110   }
111   QMap<int, int> map;
112   QString errCause;
113   QgsVectorLayerExporter::ExportError errCode = QgsPostgresProvider::createEmptyLayer(
114         newUri.uri(),
115         fields,
116         wkbType,
117         srs,
118         overwrite,
119         &map,
120         &errCause,
121         options
122       );
123   if ( errCode != QgsVectorLayerExporter::ExportError::NoError )
124   {
125     throw QgsProviderConnectionException( QObject::tr( "An error occurred while creating the vector layer: %1" ).arg( errCause ) );
126   }
127 }
128 
tableUri(const QString & schema,const QString & name) const129 QString QgsPostgresProviderConnection::tableUri( const QString &schema, const QString &name ) const
130 {
131   const auto tableInfo { table( schema, name ) };
132   QgsDataSourceUri dsUri( uri() );
133   dsUri.setTable( name );
134   dsUri.setSchema( schema );
135   return dsUri.uri( false );
136 }
137 
dropVectorTable(const QString & schema,const QString & name) const138 void QgsPostgresProviderConnection::dropVectorTable( const QString &schema, const QString &name ) const
139 {
140   checkCapability( Capability::DropVectorTable );
141   dropTablePrivate( schema, name );
142 }
143 
dropRasterTable(const QString & schema,const QString & name) const144 void QgsPostgresProviderConnection::dropRasterTable( const QString &schema, const QString &name ) const
145 {
146   checkCapability( Capability::DropRasterTable );
147   dropTablePrivate( schema, name );
148 }
149 
150 
renameTablePrivate(const QString & schema,const QString & name,const QString & newName) const151 void QgsPostgresProviderConnection::renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const
152 {
153   executeSqlPrivate( QStringLiteral( "ALTER TABLE %1.%2 RENAME TO %3" )
154                      .arg( QgsPostgresConn::quotedIdentifier( schema ) )
155                      .arg( QgsPostgresConn::quotedIdentifier( name ) )
156                      .arg( QgsPostgresConn::quotedIdentifier( newName ) ) );
157 }
158 
renameVectorTable(const QString & schema,const QString & name,const QString & newName) const159 void QgsPostgresProviderConnection::renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const
160 {
161   checkCapability( Capability::RenameVectorTable );
162   renameTablePrivate( schema, name, newName );
163 }
164 
renameRasterTable(const QString & schema,const QString & name,const QString & newName) const165 void QgsPostgresProviderConnection::renameRasterTable( const QString &schema, const QString &name, const QString &newName ) const
166 {
167   checkCapability( Capability::RenameRasterTable );
168   renameTablePrivate( schema, name, newName );
169 }
170 
createSchema(const QString & name) const171 void QgsPostgresProviderConnection::createSchema( const QString &name ) const
172 {
173   checkCapability( Capability::CreateSchema );
174   executeSqlPrivate( QStringLiteral( "CREATE SCHEMA %1" )
175                      .arg( QgsPostgresConn::quotedIdentifier( name ) ) );
176 
177 }
178 
dropSchema(const QString & name,bool force) const179 void QgsPostgresProviderConnection::dropSchema( const QString &name,  bool force ) const
180 {
181   checkCapability( Capability::DropSchema );
182   executeSqlPrivate( QStringLiteral( "DROP SCHEMA %1 %2" )
183                      .arg( QgsPostgresConn::quotedIdentifier( name ) )
184                      .arg( force ? QStringLiteral( "CASCADE" ) : QString() ) );
185 }
186 
renameSchema(const QString & name,const QString & newName) const187 void QgsPostgresProviderConnection::renameSchema( const QString &name, const QString &newName ) const
188 {
189   checkCapability( Capability::RenameSchema );
190   executeSqlPrivate( QStringLiteral( "ALTER SCHEMA %1 RENAME TO %2" )
191                      .arg( QgsPostgresConn::quotedIdentifier( name ) )
192                      .arg( QgsPostgresConn::quotedIdentifier( newName ) ) );
193 }
194 
executeSql(const QString & sql,QgsFeedback * feedback) const195 QList<QVariantList> QgsPostgresProviderConnection::executeSql( const QString &sql, QgsFeedback *feedback ) const
196 {
197   checkCapability( Capability::ExecuteSql );
198   return executeSqlPrivate( sql, true, feedback );
199 }
200 
executeSqlPrivate(const QString & sql,bool resolveTypes,QgsFeedback * feedback,std::shared_ptr<QgsPoolPostgresConn> pgconn) const201 QList<QVariantList> QgsPostgresProviderConnection::executeSqlPrivate( const QString &sql, bool resolveTypes, QgsFeedback *feedback, std::shared_ptr<QgsPoolPostgresConn> pgconn ) const
202 {
203   QList<QVariantList> results;
204 
205   // Check feedback first!
206   if ( feedback && feedback->isCanceled() )
207   {
208     return results;
209   }
210 
211   if ( ! pgconn )
212     pgconn = std::make_shared<QgsPoolPostgresConn>( QgsDataSourceUri( uri() ).connectionInfo( false ) );
213   QgsPostgresConn *conn = pgconn->get();
214   if ( ! conn )
215   {
216     throw QgsProviderConnectionException( QObject::tr( "Connection failed: %1" ).arg( uri() ) );
217   }
218   else
219   {
220 
221     if ( feedback && feedback->isCanceled() )
222     {
223       return results;
224     }
225 
226     // This is gross but I tried with both conn and a context QObject without success: the lambda is never called.
227     QMetaObject::Connection qtConnection;
228     if ( feedback )
229     {
230       qtConnection = QObject::connect( feedback, &QgsFeedback::canceled, [ &conn ]
231       {
232         conn->PQCancel();
233       } );
234     }
235 
236     QgsPostgresResult res( conn->PQexec( sql ) );
237     if ( feedback )
238     {
239       QObject::disconnect( qtConnection );
240     }
241 
242     QString errCause;
243     if ( conn->PQstatus() != CONNECTION_OK || ! res.result() )
244     {
245       errCause = QObject::tr( "Connection error: %1 returned %2 [%3]" )
246                  .arg( sql ).arg( conn->PQstatus() )
247                  .arg( conn->PQerrorMessage() );
248     }
249     else
250     {
251       const QString err { conn->PQerrorMessage() };
252       if ( ! err.isEmpty() )
253       {
254         errCause = QObject::tr( "SQL error: %1 returned %2 [%3]" )
255                    .arg( sql )
256                    .arg( conn->PQstatus() )
257                    .arg( err );
258       }
259     }
260     if ( res.PQntuples() > 0 )
261     {
262       // Try to convert value types at least for basic simple types that can be directly mapped to Python
263       QMap<int, QVariant::Type> typeMap;
264       if ( resolveTypes )
265       {
266         for ( int rowIdx = 0; rowIdx < res.PQnfields(); rowIdx++ )
267         {
268           if ( feedback && feedback->isCanceled() )
269           {
270             break;
271           }
272           const Oid oid { res.PQftype( rowIdx ) };
273           QList<QVariantList> typeRes = executeSqlPrivate( QStringLiteral( "SELECT typname FROM pg_type WHERE oid = %1" ).arg( oid ), false, nullptr, pgconn );
274           // Set the default to string
275           QVariant::Type vType { QVariant::Type::String };
276           if ( typeRes.size() > 0 && typeRes.first().size() > 0 )
277           {
278             static const QStringList intTypes = { QStringLiteral( "oid" ),
279                                                   QStringLiteral( "int2" ),
280                                                   QStringLiteral( "int4" ),
281                                                   QStringLiteral( "int8" )
282                                                 };
283             static const QStringList floatTypes = { QStringLiteral( "float4" ),
284                                                     QStringLiteral( "float8" ),
285                                                     QStringLiteral( "numeric" )
286                                                   };
287             const QString typName { typeRes.first().first().toString() };
288 
289             if ( floatTypes.contains( typName ) )
290             {
291               vType = QVariant::Double;
292             }
293             else if ( intTypes.contains( typName ) )
294             {
295               vType = QVariant::LongLong;
296             }
297             else if ( typName == QLatin1String( "date" ) )
298             {
299               vType = QVariant::Date;
300             }
301             else if ( typName.startsWith( QLatin1String( "timestamp" ) ) )
302             {
303               vType = QVariant::DateTime;
304             }
305             else if ( typName == QLatin1String( "time" ) )
306             {
307               vType = QVariant::Time;
308             }
309             else if ( typName == QLatin1String( "bool" ) )
310             {
311               vType = QVariant::Bool;
312             }
313             else if ( typName == QLatin1String( "char" ) )
314             {
315               vType = QVariant::Char;
316             }
317             else
318             {
319               // Just a warning, usually ok
320               QgsDebugMsgLevel( QStringLiteral( "Unhandled PostgreSQL type %1, assuming string" ).arg( typName ), 2 );
321             }
322           }
323           typeMap[ rowIdx ] = vType;
324         }
325       }
326       for ( int rowIdx = 0; rowIdx < res.PQntuples(); rowIdx++ )
327       {
328         if ( feedback && feedback->isCanceled() )
329         {
330           break;
331         }
332         QVariantList row;
333         for ( int colIdx = 0; colIdx < res.PQnfields(); colIdx++ )
334         {
335           if ( resolveTypes )
336           {
337             const QVariant::Type vType { typeMap.value( colIdx, QVariant::Type::String ) };
338             QVariant val = res.PQgetvalue( rowIdx, colIdx );
339             // Special case for bools: 'f' and 't'
340             if ( vType == QVariant::Bool )
341             {
342               if ( ! val.toString().isEmpty() )
343               {
344                 val = val.toString() == 't';
345               }
346             }
347             else if ( val.canConvert( static_cast<int>( vType ) ) )
348             {
349               val.convert( static_cast<int>( vType ) );
350             }
351             row.push_back( val );
352           }
353           else
354           {
355             row.push_back( res.PQgetvalue( rowIdx, colIdx ) );
356           }
357         }
358         results.push_back( row );
359       }
360     }
361     if ( ! errCause.isEmpty() )
362     {
363       throw QgsProviderConnectionException( errCause );
364     }
365   }
366   return results;
367 }
368 
vacuum(const QString & schema,const QString & name) const369 void QgsPostgresProviderConnection::vacuum( const QString &schema, const QString &name ) const
370 {
371   checkCapability( Capability::Vacuum );
372   executeSql( QStringLiteral( "VACUUM FULL ANALYZE %1.%2" )
373               .arg( QgsPostgresConn::quotedIdentifier( schema ) )
374               .arg( QgsPostgresConn::quotedIdentifier( name ) ) );
375 }
376 
createSpatialIndex(const QString & schema,const QString & name,const QgsAbstractDatabaseProviderConnection::SpatialIndexOptions & options) const377 void QgsPostgresProviderConnection::createSpatialIndex( const QString &schema, const QString &name, const QgsAbstractDatabaseProviderConnection::SpatialIndexOptions &options ) const
378 {
379   checkCapability( Capability::CreateSpatialIndex );
380 
381   QString geometryColumnName { options.geometryColumnName };
382   if ( geometryColumnName.isEmpty() )
383   {
384     // Can we guess it?
385     try
386     {
387       const auto tp { table( schema, name ) };
388       geometryColumnName = tp.geometryColumn();
389     }
390     catch ( QgsProviderConnectionException & )
391     {
392       // pass
393     }
394   }
395 
396   if ( geometryColumnName.isEmpty() )
397   {
398     throw QgsProviderConnectionException( QObject::tr( "Geometry column name not specified while creating spatial index" ) );
399   }
400 
401   const QString indexName = QStringLiteral( "sidx_%1_%2" ).arg( name, geometryColumnName );
402   executeSql( QStringLiteral( "CREATE INDEX %1 ON %2.%3 USING GIST (%4);" )
403               .arg( QgsPostgresConn::quotedIdentifier( indexName ),
404                     QgsPostgresConn::quotedIdentifier( schema ),
405                     QgsPostgresConn::quotedIdentifier( name ),
406                     QgsPostgresConn::quotedIdentifier( geometryColumnName ) ) );
407 }
408 
spatialIndexExists(const QString & schema,const QString & name,const QString & geometryColumn) const409 bool QgsPostgresProviderConnection::spatialIndexExists( const QString &schema, const QString &name, const QString &geometryColumn ) const
410 {
411   checkCapability( Capability::SpatialIndexExists );
412 
413   const QList<QVariantList> res = executeSql( QStringLiteral( R"""(SELECT COUNT(*)
414                                                               FROM pg_class t, pg_class i, pg_namespace ns, pg_index ix, pg_attribute a
415                                                               WHERE
416                                                                   t.oid=ix.indrelid
417                                                                   AND t.relnamespace=ns.oid
418                                                                   AND i.oid=ix.indexrelid
419                                                                   AND a.attrelid=t.oid
420                                                                   AND a.attnum=ANY(ix.indkey)
421                                                                   AND t.relkind IN ('r', 'm')
422                                                                   AND ns.nspname=%1
423                                                                   AND t.relname=%2
424                                                                   AND a.attname=%3;
425                                                               )""" ).arg(
426                                     QgsPostgresConn::quotedValue( schema ),
427                                     QgsPostgresConn::quotedValue( name ),
428                                     QgsPostgresConn::quotedValue( geometryColumn ) ) );
429   return !res.isEmpty() && !res.at( 0 ).isEmpty() && res.at( 0 ).at( 0 ).toBool();
430 }
431 
deleteSpatialIndex(const QString & schema,const QString & name,const QString & geometryColumn) const432 void QgsPostgresProviderConnection::deleteSpatialIndex( const QString &schema, const QString &name, const QString &geometryColumn ) const
433 {
434   checkCapability( Capability::DeleteSpatialIndex );
435 
436   const QList<QVariantList> res = executeSql( QStringLiteral( R"""(SELECT i.relname
437                                                                 FROM pg_class t, pg_class i, pg_namespace ns, pg_index ix, pg_attribute a
438                                                                 WHERE
439                                                                     t.oid=ix.indrelid
440                                                                     AND t.relnamespace=ns.oid
441                                                                     AND i.oid=ix.indexrelid
442                                                                     AND a.attrelid=t.oid
443                                                                     AND a.attnum=ANY(ix.indkey)
444                                                                     AND t.relkind='r'
445                                                                     AND ns.nspname=%1
446                                                                     AND t.relname=%2
447                                                                     AND a.attname=%3;
448                                                                 )""" ).arg(
449                                     QgsPostgresConn::quotedValue( schema ),
450                                     QgsPostgresConn::quotedValue( name ),
451                                     QgsPostgresConn::quotedValue( geometryColumn ) ) );
452   if ( res.isEmpty() )
453     throw QgsProviderConnectionException( QObject::tr( "No spatial index exists for %1.%2" ).arg( schema, name ) );
454 
455   const QString indexName = res.at( 0 ).at( 0 ).toString();
456 
457   executeSql( QStringLiteral( "DROP INDEX %1.%2" ).arg( QgsPostgresConn::quotedIdentifier( schema ),
458               QgsPostgresConn::quotedIdentifier( indexName ) ) );
459 }
460 
tables(const QString & schema,const TableFlags & flags) const461 QList<QgsPostgresProviderConnection::TableProperty> QgsPostgresProviderConnection::tables( const QString &schema, const TableFlags &flags ) const
462 {
463   checkCapability( Capability::Tables );
464   QList<QgsPostgresProviderConnection::TableProperty> tables;
465   QString errCause;
466   // TODO: set flags from the connection if flags argument is 0
467   const QgsDataSourceUri dsUri { uri() };
468   QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ) );
469   if ( !conn )
470   {
471     errCause = QObject::tr( "Connection failed: %1" ).arg( uri() );
472   }
473   else
474   {
475     QVector<QgsPostgresLayerProperty> properties;
476     const bool aspatial { ! flags || flags.testFlag( TableFlag::Aspatial ) };
477     bool ok = conn->supportedLayers( properties, false, schema == QStringLiteral( "public" ), aspatial, schema );
478     if ( ! ok )
479     {
480       errCause = QObject::tr( "Could not retrieve tables: %1" ).arg( uri() );
481     }
482     else
483     {
484 
485       bool dontResolveType = configuration().value( QStringLiteral( "dontResolveType" ), false ).toBool();
486       bool useEstimatedMetadata = configuration().value( QStringLiteral( "estimatedMetadata" ), false ).toBool();
487 
488       // Cannot be const:
489       for ( auto &pr : properties )
490       {
491         // Classify
492         TableFlags prFlags;
493         if ( pr.isView )
494         {
495           prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::View );
496         }
497         if ( pr.isMaterializedView )
498         {
499           prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::MaterializedView );
500         }
501         if ( pr.isForeignTable )
502         {
503           prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Foreign );
504         }
505         if ( pr.isRaster )
506         {
507           prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Raster );
508         }
509         else if ( pr.nSpCols != 0 )
510         {
511           prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Vector );
512         }
513         else
514         {
515           prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Aspatial );
516         }
517         // Filter
518         if ( ! flags || ( prFlags & flags ) )
519         {
520           // retrieve layer types if needed
521           if ( ! dontResolveType && ( !pr.geometryColName.isNull() &&
522                                       ( pr.types.value( 0, QgsWkbTypes::Unknown ) == QgsWkbTypes::Unknown ||
523                                         pr.srids.value( 0, std::numeric_limits<int>::min() ) == std::numeric_limits<int>::min() ) ) )
524           {
525             conn->retrieveLayerTypes( pr, useEstimatedMetadata );
526           }
527           QgsPostgresProviderConnection::TableProperty property;
528           property.setFlags( prFlags );
529           for ( int i = 0; i < std::min( pr.types.size(), pr.srids.size() ) ; i++ )
530           {
531             property.addGeometryColumnType( pr.types.at( i ), QgsCoordinateReferenceSystem::fromEpsgId( pr.srids.at( i ) ) );
532           }
533           property.setTableName( pr.tableName );
534           property.setSchema( pr.schemaName );
535           property.setGeometryColumn( pr.geometryColName );
536           // These are candidates, not actual PKs
537           // property.setPrimaryKeyColumns( pr.pkCols );
538           property.setGeometryColumnCount( static_cast<int>( pr.nSpCols ) );
539           property.setComment( pr.tableComment );
540 
541           // Get PKs
542           if ( pr.isView || pr.isMaterializedView || pr.isForeignTable )
543           {
544             // Set the candidates
545             property.setPrimaryKeyColumns( pr.pkCols );
546           }
547           else  // Fetch and set the real pks
548           {
549             try
550             {
551               const auto pks = executeSql( QStringLiteral( R"(
552               WITH pkrelid AS (
553               SELECT indexrelid AS idxri FROM pg_index WHERE indrelid='%1.%2'::regclass AND (indisprimary OR indisunique)
554                 ORDER BY CASE WHEN indisprimary THEN 1 ELSE 2 END LIMIT 1)
555               SELECT attname FROM pg_index,pg_attribute, pkrelid
556               WHERE indexrelid=pkrelid.idxri AND indrelid=attrelid AND pg_attribute.attnum=any(pg_index.indkey);
557              )" ).arg( QgsPostgresConn::quotedIdentifier( pr.schemaName ) )
558                                            .arg( QgsPostgresConn::quotedIdentifier( pr.tableName ) ) );
559               QStringList pkNames;
560               for ( const auto &pk : qgis::as_const( pks ) )
561               {
562                 pkNames.push_back( pk.first().toString() );
563               }
564               property.setPrimaryKeyColumns( pkNames );
565             }
566             catch ( const QgsProviderConnectionException &ex )
567             {
568               QgsDebugMsg( QStringLiteral( "Error retrieving primary keys: %1" ).arg( ex.what() ) );
569             }
570           }
571 
572           tables.push_back( property );
573         }
574       }
575     }
576     QgsPostgresConnPool::instance()->releaseConnection( conn );
577   }
578   if ( ! errCause.isEmpty() )
579   {
580     throw QgsProviderConnectionException( errCause );
581   }
582   return tables;
583 }
584 
schemas() const585 QStringList QgsPostgresProviderConnection::schemas( ) const
586 {
587   checkCapability( Capability::Schemas );
588   QStringList schemas;
589   QString errCause;
590   const QgsDataSourceUri dsUri { uri() };
591   QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ) );
592   if ( !conn )
593   {
594     errCause = QObject::tr( "Connection failed: %1" ).arg( uri() );
595   }
596   else
597   {
598     QList<QgsPostgresSchemaProperty> schemaProperties;
599     bool ok = conn->getSchemas( schemaProperties );
600     QgsPostgresConnPool::instance()->releaseConnection( conn );
601     if ( ! ok )
602     {
603       errCause = QObject::tr( "Could not retrieve schemas: %1" ).arg( uri() );
604     }
605     else
606     {
607       for ( const auto &s : qgis::as_const( schemaProperties ) )
608       {
609         schemas.push_back( s.name );
610       }
611     }
612   }
613   if ( ! errCause.isEmpty() )
614   {
615     throw QgsProviderConnectionException( errCause );
616   }
617   return schemas;
618 }
619 
620 
store(const QString & name) const621 void QgsPostgresProviderConnection::store( const QString &name ) const
622 {
623   // TODO: move this to class configuration?
624   QString baseKey = QStringLiteral( "/PostgreSQL/connections/" );
625   // delete the original entry first
626   remove( name );
627 
628   QgsSettings settings;
629   settings.beginGroup( baseKey );
630   settings.beginGroup( name );
631 
632   // From URI
633   const QgsDataSourceUri dsUri { uri() };
634   settings.setValue( "service", dsUri.service() );
635   settings.setValue( "host",  dsUri.host() );
636   settings.setValue( "port", dsUri.port() );
637   settings.setValue( "database", dsUri.database() );
638   settings.setValue( "username", dsUri.username() );
639   settings.setValue( "password", dsUri.password() );
640   settings.setValue( "authcfg", dsUri.authConfigId() );
641   settings.setEnumValue( "sslmode", dsUri.sslMode() );
642 
643   // From configuration
644   static const QStringList configurationParameters
645   {
646     QStringLiteral( "publicOnly" ),
647     QStringLiteral( "geometryColumnsOnly" ),
648     QStringLiteral( "dontResolveType" ),
649     QStringLiteral( "allowGeometrylessTables" ),
650     QStringLiteral( "saveUsername" ),
651     QStringLiteral( "savePassword" ),
652     QStringLiteral( "estimatedMetadata" ),
653     QStringLiteral( "projectsInDatabase" )
654   };
655   for ( const auto &p : configurationParameters )
656   {
657     if ( configuration().contains( p ) )
658     {
659       settings.setValue( p, configuration().value( p ) );
660     }
661   }
662   settings.endGroup();
663   settings.endGroup();
664 }
665 
remove(const QString & name) const666 void QgsPostgresProviderConnection::remove( const QString &name ) const
667 {
668   QgsPostgresConn::deleteConnection( name );
669 }
670 
icon() const671 QIcon QgsPostgresProviderConnection::icon() const
672 {
673   return QgsApplication::getThemeIcon( QStringLiteral( "mIconPostgis.svg" ) );
674 }
675 
676 
nativeTypes() const677 QList<QgsVectorDataProvider::NativeType> QgsPostgresProviderConnection::nativeTypes() const
678 {
679   QList<QgsVectorDataProvider::NativeType> types;
680   QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( QgsDataSourceUri{ uri() }.connectionInfo( false ) );
681   if ( conn )
682   {
683     types = conn->nativeTypes();
684     QgsPostgresConnPool::instance()->releaseConnection( conn );
685   }
686   if ( types.isEmpty() )
687   {
688     throw QgsProviderConnectionException( QObject::tr( "Error retrieving native types for connection %1" ).arg( uri() ) );
689   }
690   return types;
691 }
692 
693 
fields(const QString & schema,const QString & tableName) const694 QgsFields QgsPostgresProviderConnection::fields( const QString &schema, const QString &tableName ) const
695 {
696   // Try the base implementation first and fall back to a more complex approch for the
697   // few PG-specific corner cases that do not work with the base implementation.
698   try
699   {
700     return QgsAbstractDatabaseProviderConnection::fields( schema, tableName );
701   }
702   catch ( QgsProviderConnectionException &ex )
703   {
704     // This table might expose multiple geometry columns (different geom type or SRID)
705     // but we are only interested in fields here, so let's pick the first one.
706     TableProperty tableInfo { table( schema, tableName ) };
707     try
708     {
709       QgsDataSourceUri tUri { tableUri( schema, tableName ) };
710       if ( tableInfo.geometryColumnTypes().count( ) > 1 )
711       {
712         TableProperty::GeometryColumnType geomCol { tableInfo.geometryColumnTypes().first() };
713         tUri.setGeometryColumn( tableInfo.geometryColumn() );
714         tUri.setWkbType( geomCol.wkbType );
715         tUri.setSrid( QString::number( geomCol.crs.postgisSrid() ) );
716       }
717       if ( tableInfo.primaryKeyColumns().count() > 0 )
718       {
719         tUri.setKeyColumn( tableInfo.primaryKeyColumns().first() );
720       }
721       tUri.setParam( QStringLiteral( "checkPrimaryKeyUnicity" ), QLatin1String( "0" ) );
722       QgsVectorLayer::LayerOptions options { false, true };
723       options.skipCrsValidation = true;
724       QgsVectorLayer vl { tUri.uri(), QStringLiteral( "temp_layer" ), mProviderKey, options };
725       if ( vl.isValid() )
726       {
727         return vl.fields();
728       }
729     }
730     catch ( QgsProviderConnectionException & )
731     {
732       // fall-through
733     }
734     throw ex;
735   }
736 }
737