1 /***************************************************************************
2                 qgsvirtuallayerdefinition.cpp
3 begin                : December 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 #include "qgsvirtuallayerdefinition.h"
18 #include "qgsvectorlayer.h"
19 #include "qgsvectordataprovider.h"
20 #include "fromencodedcomponenthelper.h"
21 
22 #include <QUrl>
23 #include <QRegularExpression>
24 #include <QStringList>
25 #include <QUrlQuery>
26 
27 
QgsVirtualLayerDefinition(const QString & filePath)28 QgsVirtualLayerDefinition::QgsVirtualLayerDefinition( const QString &filePath )
29   : mFilePath( filePath )
30 {
31 }
32 
fromUrl(const QUrl & url)33 QgsVirtualLayerDefinition QgsVirtualLayerDefinition::fromUrl( const QUrl &url )
34 {
35   QgsVirtualLayerDefinition def;
36 
37   def.setFilePath( url.toLocalFile() );
38 
39   // regexp for column name
40   const QString columnNameRx( QStringLiteral( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" ) );
41 
42   QgsFields fields;
43 
44   int layerIdx = 0;
45 
46   const QList<QPair<QString, QString> > items = QUrlQuery( url ).queryItems( QUrl::FullyEncoded );
47   for ( int i = 0; i < items.size(); i++ )
48   {
49     const QString key = items.at( i ).first;
50     const QString value = items.at( i ).second;
51     if ( key == QLatin1String( "layer_ref" ) )
52     {
53       layerIdx++;
54       // layer id, with optional layer_name
55       const int pos = value.indexOf( ':' );
56       QString layerId, vlayerName;
57       if ( pos == -1 )
58       {
59         layerId = QUrl::fromPercentEncoding( value.toUtf8() );
60         vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
61       }
62       else
63       {
64         layerId = QUrl::fromPercentEncoding( value.left( pos ).toUtf8() );
65         vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
66       }
67       // add the layer to the list
68       def.addSource( vlayerName, layerId );
69     }
70     else if ( key == QLatin1String( "layer" ) )
71     {
72       layerIdx++;
73       // syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
74       const int pos = value.indexOf( ':' );
75       if ( pos != -1 )
76       {
77         QString providerKey, source, vlayerName, encoding = QStringLiteral( "UTF-8" );
78 
79         providerKey = value.left( pos );
80         int pos2 = value.indexOf( ':', pos + 1 );
81         if ( pos2 - pos == 2 )
82           pos2 = value.indexOf( ':', pos + 3 );
83         if ( pos2 != -1 )
84         {
85           source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
86           const int pos3 = value.indexOf( ':', pos2 + 1 );
87           if ( pos3 != -1 )
88           {
89             vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
90             encoding = value.mid( pos3 + 1 );
91           }
92           else
93           {
94             vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
95           }
96         }
97         else
98         {
99           source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
100           vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
101         }
102 
103         def.addSource( vlayerName, source, providerKey, encoding );
104       }
105     }
106     else if ( key == QLatin1String( "geometry" ) )
107     {
108       // geometry field definition, optional
109       // geometry_column(:wkb_type:srid)?
110       const QRegularExpression reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
111       const QRegularExpressionMatch match = reGeom.match( value );
112       if ( match.hasMatch() )
113       {
114         def.setGeometryField( match.captured( 1 ) );
115         if ( match.capturedTexts().size() > 2 )
116         {
117           // not used by the spatialite provider for now ...
118           QgsWkbTypes::Type wkbType = QgsWkbTypes::parseType( match.captured( 2 ) );
119           if ( wkbType == QgsWkbTypes::Unknown )
120           {
121             wkbType = static_cast<QgsWkbTypes::Type>( match.captured( 2 ).toLong() );
122           }
123           def.setGeometryWkbType( wkbType );
124           def.setGeometrySrid( match.captured( 3 ).toLong() );
125         }
126       }
127     }
128     else if ( key == QLatin1String( "nogeometry" ) )
129     {
130       def.setGeometryWkbType( QgsWkbTypes::NoGeometry );
131     }
132     else if ( key == QLatin1String( "uid" ) )
133     {
134       def.setUid( value );
135     }
136     else if ( key == QLatin1String( "query" ) )
137     {
138       // url encoded query
139       def.setQuery( QUrl::fromPercentEncoding( value.toUtf8() ) );
140     }
141     else if ( key == QLatin1String( "field" ) )
142     {
143       // field_name:type (int, real, text)
144       const QRegularExpression reField( "(" + columnNameRx + "):(int|real|text)" );
145       const QRegularExpressionMatch match = reField.match( value );
146       if ( match.hasMatch() )
147       {
148         const QString fieldName( match.captured( 1 ) );
149         const QString fieldType( match.captured( 2 ) );
150         if ( fieldType == QLatin1String( "int" ) )
151         {
152           fields.append( QgsField( fieldName, QVariant::LongLong, fieldType ) );
153         }
154         else if ( fieldType == QLatin1String( "real" ) )
155         {
156           fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
157         }
158         if ( fieldType == QLatin1String( "text" ) )
159         {
160           fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
161         }
162       }
163     }
164     else if ( key == QLatin1String( "lazy" ) )
165     {
166       def.setLazy( true );
167     }
168     else if ( key == QLatin1String( "subsetstring" ) )
169     {
170       def.setSubsetString( QUrl::fromPercentEncoding( value.toUtf8() ) );
171     }
172   }
173   def.setFields( fields );
174 
175   return def;
176 }
177 
toUrl() const178 QUrl QgsVirtualLayerDefinition::toUrl() const
179 {
180   QUrl url;
181   if ( !filePath().isEmpty() )
182     url = QUrl::fromLocalFile( filePath() );
183 
184   QUrlQuery urlQuery( url );
185 
186   const auto constSourceLayers = sourceLayers();
187   for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
188   {
189     if ( l.isReferenced() )
190       urlQuery.addQueryItem( QStringLiteral( "layer_ref" ), QStringLiteral( "%1:%2" ).arg( l.reference(), l.name() ) );
191     else
192       // if you can find a way to port this away from fromEncodedComponent_helper without breaking existing projects,
193       // please do so... this is GROSS!
194       urlQuery.addQueryItem( fromEncodedComponent_helper( "layer" ),
195                              fromEncodedComponent_helper( QStringLiteral( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
196                                  .arg( l.provider(),
197                                        QString( QUrl::toPercentEncoding( l.name() ) ),
198                                        l.encoding(),
199                                        QString( QUrl::toPercentEncoding( l.source() ) ) ).toUtf8() ) );
200   }
201 
202   if ( !query().isEmpty() )
203   {
204     urlQuery.addQueryItem( QStringLiteral( "query" ), query() );
205   }
206 
207   if ( !uid().isEmpty() )
208     urlQuery.addQueryItem( QStringLiteral( "uid" ), uid() );
209 
210   if ( geometryWkbType() == QgsWkbTypes::NoGeometry )
211     urlQuery.addQueryItem( QStringLiteral( "nogeometry" ), QString() );
212   else if ( !geometryField().isEmpty() )
213   {
214     if ( hasDefinedGeometry() )
215       urlQuery.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
216     else
217       urlQuery.addQueryItem( QStringLiteral( "geometry" ), geometryField() );
218   }
219 
220   const auto constFields = fields();
221   for ( const QgsField &f : constFields )
222   {
223     if ( f.type() == QVariant::Int
224          || f.type() == QVariant::UInt
225          || f.type() == QVariant::Bool
226          || f.type() == QVariant::LongLong )
227       urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":int" );
228     else if ( f.type() == QVariant::Double )
229       urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":real" );
230     else if ( f.type() == QVariant::String )
231       urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":text" );
232   }
233 
234   if ( isLazy() )
235   {
236     urlQuery.addQueryItem( QStringLiteral( "lazy" ), QString() );
237   }
238 
239   if ( ! subsetString().isEmpty() )
240   {
241     urlQuery.addQueryItem( QStringLiteral( "subsetstring" ), QUrl::toPercentEncoding( subsetString() ) );
242   }
243 
244   url.setQuery( urlQuery );
245 
246   return url;
247 }
248 
toString() const249 QString QgsVirtualLayerDefinition::toString() const
250 {
251   return QString( toUrl().toEncoded() );
252 }
253 
addSource(const QString & name,const QString & ref)254 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &ref )
255 {
256   mSourceLayers.append( SourceLayer( name, ref ) );
257 }
258 
addSource(const QString & name,const QString & source,const QString & provider,const QString & encoding)259 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &source, const QString &provider, const QString &encoding )
260 {
261   mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
262 }
263 
hasSourceLayer(const QString & name) const264 bool QgsVirtualLayerDefinition::hasSourceLayer( const QString &name ) const
265 {
266   const auto constSourceLayers = sourceLayers();
267   for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
268   {
269     if ( l.name() == name )
270     {
271       return true;
272     }
273   }
274   return false;
275 }
276 
hasReferencedLayers() const277 bool QgsVirtualLayerDefinition::hasReferencedLayers() const
278 {
279   const auto constSourceLayers = sourceLayers();
280   for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
281   {
282     if ( l.isReferenced() )
283     {
284       return true;
285     }
286   }
287   return false;
288 }
289 
subsetString() const290 QString QgsVirtualLayerDefinition::subsetString() const
291 {
292   return mSubsetString;
293 }
294 
setSubsetString(const QString & subsetString)295 void QgsVirtualLayerDefinition::setSubsetString( const QString &subsetString )
296 {
297   mSubsetString = subsetString;
298 }
299