1 /***************************************************************************
2            qgsvirtuallayerprovider.cpp Virtual layer data provider
3 begin                : Jan, 2015
4 copyright            : (C) 2015 Hugo Mercier, Oslandia
5 email                : hugo dot mercier at oslandia dot com
6  ***************************************************************************/
7 
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 
17 extern "C"
18 {
19 #include <sqlite3.h>
20 #include <spatialite.h>
21 }
22 
23 #include <QUrl>
24 
25 #include <stdexcept>
26 
27 #include "qgsvirtuallayerprovider.h"
28 #include "qgsvirtuallayerdefinition.h"
29 #include "qgsvirtuallayerfeatureiterator.h"
30 #include "qgsvectorlayer.h"
31 #include "qgsproject.h"
32 #include "qgslogger.h"
33 #include "qgsapplication.h"
34 
35 #include "qgsvirtuallayerprovider.h"
36 #include "qgsvirtuallayersqlitemodule.h"
37 #include "qgsvirtuallayerqueryparser.h"
38 
39 #ifdef HAVE_GUI
40 #include "qgssourceselectprovider.h"
41 #include "qgsvirtuallayersourceselect.h"
42 #endif
43 
44 const QString VIRTUAL_LAYER_KEY = QStringLiteral( "virtual" );
45 const QString VIRTUAL_LAYER_DESCRIPTION = QStringLiteral( "Virtual layer data provider" );
46 
47 const QString VIRTUAL_LAYER_QUERY_VIEW = QStringLiteral( "_query" );
48 
quotedColumn(QString name)49 static QString quotedColumn( QString name )
50 {
51   return "\"" + name.replace( QLatin1String( "\"" ), QLatin1String( "\"\"" ) ) + "\"";
52 }
53 
54 #define PROVIDER_ERROR( msg ) do { mError = QgsError( msg, VIRTUAL_LAYER_KEY ); QgsDebugMsg( msg ); } while(0)
55 
56 
QgsVirtualLayerProvider(QString const & uri,const QgsDataProvider::ProviderOptions & options,QgsDataProvider::ReadFlags flags)57 QgsVirtualLayerProvider::QgsVirtualLayerProvider( QString const &uri,
58     const QgsDataProvider::ProviderOptions &options,
59     QgsDataProvider::ReadFlags flags )
60   : QgsVectorDataProvider( uri, options, flags )
61 {
62   mError.clear();
63 
64   QUrl url = QUrl::fromEncoded( uri.toUtf8() );
65   if ( !url.isValid() )
66   {
67     mValid = false;
68     PROVIDER_ERROR( "Malformed URL" );
69     return;
70   }
71 
72   // xxxxx = open a virtual layer
73   // xxxxx?key=value&key=value = create a virtual layer
74   // ?key=value = create a temporary virtual layer
75 
76   // read url
77   try
78   {
79     mDefinition = QgsVirtualLayerDefinition::fromUrl( url );
80 
81     mSubset = mDefinition.subsetString();
82 
83     if ( !mDefinition.isLazy() )
84     {
85       reloadData();
86     }
87   }
88   catch ( std::runtime_error &e )
89   {
90     mValid = false;
91     PROVIDER_ERROR( e.what() );
92     return;
93   }
94 
95   if ( mDefinition.geometrySrid() != -1 )
96   {
97     Q_NOWARN_DEPRECATED_PUSH
98     mCrs = QgsCoordinateReferenceSystem( mDefinition.geometrySrid() );
99     Q_NOWARN_DEPRECATED_POP
100   }
101 }
102 
reloadProviderData()103 void QgsVirtualLayerProvider::reloadProviderData()
104 {
105   if ( mDefinition.sourceLayers().empty() && !mDefinition.filePath().isEmpty() && mDefinition.query().isEmpty() )
106   {
107     // open the file
108     mValid = openIt();
109   }
110   else
111   {
112     // create the file
113     mValid = createIt();
114   }
115 }
116 
loadSourceLayers()117 bool QgsVirtualLayerProvider::loadSourceLayers()
118 {
119   const auto constSourceLayers = mDefinition.sourceLayers();
120   for ( const QgsVirtualLayerDefinition::SourceLayer &layer : constSourceLayers )
121   {
122     if ( layer.isReferenced() )
123     {
124       QgsMapLayer *l = QgsProject::instance()->mapLayer( layer.reference() );
125       if ( !l )
126       {
127         PROVIDER_ERROR( QString( "Cannot find layer %1" ).arg( layer.reference() ) );
128         return false;
129       }
130       if ( l->type() != QgsMapLayerType::VectorLayer )
131       {
132         PROVIDER_ERROR( QString( "Layer %1 is not a vector layer" ).arg( layer.reference() ) );
133         return false;
134       }
135       // add the layer to the list
136       QgsVectorLayer *vl = static_cast<QgsVectorLayer *>( l );
137       mLayers << SourceLayer( vl, layer.name() );
138       // connect to modification signals to invalidate statistics
139       connect( vl, &QgsVectorLayer::featureAdded, this, &QgsVirtualLayerProvider::invalidateStatistics );
140       connect( vl, &QgsVectorLayer::featureDeleted, this, &QgsVirtualLayerProvider::invalidateStatistics );
141       connect( vl, &QgsVectorLayer::geometryChanged, this, &QgsVirtualLayerProvider::invalidateStatistics );
142       connect( vl, &QgsVectorLayer::updatedFields, this, [ = ] { createVirtualTable( vl, layer.name() ); } );
143     }
144     else
145     {
146       mLayers << SourceLayer( layer.provider(), layer.source(), layer.name(), layer.encoding() );
147     }
148   }
149   return true;
150 }
151 
openIt()152 bool QgsVirtualLayerProvider::openIt()
153 {
154   spatialite_init( 0 );
155 
156   mPath = mDefinition.filePath();
157 
158   try
159   {
160     QgsScopedSqlite p( mPath );
161     mSqlite = p;
162   }
163   catch ( std::runtime_error &e )
164   {
165     PROVIDER_ERROR( e.what() );
166     return false;
167   }
168 
169   {
170     Sqlite::Query q( mSqlite.get(), QStringLiteral( "SELECT name FROM sqlite_master WHERE name='_meta'" ) );
171     if ( q.step() != SQLITE_ROW )
172     {
173       PROVIDER_ERROR( "No metadata tables!" );
174       return false;
175     }
176   }
177   // look for the correct version and the stored url
178   {
179     Sqlite::Query q( mSqlite.get(), QStringLiteral( "SELECT version, url FROM _meta" ) );
180     int version = 0;
181     if ( q.step() == SQLITE_ROW )
182     {
183       version = q.columnInt( 0 );
184       if ( version != VIRTUAL_LAYER_VERSION )
185       {
186         PROVIDER_ERROR( "Wrong virtual layer version!" );
187         return false;
188       }
189       mDefinition = QgsVirtualLayerDefinition::fromUrl( QUrl( q.columnText( 1 ) ) );
190     }
191   }
192   // overwrite the uri part of the definition
193   mDefinition.setFilePath( mPath );
194 
195 
196   // load source layers
197   if ( !loadSourceLayers() )
198   {
199     return false;
200   }
201 
202   /* only one table */
203   if ( mDefinition.query().isEmpty() )
204   {
205     mTableName = mLayers[0].name;
206   }
207   else
208   {
209     mTableName = VIRTUAL_LAYER_QUERY_VIEW;
210   }
211 
212   mSubset = mDefinition.subsetString();
213 
214   return true;
215 }
216 
createIt()217 bool QgsVirtualLayerProvider::createIt()
218 {
219   using namespace QgsVirtualLayerQueryParser;
220 
221   // consistency check
222   if ( mDefinition.sourceLayers().size() > 1 && mDefinition.query().isEmpty() )
223   {
224     PROVIDER_ERROR( QString( "Don't know how to join layers, please specify a query" ) );
225     return false;
226   }
227 
228   if ( mDefinition.sourceLayers().empty() && mDefinition.filePath().isEmpty() && mDefinition.query().isEmpty() )
229   {
230     PROVIDER_ERROR( QString( "Please specify at least one source layer or a query" ) );
231     return false;
232   }
233 
234   if ( !mDefinition.filePath().isEmpty() && mDefinition.hasReferencedLayers() )
235   {
236     PROVIDER_ERROR( QString( "Cannot store referenced layers" ) );
237     return false;
238   }
239 
240 
241   QVector<ColumnDef> gFields;
242   if ( !mDefinition.query().isEmpty() )
243   {
244 
245     QStringList tables = referencedTables( mDefinition.query() );
246     const auto constTables = tables;
247     for ( const QString &tname : constTables )
248     {
249       // is it in source layers ?
250       if ( mDefinition.hasSourceLayer( tname ) )
251       {
252         continue;
253       }
254       // is it in loaded layers ?
255       bool found = false;
256       const auto constMapLayers = QgsProject::instance()->mapLayers();
257       for ( const QgsMapLayer *l : constMapLayers )
258       {
259         if ( l->type() != QgsMapLayerType::VectorLayer )
260           continue;
261 
262         const QgsVectorLayer *vl = static_cast<const QgsVectorLayer *>( l );
263         if ( ( vl->name() == tname ) || ( vl->name().compare( tname.toLower(), Qt::CaseInsensitive ) == 0 ) || ( vl->id() == tname ) )
264         {
265           mDefinition.addSource( tname, vl->id() );
266           found = true;
267           break;
268         }
269       }
270       if ( !found )
271       {
272         PROVIDER_ERROR( QString( "Referenced table %1 in query not found!" ).arg( tname ) );
273         return false;
274       }
275     }
276   }
277 
278   QString path;
279   mPath = mDefinition.filePath();
280   // use a temporary file if needed
281   if ( mPath.isEmpty() )
282     path = QStringLiteral( ":memory:" );
283   else
284     path = mPath;
285 
286   spatialite_init( 0 );
287 
288   try
289   {
290     QgsScopedSqlite sqlite( path );
291     mSqlite = sqlite;
292   }
293   catch ( std::runtime_error &e )
294   {
295     PROVIDER_ERROR( e.what() );
296     return false;
297   }
298 
299   resetSqlite();
300   initVirtualLayerMetadata( mSqlite.get() );
301 
302   bool noGeometry = mDefinition.geometryWkbType() == QgsWkbTypes::NoGeometry;
303 
304   // load source layers (and populate mLayers)
305   if ( !loadSourceLayers() )
306   {
307     return false;
308   }
309 
310   // now create virtual tables based on layers
311   for ( int i = 0; i < mLayers.size(); i++ )
312   {
313     QgsVectorLayer *vlayer = mLayers.at( i ).layer;
314     QString vname = mLayers.at( i ).name;
315     if ( vlayer )
316     {
317       createVirtualTable( vlayer, vname );
318     }
319     else
320     {
321       QString provider = mLayers.at( i ).provider;
322       // double each single quote
323       provider.replace( QLatin1String( "'" ), QLatin1String( "''" ) );
324       QString source = mLayers.at( i ).source;
325       source.replace( QLatin1String( "'" ), QLatin1String( "''" ) );
326       QString encoding = mLayers.at( i ).encoding;
327       QString createStr = QStringLiteral( "DROP TABLE IF EXISTS \"%1\"; CREATE VIRTUAL TABLE \"%1\" USING QgsVLayer('%2','%4',%3)" )
328                           .arg( vname,
329                                 provider,
330                                 encoding,
331                                 source ); // source must be the last argument here, since it can contains '%x' strings that would be replaced
332       Sqlite::Query::exec( mSqlite.get(), createStr );
333     }
334   }
335 
336   QgsFields tfields;
337   if ( !mDefinition.query().isEmpty() )
338   {
339     // look for column types of the query
340     TableDef columns = columnDefinitionsFromQuery( mSqlite.get(), mDefinition.query() );
341 
342     for ( int i = 0; i < columns.size(); i++ )
343     {
344       ColumnDef &c = columns[i];
345 
346       if ( c.name().isEmpty() )
347       {
348         PROVIDER_ERROR( QString( "Result column #%1 has no name!" ).arg( i + 1 ) );
349         return false;
350       }
351 
352       // then override types by the ones defined in the url
353       if ( mDefinition.fields().indexFromName( c.name() ) != -1 )
354       {
355         c.setScalarType( mDefinition.fields().field( c.name() ).type() );
356       }
357 
358       if ( c.isGeometry() )
359       {
360         gFields << c;
361       }
362       // if the geometry field is not detected as a geometry, move it to the geometry fields
363       // with the provided type and srid
364       else if ( mDefinition.hasDefinedGeometry() && c.name() == mDefinition.geometryField() )
365       {
366         ColumnDef g;
367         g.setName( mDefinition.geometryField() );
368         g.setGeometry( mDefinition.geometryWkbType() );
369         g.setSrid( mDefinition.geometrySrid() );
370         gFields << g;
371       }
372       // default type: string
373       else if ( c.scalarType() == QVariant::Invalid )
374       {
375         c.setScalarType( QVariant::String );
376       }
377       else
378       {
379         tfields.append( QgsField( c.name(), c.scalarType() ) );
380       }
381     }
382 
383     // process geometry field
384     if ( !noGeometry )
385     {
386       // no geometry field defined yet, take the first detected
387       if ( mDefinition.geometryField().isEmpty() )
388       {
389         if ( gFields.count() > 0 )
390         {
391           mDefinition.setGeometryField( gFields[0].name() );
392           mDefinition.setGeometryWkbType( gFields[0].wkbType() );
393           mDefinition.setGeometrySrid( gFields[0].srid() );
394         }
395       }
396       // a geometry field is named, but has no type yet
397       // look for a detected type
398       else if ( !mDefinition.hasDefinedGeometry() )
399       {
400         bool found = false;
401         for ( int i = 0; i < gFields.size(); i++ )
402         {
403           if ( gFields[i].name() == mDefinition.geometryField() )
404           {
405             // override the geometry type
406             mDefinition.setGeometryWkbType( gFields[i].wkbType() );
407             mDefinition.setGeometrySrid( gFields[i].srid() );
408             found = true;
409             break;
410           }
411         }
412         if ( !found )
413         {
414           PROVIDER_ERROR( "Cannot find the specified geometry field!" );
415           return false;
416         }
417       }
418 
419       if ( !mDefinition.geometryField().isEmpty() && !mDefinition.hasDefinedGeometry() )
420       {
421         PROVIDER_ERROR( "Can't deduce the geometry type of the geometry field!" );
422         return false;
423       }
424     }
425 
426     // save field definitions
427     mDefinition.setFields( tfields );
428 
429     mTableName = VIRTUAL_LAYER_QUERY_VIEW;
430 
431     // create a view
432     QString viewStr = QStringLiteral( "DROP VIEW IF EXISTS %1; CREATE VIEW %1 AS %2" )
433                       .arg( VIRTUAL_LAYER_QUERY_VIEW,
434                             mDefinition.query() );
435     Sqlite::Query::exec( mSqlite.get(), viewStr );
436   }
437   else
438   {
439     // no query => implies we must only have one virtual table
440     mTableName = mLayers[0].name;
441 
442     TableDef td = tableDefinitionFromVirtualTable( mSqlite.get(), mTableName );
443     const auto constTd = td;
444     for ( const ColumnDef &c : constTd )
445     {
446       if ( !c.isGeometry() )
447       {
448         tfields.append( QgsField( c.name(), c.scalarType() ) );
449       }
450       else if ( !noGeometry )
451       {
452         mDefinition.setGeometryField( QStringLiteral( "geometry" ) );
453         mDefinition.setGeometryWkbType( c.wkbType() );
454         mDefinition.setGeometrySrid( c.srid() );
455       }
456     }
457     mDefinition.setFields( tfields );
458   }
459 
460   // Save the definition back to the sqlite file
461   {
462     Sqlite::Query q( mSqlite.get(), QStringLiteral( "UPDATE _meta SET url=?" ) );
463     q.bind( mDefinition.toUrl().toString() );
464     q.step();
465   }
466 
467   return true;
468 }
469 
createVirtualTable(QgsVectorLayer * vlayer,const QString & vname)470 void QgsVirtualLayerProvider::createVirtualTable( QgsVectorLayer *vlayer, const QString &vname )
471 {
472   QString createStr = QStringLiteral( "DROP TABLE IF EXISTS \"%1\"; CREATE VIRTUAL TABLE \"%1\" USING QgsVLayer(%2);" ).arg( vname, vlayer->id() );
473   Sqlite::Query::exec( mSqlite.get(), createStr );
474 }
475 
cancelReload()476 bool QgsVirtualLayerProvider::cancelReload()
477 {
478   return mSqlite.interrupt();
479 }
480 
resetSqlite()481 void QgsVirtualLayerProvider::resetSqlite()
482 {
483   bool hasSpatialrefsys = false;
484   {
485     Sqlite::Query q( mSqlite.get(), QStringLiteral( "SELECT name FROM sqlite_master WHERE name='spatial_ref_sys'" ) );
486     hasSpatialrefsys = q.step() == SQLITE_ROW;
487   }
488 
489   QString sql = QStringLiteral( "DROP TABLE IF EXISTS _meta;" );
490   if ( !hasSpatialrefsys )
491   {
492     sql += QLatin1String( "SELECT InitSpatialMetadata(1);" );
493   }
494   Sqlite::Query::exec( mSqlite.get(), sql );
495 }
496 
featureSource() const497 QgsAbstractFeatureSource *QgsVirtualLayerProvider::featureSource() const
498 {
499   return new QgsVirtualLayerFeatureSource( this );
500 }
501 
storageType() const502 QString QgsVirtualLayerProvider::storageType() const
503 {
504   return QStringLiteral( "No storage per se, view data from other data sources" );
505 }
506 
crs() const507 QgsCoordinateReferenceSystem QgsVirtualLayerProvider::crs() const
508 {
509   return mCrs;
510 }
511 
getFeatures(const QgsFeatureRequest & request) const512 QgsFeatureIterator QgsVirtualLayerProvider::getFeatures( const QgsFeatureRequest &request ) const
513 {
514   return QgsFeatureIterator( new QgsVirtualLayerFeatureIterator( new QgsVirtualLayerFeatureSource( this ), false, request ) );
515 }
516 
subsetString() const517 QString QgsVirtualLayerProvider::subsetString() const
518 {
519   return mSubset;
520 }
521 
setSubsetString(const QString & subset,bool updateFeatureCount)522 bool QgsVirtualLayerProvider::setSubsetString( const QString &subset, bool updateFeatureCount )
523 {
524   if ( subset == mSubset )
525     return true;
526 
527   mSubset = subset;
528   clearMinMaxCache();
529   if ( updateFeatureCount )
530     updateStatistics();
531 
532   mDefinition.setSubsetString( subset );
533 
534   setDataSourceUri( mDefinition.toString() );
535 
536   emit dataChanged();
537 
538   return true;
539 }
540 
541 
wkbType() const542 QgsWkbTypes::Type QgsVirtualLayerProvider::wkbType() const
543 {
544   return static_cast<QgsWkbTypes::Type>( mDefinition.geometryWkbType() );
545 }
546 
featureCount() const547 long QgsVirtualLayerProvider::featureCount() const
548 {
549   if ( !mCachedStatistics )
550   {
551     updateStatistics();
552   }
553   return mFeatureCount;
554 }
555 
extent() const556 QgsRectangle QgsVirtualLayerProvider::extent() const
557 {
558   if ( !mCachedStatistics )
559   {
560     updateStatistics();
561   }
562   return mExtent;
563 }
564 
updateStatistics() const565 void QgsVirtualLayerProvider::updateStatistics() const
566 {
567   bool hasGeometry = mDefinition.geometryWkbType() != QgsWkbTypes::NoGeometry;
568   QString subset = mSubset.isEmpty() ? QString() : " WHERE " + mSubset;
569   QString sql = QStringLiteral( "SELECT Count(*)%1 FROM %2%3" )
570                 .arg( hasGeometry ? QStringLiteral( ",Min(MbrMinX(%1)),Min(MbrMinY(%1)),Max(MbrMaxX(%1)),Max(MbrMaxY(%1))" ).arg( quotedColumn( mDefinition.geometryField() ) ) : QString(),
571                       mTableName,
572                       subset );
573 
574   try
575   {
576     Sqlite::Query q( mSqlite.get(), sql );
577     if ( q.step() == SQLITE_ROW )
578     {
579       mFeatureCount = q.columnInt64( 0 );
580       if ( hasGeometry )
581       {
582         double x1, y1, x2, y2;
583         x1 = q.columnDouble( 1 );
584         y1 = q.columnDouble( 2 );
585         x2 = q.columnDouble( 3 );
586         y2 = q.columnDouble( 4 );
587         mExtent = QgsRectangle( x1, y1, x2, y2 );
588       }
589       mCachedStatistics = true;
590     }
591   }
592   catch ( std::runtime_error &e )
593   {
594     pushError( tr( "Error while executing feature count request : %1" ).arg( e.what() ) );
595     mFeatureCount = 0;
596     return;
597   }
598 }
599 
invalidateStatistics()600 void QgsVirtualLayerProvider::invalidateStatistics()
601 {
602   mCachedStatistics = false;
603 }
604 
fields() const605 QgsFields QgsVirtualLayerProvider::fields() const
606 {
607   return mDefinition.fields();
608 }
609 
isValid() const610 bool QgsVirtualLayerProvider::isValid() const
611 {
612   return mValid;
613 }
614 
capabilities() const615 QgsVectorDataProvider::Capabilities QgsVirtualLayerProvider::capabilities() const
616 {
617   QgsVectorDataProvider::Capabilities capabilities = CancelSupport;
618 
619   if ( !mDefinition.uid().isNull() )
620   {
621     capabilities |= SelectAtId;
622   }
623 
624   return capabilities;
625 }
626 
name() const627 QString QgsVirtualLayerProvider::name() const
628 {
629   return VIRTUAL_LAYER_KEY;
630 }
631 
description() const632 QString QgsVirtualLayerProvider::description() const
633 {
634   return VIRTUAL_LAYER_DESCRIPTION;
635 }
636 
pkAttributeIndexes() const637 QgsAttributeList QgsVirtualLayerProvider::pkAttributeIndexes() const
638 {
639   if ( !mDefinition.uid().isNull() )
640   {
641     const QgsFields &fields = mDefinition.fields();
642     for ( int i = 0; i < fields.size(); i++ )
643     {
644       if ( fields.at( i ).name().compare( mDefinition.uid(), Qt::CaseInsensitive ) == 0 )
645       {
646         QgsAttributeList l;
647         l << i;
648         return l;
649       }
650     }
651   }
652   return QgsAttributeList();
653 }
654 
dependencies() const655 QSet<QgsMapLayerDependency> QgsVirtualLayerProvider::dependencies() const
656 {
657   QSet<QgsMapLayerDependency> deps;
658   const auto constSourceLayers = mDefinition.sourceLayers();
659   for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
660   {
661     if ( l.isReferenced() )
662       deps << QgsMapLayerDependency( l.reference(), QgsMapLayerDependency::PresenceDependency, QgsMapLayerDependency::FromProvider );
663   }
664   return deps;
665 }
666 
createProvider(const QString & uri,const QgsDataProvider::ProviderOptions & options,QgsDataProvider::ReadFlags flags)667 QgsVirtualLayerProvider *QgsVirtualLayerProviderMetadata::createProvider(
668   const QString &uri,
669   const QgsDataProvider::ProviderOptions &options,
670   QgsDataProvider::ReadFlags flags )
671 {
672   return new QgsVirtualLayerProvider( uri, options, flags );
673 }
674 
675 
676 #ifdef HAVE_GUI
677 
678 //! Provider for virtual layers source select
679 class QgsVirtualSourceSelectProvider : public QgsSourceSelectProvider
680 {
681   public:
682 
providerKey() const683     QString providerKey() const override { return QStringLiteral( "virtual" ); }
text() const684     QString text() const override { return QObject::tr( "Virtual Layer" ); }
ordering() const685     int ordering() const override { return QgsSourceSelectProvider::OrderDatabaseProvider + 60; }
toolTip() const686     QString toolTip() const override { return QObject::tr( "Add Virtual Layer" ); }
icon() const687     QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddVirtualLayer.svg" ) ); }
createDataSourceWidget(QWidget * parent=nullptr,Qt::WindowFlags fl=Qt::Widget,QgsProviderRegistry::WidgetMode widgetMode=QgsProviderRegistry::WidgetMode::Embedded) const688     QgsAbstractDataSourceWidget *createDataSourceWidget( QWidget *parent = nullptr, Qt::WindowFlags fl = Qt::Widget, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::Embedded ) const override
689     {
690       return new QgsVirtualLayerSourceSelect( parent, fl, widgetMode );
691     }
692 };
693 
694 
QgsVirtualLayerProviderGuiMetadata()695 QgsVirtualLayerProviderGuiMetadata::QgsVirtualLayerProviderGuiMetadata()
696   : QgsProviderGuiMetadata( VIRTUAL_LAYER_KEY )
697 {
698 }
699 
sourceSelectProviders()700 QList<QgsSourceSelectProvider *> QgsVirtualLayerProviderGuiMetadata::sourceSelectProviders()
701 {
702   QList<QgsSourceSelectProvider *> providers;
703   providers << new QgsVirtualSourceSelectProvider;
704   return providers;
705 }
706 #endif
707 
QgsVirtualLayerProviderMetadata()708 QgsVirtualLayerProviderMetadata::QgsVirtualLayerProviderMetadata():
709   QgsProviderMetadata( VIRTUAL_LAYER_KEY, VIRTUAL_LAYER_DESCRIPTION )
710 {
711 }
712 
713 
providerMetadataFactory()714 QGISEXTERN QgsProviderMetadata *providerMetadataFactory()
715 {
716   return new QgsVirtualLayerProviderMetadata();
717 }
718 
719 #ifdef HAVE_GUI
providerGuiMetadataFactory()720 QGISEXTERN QgsProviderGuiMetadata *providerGuiMetadataFactory()
721 {
722   return new QgsVirtualLayerProviderGuiMetadata();
723 }
724 #endif
725