QgsVirtualLayerDefinition QgsVirtualLayerDefinitionUtils::fromJoinedLayer( QgsVectorLayer *layer )
{
  QgsVirtualLayerDefinition def;

  QStringList leftJoins;
  QStringList columns;

  // add the geometry column if the layer is spatial
  if ( layer->isSpatial() )
    columns << "t.geometry";

  // look for the uid
  QgsFields fields = layer->dataProvider()->fields();
  {
    QgsAttributeList pk = layer->dataProvider()->pkAttributeIndexes();
    if ( pk.size() == 1 )
    {
      def.setUid( fields.field( pk[0] ).name() );
    }
    else
    {
      // find an uid name
      QString uid = QStringLiteral( "uid" );
      while ( fields.lookupField( uid ) != -1 )
        uid += QLatin1String( "_" ); // add "_" each time this name already exists

      // add a column
      columns << "t.rowid AS " + uid;
      def.setUid( uid );
    }
  }
  const QgsFields providerFields = layer->dataProvider()->fields();
  for ( const auto &f : providerFields )
  {
    columns << "t.\"" + f.name() + "\"";
  }

  int joinIdx = 0;
  Q_FOREACH ( const QgsVectorLayerJoinInfo &join, layer->vectorJoins() )
  {
    QString joinName = QStringLiteral( "j%1" ).arg( ++joinIdx );
    QgsVectorLayer *joinedLayer = join.joinLayer();
    if ( !joinedLayer )
      continue;
    QString prefix = join.prefix().isEmpty() ? joinedLayer->name() + "_" : join.prefix();

    leftJoins << QStringLiteral( "LEFT JOIN \"%1\" AS %2 ON t.\"%5\"=%2.\"%3\"" ).arg( joinedLayer->id(), joinName, join.joinFieldName(), join.targetFieldName() );
    if ( join.joinFieldNamesSubset() )
    {
      Q_FOREACH ( const QString &f, *join.joinFieldNamesSubset() )
      {
        columns << joinName + ".\"" + f + "\" AS \"" + prefix + f + "\"";
      }
    }
    else
    {
QGISEXTERN QgsVirtualLayerSourceSelect *createWidget( QWidget *parent, Qt::WindowFlags fl, const QList<QPair<QString, QString> >& parameters )
{
    QgsVirtualLayerSourceSelect *w = new QgsVirtualLayerSourceSelect( parent, fl );
    QString name, source, encoding;
    foreach ( const auto& p, parameters ) {
        if ((p.first == "fromUrl") || (p.first == "fromFile")) {
            QgsVirtualLayerDefinition def;
            if (p.first == "fromUrl") {
                QUrl url( QUrl::fromEncoded( p.second.toLocal8Bit() ) );
                def.fromUrl( url );
            }
            else {
                def = virtualLayerDefinitionFromSqlite( p.second );
            }
            w->setQuery( def.query() );
            w->setUid( def.uid() );
            w->setGeometryColumn( def.geometryField() );
            foreach ( const auto& l, def.sourceLayers() ) {
                w->addSource( l.name(), l.source(), l.provider(), l.encoding() );
            }
            w->setFilename( def.uri() );
            break;
        }
        else if (p.first == "layer") {
QgsVirtualLayerDefinition QgsVirtualLayerDefinition::fromUrl( const QUrl& url )
{
  QgsVirtualLayerDefinition def;

  def.setFilePath( url.path() );

  // regexp for column name
  const QString columnNameRx( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" );

  QgsFields fields;

  int layerIdx = 0;
  QList<QPair<QString, QString> > items = url.queryItems();
  for ( int i = 0; i < items.size(); i++ )
  {
    QString key = items.at( i ).first;
    QString value = items.at( i ).second;
    if ( key == "layer_ref" )
    {
      layerIdx++;
      // layer id, with optional layer_name
      int pos = value.indexOf( ':' );
      QString layerId, vlayerName;
      if ( pos == -1 )
      {
        layerId = value;
        vlayerName = QString( "vtab%1" ).arg( layerIdx );
      }
      else
      {
        layerId = value.left( pos );
        vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
      }
      // add the layer to the list
      def.addSource( vlayerName, layerId );
    }
    else if ( key == "layer" )
    {
      layerIdx++;
      // syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
      int pos = value.indexOf( ':' );
      if ( pos != -1 )
      {
        QString providerKey, source, vlayerName, encoding = "UTF-8";

        providerKey = value.left( pos );
        int pos2 = value.indexOf( ':', pos + 1 );
        if ( pos2 != -1 )
        {
          source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
          int pos3 = value.indexOf( ':', pos2 + 1 );
          if ( pos3 != -1 )
          {
            vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
            encoding = value.mid( pos3 + 1 );
          }
          else
          {
            vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
          }
        }
        else
        {
          source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
          vlayerName = QString( "vtab%1" ).arg( layerIdx );
        }

        def.addSource( vlayerName, source, providerKey, encoding );
      }
    }
    else if ( key == "geometry" )
    {
      // geometry field definition, optional
      // geometry_column(:wkb_type:srid)?
      QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
      int pos = reGeom.indexIn( value );
      if ( pos >= 0 )
      {
        def.setGeometryField( reGeom.cap( 1 ) );
        if ( reGeom.captureCount() > 1 )
        {
          // not used by the spatialite provider for now ...
          QgsWKBTypes::Type wkbType = QgsWKBTypes::parseType( reGeom.cap( 2 ) );
          if ( wkbType == QgsWKBTypes::Unknown )
          {
            wkbType = static_cast<QgsWKBTypes::Type>( reGeom.cap( 2 ).toLong() );
          }
          def.setGeometryWkbType( wkbType );
          def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
        }
      }
    }
    else if ( key == "nogeometry" )
    {
      def.setGeometryWkbType( QgsWKBTypes::NoGeometry );
    }
    else if ( key == "uid" )
    {
      def.setUid( value );
    }
    else if ( key == "query" )
    {
      // url encoded query
      def.setQuery( value );
    }
    else if ( key == "field" )
    {
      // field_name:type (int, real, text)
      QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
      int pos = reField.indexIn( value );
      if ( pos >= 0 )
      {
        QString fieldName( reField.cap( 1 ) );
        QString fieldType( reField.cap( 2 ) );
        if ( fieldType == "int" )
        {
          fields.append( QgsField( fieldName, QVariant::Int, fieldType ) );
        }
        else if ( fieldType == "real" )
        {
          fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
        }
        if ( fieldType == "text" )
        {
          fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
        }
      }
    }
  }
  def.setFields( fields );

  return def;
}