QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsvirtuallayerdefinition.cpp
Go to the documentation of this file.
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 <QUrl>
18 #include <QRegExp>
19 #include <QStringList>
20 #include <QUrlQuery>
21 #include <QtEndian>
22 
24 #include "qgsvectorlayer.h"
25 #include "qgsvectordataprovider.h"
26 
28  : mFilePath( filePath )
29 {
30 }
31 
33 {
35 
36  def.setFilePath( url.toLocalFile() );
37 
38  // regexp for column name
39  const QString columnNameRx( QStringLiteral( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" ) );
40 
42 
43  int layerIdx = 0;
44 
45  const QList<QPair<QString, QString> > items = QUrlQuery( url ).queryItems( QUrl::FullyEncoded );
46  for ( int i = 0; i < items.size(); i++ )
47  {
48  QString key = items.at( i ).first;
49  QString value = items.at( i ).second;
50  if ( key == QLatin1String( "layer_ref" ) )
51  {
52  layerIdx++;
53  // layer id, with optional layer_name
54  int pos = value.indexOf( ':' );
55  QString layerId, vlayerName;
56  if ( pos == -1 )
57  {
58  layerId = value;
59  vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
60  }
61  else
62  {
63  layerId = value.left( pos );
64  vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
65  }
66  // add the layer to the list
67  def.addSource( vlayerName, layerId );
68  }
69  else if ( key == QLatin1String( "layer" ) )
70  {
71  layerIdx++;
72  // syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
73  int pos = value.indexOf( ':' );
74  if ( pos != -1 )
75  {
76  QString providerKey, source, vlayerName, encoding = QStringLiteral( "UTF-8" );
77 
78  providerKey = value.left( pos );
79  int pos2 = value.indexOf( ':', pos + 1 );
80  if ( pos2 - pos == 2 )
81  pos2 = value.indexOf( ':', pos + 3 );
82  if ( pos2 != -1 )
83  {
84  source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
85  int pos3 = value.indexOf( ':', pos2 + 1 );
86  if ( pos3 != -1 )
87  {
88  vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
89  encoding = value.mid( pos3 + 1 );
90  }
91  else
92  {
93  vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
94  }
95  }
96  else
97  {
98  source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
99  vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
100  }
101 
102  def.addSource( vlayerName, source, providerKey, encoding );
103  }
104  }
105  else if ( key == QLatin1String( "geometry" ) )
106  {
107  // geometry field definition, optional
108  // geometry_column(:wkb_type:srid)?
109  QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
110  int pos = reGeom.indexIn( value );
111  if ( pos >= 0 )
112  {
113  def.setGeometryField( reGeom.cap( 1 ) );
114  if ( reGeom.captureCount() > 1 )
115  {
116  // not used by the spatialite provider for now ...
117  QgsWkbTypes::Type wkbType = QgsWkbTypes::parseType( reGeom.cap( 2 ) );
118  if ( wkbType == QgsWkbTypes::Unknown )
119  {
120  wkbType = static_cast<QgsWkbTypes::Type>( reGeom.cap( 2 ).toLong() );
121  }
122  def.setGeometryWkbType( wkbType );
123  def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
124  }
125  }
126  }
127  else if ( key == QLatin1String( "nogeometry" ) )
128  {
130  }
131  else if ( key == QLatin1String( "uid" ) )
132  {
133  def.setUid( value );
134  }
135  else if ( key == QLatin1String( "query" ) )
136  {
137  // url encoded query
138  def.setQuery( QUrl::fromPercentEncoding( value.toUtf8() ) );
139  }
140  else if ( key == QLatin1String( "field" ) )
141  {
142  // field_name:type (int, real, text)
143  QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
144  int pos = reField.indexIn( value );
145  if ( pos >= 0 )
146  {
147  QString fieldName( reField.cap( 1 ) );
148  QString fieldType( reField.cap( 2 ) );
149  if ( fieldType == QLatin1String( "int" ) )
150  {
151  fields.append( QgsField( fieldName, QVariant::LongLong, fieldType ) );
152  }
153  else if ( fieldType == QLatin1String( "real" ) )
154  {
155  fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
156  }
157  if ( fieldType == QLatin1String( "text" ) )
158  {
159  fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
160  }
161  }
162  }
163  else if ( key == QLatin1String( "lazy" ) )
164  {
165  def.setLazy( true );
166  }
167  else if ( key == QLatin1String( "subsetstring" ) )
168  {
169  def.setSubsetString( QUrl::fromPercentEncoding( value.toUtf8() ) );
170  }
171  }
172  def.setFields( fields );
173 
174  return def;
175 }
176 
177 // Mega ewwww. all this is taken from Qt's QUrl::addEncodedQueryItem compatibility helper.
178 // (I can't see any way to port the below code to NOT require this without breaking
179 // existing projects.)
180 
181 inline char toHexUpper( uint value ) noexcept
182 {
183  return "0123456789ABCDEF"[value & 0xF];
184 }
185 
186 static inline ushort encodeNibble( ushort c )
187 {
188  return ushort( toHexUpper( c ) );
189 }
190 
191 static bool qt_is_ascii( const char *&ptr, const char *end ) noexcept
192 {
193  while ( ptr + 4 <= end )
194  {
195  quint32 data = qFromUnaligned<quint32>( ptr );
196  if ( data &= 0x80808080U )
197  {
198 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
199  uint idx = qCountLeadingZeroBits( data );
200 #else
201  uint idx = qCountTrailingZeroBits( data );
202 #endif
203  ptr += idx / 8;
204  return false;
205  }
206  ptr += 4;
207  }
208  while ( ptr != end )
209  {
210  if ( quint8( *ptr ) & 0x80 )
211  return false;
212  ++ptr;
213  }
214  return true;
215 }
216 
217 QString fromEncodedComponent_helper( const QByteArray &ba )
218 {
219  if ( ba.isNull() )
220  return QString();
221  // scan ba for anything above or equal to 0x80
222  // control points below 0x20 are fine in QString
223  const char *in = ba.constData();
224  const char *const end = ba.constEnd();
225  if ( qt_is_ascii( in, end ) )
226  {
227  // no non-ASCII found, we're safe to convert to QString
228  return QString::fromLatin1( ba, ba.size() );
229  }
230  // we found something that we need to encode
231  QByteArray intermediate = ba;
232  intermediate.resize( ba.size() * 3 - ( in - ba.constData() ) );
233  uchar *out = reinterpret_cast<uchar *>( intermediate.data() + ( in - ba.constData() ) );
234  for ( ; in < end; ++in )
235  {
236  if ( *in & 0x80 )
237  {
238  // encode
239  *out++ = '%';
240  *out++ = encodeNibble( uchar( *in ) >> 4 );
241  *out++ = encodeNibble( uchar( *in ) & 0xf );
242  }
243  else
244  {
245  // keep
246  *out++ = uchar( *in );
247  }
248  }
249  // now it's safe to call fromLatin1
250  return QString::fromLatin1( intermediate, out - reinterpret_cast<uchar *>( intermediate.data() ) );
251 }
252 
254 {
255  QUrl url;
256  if ( !filePath().isEmpty() )
257  url = QUrl::fromLocalFile( filePath() );
258 
259  QUrlQuery urlQuery( url );
260 
261  const auto constSourceLayers = sourceLayers();
262  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
263  {
264  if ( l.isReferenced() )
265  urlQuery.addQueryItem( QStringLiteral( "layer_ref" ), QStringLiteral( "%1:%2" ).arg( l.reference(), l.name() ) );
266  else
267  // if you can find a way to port this away from fromEncodedComponent_helper without breaking existing projects,
268  // please do so... this is GROSS!
269  urlQuery.addQueryItem( fromEncodedComponent_helper( "layer" ),
270  fromEncodedComponent_helper( QStringLiteral( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
271  .arg( l.provider(),
272  QString( QUrl::toPercentEncoding( l.name() ) ),
273  l.encoding(),
274  QString( QUrl::toPercentEncoding( l.source() ) ) ).toUtf8() ) );
275  }
276 
277  if ( !query().isEmpty() )
278  {
279  urlQuery.addQueryItem( QStringLiteral( "query" ), query() );
280  }
281 
282  if ( !uid().isEmpty() )
283  urlQuery.addQueryItem( QStringLiteral( "uid" ), uid() );
284 
286  urlQuery.addQueryItem( QStringLiteral( "nogeometry" ), QString() );
287  else if ( !geometryField().isEmpty() )
288  {
289  if ( hasDefinedGeometry() )
290  urlQuery.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
291  else
292  urlQuery.addQueryItem( QStringLiteral( "geometry" ), geometryField() );
293  }
294 
295  const auto constFields = fields();
296  for ( const QgsField &f : constFields )
297  {
298  if ( f.type() == QVariant::Int
299  || f.type() == QVariant::UInt
300  || f.type() == QVariant::Bool
301  || f.type() == QVariant::LongLong )
302  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":int" );
303  else if ( f.type() == QVariant::Double )
304  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":real" );
305  else if ( f.type() == QVariant::String )
306  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":text" );
307  }
308 
309  if ( isLazy() )
310  {
311  urlQuery.addQueryItem( QStringLiteral( "lazy" ), QString() );
312  }
313 
314  if ( ! subsetString().isEmpty() )
315  {
316  urlQuery.addQueryItem( QStringLiteral( "subsetstring" ), QUrl::toPercentEncoding( subsetString() ) );
317  }
318 
319  url.setQuery( urlQuery );
320 
321  return url;
322 }
323 
325 {
326  return QString( toUrl().toEncoded() );
327 }
328 
329 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &ref )
330 {
331  mSourceLayers.append( SourceLayer( name, ref ) );
332 }
333 
334 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &source, const QString &provider, const QString &encoding )
335 {
336  mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
337 }
338 
339 bool QgsVirtualLayerDefinition::hasSourceLayer( const QString &name ) const
340 {
341  const auto constSourceLayers = sourceLayers();
342  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
343  {
344  if ( l.name() == name )
345  {
346  return true;
347  }
348  }
349  return false;
350 }
351 
353 {
354  const auto constSourceLayers = sourceLayers();
355  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
356  {
357  if ( l.isReferenced() )
358  {
359  return true;
360  }
361  }
362  return false;
363 }
364 
366 {
367  return mSubsetString;
368 }
369 
370 void QgsVirtualLayerDefinition::setSubsetString( const QString &subsetString )
371 {
372  mSubsetString = subsetString;
373 }
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
A SourceLayer is either a reference to a live layer in the registry or all the parameters needed to l...
Class to manipulate the definition of a virtual layer.
QString geometryField() const
Gets the name of the geometry field. Empty if no geometry field.
QString query() const
Gets the SQL query.
QgsFields fields() const
Gets field definitions.
long geometrySrid() const
Gets the SRID of the geometry.
bool hasSourceLayer(const QString &name) const
Convenience method to test if a given source layer is part of the definition.
QgsVirtualLayerDefinition(const QString &filePath="")
Constructor with an optional file path.
void setUid(const QString &uid)
Sets the name of the field with unique identifiers.
bool hasDefinedGeometry() const
Convenient method to test if the geometry is defined (not NoGeometry and not Unknown)
void setSubsetString(const QString &subsetString)
Sets the subsetString.
const QgsVirtualLayerDefinition::SourceLayers & sourceLayers() const
Gets access to the source layers.
void setLazy(bool lazy)
Sets the lazy mode.
bool hasReferencedLayers() const
Convenience method to test whether the definition has referenced (live) layers.
void setFilePath(const QString &filePath)
Sets the file path.
void setGeometryWkbType(QgsWkbTypes::Type t)
Sets the type of the geometry.
QString subsetString() const
Returns the subset string.
QUrl toUrl() const
Convert the definition into a QUrl.
void setGeometrySrid(long srid)
Sets the SRID of the geometry.
void addSource(const QString &name, const QString &ref)
Add a live layer source layer.
void setGeometryField(const QString &geometryField)
Sets the name of the geometry field.
QString uid() const
Gets the name of the field with unique identifiers.
QString filePath() const
Gets the file path. May be empty.
QgsWkbTypes::Type geometryWkbType() const
Gets the type of the geometry QgsWkbTypes::NoGeometry to hide any geometry QgsWkbTypes::Unknown for u...
static QgsVirtualLayerDefinition fromUrl(const QUrl &url)
Constructor to build a definition from a QUrl The path part of the URL is extracted as well as the fo...
QString toString() const
Convert into a QString that can be read by the virtual layer provider.
void setFields(const QgsFields &fields)
Sets field definitions.
void setQuery(const QString &query)
Sets the SQL query.
bool isLazy() const
Returns the lazy mode.
static Type parseType(const QString &wktStr)
Attempts to extract the WKB type from a WKT string.
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qt_is_ascii(const char *&ptr, const char *end) noexcept
QString fromEncodedComponent_helper(const QByteArray &ba)
char toHexUpper(uint value) noexcept