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