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