1 /***************************************************************************
2   qgspostgresdataitemguiprovider.cpp
3   --------------------------------------
4   Date                 : June 2019
5   Copyright            : (C) 2019 by Martin Dobias
6   Email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgspostgresdataitemguiprovider.h"
17 
18 #include "qgsmanageconnectionsdialog.h"
19 #include "qgspostgresdataitems.h"
20 #include "qgspostgresprovider.h"
21 #include "qgspgnewconnection.h"
22 #include "qgsnewnamedialog.h"
23 #include "qgspgsourceselect.h"
24 
25 #include <QFileDialog>
26 #include <QInputDialog>
27 #include <QMessageBox>
28 
29 
populateContextMenu(QgsDataItem * item,QMenu * menu,const QList<QgsDataItem * > &,QgsDataItemGuiContext context)30 void QgsPostgresDataItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &, QgsDataItemGuiContext context )
31 {
32   if ( QgsPGRootItem *rootItem = qobject_cast< QgsPGRootItem * >( item ) )
33   {
34     QAction *actionNew = new QAction( tr( "New Connection…" ), this );
35     connect( actionNew, &QAction::triggered, this, [rootItem] { newConnection( rootItem ); } );
36     menu->addAction( actionNew );
37 
38     QAction *actionSaveServers = new QAction( tr( "Save Connections…" ), this );
39     connect( actionSaveServers, &QAction::triggered, this, [] { saveConnections(); } );
40     menu->addAction( actionSaveServers );
41 
42     QAction *actionLoadServers = new QAction( tr( "Load Connections…" ), this );
43     connect( actionLoadServers, &QAction::triggered, this, [rootItem] { loadConnections( rootItem ); } );
44     menu->addAction( actionLoadServers );
45   }
46 
47   if ( QgsPGConnectionItem *connItem = qobject_cast< QgsPGConnectionItem * >( item ) )
48   {
49     QAction *actionRefresh = new QAction( tr( "Refresh" ), this );
50     connect( actionRefresh, &QAction::triggered, this, [connItem] { refreshConnection( connItem ); } );
51     menu->addAction( actionRefresh );
52 
53     menu->addSeparator();
54 
55     QAction *actionEdit = new QAction( tr( "Edit Connection…" ), this );
56     connect( actionEdit, &QAction::triggered, this, [connItem] { editConnection( connItem ); } );
57     menu->addAction( actionEdit );
58 
59     QAction *actionDelete = new QAction( tr( "Delete Connection" ), this );
60     connect( actionDelete, &QAction::triggered, this, [connItem] { deleteConnection( connItem ); } );
61     menu->addAction( actionDelete );
62 
63     menu->addSeparator();
64 
65     QAction *actionCreateSchema = new QAction( tr( "New Schema…" ), this );
66     connect( actionCreateSchema, &QAction::triggered, this, [connItem, context] { createSchema( connItem, context ); } );
67     menu->addAction( actionCreateSchema );
68 
69   }
70 
71   if ( QgsPGSchemaItem *schemaItem = qobject_cast< QgsPGSchemaItem * >( item ) )
72   {
73     QAction *actionRefresh = new QAction( tr( "Refresh" ), this );
74     connect( actionRefresh, &QAction::triggered, this, [schemaItem] { schemaItem->refresh(); } );
75     menu->addAction( actionRefresh );
76 
77     menu->addSeparator();
78 
79     QMenu *maintainMenu = new QMenu( tr( "Schema Operations" ), menu );
80 
81     QAction *actionRename = new QAction( tr( "Rename Schema…" ), this );
82     connect( actionRename, &QAction::triggered, this, [schemaItem, context] { renameSchema( schemaItem, context ); } );
83     maintainMenu->addAction( actionRename );
84 
85     QAction *actionDelete = new QAction( tr( "Delete Schema…" ), this );
86     connect( actionDelete, &QAction::triggered, this, [schemaItem, context] { deleteSchema( schemaItem, context ); } );
87     maintainMenu->addAction( actionDelete );
88 
89     menu->addMenu( maintainMenu );
90   }
91 
92   if ( QgsPGLayerItem *layerItem = qobject_cast< QgsPGLayerItem * >( item ) )
93   {
94     const QgsPostgresLayerProperty &layerInfo = layerItem->layerInfo();
95     QString typeName = layerInfo.isView ? tr( "View" ) : tr( "Table" );
96 
97     QMenu *maintainMenu = new QMenu( tr( "%1 Operations" ).arg( typeName ), menu );
98 
99     QAction *actionRenameLayer = new QAction( tr( "Rename %1…" ).arg( typeName ), this );
100     connect( actionRenameLayer, &QAction::triggered, this, [layerItem, context] { renameLayer( layerItem, context ); } );
101     maintainMenu->addAction( actionRenameLayer );
102 
103     if ( !layerInfo.isView )
104     {
105       QAction *actionTruncateLayer = new QAction( tr( "Truncate %1…" ).arg( typeName ), this );
106       connect( actionTruncateLayer, &QAction::triggered, this, [layerItem, context] { truncateTable( layerItem, context ); } );
107       maintainMenu->addAction( actionTruncateLayer );
108     }
109 
110     if ( layerInfo.isMaterializedView )
111     {
112       QAction *actionRefreshMaterializedView = new QAction( tr( "Refresh Materialized View…" ), this );
113       connect( actionRefreshMaterializedView, &QAction::triggered, this, [layerItem, context] { refreshMaterializedView( layerItem, context ); } );
114       maintainMenu->addAction( actionRefreshMaterializedView );
115     }
116     menu->addMenu( maintainMenu );
117   }
118 }
119 
120 
deleteLayer(QgsLayerItem * item,QgsDataItemGuiContext context)121 bool QgsPostgresDataItemGuiProvider::deleteLayer( QgsLayerItem *item, QgsDataItemGuiContext context )
122 {
123   if ( QgsPGLayerItem *layerItem = qobject_cast< QgsPGLayerItem * >( item ) )
124   {
125     const QgsPostgresLayerProperty &layerInfo = layerItem->layerInfo();
126     const QString typeName = layerInfo.isView ? tr( "View" ) : tr( "Table" );
127 
128     if ( QMessageBox::question( nullptr, tr( "Delete %1" ).arg( typeName ),
129                                 QObject::tr( "Are you sure you want to delete %1 '%2.%3'?" ).arg( typeName.toLower(), layerInfo.schemaName, layerInfo.tableName ),
130                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
131       return false;
132 
133     QString errCause;
134     const bool res = QgsPostgresUtils::deleteLayer( layerItem->uri(), errCause );
135     if ( !res )
136     {
137       notify( tr( "Delete %1" ).arg( typeName ), errCause, context, Qgis::MessageLevel::Warning );
138       return false;
139     }
140     else
141     {
142       notify( tr( "Delete %1" ).arg( typeName ), tr( "%1 '%2' deleted successfully." ).arg( typeName, layerInfo.tableName ), context, Qgis::MessageLevel::Success );
143       if ( layerItem->parent() )
144         layerItem->parent()->refresh();
145       return true;
146     }
147   }
148   return false;
149 }
150 
acceptDrop(QgsDataItem * item,QgsDataItemGuiContext)151 bool QgsPostgresDataItemGuiProvider::acceptDrop( QgsDataItem *item, QgsDataItemGuiContext )
152 {
153   if ( qobject_cast< QgsPGConnectionItem * >( item ) )
154     return true;
155   if ( qobject_cast< QgsPGSchemaItem * >( item ) )
156     return true;
157 
158   return false;
159 }
160 
handleDrop(QgsDataItem * item,QgsDataItemGuiContext,const QMimeData * data,Qt::DropAction)161 bool QgsPostgresDataItemGuiProvider::handleDrop( QgsDataItem *item, QgsDataItemGuiContext, const QMimeData *data, Qt::DropAction )
162 {
163   if ( QgsPGConnectionItem *connItem = qobject_cast< QgsPGConnectionItem * >( item ) )
164   {
165     return connItem->handleDrop( data, QString() );
166   }
167   else if ( QgsPGSchemaItem *schemaItem = qobject_cast< QgsPGSchemaItem * >( item ) )
168   {
169     QgsPGConnectionItem *connItem = qobject_cast<QgsPGConnectionItem *>( schemaItem->parent() );
170     if ( !connItem )
171       return false;
172 
173     return connItem->handleDrop( data, schemaItem->name() );
174   }
175   return false;
176 }
177 
createParamWidget(QgsDataItem * root,QgsDataItemGuiContext)178 QWidget *QgsPostgresDataItemGuiProvider::createParamWidget( QgsDataItem *root, QgsDataItemGuiContext )
179 {
180   QgsPGRootItem *pgRootItem = qobject_cast<QgsPGRootItem *>( root );
181   if ( pgRootItem != nullptr )
182   {
183     QgsPgSourceSelect *select = new QgsPgSourceSelect( nullptr, QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode::Manager );
184     connect( select, &QgsPgSourceSelect::connectionsChanged, pgRootItem, &QgsPGRootItem::onConnectionsChanged );
185     return select;
186   }
187   else
188   {
189     return nullptr;
190   }
191 }
192 
193 
newConnection(QgsDataItem * item)194 void QgsPostgresDataItemGuiProvider::newConnection( QgsDataItem *item )
195 {
196   QgsPgNewConnection nc( nullptr );
197   if ( nc.exec() )
198   {
199     item->refresh();
200   }
201 }
202 
editConnection(QgsDataItem * item)203 void QgsPostgresDataItemGuiProvider::editConnection( QgsDataItem *item )
204 {
205   QgsPgNewConnection nc( nullptr, item->name() );
206   nc.setWindowTitle( tr( "Edit PostGIS Connection" ) );
207   if ( nc.exec() )
208   {
209     // the parent should be updated
210     if ( item->parent() )
211       item->parent()->refreshConnections();
212   }
213 }
214 
deleteConnection(QgsDataItem * item)215 void QgsPostgresDataItemGuiProvider::deleteConnection( QgsDataItem *item )
216 {
217   if ( QMessageBox::question( nullptr, QObject::tr( "Delete Connection" ),
218                               QObject::tr( "Are you sure you want to delete the connection to %1?" ).arg( item->name() ),
219                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
220     return;
221 
222   QgsPostgresConn::deleteConnection( item->name() );
223   // the parent should be updated
224   if ( item->parent() )
225     item->parent()->refreshConnections();
226 }
227 
refreshConnection(QgsDataItem * item)228 void QgsPostgresDataItemGuiProvider::refreshConnection( QgsDataItem *item )
229 {
230   item->refresh();
231   // the parent should be updated
232   if ( item->parent() )
233     item->parent()->refreshConnections();
234 }
235 
createSchema(QgsDataItem * item,QgsDataItemGuiContext context)236 void QgsPostgresDataItemGuiProvider::createSchema( QgsDataItem *item, QgsDataItemGuiContext context )
237 {
238   QString schemaName = QInputDialog::getText( nullptr, tr( "Create Schema" ), tr( "Schema name:" ) );
239   if ( schemaName.isEmpty() )
240     return;
241 
242   QgsDataSourceUri uri = QgsPostgresConn::connUri( item->name() );
243   QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri.connectionInfo( false ), false );
244   if ( !conn )
245   {
246     notify( tr( "New Schema" ), tr( "Unable to create schema." ), context, Qgis::MessageLevel::Warning );
247     return;
248   }
249 
250   //create the schema
251   QString sql = QStringLiteral( "CREATE SCHEMA %1" ).arg( QgsPostgresConn::quotedIdentifier( schemaName ) );
252 
253   QgsPostgresResult result( conn->PQexec( sql ) );
254   if ( result.PQresultStatus() != PGRES_COMMAND_OK )
255   {
256     notify( tr( "New Schema" ), tr( "Unable to create schema '%1'\n%2" ).arg( schemaName,
257             result.PQresultErrorMessage() ), context, Qgis::MessageLevel::Warning );
258     conn->unref();
259     return;
260   }
261 
262   conn->unref();
263 
264   notify( tr( "New Schema" ), tr( "Schema '%1' created successfully." ).arg( schemaName ), context, Qgis::MessageLevel::Success );
265 
266   item->refresh();
267   // the parent should be updated
268   if ( item->parent() )
269     item->parent()->refreshConnections();
270 }
271 
272 
deleteSchema(QgsPGSchemaItem * schemaItem,QgsDataItemGuiContext context)273 void QgsPostgresDataItemGuiProvider::deleteSchema( QgsPGSchemaItem *schemaItem, QgsDataItemGuiContext context )
274 {
275   // check if schema contains tables/views
276   QgsDataSourceUri uri = QgsPostgresConn::connUri( schemaItem->connectionName() );
277   QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri.connectionInfo( false ), false );
278   if ( !conn )
279   {
280     notify( tr( "Delete Schema" ), tr( "Unable to delete schema." ), context, Qgis::MessageLevel::Warning );
281     return;
282   }
283 
284   QString sql = QStringLiteral( "SELECT table_name FROM information_schema.tables WHERE table_schema='%1'" ).arg( schemaItem->name() );
285   QgsPostgresResult result( conn->PQexec( sql ) );
286   if ( result.PQresultStatus() != PGRES_TUPLES_OK )
287   {
288     notify( tr( "Delete Schema" ), tr( "Unable to delete schema." ), context, Qgis::MessageLevel::Warning );
289     conn->unref();
290     return;
291   }
292 
293   QStringList childObjects;
294   int maxListed = 10;
295   for ( int idx = 0; idx < result.PQntuples(); idx++ )
296   {
297     childObjects << result.PQgetvalue( idx, 0 );
298     QgsPostgresSchemaProperty schema;
299     if ( idx == maxListed - 1 )
300       break;
301   }
302 
303   int count = result.PQntuples();
304   if ( count > 0 )
305   {
306     QString objects = childObjects.join( QLatin1Char( '\n' ) );
307     if ( count > maxListed )
308     {
309       objects += QStringLiteral( "\n[%1 additional objects not listed]" ).arg( count - maxListed );
310     }
311     if ( QMessageBox::question( nullptr, QObject::tr( "Delete Schema" ),
312                                 QObject::tr( "Schema '%1' contains objects:\n\n%2\n\nAre you sure you want to delete the schema and all these objects?" ).arg( schemaItem->name(), objects ),
313                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
314     {
315       conn->unref();
316       return;
317     }
318   }
319   else
320   {
321     if ( QMessageBox::question( nullptr, QObject::tr( "Delete Schema" ),
322                                 QObject::tr( "Are you sure you want to delete schema '%1'?" ).arg( schemaItem->name() ),
323                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
324       return;
325   }
326 
327   QString errCause;
328   bool res = QgsPostgresUtils::deleteSchema( schemaItem->name(), uri, errCause, count > 0 );
329   if ( !res )
330   {
331     notify( tr( "Delete Schema" ), tr( "Unable to delete schema: '%1'." ).arg( errCause ), context, Qgis::MessageLevel::Warning );
332   }
333   else
334   {
335     notify( tr( "Delete Schema" ), tr( "Schema '%1' deleted successfully." ).arg( schemaItem->name() ), context, Qgis::MessageLevel::Success );
336     if ( schemaItem->parent() )
337       schemaItem->parent()->refresh();
338   }
339 }
340 
renameSchema(QgsPGSchemaItem * schemaItem,QgsDataItemGuiContext context)341 void QgsPostgresDataItemGuiProvider::renameSchema( QgsPGSchemaItem *schemaItem, QgsDataItemGuiContext context )
342 {
343   QgsNewNameDialog dlg( tr( "schema '%1'" ).arg( schemaItem->name() ), schemaItem->name() );
344   dlg.setWindowTitle( tr( "Rename Schema" ) );
345   if ( dlg.exec() != QDialog::Accepted || dlg.name() == schemaItem->name() )
346     return;
347 
348   QString schemaName = QgsPostgresConn::quotedIdentifier( schemaItem->name() );
349   QgsDataSourceUri uri = QgsPostgresConn::connUri( schemaItem->connectionName() );
350   QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri.connectionInfo( false ), false );
351   if ( !conn )
352   {
353     notify( tr( "Rename Schema" ), tr( "Unable to rename schema." ), context, Qgis::MessageLevel::Warning );
354     return;
355   }
356 
357   //rename the schema
358   QString sql = QStringLiteral( "ALTER SCHEMA %1 RENAME TO %2" )
359                 .arg( schemaName, QgsPostgresConn::quotedIdentifier( dlg.name() ) );
360 
361   QgsPostgresResult result( conn->PQexec( sql ) );
362   if ( result.PQresultStatus() != PGRES_COMMAND_OK )
363   {
364     notify( tr( "Rename Schema" ), tr( "Unable to rename schema '%1'\n%2" ).arg( schemaItem->name(),
365             result.PQresultErrorMessage() ), context, Qgis::MessageLevel::Warning );
366     conn->unref();
367     return;
368   }
369 
370   notify( tr( "Rename Schema" ), tr( "Schema '%1' renamed correctly to '%2'." ).arg( schemaItem->name(),
371           dlg.name() ), context, Qgis::MessageLevel::Success );
372 
373   conn->unref();
374   if ( schemaItem->parent() )
375     schemaItem->parent()->refresh();
376 }
377 
renameLayer(QgsPGLayerItem * layerItem,QgsDataItemGuiContext context)378 void QgsPostgresDataItemGuiProvider::renameLayer( QgsPGLayerItem *layerItem, QgsDataItemGuiContext context )
379 {
380   const QgsPostgresLayerProperty &layerInfo = layerItem->layerInfo();
381   QString typeName = layerInfo.isView ? tr( "View" ) : tr( "Table" );
382   QString lowerTypeName = layerInfo.isView ? tr( "view" ) : tr( "table" );
383 
384   QgsNewNameDialog dlg( tr( "%1 %2.%3" ).arg( lowerTypeName, layerInfo.schemaName, layerInfo.tableName ), layerInfo.tableName );
385   dlg.setWindowTitle( tr( "Rename %1" ).arg( typeName ) );
386   if ( dlg.exec() != QDialog::Accepted || dlg.name() == layerInfo.tableName )
387     return;
388 
389   QString schemaName = layerInfo.schemaName;
390   QString tableName = layerInfo.tableName;
391   QString schemaTableName;
392   if ( !schemaName.isEmpty() )
393   {
394     schemaTableName = QgsPostgresConn::quotedIdentifier( schemaName ) + '.';
395   }
396   QString oldName = schemaTableName + QgsPostgresConn::quotedIdentifier( tableName );
397   QString newName = QgsPostgresConn::quotedIdentifier( dlg.name() );
398 
399   QgsDataSourceUri dsUri( layerItem->uri() );
400   QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri.connectionInfo( false ), false );
401   if ( !conn )
402   {
403     notify( tr( "Rename %1" ).arg( typeName ), tr( "Unable to rename '%1'." ).arg( lowerTypeName ), context, Qgis::MessageLevel::Warning );
404     return;
405   }
406 
407   //rename the layer
408   QString sql;
409   if ( layerInfo.isView )
410   {
411     sql = QStringLiteral( "ALTER %1 VIEW %2 RENAME TO %3" ).arg( layerInfo.relKind == QLatin1String( "m" ) ? QStringLiteral( "MATERIALIZED" ) : QString(),
412           oldName, newName );
413   }
414   else
415   {
416     sql = QStringLiteral( "ALTER TABLE %1 RENAME TO %2" ).arg( oldName, newName );
417   }
418 
419   QgsPostgresResult result( conn->PQexec( sql ) );
420   if ( result.PQresultStatus() != PGRES_COMMAND_OK )
421   {
422     notify( tr( "Rename %1" ).arg( typeName ), tr( "Unable to rename '%1' %2\n%3" ).arg( lowerTypeName, layerItem->name(),
423             result.PQresultErrorMessage() ), context, Qgis::MessageLevel::Warning );
424     conn->unref();
425     return;
426   }
427 
428   notify( tr( "Rename %1" ).arg( typeName ), tr( "%1 '%2' renamed correctly to '%3'." ).arg( typeName, oldName, newName ),
429           context, Qgis::MessageLevel::Success );
430 
431   conn->unref();
432 
433   if ( layerItem->parent() )
434     layerItem->parent()->refresh();
435 }
436 
truncateTable(QgsPGLayerItem * layerItem,QgsDataItemGuiContext context)437 void QgsPostgresDataItemGuiProvider::truncateTable( QgsPGLayerItem *layerItem, QgsDataItemGuiContext context )
438 {
439   const QgsPostgresLayerProperty &layerInfo = layerItem->layerInfo();
440   if ( QMessageBox::question( nullptr, QObject::tr( "Truncate Table" ),
441                               QObject::tr( "Are you sure you want to truncate \"%1.%2\"?\n\nThis will delete all data within the table." ).arg( layerInfo.schemaName, layerInfo.tableName ),
442                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
443     return;
444 
445   QgsDataSourceUri dsUri( layerItem->uri() );
446   QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri.connectionInfo( false ), false );
447   if ( !conn )
448   {
449     notify( tr( "Truncate Table" ), tr( "Unable to truncate table." ), context, Qgis::MessageLevel::Warning );
450     return;
451   }
452 
453   QString schemaName = layerInfo.schemaName;
454   QString tableName = layerInfo.tableName;
455   QString schemaTableName;
456   if ( !schemaName.isEmpty() )
457   {
458     schemaTableName = QgsPostgresConn::quotedIdentifier( schemaName ) + '.';
459   }
460   QString tableRef = schemaTableName + QgsPostgresConn::quotedIdentifier( tableName );
461 
462   QString sql = QStringLiteral( "TRUNCATE TABLE %1" ).arg( tableRef );
463 
464   QgsPostgresResult result( conn->PQexec( sql ) );
465   if ( result.PQresultStatus() != PGRES_COMMAND_OK )
466   {
467     notify( tr( "Truncate Table" ), tr( "Unable to truncate '%1'\n%2" ).arg( tableName,
468             result.PQresultErrorMessage() ), context, Qgis::MessageLevel::Warning );
469     conn->unref();
470     return;
471   }
472 
473   conn->unref();
474   notify( tr( "Truncate Table" ), tr( "Table '%1' truncated successfully." ).arg( tableName ), context, Qgis::MessageLevel::Success );
475 }
476 
refreshMaterializedView(QgsPGLayerItem * layerItem,QgsDataItemGuiContext context)477 void QgsPostgresDataItemGuiProvider::refreshMaterializedView( QgsPGLayerItem *layerItem, QgsDataItemGuiContext context )
478 {
479   const QgsPostgresLayerProperty &layerInfo = layerItem->layerInfo();
480   if ( QMessageBox::question( nullptr, QObject::tr( "Refresh Materialized View" ),
481                               QObject::tr( "Are you sure you want to refresh the materialized view \"%1.%2\"?\n\nThis will update all data within the table." ).arg( layerInfo.schemaName, layerInfo.tableName ),
482                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
483     return;
484 
485   QgsDataSourceUri dsUri( layerItem->uri() );
486   QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri.connectionInfo( false ), false );
487   if ( !conn )
488   {
489     notify( tr( "Refresh View" ), tr( "Unable to refresh the view." ), context, Qgis::MessageLevel::Warning );
490     return;
491   }
492 
493   QString schemaName = layerInfo.schemaName;
494   QString tableName = layerInfo.tableName;
495   QString schemaTableName;
496   if ( !schemaName.isEmpty() )
497   {
498     schemaTableName = QgsPostgresConn::quotedIdentifier( schemaName ) + '.';
499   }
500   QString tableRef = schemaTableName + QgsPostgresConn::quotedIdentifier( tableName );
501 
502   QString sql = QStringLiteral( "REFRESH MATERIALIZED VIEW CONCURRENTLY %1" ).arg( tableRef );
503 
504   QgsPostgresResult result( conn->PQexec( sql ) );
505   if ( result.PQresultStatus() != PGRES_COMMAND_OK )
506   {
507     notify( tr( "Refresh View" ), tr( "Unable to refresh the view '%1'\n%2" ).arg( tableRef,
508             result.PQresultErrorMessage() ), context, Qgis::MessageLevel::Warning );
509     conn->unref();
510     return;
511   }
512 
513   conn->unref();
514   notify( tr( "Refresh View" ), tr( "Materialized view '%1' refreshed successfully." ).arg( tableName ), context, Qgis::MessageLevel::Success );
515 }
516 
saveConnections()517 void QgsPostgresDataItemGuiProvider::saveConnections()
518 {
519   QgsManageConnectionsDialog dlg( nullptr, QgsManageConnectionsDialog::Export, QgsManageConnectionsDialog::PostGIS );
520   dlg.exec();
521 }
522 
loadConnections(QgsDataItem * item)523 void QgsPostgresDataItemGuiProvider::loadConnections( QgsDataItem *item )
524 {
525   QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Connections" ), QDir::homePath(),
526                      tr( "XML files (*.xml *.XML)" ) );
527   if ( fileName.isEmpty() )
528   {
529     return;
530   }
531 
532   QgsManageConnectionsDialog dlg( nullptr, QgsManageConnectionsDialog::Import, QgsManageConnectionsDialog::PostGIS, fileName );
533   if ( dlg.exec() == QDialog::Accepted )
534     item->refreshConnections();
535 }
536