qgis / src / providers / postgres / qgspostgresprovider.cpp @ master
History | View | Annotate | Download (211 KB)
1 |
/***************************************************************************
|
---|---|
2 |
qgspostgresprovider.cpp - QGIS data provider for PostgreSQL/PostGIS layers
|
3 |
-------------------
|
4 |
begin : 2004/01/07
|
5 |
copyright : (C) 2004 by Gary E.Sherman
|
6 |
email : sherman at mrcc.com
|
7 |
***************************************************************************/
|
8 |
|
9 |
/***************************************************************************
|
10 |
* *
|
11 |
* This program is free software; you can redistribute it and/or modify *
|
12 |
* it under the terms of the GNU General Public License as published by *
|
13 |
* the Free Software Foundation; either version 2 of the License, or *
|
14 |
* (at your option) any later version. *
|
15 |
* *
|
16 |
***************************************************************************/
|
17 |
|
18 |
#include "qgsapplication.h" |
19 |
#include "qgsfeature.h" |
20 |
#include "qgsfield.h" |
21 |
#include "qgsgeometry.h" |
22 |
#include "qgsmessagelog.h" |
23 |
#include "qgsprojectstorageregistry.h" |
24 |
#include "qgslayermetadataproviderregistry.h" |
25 |
#include "qgsrectangle.h" |
26 |
#include "qgscoordinatereferencesystem.h" |
27 |
#include "qgsxmlutils.h" |
28 |
#include "qgsvectorlayer.h" |
29 |
#include "qgspostgresprovider.h" |
30 |
#include "moc_qgspostgresprovider.cpp" |
31 |
#include "qgspostgresconn.h" |
32 |
#include "qgspostgresconnpool.h" |
33 |
#include "qgspostgresdataitems.h" |
34 |
#include "qgspostgresfeatureiterator.h" |
35 |
#include "qgspostgrestransaction.h" |
36 |
#include "qgspostgreslistener.h" |
37 |
#include "qgspostgresprojectstorage.h" |
38 |
#include "qgspostgresproviderconnection.h" |
39 |
#include "qgslogger.h" |
40 |
#include "qgsfeedback.h" |
41 |
#include "qgsstringutils.h" |
42 |
#include "qgsjsonutils.h" |
43 |
#include "qgsdbquerylog.h" |
44 |
#include "qgsdbquerylog_p.h" |
45 |
#include "qgspostgreslayermetadataprovider.h" |
46 |
|
47 |
#include "qgspostgresprovider.h" |
48 |
#include "qgsprovidermetadata.h" |
49 |
#include "qgspostgresproviderconnection.h" |
50 |
#include "qgspostgresprovidermetadatautils.h" |
51 |
#include <QRegularExpression> |
52 |
|
53 |
const QString QgsPostgresProvider::POSTGRES_KEY = QStringLiteral( "postgres" ); |
54 |
const QString QgsPostgresProvider::POSTGRES_DESCRIPTION = QStringLiteral( "PostgreSQL/PostGIS data provider" ); |
55 |
|
56 |
static const QString EDITOR_WIDGET_STYLES_TABLE = QStringLiteral( "qgis_editor_widget_styles" ); |
57 |
|
58 |
inline qint64 PKINT2FID( qint32 x )
|
59 |
{ |
60 |
return QgsPostgresUtils::int32pk_to_fid( x );
|
61 |
} |
62 |
|
63 |
inline qint32 FID2PKINT( qint64 x )
|
64 |
{ |
65 |
return QgsPostgresUtils::fid_to_int32pk( x );
|
66 |
} |
67 |
|
68 |
static bool tableExists( QgsPostgresConn &conn, const QString &name ) |
69 |
{ |
70 |
QgsPostgresResult res( conn.LoggedPQexec( QStringLiteral( "tableExists" ), "SELECT EXISTS ( SELECT oid FROM pg_catalog.pg_class WHERE relname=" + QgsPostgresConn::quotedValue( name ) + ")" ) ); |
71 |
return res.PQgetvalue( 0, 0 ).startsWith( 't' ); |
72 |
} |
73 |
|
74 |
static bool columnExists( QgsPostgresConn &conn, const QString &table, const QString &column ) |
75 |
{ |
76 |
QgsPostgresResult res( conn.LoggedPQexec( QStringLiteral( "columnExists" ), "SELECT COUNT(*) FROM information_schema.columns WHERE table_name=" + QgsPostgresConn::quotedValue( table ) + " and column_name=" + QgsPostgresConn::quotedValue( column ) ) ); |
77 |
return res.PQgetvalue( 0, 0 ).toInt() > 0; |
78 |
} |
79 |
|
80 |
QgsPostgresPrimaryKeyType |
81 |
QgsPostgresProvider::pkType( const QgsField &f ) const |
82 |
{ |
83 |
switch ( f.type() )
|
84 |
{ |
85 |
case QMetaType::Type::LongLong:
|
86 |
// PostgreSQL doesn't have native "unsigned" types.
|
87 |
// Unsigned primary keys are emulated by the serial/bigserial
|
88 |
// pseudo-types, in which autogenerated values are always > 0;
|
89 |
// however, the database accepts manually inserted 0 and negative values
|
90 |
// in these fields.
|
91 |
return PktInt64;
|
92 |
|
93 |
case QMetaType::Type::Int:
|
94 |
return PktInt;
|
95 |
|
96 |
default:
|
97 |
return PktFidMap;
|
98 |
} |
99 |
} |
100 |
|
101 |
|
102 |
QgsPostgresProvider::QgsPostgresProvider( QString const &uri, const ProviderOptions &options, Qgis::DataProviderReadFlags flags ) |
103 |
: QgsVectorDataProvider( uri, options, flags ) |
104 |
, mShared( new QgsPostgresSharedData )
|
105 |
{ |
106 |
QgsDebugMsgLevel( QStringLiteral( "URI: %1 " ).arg( uri ), 2 ); |
107 |
|
108 |
mUri = QgsDataSourceUri( uri ); |
109 |
|
110 |
// populate members from the uri structure
|
111 |
mSchemaName = mUri.schema(); |
112 |
mTableName = mUri.table(); |
113 |
mGeometryColumn = mUri.geometryColumn(); |
114 |
mBoundingBoxColumn = mUri.param( "bbox" );
|
115 |
if ( mBoundingBoxColumn.isEmpty() )
|
116 |
{ |
117 |
mBoundingBoxColumn = mGeometryColumn; |
118 |
} |
119 |
mSqlWhereClause = mUri.sql(); |
120 |
mRequestedSrid = mUri.srid(); |
121 |
mRequestedGeomType = mUri.wkbType(); |
122 |
|
123 |
const QString checkUnicityKey { QStringLiteral( "checkPrimaryKeyUnicity" ) }; |
124 |
if ( mUri.hasParam( checkUnicityKey ) )
|
125 |
{ |
126 |
if ( mUri.param( checkUnicityKey ).compare( QLatin1String( "0" ) ) == 0 ) |
127 |
{ |
128 |
mCheckPrimaryKeyUnicity = false;
|
129 |
} |
130 |
else
|
131 |
{ |
132 |
mCheckPrimaryKeyUnicity = true;
|
133 |
} |
134 |
if ( mReadFlags & Qgis::DataProviderReadFlag::TrustDataSource )
|
135 |
{ |
136 |
mCheckPrimaryKeyUnicity = false;
|
137 |
} |
138 |
} |
139 |
|
140 |
if ( mSchemaName.isEmpty() && mTableName.startsWith( '(' ) && mTableName.endsWith( ')' ) ) |
141 |
{ |
142 |
mIsQuery = true;
|
143 |
setQuery( mTableName ); |
144 |
mTableName.clear(); |
145 |
} |
146 |
else
|
147 |
{ |
148 |
mIsQuery = false;
|
149 |
|
150 |
setQuery( ( !mSchemaName.isEmpty() ? quotedIdentifier( mSchemaName ) + '.' : QString() ) + ( !mTableName.isEmpty() ? quotedIdentifier( mTableName ) : QString() ) );
|
151 |
} |
152 |
|
153 |
mUseEstimatedMetadata = mUri.useEstimatedMetadata(); |
154 |
if ( mReadFlags & Qgis::DataProviderReadFlag::TrustDataSource )
|
155 |
{ |
156 |
mUseEstimatedMetadata = true;
|
157 |
} |
158 |
mSelectAtIdDisabled = mUri.selectAtIdDisabled(); |
159 |
|
160 |
QgsDebugMsgLevel( QStringLiteral( "Connection info is %1" ).arg( mUri.connectionInfo( false ) ), 2 ); |
161 |
QgsDebugMsgLevel( QStringLiteral( "Geometry column is: %1" ).arg( mGeometryColumn ), 2 ); |
162 |
QgsDebugMsgLevel( QStringLiteral( "Schema is: %1" ).arg( mSchemaName ), 2 ); |
163 |
QgsDebugMsgLevel( QStringLiteral( "Table name is: %1" ).arg( mTableName ), 2 ); |
164 |
QgsDebugMsgLevel( QStringLiteral( "Query is: %1" ).arg( mQuery ), 2 ); |
165 |
QgsDebugMsgLevel( QStringLiteral( "Where clause is: %1" ).arg( mSqlWhereClause ), 2 ); |
166 |
|
167 |
// no table/query passed, the provider could be used to get tables
|
168 |
if ( mQuery.isEmpty() )
|
169 |
{ |
170 |
return;
|
171 |
} |
172 |
|
173 |
mConnectionRO = QgsPostgresConn::connectDb( mUri, true, true, false, !mReadFlags.testFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest ) ); |
174 |
if ( !mConnectionRO )
|
175 |
{ |
176 |
return;
|
177 |
} |
178 |
|
179 |
// if credentials were updated during database connection, update the provider's uri accordingly
|
180 |
if ( !mUri.username().isEmpty() )
|
181 |
mUri.setUsername( mConnectionRO->uri().username() ); |
182 |
if ( !mUri.password().isEmpty() )
|
183 |
mUri.setPassword( mConnectionRO->uri().password() ); |
184 |
|
185 |
if ( !hasSufficientPermsAndCapabilities() ) // check permissions and set capabilities |
186 |
{ |
187 |
disconnectDb(); |
188 |
return;
|
189 |
} |
190 |
|
191 |
if ( !getGeometryDetails() ) // gets srid, geometry and data type |
192 |
{ |
193 |
// the table is not a geometry table
|
194 |
QgsMessageLog::logMessage( tr( "Invalid PostgreSQL layer" ), tr( "PostGIS" ) ); |
195 |
disconnectDb(); |
196 |
return;
|
197 |
} |
198 |
|
199 |
if ( mDetectedGeomType != Qgis::WkbType::Unknown )
|
200 |
{ |
201 |
// most common case, elevation can be computed via mDetectedGeomType
|
202 |
elevationProperties()->setContainsElevationData( QgsWkbTypes::hasZ( mDetectedGeomType ) ); |
203 |
} |
204 |
|
205 |
// NOTE: mValid would be true after true return from
|
206 |
// getGeometryDetails, see https://github.com/qgis/QGIS/issues/21807
|
207 |
|
208 |
if ( mSpatialColType == SctTopoGeometry )
|
209 |
{ |
210 |
if ( !getTopoLayerInfo() ) // gets topology name and layer id |
211 |
{ |
212 |
QgsMessageLog::logMessage( tr( "Invalid PostgreSQL topology layer" ), tr( "PostGIS" ) ); |
213 |
mValid = false;
|
214 |
disconnectDb(); |
215 |
return;
|
216 |
} |
217 |
} |
218 |
|
219 |
// Try to load metadata
|
220 |
const QString schemaQuery = QStringLiteral( "SELECT table_schema FROM information_schema.tables WHERE table_name = 'qgis_layer_metadata'" ); |
221 |
QgsPostgresResult res( mConnectionRO->LoggedPQexec( "QgsPostgresProvider", schemaQuery ) );
|
222 |
if ( res.PQntuples() > 0 ) |
223 |
{ |
224 |
const QString schemaName = res.PQgetvalue( 0, 0 ); |
225 |
// TODO: also filter CRS?
|
226 |
const QString selectQuery = QStringLiteral( R"SQL( |
227 |
SELECT
|
228 |
qmd
|
229 |
FROM %4.qgis_layer_metadata
|
230 |
WHERE
|
231 |
f_table_schema=%1
|
232 |
AND f_table_name=%2
|
233 |
AND f_geometry_column %3
|
234 |
AND layer_type='vector'
|
235 |
)SQL" )
|
236 |
.arg( QgsPostgresConn::quotedValue( mUri.schema() ) ) |
237 |
.arg( QgsPostgresConn::quotedValue( mUri.table() ) ) |
238 |
.arg( mUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( mUri.geometryColumn() ) ) ) |
239 |
.arg( QgsPostgresConn::quotedIdentifier( schemaName ) ); |
240 |
|
241 |
QgsPostgresResult res( mConnectionRO->LoggedPQexec( "QgsPostgresProvider", selectQuery ) );
|
242 |
if ( res.PQntuples() > 0 ) |
243 |
{ |
244 |
QgsLayerMetadata metadata; |
245 |
QDomDocument doc; |
246 |
doc.setContent( res.PQgetvalue( 0, 0 ) ); |
247 |
mLayerMetadata.readMetadataXml( doc.documentElement() ); |
248 |
} |
249 |
} |
250 |
|
251 |
// set the primary key
|
252 |
if ( !determinePrimaryKey() )
|
253 |
{ |
254 |
QgsMessageLog::logMessage( tr( "PostgreSQL layer has no primary key." ), tr( "PostGIS" ) ); |
255 |
mValid = false;
|
256 |
disconnectDb(); |
257 |
return;
|
258 |
} |
259 |
|
260 |
setNativeTypes( mConnectionRO->nativeTypes() ); |
261 |
|
262 |
QString key; |
263 |
switch ( mPrimaryKeyType )
|
264 |
{ |
265 |
case PktOid:
|
266 |
key = QStringLiteral( "oid" );
|
267 |
break;
|
268 |
case PktTid:
|
269 |
key = QStringLiteral( "tid" );
|
270 |
break;
|
271 |
case PktInt:
|
272 |
Q_ASSERT( mPrimaryKeyAttrs.size() == 1 );
|
273 |
Q_ASSERT( mPrimaryKeyAttrs[0] >= 0 && mPrimaryKeyAttrs[0] < mAttributeFields.count() ); |
274 |
key = mAttributeFields.at( mPrimaryKeyAttrs.at( 0 ) ).name();
|
275 |
break;
|
276 |
case PktInt64:
|
277 |
case PktUint64:
|
278 |
case PktFidMap:
|
279 |
{ |
280 |
QString delim; |
281 |
const auto constMPrimaryKeyAttrs = mPrimaryKeyAttrs; |
282 |
for ( int idx : constMPrimaryKeyAttrs ) |
283 |
{ |
284 |
key += delim + mAttributeFields.at( idx ).name(); |
285 |
delim = ',';
|
286 |
} |
287 |
} |
288 |
break;
|
289 |
case PktUnknown:
|
290 |
QgsMessageLog::logMessage( tr( "PostgreSQL layer has unknown primary key type." ), tr( "PostGIS" ) ); |
291 |
mValid = false;
|
292 |
break;
|
293 |
} |
294 |
|
295 |
if ( mValid )
|
296 |
{ |
297 |
mUri.setKeyColumn( key ); |
298 |
setDataSourceUri( mUri.uri( false ) );
|
299 |
} |
300 |
else
|
301 |
{ |
302 |
disconnectDb(); |
303 |
} |
304 |
|
305 |
mLayerMetadata.setType( QStringLiteral( "dataset" ) );
|
306 |
mLayerMetadata.setCrs( crs() ); |
307 |
|
308 |
// Constructor is called in another thread than the thread where the provider will live,
|
309 |
// so we disconnect the DB, connection will be done later in the provider thread when needed
|
310 |
if ( flags.testFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading ) )
|
311 |
disconnectDb(); |
312 |
} |
313 |
|
314 |
QgsPostgresProvider::~QgsPostgresProvider() |
315 |
{ |
316 |
disconnectDb(); |
317 |
|
318 |
QgsDebugMsgLevel( QStringLiteral( "deconstructing." ), 3 ); |
319 |
} |
320 |
|
321 |
|
322 |
QgsAbstractFeatureSource *QgsPostgresProvider::featureSource() const
|
323 |
{ |
324 |
return new QgsPostgresFeatureSource( this ); |
325 |
} |
326 |
|
327 |
QgsPostgresConn *QgsPostgresProvider::connectionRO() const
|
328 |
{ |
329 |
if ( mTransaction )
|
330 |
return mTransaction->connection();
|
331 |
|
332 |
if ( !mConnectionRO )
|
333 |
mConnectionRO = QgsPostgresConn::connectDb( mUri, true, true, false, !mReadFlags.testFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest ) ); |
334 |
|
335 |
return mConnectionRO;
|
336 |
} |
337 |
|
338 |
void QgsPostgresProvider::setListening( bool isListening ) |
339 |
{ |
340 |
if ( !mValid )
|
341 |
return;
|
342 |
|
343 |
if ( isListening && !mListener )
|
344 |
{ |
345 |
mListener.reset( QgsPostgresListener::create( mUri.connectionInfo( false ) ).release() );
|
346 |
connect( mListener.get(), &QgsPostgresListener::notify, this, &QgsPostgresProvider::notify );
|
347 |
} |
348 |
else if ( !isListening && mListener ) |
349 |
{ |
350 |
disconnect( mListener.get(), &QgsPostgresListener::notify, this, &QgsPostgresProvider::notify );
|
351 |
mListener.reset(); |
352 |
} |
353 |
} |
354 |
|
355 |
Qgis::VectorLayerTypeFlags QgsPostgresProvider::vectorLayerTypeFlags() const
|
356 |
{ |
357 |
Qgis::VectorLayerTypeFlags flags; |
358 |
if ( mValid && mIsQuery )
|
359 |
{ |
360 |
flags.setFlag( Qgis::VectorLayerTypeFlag::SqlQuery ); |
361 |
} |
362 |
return flags;
|
363 |
} |
364 |
|
365 |
void QgsPostgresProvider::handlePostCloneOperations( QgsVectorDataProvider *source )
|
366 |
{ |
367 |
mShared = qobject_cast<QgsPostgresProvider *>( source )->mShared; |
368 |
} |
369 |
|
370 |
void QgsPostgresProvider::reloadProviderData()
|
371 |
{ |
372 |
mShared->setFeaturesCounted( -1 );
|
373 |
mLayerExtent.reset(); |
374 |
} |
375 |
|
376 |
QgsPostgresConn *QgsPostgresProvider::connectionRW() |
377 |
{ |
378 |
if ( mTransaction )
|
379 |
{ |
380 |
return mTransaction->connection();
|
381 |
} |
382 |
else if ( !mConnectionRW ) |
383 |
{ |
384 |
mConnectionRW = QgsPostgresConn::connectDb( mUri, false );
|
385 |
} |
386 |
return mConnectionRW;
|
387 |
} |
388 |
|
389 |
QgsTransaction *QgsPostgresProvider::transaction() const
|
390 |
{ |
391 |
return mTransaction;
|
392 |
} |
393 |
|
394 |
QString QgsPostgresProvider::providerKey() |
395 |
{ |
396 |
return POSTGRES_KEY;
|
397 |
} |
398 |
|
399 |
void QgsPostgresProvider::setTransaction( QgsTransaction *transaction )
|
400 |
{ |
401 |
// static_cast since layers cannot be added to a transaction of a non-matching provider
|
402 |
mTransaction = static_cast<QgsPostgresTransaction *>( transaction );
|
403 |
|
404 |
const QString sessionRoleKey = QStringLiteral( "session_role" ); |
405 |
if ( mUri.hasParam( sessionRoleKey ) )
|
406 |
{ |
407 |
const QString sessionRole = mUri.param( sessionRoleKey );
|
408 |
if ( !sessionRole.isEmpty() )
|
409 |
{ |
410 |
if ( !mTransaction->connection()->setSessionRole( sessionRole ) )
|
411 |
{ |
412 |
QgsDebugMsgLevel( |
413 |
QStringLiteral( |
414 |
"Set session role failed for ROLE %1"
|
415 |
) |
416 |
.arg( quotedValue( sessionRole ) ), |
417 |
2
|
418 |
); |
419 |
} |
420 |
} |
421 |
} |
422 |
} |
423 |
|
424 |
QgsReferencedGeometry QgsPostgresProvider::fromEwkt( const QString &ewkt, QgsPostgresConn *conn )
|
425 |
{ |
426 |
thread_local const QRegularExpression regularExpressionSRID( "^SRID=(\\d+);" ); |
427 |
|
428 |
QRegularExpressionMatch regularExpressionMatch = regularExpressionSRID.match( ewkt ); |
429 |
if ( !regularExpressionMatch.hasMatch() )
|
430 |
{ |
431 |
QgsGeometry geom = QgsGeometry::fromWkt( ewkt ); |
432 |
QgsCoordinateReferenceSystem crs; // TODO: use projects' crs ?
|
433 |
return QgsReferencedGeometry( geom, crs );
|
434 |
} |
435 |
|
436 |
QString wkt = ewkt.mid( regularExpressionMatch.captured( 0 ).size() );
|
437 |
int srid = regularExpressionMatch.captured( 1 ).toInt(); |
438 |
|
439 |
|
440 |
QgsGeometry geom = QgsGeometry::fromWkt( wkt ); |
441 |
return QgsReferencedGeometry( geom, sridToCrs( srid, conn ) );
|
442 |
} |
443 |
|
444 |
QString QgsPostgresProvider::toEwkt( const QgsReferencedGeometry &geom, QgsPostgresConn *conn )
|
445 |
{ |
446 |
if ( !geom.isNull() )
|
447 |
return QStringLiteral( "SRID=%1;%2" ).arg( QString::number( crsToSrid( geom.crs(), conn ) ), geom.asWkt() ); |
448 |
else
|
449 |
return QString();
|
450 |
} |
451 |
|
452 |
QString QgsPostgresProvider::geomAttrToString( const QVariant &attr, QgsPostgresConn *conn )
|
453 |
{ |
454 |
if ( attr.userType() == QMetaType::Type::QString )
|
455 |
return attr.toString();
|
456 |
else
|
457 |
return toEwkt( attr.value<QgsReferencedGeometry>(), conn );
|
458 |
} |
459 |
|
460 |
int QgsPostgresProvider::crsToSrid( const QgsCoordinateReferenceSystem &crs, QgsPostgresConn *conn ) |
461 |
{ |
462 |
int srid = -1; |
463 |
if ( conn )
|
464 |
srid = conn->crsToSrid( crs ); |
465 |
return srid;
|
466 |
} |
467 |
|
468 |
QgsCoordinateReferenceSystem QgsPostgresProvider::sridToCrs( int srid, QgsPostgresConn *conn )
|
469 |
{ |
470 |
QgsCoordinateReferenceSystem crs; |
471 |
if ( conn )
|
472 |
crs = conn->sridToCrs( srid ); |
473 |
return crs;
|
474 |
} |
475 |
|
476 |
void QgsPostgresProvider::disconnectDb()
|
477 |
{ |
478 |
if ( mConnectionRO )
|
479 |
{ |
480 |
mConnectionRO->unref(); |
481 |
mConnectionRO = nullptr; |
482 |
} |
483 |
|
484 |
if ( mConnectionRW )
|
485 |
{ |
486 |
mConnectionRW->unref(); |
487 |
mConnectionRW = nullptr; |
488 |
} |
489 |
} |
490 |
|
491 |
QString QgsPostgresProvider::quotedByteaValue( const QVariant &value )
|
492 |
{ |
493 |
if ( QgsVariantUtils::isNull( value ) )
|
494 |
return QStringLiteral( "NULL" ); |
495 |
|
496 |
const QByteArray ba = value.toByteArray();
|
497 |
const unsigned char *buf = reinterpret_cast<const unsigned char *>( ba.constData() ); |
498 |
QString param; |
499 |
param.reserve( ba.length() * 4 );
|
500 |
for ( int i = 0; i < ba.length(); ++i ) |
501 |
{ |
502 |
param += QStringLiteral( "\\%1" ).arg( static_cast<int>( buf[i] ), 3, 8, QChar( '0' ) ); |
503 |
} |
504 |
return QStringLiteral( "decode('%1','escape')" ).arg( param ); |
505 |
} |
506 |
|
507 |
QString QgsPostgresProvider::storageType() const
|
508 |
{ |
509 |
return QStringLiteral( "PostgreSQL database with PostGIS extension" ); |
510 |
} |
511 |
|
512 |
QgsFeatureIterator QgsPostgresProvider::getFeatures( const QgsFeatureRequest &request ) const |
513 |
{ |
514 |
if ( !mValid )
|
515 |
{ |
516 |
QgsMessageLog::logMessage( tr( "Read attempt on an invalid PostgreSQL data source" ), tr( "PostGIS" ) ); |
517 |
return QgsFeatureIterator();
|
518 |
} |
519 |
|
520 |
QgsPostgresFeatureSource *featureSrc = static_cast<QgsPostgresFeatureSource *>( featureSource() );
|
521 |
return QgsFeatureIterator( new QgsPostgresFeatureIterator( featureSrc, true, request ) ); |
522 |
} |
523 |
|
524 |
|
525 |
QString QgsPostgresProvider::pkParamWhereClause( int offset, const char *alias ) const |
526 |
{ |
527 |
QString whereClause; |
528 |
|
529 |
QString aliased; |
530 |
if ( alias )
|
531 |
aliased = QStringLiteral( "%1." ).arg( alias );
|
532 |
|
533 |
switch ( mPrimaryKeyType )
|
534 |
{ |
535 |
case PktTid:
|
536 |
whereClause = QStringLiteral( "%2ctid=$%1" ).arg( offset ).arg( aliased );
|
537 |
break;
|
538 |
|
539 |
case PktOid:
|
540 |
whereClause = QStringLiteral( "%2oid=$%1" ).arg( offset ).arg( aliased );
|
541 |
break;
|
542 |
|
543 |
case PktInt:
|
544 |
Q_ASSERT( mPrimaryKeyAttrs.size() == 1 );
|
545 |
whereClause = QStringLiteral( "%3%1=$%2" ).arg( quotedIdentifier( field( mPrimaryKeyAttrs[0] ).name() ) ).arg( offset ).arg( aliased ); |
546 |
break;
|
547 |
|
548 |
case PktInt64:
|
549 |
case PktUint64:
|
550 |
case PktFidMap:
|
551 |
{ |
552 |
QString delim; |
553 |
for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) |
554 |
{ |
555 |
int idx = mPrimaryKeyAttrs[i];
|
556 |
QgsField fld = field( idx ); |
557 |
|
558 |
whereClause += delim + QStringLiteral( "%3%1=$%2" ).arg( connectionRO()->fieldExpressionForWhereClause( fld ) ).arg( offset++ ).arg( aliased );
|
559 |
delim = QStringLiteral( " AND " );
|
560 |
} |
561 |
} |
562 |
break;
|
563 |
|
564 |
case PktUnknown:
|
565 |
Q_ASSERT( !"FAILURE: Primary key unknown" );
|
566 |
whereClause = QStringLiteral( "NULL" );
|
567 |
break;
|
568 |
} |
569 |
|
570 |
if ( !mSqlWhereClause.isEmpty() )
|
571 |
{ |
572 |
if ( !whereClause.isEmpty() )
|
573 |
whereClause += QLatin1String( " AND " );
|
574 |
|
575 |
whereClause += '(' + mSqlWhereClause + ')'; |
576 |
} |
577 |
|
578 |
return whereClause;
|
579 |
} |
580 |
|
581 |
void QgsPostgresProvider::appendPkParams( QgsFeatureId featureId, QStringList ¶ms ) const |
582 |
{ |
583 |
switch ( mPrimaryKeyType )
|
584 |
{ |
585 |
case PktOid:
|
586 |
params << QString::number( featureId ); |
587 |
break;
|
588 |
|
589 |
case PktInt:
|
590 |
params << QString::number( FID2PKINT( featureId ) ); |
591 |
break;
|
592 |
|
593 |
case PktTid:
|
594 |
params << QStringLiteral( "'(%1,%2)'" ).arg( FID_TO_NUMBER( featureId ) >> 16 ).arg( FID_TO_NUMBER( featureId ) & 0xffff ); |
595 |
break;
|
596 |
|
597 |
case PktInt64:
|
598 |
case PktUint64:
|
599 |
case PktFidMap:
|
600 |
{ |
601 |
QVariantList pkVals = mShared->lookupKey( featureId ); |
602 |
if ( !pkVals.isEmpty() )
|
603 |
{ |
604 |
Q_ASSERT( pkVals.size() == mPrimaryKeyAttrs.size() ); |
605 |
} |
606 |
|
607 |
for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) |
608 |
{ |
609 |
if ( i < pkVals.size() )
|
610 |
{ |
611 |
params << pkVals[i].toString(); |
612 |
} |
613 |
else
|
614 |
{ |
615 |
QgsDebugError( QStringLiteral( "FAILURE: Key value %1 for feature %2 not found." ).arg( mPrimaryKeyAttrs[i] ).arg( featureId ) );
|
616 |
params << QStringLiteral( "NULL" );
|
617 |
} |
618 |
} |
619 |
|
620 |
QgsDebugMsgLevel( QStringLiteral( "keys params: %1" ).arg( params.join( "; " ) ), 2 ); |
621 |
} |
622 |
break;
|
623 |
|
624 |
case PktUnknown:
|
625 |
Q_ASSERT( !"FAILURE: Primary key unknown" );
|
626 |
break;
|
627 |
} |
628 |
} |
629 |
|
630 |
|
631 |
QString QgsPostgresProvider::whereClause( QgsFeatureId featureId ) const
|
632 |
{ |
633 |
return QgsPostgresUtils::whereClause( featureId, mAttributeFields, connectionRO(), mPrimaryKeyType, mPrimaryKeyAttrs, mShared );
|
634 |
} |
635 |
|
636 |
QString QgsPostgresProvider::whereClause( QgsFeatureIds featureIds ) const
|
637 |
{ |
638 |
return QgsPostgresUtils::whereClause( featureIds, mAttributeFields, connectionRO(), mPrimaryKeyType, mPrimaryKeyAttrs, mShared );
|
639 |
} |
640 |
|
641 |
|
642 |
QString QgsPostgresUtils::whereClause( QgsFeatureId featureId, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList<int> &pkAttrs, const std::shared_ptr<QgsPostgresSharedData> &sharedData ) |
643 |
{ |
644 |
QString whereClause; |
645 |
|
646 |
switch ( pkType )
|
647 |
{ |
648 |
case PktTid:
|
649 |
whereClause = QStringLiteral( "ctid='(%1,%2)'" )
|
650 |
.arg( FID_TO_NUMBER( featureId ) >> 16 )
|
651 |
.arg( FID_TO_NUMBER( featureId ) & 0xffff );
|
652 |
break;
|
653 |
|
654 |
case PktOid:
|
655 |
whereClause = QStringLiteral( "oid=%1" ).arg( featureId );
|
656 |
break;
|
657 |
|
658 |
case PktInt:
|
659 |
Q_ASSERT( pkAttrs.size() == 1 );
|
660 |
whereClause = QStringLiteral( "%1=%2" ).arg( QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ).arg( FID2PKINT( featureId ) ); |
661 |
break;
|
662 |
|
663 |
case PktInt64:
|
664 |
case PktUint64:
|
665 |
{ |
666 |
Q_ASSERT( pkAttrs.size() == 1 );
|
667 |
QVariantList pkVals = sharedData->lookupKey( featureId ); |
668 |
if ( !pkVals.isEmpty() )
|
669 |
{ |
670 |
QgsField fld = fields.at( pkAttrs[0] );
|
671 |
whereClause = conn->fieldExpression( fld ); |
672 |
if ( !QgsVariantUtils::isNull( pkVals[0] ) ) |
673 |
whereClause += '=' + pkVals[0].toString(); |
674 |
else
|
675 |
whereClause += QLatin1String( " IS NULL" );
|
676 |
} |
677 |
} |
678 |
break;
|
679 |
|
680 |
case PktFidMap:
|
681 |
{ |
682 |
QVariantList pkVals = sharedData->lookupKey( featureId ); |
683 |
if ( !pkVals.isEmpty() )
|
684 |
{ |
685 |
Q_ASSERT( pkVals.size() == pkAttrs.size() ); |
686 |
|
687 |
QString delim; |
688 |
for ( int i = 0; i < pkAttrs.size(); i++ ) |
689 |
{ |
690 |
int idx = pkAttrs[i];
|
691 |
QgsField fld = fields.at( idx ); |
692 |
|
693 |
whereClause += delim + conn->fieldExpressionForWhereClause( fld, static_cast<QMetaType::Type>( pkVals[i].userType() ) );
|
694 |
if ( QgsVariantUtils::isNull( pkVals[i] ) )
|
695 |
whereClause += QLatin1String( " IS NULL" );
|
696 |
else
|
697 |
whereClause += '=' + QgsPostgresConn::quotedValue( pkVals[i] ); // remove toString as it must be handled by quotedValue function |
698 |
|
699 |
delim = QStringLiteral( " AND " );
|
700 |
} |
701 |
} |
702 |
else
|
703 |
{ |
704 |
QgsDebugError( QStringLiteral( "FAILURE: Key values for feature %1 not found." ).arg( featureId ) );
|
705 |
whereClause = QStringLiteral( "NULL" );
|
706 |
} |
707 |
} |
708 |
break;
|
709 |
|
710 |
case PktUnknown:
|
711 |
Q_ASSERT( !"FAILURE: Primary key unknown" );
|
712 |
whereClause = QStringLiteral( "NULL" );
|
713 |
break;
|
714 |
} |
715 |
|
716 |
return whereClause;
|
717 |
} |
718 |
|
719 |
QString QgsPostgresUtils::whereClause( const QgsFeatureIds &featureIds, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList<int> &pkAttrs, const std::shared_ptr<QgsPostgresSharedData> &sharedData ) |
720 |
{ |
721 |
auto lookupKeyWhereClause = [=] {
|
722 |
if ( featureIds.isEmpty() )
|
723 |
return QString();
|
724 |
|
725 |
//simple primary key, so prefer to use an "IN (...)" query. These are much faster then multiple chained ...OR... clauses
|
726 |
QString delim; |
727 |
QString expr = QStringLiteral( "%1 IN (" ).arg( QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ); |
728 |
|
729 |
for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) |
730 |
{ |
731 |
const QVariantList pkVals = sharedData->lookupKey( featureId );
|
732 |
if ( !pkVals.isEmpty() )
|
733 |
{ |
734 |
expr += delim + QgsPostgresConn::quotedValue( pkVals.at( 0 ) );
|
735 |
delim = ',';
|
736 |
} |
737 |
} |
738 |
expr += ')';
|
739 |
|
740 |
return expr;
|
741 |
}; |
742 |
|
743 |
switch ( pkType )
|
744 |
{ |
745 |
case PktOid:
|
746 |
case PktInt:
|
747 |
{ |
748 |
QString expr; |
749 |
|
750 |
//simple primary key, so prefer to use an "IN (...)" query. These are much faster then multiple chained ...OR... clauses
|
751 |
if ( !featureIds.isEmpty() )
|
752 |
{ |
753 |
QString delim; |
754 |
expr = QStringLiteral( "%1 IN (" ).arg( ( pkType == PktOid ? QStringLiteral( "oid" ) : QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ) ); |
755 |
|
756 |
for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) |
757 |
{ |
758 |
expr += delim + FID_TO_STRING( ( pkType == PktOid ? featureId : FID2PKINT( featureId ) ) ); |
759 |
delim = ',';
|
760 |
} |
761 |
expr += ')';
|
762 |
} |
763 |
|
764 |
return expr;
|
765 |
} |
766 |
case PktInt64:
|
767 |
case PktUint64:
|
768 |
return lookupKeyWhereClause();
|
769 |
|
770 |
case PktFidMap:
|
771 |
case PktTid:
|
772 |
case PktUnknown:
|
773 |
{ |
774 |
// on simple string primary key we can use IN
|
775 |
if ( pkType == PktFidMap && pkAttrs.count() == 1 && fields.at( pkAttrs[0] ).type() == QMetaType::Type::QString ) |
776 |
return lookupKeyWhereClause();
|
777 |
|
778 |
//complex primary key, need to build up where string
|
779 |
QStringList whereClauses; |
780 |
for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) |
781 |
{ |
782 |
whereClauses << whereClause( featureId, fields, conn, pkType, pkAttrs, sharedData ); |
783 |
} |
784 |
return whereClauses.isEmpty() ? QString() : whereClauses.join( QLatin1String( " OR " ) ).prepend( '(' ).append( ')' ); |
785 |
} |
786 |
} |
787 |
return QString(); //avoid warning |
788 |
} |
789 |
|
790 |
QString QgsPostgresUtils::andWhereClauses( const QString &c1, const QString &c2 ) |
791 |
{ |
792 |
if ( c1.isEmpty() )
|
793 |
return c2;
|
794 |
if ( c2.isEmpty() )
|
795 |
return c1;
|
796 |
|
797 |
return QStringLiteral( "(%1) AND (%2)" ).arg( c1, c2 ); |
798 |
} |
799 |
|
800 |
void QgsPostgresUtils::replaceInvalidXmlChars( QString &xml )
|
801 |
{ |
802 |
static const QRegularExpression replaceRe { QStringLiteral( "([\\x00-\\x08\\x0B-\\x1F\\x7F])" ) }; |
803 |
QRegularExpressionMatchIterator it { replaceRe.globalMatch( xml ) }; |
804 |
while ( it.hasNext() )
|
805 |
{ |
806 |
const QRegularExpressionMatch match { it.next() };
|
807 |
const QChar c { match.captured( 1 ).at( 0 ) }; |
808 |
xml.replace( c, QStringLiteral( "UTF-8[%1]" ).arg( c.unicode() ) );
|
809 |
} |
810 |
} |
811 |
|
812 |
void QgsPostgresUtils::restoreInvalidXmlChars( QString &xml )
|
813 |
{ |
814 |
static const QRegularExpression replaceRe { QStringLiteral( R"raw(UTF-8\[(\d+)\])raw" ) }; |
815 |
QRegularExpressionMatchIterator it { replaceRe.globalMatch( xml ) };
|
816 |
while ( it.hasNext() )
|
817 |
{
|
818 |
const QRegularExpressionMatch match { it.next() };
|
819 |
bool ok;
|
820 |
const ushort code { match.captured( 1 ).toUShort( &ok ) };
|
821 |
if ( ok )
|
822 |
{
|
823 |
xml.replace( QStringLiteral( "UTF-8[%1]" ).arg( code ), QChar( code ) ); |
824 |
}
|
825 |
}
|
826 |
}
|
827 |
|
828 |
QString QgsPostgresProvider::filterWhereClause() const
|
829 |
{
|
830 |
QString where;
|
831 |
QString delim = QStringLiteral( " WHERE " ); |
832 |
|
833 |
if ( !mSqlWhereClause.isEmpty() )
|
834 |
{
|
835 |
where += delim + '(' + mSqlWhereClause + ')';
|
836 |
delim = QStringLiteral( " AND " ); |
837 |
}
|
838 |
|
839 |
if ( !mRequestedSrid.isEmpty() && ( mRequestedSrid != mDetectedSrid || mRequestedSrid.toInt() == 0 ) )
|
840 |
{
|
841 |
where += delim + QStringLiteral( "%1(%2%3)=%4" ).arg( connectionRO()->majorVersion() < 2 ? "srid" : "st_srid", quotedIdentifier( mGeometryColumn ), mSpatialColType == SctGeography ? "::geography" : "", mRequestedSrid ); |
842 |
delim = QStringLiteral( " AND " ); |
843 |
}
|
844 |
|
845 |
if ( mRequestedGeomType != Qgis::WkbType::Unknown && mRequestedGeomType != mDetectedGeomType )
|
846 |
{
|
847 |
where += delim + QgsPostgresConn::postgisTypeFilter( mGeometryColumn, mRequestedGeomType, mSpatialColType == SctGeography );
|
848 |
delim = QStringLiteral( " AND " ); |
849 |
}
|
850 |
|
851 |
return where;
|
852 |
}
|
853 |
|
854 |
void QgsPostgresProvider::setExtent( const QgsRectangle &newExtent )
|
855 |
{
|
856 |
mLayerExtent.emplace( newExtent );
|
857 |
}
|
858 |
|
859 |
/**
|
860 |
* Returns the feature type
|
861 |
*/
|
862 |
Qgis::WkbType QgsPostgresProvider::wkbType() const
|
863 |
{
|
864 |
return mRequestedGeomType != Qgis::WkbType::Unknown ? mRequestedGeomType : mDetectedGeomType;
|
865 |
}
|
866 |
|
867 |
QgsLayerMetadata QgsPostgresProvider::layerMetadata() const
|
868 |
{
|
869 |
return mLayerMetadata;
|
870 |
}
|
871 |
|
872 |
QgsField QgsPostgresProvider::field( int index ) const
|
873 |
{
|
874 |
if ( index < 0 || index >= mAttributeFields.count() )
|
875 |
{
|
876 |
QgsMessageLog::logMessage( tr( "FAILURE: Field %1 not found." ).arg( index ), tr( "PostGIS" ) ); |
877 |
throw PGFieldNotFound();
|
878 |
}
|
879 |
|
880 |
return mAttributeFields.at( index );
|
881 |
}
|
882 |
|
883 |
QgsFields QgsPostgresProvider::fields() const
|
884 |
{
|
885 |
return mAttributeFields;
|
886 |
}
|
887 |
|
888 |
QString QgsPostgresProvider::dataComment() const
|
889 |
{
|
890 |
return mDataComment;
|
891 |
}
|
892 |
|
893 |
|
894 |
//! \todo XXX Perhaps this should be promoted to QgsDataProvider?
|
895 |
QString QgsPostgresProvider::endianString()
|
896 |
{
|
897 |
switch ( QgsApplication::endian() )
|
898 |
{
|
899 |
case QgsApplication::NDR:
|
900 |
return QStringLiteral( "NDR" ); |
901 |
case QgsApplication::XDR:
|
902 |
return QStringLiteral( "XDR" ); |
903 |
default:
|
904 |
return QStringLiteral( "Unknown" ); |
905 |
}
|
906 |
}
|
907 |
|
908 |
|
909 |
struct PGTypeInfo
|
910 |
{
|
911 |
QString typeName;
|
912 |
QString typeType;
|
913 |
QString typeElem;
|
914 |
int typeLen;
|
915 |
};
|
916 |
|
917 |
bool QgsPostgresProvider::loadFields()
|
918 |
{
|
919 |
// Clear cached information about enum values support
|
920 |
mShared->clearSupportsEnumValuesCache();
|
921 |
|
922 |
QString sql;
|
923 |
QString attroidsFilter;
|
924 |
|
925 |
if ( !mIsQuery )
|
926 |
{
|
927 |
QgsDebugMsgLevel( QStringLiteral( "Loading fields for table %1" ).arg( mTableName ), 2 ); |
928 |
|
929 |
if ( mLayerMetadata.abstract().isEmpty() )
|
930 |
{
|
931 |
// Get the table description
|
932 |
sql = QStringLiteral( "SELECT description FROM pg_description WHERE objoid=regclass(%1)::oid AND objsubid=0" ).arg( quotedValue( mQuery ) ); |
933 |
QgsPostgresResult tresult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); |
934 |
|
935 |
if ( !tresult.result() )
|
936 |
{
|
937 |
throw PGException( tresult );
|
938 |
}
|
939 |
|
940 |
if ( tresult.PQntuples() > 0 )
|
941 |
{
|
942 |
mDataComment = tresult.PQgetvalue( 0, 0 );
|
943 |
mLayerMetadata.setAbstract( mDataComment );
|
944 |
}
|
945 |
}
|
946 |
}
|
947 |
|
948 |
// Populate the field vector for this layer. The field vector contains
|
949 |
// field name, type, length, and precision (if numeric)
|
950 |
sql = QStringLiteral( "SELECT * FROM %1 LIMIT 0" ).arg( mQuery ); |
951 |
|
952 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); |
953 |
|
954 |
QMap<Oid, QMap<int, QString>> fmtFieldTypeMap, descrMap, defValMap, identityMap, generatedMap;
|
955 |
QMap<Oid, QMap<int, Oid>> attTypeIdMap;
|
956 |
QMap<Oid, QMap<int, bool>> notNullMap, uniqueMap;
|
957 |
if ( result.PQnfields() > 0 )
|
958 |
{
|
959 |
// Collect attribute oids
|
960 |
QSet<Oid> attroids;
|
961 |
for ( int i = 0; i < result.PQnfields(); i++ )
|
962 |
{
|
963 |
Oid attroid = result.PQftype( i );
|
964 |
attroids.insert( attroid );
|
965 |
}
|
966 |
|
967 |
// Collect table oids
|
968 |
QSet<Oid> tableoids;
|
969 |
for ( int i = 0; i < result.PQnfields(); i++ )
|
970 |
{
|
971 |
Oid tableoid = result.PQftable( i );
|
972 |
if ( tableoid > 0 )
|
973 |
{
|
974 |
tableoids.insert( tableoid );
|
975 |
}
|
976 |
}
|
977 |
|
978 |
if ( !tableoids.isEmpty() )
|
979 |
{
|
980 |
QStringList tableoidsList;
|
981 |
const auto constTableoids = tableoids;
|
982 |
for ( Oid tableoid : constTableoids )
|
983 |
{
|
984 |
tableoidsList.append( QString::number( tableoid ) );
|
985 |
}
|
986 |
|
987 |
QString tableoidsFilter = '(' + tableoidsList.join( QLatin1Char( ',' ) ) + ')';
|
988 |
|
989 |
// Collect formatted field types
|
990 |
sql = QStringLiteral(
|
991 |
"SELECT attrelid, attnum, pg_catalog.format_type(atttypid,atttypmod), pg_catalog.col_description(attrelid,attnum), pg_catalog.pg_get_expr(adbin,adrelid), atttypid, attnotnull::int, indisunique::int%1%2" |
992 |
" FROM pg_attribute" |
993 |
" LEFT OUTER JOIN pg_attrdef ON attrelid=adrelid AND attnum=adnum" |
994 |
|
995 |
// find unique constraints if present. Text cast required to handle int2vector comparison. Distinct required as multiple unique constraints may exist
|
996 |
" LEFT OUTER JOIN ( SELECT DISTINCT indrelid, indkey, indisunique FROM pg_index WHERE indisunique ) uniq ON attrelid=indrelid AND attnum::text=indkey::text " |
997 |
|
998 |
" WHERE attrelid IN %3" |
999 |
)
|
1000 |
.arg( connectionRO()->pgVersion() >= 100000 ? QStringLiteral( ", attidentity" ) : QString(), connectionRO()->pgVersion() >= 120000 ? QStringLiteral( ", attgenerated" ) : QString(), tableoidsFilter ); |
1001 |
|
1002 |
QgsPostgresResult fmtFieldTypeResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); |
1003 |
|
1004 |
if ( !fmtFieldTypeResult.result() )
|
1005 |
{
|
1006 |
throw PGException( fmtFieldTypeResult );
|
1007 |
}
|
1008 |
|
1009 |
for ( int i = 0; i < fmtFieldTypeResult.PQntuples(); ++i )
|
1010 |
{
|
1011 |
Oid attrelid = fmtFieldTypeResult.PQgetvalue( i, 0 ).toUInt();
|
1012 |
int attnum = fmtFieldTypeResult.PQgetvalue( i, 1 ).toInt(); // Int2
|
1013 |
QString formatType = fmtFieldTypeResult.PQgetvalue( i, 2 );
|
1014 |
QString descr = fmtFieldTypeResult.PQgetvalue( i, 3 );
|
1015 |
QString defVal = fmtFieldTypeResult.PQgetvalue( i, 4 );
|
1016 |
Oid attType = fmtFieldTypeResult.PQgetvalue( i, 5 ).toUInt();
|
1017 |
bool attNotNull = fmtFieldTypeResult.PQgetvalue( i, 6 ).toInt();
|
1018 |
bool uniqueConstraint = fmtFieldTypeResult.PQgetvalue( i, 7 ).toInt();
|
1019 |
QString attIdentity = connectionRO()->pgVersion() >= 100000 ? fmtFieldTypeResult.PQgetvalue( i, 8 ) : " "; |
1020 |
|
1021 |
// On PostgreSQL 12, the field pg_attribute.attgenerated is always filled with "s" if the field is generated, |
1022 |
// with the possibility of other values in future releases. This indicates "STORED" generated fields. |
1023 |
// The documentation for version 12 indicates that there is a future possibility of supporting virtual
|
1024 |
// generated values, which might make possible to have values other than "s" on pg_attribute.attgenerated, |
1025 |
// which should be unimportant for QGIS if the user still won't be able to overwrite the column value.
|
1026 |
// See https://www.postgresql.org/docs/12/ddl-generated-columns.html
|
1027 |
QString attGenerated = connectionRO()->pgVersion() >= 120000 ? fmtFieldTypeResult.PQgetvalue( i, 9 ) : ""; |
1028 |
fmtFieldTypeMap[attrelid][attnum] = formatType;
|
1029 |
descrMap[attrelid][attnum] = descr;
|
1030 |
defValMap[attrelid][attnum] = defVal;
|
1031 |
attTypeIdMap[attrelid][attnum] = attType;
|
1032 |
notNullMap[attrelid][attnum] = attNotNull;
|
1033 |
uniqueMap[attrelid][attnum] = uniqueConstraint;
|
1034 |
identityMap[attrelid][attnum] = attIdentity.isEmpty() ? " " : attIdentity; |
1035 |
generatedMap[attrelid][attnum] = attGenerated.isEmpty() ? QString() : defVal;
|
1036 |
|
1037 |
// Also include atttype oid from pg_attribute, because PQnfields only returns basic type for for domains
|
1038 |
attroids.insert( attType );
|
1039 |
}
|
1040 |
}
|
1041 |
|
1042 |
// Prepare filter for fetching pg_type info
|
1043 |
if ( !attroids.isEmpty() )
|
1044 |
{
|
1045 |
QStringList attroidsList;
|
1046 |
for ( Oid attroid : std::as_const( attroids ) )
|
1047 |
{
|
1048 |
attroidsList.append( QString::number( attroid ) );
|
1049 |
}
|
1050 |
attroidsFilter = QStringLiteral( "WHERE oid in (%1)" ).arg( attroidsList.join( ',' ) ); |
1051 |
}
|
1052 |
}
|
1053 |
|
1054 |
// Collect type info
|
1055 |
sql = QStringLiteral( "SELECT oid,typname,typtype,typelem,typlen FROM pg_type %1" ).arg( attroidsFilter ); |
1056 |
QgsPostgresResult typeResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); |
1057 |
|
1058 |
QMap<Oid, PGTypeInfo> typeMap;
|
1059 |
for ( int i = 0; i < typeResult.PQntuples(); ++i )
|
1060 |
{
|
1061 |
PGTypeInfo typeInfo = {
|
1062 |
/* typeName = */ typeResult.PQgetvalue( i, 1 ),
|
1063 |
/* typeType = */ typeResult.PQgetvalue( i, 2 ),
|
1064 |
/* typeElem = */ typeResult.PQgetvalue( i, 3 ),
|
1065 |
/* typeLen = */ typeResult.PQgetvalue( i, 4 ).toInt()
|
1066 |
};
|
1067 |
typeMap.insert( typeResult.PQgetvalue( i, 0 ).toUInt(), typeInfo );
|
1068 |
}
|
1069 |
|
1070 |
QSet<QString> fields;
|
1071 |
mAttributeFields.clear();
|
1072 |
mIdentityFields.clear();
|
1073 |
for ( int i = 0; i < result.PQnfields(); i++ )
|
1074 |
{
|
1075 |
QString fieldName = result.PQfname( i );
|
1076 |
Oid tableoid = result.PQftable( i );
|
1077 |
int attnum = result.PQftablecol( i );
|
1078 |
QString formattedFieldType = fmtFieldTypeMap[tableoid][attnum];
|
1079 |
if ( fieldName == mGeometryColumn )
|
1080 |
{
|
1081 |
if ( mDetectedGeomType == Qgis::WkbType::Unknown )
|
1082 |
{
|
1083 |
// rare case, elevation can not be computed via mDetectedGeomType, we parse the formattedFieldType and look for Z data
|
1084 |
const thread_local QRegularExpression re( QRegularExpression::anchoredPattern( "geometry\\(\\w+\\s*ZM?(,\\d+)?\\)" ), QRegularExpression::CaseInsensitiveOption ); |
1085 |
const QRegularExpressionMatch match = re.match( formattedFieldType );
|
1086 |
elevationProperties()->setContainsElevationData( match.hasMatch() );
|
1087 |
}
|
1088 |
continue;
|
1089 |
}
|
1090 |
|
1091 |
Oid fldtyp = result.PQftype( i );
|
1092 |
int fldMod = result.PQfmod( i );
|
1093 |
int fieldPrec = 0;
|
1094 |
Oid atttypid = attTypeIdMap[tableoid][attnum];
|
1095 |
|
1096 |
const PGTypeInfo &typeInfo = typeMap.value( fldtyp );
|
1097 |
QString fieldTypeName = typeInfo.typeName;
|
1098 |
QString fieldTType = typeInfo.typeType;
|
1099 |
int fieldSize = typeInfo.typeLen;
|
1100 |
|
1101 |
bool isDomain = ( typeMap.value( atttypid ).typeType == QLatin1String( "d" ) ); |
1102 |
|
1103 |
QString originalFormattedFieldType = formattedFieldType;
|
1104 |
if ( isDomain )
|
1105 |
{
|
1106 |
// get correct formatted field type for domain
|
1107 |
sql = QStringLiteral( "SELECT format_type(%1, %2)" ).arg( fldtyp ).arg( fldMod ); |
1108 |
QgsPostgresResult fmtFieldModResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); |
1109 |
if ( fmtFieldModResult.PQntuples() > 0 )
|
1110 |
{
|
1111 |
formattedFieldType = fmtFieldModResult.PQgetvalue( 0, 0 );
|
1112 |
}
|
1113 |
}
|
1114 |
|
1115 |
QString fieldComment = descrMap[tableoid][attnum];
|
1116 |
|
1117 |
QMetaType::Type fieldType = QMetaType::Type::UnknownType;
|
1118 |
QMetaType::Type fieldSubType = QMetaType::Type::UnknownType;
|
1119 |
|
1120 |
if ( fieldTType == QLatin1String( "b" ) ) |
1121 |
{
|
1122 |
bool isArray = fieldTypeName.startsWith( '_' );
|
1123 |
|
1124 |
if ( isArray )
|
1125 |
fieldTypeName = fieldTypeName.mid( 1 );
|
1126 |
|
1127 |
if ( fieldTypeName == QLatin1String( "int8" ) || fieldTypeName == QLatin1String( "serial8" ) ) |
1128 |
{
|
1129 |
fieldType = QMetaType::Type::LongLong;
|
1130 |
fieldSize = -1;
|
1131 |
fieldPrec = 0;
|
1132 |
}
|
1133 |
else if ( fieldTypeName == QLatin1String( "int2" ) || fieldTypeName == QLatin1String( "int4" ) || fieldTypeName == QLatin1String( "oid" ) || fieldTypeName == QLatin1String( "serial" ) ) |
1134 |
{
|
1135 |
fieldType = QMetaType::Type::Int;
|
1136 |
fieldSize = -1;
|
1137 |
fieldPrec = 0;
|
1138 |
}
|
1139 |
else if ( fieldTypeName == QLatin1String( "real" ) || fieldTypeName == QLatin1String( "double precision" ) || fieldTypeName == QLatin1String( "float4" ) || fieldTypeName == QLatin1String( "float8" ) ) |
1140 |
{
|
1141 |
fieldType = QMetaType::Type::Double;
|
1142 |
fieldSize = -1;
|
1143 |
fieldPrec = 0;
|
1144 |
}
|
1145 |
else if ( fieldTypeName == QLatin1String( "numeric" ) ) |
1146 |
{
|
1147 |
fieldType = QMetaType::Type::Double;
|
1148 |
|
1149 |
if ( formattedFieldType == QLatin1String( "numeric" ) || formattedFieldType.isEmpty() ) |
1150 |
{
|
1151 |
fieldSize = -1;
|
1152 |
fieldPrec = 0;
|
1153 |
}
|
1154 |
else
|
1155 |
{
|
1156 |
const thread_local QRegularExpression re( QRegularExpression::anchoredPattern( QStringLiteral( "numeric\\((\\d+),(\\d+)\\)" ) ) ); |
1157 |
const QRegularExpressionMatch match = re.match( formattedFieldType );
|
1158 |
if ( match.hasMatch() )
|
1159 |
{
|
1160 |
fieldSize = match.captured( 1 ).toInt();
|
1161 |
fieldPrec = match.captured( 2 ).toInt();
|
1162 |
}
|
1163 |
else if ( formattedFieldType != QLatin1String( "numeric" ) ) |
1164 |
{
|
1165 |
QgsMessageLog::logMessage( tr( "Unexpected formatted field type '%1' for field %2" ).arg( formattedFieldType, fieldName ), tr( "PostGIS" ) ); |
1166 |
fieldSize = -1;
|
1167 |
fieldPrec = 0;
|
1168 |
}
|
1169 |
}
|
1170 |
}
|
1171 |
else if ( fieldTypeName == QLatin1String( "money" ) ) |
1172 |
{
|
1173 |
fieldType = QMetaType::Type::Double;
|
1174 |
fieldSize = -1;
|
1175 |
fieldPrec = 2;
|
1176 |
}
|
1177 |
else if ( fieldTypeName == QLatin1String( "varchar" ) ) |
1178 |
{
|
1179 |
fieldType = QMetaType::Type::QString;
|
1180 |
|
1181 |
const thread_local QRegularExpression re( QRegularExpression::anchoredPattern( "character varying\\((\\d+)\\)" ) ); |
1182 |
const QRegularExpressionMatch match = re.match( formattedFieldType );
|
1183 |
if ( match.hasMatch() )
|
1184 |
{
|
1185 |
fieldSize = match.captured( 1 ).toInt();
|
1186 |
}
|
1187 |
else
|
1188 |
{
|
1189 |
fieldSize = -1;
|
1190 |
}
|
1191 |
}
|
1192 |
else if ( fieldTypeName == QLatin1String( "date" ) ) |
1193 |
{
|
1194 |
fieldType = QMetaType::Type::QDate;
|
1195 |
fieldSize = -1;
|
1196 |
}
|
1197 |
else if ( fieldTypeName == QLatin1String( "time" ) ) |
1198 |
{
|
1199 |
fieldType = QMetaType::Type::QTime;
|
1200 |
fieldSize = -1;
|
1201 |
}
|
1202 |
else if ( fieldTypeName == QLatin1String( "timestamp" ) || fieldTypeName == QLatin1String( "timestamptz" ) ) |
1203 |
{
|
1204 |
fieldType = QMetaType::Type::QDateTime;
|
1205 |
fieldSize = -1;
|
1206 |
}
|
1207 |
else if ( fieldTypeName == QLatin1String( "bytea" ) ) |
1208 |
{
|
1209 |
fieldType = QMetaType::Type::QByteArray;
|
1210 |
fieldSize = -1;
|
1211 |
}
|
1212 |
else if ( fieldTypeName == QLatin1String( "text" ) || fieldTypeName == QLatin1String( "citext" ) || fieldTypeName == QLatin1String( "geography" ) || fieldTypeName == QLatin1String( "inet" ) || fieldTypeName == QLatin1String( "cidr" ) || fieldTypeName == QLatin1String( "macaddr" ) || fieldTypeName == QLatin1String( "macaddr8" ) || fieldTypeName == QLatin1String( "ltree" ) || fieldTypeName == QLatin1String( "uuid" ) || fieldTypeName == QLatin1String( "xml" ) || fieldTypeName == QLatin1String( "bit" ) || fieldTypeName == QLatin1String( "varbit" ) || fieldTypeName.startsWith( QLatin1String( "time" ) ) || fieldTypeName.startsWith( QLatin1String( "date" ) ) ) |
1213 |
{
|
1214 |
fieldType = QMetaType::Type::QString;
|
1215 |
fieldSize = -1;
|
1216 |
}
|
1217 |
else if ( fieldTypeName == QLatin1String( "geometry" ) ) |
1218 |
{
|
1219 |
fieldType = QMetaType::Type::User;
|
1220 |
fieldSize = -1;
|
1221 |
}
|
1222 |
else if ( fieldTypeName == QLatin1String( "bpchar" ) ) |
1223 |
{
|
1224 |
// although postgres internally uses "bpchar", this is exposed to users as character in postgres |
1225 |
fieldTypeName = QStringLiteral( "character" ); |
1226 |
|
1227 |
fieldType = QMetaType::Type::QString;
|
1228 |
|
1229 |
const thread_local QRegularExpression re( QRegularExpression::anchoredPattern( "character\\((\\d+)\\)" ) ); |
1230 |
const QRegularExpressionMatch match = re.match( formattedFieldType );
|
1231 |
if ( match.hasMatch() )
|
1232 |
{
|
1233 |
fieldSize = match.captured( 1 ).toInt();
|
1234 |
}
|
1235 |
else
|
1236 |
{
|
1237 |
QgsDebugError( QStringLiteral( "Unexpected formatted field type '%1' for field %2" ) |
1238 |
.arg( formattedFieldType, fieldName ) );
|
1239 |
fieldSize = -1;
|
1240 |
fieldPrec = 0;
|
1241 |
}
|
1242 |
}
|
1243 |
else if ( fieldTypeName == QLatin1String( "char" ) ) |
1244 |
{
|
1245 |
fieldType = QMetaType::Type::QString;
|
1246 |
|
1247 |
const thread_local QRegularExpression re( QRegularExpression::anchoredPattern( QStringLiteral( "char\\((\\d+)\\)" ) ) ); |
1248 |
const QRegularExpressionMatch match = re.match( formattedFieldType );
|
1249 |
if ( match.hasMatch() )
|
1250 |
{
|
1251 |
fieldSize = match.captured( 1 ).toInt();
|
1252 |
}
|
1253 |
else
|
1254 |
{
|
1255 |
QgsMessageLog::logMessage( tr( "Unexpected formatted field type '%1' for field %2" ) |
1256 |
.arg( formattedFieldType, fieldName ) );
|
1257 |
fieldSize = -1;
|
1258 |
fieldPrec = 0;
|
1259 |
}
|
1260 |
}
|
1261 |
else if ( fieldTypeName == QLatin1String( "hstore" ) || fieldTypeName == QLatin1String( "json" ) || fieldTypeName == QLatin1String( "jsonb" ) ) |
1262 |
{
|
1263 |
fieldType = QMetaType::Type::QVariantMap;
|
1264 |
fieldSubType = QMetaType::Type::QString;
|
1265 |
fieldSize = -1;
|
1266 |
}
|
1267 |
else if ( fieldTypeName == QLatin1String( "bool" ) ) |
1268 |
{
|
1269 |
// enum
|
1270 |
fieldType = QMetaType::Type::Bool;
|
1271 |
fieldSize = -1;
|
1272 |
}
|
1273 |
// PG 12 returns "name" type for some system table fields (e.g. information_schema.tables) |
1274 |
else if ( fieldTypeName == QLatin1String( "name" ) ) |
1275 |
{
|
1276 |
fieldType = QMetaType::Type::QString;
|
1277 |
fieldSize = 63;
|
1278 |
}
|
1279 |
else
|
1280 |
{
|
1281 |
// be tolerant in case of views: this might be a field used as a key
|
1282 |
const Qgis::PostgresRelKind type = relkind();
|
1283 |
if ( ( type == Qgis::PostgresRelKind::View || type == Qgis::PostgresRelKind::MaterializedView ) && parseUriKey( mUri.keyColumn() ).contains( fieldName ) )
|
1284 |
{
|
1285 |
// Assume it is convertible to text
|
1286 |
fieldType = QMetaType::Type::QString;
|
1287 |
fieldSize = -1;
|
1288 |
}
|
1289 |
else if ( fieldTypeName == QLatin1String( "unknown" ) ) |
1290 |
{
|
1291 |
// Assume it is convertible to text
|
1292 |
fieldType = QMetaType::Type::QString;
|
1293 |
fieldSize = -1;
|
1294 |
}
|
1295 |
else
|
1296 |
{
|
1297 |
QgsMessageLog::logMessage( tr( "Field %1 ignored, because of unsupported type %2" ).arg( fieldName, fieldTypeName ), tr( "PostGIS" ) ); |
1298 |
continue;
|
1299 |
}
|
1300 |
}
|
1301 |
|
1302 |
if ( isArray )
|
1303 |
{
|
1304 |
fieldTypeName = '_' + fieldTypeName;
|
1305 |
fieldSubType = fieldType;
|
1306 |
fieldType = ( fieldType == QMetaType::Type::QString ? QMetaType::Type::QStringList : QMetaType::Type::QVariantList );
|
1307 |
fieldSize = -1;
|
1308 |
}
|
1309 |
}
|
1310 |
else if ( fieldTType == QLatin1String( "e" ) ) |
1311 |
{
|
1312 |
// enum
|
1313 |
fieldType = QMetaType::Type::QString;
|
1314 |
fieldSize = -1;
|
1315 |
}
|
1316 |
else
|
1317 |
{
|
1318 |
QgsMessageLog::logMessage( tr( "Field %1 ignored, because of unsupported type %2" ).arg( fieldName, fieldTType ), tr( "PostGIS" ) ); |
1319 |
continue;
|
1320 |
}
|
1321 |
|
1322 |
if ( fields.contains( fieldName ) )
|
1323 |
{
|
1324 |
QgsMessageLog::logMessage( tr( "Duplicate field %1 found\n" ).arg( fieldName ), tr( "PostGIS" ) ); |
1325 |
// In case of read-only query layers we can safely ignore the issue and rename the duplicated field
|
1326 |
if ( !mIsQuery )
|
1327 |
{
|
1328 |
return false;
|
1329 |
}
|
1330 |
else
|
1331 |
{
|
1332 |
unsigned short int i = 1;
|
1333 |
while ( i < std::numeric_limits<unsigned short int>::max() )
|
1334 |
{
|
1335 |
const QString newName { QStringLiteral( "%1 (%2)" ).arg( fieldName ).arg( ++i ) }; |
1336 |
if ( !fields.contains( newName ) )
|
1337 |
{
|
1338 |
fieldName = newName;
|
1339 |
break;
|
1340 |
}
|
1341 |
}
|
1342 |
}
|
1343 |
}
|
1344 |
|
1345 |
fields << fieldName;
|
1346 |
|
1347 |
if ( isDomain )
|
1348 |
{
|
1349 |
//field was defined using domain, so use domain type name for fieldTypeName
|
1350 |
fieldTypeName = originalFormattedFieldType;
|
1351 |
}
|
1352 |
|
1353 |
// If this is an identity field with constraints and there is no default, let's look for a sequence:
|
1354 |
// we might have a default value created by a sequence named <table>_<field>_seq
|
1355 |
if ( !identityMap[tableoid][attnum].isEmpty()
|
1356 |
&& notNullMap[tableoid][attnum]
|
1357 |
&& uniqueMap[tableoid][attnum]
|
1358 |
&& defValMap[tableoid][attnum].isEmpty() )
|
1359 |
{
|
1360 |
const QString seqSql = QStringLiteral( "SELECT pg_get_serial_sequence(%1, %2)" ) |
1361 |
.arg( quotedValue( mQuery ) )
|
1362 |
.arg( quotedValue( fieldName ) );
|
1363 |
QgsPostgresResult seqResult( connectionRO()->PQexec( seqSql ) );
|
1364 |
if ( seqResult.PQntuples() == 1 && !seqResult.PQgetisnull( 0, 0 ) )
|
1365 |
{
|
1366 |
defValMap[tableoid][attnum] = QStringLiteral( "nextval(%1)" ).arg( quotedValue( seqResult.PQgetvalue( 0, 0 ) ) ); |
1367 |
}
|
1368 |
}
|
1369 |
|
1370 |
mDefaultValues.insert( mAttributeFields.size(), defValMap[tableoid][attnum] );
|
1371 |
|
1372 |
const QString generatedValue = generatedMap[tableoid][attnum];
|
1373 |
if ( !generatedValue.isNull() )
|
1374 |
mGeneratedValues.insert( mAttributeFields.size(), generatedValue );
|
1375 |
|
1376 |
QgsField newField = QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment, fieldSubType );
|
1377 |
newField.setReadOnly( !generatedValue.isNull() );
|
1378 |
|
1379 |
QgsFieldConstraints constraints;
|
1380 |
if ( notNullMap[tableoid][attnum] || ( mPrimaryKeyAttrs.size() == 1 && mPrimaryKeyAttrs[0] == i ) || identityMap[tableoid][attnum] != ' ' )
|
1381 |
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider );
|
1382 |
if ( uniqueMap[tableoid][attnum] || ( mPrimaryKeyAttrs.size() == 1 && mPrimaryKeyAttrs[0] == i ) || identityMap[tableoid][attnum] != ' ' )
|
1383 |
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider );
|
1384 |
newField.setConstraints( constraints );
|
1385 |
|
1386 |
mIdentityFields.insert( mAttributeFields.size(), identityMap[tableoid][attnum][0].toLatin1() );
|
1387 |
mAttributeFields.append( newField );
|
1388 |
|
1389 |
// if we know for sure that this field is not enumerated type or a domain type, let's
|
1390 |
// mark it here, so that enumValues() does not need to check it again (for types like int, text, ...)
|
1391 |
if ( fieldTType != QLatin1String( "e" ) && !isDomain ) |
1392 |
mShared->setFieldSupportsEnumValues( fields.count() - 1, false );
|
1393 |
}
|
1394 |
|
1395 |
setEditorWidgets();
|
1396 |
|
1397 |
return true;
|
1398 |
}
|
1399 |
|
1400 |
void QgsPostgresProvider::setEditorWidgets()
|
1401 |
{
|
1402 |
if ( !tableExists( *connectionRO(), EDITOR_WIDGET_STYLES_TABLE ) )
|
1403 |
{
|
1404 |
return;
|
1405 |
}
|
1406 |
|
1407 |
QStringList quotedFnames;
|
1408 |
const QStringList fieldNames = mAttributeFields.names();
|
1409 |
for ( const QString &name : fieldNames )
|
1410 |
{
|
1411 |
quotedFnames << quotedValue( name );
|
1412 |
}
|
1413 |
|
1414 |
// We expect the table to be created like this:
|
1415 |
//
|
1416 |
// CREATE TABLE qgis_editor_widget_styles (schema_name TEXT NOT NULL, table_name TEXT NOT NULL, field_name TEXT NOT NULL,
|
1417 |
// type TEXT NOT NULL, config TEXT,
|
1418 |
// PRIMARY KEY(schema_name, table_name, field_name));
|
1419 |
const QString sql = QStringLiteral( "SELECT field_name, type, config " |
1420 |
"FROM %1 WHERE schema_name = %2 " |
1421 |
"AND table_name = %3 " |
1422 |
"AND field_name IN ( %4 )" ) |
1423 |
.arg( EDITOR_WIDGET_STYLES_TABLE, quotedValue( mSchemaName ), quotedValue( mTableName ), quotedFnames.join( "," ) ); |
1424 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); |
1425 |
for ( int i = 0; i < result.PQntuples(); ++i )
|
1426 |
{
|
1427 |
if ( result.PQgetisnull( i, 2 ) )
|
1428 |
continue; // config can be null and it's OK
|
1429 |
|
1430 |
const QString &configTxt = result.PQgetvalue( i, 2 );
|
1431 |
const QString &type = result.PQgetvalue( i, 1 );
|
1432 |
const QString &fname = result.PQgetvalue( i, 0 );
|
1433 |
QVariantMap config;
|
1434 |
QDomDocument doc;
|
1435 |
if ( doc.setContent( configTxt ) )
|
1436 |
{
|
1437 |
config = QgsXmlUtils::readVariant( doc.documentElement() ).toMap();
|
1438 |
}
|
1439 |
else
|
1440 |
{
|
1441 |
QgsMessageLog::logMessage(
|
1442 |
tr( "Cannot parse widget configuration for field %1.%2.%3\n" ) |
1443 |
.arg( mSchemaName, mTableName, fname ),
|
1444 |
tr( "PostGIS" ) |
1445 |
);
|
1446 |
continue;
|
1447 |
}
|
1448 |
|
1449 |
// Set corresponding editor widget
|
1450 |
for ( auto &field : mAttributeFields )
|
1451 |
{
|
1452 |
if ( field.name() == fname )
|
1453 |
{
|
1454 |
field.setEditorWidgetSetup( QgsEditorWidgetSetup( type, config ) );
|
1455 |
break;
|
1456 |
}
|
1457 |
}
|
1458 |
}
|
1459 |
}
|
1460 |
|
1461 |
bool QgsPostgresProvider::hasSufficientPermsAndCapabilities()
|
1462 |
{
|
1463 |
QgsDebugMsgLevel( QStringLiteral( "Checking for permissions on the relation" ), 2 ); |
1464 |
|
1465 |
mEnabledCapabilities = Qgis::VectorProviderCapability::ReloadData;
|
1466 |
|
1467 |
QgsPostgresResult testAccess;
|
1468 |
|
1469 |
bool forceReadOnly = ( mReadFlags & Qgis::DataProviderReadFlag::ForceReadOnly );
|
1470 |
bool inRecovery = false;
|
1471 |
|
1472 |
if ( !mIsQuery )
|
1473 |
{
|
1474 |
// postgres has fast access to features at id (thanks to primary key / unique index)
|
1475 |
// the latter flag is here just for compatibility
|
1476 |
if ( !mSelectAtIdDisabled )
|
1477 |
{
|
1478 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::SelectAtId;
|
1479 |
}
|
1480 |
|
1481 |
QString sql = QStringLiteral(
|
1482 |
"SELECT " |
1483 |
"has_table_privilege(%1,'SELECT')," // 0 (select priv) |
1484 |
"pg_is_in_recovery()," // 1 (in recovery) |
1485 |
"current_schema() " // 2 (current schema) |
1486 |
)
|
1487 |
.arg( quotedValue( mQuery ) );
|
1488 |
|
1489 |
|
1490 |
if ( connectionRO()->pgVersion() >= 80400 )
|
1491 |
{
|
1492 |
sql += QString(
|
1493 |
",has_any_column_privilege(%1,'INSERT')" // 3 (insert priv) |
1494 |
",has_table_privilege(%1,'DELETE')" // 4 (delete priv) |
1495 |
",has_any_column_privilege(%1,'UPDATE')" // 5 (update priv) |
1496 |
",%2" // 6 (geom upd priv) |
1497 |
)
|
1498 |
.arg( quotedValue( mQuery ), mGeometryColumn.isNull() ? QStringLiteral( "'f'" ) : QStringLiteral( "has_column_privilege(%1,%2,'UPDATE')" ).arg( quotedValue( mQuery ), quotedValue( mGeometryColumn ) ) ); |
1499 |
}
|
1500 |
else
|
1501 |
{
|
1502 |
sql += QString(
|
1503 |
",has_table_privilege(%1,'INSERT')" // 3 (insert priv) |
1504 |
",has_table_privilege(%1,'DELETE')" // 4 (delete priv) |
1505 |
",has_table_privilege(%1,'UPDATE')" // 5 (update priv) |
1506 |
",has_table_privilege(%1,'UPDATE')" // 6 (geom col priv) |
1507 |
)
|
1508 |
.arg( quotedValue( mQuery ) );
|
1509 |
}
|
1510 |
|
1511 |
testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); |
1512 |
if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK )
|
1513 |
{
|
1514 |
QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" ).arg( mQuery, testAccess.PQresultErrorMessage(), sql ), tr( "PostGIS" ) ); |
1515 |
return false;
|
1516 |
}
|
1517 |
|
1518 |
if ( testAccess.PQgetvalue( 0, 0 ) != QLatin1String( "t" ) ) |
1519 |
{
|
1520 |
// SELECT
|
1521 |
QgsMessageLog::logMessage( tr( "User has no SELECT privilege on %1 relation." ).arg( mQuery ), tr( "PostGIS" ) ); |
1522 |
return false;
|
1523 |
}
|
1524 |
|
1525 |
if ( testAccess.PQgetvalue( 0, 1 ) == QLatin1String( "t" ) ) |
1526 |
{
|
1527 |
// RECOVERY
|
1528 |
QgsMessageLog::logMessage(
|
1529 |
tr( "PostgreSQL is still in recovery after a database crash\n(or you are connected to a (read-only) standby server).\nWrite accesses will be denied." ), |
1530 |
tr( "PostGIS" ) |
1531 |
);
|
1532 |
inRecovery = true;
|
1533 |
}
|
1534 |
|
1535 |
// CURRENT SCHEMA
|
1536 |
if ( mSchemaName.isEmpty() )
|
1537 |
mSchemaName = testAccess.PQgetvalue( 0, 2 );
|
1538 |
|
1539 |
// Do not set editable capabilities if the provider has been forced to be
|
1540 |
// in read-only mode or if the database is still in recovery
|
1541 |
if ( !forceReadOnly && !inRecovery )
|
1542 |
{
|
1543 |
if ( testAccess.PQgetvalue( 0, 3 ) == QLatin1String( "t" ) ) |
1544 |
{
|
1545 |
// INSERT
|
1546 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::AddFeatures;
|
1547 |
}
|
1548 |
|
1549 |
if ( testAccess.PQgetvalue( 0, 4 ) == QLatin1String( "t" ) ) |
1550 |
{
|
1551 |
// DELETE
|
1552 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::DeleteFeatures | Qgis::VectorProviderCapability::FastTruncate;
|
1553 |
}
|
1554 |
|
1555 |
if ( testAccess.PQgetvalue( 0, 5 ) == QLatin1String( "t" ) ) |
1556 |
{
|
1557 |
// UPDATE
|
1558 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::ChangeAttributeValues;
|
1559 |
}
|
1560 |
|
1561 |
if ( testAccess.PQgetvalue( 0, 6 ) == QLatin1String( "t" ) ) |
1562 |
{
|
1563 |
// UPDATE (geom column specific)
|
1564 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::ChangeGeometries;
|
1565 |
}
|
1566 |
|
1567 |
// TODO: merge this with the previous query
|
1568 |
sql = QString( "SELECT 1 FROM pg_class,pg_namespace WHERE " |
1569 |
"pg_class.relnamespace=pg_namespace.oid AND " |
1570 |
"%3 AND " |
1571 |
"relname=%1 AND nspname=%2" ) |
1572 |
.arg( quotedValue( mTableName ), quotedValue( mSchemaName ), connectionRO()->pgVersion() < 80100 ? "pg_get_userbyid(relowner)=current_user" : "pg_has_role(relowner,'MEMBER')" ); |
1573 |
testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); |
1574 |
if ( testAccess.PQresultStatus() == PGRES_TUPLES_OK && testAccess.PQntuples() == 1 )
|
1575 |
{
|
1576 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::AddAttributes | Qgis::VectorProviderCapability::DeleteAttributes | Qgis::VectorProviderCapability::RenameAttributes;
|
1577 |
}
|
1578 |
}
|
1579 |
}
|
1580 |
else
|
1581 |
{
|
1582 |
// Check if the sql is a select query
|
1583 |
if ( !mQuery.startsWith( '(' ) && !mQuery.endsWith( ')' ) )
|
1584 |
{
|
1585 |
QgsMessageLog::logMessage( tr( "The custom query is not a select query." ), tr( "PostGIS" ) ); |
1586 |
return false;
|
1587 |
}
|
1588 |
|
1589 |
// get a new alias for the subquery
|
1590 |
int index = 0;
|
1591 |
QString alias;
|
1592 |
QRegularExpression regex;
|
1593 |
do
|
1594 |
{
|
1595 |
alias = QStringLiteral( "subQuery_%1" ).arg( QString::number( index++ ) ); |
1596 |
QString pattern = QStringLiteral( "(\\\"?)%1\\1" ).arg( QgsStringUtils::qRegExpEscape( alias ) ); |
1597 |
regex.setPattern( pattern ); |
1598 |
regex.setPatternOptions( QRegularExpression::CaseInsensitiveOption ); |
1599 |
} while ( mQuery.contains( regex ) );
|
1600 |
|
1601 |
// convert the custom query into a subquery
|
1602 |
setQuery( QStringLiteral( "%1 AS %2" )
|
1603 |
.arg( mQuery, quotedIdentifier( alias ) ) ); |
1604 |
|
1605 |
QString sql = QStringLiteral( "SELECT * FROM %1 LIMIT 1" ).arg( mQuery );
|
1606 |
|
1607 |
testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
1608 |
if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK )
|
1609 |
{ |
1610 |
QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ).arg( testAccess.PQresultErrorMessage(), sql ), tr( "PostGIS" ) ); |
1611 |
return false; |
1612 |
} |
1613 |
|
1614 |
if ( !mSelectAtIdDisabled )
|
1615 |
{ |
1616 |
mEnabledCapabilities = Qgis::VectorProviderCapability::SelectAtId; |
1617 |
} |
1618 |
} |
1619 |
|
1620 |
// supports geometry simplification on provider side
|
1621 |
mEnabledCapabilities |= ( Qgis::VectorProviderCapability::SimplifyGeometries | Qgis::VectorProviderCapability::SimplifyGeometriesWithTopologicalValidation ); |
1622 |
|
1623 |
//supports transactions
|
1624 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::TransactionSupport; |
1625 |
|
1626 |
// supports circular geometries
|
1627 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::CircularGeometries; |
1628 |
|
1629 |
// supports layer metadata
|
1630 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::ReadLayerMetadata; |
1631 |
|
1632 |
if ( ( mEnabledCapabilities & Qgis::VectorProviderCapability::ChangeGeometries ) && ( mEnabledCapabilities & Qgis::VectorProviderCapability::ChangeAttributeValues ) && mSpatialColType != SctTopoGeometry )
|
1633 |
{ |
1634 |
mEnabledCapabilities |= Qgis::VectorProviderCapability::ChangeFeatures; |
1635 |
} |
1636 |
|
1637 |
return true; |
1638 |
} |
1639 |
|
1640 |
bool QgsPostgresProvider::determinePrimaryKey()
|
1641 |
{ |
1642 |
if ( !loadFields() )
|
1643 |
{ |
1644 |
return false; |
1645 |
} |
1646 |
|
1647 |
// check to see if there is an unique index on the relation, which
|
1648 |
// can be used as a key into the table. Primary keys are always
|
1649 |
// unique indices, so we catch them as well.
|
1650 |
|
1651 |
QString sql; |
1652 |
if ( !mIsQuery )
|
1653 |
{ |
1654 |
sql = QStringLiteral( "SELECT count(*) FROM pg_inherits WHERE inhparent=%1::regclass" ).arg( quotedValue( mQuery ) );
|
1655 |
QgsDebugMsgLevel( QStringLiteral( "Checking whether %1 is a parent table" ).arg( sql ), 2 ); |
1656 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
1657 |
bool isParentTable( res.PQntuples() == 0 || res.PQgetvalue( 0, 0 ).toInt() > 0 ); |
1658 |
|
1659 |
sql = QStringLiteral( "SELECT indexrelid FROM pg_index WHERE indrelid=%1::regclass AND (indisprimary OR indisunique) ORDER BY CASE WHEN indisprimary THEN 1 ELSE 2 END LIMIT 1" ).arg( quotedValue( mQuery ) );
|
1660 |
QgsDebugMsgLevel( QStringLiteral( "Retrieving first primary or unique index: %1" ).arg( sql ), 2 ); |
1661 |
|
1662 |
res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
1663 |
QgsDebugMsgLevel( QStringLiteral( "Got %1 rows." ).arg( res.PQntuples() ), 2 ); |
1664 |
|
1665 |
// no primary or unique indices found
|
1666 |
if ( res.PQntuples() == 0 ) |
1667 |
{ |
1668 |
QgsDebugMsgLevel( QStringLiteral( "Relation has no primary key -- investigating alternatives" ), 2 ); |
1669 |
|
1670 |
// Two options here. If the relation is a table, see if there is
|
1671 |
// an oid column that can be used instead.
|
1672 |
// If the relation is a view try to find a suitable column to use as
|
1673 |
// the primary key.
|
1674 |
|
1675 |
const Qgis::PostgresRelKind type = relkind();
|
1676 |
|
1677 |
if ( type == Qgis::PostgresRelKind::OrdinaryTable || type == Qgis::PostgresRelKind::PartitionedTable )
|
1678 |
{ |
1679 |
QgsDebugMsgLevel( QStringLiteral( "Relation is a table. Checking to see if it has an oid column." ), 2 ); |
1680 |
|
1681 |
mPrimaryKeyAttrs.clear(); |
1682 |
mPrimaryKeyType = PktUnknown; |
1683 |
|
1684 |
if ( connectionRO()->pgVersion() >= 100000 ) |
1685 |
{ |
1686 |
// If there is an generated id on the table, use that instead,
|
1687 |
sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attidentity IN ('a','d') AND attrelid=regclass(%1) LIMIT 1" ).arg( quotedValue( mQuery ) );
|
1688 |
res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
1689 |
if ( res.PQntuples() == 1 ) |
1690 |
{ |
1691 |
// Could warn the user here that performance will suffer if
|
1692 |
// attribute isn't indexed (and that they may want to add a
|
1693 |
// primary key to the table)
|
1694 |
int idx = fieldNameIndex( res.PQgetvalue( 0, 0 ) ); |
1695 |
mPrimaryKeyType = pkType( mAttributeFields.at( idx ) ); |
1696 |
mPrimaryKeyAttrs << idx; |
1697 |
} |
1698 |
} |
1699 |
|
1700 |
if ( mPrimaryKeyType == PktUnknown )
|
1701 |
{ |
1702 |
// If there is an oid on the table, use that instead,
|
1703 |
sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) );
|
1704 |
|
1705 |
res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
1706 |
if ( res.PQntuples() == 1 ) |
1707 |
{ |
1708 |
// Could warn the user here that performance will suffer if
|
1709 |
// oid isn't indexed (and that they may want to add a
|
1710 |
// primary key to the table)
|
1711 |
mPrimaryKeyType = PktOid; |
1712 |
} |
1713 |
} |
1714 |
|
1715 |
if ( mPrimaryKeyType == PktUnknown )
|
1716 |
{ |
1717 |
sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) );
|
1718 |
|
1719 |
res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
1720 |
if ( res.PQntuples() == 1 ) |
1721 |
{ |
1722 |
mPrimaryKeyType = PktTid; |
1723 |
|
1724 |
QgsMessageLog::logMessage( tr( "Primary key is ctid - changing of existing features disabled (%1; %2)" ).arg( mGeometryColumn, mQuery ) );
|
1725 |
mEnabledCapabilities &= ~( Qgis::VectorProviderCapability::DeleteFeatures | Qgis::VectorProviderCapability::ChangeAttributeValues | Qgis::VectorProviderCapability::ChangeGeometries | Qgis::VectorProviderCapability::ChangeFeatures ); |
1726 |
} |
1727 |
} |
1728 |
|
1729 |
if ( mPrimaryKeyType == PktUnknown )
|
1730 |
{ |
1731 |
QgsMessageLog::logMessage( tr( "The table has no column suitable for use as a key. QGIS requires a primary key, a PostgreSQL oid column or a ctid for tables." ), tr( "PostGIS" ) ); |
1732 |
} |
1733 |
} |
1734 |
else if ( type == Qgis::PostgresRelKind::View || type == Qgis::PostgresRelKind::MaterializedView || type == Qgis::PostgresRelKind::ForeignTable ) |
1735 |
{ |
1736 |
determinePrimaryKeyFromUriKeyColumn(); |
1737 |
} |
1738 |
else
|
1739 |
{ |
1740 |
QgsMessageLog::logMessage( tr( "Unexpected relation type '%1'." ).arg( qgsEnumValueToKey( type ) ), tr( "PostGIS" ) ); |
1741 |
} |
1742 |
} |
1743 |
else
|
1744 |
{ |
1745 |
// have a primary key or unique index
|
1746 |
QString indrelid = res.PQgetvalue( 0, 0 ); |
1747 |
sql = QStringLiteral( "SELECT attname,attnotnull FROM pg_index,pg_attribute WHERE indexrelid=%1 AND indrelid=attrelid AND pg_attribute.attnum=any(pg_index.indkey)" ).arg( indrelid );
|
1748 |
|
1749 |
QgsDebugMsgLevel( "Retrieving key columns: " + sql, 2 ); |
1750 |
res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
1751 |
QgsDebugMsgLevel( QStringLiteral( "Got %1 rows." ).arg( res.PQntuples() ), 2 ); |
1752 |
|
1753 |
bool mightBeNull = false; |
1754 |
QString primaryKey; |
1755 |
QString delim; |
1756 |
|
1757 |
mPrimaryKeyType = PktFidMap; // map by default, will downgrade if needed
|
1758 |
for ( int i = 0; i < res.PQntuples(); i++ ) |
1759 |
{ |
1760 |
QString name = res.PQgetvalue( i, 0 );
|
1761 |
if ( res.PQgetvalue( i, 1 ).startsWith( 'f' ) ) |
1762 |
{ |
1763 |
QgsMessageLog::logMessage( tr( "Unique column '%1' doesn't have a NOT NULL constraint." ).arg( name ), tr( "PostGIS" ) ); |
1764 |
mightBeNull = true;
|
1765 |
} |
1766 |
|
1767 |
primaryKey += delim + quotedIdentifier( name ); |
1768 |
delim = ',';
|
1769 |
|
1770 |
int idx = fieldNameIndex( name );
|
1771 |
if ( idx == -1 ) |
1772 |
{ |
1773 |
QgsDebugMsgLevel( "Skipping " + name, 2 ); |
1774 |
continue;
|
1775 |
} |
1776 |
QgsField fld = mAttributeFields.at( idx ); |
1777 |
|
1778 |
// Always use PktFidMap for multi-field keys
|
1779 |
mPrimaryKeyType = i ? PktFidMap : pkType( fld ); |
1780 |
|
1781 |
mPrimaryKeyAttrs << idx; |
1782 |
} |
1783 |
|
1784 |
if ( ( mightBeNull || isParentTable ) && !mUseEstimatedMetadata && !uniqueData( primaryKey ) )
|
1785 |
{ |
1786 |
QgsMessageLog::logMessage( tr( "Ignoring key candidate because of NULL values or inheritance" ), tr( "PostGIS" ) ); |
1787 |
mPrimaryKeyType = PktUnknown; |
1788 |
mPrimaryKeyAttrs.clear(); |
1789 |
} |
1790 |
} |
1791 |
} |
1792 |
else
|
1793 |
{ |
1794 |
determinePrimaryKeyFromUriKeyColumn(); |
1795 |
} |
1796 |
|
1797 |
if ( mPrimaryKeyAttrs.size() == 1 ) |
1798 |
{ |
1799 |
//primary keys are unique, not null
|
1800 |
QgsFieldConstraints constraints = mAttributeFields.at( mPrimaryKeyAttrs[0] ).constraints();
|
1801 |
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider ); |
1802 |
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider ); |
1803 |
mAttributeFields[mPrimaryKeyAttrs[0]].setConstraints( constraints );
|
1804 |
} |
1805 |
|
1806 |
mValid = mPrimaryKeyType != PktUnknown; |
1807 |
|
1808 |
return mValid;
|
1809 |
} |
1810 |
|
1811 |
/* static */
|
1812 |
QStringList QgsPostgresProvider::parseUriKey( const QString &key )
|
1813 |
{ |
1814 |
if ( key.isEmpty() )
|
1815 |
return QStringList();
|
1816 |
|
1817 |
QStringList cols; |
1818 |
|
1819 |
// remove quotes from key list
|
1820 |
if ( key.startsWith( '"' ) && key.endsWith( '"' ) ) |
1821 |
{ |
1822 |
int i = 1; |
1823 |
QString col; |
1824 |
while ( i < key.size() )
|
1825 |
{ |
1826 |
if ( key[i] == '"' ) |
1827 |
{ |
1828 |
if ( i + 1 < key.size() && key[i + 1] == '"' ) |
1829 |
{ |
1830 |
i++; |
1831 |
} |
1832 |
else
|
1833 |
{ |
1834 |
cols << col; |
1835 |
col.clear(); |
1836 |
|
1837 |
if ( ++i == key.size() )
|
1838 |
break;
|
1839 |
|
1840 |
Q_ASSERT( key[i] == ',' );
|
1841 |
i++; |
1842 |
Q_ASSERT( key[i] == '"' );
|
1843 |
i++; |
1844 |
col.clear(); |
1845 |
continue;
|
1846 |
} |
1847 |
} |
1848 |
|
1849 |
col += key[i++]; |
1850 |
} |
1851 |
} |
1852 |
else if ( key.contains( ',' ) ) |
1853 |
{ |
1854 |
cols = key.split( ',' );
|
1855 |
} |
1856 |
else
|
1857 |
{ |
1858 |
cols << key; |
1859 |
} |
1860 |
|
1861 |
return cols;
|
1862 |
} |
1863 |
|
1864 |
void QgsPostgresProvider::determinePrimaryKeyFromUriKeyColumn()
|
1865 |
{ |
1866 |
QString primaryKey = mUri.keyColumn(); |
1867 |
mPrimaryKeyType = PktUnknown; |
1868 |
|
1869 |
if ( !primaryKey.isEmpty() )
|
1870 |
{ |
1871 |
const QStringList cols = parseUriKey( primaryKey );
|
1872 |
|
1873 |
primaryKey.clear(); |
1874 |
QString del; |
1875 |
for ( const QString &col : cols ) |
1876 |
{ |
1877 |
primaryKey += del + quotedIdentifier( col ); |
1878 |
del = QStringLiteral( "," );
|
1879 |
} |
1880 |
|
1881 |
for ( const QString &col : cols ) |
1882 |
{ |
1883 |
int idx = fieldNameIndex( col );
|
1884 |
if ( idx < 0 ) |
1885 |
{ |
1886 |
QgsMessageLog::logMessage( tr( "Key field '%1' for view/query not found." ).arg( col ), tr( "PostGIS" ) ); |
1887 |
mPrimaryKeyAttrs.clear(); |
1888 |
break;
|
1889 |
} |
1890 |
|
1891 |
mPrimaryKeyAttrs << idx; |
1892 |
} |
1893 |
|
1894 |
if ( !mPrimaryKeyAttrs.isEmpty() )
|
1895 |
{ |
1896 |
bool unique = true; |
1897 |
if ( mCheckPrimaryKeyUnicity )
|
1898 |
{ |
1899 |
unique = uniqueData( primaryKey ); |
1900 |
} |
1901 |
|
1902 |
if ( mUseEstimatedMetadata || unique )
|
1903 |
{ |
1904 |
mPrimaryKeyType = PktFidMap; // Map by default
|
1905 |
if ( mPrimaryKeyAttrs.size() == 1 ) |
1906 |
{ |
1907 |
QgsField fld = mAttributeFields.at( mPrimaryKeyAttrs.at( 0 ) );
|
1908 |
mPrimaryKeyType = pkType( fld ); |
1909 |
} |
1910 |
} |
1911 |
else
|
1912 |
{ |
1913 |
QgsMessageLog::logMessage( tr( "Primary key field '%1' for view/query not unique." ).arg( primaryKey ), tr( "PostGIS" ) ); |
1914 |
} |
1915 |
} |
1916 |
else
|
1917 |
{ |
1918 |
QgsMessageLog::logMessage( tr( "Keys for view/query undefined." ), tr( "PostGIS" ) ); |
1919 |
} |
1920 |
} |
1921 |
else
|
1922 |
{ |
1923 |
QgsMessageLog::logMessage( tr( "No key field for view/query given." ), tr( "PostGIS" ) ); |
1924 |
} |
1925 |
} |
1926 |
|
1927 |
bool QgsPostgresProvider::uniqueData( const QString "edColNames ) |
1928 |
{ |
1929 |
// Check to see if the given columns contain unique data
|
1930 |
QString sql = QStringLiteral( "SELECT count(distinct (%1))=count((%1)) FROM %2%3" )
|
1931 |
.arg( quotedColNames, mQuery, filterWhereClause() ); |
1932 |
|
1933 |
QgsPostgresResult unique( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
1934 |
|
1935 |
if ( unique.PQresultStatus() != PGRES_TUPLES_OK )
|
1936 |
{ |
1937 |
pushError( unique.PQresultErrorMessage() ); |
1938 |
return false; |
1939 |
} |
1940 |
return unique.PQntuples() == 1 && unique.PQgetvalue( 0, 0 ).startsWith( 't' ); |
1941 |
} |
1942 |
|
1943 |
// Returns the minimum value of an attribute
|
1944 |
QVariant QgsPostgresProvider::minimumValue( int index ) const |
1945 |
{ |
1946 |
try
|
1947 |
{ |
1948 |
// get the field name
|
1949 |
QgsField fld = field( index ); |
1950 |
QString sql = QStringLiteral( "SELECT min(%1) AS %1 FROM %2" )
|
1951 |
.arg( quotedIdentifier( fld.name() ), mQuery ); |
1952 |
|
1953 |
if ( !mSqlWhereClause.isEmpty() )
|
1954 |
{ |
1955 |
sql += QStringLiteral( " WHERE %1" ).arg( mSqlWhereClause );
|
1956 |
} |
1957 |
|
1958 |
sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql );
|
1959 |
|
1960 |
QgsPostgresResult rmin( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
1961 |
return convertValue( fld.type(), fld.subType(), rmin.PQgetvalue( 0, 0 ), fld.typeName() ); |
1962 |
} |
1963 |
catch ( PGFieldNotFound )
|
1964 |
{ |
1965 |
return QVariant( QString() );
|
1966 |
} |
1967 |
} |
1968 |
|
1969 |
// Returns the list of unique values of an attribute
|
1970 |
QSet<QVariant> QgsPostgresProvider::uniqueValues( int index, int limit ) const |
1971 |
{ |
1972 |
QSet<QVariant> uniqueValues; |
1973 |
|
1974 |
try
|
1975 |
{ |
1976 |
// get the field name
|
1977 |
QgsField fld = field( index ); |
1978 |
QString sql = QStringLiteral( "SELECT DISTINCT %1 FROM %2" )
|
1979 |
.arg( quotedIdentifier( fld.name() ), mQuery ); |
1980 |
|
1981 |
if ( !mSqlWhereClause.isEmpty() )
|
1982 |
{ |
1983 |
sql += QStringLiteral( " WHERE %1" ).arg( mSqlWhereClause );
|
1984 |
} |
1985 |
|
1986 |
sql += QStringLiteral( " ORDER BY %1" ).arg( quotedIdentifier( fld.name() ) );
|
1987 |
|
1988 |
if ( limit >= 0 ) |
1989 |
{ |
1990 |
sql += QStringLiteral( " LIMIT %1" ).arg( limit );
|
1991 |
} |
1992 |
|
1993 |
sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql );
|
1994 |
|
1995 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
1996 |
if ( res.PQresultStatus() == PGRES_TUPLES_OK )
|
1997 |
{ |
1998 |
for ( int i = 0; i < res.PQntuples(); i++ ) |
1999 |
uniqueValues.insert( convertValue( fld.type(), fld.subType(), res.PQgetvalue( i, 0 ), fld.typeName() ) );
|
2000 |
} |
2001 |
} |
2002 |
catch ( PGFieldNotFound )
|
2003 |
{ |
2004 |
} |
2005 |
return uniqueValues;
|
2006 |
} |
2007 |
|
2008 |
QStringList QgsPostgresProvider::uniqueStringsMatching( int index, const QString &substring, int limit, QgsFeedback *feedback ) const |
2009 |
{ |
2010 |
QStringList results; |
2011 |
|
2012 |
try
|
2013 |
{ |
2014 |
// get the field name
|
2015 |
QgsField fld = field( index ); |
2016 |
QString sql = QStringLiteral( "SELECT DISTINCT %1 FROM %2 WHERE" )
|
2017 |
.arg( quotedIdentifier( fld.name() ), mQuery ); |
2018 |
|
2019 |
if ( !mSqlWhereClause.isEmpty() )
|
2020 |
{ |
2021 |
sql += QStringLiteral( " ( %1 ) AND " ).arg( mSqlWhereClause );
|
2022 |
} |
2023 |
|
2024 |
sql += QStringLiteral( " %1::text ILIKE '%%2%'" ).arg( quotedIdentifier( fld.name() ), substring );
|
2025 |
|
2026 |
|
2027 |
sql += QStringLiteral( " ORDER BY %1" ).arg( quotedIdentifier( fld.name() ) );
|
2028 |
|
2029 |
if ( limit >= 0 ) |
2030 |
{ |
2031 |
sql += QStringLiteral( " LIMIT %1" ).arg( limit );
|
2032 |
} |
2033 |
|
2034 |
sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql );
|
2035 |
|
2036 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), sql ) );
|
2037 |
if ( res.PQresultStatus() == PGRES_TUPLES_OK )
|
2038 |
{ |
2039 |
for ( int i = 0; i < res.PQntuples(); i++ ) |
2040 |
{ |
2041 |
results << ( convertValue( fld.type(), fld.subType(), res.PQgetvalue( i, 0 ), fld.typeName() ) ).toString();
|
2042 |
if ( feedback && feedback->isCanceled() )
|
2043 |
break;
|
2044 |
} |
2045 |
} |
2046 |
} |
2047 |
catch ( PGFieldNotFound )
|
2048 |
{ |
2049 |
} |
2050 |
return results;
|
2051 |
} |
2052 |
|
2053 |
void QgsPostgresProvider::enumValues( int index, QStringList &enumList ) const |
2054 |
{ |
2055 |
if ( index < 0 || index >= mAttributeFields.count() ) |
2056 |
return;
|
2057 |
|
2058 |
if ( !mShared->fieldSupportsEnumValuesIsSet( index ) )
|
2059 |
{ |
2060 |
mShared->setFieldSupportsEnumValues( index, true );
|
2061 |
} |
2062 |
else if ( !mShared->fieldSupportsEnumValues( index ) ) |
2063 |
{ |
2064 |
return;
|
2065 |
} |
2066 |
|
2067 |
//find out type of index
|
2068 |
const QString fieldName = mAttributeFields.at( index ).name();
|
2069 |
QString typeName = mAttributeFields.at( index ).typeName(); |
2070 |
|
2071 |
// Remove schema extension from typeName
|
2072 |
typeName.remove( QRegularExpression( "^([^.]+\\.)+" ) );
|
2073 |
|
2074 |
//is type an enum?
|
2075 |
const QString typeSql = QStringLiteral( "SELECT typtype FROM pg_type WHERE typname=%1" ).arg( quotedValue( typeName ) ); |
2076 |
QgsPostgresResult typeRes( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), typeSql ) );
|
2077 |
if ( typeRes.PQresultStatus() != PGRES_TUPLES_OK || typeRes.PQntuples() < 1 ) |
2078 |
{ |
2079 |
mShared->setFieldSupportsEnumValues( index, false );
|
2080 |
return;
|
2081 |
} |
2082 |
|
2083 |
const QString typtype = typeRes.PQgetvalue( 0, 0 ); |
2084 |
if ( typtype.compare( QLatin1String( "e" ), Qt::CaseInsensitive ) == 0 ) |
2085 |
{ |
2086 |
//try to read enum_range of attribute
|
2087 |
if ( !parseEnumRange( enumList, fieldName ) )
|
2088 |
{ |
2089 |
mShared->setFieldSupportsEnumValues( index, false );
|
2090 |
} |
2091 |
} |
2092 |
else
|
2093 |
{ |
2094 |
//is there a domain check constraint for the attribute?
|
2095 |
if ( !parseDomainCheckConstraint( enumList, fieldName ) )
|
2096 |
{ |
2097 |
mShared->setFieldSupportsEnumValues( index, false );
|
2098 |
} |
2099 |
} |
2100 |
} |
2101 |
|
2102 |
bool QgsPostgresProvider::parseEnumRange( QStringList &enumValues, const QString &attributeName ) const |
2103 |
{ |
2104 |
enumValues.clear(); |
2105 |
|
2106 |
QString enumRangeSql = QStringLiteral( "SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid=(SELECT atttypid::regclass FROM pg_attribute WHERE attrelid=%1::regclass AND attname=%2)" )
|
2107 |
.arg( quotedValue( mQuery ), quotedValue( attributeName ) ); |
2108 |
QgsPostgresResult enumRangeRes( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), enumRangeSql ) );
|
2109 |
if ( enumRangeRes.PQresultStatus() != PGRES_TUPLES_OK )
|
2110 |
return false; |
2111 |
|
2112 |
for ( int i = 0; i < enumRangeRes.PQntuples(); i++ ) |
2113 |
{ |
2114 |
enumValues << enumRangeRes.PQgetvalue( i, 0 );
|
2115 |
} |
2116 |
|
2117 |
return true; |
2118 |
} |
2119 |
|
2120 |
bool QgsPostgresProvider::parseDomainCheckConstraint( QStringList &enumValues, const QString &attributeName ) const |
2121 |
{ |
2122 |
enumValues.clear(); |
2123 |
|
2124 |
//is it a domain type with a check constraint?
|
2125 |
QString domainSql = QStringLiteral( "SELECT domain_name, domain_schema FROM information_schema.columns WHERE table_name=%1 AND column_name=%2" ).arg( quotedValue( mTableName ), quotedValue( attributeName ) );
|
2126 |
QgsPostgresResult domainResult( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), domainSql ) );
|
2127 |
if ( domainResult.PQresultStatus() == PGRES_TUPLES_OK && domainResult.PQntuples() > 0 && !domainResult.PQgetvalue( 0, 0 ).isNull() ) |
2128 |
{ |
2129 |
QString domainCheckDefinitionSql; |
2130 |
if ( connectionRO()->pgVersion() < 120000 ) |
2131 |
{ |
2132 |
domainCheckDefinitionSql = QStringLiteral( ""
|
2133 |
"SELECT consrc FROM pg_constraint "
|
2134 |
" WHERE contypid =("
|
2135 |
" SELECT oid FROM pg_type "
|
2136 |
" WHERE typname = %1 "
|
2137 |
" AND typnamespace =("
|
2138 |
" SELECT oid FROM pg_namespace WHERE nspname = %2"
|
2139 |
" )"
|
2140 |
" )" )
|
2141 |
.arg( quotedValue( domainResult.PQgetvalue( 0, 0 ) ) ) |
2142 |
.arg( quotedValue( domainResult.PQgetvalue( 0, 1 ) ) ); |
2143 |
} |
2144 |
else
|
2145 |
{ |
2146 |
domainCheckDefinitionSql = QStringLiteral( ""
|
2147 |
"SELECT pg_catalog.pg_get_constraintdef( ( "
|
2148 |
" SELECT oid FROM pg_constraint WHERE contypid = ( "
|
2149 |
" SELECT oid FROM pg_type "
|
2150 |
" WHERE typname = %1 "
|
2151 |
" AND typnamespace =("
|
2152 |
" SELECT oid FROM pg_namespace WHERE nspname = %2"
|
2153 |
" )"
|
2154 |
" )"
|
2155 |
" ), true );" )
|
2156 |
.arg( quotedValue( domainResult.PQgetvalue( 0, 0 ) ) ) |
2157 |
.arg( quotedValue( domainResult.PQgetvalue( 0, 1 ) ) ); |
2158 |
} |
2159 |
|
2160 |
QgsPostgresResult domainCheckRes( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), domainCheckDefinitionSql ) );
|
2161 |
if ( domainCheckRes.PQresultStatus() == PGRES_TUPLES_OK && domainCheckRes.PQntuples() > 0 ) |
2162 |
{ |
2163 |
QString checkDefinition = domainCheckRes.PQgetvalue( 0, 0 ); |
2164 |
|
2165 |
//we assume that the constraint is of the following form:
|
2166 |
//(VALUE = ANY (ARRAY['a'::text, 'b'::text, 'c'::text, 'd'::text]))
|
2167 |
//normally, PostgreSQL creates that if the constraint has been specified as 'VALUE in ('a', 'b', 'c', 'd')
|
2168 |
const thread_local QRegularExpression definitionRegExp( "VALUE\\s*=\\s*ANY\\s*\\(\\s*ARRAY\\s*\\[" ); |
2169 |
int anyPos = checkDefinition.indexOf( definitionRegExp );
|
2170 |
int arrayPosition = checkDefinition.lastIndexOf( QLatin1String( "ARRAY[" ) ); |
2171 |
int closingBracketPos = checkDefinition.indexOf( ']', arrayPosition + 6 ); |
2172 |
|
2173 |
if ( anyPos == -1 || anyPos >= arrayPosition ) |
2174 |
{ |
2175 |
return false; //constraint has not the required format |
2176 |
} |
2177 |
|
2178 |
if ( arrayPosition != -1 ) |
2179 |
{ |
2180 |
QString valueList = checkDefinition.mid( arrayPosition + 6, closingBracketPos );
|
2181 |
const QStringList commaSeparation = valueList.split( ',', Qt::SkipEmptyParts ); |
2182 |
QStringList::const_iterator cIt = commaSeparation.constBegin(); |
2183 |
for ( ; cIt != commaSeparation.constEnd(); ++cIt )
|
2184 |
{ |
2185 |
//get string between ''
|
2186 |
int beginQuotePos = cIt->indexOf( '\'' ); |
2187 |
int endQuotePos = cIt->lastIndexOf( '\'' ); |
2188 |
if ( beginQuotePos != -1 && ( endQuotePos - beginQuotePos ) > 1 ) |
2189 |
{ |
2190 |
enumValues << cIt->mid( beginQuotePos + 1, endQuotePos - beginQuotePos - 1 ); |
2191 |
} |
2192 |
} |
2193 |
} |
2194 |
return true; |
2195 |
} |
2196 |
} |
2197 |
return false; |
2198 |
} |
2199 |
|
2200 |
// Returns the maximum value of an attribute
|
2201 |
QVariant QgsPostgresProvider::maximumValue( int index ) const |
2202 |
{ |
2203 |
try
|
2204 |
{ |
2205 |
// get the field name
|
2206 |
QgsField fld = field( index ); |
2207 |
QString sql = QStringLiteral( "SELECT max(%1) AS %1 FROM %2" )
|
2208 |
.arg( quotedIdentifier( fld.name() ), mQuery ); |
2209 |
|
2210 |
if ( !mSqlWhereClause.isEmpty() )
|
2211 |
{ |
2212 |
sql += QStringLiteral( " WHERE %1" ).arg( mSqlWhereClause );
|
2213 |
} |
2214 |
|
2215 |
sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql );
|
2216 |
|
2217 |
QgsPostgresResult rmax( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
2218 |
|
2219 |
return convertValue( fld.type(), fld.subType(), rmax.PQgetvalue( 0, 0 ), fld.typeName() ); |
2220 |
} |
2221 |
catch ( PGFieldNotFound )
|
2222 |
{ |
2223 |
return QVariant( QString() );
|
2224 |
} |
2225 |
} |
2226 |
|
2227 |
|
2228 |
bool QgsPostgresProvider::isValid() const |
2229 |
{ |
2230 |
return mValid;
|
2231 |
} |
2232 |
|
2233 |
Qgis::ProviderStyleStorageCapabilities QgsPostgresProvider::styleStorageCapabilities() const
|
2234 |
{ |
2235 |
Qgis::ProviderStyleStorageCapabilities storageCapabilities; |
2236 |
if ( isValid() )
|
2237 |
{ |
2238 |
storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; |
2239 |
storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; |
2240 |
storageCapabilities |= Qgis::ProviderStyleStorageCapability::DeleteFromDatabase; |
2241 |
} |
2242 |
return storageCapabilities;
|
2243 |
} |
2244 |
|
2245 |
QString QgsPostgresProvider::defaultValueClause( int fieldId ) const |
2246 |
{ |
2247 |
QString defVal = mDefaultValues.value( fieldId, QString() ); |
2248 |
|
2249 |
// with generated columns (PostgreSQL 12+), the provider will ALWAYS evaluate the default values.
|
2250 |
// The only acceptable value for such columns on INSERT or UPDATE clauses is the keyword "DEFAULT".
|
2251 |
// Here, we return the expression used to generate the field value, so the
|
2252 |
// user can see what is happening when inserting a new feature.
|
2253 |
// On inserting a new feature or updating a generated field, this is
|
2254 |
// omitted from the generated queries.
|
2255 |
// See https://www.postgresql.org/docs/12/ddl-generated-columns.html
|
2256 |
if ( mGeneratedValues.contains( fieldId ) )
|
2257 |
{ |
2258 |
return defVal;
|
2259 |
} |
2260 |
|
2261 |
if ( !providerProperty( EvaluateDefaultValues, false ).toBool() && !defVal.isEmpty() ) |
2262 |
{ |
2263 |
return defVal;
|
2264 |
} |
2265 |
|
2266 |
return QString();
|
2267 |
} |
2268 |
|
2269 |
QVariant QgsPostgresProvider::defaultValue( int fieldId ) const |
2270 |
{ |
2271 |
QString defVal = mDefaultValues.value( fieldId, QString() ); |
2272 |
|
2273 |
if ( providerProperty( EvaluateDefaultValues, false ).toBool() && !defVal.isEmpty() ) |
2274 |
{ |
2275 |
QgsField fld = field( fieldId ); |
2276 |
|
2277 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT %1" ).arg( defVal ) ) ); |
2278 |
|
2279 |
if ( res.result() )
|
2280 |
{ |
2281 |
return convertValue( fld.type(), fld.subType(), res.PQgetvalue( 0, 0 ), fld.typeName() ); |
2282 |
} |
2283 |
else
|
2284 |
{ |
2285 |
pushError( tr( "Could not execute query" ) );
|
2286 |
return QVariant();
|
2287 |
} |
2288 |
} |
2289 |
|
2290 |
return QVariant();
|
2291 |
} |
2292 |
|
2293 |
bool QgsPostgresProvider::skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint, const QVariant &value ) const |
2294 |
{ |
2295 |
if ( providerProperty( EvaluateDefaultValues, false ).toBool() ) |
2296 |
{ |
2297 |
return !mDefaultValues.value( fieldIndex ).isEmpty();
|
2298 |
} |
2299 |
else
|
2300 |
{ |
2301 |
// stricter check - if we are evaluating default values only on commit then we can only bypass the check
|
2302 |
// if the attribute values matches the original default clause
|
2303 |
return mDefaultValues.contains( fieldIndex ) && mDefaultValues.value( fieldIndex ) == value.toString() && !QgsVariantUtils::isNull( value );
|
2304 |
} |
2305 |
} |
2306 |
|
2307 |
QString QgsPostgresProvider::paramValue( const QString &fieldValue, const QString &defaultValue ) const |
2308 |
{ |
2309 |
if ( fieldValue.isNull() )
|
2310 |
return QString();
|
2311 |
|
2312 |
if ( fieldValue == defaultValue && !defaultValue.isNull() )
|
2313 |
{ |
2314 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT %1" ).arg( defaultValue ) ) ); |
2315 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
2316 |
throw PGException( result );
|
2317 |
|
2318 |
return result.PQgetvalue( 0, 0 ); |
2319 |
} |
2320 |
|
2321 |
return fieldValue;
|
2322 |
} |
2323 |
|
2324 |
|
2325 |
/* private */
|
2326 |
bool QgsPostgresProvider::getTopoLayerInfo()
|
2327 |
{ |
2328 |
QString sql = QStringLiteral( R"SQL(
|
2329 |
SELECT
|
2330 |
t.name,
|
2331 |
l.layer_id,
|
2332 |
l.level,
|
2333 |
l.feature_type
|
2334 |
FROM topology.layer l
|
2335 |
JOIN topology.topology t ON (
|
2336 |
l.topology_id = t.id
|
2337 |
)
|
2338 |
WHERE l.schema_name=%1
|
2339 |
AND l.table_name=%2 AND l.feature_column=%3
|
2340 |
)SQL" )
|
2341 |
.arg( quotedValue( mSchemaName ), quotedValue( mTableName ), quotedValue( mGeometryColumn ) ); |
2342 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
2343 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
2344 |
{ |
2345 |
throw PGException( result ); // we should probably not do this |
2346 |
} |
2347 |
if ( result.PQntuples() < 1 ) |
2348 |
{ |
2349 |
QgsMessageLog::logMessage( tr( "Could not find topology of layer %1.%2.%3" ).arg( quotedValue( mSchemaName ), quotedValue( mTableName ), quotedValue( mGeometryColumn ) ), tr( "PostGIS" ) ); |
2350 |
return false; |
2351 |
} |
2352 |
mTopoLayerInfo.topologyName = result.PQgetvalue( 0, 0 ); |
2353 |
mTopoLayerInfo.layerId = result.PQgetvalue( 0, 1 ).toLong(); |
2354 |
mTopoLayerInfo.layerLevel = result.PQgetvalue( 0, 2 ).toInt(); |
2355 |
switch ( result.PQgetvalue( 0, 3 ).toInt() ) |
2356 |
{ |
2357 |
case 1: |
2358 |
mTopoLayerInfo.featureType = TopoLayerInfo::Puntal; |
2359 |
break;
|
2360 |
case 2: |
2361 |
mTopoLayerInfo.featureType = TopoLayerInfo::Lineal; |
2362 |
break;
|
2363 |
case 3: |
2364 |
mTopoLayerInfo.featureType = TopoLayerInfo::Polygonal; |
2365 |
break;
|
2366 |
case 4: |
2367 |
default:
|
2368 |
mTopoLayerInfo.featureType = TopoLayerInfo::Mixed; |
2369 |
break;
|
2370 |
} |
2371 |
return true; |
2372 |
} |
2373 |
|
2374 |
/* private */
|
2375 |
void QgsPostgresProvider::dropOrphanedTopoGeoms()
|
2376 |
{ |
2377 |
QString sql = QString( "DELETE FROM %1.relation WHERE layer_id = %2 AND "
|
2378 |
"topogeo_id NOT IN ( SELECT id(%3) FROM %4.%5 )" )
|
2379 |
.arg( quotedIdentifier( mTopoLayerInfo.topologyName ) ) |
2380 |
.arg( mTopoLayerInfo.layerId ) |
2381 |
.arg( quotedIdentifier( mGeometryColumn ), quotedIdentifier( mSchemaName ), quotedIdentifier( mTableName ) ); |
2382 |
|
2383 |
QgsDebugMsgLevel( "TopoGeom orphans cleanup query: " + sql, 2 ); |
2384 |
|
2385 |
connectionRW()->LoggedPQexecNR( "QgsPostgresProvider", sql );
|
2386 |
} |
2387 |
|
2388 |
QString QgsPostgresProvider::geomParam( int offset ) const |
2389 |
{ |
2390 |
QString geometry; |
2391 |
|
2392 |
bool forceMulti = false; |
2393 |
|
2394 |
if ( mSpatialColType != SctTopoGeometry )
|
2395 |
{ |
2396 |
forceMulti = QgsWkbTypes::isMultiType( wkbType() ); |
2397 |
} |
2398 |
|
2399 |
if ( mSpatialColType == SctTopoGeometry )
|
2400 |
{ |
2401 |
geometry += QLatin1String( "toTopoGeom(" );
|
2402 |
} |
2403 |
|
2404 |
if ( forceMulti )
|
2405 |
{ |
2406 |
geometry += connectionRO()->majorVersion() < 2 ? "multi(" : "st_multi("; |
2407 |
} |
2408 |
|
2409 |
geometry += QStringLiteral( "%1($%2%3,%4)" )
|
2410 |
.arg( connectionRO()->majorVersion() < 2 ? "geomfromwkb" : "st_geomfromwkb" ) |
2411 |
.arg( offset ) |
2412 |
.arg( connectionRO()->useWkbHex() ? "" : "::bytea", mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid ); |
2413 |
|
2414 |
if ( forceMulti )
|
2415 |
{ |
2416 |
geometry += ')';
|
2417 |
} |
2418 |
|
2419 |
if ( mSpatialColType == SctTopoGeometry )
|
2420 |
{ |
2421 |
geometry += QStringLiteral( ",%1,%2)" )
|
2422 |
.arg( quotedValue( mTopoLayerInfo.topologyName ) ) |
2423 |
.arg( mTopoLayerInfo.layerId ); |
2424 |
} |
2425 |
|
2426 |
return geometry;
|
2427 |
} |
2428 |
|
2429 |
bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags )
|
2430 |
{ |
2431 |
if ( flist.isEmpty() )
|
2432 |
return true; |
2433 |
|
2434 |
if ( mIsQuery )
|
2435 |
return false; |
2436 |
|
2437 |
QgsPostgresConn *conn = connectionRW(); |
2438 |
if ( !conn )
|
2439 |
{ |
2440 |
return false; |
2441 |
} |
2442 |
conn->lock(); |
2443 |
|
2444 |
bool returnvalue = true; |
2445 |
|
2446 |
try
|
2447 |
{ |
2448 |
conn->begin(); |
2449 |
|
2450 |
// Prepare the INSERT statement
|
2451 |
QString insert = QStringLiteral( "INSERT INTO %1(" ).arg( mQuery );
|
2452 |
QString values; |
2453 |
QString delim; |
2454 |
int offset = 1; |
2455 |
|
2456 |
QStringList defaultValues; |
2457 |
QList<int> fieldId;
|
2458 |
|
2459 |
if ( !mGeometryColumn.isNull() )
|
2460 |
{ |
2461 |
insert += quotedIdentifier( mGeometryColumn ); |
2462 |
|
2463 |
values += geomParam( offset++ ); |
2464 |
|
2465 |
delim = ',';
|
2466 |
} |
2467 |
|
2468 |
// Optimization: if we have a single primary key column whose default value
|
2469 |
// is a sequence, and that none of the features have a value set for that
|
2470 |
// column, then we can completely omit inserting it.
|
2471 |
bool skipSinglePKField = false; |
2472 |
bool overrideIdentity = false; |
2473 |
|
2474 |
if ( ( mPrimaryKeyType == PktInt || mPrimaryKeyType == PktInt64 || mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktUint64 ) )
|
2475 |
{ |
2476 |
if ( mPrimaryKeyAttrs.size() == 1 && defaultValueClause( mPrimaryKeyAttrs[0] ).startsWith( "nextval(" ) ) |
2477 |
{ |
2478 |
bool foundNonEmptyPK = false; |
2479 |
int idx = mPrimaryKeyAttrs[0]; |
2480 |
QString defaultValue = defaultValueClause( idx ); |
2481 |
for ( int i = 0; i < flist.size(); i++ ) |
2482 |
{ |
2483 |
QgsAttributes attrs2 = flist[i].attributes(); |
2484 |
QVariant v2 = attrs2.value( idx, QgsVariantUtils::createNullVariant( QMetaType::Type::Int ) ); |
2485 |
// a PK field with a sequence val is auto populate by QGIS with this default
|
2486 |
// we are only interested in non default values
|
2487 |
if ( !QgsVariantUtils::isNull( v2 ) && v2.toString() != defaultValue )
|
2488 |
{ |
2489 |
foundNonEmptyPK = true;
|
2490 |
break;
|
2491 |
} |
2492 |
} |
2493 |
skipSinglePKField = !foundNonEmptyPK; |
2494 |
} |
2495 |
|
2496 |
if ( !skipSinglePKField )
|
2497 |
{ |
2498 |
for ( int idx : mPrimaryKeyAttrs ) |
2499 |
{ |
2500 |
if ( mIdentityFields[idx] == 'a' ) |
2501 |
overrideIdentity = true;
|
2502 |
insert += delim + quotedIdentifier( field( idx ).name() ); |
2503 |
values += delim + QStringLiteral( "$%1" ).arg( defaultValues.size() + offset );
|
2504 |
delim = ',';
|
2505 |
fieldId << idx; |
2506 |
defaultValues << defaultValueClause( idx ); |
2507 |
} |
2508 |
} |
2509 |
} |
2510 |
|
2511 |
QgsAttributes attributevec = flist[0].attributes();
|
2512 |
|
2513 |
// look for unique attribute values to place in statement instead of passing as parameter
|
2514 |
// e.g. for defaults
|
2515 |
for ( int idx = 0; idx < attributevec.count(); ++idx ) |
2516 |
{ |
2517 |
QVariant v = attributevec.value( idx, QgsVariantUtils::createNullVariant( QMetaType::Type::Int ) ); // default to NULL for missing attributes
|
2518 |
if ( skipSinglePKField && idx == mPrimaryKeyAttrs[0] ) |
2519 |
continue;
|
2520 |
if ( fieldId.contains( idx ) )
|
2521 |
continue;
|
2522 |
|
2523 |
if ( idx >= mAttributeFields.count() )
|
2524 |
continue;
|
2525 |
|
2526 |
QString fieldname = mAttributeFields.at( idx ).name(); |
2527 |
|
2528 |
if ( !mGeneratedValues.value( idx, QString() ).isEmpty() )
|
2529 |
{ |
2530 |
QgsDebugMsgLevel( QStringLiteral( "Skipping field %1 (idx %2) which is GENERATED." ).arg( fieldname, QString::number( idx ) ), 2 ); |
2531 |
continue;
|
2532 |
} |
2533 |
|
2534 |
QString fieldTypeName = mAttributeFields.at( idx ).typeName(); |
2535 |
|
2536 |
QgsDebugMsgLevel( "Checking field against: " + fieldname, 2 ); |
2537 |
|
2538 |
if ( fieldname.isEmpty() || fieldname == mGeometryColumn )
|
2539 |
continue;
|
2540 |
|
2541 |
int i;
|
2542 |
for ( i = 1; i < flist.size(); i++ ) |
2543 |
{ |
2544 |
QgsAttributes attrs2 = flist[i].attributes(); |
2545 |
QVariant v2 = attrs2.value( idx, QgsVariantUtils::createNullVariant( QMetaType::Type::Int ) ); // default to NULL for missing attributes
|
2546 |
|
2547 |
if ( v2 != v )
|
2548 |
break;
|
2549 |
} |
2550 |
|
2551 |
insert += delim + quotedIdentifier( fieldname ); |
2552 |
|
2553 |
if ( mIdentityFields[idx] == 'a' ) |
2554 |
overrideIdentity = true;
|
2555 |
|
2556 |
QString defVal = defaultValueClause( idx ); |
2557 |
|
2558 |
if ( i == flist.size() )
|
2559 |
{ |
2560 |
if ( qgsVariantEqual( v, defVal ) )
|
2561 |
{ |
2562 |
if ( defVal.isNull() )
|
2563 |
{ |
2564 |
values += delim + "NULL";
|
2565 |
} |
2566 |
else
|
2567 |
{ |
2568 |
values += delim + defVal; |
2569 |
} |
2570 |
} |
2571 |
else if ( fieldTypeName == QLatin1String( "geometry" ) ) |
2572 |
{ |
2573 |
QString val = geomAttrToString( v, connectionRO() ); |
2574 |
values += QStringLiteral( "%1%2(%3)" )
|
2575 |
.arg( delim, connectionRO()->majorVersion() < 2 ? "geomfromewkt" : "st_geomfromewkt", quotedValue( val ) ); |
2576 |
} |
2577 |
else if ( fieldTypeName == QLatin1String( "geography" ) ) |
2578 |
{ |
2579 |
values += QStringLiteral( "%1st_geographyfromtext(%2)" )
|
2580 |
.arg( delim, quotedValue( v.toString() ) ); |
2581 |
} |
2582 |
else if ( fieldTypeName == QLatin1String( "jsonb" ) ) |
2583 |
{ |
2584 |
values += delim + quotedJsonValue( v ) + QStringLiteral( "::jsonb" );
|
2585 |
} |
2586 |
else if ( fieldTypeName == QLatin1String( "json" ) ) |
2587 |
{ |
2588 |
values += delim + quotedJsonValue( v ) + QStringLiteral( "::json" );
|
2589 |
} |
2590 |
else if ( fieldTypeName == QLatin1String( "bytea" ) ) |
2591 |
{ |
2592 |
values += delim + quotedByteaValue( v ); |
2593 |
} |
2594 |
//TODO: convert arrays and hstore to native types
|
2595 |
else
|
2596 |
{ |
2597 |
values += delim + quotedValue( v ); |
2598 |
} |
2599 |
} |
2600 |
else
|
2601 |
{ |
2602 |
// value is not unique => add parameter
|
2603 |
if ( fieldTypeName == QLatin1String( "geometry" ) ) |
2604 |
{ |
2605 |
values += QStringLiteral( "%1%2($%3)" )
|
2606 |
.arg( delim, connectionRO()->majorVersion() < 2 ? "geomfromewkt" : "st_geomfromewkt" ) |
2607 |
.arg( defaultValues.size() + offset ); |
2608 |
} |
2609 |
else if ( fieldTypeName == QLatin1String( "geography" ) ) |
2610 |
{ |
2611 |
values += QStringLiteral( "%1st_geographyfromtext($%2)" )
|
2612 |
.arg( delim ) |
2613 |
.arg( defaultValues.size() + offset ); |
2614 |
} |
2615 |
else
|
2616 |
{ |
2617 |
values += QStringLiteral( "%1$%2" )
|
2618 |
.arg( delim ) |
2619 |
.arg( defaultValues.size() + offset ); |
2620 |
} |
2621 |
defaultValues.append( defVal ); |
2622 |
fieldId.append( idx ); |
2623 |
} |
2624 |
|
2625 |
delim = ',';
|
2626 |
} |
2627 |
|
2628 |
insert += QStringLiteral( ") %1VALUES (%2)" ).arg( overrideIdentity ? "OVERRIDING SYSTEM VALUE " : "" ).arg( values ); |
2629 |
|
2630 |
if ( !( flags & QgsFeatureSink::FastInsert ) )
|
2631 |
{ |
2632 |
if ( mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktInt || mPrimaryKeyType == PktInt64 || mPrimaryKeyType == PktUint64 )
|
2633 |
{ |
2634 |
insert += QLatin1String( " RETURNING " );
|
2635 |
|
2636 |
QString delim; |
2637 |
const auto constMPrimaryKeyAttrs = mPrimaryKeyAttrs; |
2638 |
for ( int idx : constMPrimaryKeyAttrs ) |
2639 |
{ |
2640 |
insert += delim + quotedIdentifier( mAttributeFields.at( idx ).name() ); |
2641 |
delim = ',';
|
2642 |
} |
2643 |
} |
2644 |
} |
2645 |
|
2646 |
QgsDebugMsgLevel( QStringLiteral( "prepare addfeatures: %1" ).arg( insert ), 2 ); |
2647 |
QgsPostgresResult stmt( conn->PQprepare( QStringLiteral( "addfeatures" ), insert, fieldId.size() + offset - 1, nullptr, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ) ); |
2648 |
|
2649 |
if ( stmt.PQresultStatus() != PGRES_COMMAND_OK )
|
2650 |
throw PGException( stmt );
|
2651 |
|
2652 |
for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); ++features )
|
2653 |
{ |
2654 |
QgsAttributes attrs = features->attributes(); |
2655 |
|
2656 |
QStringList params; |
2657 |
if ( !mGeometryColumn.isNull() )
|
2658 |
{ |
2659 |
appendGeomParam( features->geometry(), params ); |
2660 |
} |
2661 |
|
2662 |
params.reserve( fieldId.size() ); |
2663 |
for ( int i = 0; i < fieldId.size(); i++ ) |
2664 |
{ |
2665 |
int attrIdx = fieldId[i];
|
2666 |
QVariant value = attrIdx < attrs.length() ? attrs.at( attrIdx ) : QgsVariantUtils::createNullVariant( QMetaType::Type::Int ); |
2667 |
|
2668 |
QString v; |
2669 |
if ( QgsVariantUtils::isNull( value ) )
|
2670 |
{ |
2671 |
QgsField fld = field( attrIdx ); |
2672 |
v = paramValue( defaultValues[i], defaultValues[i] ); |
2673 |
features->setAttribute( attrIdx, convertValue( fld.type(), fld.subType(), v, fld.typeName() ) ); |
2674 |
} |
2675 |
else
|
2676 |
{ |
2677 |
// the conversion functions expects the list as a string, so convert it
|
2678 |
if ( value.userType() == QMetaType::Type::QStringList )
|
2679 |
{ |
2680 |
QStringList list_vals = value.toStringList(); |
2681 |
// all strings need to be double quoted to allow special postgres
|
2682 |
// array characters such as {, or whitespace in the string
|
2683 |
// but we need to escape all double quotes and backslashes
|
2684 |
list_vals.replaceInStrings( "\\", "\\\\" ); |
2685 |
list_vals.replaceInStrings( "\"", "\\\"" ); |
2686 |
v = QStringLiteral( "{\"" ) + value.toStringList().join( QLatin1String( "\",\"" ) ) + QStringLiteral( "\"}" ); |
2687 |
} |
2688 |
else if ( value.userType() == QMetaType::Type::QVariantList ) |
2689 |
{ |
2690 |
v = "{" + value.toStringList().join( "," ) + "}"; |
2691 |
} |
2692 |
else
|
2693 |
{ |
2694 |
v = paramValue( value.toString(), defaultValues[i] ); |
2695 |
} |
2696 |
|
2697 |
if ( v != value.toString() )
|
2698 |
{ |
2699 |
QgsField fld = field( attrIdx ); |
2700 |
features->setAttribute( attrIdx, convertValue( fld.type(), fld.subType(), v, fld.typeName() ) ); |
2701 |
} |
2702 |
} |
2703 |
|
2704 |
params << v; |
2705 |
} |
2706 |
|
2707 |
QgsPostgresResult result( conn->PQexecPrepared( QStringLiteral( "addfeatures" ), params, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ) ); |
2708 |
|
2709 |
if ( !( flags & QgsFeatureSink::FastInsert ) && result.PQresultStatus() == PGRES_TUPLES_OK )
|
2710 |
{ |
2711 |
for ( int i = 0; i < mPrimaryKeyAttrs.size(); ++i ) |
2712 |
{ |
2713 |
const int idx = mPrimaryKeyAttrs.at( i ); |
2714 |
const QgsField fld = mAttributeFields.at( idx );
|
2715 |
features->setAttribute( idx, convertValue( fld.type(), fld.subType(), result.PQgetvalue( 0, i ), fld.typeName() ) );
|
2716 |
} |
2717 |
} |
2718 |
else if ( result.PQresultStatus() != PGRES_COMMAND_OK ) |
2719 |
throw PGException( result );
|
2720 |
|
2721 |
if ( !( flags & QgsFeatureSink::FastInsert ) && mPrimaryKeyType == PktOid )
|
2722 |
{ |
2723 |
features->setId( result.PQoidValue() ); |
2724 |
QgsDebugMsgLevel( QStringLiteral( "new fid=%1" ).arg( features->id() ), 4 ); |
2725 |
} |
2726 |
} |
2727 |
|
2728 |
if ( !( flags & QgsFeatureSink::FastInsert ) )
|
2729 |
{ |
2730 |
// update feature ids
|
2731 |
if ( mPrimaryKeyType == PktInt || mPrimaryKeyType == PktInt64 || mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktUint64 )
|
2732 |
{ |
2733 |
for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); ++features )
|
2734 |
{ |
2735 |
QgsAttributes attrs = features->attributes(); |
2736 |
|
2737 |
if ( mPrimaryKeyType == PktInt )
|
2738 |
{ |
2739 |
features->setId( PKINT2FID( STRING_TO_FID( attrs.at( mPrimaryKeyAttrs.at( 0 ) ) ) ) );
|
2740 |
} |
2741 |
else
|
2742 |
{ |
2743 |
QVariantList primaryKeyVals; |
2744 |
|
2745 |
const auto constMPrimaryKeyAttrs = mPrimaryKeyAttrs; |
2746 |
for ( int idx : constMPrimaryKeyAttrs ) |
2747 |
{ |
2748 |
primaryKeyVals << attrs.at( idx ); |
2749 |
} |
2750 |
|
2751 |
features->setId( mShared->lookupFid( primaryKeyVals ) ); |
2752 |
} |
2753 |
QgsDebugMsgLevel( QStringLiteral( "new fid=%1" ).arg( features->id() ), 4 ); |
2754 |
} |
2755 |
} |
2756 |
} |
2757 |
|
2758 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE addfeatures" ) ); |
2759 |
|
2760 |
returnvalue &= conn->commit(); |
2761 |
if ( mTransaction )
|
2762 |
mTransaction->dirtyLastSavePoint(); |
2763 |
|
2764 |
mShared->addFeaturesCounted( flist.size() ); |
2765 |
} |
2766 |
catch ( PGException &e )
|
2767 |
{ |
2768 |
pushError( tr( "PostGIS error while adding features: %1" ).arg( e.errorMessage() ) );
|
2769 |
conn->rollback(); |
2770 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE addfeatures" ) ); |
2771 |
returnvalue = false;
|
2772 |
} |
2773 |
|
2774 |
conn->unlock(); |
2775 |
return returnvalue;
|
2776 |
} |
2777 |
|
2778 |
bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds &ids ) |
2779 |
{ |
2780 |
if ( ids.isEmpty() )
|
2781 |
return true; |
2782 |
|
2783 |
bool returnvalue = true; |
2784 |
|
2785 |
if ( mIsQuery )
|
2786 |
{ |
2787 |
QgsDebugError( QStringLiteral( "Cannot delete features (is a query)" ) );
|
2788 |
return false; |
2789 |
} |
2790 |
|
2791 |
QgsPostgresConn *conn = connectionRW(); |
2792 |
if ( !conn )
|
2793 |
{ |
2794 |
return false; |
2795 |
} |
2796 |
conn->lock(); |
2797 |
|
2798 |
try
|
2799 |
{ |
2800 |
conn->begin(); |
2801 |
|
2802 |
QgsFeatureIds chunkIds; |
2803 |
const int countIds = ids.size(); |
2804 |
int i = 0; |
2805 |
for ( QgsFeatureIds::const_iterator it = ids.constBegin(); it != ids.constEnd(); ++it )
|
2806 |
{ |
2807 |
// create chunks of fids to delete, the last chunk may be smaller
|
2808 |
chunkIds.insert( *it ); |
2809 |
i++; |
2810 |
if ( chunkIds.size() < 5000 && i < countIds ) |
2811 |
continue;
|
2812 |
|
2813 |
const QString sql = QStringLiteral( "DELETE FROM %1 WHERE %2" ) |
2814 |
.arg( mQuery, whereClause( chunkIds ) ); |
2815 |
QgsDebugMsgLevel( "delete sql: " + sql, 2 ); |
2816 |
|
2817 |
//send DELETE statement and do error handling
|
2818 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
2819 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
2820 |
throw PGException( result );
|
2821 |
|
2822 |
for ( QgsFeatureIds::const_iterator chunkIt = chunkIds.constBegin(); chunkIt != chunkIds.constEnd(); ++chunkIt )
|
2823 |
{ |
2824 |
mShared->removeFid( *chunkIt ); |
2825 |
} |
2826 |
chunkIds.clear(); |
2827 |
} |
2828 |
|
2829 |
returnvalue &= conn->commit(); |
2830 |
if ( mTransaction )
|
2831 |
mTransaction->dirtyLastSavePoint(); |
2832 |
|
2833 |
if ( mSpatialColType == SctTopoGeometry )
|
2834 |
{ |
2835 |
// NOTE: in presence of multiple TopoGeometry objects
|
2836 |
// for the same table or when deleting a Geometry
|
2837 |
// layer _also_ having a TopoGeometry component,
|
2838 |
// orphans would still be left.
|
2839 |
// TODO: decouple layer from table and signal table when
|
2840 |
// records are added or removed
|
2841 |
dropOrphanedTopoGeoms(); |
2842 |
} |
2843 |
|
2844 |
mShared->addFeaturesCounted( -ids.size() ); |
2845 |
} |
2846 |
catch ( PGException &e )
|
2847 |
{ |
2848 |
pushError( tr( "PostGIS error while deleting features: %1" ).arg( e.errorMessage() ) );
|
2849 |
conn->rollback(); |
2850 |
returnvalue = false;
|
2851 |
} |
2852 |
|
2853 |
conn->unlock(); |
2854 |
return returnvalue;
|
2855 |
} |
2856 |
|
2857 |
bool QgsPostgresProvider::truncate()
|
2858 |
{ |
2859 |
bool returnvalue = true; |
2860 |
|
2861 |
if ( mIsQuery )
|
2862 |
{ |
2863 |
QgsDebugError( QStringLiteral( "Cannot truncate (is a query)" ) );
|
2864 |
return false; |
2865 |
} |
2866 |
|
2867 |
QgsPostgresConn *conn = connectionRW(); |
2868 |
if ( !conn )
|
2869 |
{ |
2870 |
return false; |
2871 |
} |
2872 |
conn->lock(); |
2873 |
|
2874 |
try
|
2875 |
{ |
2876 |
conn->begin(); |
2877 |
|
2878 |
QString sql = QStringLiteral( "TRUNCATE %1" ).arg( mQuery );
|
2879 |
QgsDebugMsgLevel( "truncate sql: " + sql, 2 ); |
2880 |
|
2881 |
//send truncate statement and do error handling
|
2882 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
2883 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
2884 |
throw PGException( result );
|
2885 |
|
2886 |
returnvalue &= conn->commit(); |
2887 |
if ( mTransaction )
|
2888 |
mTransaction->dirtyLastSavePoint(); |
2889 |
|
2890 |
if ( returnvalue )
|
2891 |
{ |
2892 |
if ( mSpatialColType == SctTopoGeometry )
|
2893 |
{ |
2894 |
// NOTE: in presence of multiple TopoGeometry objects
|
2895 |
// for the same table or when deleting a Geometry
|
2896 |
// layer _also_ having a TopoGeometry component,
|
2897 |
// orphans would still be left.
|
2898 |
// TODO: decouple layer from table and signal table when
|
2899 |
// records are added or removed
|
2900 |
dropOrphanedTopoGeoms(); |
2901 |
} |
2902 |
mShared->clear(); |
2903 |
} |
2904 |
} |
2905 |
catch ( PGException &e )
|
2906 |
{ |
2907 |
pushError( tr( "PostGIS error while truncating: %1" ).arg( e.errorMessage() ) );
|
2908 |
conn->rollback(); |
2909 |
returnvalue = false;
|
2910 |
} |
2911 |
|
2912 |
conn->unlock(); |
2913 |
return returnvalue;
|
2914 |
} |
2915 |
|
2916 |
bool QgsPostgresProvider::addAttributes( const QList<QgsField> &attributes ) |
2917 |
{ |
2918 |
bool returnvalue = true; |
2919 |
|
2920 |
if ( mIsQuery )
|
2921 |
return false; |
2922 |
|
2923 |
if ( attributes.isEmpty() )
|
2924 |
return true; |
2925 |
|
2926 |
QgsPostgresConn *conn = connectionRW(); |
2927 |
if ( !conn )
|
2928 |
{ |
2929 |
return false; |
2930 |
} |
2931 |
conn->lock(); |
2932 |
|
2933 |
try
|
2934 |
{ |
2935 |
conn->begin(); |
2936 |
|
2937 |
QString delim; |
2938 |
QString sql = QStringLiteral( "ALTER TABLE %1 " ).arg( mQuery );
|
2939 |
for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
|
2940 |
{ |
2941 |
QString type = iter->typeName(); |
2942 |
if ( type == QLatin1String( "char" ) || type == QLatin1String( "varchar" ) ) |
2943 |
{ |
2944 |
if ( iter->length() > 0 ) |
2945 |
type = QStringLiteral( "%1(%2)" ).arg( type ).arg( iter->length() );
|
2946 |
} |
2947 |
else if ( type == QLatin1String( "numeric" ) || type == QLatin1String( "decimal" ) ) |
2948 |
{ |
2949 |
if ( iter->length() > 0 && iter->precision() > 0 ) |
2950 |
type = QStringLiteral( "%1(%2,%3)" ).arg( type ).arg( iter->length() ).arg( iter->precision() );
|
2951 |
} |
2952 |
sql.append( QStringLiteral( "%1ADD COLUMN %2 %3" ).arg( delim, quotedIdentifier( iter->name() ), type ) );
|
2953 |
delim = ',';
|
2954 |
} |
2955 |
|
2956 |
//send sql statement and do error handling
|
2957 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
2958 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
2959 |
throw PGException( result );
|
2960 |
|
2961 |
for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
|
2962 |
{ |
2963 |
if ( !iter->comment().isEmpty() )
|
2964 |
{ |
2965 |
sql = QStringLiteral( "COMMENT ON COLUMN %1.%2 IS %3" )
|
2966 |
.arg( mQuery, quotedIdentifier( iter->name() ), quotedValue( iter->comment() ) ); |
2967 |
result = conn->LoggedPQexec( "QgsPostgresProvider", sql );
|
2968 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
2969 |
throw PGException( result );
|
2970 |
} |
2971 |
} |
2972 |
|
2973 |
returnvalue &= conn->commit(); |
2974 |
if ( mTransaction )
|
2975 |
mTransaction->dirtyLastSavePoint(); |
2976 |
} |
2977 |
catch ( PGException &e )
|
2978 |
{ |
2979 |
pushError( tr( "PostGIS error while adding attributes: %1" ).arg( e.errorMessage() ) );
|
2980 |
conn->rollback(); |
2981 |
returnvalue = false;
|
2982 |
} |
2983 |
|
2984 |
loadFields(); |
2985 |
conn->unlock(); |
2986 |
return returnvalue;
|
2987 |
} |
2988 |
|
2989 |
bool QgsPostgresProvider::deleteAttributes( const QgsAttributeIds &ids ) |
2990 |
{ |
2991 |
bool returnvalue = true; |
2992 |
|
2993 |
if ( mIsQuery )
|
2994 |
return false; |
2995 |
|
2996 |
QgsPostgresConn *conn = connectionRW(); |
2997 |
if ( !conn )
|
2998 |
{ |
2999 |
return false; |
3000 |
} |
3001 |
conn->lock(); |
3002 |
|
3003 |
try
|
3004 |
{ |
3005 |
conn->begin(); |
3006 |
|
3007 |
QList<int> idsList = ids.values();
|
3008 |
std::sort( idsList.begin(), idsList.end(), std::greater<int>() );
|
3009 |
|
3010 |
for ( auto iter = idsList.constBegin(); iter != idsList.constEnd(); ++iter ) |
3011 |
{ |
3012 |
int index = *iter;
|
3013 |
if ( index < 0 || index >= mAttributeFields.count() ) |
3014 |
continue;
|
3015 |
|
3016 |
QString column = mAttributeFields.at( index ).name(); |
3017 |
QString sql = QStringLiteral( "ALTER TABLE %1 DROP COLUMN %2" )
|
3018 |
.arg( mQuery, quotedIdentifier( column ) ); |
3019 |
|
3020 |
//send sql statement and do error handling
|
3021 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3022 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
3023 |
throw PGException( result );
|
3024 |
|
3025 |
//delete the attribute from mAttributeFields
|
3026 |
mAttributeFields.remove( index ); |
3027 |
} |
3028 |
|
3029 |
returnvalue &= conn->commit(); |
3030 |
if ( mTransaction )
|
3031 |
mTransaction->dirtyLastSavePoint(); |
3032 |
} |
3033 |
catch ( PGException &e )
|
3034 |
{ |
3035 |
pushError( tr( "PostGIS error while deleting attributes: %1" ).arg( e.errorMessage() ) );
|
3036 |
conn->rollback(); |
3037 |
returnvalue = false;
|
3038 |
} |
3039 |
|
3040 |
loadFields(); |
3041 |
conn->unlock(); |
3042 |
return returnvalue;
|
3043 |
} |
3044 |
|
3045 |
bool QgsPostgresProvider::renameAttributes( const QgsFieldNameMap &renamedAttributes ) |
3046 |
{ |
3047 |
if ( mIsQuery )
|
3048 |
return false; |
3049 |
|
3050 |
|
3051 |
QString sql = QStringLiteral( "BEGIN;" );
|
3052 |
|
3053 |
QgsFieldNameMap::const_iterator renameIt = renamedAttributes.constBegin(); |
3054 |
bool returnvalue = true; |
3055 |
for ( ; renameIt != renamedAttributes.constEnd(); ++renameIt )
|
3056 |
{ |
3057 |
int fieldIndex = renameIt.key();
|
3058 |
if ( fieldIndex < 0 || fieldIndex >= mAttributeFields.count() ) |
3059 |
{ |
3060 |
pushError( tr( "Invalid attribute index: %1" ).arg( fieldIndex ) );
|
3061 |
return false; |
3062 |
} |
3063 |
if ( mAttributeFields.indexFromName( renameIt.value() ) >= 0 ) |
3064 |
{ |
3065 |
//field name already in use
|
3066 |
pushError( tr( "Error renaming field %1: name '%2' already exists" ).arg( fieldIndex ).arg( renameIt.value() ) );
|
3067 |
return false; |
3068 |
} |
3069 |
|
3070 |
sql += QStringLiteral( "ALTER TABLE %1 RENAME COLUMN %2 TO %3;" )
|
3071 |
.arg( mQuery, quotedIdentifier( mAttributeFields.at( fieldIndex ).name() ), quotedIdentifier( renameIt.value() ) ); |
3072 |
} |
3073 |
sql += QLatin1String( "COMMIT;" );
|
3074 |
|
3075 |
QgsPostgresConn *conn = connectionRW(); |
3076 |
if ( !conn )
|
3077 |
{ |
3078 |
return false; |
3079 |
} |
3080 |
conn->lock(); |
3081 |
|
3082 |
try
|
3083 |
{ |
3084 |
conn->begin(); |
3085 |
//send sql statement and do error handling
|
3086 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3087 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
3088 |
throw PGException( result );
|
3089 |
returnvalue = conn->commit(); |
3090 |
if ( mTransaction )
|
3091 |
mTransaction->dirtyLastSavePoint(); |
3092 |
} |
3093 |
catch ( PGException &e )
|
3094 |
{ |
3095 |
pushError( tr( "PostGIS error while renaming attributes: %1" ).arg( e.errorMessage() ) );
|
3096 |
conn->rollback(); |
3097 |
returnvalue = false;
|
3098 |
} |
3099 |
|
3100 |
loadFields(); |
3101 |
conn->unlock(); |
3102 |
return returnvalue;
|
3103 |
} |
3104 |
|
3105 |
bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map ) |
3106 |
{ |
3107 |
bool returnvalue = true; |
3108 |
|
3109 |
if ( mIsQuery )
|
3110 |
return false; |
3111 |
|
3112 |
if ( attr_map.isEmpty() )
|
3113 |
return true; |
3114 |
|
3115 |
QgsPostgresConn *conn = connectionRW(); |
3116 |
if ( !conn )
|
3117 |
return false; |
3118 |
|
3119 |
conn->lock(); |
3120 |
|
3121 |
try
|
3122 |
{ |
3123 |
conn->begin(); |
3124 |
|
3125 |
// cycle through the features
|
3126 |
for ( QgsChangedAttributesMap::const_iterator iter = attr_map.constBegin(); iter != attr_map.constEnd(); ++iter )
|
3127 |
{ |
3128 |
QgsFeatureId fid = iter.key(); |
3129 |
|
3130 |
// skip added features
|
3131 |
if ( FID_IS_NEW( fid ) )
|
3132 |
continue;
|
3133 |
|
3134 |
const QgsAttributeMap &attrs = iter.value();
|
3135 |
if ( attrs.isEmpty() )
|
3136 |
continue;
|
3137 |
|
3138 |
QString sql = QStringLiteral( "UPDATE %1 SET " ).arg( mQuery );
|
3139 |
|
3140 |
bool pkChanged = false; |
3141 |
|
3142 |
// cycle through the changed attributes of the feature
|
3143 |
QString delim; |
3144 |
int numChangedFields = 0; |
3145 |
for ( QgsAttributeMap::const_iterator siter = attrs.constBegin(); siter != attrs.constEnd(); ++siter )
|
3146 |
{ |
3147 |
try
|
3148 |
{ |
3149 |
QgsField fld = field( siter.key() ); |
3150 |
|
3151 |
pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() ); |
3152 |
|
3153 |
if ( mGeneratedValues.contains( siter.key() ) )
|
3154 |
{ |
3155 |
QgsLogger::warning( tr( "Changing the value of GENERATED field %1 is not allowed." ).arg( fld.name() ) );
|
3156 |
continue;
|
3157 |
} |
3158 |
|
3159 |
numChangedFields++; |
3160 |
|
3161 |
sql += delim + QStringLiteral( "%1=" ).arg( quotedIdentifier( fld.name() ) );
|
3162 |
delim = ',';
|
3163 |
|
3164 |
QString defVal = defaultValueClause( siter.key() ); |
3165 |
if ( qgsVariantEqual( *siter, defVal ) )
|
3166 |
{ |
3167 |
sql += defVal.isNull() ? "NULL" : defVal;
|
3168 |
} |
3169 |
else if ( fld.typeName() == QLatin1String( "geometry" ) ) |
3170 |
{ |
3171 |
QString val = geomAttrToString( siter.value(), connectionRO() ); |
3172 |
|
3173 |
sql += QStringLiteral( "%1(%2)" )
|
3174 |
.arg( connectionRO()->majorVersion() < 2 ? "geomfromewkt" : "st_geomfromewkt", quotedValue( val ) ); |
3175 |
} |
3176 |
else if ( fld.typeName() == QLatin1String( "geography" ) ) |
3177 |
{ |
3178 |
sql += QStringLiteral( "st_geographyfromtext(%1)" )
|
3179 |
.arg( quotedValue( siter->toString() ) ); |
3180 |
} |
3181 |
else if ( fld.typeName() == QLatin1String( "jsonb" ) ) |
3182 |
{ |
3183 |
sql += QStringLiteral( "%1::jsonb" )
|
3184 |
.arg( quotedJsonValue( siter.value() ) ); |
3185 |
} |
3186 |
else if ( fld.typeName() == QLatin1String( "json" ) ) |
3187 |
{ |
3188 |
sql += QStringLiteral( "%1::json" )
|
3189 |
.arg( quotedJsonValue( siter.value() ) ); |
3190 |
} |
3191 |
else if ( fld.typeName() == QLatin1String( "bytea" ) ) |
3192 |
{ |
3193 |
sql += quotedByteaValue( siter.value() ); |
3194 |
} |
3195 |
else
|
3196 |
{ |
3197 |
sql += quotedValue( *siter ); |
3198 |
} |
3199 |
} |
3200 |
catch ( PGFieldNotFound )
|
3201 |
{ |
3202 |
// Field was missing - shouldn't happen
|
3203 |
} |
3204 |
} |
3205 |
|
3206 |
sql += QStringLiteral( " WHERE %1" ).arg( whereClause( fid ) );
|
3207 |
|
3208 |
// Don't try to UPDATE an empty set of values (might happen if the table only has GENERATED fields,
|
3209 |
// or if the user only changed GENERATED fields in the form/attribute table.
|
3210 |
if ( numChangedFields > 0 ) |
3211 |
{ |
3212 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3213 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
3214 |
throw PGException( result );
|
3215 |
} |
3216 |
else // let the user know that no field was actually changed |
3217 |
{ |
3218 |
QgsLogger::warning( tr( "No fields were updated on the database." ) );
|
3219 |
} |
3220 |
|
3221 |
// update feature id map if key was changed
|
3222 |
// PktInt64 also uses a fid map even if it is a stand alone field.
|
3223 |
if ( pkChanged && ( mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktInt64 ) )
|
3224 |
{ |
3225 |
QVariantList k = mShared->removeFid( fid ); |
3226 |
|
3227 |
int keyCount = std::min( mPrimaryKeyAttrs.size(), k.size() );
|
3228 |
|
3229 |
for ( int i = 0; i < keyCount; i++ ) |
3230 |
{ |
3231 |
int idx = mPrimaryKeyAttrs.at( i );
|
3232 |
if ( !attrs.contains( idx ) )
|
3233 |
continue;
|
3234 |
|
3235 |
k[i] = attrs[idx]; |
3236 |
} |
3237 |
|
3238 |
mShared->insertFid( fid, k ); |
3239 |
} |
3240 |
} |
3241 |
|
3242 |
returnvalue &= conn->commit(); |
3243 |
if ( mTransaction )
|
3244 |
mTransaction->dirtyLastSavePoint(); |
3245 |
} |
3246 |
catch ( PGException &e )
|
3247 |
{ |
3248 |
pushError( tr( "PostGIS error while changing attributes: %1" ).arg( e.errorMessage() ) );
|
3249 |
conn->rollback(); |
3250 |
returnvalue = false;
|
3251 |
} |
3252 |
|
3253 |
conn->unlock(); |
3254 |
return returnvalue;
|
3255 |
} |
3256 |
|
3257 |
void QgsPostgresProvider::appendGeomParam( const QgsGeometry &geom, QStringList ¶ms ) const |
3258 |
{ |
3259 |
if ( geom.isNull() )
|
3260 |
{ |
3261 |
params << QString(); |
3262 |
return;
|
3263 |
} |
3264 |
|
3265 |
QString param; |
3266 |
|
3267 |
const QgsGeometry convertedGeom( convertToProviderType( geom, wkbType() ) );
|
3268 |
QByteArray wkb( !convertedGeom.isNull() ? convertedGeom.asWkb() : geom.asWkb() ); |
3269 |
const unsigned char *buf = reinterpret_cast<const unsigned char *>( wkb.constData() ); |
3270 |
int wkbSize = wkb.length();
|
3271 |
|
3272 |
for ( int i = 0; i < wkbSize; ++i ) |
3273 |
{ |
3274 |
if ( connectionRO()->useWkbHex() )
|
3275 |
param += QStringLiteral( "%1" ).arg( ( int ) buf[i], 2, 16, QChar( '0' ) ); |
3276 |
else
|
3277 |
param += QStringLiteral( "\\%1" ).arg( ( int ) buf[i], 3, 8, QChar( '0' ) ); |
3278 |
} |
3279 |
params << param; |
3280 |
} |
3281 |
|
3282 |
bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) |
3283 |
{ |
3284 |
if ( mIsQuery || mGeometryColumn.isNull() )
|
3285 |
return false; |
3286 |
|
3287 |
QgsPostgresConn *conn = connectionRW(); |
3288 |
if ( !conn )
|
3289 |
{ |
3290 |
return false; |
3291 |
} |
3292 |
conn->lock(); |
3293 |
|
3294 |
bool returnvalue = true; |
3295 |
|
3296 |
try
|
3297 |
{ |
3298 |
// Start the PostGIS transaction
|
3299 |
conn->begin(); |
3300 |
|
3301 |
QString update; |
3302 |
QgsPostgresResult result; |
3303 |
|
3304 |
if ( mSpatialColType == SctTopoGeometry )
|
3305 |
{ |
3306 |
// We will create a new TopoGeometry object with the new shape.
|
3307 |
// Later, we'll replace the old TopoGeometry with the new one,
|
3308 |
// to avoid orphans and retain higher level in an eventual
|
3309 |
// hierarchical definition
|
3310 |
update = QStringLiteral( "SELECT id(%1) FROM %2 o WHERE %3" )
|
3311 |
.arg( geomParam( 1 ), mQuery, pkParamWhereClause( 2 ) ); |
3312 |
|
3313 |
QString getid = QStringLiteral( "SELECT id(%1) FROM %2 WHERE %3" )
|
3314 |
.arg( quotedIdentifier( mGeometryColumn ), mQuery, pkParamWhereClause( 1 ) );
|
3315 |
|
3316 |
QgsDebugMsgLevel( "getting old topogeometry id: " + getid, 2 ); |
3317 |
|
3318 |
result = connectionRO()->PQprepare( QStringLiteral( "getid" ), getid, 1, nullptr, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ); |
3319 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
3320 |
{ |
3321 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQprepare of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
3322 |
.arg( result.PQresultStatus() ) |
3323 |
.arg( PGRES_COMMAND_OK ) |
3324 |
.arg( getid ) ); |
3325 |
throw PGException( result );
|
3326 |
} |
3327 |
|
3328 |
QString replace = QString( "UPDATE %1 SET %2="
|
3329 |
"( topology_id(%2),layer_id(%2),$1,type(%2) )"
|
3330 |
"WHERE %3" )
|
3331 |
.arg( mQuery, quotedIdentifier( mGeometryColumn ), pkParamWhereClause( 2 ) );
|
3332 |
QgsDebugMsgLevel( "TopoGeom swap: " + replace, 2 ); |
3333 |
result = conn->PQprepare( QStringLiteral( "replacetopogeom" ), replace, 2, nullptr, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ); |
3334 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
3335 |
{ |
3336 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQprepare of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
3337 |
.arg( result.PQresultStatus() ) |
3338 |
.arg( PGRES_COMMAND_OK ) |
3339 |
.arg( replace ) ); |
3340 |
throw PGException( result );
|
3341 |
} |
3342 |
} |
3343 |
else
|
3344 |
{ |
3345 |
update = QStringLiteral( "UPDATE %1 SET %2=%3 WHERE %4" )
|
3346 |
.arg( mQuery, quotedIdentifier( mGeometryColumn ), geomParam( 1 ), pkParamWhereClause( 2 ) ); |
3347 |
} |
3348 |
|
3349 |
QgsDebugMsgLevel( "updating: " + update, 2 ); |
3350 |
|
3351 |
result = conn->PQprepare( QStringLiteral( "updatefeatures" ), update, 2, nullptr, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ); |
3352 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
3353 |
{ |
3354 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQprepare of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
3355 |
.arg( result.PQresultStatus() ) |
3356 |
.arg( PGRES_COMMAND_OK ) |
3357 |
.arg( update ) ); |
3358 |
throw PGException( result );
|
3359 |
} |
3360 |
|
3361 |
QgsDebugMsgLevel( QStringLiteral( "iterating over the map of changed geometries..." ), 2 ); |
3362 |
|
3363 |
for ( QgsGeometryMap::const_iterator iter = geometry_map.constBegin();
|
3364 |
iter != geometry_map.constEnd(); |
3365 |
++iter ) |
3366 |
{ |
3367 |
QgsDebugMsgLevel( "iterating over feature id " + FID_TO_STRING( iter.key() ), 2 ); |
3368 |
|
3369 |
// Save the id of the current topogeometry
|
3370 |
long old_tg_id = -1; |
3371 |
if ( mSpatialColType == SctTopoGeometry )
|
3372 |
{ |
3373 |
QStringList params; |
3374 |
appendPkParams( iter.key(), params ); |
3375 |
result = connectionRO()->PQexecPrepared( QStringLiteral( "getid" ), params, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ); |
3376 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
3377 |
{ |
3378 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQexecPrepared of 'getid' returning != PGRES_TUPLES_OK (%1 != expected %2)" )
|
3379 |
.arg( result.PQresultStatus() ) |
3380 |
.arg( PGRES_TUPLES_OK ) ); |
3381 |
throw PGException( result );
|
3382 |
} |
3383 |
// TODO: watch out for NULL, handle somehow
|
3384 |
old_tg_id = result.PQgetvalue( 0, 0 ).toLong(); |
3385 |
QgsDebugMsgLevel( QStringLiteral( "Old TG id is %1" ).arg( old_tg_id ), 2 ); |
3386 |
} |
3387 |
|
3388 |
QStringList params; |
3389 |
appendGeomParam( *iter, params ); |
3390 |
appendPkParams( iter.key(), params ); |
3391 |
|
3392 |
result = conn->PQexecPrepared( QStringLiteral( "updatefeatures" ), params, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ); |
3393 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
3394 |
throw PGException( result );
|
3395 |
|
3396 |
if ( mSpatialColType == SctTopoGeometry )
|
3397 |
{ |
3398 |
long new_tg_id = result.PQgetvalue( 0, 0 ).toLong(); // new topogeo_id |
3399 |
|
3400 |
// Replace old TopoGeom with new TopoGeom, so that
|
3401 |
// any hierarchically defined TopoGeom will still have its
|
3402 |
// definition and we'll leave no orphans
|
3403 |
QString replace = QString( "DELETE FROM %1.relation WHERE "
|
3404 |
"layer_id = %2 AND topogeo_id = %3" )
|
3405 |
.arg( quotedIdentifier( mTopoLayerInfo.topologyName ) ) |
3406 |
.arg( mTopoLayerInfo.layerId ) |
3407 |
.arg( old_tg_id ); |
3408 |
result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), replace );
|
3409 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
3410 |
{ |
3411 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQexec of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
3412 |
.arg( result.PQresultStatus() ) |
3413 |
.arg( PGRES_COMMAND_OK ) |
3414 |
.arg( replace ) ); |
3415 |
throw PGException( result );
|
3416 |
} |
3417 |
// TODO: use prepared query here
|
3418 |
replace = QString( "UPDATE %1.relation SET topogeo_id = %2 "
|
3419 |
"WHERE layer_id = %3 AND topogeo_id = %4" )
|
3420 |
.arg( quotedIdentifier( mTopoLayerInfo.topologyName ) ) |
3421 |
.arg( old_tg_id ) |
3422 |
.arg( mTopoLayerInfo.layerId ) |
3423 |
.arg( new_tg_id ); |
3424 |
QgsDebugMsgLevel( "relation swap: " + replace, 2 ); |
3425 |
result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), replace );
|
3426 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
3427 |
{ |
3428 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQexec of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
3429 |
.arg( result.PQresultStatus() ) |
3430 |
.arg( PGRES_COMMAND_OK ) |
3431 |
.arg( replace ) ); |
3432 |
throw PGException( result );
|
3433 |
} |
3434 |
} // if TopoGeometry
|
3435 |
|
3436 |
} // for each feature
|
3437 |
|
3438 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE updatefeatures" ) ); |
3439 |
if ( mSpatialColType == SctTopoGeometry )
|
3440 |
{ |
3441 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE getid" ) ); |
3442 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE replacetopogeom" ) ); |
3443 |
} |
3444 |
|
3445 |
returnvalue &= conn->commit(); |
3446 |
if ( mTransaction )
|
3447 |
mTransaction->dirtyLastSavePoint(); |
3448 |
} |
3449 |
catch ( PGException &e )
|
3450 |
{ |
3451 |
pushError( tr( "PostGIS error while changing geometry values: %1" ).arg( e.errorMessage() ) );
|
3452 |
conn->rollback(); |
3453 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE updatefeatures" ) ); |
3454 |
if ( mSpatialColType == SctTopoGeometry )
|
3455 |
{ |
3456 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE getid" ) ); |
3457 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE replacetopogeom" ) ); |
3458 |
} |
3459 |
returnvalue = false;
|
3460 |
} |
3461 |
|
3462 |
conn->unlock(); |
3463 |
|
3464 |
QgsDebugMsgLevel( QStringLiteral( "leaving." ), 4 ); |
3465 |
|
3466 |
return returnvalue;
|
3467 |
} |
3468 |
|
3469 |
bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_map, const QgsGeometryMap &geometry_map ) |
3470 |
{ |
3471 |
Q_ASSERT( mSpatialColType != SctTopoGeometry ); |
3472 |
|
3473 |
bool returnvalue = true; |
3474 |
|
3475 |
if ( mIsQuery )
|
3476 |
return false; |
3477 |
|
3478 |
if ( attr_map.isEmpty() )
|
3479 |
return true; |
3480 |
|
3481 |
QgsPostgresConn *conn = connectionRW(); |
3482 |
if ( !conn )
|
3483 |
return false; |
3484 |
|
3485 |
conn->lock(); |
3486 |
|
3487 |
try
|
3488 |
{ |
3489 |
conn->begin(); |
3490 |
|
3491 |
QgsFeatureIds ids( qgis::listToSet( attr_map.keys() ) ); |
3492 |
ids |= qgis::listToSet( geometry_map.keys() ); |
3493 |
|
3494 |
// cycle through the features
|
3495 |
const auto constIds = ids; |
3496 |
for ( QgsFeatureId fid : constIds )
|
3497 |
{ |
3498 |
// skip added features
|
3499 |
if ( FID_IS_NEW( fid ) )
|
3500 |
continue;
|
3501 |
|
3502 |
const QgsAttributeMap &attrs = attr_map.value( fid );
|
3503 |
if ( attrs.isEmpty() && !geometry_map.contains( fid ) )
|
3504 |
continue;
|
3505 |
|
3506 |
QString sql = QStringLiteral( "UPDATE %1 SET " ).arg( mQuery );
|
3507 |
|
3508 |
bool pkChanged = false; |
3509 |
|
3510 |
// cycle through the changed attributes of the feature
|
3511 |
QString delim; |
3512 |
int numChangedFields = 0; |
3513 |
|
3514 |
for ( QgsAttributeMap::const_iterator siter = attrs.constBegin(); siter != attrs.constEnd(); ++siter )
|
3515 |
{ |
3516 |
try
|
3517 |
{ |
3518 |
QgsField fld = field( siter.key() ); |
3519 |
|
3520 |
pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() ); |
3521 |
|
3522 |
if ( mGeneratedValues.contains( siter.key() ) )
|
3523 |
{ |
3524 |
QgsLogger::warning( tr( "Changing the value of GENERATED field %1 is not allowed." ).arg( fld.name() ) );
|
3525 |
continue;
|
3526 |
} |
3527 |
|
3528 |
numChangedFields++; |
3529 |
|
3530 |
sql += delim + QStringLiteral( "%1=" ).arg( quotedIdentifier( fld.name() ) );
|
3531 |
delim = ',';
|
3532 |
|
3533 |
if ( fld.typeName() == QLatin1String( "geometry" ) ) |
3534 |
{ |
3535 |
QString val = geomAttrToString( siter.value(), connectionRO() ); |
3536 |
sql += QStringLiteral( "%1(%2)" )
|
3537 |
.arg( connectionRO()->majorVersion() < 2 ? "geomfromewkt" : "st_geomfromewkt", quotedValue( val ) ); |
3538 |
} |
3539 |
else if ( fld.typeName() == QLatin1String( "geography" ) ) |
3540 |
{ |
3541 |
sql += QStringLiteral( "st_geographyfromtext(%1)" )
|
3542 |
.arg( quotedValue( siter->toString() ) ); |
3543 |
} |
3544 |
else if ( fld.typeName() == QLatin1String( "jsonb" ) ) |
3545 |
{ |
3546 |
sql += QStringLiteral( "%1::jsonb" )
|
3547 |
.arg( quotedJsonValue( siter.value() ) ); |
3548 |
} |
3549 |
else if ( fld.typeName() == QLatin1String( "json" ) ) |
3550 |
{ |
3551 |
sql += QStringLiteral( "%1::json" )
|
3552 |
.arg( quotedJsonValue( siter.value() ) ); |
3553 |
} |
3554 |
else if ( fld.typeName() == QLatin1String( "bytea" ) ) |
3555 |
{ |
3556 |
sql += quotedByteaValue( siter.value() ); |
3557 |
} |
3558 |
else
|
3559 |
{ |
3560 |
sql += quotedValue( *siter ); |
3561 |
} |
3562 |
} |
3563 |
catch ( PGFieldNotFound )
|
3564 |
{ |
3565 |
// Field was missing - shouldn't happen
|
3566 |
} |
3567 |
} |
3568 |
|
3569 |
if ( !geometry_map.contains( fid ) )
|
3570 |
{ |
3571 |
// Don't try to UPDATE an empty set of values (might happen if the table only has GENERATED fields,
|
3572 |
// or if the user only changed GENERATED fields in the form/attribute table.
|
3573 |
if ( numChangedFields > 0 ) |
3574 |
{ |
3575 |
sql += QStringLiteral( " WHERE %1" ).arg( whereClause( fid ) );
|
3576 |
|
3577 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3578 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
3579 |
throw PGException( result );
|
3580 |
} |
3581 |
else // let the user know that nothing has actually changed |
3582 |
{ |
3583 |
QgsLogger::warning( tr( "No fields/geometries were updated on the database." ) );
|
3584 |
} |
3585 |
} |
3586 |
else
|
3587 |
{ |
3588 |
sql += QStringLiteral( "%1%2=%3" ).arg( delim, quotedIdentifier( mGeometryColumn ), geomParam( 1 ) ); |
3589 |
sql += QStringLiteral( " WHERE %1" ).arg( whereClause( fid ) );
|
3590 |
|
3591 |
QgsPostgresResult result( conn->PQprepare( QStringLiteral( "updatefeature" ), sql, 1, nullptr, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ) ); |
3592 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
3593 |
{ |
3594 |
QgsDebugError( QStringLiteral( "Exception thrown due to PQprepare of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
3595 |
.arg( result.PQresultStatus() ) |
3596 |
.arg( PGRES_COMMAND_OK ) |
3597 |
.arg( sql ) ); |
3598 |
throw PGException( result );
|
3599 |
} |
3600 |
|
3601 |
QStringList params; |
3602 |
const QgsGeometry &geom = geometry_map[fid];
|
3603 |
appendGeomParam( geom, params ); |
3604 |
|
3605 |
result = conn->PQexecPrepared( QStringLiteral( "updatefeature" ), params, QStringLiteral( "QgsPostgresProvider" ), QGS_QUERY_LOG_ORIGIN ); |
3606 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
|
3607 |
{ |
3608 |
conn->rollback(); |
3609 |
conn->PQexecNR( QStringLiteral( "DEALLOCATE updatefeature" ) );
|
3610 |
throw PGException( result );
|
3611 |
} |
3612 |
|
3613 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE updatefeature" ) ); |
3614 |
} |
3615 |
|
3616 |
// update feature id map if key was changed
|
3617 |
// PktInt64 also uses a fid map even though it is a single field.
|
3618 |
if ( pkChanged && ( mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktInt64 ) )
|
3619 |
{ |
3620 |
QVariantList k = mShared->removeFid( fid ); |
3621 |
|
3622 |
for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) |
3623 |
{ |
3624 |
int idx = mPrimaryKeyAttrs.at( i );
|
3625 |
if ( !attrs.contains( idx ) )
|
3626 |
continue;
|
3627 |
|
3628 |
k[i] = attrs[idx]; |
3629 |
} |
3630 |
|
3631 |
mShared->insertFid( fid, k ); |
3632 |
} |
3633 |
} |
3634 |
|
3635 |
returnvalue &= conn->commit(); |
3636 |
if ( mTransaction )
|
3637 |
mTransaction->dirtyLastSavePoint(); |
3638 |
} |
3639 |
catch ( PGException &e )
|
3640 |
{ |
3641 |
pushError( tr( "PostGIS error while changing attributes: %1" ).arg( e.errorMessage() ) );
|
3642 |
conn->rollback(); |
3643 |
returnvalue = false;
|
3644 |
} |
3645 |
|
3646 |
conn->unlock(); |
3647 |
|
3648 |
QgsDebugMsgLevel( QStringLiteral( "leaving." ), 4 ); |
3649 |
|
3650 |
return returnvalue;
|
3651 |
} |
3652 |
|
3653 |
QgsAttributeList QgsPostgresProvider::attributeIndexes() const
|
3654 |
{ |
3655 |
QgsAttributeList lst; |
3656 |
lst.reserve( mAttributeFields.count() ); |
3657 |
for ( int i = 0; i < mAttributeFields.count(); ++i ) |
3658 |
lst.append( i ); |
3659 |
return lst;
|
3660 |
} |
3661 |
|
3662 |
Qgis::VectorProviderCapabilities QgsPostgresProvider::capabilities() const
|
3663 |
{ |
3664 |
return mEnabledCapabilities;
|
3665 |
} |
3666 |
|
3667 |
Qgis::VectorDataProviderAttributeEditCapabilities QgsPostgresProvider::attributeEditCapabilities() const
|
3668 |
{ |
3669 |
return Qgis::VectorDataProviderAttributeEditCapability::EditComment;
|
3670 |
} |
3671 |
|
3672 |
Qgis::SpatialIndexPresence QgsPostgresProvider::hasSpatialIndex() const
|
3673 |
{ |
3674 |
QgsPostgresProviderConnection conn( mUri.uri(), QVariantMap() ); |
3675 |
try
|
3676 |
{ |
3677 |
return conn.spatialIndexExists( mUri.schema(), mUri.table(), mUri.geometryColumn() ) ? Qgis::SpatialIndexPresence::Present : Qgis::SpatialIndexPresence::NotPresent;
|
3678 |
} |
3679 |
catch ( QgsProviderConnectionException & )
|
3680 |
{ |
3681 |
return Qgis::SpatialIndexPresence::Unknown;
|
3682 |
} |
3683 |
} |
3684 |
|
3685 |
bool QgsPostgresProvider::setSubsetString( const QString &theSQL, bool updateFeatureCount ) |
3686 |
{ |
3687 |
if ( theSQL.trimmed() == mSqlWhereClause )
|
3688 |
return true; |
3689 |
|
3690 |
QString prevWhere = mSqlWhereClause; |
3691 |
|
3692 |
mSqlWhereClause = theSQL.trimmed(); |
3693 |
|
3694 |
QString sql = QStringLiteral( "SELECT * FROM %1" ).arg( mQuery );
|
3695 |
|
3696 |
if ( !mSqlWhereClause.isEmpty() )
|
3697 |
{ |
3698 |
sql += QStringLiteral( " WHERE %1" ).arg( mSqlWhereClause );
|
3699 |
} |
3700 |
|
3701 |
sql += QLatin1String( " LIMIT 0" );
|
3702 |
|
3703 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3704 |
if ( res.PQresultStatus() != PGRES_TUPLES_OK )
|
3705 |
{ |
3706 |
pushError( res.PQresultErrorMessage() ); |
3707 |
mSqlWhereClause = prevWhere; |
3708 |
return false; |
3709 |
} |
3710 |
|
3711 |
#if 0
|
3712 |
// FIXME
|
3713 |
if ( mPrimaryKeyType == PktInt && !uniqueData( primaryKeyAttr ) )
|
3714 |
{
|
3715 |
sqlWhereClause = prevWhere;
|
3716 |
return false;
|
3717 |
}
|
3718 |
#endif
|
3719 |
|
3720 |
// Update datasource uri too
|
3721 |
mUri.setSql( theSQL ); |
3722 |
// Update yet another copy of the uri. Why are there 3 copies of the
|
3723 |
// uri? Perhaps this needs some rationalisation.....
|
3724 |
setDataSourceUri( mUri.uri( false ) );
|
3725 |
|
3726 |
if ( updateFeatureCount )
|
3727 |
{ |
3728 |
reloadData(); |
3729 |
} |
3730 |
else
|
3731 |
{ |
3732 |
mLayerExtent.reset(); |
3733 |
emit dataChanged(); |
3734 |
} |
3735 |
|
3736 |
return true; |
3737 |
} |
3738 |
|
3739 |
bool QgsPostgresProvider::supportsSubsetString() const |
3740 |
{ |
3741 |
return true; |
3742 |
} |
3743 |
|
3744 |
QString QgsPostgresProvider::subsetStringDialect() const
|
3745 |
{ |
3746 |
return tr( "PostgreSQL WHERE clause" ); |
3747 |
} |
3748 |
|
3749 |
QString QgsPostgresProvider::subsetStringHelpUrl() const
|
3750 |
{ |
3751 |
return QStringLiteral( "https://www.postgresql.org/docs/current/sql-expressions.html" ); |
3752 |
} |
3753 |
|
3754 |
long long QgsPostgresProvider::featureCount() const |
3755 |
{ |
3756 |
long long featuresCounted = mShared->featuresCounted(); |
3757 |
if ( featuresCounted >= 0 ) |
3758 |
return featuresCounted;
|
3759 |
|
3760 |
// See: https://github.com/qgis/QGIS/issues/25285 - QGIS crashes on featureCount())
|
3761 |
if ( !connectionRO() )
|
3762 |
{ |
3763 |
return 0; |
3764 |
} |
3765 |
|
3766 |
// get total number of features
|
3767 |
QString sql; |
3768 |
|
3769 |
long long num = -1; |
3770 |
if ( !mIsQuery && mUseEstimatedMetadata )
|
3771 |
{ |
3772 |
if ( ( relkind() == Qgis::PostgresRelKind::View || !mSqlWhereClause.isEmpty() ) && connectionRO()->pgVersion() >= 90000 ) |
3773 |
{ |
3774 |
// parse explain output to estimate feature count
|
3775 |
// we don't use pg_class reltuples because it returns 0 for view
|
3776 |
sql = QStringLiteral( "EXPLAIN (FORMAT JSON) SELECT 1 FROM %1%2" ).arg( mQuery, filterWhereClause() );
|
3777 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3778 |
|
3779 |
const QString json = result.PQgetvalue( 0, 0 ); |
3780 |
const QVariantList explain = QgsJsonUtils::parseJson( json ).toList();
|
3781 |
const QVariantMap countPlan = !explain.isEmpty() ? explain[0].toMap().value( "Plan" ).toMap() : QVariantMap(); |
3782 |
const QVariant nbRows = countPlan.value( "Plan Rows" ); |
3783 |
|
3784 |
if ( nbRows.isValid() )
|
3785 |
num = nbRows.toLongLong(); |
3786 |
else
|
3787 |
QgsLogger::warning( QStringLiteral( "Cannot parse JSON explain result to estimate feature count (%1) : %2" ).arg( sql, json ) );
|
3788 |
} |
3789 |
else
|
3790 |
{ |
3791 |
sql = QStringLiteral( "SELECT reltuples::bigint FROM pg_catalog.pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
|
3792 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3793 |
num = result.PQgetvalue( 0, 0 ).toLongLong(); |
3794 |
} |
3795 |
} |
3796 |
else
|
3797 |
{ |
3798 |
sql = QStringLiteral( "SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() );
|
3799 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3800 |
|
3801 |
QgsDebugMsgLevel( "number of features as text: " + result.PQgetvalue( 0, 0 ), 2 ); |
3802 |
|
3803 |
num = result.PQgetvalue( 0, 0 ).toLongLong(); |
3804 |
} |
3805 |
|
3806 |
mShared->setFeaturesCounted( num ); |
3807 |
|
3808 |
QgsDebugMsgLevel( "number of features: " + QString::number( num ), 2 ); |
3809 |
|
3810 |
return num;
|
3811 |
} |
3812 |
|
3813 |
bool QgsPostgresProvider::empty() const |
3814 |
{ |
3815 |
QString sql = QStringLiteral( "SELECT EXISTS (SELECT * FROM %1%2 LIMIT 1)" ).arg( mQuery, filterWhereClause() );
|
3816 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3817 |
if ( res.PQresultStatus() != PGRES_TUPLES_OK )
|
3818 |
{ |
3819 |
pushError( res.PQresultErrorMessage() ); |
3820 |
return false; |
3821 |
} |
3822 |
|
3823 |
return res.PQgetvalue( 0, 0 ) != QLatin1String( "t" ); |
3824 |
} |
3825 |
|
3826 |
QgsRectangle QgsPostgresProvider::extent() const
|
3827 |
{ |
3828 |
return extent3D().toRectangle();
|
3829 |
} |
3830 |
|
3831 |
bool QgsPostgresProvider::estimateExtent() const |
3832 |
{ |
3833 |
// Cannot estimate extent of a query
|
3834 |
if ( mIsQuery )
|
3835 |
{ |
3836 |
QgsDebugMsgLevel( "Estimating extent of queries is not supported", 2 ); |
3837 |
return false; |
3838 |
} |
3839 |
|
3840 |
const int vmaj = connectionRO()->majorVersion(); |
3841 |
const int vmin = connectionRO()->minorVersion(); |
3842 |
|
3843 |
if ( mSpatialColType == SctGeography )
|
3844 |
{ |
3845 |
// PostGIS up to PostGIS-3.4.x had bogus estimation
|
3846 |
// for geography type, https://trac.osgeo.org/postgis/ticket/5734
|
3847 |
if ( vmaj < 3 || ( vmaj == 3 && vmin < 5 ) ) |
3848 |
{ |
3849 |
QgsDebugMsgLevel( QStringLiteral( "Estimating extent of geography columns was not supported by PostGIS %1.%2 (3.5+ required)" ).arg( vmaj, vmin ), 2 ); |
3850 |
return false; |
3851 |
} |
3852 |
} |
3853 |
|
3854 |
QString sql = QStringLiteral( "SELECT %1(%2,%3,%4)" )
|
3855 |
.arg( |
3856 |
vmaj < 2 ? "estimated_extent" : ( vmaj == 2 && vmin < 1 ? "st_estimated_extent" : "st_estimatedextent" ), |
3857 |
quotedValue( mSchemaName ), |
3858 |
quotedValue( mTableName ), |
3859 |
quotedValue( mGeometryColumn ) |
3860 |
); |
3861 |
|
3862 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3863 |
|
3864 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
3865 |
{ |
3866 |
pushError( result.PQresultErrorMessage() ); |
3867 |
return false; |
3868 |
} |
3869 |
if ( result.PQntuples() != 1 ) |
3870 |
{ |
3871 |
pushError( tr( "Unexpected number of tuples from estimated extent query %1: %2 (1 expected)." )
|
3872 |
.arg( sql ) |
3873 |
.arg( result.PQntuples() ) ); |
3874 |
return false; |
3875 |
} |
3876 |
|
3877 |
if ( result.PQgetisnull( 0, 0 ) ) |
3878 |
return false; |
3879 |
|
3880 |
QString box2dString = result.PQgetvalue( 0, 0 ); |
3881 |
|
3882 |
QgsDebugMsgLevel( QStringLiteral( "Got estimated extent (%1) using: %2" ).arg( box2dString ).arg( sql ), 2 ); |
3883 |
|
3884 |
const thread_local QRegularExpression rx2d( "\\((.+) (.+),(.+) (.+)\\)" ); |
3885 |
const QRegularExpressionMatch match = rx2d.match( box2dString );
|
3886 |
if ( !match.hasMatch() )
|
3887 |
{ |
3888 |
pushError( tr( "Unexpected format from estimated extent query %1: %2." ).arg( sql, box2dString ) );
|
3889 |
return false; // throw instead ? |
3890 |
} |
3891 |
|
3892 |
mLayerExtent.emplace( |
3893 |
match.captured( 1 ).toDouble(), // xmin |
3894 |
match.captured( 2 ).toDouble(), // ymin |
3895 |
std::numeric_limits<double>::quiet_NaN(), // zmin |
3896 |
match.captured( 3 ).toDouble(), // xmax |
3897 |
match.captured( 4 ).toDouble(), // ymax |
3898 |
std::numeric_limits<double>::quiet_NaN() // zmax |
3899 |
); |
3900 |
|
3901 |
QgsDebugMsgLevel( "Set extents to estimated value: " + mLayerExtent->toString(), 2 ); |
3902 |
return true; |
3903 |
} |
3904 |
|
3905 |
bool QgsPostgresProvider::computeExtent3D() const |
3906 |
{ |
3907 |
QString sql = QStringLiteral( "SELECT %1(%2%3) FROM %4%5" )
|
3908 |
.arg( connectionRO()->majorVersion() < 2 ? "extent" : "ST_3DExtent", quotedIdentifier( mBoundingBoxColumn ), ( mSpatialColType == SctPcPatch || mSpatialColType == SctGeography ) ? "::geometry" : "", mQuery, filterWhereClause() ); |
3909 |
|
3910 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
3911 |
|
3912 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
3913 |
{ |
3914 |
pushError( result.PQresultErrorMessage() ); |
3915 |
return false; |
3916 |
} |
3917 |
|
3918 |
if ( result.PQntuples() != 1 ) |
3919 |
{ |
3920 |
pushError( tr( "Unexpected number of tuples from compute extent query %1: %2 (1 expected)." )
|
3921 |
.arg( sql ) |
3922 |
.arg( result.PQntuples() ) ); |
3923 |
return false; |
3924 |
} |
3925 |
|
3926 |
if ( result.PQgetisnull( 0, 0 ) ) |
3927 |
{ |
3928 |
// Layer is empty, set layerExtent to null (default-construct)
|
3929 |
QgsDebugMsgLevel( QStringLiteral( "Got null from extent aggregate, setting layer extent to null as well" ), 2 ); |
3930 |
mLayerExtent.emplace(); // constructs a NULL
|
3931 |
return true; |
3932 |
} |
3933 |
|
3934 |
QString ext = result.PQgetvalue( 0, 0 ); |
3935 |
|
3936 |
if ( ext.isEmpty() )
|
3937 |
{ |
3938 |
pushError( tr( "Unexpected empty result from extent query %1." ).arg( sql ) );
|
3939 |
return false; |
3940 |
} |
3941 |
|
3942 |
QgsDebugMsgLevel( QStringLiteral( "Got extents (%1) using: %2" ).arg( ext ).arg( sql ), 2 ); |
3943 |
|
3944 |
// Try the BOX3D format
|
3945 |
const thread_local QRegularExpression rx3d( "\\((.+) (.+) (.+),(.+) (.+) (.+)\\)" ); |
3946 |
QRegularExpressionMatch match = rx3d.match( ext ); |
3947 |
if ( match.hasMatch() )
|
3948 |
{ |
3949 |
mLayerExtent.emplace( |
3950 |
match.captured( 1 ).toDouble(), // xmin |
3951 |
match.captured( 2 ).toDouble(), // ymin |
3952 |
match.captured( 3 ).toDouble(), // zmin |
3953 |
match.captured( 4 ).toDouble(), // xmax |
3954 |
match.captured( 5 ).toDouble(), // ymax |
3955 |
match.captured( 6 ).toDouble() // zmax |
3956 |
); |
3957 |
QgsDebugMsgLevel( "Set extents to computed 3D value: " + mLayerExtent->toString(), 2 ); |
3958 |
if ( !elevationProperties()->containsElevationData() )
|
3959 |
{ |
3960 |
// TODO: add a QgsBox3D::force2D method
|
3961 |
mLayerExtent->setZMinimum( std::numeric_limits<double>::quiet_NaN() );
|
3962 |
mLayerExtent->setZMaximum( std::numeric_limits<double>::quiet_NaN() );
|
3963 |
QgsDebugMsgLevel( "Removed Z from extent as layer is configured to not have elevation properties", 2 ); |
3964 |
} |
3965 |
return true; |
3966 |
} |
3967 |
|
3968 |
// Try the BOX2D format
|
3969 |
const thread_local QRegularExpression rx2d( "\\((.+) (.+),(.+) (.+)\\)" ); |
3970 |
match = rx2d.match( ext ); |
3971 |
if ( match.hasMatch() )
|
3972 |
{ |
3973 |
mLayerExtent.emplace( |
3974 |
match.captured( 1 ).toDouble(), // xmin |
3975 |
match.captured( 2 ).toDouble(), // ymin |
3976 |
std::numeric_limits<double>::quiet_NaN(), // zmin |
3977 |
match.captured( 3 ).toDouble(), // xmax |
3978 |
match.captured( 4 ).toDouble(), // ymax |
3979 |
std::numeric_limits<double>::quiet_NaN() // zmax |
3980 |
); |
3981 |
QgsDebugMsgLevel( "Set extents to computed 2D value: " + mLayerExtent->toString(), 2 ); |
3982 |
return true; |
3983 |
} |
3984 |
|
3985 |
QgsMessageLog::logMessage( tr( "Unexpected result from extent query %1: %2" ).arg( sql, ext ), tr( "PostGIS" ) ); |
3986 |
return false; |
3987 |
} |
3988 |
|
3989 |
QgsBox3D QgsPostgresProvider::extent3D() const
|
3990 |
{ |
3991 |
if ( !isValid() || mGeometryColumn.isNull() )
|
3992 |
return QgsBox3D();
|
3993 |
|
3994 |
if ( mLayerExtent.has_value() )
|
3995 |
return *mLayerExtent;
|
3996 |
|
3997 |
// Return the estimated extents, if requested and possible
|
3998 |
if ( mUseEstimatedMetadata )
|
3999 |
estimateExtent(); |
4000 |
|
4001 |
// Compute the extents, if estimation failed or was disabled
|
4002 |
if ( !mLayerExtent.has_value() )
|
4003 |
computeExtent3D(); |
4004 |
|
4005 |
if ( mLayerExtent.has_value() )
|
4006 |
{ |
4007 |
return *mLayerExtent;
|
4008 |
} |
4009 |
else
|
4010 |
{ |
4011 |
pushError( tr( "Could not extract layer extent" ) );
|
4012 |
return QgsBox3D();
|
4013 |
} |
4014 |
} |
4015 |
|
4016 |
void QgsPostgresProvider::updateExtents()
|
4017 |
{ |
4018 |
mLayerExtent.reset(); |
4019 |
} |
4020 |
|
4021 |
bool QgsPostgresProvider::getGeometryDetails()
|
4022 |
{ |
4023 |
if ( mGeometryColumn.isNull() )
|
4024 |
{ |
4025 |
mDetectedGeomType = Qgis::WkbType::NoGeometry; |
4026 |
mValid = true;
|
4027 |
return true; |
4028 |
} |
4029 |
|
4030 |
QgsPostgresResult result; |
4031 |
QString sql; |
4032 |
|
4033 |
QString schemaName = mSchemaName; |
4034 |
QString tableName = mTableName; |
4035 |
QString geomCol = mGeometryColumn; |
4036 |
QString geomColType; |
4037 |
|
4038 |
// Trust the datasource config means that we used requested geometry type and srid
|
4039 |
// We only need to get the spatial column type
|
4040 |
if ( ( mReadFlags & Qgis::DataProviderReadFlag::TrustDataSource ) && mRequestedGeomType != Qgis::WkbType::Unknown && !mRequestedSrid.isEmpty() )
|
4041 |
{ |
4042 |
if ( mIsQuery )
|
4043 |
{ |
4044 |
sql = QStringLiteral( |
4045 |
"SELECT t.typname FROM pg_type t inner join (SELECT pg_typeof(%1) typeof FROM %2 LIMIT 1) g ON oid = g.typeof"
|
4046 |
) |
4047 |
.arg( quotedIdentifier( geomCol ), mQuery ); |
4048 |
} |
4049 |
else
|
4050 |
{ |
4051 |
sql = QStringLiteral( |
4052 |
"SELECT t.typname FROM pg_type t inner join (SELECT pg_typeof(%1) typeof FROM %2.%3 LIMIT 1) g ON oid = g.typeof"
|
4053 |
) |
4054 |
.arg( quotedIdentifier( geomCol ), quotedIdentifier( schemaName ), quotedIdentifier( tableName ) ); |
4055 |
} |
4056 |
QgsDebugMsgLevel( QStringLiteral( "Getting the spatial column type: %1" ).arg( sql ), 2 ); |
4057 |
|
4058 |
result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
4059 |
if ( PGRES_TUPLES_OK == result.PQresultStatus() )
|
4060 |
{ |
4061 |
geomColType = result.PQgetvalue( 0, 0 ); |
4062 |
|
4063 |
// Get spatial col type
|
4064 |
if ( geomColType == QLatin1String( "geometry" ) ) |
4065 |
mSpatialColType = SctGeometry; |
4066 |
else if ( geomColType == QLatin1String( "geography" ) ) |
4067 |
mSpatialColType = SctGeography; |
4068 |
else if ( geomColType == QLatin1String( "topogeometry" ) ) |
4069 |
mSpatialColType = SctTopoGeometry; |
4070 |
else if ( geomColType == QLatin1String( "pcpatch" ) ) |
4071 |
mSpatialColType = SctPcPatch; |
4072 |
else
|
4073 |
mSpatialColType = SctNone; |
4074 |
|
4075 |
// Use requested geometry type and srid
|
4076 |
mDetectedGeomType = mRequestedGeomType; |
4077 |
mDetectedSrid = mRequestedSrid; |
4078 |
mValid = true;
|
4079 |
return true; |
4080 |
} |
4081 |
else
|
4082 |
{ |
4083 |
mValid = false;
|
4084 |
return false; |
4085 |
} |
4086 |
} |
4087 |
|
4088 |
if ( mIsQuery )
|
4089 |
{ |
4090 |
sql = QStringLiteral( "SELECT %1 FROM %2 LIMIT 0" ).arg( quotedIdentifier( mGeometryColumn ), mQuery );
|
4091 |
|
4092 |
QgsDebugMsgLevel( QStringLiteral( "Getting geometry column: %1" ).arg( sql ), 2 ); |
4093 |
|
4094 |
QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
4095 |
if ( PGRES_TUPLES_OK == result.PQresultStatus() )
|
4096 |
{ |
4097 |
Oid tableoid = result.PQftable( 0 );
|
4098 |
int column = result.PQftablecol( 0 ); |
4099 |
|
4100 |
result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
4101 |
if ( tableoid > 0 && PGRES_TUPLES_OK == result.PQresultStatus() ) |
4102 |
{ |
4103 |
sql = QStringLiteral( "SELECT pg_namespace.nspname,pg_class.relname FROM pg_class,pg_namespace WHERE pg_class.relnamespace=pg_namespace.oid AND pg_class.oid=%1" ).arg( tableoid );
|
4104 |
result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
4105 |
|
4106 |
if ( PGRES_TUPLES_OK == result.PQresultStatus() && 1 == result.PQntuples() ) |
4107 |
{ |
4108 |
schemaName = result.PQgetvalue( 0, 0 ); |
4109 |
tableName = result.PQgetvalue( 0, 1 ); |
4110 |
|
4111 |
sql = QStringLiteral( "SELECT a.attname, t.typname FROM pg_attribute a, pg_type t WHERE a.attrelid=%1 AND a.attnum=%2 AND a.atttypid = t.oid" ).arg( tableoid ).arg( column );
|
4112 |
result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
4113 |
if ( PGRES_TUPLES_OK == result.PQresultStatus() && 1 == result.PQntuples() ) |
4114 |
{ |
4115 |
geomCol = result.PQgetvalue( 0, 0 ); |
4116 |
geomColType = result.PQgetvalue( 0, 1 ); |
4117 |
if ( geomColType == QLatin1String( "geometry" ) ) |
4118 |
mSpatialColType = SctGeometry; |
4119 |
else if ( geomColType == QLatin1String( "geography" ) ) |
4120 |
mSpatialColType = SctGeography; |
4121 |
else if ( geomColType == QLatin1String( "topogeometry" ) ) |
4122 |
mSpatialColType = SctTopoGeometry; |
4123 |
else if ( geomColType == QLatin1String( "pcpatch" ) ) |
4124 |
mSpatialColType = SctPcPatch; |
4125 |
else
|
4126 |
mSpatialColType = SctNone; |
4127 |
} |
4128 |
else
|
4129 |
{ |
4130 |
schemaName = mSchemaName; |
4131 |
tableName = mTableName; |
4132 |
} |
4133 |
} |
4134 |
} |
4135 |
else
|
4136 |
{ |
4137 |
schemaName.clear(); |
4138 |
tableName = mQuery; |
4139 |
} |
4140 |
} |
4141 |
else
|
4142 |
{ |
4143 |
mValid = false;
|
4144 |
return false; |
4145 |
} |
4146 |
} |
4147 |
|
4148 |
QString detectedType; |
4149 |
QString detectedSrid; |
4150 |
if ( !schemaName.isEmpty() )
|
4151 |
{ |
4152 |
// check geometry columns
|
4153 |
sql = QStringLiteral( "SELECT upper(type),srid,coord_dimension FROM geometry_columns WHERE f_table_name=%1 AND f_geometry_column=%2 AND f_table_schema=%3" )
|
4154 |
.arg( quotedValue( tableName ), quotedValue( geomCol ), quotedValue( schemaName ) ); |
4155 |
|
4156 |
QgsDebugMsgLevel( QStringLiteral( "Getting geometry column: %1" ).arg( sql ), 2 ); |
4157 |
result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
4158 |
QgsDebugMsgLevel( QStringLiteral( "Geometry column query returned %1 rows" ).arg( result.PQntuples() ), 2 ); |
4159 |
|
4160 |
if ( result.PQntuples() == 1 ) |
4161 |
{ |
4162 |
detectedType = result.PQgetvalue( 0, 0 ); |
4163 |
|
4164 |
// Do not override the SRID if set in the data source URI
|
4165 |
if ( detectedSrid.isEmpty() )
|
4166 |
{ |
4167 |
detectedSrid = result.PQgetvalue( 0, 1 ); |
4168 |
} |
4169 |
|
4170 |
QString dim = result.PQgetvalue( 0, 2 ); |
4171 |
if ( dim == QLatin1String( "3" ) && !detectedType.endsWith( 'M' ) ) |
4172 |
detectedType += QLatin1Char( 'Z' );
|
4173 |
else if ( dim == QLatin1String( "4" ) ) |
4174 |
detectedType += QLatin1String( "ZM" );
|
4175 |
|
4176 |
QString ds = result.PQgetvalue( 0, 1 ); |
4177 |
if ( ds != QLatin1String( "0" ) ) |
4178 |
detectedSrid = ds; |
4179 |
mSpatialColType = SctGeometry; |
4180 |
} |
4181 |
else
|
4182 |
{ |
4183 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4184 |
} |
4185 |
|
4186 |
if ( detectedType.isEmpty() )
|
4187 |
{ |
4188 |
// check geography columns
|
4189 |
sql = QStringLiteral( "SELECT upper(type),srid FROM geography_columns WHERE f_table_name=%1 AND f_geography_column=%2 AND f_table_schema=%3" )
|
4190 |
.arg( quotedValue( tableName ), quotedValue( geomCol ), quotedValue( schemaName ) ); |
4191 |
|
4192 |
QgsDebugMsgLevel( QStringLiteral( "Getting geography column: %1" ).arg( sql ), 2 ); |
4193 |
result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql );
|
4194 |
QgsDebugMsgLevel( QStringLiteral( "Geography column query returned %1" ).arg( result.PQntuples() ), 2 ); |
4195 |
|
4196 |
if ( result.PQntuples() == 1 ) |
4197 |
{ |
4198 |
QString dt = result.PQgetvalue( 0, 0 ); |
4199 |
if ( dt != "GEOMETRY" ) |
4200 |
detectedType = dt; |
4201 |
QString ds = result.PQgetvalue( 0, 1 ); |
4202 |
if ( ds != "0" ) |
4203 |
detectedSrid = ds; |
4204 |
mSpatialColType = SctGeography; |
4205 |
} |
4206 |
else
|
4207 |
{ |
4208 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4209 |
} |
4210 |
} |
4211 |
|
4212 |
if ( detectedType.isEmpty() && connectionRO()->hasTopology() )
|
4213 |
{ |
4214 |
// check topology.layer
|
4215 |
sql = QString( "SELECT CASE "
|
4216 |
"WHEN l.feature_type = 1 THEN 'MULTIPOINT' "
|
4217 |
"WHEN l.feature_type = 2 THEN 'MULTILINESTRING' "
|
4218 |
"WHEN l.feature_type = 3 THEN 'MULTIPOLYGON' "
|
4219 |
"WHEN l.feature_type = 4 THEN 'GEOMETRYCOLLECTION' "
|
4220 |
"END AS type, t.srid FROM topology.layer l, topology.topology t "
|
4221 |
"WHERE l.topology_id = t.id AND l.schema_name=%3 "
|
4222 |
"AND l.table_name=%1 AND l.feature_column=%2" )
|
4223 |
.arg( quotedValue( tableName ), quotedValue( geomCol ), quotedValue( schemaName ) ); |
4224 |
|
4225 |
QgsDebugMsgLevel( QStringLiteral( "Getting TopoGeometry column: %1" ).arg( sql ), 2 ); |
4226 |
result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql );
|
4227 |
QgsDebugMsgLevel( QStringLiteral( "TopoGeometry column query returned %1" ).arg( result.PQntuples() ), 2 ); |
4228 |
|
4229 |
if ( result.PQntuples() == 1 ) |
4230 |
{ |
4231 |
detectedType = result.PQgetvalue( 0, 0 ); |
4232 |
detectedSrid = result.PQgetvalue( 0, 1 ); |
4233 |
mSpatialColType = SctTopoGeometry; |
4234 |
} |
4235 |
else
|
4236 |
{ |
4237 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4238 |
} |
4239 |
} |
4240 |
|
4241 |
if ( detectedType.isEmpty() && connectionRO()->hasPointcloud() )
|
4242 |
{ |
4243 |
// check pointcloud columns
|
4244 |
sql = QStringLiteral( "SELECT 'POLYGON',srid FROM pointcloud_columns WHERE \"table\"=%1 AND \"column\"=%2 AND \"schema\"=%3" )
|
4245 |
.arg( quotedValue( tableName ), quotedValue( geomCol ), quotedValue( schemaName ) ); |
4246 |
|
4247 |
QgsDebugMsgLevel( QStringLiteral( "Getting pointcloud column: %1" ).arg( sql ), 2 ); |
4248 |
result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql );
|
4249 |
QgsDebugMsgLevel( QStringLiteral( "Pointcloud column query returned %1" ).arg( result.PQntuples() ), 2 ); |
4250 |
|
4251 |
if ( result.PQntuples() == 1 ) |
4252 |
{ |
4253 |
detectedType = result.PQgetvalue( 0, 0 ); |
4254 |
detectedSrid = result.PQgetvalue( 0, 1 ); |
4255 |
mSpatialColType = SctPcPatch; |
4256 |
} |
4257 |
else
|
4258 |
{ |
4259 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4260 |
} |
4261 |
} |
4262 |
|
4263 |
if ( mSpatialColType == SctNone )
|
4264 |
{ |
4265 |
sql = QString( "SELECT t.typname FROM "
|
4266 |
"pg_attribute a, pg_class c, pg_namespace n, pg_type t "
|
4267 |
"WHERE a.attrelid=c.oid AND c.relnamespace=n.oid "
|
4268 |
"AND a.atttypid=t.oid "
|
4269 |
"AND n.nspname=%3 AND c.relname=%1 AND a.attname=%2" )
|
4270 |
.arg( quotedValue( tableName ), quotedValue( geomCol ), quotedValue( schemaName ) ); |
4271 |
QgsDebugMsgLevel( QStringLiteral( "Getting column datatype: %1" ).arg( sql ), 2 ); |
4272 |
result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql );
|
4273 |
QgsDebugMsgLevel( QStringLiteral( "Column datatype query returned %1" ).arg( result.PQntuples() ), 2 ); |
4274 |
if ( result.PQntuples() == 1 ) |
4275 |
{ |
4276 |
geomColType = result.PQgetvalue( 0, 0 ); |
4277 |
if ( geomColType == QLatin1String( "geometry" ) ) |
4278 |
mSpatialColType = SctGeometry; |
4279 |
else if ( geomColType == QLatin1String( "geography" ) ) |
4280 |
mSpatialColType = SctGeography; |
4281 |
else if ( geomColType == QLatin1String( "topogeometry" ) ) |
4282 |
mSpatialColType = SctTopoGeometry; |
4283 |
else if ( geomColType == QLatin1String( "pcpatch" ) ) |
4284 |
mSpatialColType = SctPcPatch; |
4285 |
} |
4286 |
else
|
4287 |
{ |
4288 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4289 |
} |
4290 |
} |
4291 |
} |
4292 |
else
|
4293 |
{ |
4294 |
sql = QStringLiteral( "SELECT %1 FROM %2 LIMIT 0" ).arg( quotedIdentifier( mGeometryColumn ), mQuery );
|
4295 |
result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql );
|
4296 |
if ( PGRES_TUPLES_OK == result.PQresultStatus() )
|
4297 |
{ |
4298 |
sql = QStringLiteral( "SELECT (SELECT t.typname FROM pg_type t WHERE oid = %1), upper(postgis_typmod_type(%2)), postgis_typmod_srid(%2)" )
|
4299 |
.arg( QString::number( result.PQftype( 0 ) ), QString::number( result.PQfmod( 0 ) ) ); |
4300 |
result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql );
|
4301 |
if ( result.PQntuples() == 1 ) |
4302 |
{ |
4303 |
geomColType = result.PQgetvalue( 0, 0 ); |
4304 |
detectedType = result.PQgetvalue( 0, 1 ); |
4305 |
detectedSrid = result.PQgetvalue( 0, 2 ); |
4306 |
if ( geomColType == QLatin1String( "geometry" ) ) |
4307 |
mSpatialColType = SctGeometry; |
4308 |
else if ( geomColType == QLatin1String( "geography" ) ) |
4309 |
mSpatialColType = SctGeography; |
4310 |
else if ( geomColType == QLatin1String( "topogeometry" ) ) |
4311 |
mSpatialColType = SctTopoGeometry; |
4312 |
else if ( geomColType == QLatin1String( "pcpatch" ) ) |
4313 |
mSpatialColType = SctPcPatch; |
4314 |
else
|
4315 |
{ |
4316 |
detectedType = mRequestedGeomType == Qgis::WkbType::Unknown ? QString() : QgsPostgresConn::postgisWkbTypeName( mRequestedGeomType ); |
4317 |
detectedSrid = mRequestedSrid; |
4318 |
} |
4319 |
} |
4320 |
else
|
4321 |
{ |
4322 |
connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4323 |
detectedType = mRequestedGeomType == Qgis::WkbType::Unknown ? QString() : QgsPostgresConn::postgisWkbTypeName( mRequestedGeomType ); |
4324 |
} |
4325 |
} |
4326 |
else
|
4327 |
{ |
4328 |
mValid = false;
|
4329 |
return false; |
4330 |
} |
4331 |
} |
4332 |
|
4333 |
mDetectedGeomType = QgsPostgresConn::wkbTypeFromPostgis( detectedType ); |
4334 |
mDetectedSrid = detectedSrid; |
4335 |
|
4336 |
if ( mDetectedGeomType == Qgis::WkbType::Unknown )
|
4337 |
{ |
4338 |
QgsPostgresLayerProperty layerProperty; |
4339 |
if ( !mIsQuery )
|
4340 |
{ |
4341 |
layerProperty.schemaName = schemaName; |
4342 |
layerProperty.tableName = tableName; |
4343 |
} |
4344 |
else
|
4345 |
{ |
4346 |
layerProperty.schemaName.clear(); |
4347 |
layerProperty.tableName = mQuery; |
4348 |
} |
4349 |
layerProperty.geometryColName = mGeometryColumn; |
4350 |
layerProperty.geometryColType = mSpatialColType; |
4351 |
|
4352 |
QString delim; |
4353 |
|
4354 |
if ( !mSqlWhereClause.isEmpty() )
|
4355 |
{ |
4356 |
layerProperty.sql += delim + '(' + mSqlWhereClause + ')'; |
4357 |
delim = QStringLiteral( " AND " );
|
4358 |
} |
4359 |
|
4360 |
connectionRO()->retrieveLayerTypes( layerProperty, mUseEstimatedMetadata ); |
4361 |
|
4362 |
mSpatialColType = layerProperty.geometryColType; |
4363 |
|
4364 |
if ( layerProperty.size() == 0 ) |
4365 |
{ |
4366 |
// no data - so take what's requested/detected
|
4367 |
if ( mRequestedGeomType == Qgis::WkbType::Unknown || mDetectedSrid.isEmpty() )
|
4368 |
{ |
4369 |
QgsMessageLog::logMessage( tr( "Geometry type and srid for empty column %1 of %2 undefined." ).arg( mGeometryColumn, mQuery ) );
|
4370 |
} |
4371 |
} |
4372 |
else
|
4373 |
{ |
4374 |
int i;
|
4375 |
for ( i = 0; i < layerProperty.size(); i++ ) |
4376 |
{ |
4377 |
Qgis::WkbType wkbType = layerProperty.types.at( i ); |
4378 |
|
4379 |
if ( ( wkbType != Qgis::WkbType::Unknown && ( mRequestedGeomType == Qgis::WkbType::Unknown || mRequestedGeomType == wkbType ) ) && ( mRequestedSrid.isEmpty() || layerProperty.srids.at( i ) == mRequestedSrid.toInt() ) )
|
4380 |
break;
|
4381 |
} |
4382 |
|
4383 |
// requested type && srid is available
|
4384 |
if ( i < layerProperty.size() )
|
4385 |
{ |
4386 |
if ( layerProperty.size() == 1 ) |
4387 |
{ |
4388 |
// only what we requested is available
|
4389 |
mDetectedGeomType = layerProperty.types.at( 0 );
|
4390 |
mDetectedSrid = QString::number( layerProperty.srids.at( 0 ) );
|
4391 |
} |
4392 |
} |
4393 |
else
|
4394 |
{ |
4395 |
// geometry type undetermined or not unrequested
|
4396 |
QgsMessageLog::logMessage( tr( "Feature type or srid for %1 of %2 could not be determined or was not requested." ).arg( mGeometryColumn, mQuery ) );
|
4397 |
} |
4398 |
} |
4399 |
} |
4400 |
|
4401 |
QgsDebugMsgLevel( QStringLiteral( "Detected SRID is %1" ).arg( mDetectedSrid ), 2 ); |
4402 |
QgsDebugMsgLevel( QStringLiteral( "Requested SRID is %1" ).arg( mRequestedSrid ), 2 ); |
4403 |
QgsDebugMsgLevel( QStringLiteral( "Detected type is %1" ).arg( qgsEnumValueToKey( mDetectedGeomType ) ), 2 ); |
4404 |
QgsDebugMsgLevel( QStringLiteral( "Requested type is %1" ).arg( qgsEnumValueToKey( mRequestedGeomType ) ), 2 ); |
4405 |
|
4406 |
mValid = ( mDetectedGeomType != Qgis::WkbType::Unknown || mRequestedGeomType != Qgis::WkbType::Unknown ) |
4407 |
&& ( !mDetectedSrid.isEmpty() || !mRequestedSrid.isEmpty() ); |
4408 |
|
4409 |
if ( !mValid )
|
4410 |
return false; |
4411 |
|
4412 |
QgsDebugMsgLevel( QStringLiteral( "Spatial column type is %1" ).arg( QgsPostgresConn::displayStringForGeomType( mSpatialColType ) ), 2 ); |
4413 |
|
4414 |
return mValid;
|
4415 |
} |
4416 |
|
4417 |
bool QgsPostgresProvider::convertField( QgsField &field, const QMap<QString, QVariant> *options ) |
4418 |
{ |
4419 |
//determine field type to use for strings
|
4420 |
QString stringFieldType = QStringLiteral( "varchar" );
|
4421 |
if ( options && options->value( QStringLiteral( "dropStringConstraints" ), false ).toBool() ) |
4422 |
{ |
4423 |
//drop string length constraints by using PostgreSQL text type for strings
|
4424 |
stringFieldType = QStringLiteral( "text" );
|
4425 |
} |
4426 |
|
4427 |
QString fieldType = stringFieldType; //default to string
|
4428 |
int fieldSize = field.length();
|
4429 |
int fieldPrec = field.precision();
|
4430 |
switch ( field.type() )
|
4431 |
{ |
4432 |
case QMetaType::Type::LongLong:
|
4433 |
fieldType = QStringLiteral( "int8" );
|
4434 |
fieldPrec = 0;
|
4435 |
break;
|
4436 |
|
4437 |
case QMetaType::Type::QDateTime:
|
4438 |
fieldType = QStringLiteral( "timestamp without time zone" );
|
4439 |
break;
|
4440 |
|
4441 |
case QMetaType::Type::QTime:
|
4442 |
fieldType = QStringLiteral( "time" );
|
4443 |
break;
|
4444 |
|
4445 |
case QMetaType::Type::QString:
|
4446 |
fieldType = stringFieldType; |
4447 |
fieldPrec = 0;
|
4448 |
break;
|
4449 |
|
4450 |
case QMetaType::Type::Int:
|
4451 |
fieldType = QStringLiteral( "int4" );
|
4452 |
fieldPrec = 0;
|
4453 |
break;
|
4454 |
|
4455 |
case QMetaType::Type::QDate:
|
4456 |
fieldType = QStringLiteral( "date" );
|
4457 |
fieldPrec = 0;
|
4458 |
break;
|
4459 |
|
4460 |
case QMetaType::Type::QVariantMap:
|
4461 |
fieldType = field.typeName(); |
4462 |
if ( fieldType.isEmpty() )
|
4463 |
fieldType = QStringLiteral( "hstore" );
|
4464 |
fieldPrec = 0;
|
4465 |
break;
|
4466 |
|
4467 |
case QMetaType::Type::QStringList:
|
4468 |
fieldType = QStringLiteral( "_text" );
|
4469 |
fieldPrec = 0;
|
4470 |
break;
|
4471 |
|
4472 |
case QMetaType::Type::QVariantList:
|
4473 |
{ |
4474 |
QgsField sub( QString(), field.subType(), QString(), fieldSize, fieldPrec ); |
4475 |
if ( !convertField( sub, nullptr ) )
|
4476 |
return false; |
4477 |
fieldType = "_" + sub.typeName();
|
4478 |
fieldPrec = 0;
|
4479 |
break;
|
4480 |
} |
4481 |
|
4482 |
case QMetaType::Type::Double:
|
4483 |
if ( fieldSize > 18 ) |
4484 |
{ |
4485 |
fieldType = QStringLiteral( "numeric" );
|
4486 |
fieldSize = -1;
|
4487 |
} |
4488 |
else
|
4489 |
{ |
4490 |
fieldType = QStringLiteral( "float8" );
|
4491 |
} |
4492 |
fieldPrec = 0;
|
4493 |
break;
|
4494 |
|
4495 |
case QMetaType::Type::Bool:
|
4496 |
fieldType = QStringLiteral( "bool" );
|
4497 |
fieldPrec = 0;
|
4498 |
fieldSize = -1;
|
4499 |
break;
|
4500 |
|
4501 |
case QMetaType::Type::QByteArray:
|
4502 |
fieldType = QStringLiteral( "bytea" );
|
4503 |
fieldPrec = 0;
|
4504 |
break;
|
4505 |
|
4506 |
default:
|
4507 |
return false; |
4508 |
} |
4509 |
|
4510 |
field.setTypeName( fieldType ); |
4511 |
field.setLength( fieldSize ); |
4512 |
field.setPrecision( fieldPrec ); |
4513 |
return true; |
4514 |
} |
4515 |
|
4516 |
|
4517 |
void postgisGeometryType( Qgis::WkbType wkbType, QString &geometryType, int &dim ) |
4518 |
{ |
4519 |
dim = 2;
|
4520 |
Qgis::WkbType flatType = QgsWkbTypes::flatType( wkbType ); |
4521 |
geometryType = QgsWkbTypes::displayString( flatType ).toUpper(); |
4522 |
switch ( flatType )
|
4523 |
{ |
4524 |
case Qgis::WkbType::Unknown:
|
4525 |
geometryType = QStringLiteral( "GEOMETRY" );
|
4526 |
break;
|
4527 |
|
4528 |
case Qgis::WkbType::NoGeometry:
|
4529 |
geometryType.clear(); |
4530 |
dim = 0;
|
4531 |
break;
|
4532 |
|
4533 |
default:
|
4534 |
break;
|
4535 |
} |
4536 |
|
4537 |
if ( QgsWkbTypes::hasZ( wkbType ) && QgsWkbTypes::hasM( wkbType ) )
|
4538 |
{ |
4539 |
dim = 4;
|
4540 |
} |
4541 |
else if ( QgsWkbTypes::hasZ( wkbType ) ) |
4542 |
{ |
4543 |
dim = 3;
|
4544 |
} |
4545 |
else if ( QgsWkbTypes::hasM( wkbType ) ) |
4546 |
{ |
4547 |
geometryType += QLatin1Char( 'M' );
|
4548 |
dim = 3;
|
4549 |
} |
4550 |
else if ( wkbType >= Qgis::WkbType::Point25D && wkbType <= Qgis::WkbType::MultiPolygon25D ) |
4551 |
{ |
4552 |
dim = 3;
|
4553 |
} |
4554 |
} |
4555 |
|
4556 |
Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &uri, const QgsFields &fields, Qgis::WkbType wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, QMap<int, int> *oldToNewAttrIdxMap, QString *errorMessage, const QMap<QString, QVariant> *options ) |
4557 |
{ |
4558 |
// populate members from the uri structure
|
4559 |
QgsDataSourceUri dsUri( uri ); |
4560 |
|
4561 |
QString schemaName = dsUri.schema(); |
4562 |
QString tableName = dsUri.table(); |
4563 |
|
4564 |
QString geometryColumn = dsUri.geometryColumn(); |
4565 |
QString geometryType; |
4566 |
|
4567 |
QString primaryKey = dsUri.keyColumn(); |
4568 |
QString primaryKeyType; |
4569 |
|
4570 |
QStringList pkList; |
4571 |
QStringList pkType; |
4572 |
|
4573 |
QString schemaTableName; |
4574 |
if ( !schemaName.isEmpty() )
|
4575 |
{ |
4576 |
schemaTableName += quotedIdentifier( schemaName ) + '.';
|
4577 |
} |
4578 |
schemaTableName += quotedIdentifier( tableName ); |
4579 |
|
4580 |
QgsDebugMsgLevel( QStringLiteral( "Connection info is: %1" ).arg( dsUri.connectionInfo( false ) ), 2 ); |
4581 |
QgsDebugMsgLevel( QStringLiteral( "Geometry column is: %1" ).arg( geometryColumn ), 2 ); |
4582 |
QgsDebugMsgLevel( QStringLiteral( "Schema is: %1" ).arg( schemaName ), 2 ); |
4583 |
QgsDebugMsgLevel( QStringLiteral( "Table name is: %1" ).arg( tableName ), 2 ); |
4584 |
|
4585 |
// create the table
|
4586 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false );
|
4587 |
if ( !conn )
|
4588 |
{ |
4589 |
if ( errorMessage )
|
4590 |
*errorMessage = QObject::tr( "Connection to database failed" );
|
4591 |
return Qgis::VectorExportResult::ErrorConnectionFailed;
|
4592 |
} |
4593 |
|
4594 |
// get the pk's name and type
|
4595 |
// Try to find a PK candidate from numeric NOT NULL / UNIQUE columns
|
4596 |
if ( primaryKey.isEmpty() )
|
4597 |
{ |
4598 |
for ( const auto &field : std::as_const( fields ) ) |
4599 |
{ |
4600 |
if ( field.isNumeric() && ( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintUnique ) && ( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull ) && ( field.constraints().constraints() & QgsFieldConstraints::ConstraintOrigin::ConstraintOriginProvider ) )
|
4601 |
{ |
4602 |
primaryKey = field.name(); |
4603 |
break;
|
4604 |
} |
4605 |
} |
4606 |
} |
4607 |
|
4608 |
// if no pk name was passed or guessed, define the new pk field name
|
4609 |
if ( primaryKey.isEmpty() )
|
4610 |
{ |
4611 |
int index = 0; |
4612 |
QString pk = primaryKey = QStringLiteral( "id" );
|
4613 |
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx ) |
4614 |
{ |
4615 |
if ( fields.at( fldIdx ).name() == primaryKey )
|
4616 |
{ |
4617 |
// it already exists, try again with a new name
|
4618 |
primaryKey = QStringLiteral( "%1_%2" ).arg( pk ).arg( index++ );
|
4619 |
fldIdx = -1; // it is incremented in the for loop, i.e. restarts at 0 |
4620 |
} |
4621 |
} |
4622 |
|
4623 |
pkList = QStringList( primaryKey ); |
4624 |
pkType = QStringList( QStringLiteral( "serial" ) );
|
4625 |
} |
4626 |
else
|
4627 |
{ |
4628 |
pkList = parseUriKey( primaryKey ); |
4629 |
const auto constPkList = pkList; |
4630 |
const bool lowercaseFieldNames = options && options->value( QStringLiteral( "lowercaseFieldNames" ), false ).toBool(); |
4631 |
for ( const QString &col : constPkList ) |
4632 |
{ |
4633 |
// search for the passed field
|
4634 |
QString type; |
4635 |
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx ) |
4636 |
{ |
4637 |
const QString fieldName = lowercaseFieldNames ? fields[fldIdx].name().toLower() : fields[fldIdx].name();
|
4638 |
if ( fieldName == col )
|
4639 |
{ |
4640 |
// found, get the field type
|
4641 |
QgsField fld = fields[fldIdx]; |
4642 |
if ( convertField( fld, options ) )
|
4643 |
{ |
4644 |
type = fld.typeName(); |
4645 |
break;
|
4646 |
} |
4647 |
} |
4648 |
} |
4649 |
if ( type.isEmpty() )
|
4650 |
type = QStringLiteral( "serial" );
|
4651 |
else
|
4652 |
{ |
4653 |
// if the pk field's type is one of the postgres integer types,
|
4654 |
// use the equivalent autoincremental type (serialN)
|
4655 |
if ( primaryKeyType == QLatin1String( "int2" ) || primaryKeyType == QLatin1String( "int4" ) ) |
4656 |
{ |
4657 |
primaryKeyType = QStringLiteral( "serial" );
|
4658 |
} |
4659 |
else if ( primaryKeyType == QLatin1String( "int8" ) ) |
4660 |
{ |
4661 |
primaryKeyType = QStringLiteral( "serial8" );
|
4662 |
} |
4663 |
} |
4664 |
pkType << type; |
4665 |
} |
4666 |
} |
4667 |
|
4668 |
try
|
4669 |
{ |
4670 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "BEGIN" ) ); |
4671 |
|
4672 |
// We want a valid schema name ...
|
4673 |
if ( schemaName.isEmpty() )
|
4674 |
{ |
4675 |
QString sql = QString( "SELECT current_schema" );
|
4676 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
4677 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
4678 |
throw PGException( result );
|
4679 |
schemaName = result.PQgetvalue( 0, 0 ); |
4680 |
if ( schemaName.isEmpty() )
|
4681 |
{ |
4682 |
schemaName = QStringLiteral( "public" );
|
4683 |
} |
4684 |
} |
4685 |
|
4686 |
QString sql = QString( "SELECT 1"
|
4687 |
" FROM pg_class AS cls JOIN pg_namespace AS nsp"
|
4688 |
" ON nsp.oid=cls.relnamespace "
|
4689 |
" WHERE cls.relname=%1 AND nsp.nspname=%2" )
|
4690 |
.arg( quotedValue( tableName ), quotedValue( schemaName ) ); |
4691 |
|
4692 |
QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
4693 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
4694 |
throw PGException( result );
|
4695 |
|
4696 |
bool exists = result.PQntuples() > 0; |
4697 |
|
4698 |
if ( exists && overwrite )
|
4699 |
{ |
4700 |
// delete the table if exists, then re-create it
|
4701 |
QString sql = QString( "SELECT DropGeometryTable(%1,%2)"
|
4702 |
" FROM pg_class AS cls JOIN pg_namespace AS nsp"
|
4703 |
" ON nsp.oid=cls.relnamespace "
|
4704 |
" WHERE cls.relname=%2 AND nsp.nspname=%1" )
|
4705 |
.arg( quotedValue( schemaName ), quotedValue( tableName ) ); |
4706 |
|
4707 |
result = conn->LoggedPQexec( "QgsPostgresProvider", sql );
|
4708 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
4709 |
throw PGException( result );
|
4710 |
} |
4711 |
|
4712 |
sql = QStringLiteral( "CREATE TABLE %1(" ).arg( schemaTableName );
|
4713 |
QString pk; |
4714 |
for ( int i = 0; i < pkList.size(); ++i ) |
4715 |
{ |
4716 |
QString col = pkList[i]; |
4717 |
const QString &type = pkType[i];
|
4718 |
|
4719 |
if ( options && options->value( QStringLiteral( "lowercaseFieldNames" ), false ).toBool() ) |
4720 |
{ |
4721 |
col = col.toLower(); |
4722 |
} |
4723 |
else
|
4724 |
{ |
4725 |
col = quotedIdentifier( col ); // no need to quote lowercase field
|
4726 |
} |
4727 |
|
4728 |
if ( i )
|
4729 |
{ |
4730 |
pk += QLatin1Char( ',' );
|
4731 |
sql += QLatin1Char( ',' );
|
4732 |
} |
4733 |
|
4734 |
pk += col; |
4735 |
sql += col + " " + type;
|
4736 |
} |
4737 |
sql += QStringLiteral( ", PRIMARY KEY (%1) )" ).arg( pk );
|
4738 |
|
4739 |
result = conn->LoggedPQexec( "QgsPostgresProvider", sql );
|
4740 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
4741 |
throw PGException( result );
|
4742 |
|
4743 |
// get geometry type, dim and srid
|
4744 |
int dim = 2; |
4745 |
long srid = srs.postgisSrid();
|
4746 |
|
4747 |
postgisGeometryType( wkbType, geometryType, dim ); |
4748 |
|
4749 |
// create geometry column
|
4750 |
if ( !geometryType.isEmpty() )
|
4751 |
{ |
4752 |
sql = QStringLiteral( "SELECT AddGeometryColumn(%1,%2,%3,%4,%5,%6)" )
|
4753 |
.arg( quotedValue( schemaName ), quotedValue( tableName ), quotedValue( geometryColumn ) ) |
4754 |
.arg( srid ) |
4755 |
.arg( quotedValue( geometryType ) ) |
4756 |
.arg( dim ); |
4757 |
|
4758 |
result = conn->LoggedPQexec( "QgsPostgresProvider", sql );
|
4759 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
4760 |
throw PGException( result );
|
4761 |
} |
4762 |
else
|
4763 |
{ |
4764 |
geometryColumn.clear(); |
4765 |
} |
4766 |
|
4767 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); |
4768 |
} |
4769 |
catch ( PGException &e )
|
4770 |
{ |
4771 |
if ( errorMessage )
|
4772 |
*errorMessage = QObject::tr( "Creation of data source %1 failed: \n%2" )
|
4773 |
.arg( schemaTableName, e.errorMessage() ); |
4774 |
|
4775 |
conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "ROLLBACK" ) ); |
4776 |
conn->unref(); |
4777 |
return Qgis::VectorExportResult::ErrorCreatingLayer;
|
4778 |
} |
4779 |
conn->unref(); |
4780 |
|
4781 |
QgsDebugMsgLevel( QStringLiteral( "layer %1 created" ).arg( schemaTableName ), 2 ); |
4782 |
|
4783 |
// use the provider to edit the table
|
4784 |
dsUri.setDataSource( schemaName, tableName, geometryColumn, QString(), primaryKey ); |
4785 |
|
4786 |
QgsDataProvider::ProviderOptions providerOptions; |
4787 |
Qgis::DataProviderReadFlags flags; |
4788 |
std::unique_ptr<QgsPostgresProvider> provider = std::make_unique<QgsPostgresProvider>( dsUri.uri( false ), providerOptions, flags );
|
4789 |
if ( !provider->isValid() )
|
4790 |
{ |
4791 |
if ( errorMessage )
|
4792 |
*errorMessage = QObject::tr( "Loading of the layer %1 failed" ).arg( schemaTableName );
|
4793 |
|
4794 |
return Qgis::VectorExportResult::ErrorInvalidLayer;
|
4795 |
} |
4796 |
|
4797 |
QgsDebugMsgLevel( QStringLiteral( "layer loaded" ), 2 ); |
4798 |
|
4799 |
// add fields to the layer
|
4800 |
if ( oldToNewAttrIdxMap )
|
4801 |
oldToNewAttrIdxMap->clear(); |
4802 |
|
4803 |
if ( fields.size() > 0 ) |
4804 |
{ |
4805 |
int offset = 1; |
4806 |
|
4807 |
// get the list of fields
|
4808 |
QList<QgsField> flist; |
4809 |
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx ) |
4810 |
{ |
4811 |
QgsField fld = fields.at( fldIdx ); |
4812 |
|
4813 |
if ( fld.name() == geometryColumn )
|
4814 |
{ |
4815 |
//the "lowercaseFieldNames" option does not affect the name of the geometry column, so we perform
|
4816 |
//this test before converting the field name to lowercase
|
4817 |
QgsDebugMsgLevel( QStringLiteral( "Found a field with the same name of the geometry column. Skip it!" ), 2 ); |
4818 |
continue;
|
4819 |
} |
4820 |
|
4821 |
if ( options && options->value( QStringLiteral( "lowercaseFieldNames" ), false ).toBool() ) |
4822 |
{ |
4823 |
//convert field name to lowercase
|
4824 |
fld.setName( fld.name().toLower() ); |
4825 |
} |
4826 |
|
4827 |
int pkIdx = -1; |
4828 |
for ( int i = 0; i < pkList.size(); ++i ) |
4829 |
{ |
4830 |
QString col = pkList[i]; |
4831 |
if ( options && options->value( QStringLiteral( "lowercaseFieldNames" ), false ).toBool() ) |
4832 |
{ |
4833 |
//convert field name to lowercase (TODO: avoid doing this
|
4834 |
//over and over)
|
4835 |
col = col.toLower(); |
4836 |
} |
4837 |
if ( fld.name() == col )
|
4838 |
{ |
4839 |
pkIdx = i; |
4840 |
break;
|
4841 |
} |
4842 |
} |
4843 |
if ( pkIdx >= 0 ) |
4844 |
{ |
4845 |
oldToNewAttrIdxMap->insert( fldIdx, pkIdx ); |
4846 |
continue;
|
4847 |
} |
4848 |
|
4849 |
if ( !( options && options->value( QStringLiteral( "skipConvertFields" ), false ).toBool() ) && !convertField( fld, options ) ) |
4850 |
{ |
4851 |
if ( errorMessage )
|
4852 |
*errorMessage = QObject::tr( "Unsupported type for field %1" ).arg( fld.name() );
|
4853 |
|
4854 |
return Qgis::VectorExportResult::ErrorAttributeTypeUnsupported;
|
4855 |
} |
4856 |
|
4857 |
QgsDebugMsgLevel( QStringLiteral( "creating field #%1 -> #%2 name %3 type %4 typename %5 width %6 precision %7" ).arg( fldIdx ).arg( offset ).arg( fld.name(), QVariant::typeToName( fld.type() ), fld.typeName() ).arg( fld.length() ).arg( fld.precision() ), 2 ); |
4858 |
|
4859 |
flist.append( fld ); |
4860 |
if ( oldToNewAttrIdxMap )
|
4861 |
oldToNewAttrIdxMap->insert( fldIdx, offset++ ); |
4862 |
} |
4863 |
|
4864 |
if ( !provider->addAttributes( flist ) )
|
4865 |
{ |
4866 |
if ( errorMessage )
|
4867 |
*errorMessage = QObject::tr( "Creation of fields failed:\n%1" ).arg( provider->errors().join( '\n' ) ); |
4868 |
|
4869 |
return Qgis::VectorExportResult::ErrorAttributeCreationFailed;
|
4870 |
} |
4871 |
|
4872 |
QgsDebugMsgLevel( QStringLiteral( "Done creating fields" ), 2 ); |
4873 |
} |
4874 |
return Qgis::VectorExportResult::Success;
|
4875 |
} |
4876 |
|
4877 |
QgsCoordinateReferenceSystem QgsPostgresProvider::crs() const
|
4878 |
{ |
4879 |
QgsCoordinateReferenceSystem srs; |
4880 |
int srid = mRequestedSrid.isEmpty() ? mDetectedSrid.toInt() : mRequestedSrid.toInt();
|
4881 |
|
4882 |
return sridToCrs( srid, connectionRO() );
|
4883 |
} |
4884 |
|
4885 |
QString QgsPostgresProvider::subsetString() const
|
4886 |
{ |
4887 |
return mSqlWhereClause;
|
4888 |
} |
4889 |
|
4890 |
QString QgsPostgresProvider::getTableName() |
4891 |
{ |
4892 |
return mTableName;
|
4893 |
} |
4894 |
|
4895 |
size_t QgsPostgresProvider::layerCount() const
|
4896 |
{ |
4897 |
return 1; // XXX need to return actual number of layers |
4898 |
} // QgsPostgresProvider::layerCount()
|
4899 |
|
4900 |
|
4901 |
QString QgsPostgresProvider::name() const
|
4902 |
{ |
4903 |
return POSTGRES_KEY;
|
4904 |
} // QgsPostgresProvider::name()
|
4905 |
|
4906 |
QString QgsPostgresProvider::description() const
|
4907 |
{ |
4908 |
QString pgVersion( tr( "PostgreSQL version: unknown" ) );
|
4909 |
QString postgisVersion( tr( "unknown" ) );
|
4910 |
|
4911 |
if ( auto *lConnectionRO = connectionRO() ) |
4912 |
{ |
4913 |
QgsPostgresResult result; |
4914 |
|
4915 |
/* TODO: expose a cached QgsPostgresConn::version() ? */
|
4916 |
result = lConnectionRO->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT version()" ) ); |
4917 |
if ( result.PQresultStatus() == PGRES_TUPLES_OK )
|
4918 |
{ |
4919 |
pgVersion = result.PQgetvalue( 0, 0 ); |
4920 |
} |
4921 |
|
4922 |
postgisVersion = lConnectionRO->postgisVersion(); |
4923 |
} |
4924 |
else
|
4925 |
{ |
4926 |
pgVersion = tr( "PostgreSQL not connected" );
|
4927 |
} |
4928 |
|
4929 |
return tr( "PostgreSQL/PostGIS provider\n%1\nPostGIS %2" ).arg( pgVersion, postgisVersion ); |
4930 |
} // QgsPostgresProvider::description()
|
4931 |
|
4932 |
static void jumpSpace( const QString &txt, int &i ) |
4933 |
{ |
4934 |
while ( i < txt.length() && txt.at( i ).isSpace() )
|
4935 |
++i; |
4936 |
} |
4937 |
|
4938 |
QString QgsPostgresProvider::getNextString( const QString &txt, int &i, const QString &sep ) |
4939 |
{ |
4940 |
jumpSpace( txt, i ); |
4941 |
if ( i < txt.length() && txt.at( i ) == '"' ) |
4942 |
{ |
4943 |
const thread_local QRegularExpression stringRe( QRegularExpression::anchoredPattern( "^\"((?:\\\\.|[^\"\\\\])*)\".*" ) ); |
4944 |
const QRegularExpressionMatch match = stringRe.match( txt.mid( i ) );
|
4945 |
if ( !match.hasMatch() )
|
4946 |
{ |
4947 |
QgsMessageLog::logMessage( tr( "Cannot find end of double quoted string: %1" ).arg( txt ), tr( "PostGIS" ) ); |
4948 |
return QString();
|
4949 |
} |
4950 |
i += match.captured( 1 ).length() + 2; |
4951 |
jumpSpace( txt, i ); |
4952 |
if ( !QStringView { txt }.mid( i ).startsWith( sep ) && i < txt.length() )
|
4953 |
{ |
4954 |
QgsMessageLog::logMessage( tr( "Cannot find separator: %1" ).arg( txt.mid( i ) ), tr( "PostGIS" ) ); |
4955 |
return QString();
|
4956 |
} |
4957 |
i += sep.length(); |
4958 |
return match.captured( 1 ).replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) ).replace( QLatin1String( "\\\\" ), QLatin1String( "\\" ) ); |
4959 |
} |
4960 |
else
|
4961 |
{ |
4962 |
int start = i;
|
4963 |
for ( ; i < txt.length(); i++ )
|
4964 |
{ |
4965 |
if ( QStringView { txt }.mid( i ).startsWith( sep ) )
|
4966 |
{ |
4967 |
QStringView v( QStringView { txt }.mid( start, i - start ) ); |
4968 |
i += sep.length(); |
4969 |
return v.trimmed().toString();
|
4970 |
} |
4971 |
} |
4972 |
return QStringView { txt }.mid( start, i - start ).trimmed().toString();
|
4973 |
} |
4974 |
} |
4975 |
|
4976 |
QVariant QgsPostgresProvider::parseHstore( const QString &txt )
|
4977 |
{ |
4978 |
QVariantMap result; |
4979 |
int i = 0; |
4980 |
while ( i < txt.length() )
|
4981 |
{ |
4982 |
QString key = getNextString( txt, i, QStringLiteral( "=>" ) );
|
4983 |
QString value = getNextString( txt, i, QStringLiteral( "," ) );
|
4984 |
if ( key.isNull() || value.isNull() )
|
4985 |
{ |
4986 |
QgsMessageLog::logMessage( tr( "Error parsing hstore: %1" ).arg( txt ), tr( "PostGIS" ) ); |
4987 |
break;
|
4988 |
} |
4989 |
result.insert( key, value ); |
4990 |
} |
4991 |
|
4992 |
return result;
|
4993 |
} |
4994 |
|
4995 |
QVariant QgsPostgresProvider::parseJson( const QString &txt )
|
4996 |
{ |
4997 |
return QgsJsonUtils::parseJson( txt );
|
4998 |
} |
4999 |
|
5000 |
QVariant QgsPostgresProvider::parseOtherArray( const QString &txt, QMetaType::Type subType, const QString &typeName, QgsPostgresConn *conn ) |
5001 |
{ |
5002 |
int i = 0; |
5003 |
QVariantList result; |
5004 |
while ( i < txt.length() )
|
5005 |
{ |
5006 |
const QString value = getNextString( txt, i, QStringLiteral( "," ) ); |
5007 |
if ( value.isNull() )
|
5008 |
{ |
5009 |
QgsMessageLog::logMessage( tr( "Error parsing array: %1" ).arg( txt ), tr( "PostGIS" ) ); |
5010 |
break;
|
5011 |
} |
5012 |
result.append( convertValue( subType, QMetaType::Type::UnknownType, value, typeName, conn ) ); |
5013 |
} |
5014 |
return result;
|
5015 |
} |
5016 |
|
5017 |
QVariant QgsPostgresProvider::parseStringArray( const QString &txt )
|
5018 |
{ |
5019 |
int i = 0; |
5020 |
QStringList result; |
5021 |
while ( i < txt.length() )
|
5022 |
{ |
5023 |
const QString value = getNextString( txt, i, QStringLiteral( "," ) ); |
5024 |
if ( value.isNull() )
|
5025 |
{ |
5026 |
QgsMessageLog::logMessage( tr( "Error parsing array: %1" ).arg( txt ), tr( "PostGIS" ) ); |
5027 |
break;
|
5028 |
} |
5029 |
result.append( value ); |
5030 |
} |
5031 |
return result;
|
5032 |
} |
5033 |
|
5034 |
QVariant QgsPostgresProvider::parseMultidimensionalArray( const QString &txt )
|
5035 |
{ |
5036 |
QStringList result; |
5037 |
if ( !txt.startsWith( '{' ) || !txt.endsWith( '}' ) ) |
5038 |
{ |
5039 |
QgsMessageLog::logMessage( tr( "Error parsing array, missing curly braces: %1" ).arg( txt ), tr( "PostGIS" ) ); |
5040 |
return result;
|
5041 |
} |
5042 |
|
5043 |
QStringList values; |
5044 |
QString text = txt; |
5045 |
while ( !text.isEmpty() )
|
5046 |
{ |
5047 |
bool escaped = false; |
5048 |
int openedBrackets = 1; |
5049 |
int i = 0; |
5050 |
while ( i < text.length() && openedBrackets > 0 ) |
5051 |
{ |
5052 |
++i; |
5053 |
|
5054 |
if ( text.at( i ) == '}' && !escaped ) |
5055 |
openedBrackets--; |
5056 |
else if ( text.at( i ) == '{' && !escaped ) |
5057 |
openedBrackets++; |
5058 |
|
5059 |
escaped = !escaped ? text.at( i ) == '\\' : false; |
5060 |
} |
5061 |
|
5062 |
values.append( text.left( ++i ) ); |
5063 |
i = text.indexOf( ',', i );
|
5064 |
i = i > 0 ? text.indexOf( '{', i ) : -1; |
5065 |
if ( i == -1 ) |
5066 |
break;
|
5067 |
|
5068 |
text = text.mid( i ); |
5069 |
} |
5070 |
return values;
|
5071 |
} |
5072 |
|
5073 |
QVariant QgsPostgresProvider::parseArray( const QString &txt, QMetaType::Type type, QMetaType::Type subType, const QString &typeName, QgsPostgresConn *conn ) |
5074 |
{ |
5075 |
if ( !txt.startsWith( '{' ) || !txt.endsWith( '}' ) ) |
5076 |
{ |
5077 |
if ( !txt.isEmpty() )
|
5078 |
QgsMessageLog::logMessage( tr( "Error parsing array, missing curly braces: %1" ).arg( txt ), tr( "PostGIS" ) ); |
5079 |
return QgsVariantUtils::createNullVariant( type );
|
5080 |
} |
5081 |
QString inner = txt.mid( 1, txt.length() - 2 ); |
5082 |
if ( ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList ) && inner.startsWith( "{" ) ) |
5083 |
return parseMultidimensionalArray( inner );
|
5084 |
else if ( type == QMetaType::Type::QStringList ) |
5085 |
return parseStringArray( inner );
|
5086 |
else
|
5087 |
return parseOtherArray( inner, subType, typeName, conn );
|
5088 |
} |
5089 |
|
5090 |
QVariant QgsPostgresProvider::convertValue( QMetaType::Type type, QMetaType::Type subType, const QString &value, const QString &typeName ) const |
5091 |
{ |
5092 |
return convertValue( type, subType, value, typeName, connectionRO() );
|
5093 |
} |
5094 |
|
5095 |
QVariant QgsPostgresProvider::convertValue( QMetaType::Type type, QMetaType::Type subType, const QString &value, const QString &typeName, QgsPostgresConn *conn ) |
5096 |
{ |
5097 |
QVariant result; |
5098 |
switch ( type )
|
5099 |
{ |
5100 |
case QMetaType::Type::QVariantMap:
|
5101 |
if ( typeName == QLatin1String( "json" ) || typeName == QLatin1String( "jsonb" ) ) |
5102 |
result = parseJson( value ); |
5103 |
else
|
5104 |
result = parseHstore( value ); |
5105 |
break;
|
5106 |
case QMetaType::Type::QStringList:
|
5107 |
case QMetaType::Type::QVariantList:
|
5108 |
result = parseArray( value, type, subType, typeName, conn ); |
5109 |
break;
|
5110 |
case QMetaType::Type::Bool:
|
5111 |
if ( value == QChar( 't' ) ) |
5112 |
result = true;
|
5113 |
else if ( value == QChar( 'f' ) ) |
5114 |
result = false;
|
5115 |
else
|
5116 |
result = QgsVariantUtils::createNullVariant( type ); |
5117 |
break;
|
5118 |
case QMetaType::Type::User:
|
5119 |
result = fromEwkt( value, conn ); |
5120 |
break;
|
5121 |
|
5122 |
default:
|
5123 |
result = value; |
5124 |
if ( !result.convert( type ) || value.isNull() )
|
5125 |
result = QgsVariantUtils::createNullVariant( type ); |
5126 |
break;
|
5127 |
} |
5128 |
|
5129 |
return result;
|
5130 |
} |
5131 |
|
5132 |
QList<QgsVectorLayer *> QgsPostgresProvider::searchLayers( const QList<QgsVectorLayer *> &layers, const QString &connectionInfo, const QString &schema, const QString &tableName ) |
5133 |
{ |
5134 |
QList<QgsVectorLayer *> result; |
5135 |
const auto constLayers = layers; |
5136 |
for ( QgsVectorLayer *layer : constLayers )
|
5137 |
{ |
5138 |
const QgsPostgresProvider *pgProvider = qobject_cast<QgsPostgresProvider *>( layer->dataProvider() );
|
5139 |
if ( pgProvider && pgProvider->mUri.connectionInfo( false ) == connectionInfo && pgProvider->mSchemaName == schema && pgProvider->mTableName == tableName ) |
5140 |
{ |
5141 |
result.append( layer ); |
5142 |
} |
5143 |
} |
5144 |
return result;
|
5145 |
} |
5146 |
|
5147 |
QList<QgsRelation> QgsPostgresProvider::discoverRelations( const QgsVectorLayer *target, const QList<QgsVectorLayer *> &layers ) const |
5148 |
{ |
5149 |
QList<QgsRelation> result; |
5150 |
|
5151 |
// Silently skip if this is a query layer or for some obscure reason there are no table and schema name
|
5152 |
if ( mIsQuery || mTableName.isEmpty() || mSchemaName.isEmpty() )
|
5153 |
{ |
5154 |
return result;
|
5155 |
} |
5156 |
|
5157 |
// Skip less silently if layer is not valid
|
5158 |
if ( !mValid )
|
5159 |
{ |
5160 |
QgsLogger::warning( tr( "Error discovering relations of %1: invalid layer" ).arg( mQuery ) );
|
5161 |
return result;
|
5162 |
} |
5163 |
|
5164 |
|
5165 |
QString sql( |
5166 |
"WITH foreign_keys AS "
|
5167 |
" ( SELECT c.conname, "
|
5168 |
" c.conrelid, "
|
5169 |
" c.confrelid, "
|
5170 |
" unnest(c.conkey) AS conkey, "
|
5171 |
" unnest(c.confkey) AS confkey, "
|
5172 |
" (SELECT relname "
|
5173 |
" FROM pg_catalog.pg_class "
|
5174 |
" WHERE oid = c.conrelid) as referencing_table, "
|
5175 |
" (SELECT relname "
|
5176 |
" FROM pg_catalog.pg_class "
|
5177 |
" WHERE oid = c.confrelid) as referenced_table, "
|
5178 |
" (SELECT relnamespace::regnamespace::text "
|
5179 |
" FROM pg_catalog.pg_class "
|
5180 |
" WHERE oid = c.confrelid) as constraint_schema "
|
5181 |
" FROM pg_constraint c "
|
5182 |
" WHERE contype = 'f' "
|
5183 |
" AND c.conrelid::regclass = "
|
5184 |
+ QgsPostgresConn::quotedValue( QString( QgsPostgresConn::quotedIdentifier( mSchemaName ) + '.' + QgsPostgresConn::quotedIdentifier( mTableName ) ) ) + "::regclass ) " |
5185 |
"SELECT fk.conname as constraint_name, "
|
5186 |
" a.attname as column_name, "
|
5187 |
" fk.constraint_schema, "
|
5188 |
" referenced_table as table_name, "
|
5189 |
" af.attname as column_name "
|
5190 |
"FROM foreign_keys fk "
|
5191 |
"JOIN pg_attribute af ON af.attnum = fk.confkey "
|
5192 |
"AND af.attrelid = fk.confrelid "
|
5193 |
"JOIN pg_attribute a ON a.attnum = conkey "
|
5194 |
"AND a.attrelid = fk.conrelid "
|
5195 |
"ORDER BY fk.confrelid, "
|
5196 |
" fk.conname ;"
|
5197 |
); |
5198 |
|
5199 |
QgsPostgresResult sqlResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
5200 |
if ( sqlResult.PQresultStatus() != PGRES_TUPLES_OK )
|
5201 |
{ |
5202 |
QgsLogger::warning( "Error getting the foreign keys of " + mTableName );
|
5203 |
return result;
|
5204 |
} |
5205 |
|
5206 |
QList<QString> refTableFound; |
5207 |
for ( int row = 0; row < sqlResult.PQntuples(); ++row ) |
5208 |
{ |
5209 |
const QString name = sqlResult.PQgetvalue( row, 0 ); |
5210 |
const QString fkColumn = sqlResult.PQgetvalue( row, 1 ); |
5211 |
QString refSchema = sqlResult.PQgetvalue( row, 2 );
|
5212 |
QString refTable = sqlResult.PQgetvalue( row, 3 );
|
5213 |
// Strip quotes
|
5214 |
if ( refTable.startsWith( '"' ) && refTable.endsWith( '"' ) ) |
5215 |
{ |
5216 |
refTable = refTable.mid( 1, refTable.length() - 2 ); |
5217 |
} |
5218 |
if ( refSchema.startsWith( '"' ) && refSchema.endsWith( '"' ) ) |
5219 |
{ |
5220 |
refSchema = refSchema.mid( 1, refSchema.length() - 2 ); |
5221 |
} |
5222 |
const QString refColumn = sqlResult.PQgetvalue( row, 4 ); |
5223 |
// try to find if we have layers for the referenced table
|
5224 |
const QList<QgsVectorLayer *> foundLayers = searchLayers( layers, mUri.connectionInfo( false ), refSchema, refTable ); |
5225 |
if ( !refTableFound.contains( refTable ) )
|
5226 |
{ |
5227 |
for ( const QgsVectorLayer *foundLayer : foundLayers ) |
5228 |
{ |
5229 |
QgsRelation relation; |
5230 |
relation.setName( name ); |
5231 |
relation.setReferencingLayer( target->id() ); |
5232 |
relation.setReferencedLayer( foundLayer->id() ); |
5233 |
relation.addFieldPair( fkColumn, refColumn ); |
5234 |
relation.generateId(); |
5235 |
if ( relation.isValid() )
|
5236 |
{ |
5237 |
result.append( relation ); |
5238 |
refTableFound.append( refTable ); |
5239 |
} |
5240 |
else
|
5241 |
{ |
5242 |
QgsLogger::warning( "Invalid relation for " + name );
|
5243 |
} |
5244 |
} |
5245 |
} |
5246 |
else
|
5247 |
{ |
5248 |
// multi reference field => add the field pair to all the referenced layers found
|
5249 |
const int resultSize = result.size(); |
5250 |
for ( int i = 0; i < resultSize; ++i ) |
5251 |
{ |
5252 |
for ( const QgsVectorLayer *foundLayer : foundLayers ) |
5253 |
{ |
5254 |
if ( result[resultSize - 1 - i].referencedLayerId() == foundLayer->id() ) |
5255 |
{ |
5256 |
result[resultSize - 1 - i].addFieldPair( fkColumn, refColumn );
|
5257 |
} |
5258 |
} |
5259 |
} |
5260 |
} |
5261 |
} |
5262 |
return result;
|
5263 |
} |
5264 |
|
5265 |
void QgsPostgresProvider::setQuery( const QString &query ) |
5266 |
{ |
5267 |
mQuery = query; |
5268 |
|
5269 |
mKind = Qgis::PostgresRelKind::NotSet; |
5270 |
} |
5271 |
|
5272 |
Qgis::PostgresRelKind QgsPostgresProvider::relkind() const
|
5273 |
{ |
5274 |
if ( mKind != Qgis::PostgresRelKind::NotSet )
|
5275 |
return mKind;
|
5276 |
|
5277 |
if ( mIsQuery || !connectionRO() )
|
5278 |
{ |
5279 |
mKind = Qgis::PostgresRelKind::Unknown; |
5280 |
} |
5281 |
else
|
5282 |
{ |
5283 |
QString sql = QStringLiteral( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
|
5284 |
QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
|
5285 |
QString type = res.PQgetvalue( 0, 0 ); |
5286 |
|
5287 |
mKind = QgsPostgresConn::relKindFromValue( type ); |
5288 |
} |
5289 |
|
5290 |
return mKind;
|
5291 |
} |
5292 |
|
5293 |
bool QgsPostgresProvider::hasMetadata() const |
5294 |
{ |
5295 |
bool hasMetadata = true; |
5296 |
Qgis::PostgresRelKind kind = relkind(); |
5297 |
|
5298 |
if ( kind == Qgis::PostgresRelKind::View || kind == Qgis::PostgresRelKind::MaterializedView )
|
5299 |
{ |
5300 |
hasMetadata = false;
|
5301 |
} |
5302 |
|
5303 |
return hasMetadata;
|
5304 |
} |
5305 |
|
5306 |
QgsDataProvider *QgsPostgresProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags ) |
5307 |
{ |
5308 |
return new QgsPostgresProvider( uri, options, flags ); |
5309 |
} |
5310 |
|
5311 |
QList<QgsDataItemProvider *> QgsPostgresProviderMetadata::dataItemProviders() const
|
5312 |
{ |
5313 |
QList<QgsDataItemProvider *> providers; |
5314 |
providers << new QgsPostgresDataItemProvider;
|
5315 |
return providers;
|
5316 |
} |
5317 |
|
5318 |
// ---------------------------------------------------------------------------
|
5319 |
|
5320 |
Qgis::VectorExportResult QgsPostgresProviderMetadata::createEmptyLayer( const QString &uri, const QgsFields &fields, Qgis::WkbType wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, QMap<int, int> &oldToNewAttrIdxMap, QString &errorMessage, const QMap<QString, QVariant> *options ) |
5321 |
{ |
5322 |
return QgsPostgresProvider::createEmptyLayer(
|
5323 |
uri, fields, wkbType, srs, overwrite, |
5324 |
&oldToNewAttrIdxMap, &errorMessage, options |
5325 |
); |
5326 |
} |
5327 |
|
5328 |
bool QgsPostgresProviderMetadata::styleExists( const QString &uri, const QString &styleId, QString &errorCause ) |
5329 |
{ |
5330 |
errorCause.clear(); |
5331 |
|
5332 |
QgsDataSourceUri dsUri( uri ); |
5333 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true );
|
5334 |
if ( !conn )
|
5335 |
{ |
5336 |
errorCause = QObject::tr( "Connection to database failed" );
|
5337 |
return false; |
5338 |
} |
5339 |
|
5340 |
if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) |
5341 |
{ |
5342 |
return false; |
5343 |
} |
5344 |
else if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) |
5345 |
{ |
5346 |
return false; |
5347 |
} |
5348 |
|
5349 |
if ( dsUri.database().isEmpty() ) // typically when a service file is used |
5350 |
{ |
5351 |
dsUri.setDatabase( conn->currentDatabase() ); |
5352 |
} |
5353 |
|
5354 |
const QString wkbTypeString = QgsPostgresConn::quotedValue( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( dsUri.wkbType() ) ) );
|
5355 |
|
5356 |
const QString checkQuery = QString( "SELECT styleName" |
5357 |
" FROM layer_styles"
|
5358 |
" WHERE f_table_catalog=%1"
|
5359 |
" AND f_table_schema=%2"
|
5360 |
" AND f_table_name=%3"
|
5361 |
" AND f_geometry_column %4"
|
5362 |
" AND (type=%5 OR type IS NULL)"
|
5363 |
" AND styleName=%6" )
|
5364 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5365 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5366 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5367 |
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "= %1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) |
5368 |
.arg( wkbTypeString ) |
5369 |
.arg( QgsPostgresConn::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) ); |
5370 |
|
5371 |
QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), checkQuery ) );
|
5372 |
if ( res.PQresultStatus() == PGRES_TUPLES_OK )
|
5373 |
{ |
5374 |
return res.PQntuples() > 0; |
5375 |
} |
5376 |
else
|
5377 |
{ |
5378 |
errorCause = res.PQresultErrorMessage(); |
5379 |
return false; |
5380 |
} |
5381 |
} |
5382 |
|
5383 |
bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &qmlStyleIn, const QString &sldStyleIn, const QString &styleName, const QString &styleDescription, const QString &uiFileContent, bool useAsDefault, QString &errCause ) |
5384 |
{ |
5385 |
QgsDataSourceUri dsUri( uri ); |
5386 |
|
5387 |
// Replace invalid XML characters
|
5388 |
QString qmlStyle { qmlStyleIn }; |
5389 |
QgsPostgresUtils::replaceInvalidXmlChars( qmlStyle ); |
5390 |
QString sldStyle { sldStyleIn }; |
5391 |
QgsPostgresUtils::replaceInvalidXmlChars( sldStyle ); |
5392 |
|
5393 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false );
|
5394 |
if ( !conn )
|
5395 |
{ |
5396 |
errCause = QObject::tr( "Connection to database failed" );
|
5397 |
return false; |
5398 |
} |
5399 |
|
5400 |
if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) |
5401 |
{ |
5402 |
QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), "CREATE TABLE layer_styles(" |
5403 |
"id SERIAL PRIMARY KEY"
|
5404 |
",f_table_catalog varchar"
|
5405 |
",f_table_schema varchar"
|
5406 |
",f_table_name varchar"
|
5407 |
",f_geometry_column varchar"
|
5408 |
",styleName text"
|
5409 |
",styleQML xml"
|
5410 |
",styleSLD xml"
|
5411 |
",useAsDefault boolean"
|
5412 |
",description text"
|
5413 |
",owner varchar(63) DEFAULT CURRENT_USER"
|
5414 |
",ui xml"
|
5415 |
",update_time timestamp DEFAULT CURRENT_TIMESTAMP"
|
5416 |
",type varchar"
|
5417 |
")" ) );
|
5418 |
if ( res.PQresultStatus() != PGRES_COMMAND_OK )
|
5419 |
{ |
5420 |
errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() );
|
5421 |
conn->unref(); |
5422 |
return false; |
5423 |
} |
5424 |
} |
5425 |
else
|
5426 |
{ |
5427 |
if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) |
5428 |
{ |
5429 |
QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), "ALTER TABLE layer_styles ADD COLUMN type varchar NULL" ) ); |
5430 |
if ( res.PQresultStatus() != PGRES_COMMAND_OK )
|
5431 |
{ |
5432 |
errCause = QObject::tr( "Unable to add column type to layer_styles table. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() );
|
5433 |
conn->unref(); |
5434 |
return false; |
5435 |
} |
5436 |
} |
5437 |
} |
5438 |
|
5439 |
if ( dsUri.database().isEmpty() ) // typically when a service file is used |
5440 |
{ |
5441 |
dsUri.setDatabase( conn->currentDatabase() ); |
5442 |
} |
5443 |
|
5444 |
QString uiFileColumn; |
5445 |
QString uiFileValue; |
5446 |
if ( !uiFileContent.isEmpty() )
|
5447 |
{ |
5448 |
uiFileColumn = QStringLiteral( ",ui" );
|
5449 |
uiFileValue = QStringLiteral( ",XMLPARSE(DOCUMENT %1)" ).arg( QgsPostgresConn::quotedValue( uiFileContent ) );
|
5450 |
} |
5451 |
|
5452 |
const QString wkbTypeString = QgsPostgresConn::quotedValue( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( dsUri.wkbType() ) ) );
|
5453 |
|
5454 |
// Note: in the construction of the INSERT and UPDATE strings the qmlStyle and sldStyle values
|
5455 |
// can contain user entered strings, which may themselves include %## values that would be
|
5456 |
// replaced by the QString.arg function. To ensure that the final SQL string is not corrupt these
|
5457 |
// two values are both replaced in the final .arg call of the string construction.
|
5458 |
|
5459 |
QString sql = QString( "INSERT INTO layer_styles("
|
5460 |
"f_table_catalog,f_table_schema,f_table_name,f_geometry_column,styleName,styleQML,styleSLD,useAsDefault,description,owner,type%12"
|
5461 |
") VALUES ("
|
5462 |
"%1,%2,%3,%4,%5,XMLPARSE(DOCUMENT %16),XMLPARSE(DOCUMENT %17),%8,%9,%10,%11%13"
|
5463 |
")" )
|
5464 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5465 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5466 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5467 |
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) |
5468 |
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) |
5469 |
.arg( useAsDefault ? "true" : "false" ) |
5470 |
.arg( QgsPostgresConn::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) ) |
5471 |
.arg( "CURRENT_USER" )
|
5472 |
.arg( uiFileColumn ) |
5473 |
.arg( uiFileValue ) |
5474 |
.arg( wkbTypeString ) |
5475 |
// Must be the final .arg replacement - see above
|
5476 |
.arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ); |
5477 |
|
5478 |
QString checkQuery = QString( "SELECT styleName"
|
5479 |
" FROM layer_styles"
|
5480 |
" WHERE f_table_catalog=%1"
|
5481 |
" AND f_table_schema=%2"
|
5482 |
" AND f_table_name=%3"
|
5483 |
" AND f_geometry_column %4"
|
5484 |
" AND (type=%5 OR type IS NULL)"
|
5485 |
" AND styleName=%6" )
|
5486 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5487 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5488 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5489 |
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) |
5490 |
.arg( wkbTypeString ) |
5491 |
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ); |
5492 |
|
5493 |
QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresProviderMetadata", checkQuery ) );
|
5494 |
if ( res.PQntuples() > 0 ) |
5495 |
{ |
5496 |
sql = QString( "UPDATE layer_styles"
|
5497 |
" SET useAsDefault=%1"
|
5498 |
",styleQML=XMLPARSE(DOCUMENT %12)"
|
5499 |
",styleSLD=XMLPARSE(DOCUMENT %13)"
|
5500 |
",description=%4"
|
5501 |
",owner=%5"
|
5502 |
",type=%2"
|
5503 |
" WHERE f_table_catalog=%6"
|
5504 |
" AND f_table_schema=%7"
|
5505 |
" AND f_table_name=%8"
|
5506 |
" AND f_geometry_column %9"
|
5507 |
" AND styleName=%10"
|
5508 |
" AND (type=%2 OR type IS NULL)" )
|
5509 |
.arg( useAsDefault ? "true" : "false" ) |
5510 |
.arg( wkbTypeString ) |
5511 |
.arg( QgsPostgresConn::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) ) |
5512 |
.arg( "CURRENT_USER" )
|
5513 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5514 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5515 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5516 |
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) |
5517 |
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) |
5518 |
// Must be the final .arg replacement - see above
|
5519 |
.arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ); |
5520 |
} |
5521 |
|
5522 |
if ( useAsDefault )
|
5523 |
{ |
5524 |
QString removeDefaultSql = QString( "UPDATE layer_styles"
|
5525 |
" SET useAsDefault=false"
|
5526 |
" WHERE f_table_catalog=%1"
|
5527 |
" AND f_table_schema=%2"
|
5528 |
" AND f_table_name=%3"
|
5529 |
" AND f_geometry_column %4"
|
5530 |
" AND (type=%5 OR type IS NULL)" )
|
5531 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5532 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5533 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5534 |
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) |
5535 |
.arg( wkbTypeString ); |
5536 |
|
5537 |
sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql );
|
5538 |
} |
5539 |
|
5540 |
res = conn->LoggedPQexec( "QgsPostgresProviderMetadata", sql );
|
5541 |
|
5542 |
bool saved = res.PQresultStatus() == PGRES_COMMAND_OK;
|
5543 |
if ( !saved )
|
5544 |
errCause = QObject::tr( "Unable to save layer style. It's not possible to insert a new record into the style table. Maybe this is due to table permissions (user=%1). Please contact your database administrator." ).arg( dsUri.username() );
|
5545 |
|
5546 |
conn->unref(); |
5547 |
|
5548 |
return saved;
|
5549 |
} |
5550 |
|
5551 |
|
5552 |
QString QgsPostgresProviderMetadata::loadStyle( const QString &uri, QString &errCause )
|
5553 |
{ |
5554 |
QString styleName; |
5555 |
return loadStoredStyle( uri, styleName, errCause );
|
5556 |
} |
5557 |
|
5558 |
QString QgsPostgresProviderMetadata::loadStoredStyle( const QString &uri, QString &styleName, QString &errCause )
|
5559 |
{ |
5560 |
QgsDataSourceUri dsUri( uri ); |
5561 |
QString selectQmlQuery; |
5562 |
|
5563 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true );
|
5564 |
if ( !conn )
|
5565 |
{ |
5566 |
errCause = QObject::tr( "Connection to database failed" );
|
5567 |
return QString();
|
5568 |
} |
5569 |
|
5570 |
if ( dsUri.database().isEmpty() ) // typically when a service file is used |
5571 |
{ |
5572 |
dsUri.setDatabase( conn->currentDatabase() ); |
5573 |
} |
5574 |
|
5575 |
if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) |
5576 |
{ |
5577 |
conn->unref(); |
5578 |
return QString();
|
5579 |
} |
5580 |
|
5581 |
QString geomColumnExpr; |
5582 |
if ( dsUri.geometryColumn().isEmpty() )
|
5583 |
{ |
5584 |
geomColumnExpr = QStringLiteral( "IS NULL" );
|
5585 |
} |
5586 |
else
|
5587 |
{ |
5588 |
geomColumnExpr = QStringLiteral( "=" ) + QgsPostgresConn::quotedValue( dsUri.geometryColumn() );
|
5589 |
} |
5590 |
|
5591 |
QString wkbTypeString = QgsPostgresConn::quotedValue( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( dsUri.wkbType() ) ) ); |
5592 |
|
5593 |
// support layer_styles without type column < 3.14
|
5594 |
if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) |
5595 |
{ |
5596 |
selectQmlQuery = QString( "SELECT styleName, styleQML"
|
5597 |
" FROM layer_styles"
|
5598 |
" WHERE f_table_catalog=%1"
|
5599 |
" AND f_table_schema=%2"
|
5600 |
" AND f_table_name=%3"
|
5601 |
" AND f_geometry_column %4"
|
5602 |
" ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END"
|
5603 |
",update_time DESC LIMIT 1" )
|
5604 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5605 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5606 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5607 |
.arg( geomColumnExpr ); |
5608 |
} |
5609 |
else
|
5610 |
{ |
5611 |
selectQmlQuery = QString( "SELECT styleName, styleQML"
|
5612 |
" FROM layer_styles"
|
5613 |
" WHERE f_table_catalog=%1"
|
5614 |
" AND f_table_schema=%2"
|
5615 |
" AND f_table_name=%3"
|
5616 |
" AND f_geometry_column %4"
|
5617 |
" AND (type=%5 OR type IS NULL)"
|
5618 |
" ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END"
|
5619 |
",update_time DESC LIMIT 1" )
|
5620 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5621 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5622 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5623 |
.arg( geomColumnExpr ) |
5624 |
.arg( wkbTypeString ); |
5625 |
} |
5626 |
|
5627 |
QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectQmlQuery ) );
|
5628 |
|
5629 |
styleName = result.PQntuples() == 1 ? result.PQgetvalue( 0, 0 ) : QString(); |
5630 |
QString style = result.PQntuples() == 1 ? result.PQgetvalue( 0, 1 ) : QString(); |
5631 |
conn->unref(); |
5632 |
|
5633 |
QgsPostgresUtils::restoreInvalidXmlChars( style ); |
5634 |
|
5635 |
return style;
|
5636 |
} |
5637 |
|
5638 |
int QgsPostgresProviderMetadata::listStyles( const QString &uri, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause ) |
5639 |
{ |
5640 |
errCause.clear(); |
5641 |
QgsDataSourceUri dsUri( uri ); |
5642 |
|
5643 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true );
|
5644 |
if ( !conn )
|
5645 |
{ |
5646 |
errCause = QObject::tr( "Connection to database failed using username: %1" ).arg( dsUri.username() );
|
5647 |
return -1; |
5648 |
} |
5649 |
|
5650 |
if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) |
5651 |
{ |
5652 |
return -1; |
5653 |
} |
5654 |
|
5655 |
if ( dsUri.database().isEmpty() ) // typically when a service file is used |
5656 |
{ |
5657 |
dsUri.setDatabase( conn->currentDatabase() ); |
5658 |
} |
5659 |
|
5660 |
QString wkbTypeString = QgsPostgresConn::quotedValue( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( dsUri.wkbType() ) ) ); |
5661 |
|
5662 |
QString selectRelatedQuery = QString( "SELECT id,styleName,description"
|
5663 |
" FROM layer_styles"
|
5664 |
" WHERE f_table_catalog=%1"
|
5665 |
" AND f_table_schema=%2"
|
5666 |
" AND f_table_name=%3"
|
5667 |
" AND %4"
|
5668 |
" AND (type=%5 OR type IS NULL)"
|
5669 |
" ORDER BY useasdefault DESC, update_time DESC" )
|
5670 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5671 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5672 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5673 |
.arg( dsUri.geometryColumn().isEmpty() ? "f_geometry_column is NULL" : QString( "f_geometry_column=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) |
5674 |
.arg( wkbTypeString ); |
5675 |
|
5676 |
QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectRelatedQuery ) );
|
5677 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
5678 |
{ |
5679 |
QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectRelatedQuery ) );
|
5680 |
errCause = QObject::tr( "Error executing the select query for related styles. The query was logged" );
|
5681 |
conn->unref(); |
5682 |
return -1; |
5683 |
} |
5684 |
|
5685 |
int numberOfRelatedStyles = result.PQntuples();
|
5686 |
for ( int i = 0; i < numberOfRelatedStyles; i++ ) |
5687 |
{ |
5688 |
ids.append( result.PQgetvalue( i, 0 ) );
|
5689 |
names.append( result.PQgetvalue( i, 1 ) );
|
5690 |
descriptions.append( result.PQgetvalue( i, 2 ) );
|
5691 |
} |
5692 |
|
5693 |
QString selectOthersQuery = QString( "SELECT id,styleName,description"
|
5694 |
" FROM layer_styles"
|
5695 |
" WHERE NOT (f_table_catalog=%1 AND f_table_schema=%2 AND f_table_name=%3 AND f_geometry_column %4 AND type=%5)"
|
5696 |
" ORDER BY update_time DESC" )
|
5697 |
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) |
5698 |
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) |
5699 |
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) |
5700 |
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) |
5701 |
.arg( wkbTypeString ); |
5702 |
|
5703 |
result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectOthersQuery );
|
5704 |
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
5705 |
{ |
5706 |
QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectOthersQuery ) );
|
5707 |
errCause = QObject::tr( "Error executing the select query for unrelated styles. The query was logged" );
|
5708 |
conn->unref(); |
5709 |
return -1; |
5710 |
} |
5711 |
|
5712 |
for ( int i = 0; i < result.PQntuples(); i++ ) |
5713 |
{ |
5714 |
ids.append( result.PQgetvalue( i, 0 ) );
|
5715 |
names.append( result.PQgetvalue( i, 1 ) );
|
5716 |
descriptions.append( result.PQgetvalue( i, 2 ) );
|
5717 |
} |
5718 |
|
5719 |
conn->unref(); |
5720 |
|
5721 |
return numberOfRelatedStyles;
|
5722 |
} |
5723 |
|
5724 |
bool QgsPostgresProviderMetadata::deleteStyleById( const QString &uri, const QString &styleId, QString &errCause ) |
5725 |
{ |
5726 |
QgsDataSourceUri dsUri( uri ); |
5727 |
bool deleted;
|
5728 |
|
5729 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false );
|
5730 |
if ( !conn )
|
5731 |
{ |
5732 |
errCause = QObject::tr( "Connection to database failed using username: %1" ).arg( dsUri.username() );
|
5733 |
deleted = false;
|
5734 |
} |
5735 |
else
|
5736 |
{ |
5737 |
QString deleteStyleQuery = QStringLiteral( "DELETE FROM layer_styles WHERE id=%1" ).arg( QgsPostgresConn::quotedValue( styleId ) );
|
5738 |
QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), deleteStyleQuery ) );
|
5739 |
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
5740 |
{ |
5741 |
QgsDebugError( |
5742 |
QString( "PQexec of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" )
|
5743 |
.arg( result.PQresultStatus() ) |
5744 |
.arg( PGRES_COMMAND_OK ) |
5745 |
.arg( deleteStyleQuery ) |
5746 |
); |
5747 |
QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( deleteStyleQuery ) );
|
5748 |
errCause = QObject::tr( "Error executing the delete query. The query was logged" );
|
5749 |
deleted = false;
|
5750 |
} |
5751 |
else
|
5752 |
{ |
5753 |
deleted = true;
|
5754 |
} |
5755 |
conn->unref(); |
5756 |
} |
5757 |
return deleted;
|
5758 |
} |
5759 |
|
5760 |
QString QgsPostgresProviderMetadata::getStyleById( const QString &uri, const QString &styleId, QString &errCause ) |
5761 |
{ |
5762 |
QgsDataSourceUri dsUri( uri ); |
5763 |
|
5764 |
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true );
|
5765 |
if ( !conn )
|
5766 |
{ |
5767 |
errCause = QObject::tr( "Connection to database failed using username: %1" ).arg( dsUri.username() );
|
5768 |
return QString();
|
5769 |
} |
5770 |
|
5771 |
QString style; |
5772 |
QString selectQmlQuery = QStringLiteral( "SELECT styleQml FROM layer_styles WHERE id=%1" ).arg( QgsPostgresConn::quotedValue( styleId ) );
|
5773 |
QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectQmlQuery ) );
|
5774 |
if ( result.PQresultStatus() == PGRES_TUPLES_OK )
|
5775 |
{ |
5776 |
if ( result.PQntuples() == 1 ) |
5777 |
style = result.PQgetvalue( 0, 0 ); |
5778 |
else
|
5779 |
errCause = QObject::tr( "Consistency error in table '%1'. Style id should be unique" ).arg( QLatin1String( "layer_styles" ) ); |
5780 |
} |
5781 |
else
|
5782 |
{ |
5783 |
QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectQmlQuery ) );
|
5784 |
errCause = QObject::tr( "Error executing the select query. The query was logged" );
|
5785 |
} |
5786 |
|
5787 |
conn->unref(); |
5788 |
|
5789 |
QgsPostgresUtils::restoreInvalidXmlChars( style ); |
5790 |
|
5791 |
return style;
|
5792 |
} |
5793 |
|
5794 |
QgsTransaction *QgsPostgresProviderMetadata::createTransaction( const QString &connString )
|
5795 |
{ |
5796 |
return new QgsPostgresTransaction( connString ); |
5797 |
} |
5798 |
|
5799 |
QMap<QString, QgsAbstractProviderConnection *> QgsPostgresProviderMetadata::connections( bool cached )
|
5800 |
{ |
5801 |
return connectionsProtected<QgsPostgresProviderConnection, QgsPostgresConn>( cached );
|
5802 |
} |
5803 |
|
5804 |
QgsAbstractProviderConnection *QgsPostgresProviderMetadata::createConnection( const QString &uri, const QVariantMap &configuration ) |
5805 |
{ |
5806 |
return new QgsPostgresProviderConnection( uri, configuration ); |
5807 |
} |
5808 |
|
5809 |
void QgsPostgresProviderMetadata::deleteConnection( const QString &name ) |
5810 |
{ |
5811 |
deleteConnectionProtected<QgsPostgresProviderConnection>( name ); |
5812 |
} |
5813 |
|
5814 |
void QgsPostgresProviderMetadata::saveConnection( const QgsAbstractProviderConnection *conn, const QString &name ) |
5815 |
{ |
5816 |
saveConnectionProtected( conn, name ); |
5817 |
} |
5818 |
|
5819 |
QgsAbstractProviderConnection *QgsPostgresProviderMetadata::createConnection( const QString &name )
|
5820 |
{ |
5821 |
return new QgsPostgresProviderConnection( name ); |
5822 |
} |
5823 |
|
5824 |
|
5825 |
QgsPostgresProjectStorage *gPgProjectStorage = nullptr; // when not null it is owned by QgsApplication::projectStorageRegistry()
|
5826 |
QgsPostgresLayerMetadataProvider *gPgLayerMetadataProvider = nullptr; // when not null it is owned by QgsApplication::layerMetadataProviderRegistry()
|
5827 |
|
5828 |
void QgsPostgresProviderMetadata::initProvider()
|
5829 |
{ |
5830 |
Q_ASSERT( !gPgProjectStorage ); |
5831 |
gPgProjectStorage = new QgsPostgresProjectStorage;
|
5832 |
QgsApplication::projectStorageRegistry()->registerProjectStorage( gPgProjectStorage ); // takes ownership
|
5833 |
Q_ASSERT( !gPgLayerMetadataProvider ); |
5834 |
gPgLayerMetadataProvider = new QgsPostgresLayerMetadataProvider();
|
5835 |
QgsApplication::layerMetadataProviderRegistry()->registerLayerMetadataProvider( gPgLayerMetadataProvider ); // takes ownership
|
5836 |
} |
5837 |
|
5838 |
void QgsPostgresProviderMetadata::cleanupProvider()
|
5839 |
{ |
5840 |
QgsApplication::projectStorageRegistry()->unregisterProjectStorage( gPgProjectStorage ); // destroys the object
|
5841 |
gPgProjectStorage = nullptr; |
5842 |
QgsApplication::layerMetadataProviderRegistry()->unregisterLayerMetadataProvider( gPgLayerMetadataProvider ); |
5843 |
gPgLayerMetadataProvider = nullptr; |
5844 |
|
5845 |
QgsPostgresConnPool::cleanupInstance(); |
5846 |
} |
5847 |
|
5848 |
|
5849 |
// ----------
|
5850 |
|
5851 |
void QgsPostgresSharedData::addFeaturesCounted( long long diff ) |
5852 |
{ |
5853 |
QMutexLocker locker( &mMutex ); |
5854 |
|
5855 |
if ( mFeaturesCounted >= 0 ) |
5856 |
mFeaturesCounted += diff; |
5857 |
} |
5858 |
|
5859 |
void QgsPostgresSharedData::ensureFeaturesCountedAtLeast( long long fetched ) |
5860 |
{ |
5861 |
QMutexLocker locker( &mMutex ); |
5862 |
|
5863 |
/* only updates the feature count if it was already once.
|
5864 |
* Otherwise, this would lead to false feature count if
|
5865 |
* an existing project is open at a restrictive extent.
|
5866 |
*/
|
5867 |
if ( mFeaturesCounted > 0 && mFeaturesCounted < fetched ) |
5868 |
{ |
5869 |
QgsDebugMsgLevel( QStringLiteral( "feature count adjusted from %1 to %2" ).arg( mFeaturesCounted ).arg( fetched ), 2 ); |
5870 |
mFeaturesCounted = fetched; |
5871 |
} |
5872 |
} |
5873 |
|
5874 |
long long QgsPostgresSharedData::featuresCounted() |
5875 |
{ |
5876 |
QMutexLocker locker( &mMutex ); |
5877 |
return mFeaturesCounted;
|
5878 |
} |
5879 |
|
5880 |
void QgsPostgresSharedData::setFeaturesCounted( long long count ) |
5881 |
{ |
5882 |
QMutexLocker locker( &mMutex ); |
5883 |
mFeaturesCounted = count; |
5884 |
} |
5885 |
|
5886 |
|
5887 |
QgsFeatureId QgsPostgresSharedData::lookupFid( const QVariantList &v )
|
5888 |
{ |
5889 |
QMutexLocker locker( &mMutex ); |
5890 |
|
5891 |
QMap<QVariantList, QgsFeatureId>::const_iterator it = mKeyToFid.constFind( v ); |
5892 |
|
5893 |
if ( it != mKeyToFid.constEnd() )
|
5894 |
{ |
5895 |
return it.value();
|
5896 |
} |
5897 |
|
5898 |
mFidToKey.insert( ++mFidCounter, v ); |
5899 |
mKeyToFid.insert( v, mFidCounter ); |
5900 |
|
5901 |
return mFidCounter;
|
5902 |
} |
5903 |
|
5904 |
|
5905 |
QVariantList QgsPostgresSharedData::removeFid( QgsFeatureId fid ) |
5906 |
{ |
5907 |
QMutexLocker locker( &mMutex ); |
5908 |
|
5909 |
QVariantList v = mFidToKey[fid]; |
5910 |
mFidToKey.remove( fid ); |
5911 |
mKeyToFid.remove( v ); |
5912 |
return v;
|
5913 |
} |
5914 |
|
5915 |
void QgsPostgresSharedData::insertFid( QgsFeatureId fid, const QVariantList &k ) |
5916 |
{ |
5917 |
QMutexLocker locker( &mMutex ); |
5918 |
|
5919 |
mFidToKey.insert( fid, k ); |
5920 |
mKeyToFid.insert( k, fid ); |
5921 |
} |
5922 |
|
5923 |
QVariantList QgsPostgresSharedData::lookupKey( QgsFeatureId featureId ) |
5924 |
{ |
5925 |
QMutexLocker locker( &mMutex ); |
5926 |
|
5927 |
QMap<QgsFeatureId, QVariantList>::const_iterator it = mFidToKey.constFind( featureId ); |
5928 |
if ( it != mFidToKey.constEnd() )
|
5929 |
return it.value();
|
5930 |
return QVariantList();
|
5931 |
} |
5932 |
|
5933 |
void QgsPostgresSharedData::clear()
|
5934 |
{ |
5935 |
QMutexLocker locker( &mMutex ); |
5936 |
mFidToKey.clear(); |
5937 |
mKeyToFid.clear(); |
5938 |
mFeaturesCounted = -1;
|
5939 |
mFidCounter = 0;
|
5940 |
} |
5941 |
|
5942 |
void QgsPostgresSharedData::clearSupportsEnumValuesCache()
|
5943 |
{ |
5944 |
QMutexLocker locker( &mMutex ); |
5945 |
mFieldSupportsEnumValues.clear(); |
5946 |
} |
5947 |
|
5948 |
bool QgsPostgresSharedData::fieldSupportsEnumValuesIsSet( int index ) |
5949 |
{ |
5950 |
QMutexLocker locker( &mMutex ); |
5951 |
return mFieldSupportsEnumValues.contains( index );
|
5952 |
} |
5953 |
|
5954 |
bool QgsPostgresSharedData::fieldSupportsEnumValues( int index ) |
5955 |
{ |
5956 |
QMutexLocker locker( &mMutex ); |
5957 |
return mFieldSupportsEnumValues.contains( index ) && mFieldSupportsEnumValues[index];
|
5958 |
} |
5959 |
|
5960 |
void QgsPostgresSharedData::setFieldSupportsEnumValues( int index, bool isSupported ) |
5961 |
{ |
5962 |
QMutexLocker locker( &mMutex ); |
5963 |
mFieldSupportsEnumValues[index] = isSupported; |
5964 |
} |
5965 |
|
5966 |
|
5967 |
QgsPostgresProviderMetadata::QgsPostgresProviderMetadata() |
5968 |
: QgsProviderMetadata( QgsPostgresProvider::POSTGRES_KEY, QgsPostgresProvider::POSTGRES_DESCRIPTION ) |
5969 |
{ |
5970 |
} |
5971 |
|
5972 |
QIcon QgsPostgresProviderMetadata::icon() const
|
5973 |
{ |
5974 |
return QgsApplication::getThemeIcon( QStringLiteral( "mIconPostgis.svg" ) ); |
5975 |
} |
5976 |
|
5977 |
#ifndef HAVE_STATIC_PROVIDERS
|
5978 |
QGISEXTERN QgsProviderMetadata *providerMetadataFactory() |
5979 |
{ |
5980 |
return new QgsPostgresProviderMetadata(); |
5981 |
} |
5982 |
#endif
|
5983 |
|
5984 |
|
5985 |
QVariantMap QgsPostgresProviderMetadata::decodeUri( const QString &uri ) const |
5986 |
{ |
5987 |
const QgsDataSourceUri dsUri { uri };
|
5988 |
QVariantMap uriParts; |
5989 |
|
5990 |
if ( !dsUri.database().isEmpty() )
|
5991 |
uriParts[QStringLiteral( "dbname" )] = dsUri.database();
|
5992 |
if ( !dsUri.host().isEmpty() )
|
5993 |
uriParts[QStringLiteral( "host" )] = dsUri.host();
|
5994 |
if ( !dsUri.port().isEmpty() )
|
5995 |
uriParts[QStringLiteral( "port" )] = dsUri.port();
|
5996 |
if ( !dsUri.service().isEmpty() )
|
5997 |
uriParts[QStringLiteral( "service" )] = dsUri.service();
|
5998 |
if ( !dsUri.username().isEmpty() )
|
5999 |
uriParts[QStringLiteral( "username" )] = dsUri.username();
|
6000 |
if ( !dsUri.password().isEmpty() )
|
6001 |
uriParts[QStringLiteral( "password" )] = dsUri.password();
|
6002 |
if ( !dsUri.authConfigId().isEmpty() )
|
6003 |
uriParts[QStringLiteral( "authcfg" )] = dsUri.authConfigId();
|
6004 |
if ( dsUri.wkbType() != Qgis::WkbType::Unknown )
|
6005 |
uriParts[QStringLiteral( "type" )] = static_cast<quint32>( dsUri.wkbType() ); |
6006 |
|
6007 |
if ( uri.contains( QStringLiteral( "selectatid=" ), Qt::CaseSensitivity::CaseInsensitive ) ) |
6008 |
uriParts[QStringLiteral( "selectatid" )] = !dsUri.selectAtIdDisabled();
|
6009 |
|
6010 |
if ( !dsUri.table().isEmpty() )
|
6011 |
uriParts[QStringLiteral( "table" )] = dsUri.table();
|
6012 |
if ( !dsUri.schema().isEmpty() )
|
6013 |
uriParts[QStringLiteral( "schema" )] = dsUri.schema();
|
6014 |
if ( !dsUri.keyColumn().isEmpty() )
|
6015 |
uriParts[QStringLiteral( "key" )] = dsUri.keyColumn();
|
6016 |
if ( !dsUri.srid().isEmpty() )
|
6017 |
uriParts[QStringLiteral( "srid" )] = dsUri.srid();
|
6018 |
|
6019 |
if ( uri.contains( QStringLiteral( "estimatedmetadata=" ), Qt::CaseSensitivity::CaseInsensitive ) ) |
6020 |
uriParts[QStringLiteral( "estimatedmetadata" )] = dsUri.useEstimatedMetadata();
|
6021 |
|
6022 |
if ( uri.contains( QStringLiteral( "sslmode=" ), Qt::CaseSensitivity::CaseInsensitive ) ) |
6023 |
uriParts[QStringLiteral( "sslmode" )] = dsUri.sslMode();
|
6024 |
|
6025 |
if ( !dsUri.sql().isEmpty() )
|
6026 |
uriParts[QStringLiteral( "sql" )] = dsUri.sql();
|
6027 |
if ( !dsUri.geometryColumn().isEmpty() )
|
6028 |
uriParts[QStringLiteral( "geometrycolumn" )] = dsUri.geometryColumn();
|
6029 |
|
6030 |
return uriParts;
|
6031 |
} |
6032 |
|
6033 |
|
6034 |
QString QgsPostgresProviderMetadata::encodeUri( const QVariantMap &parts ) const |
6035 |
{ |
6036 |
QgsDataSourceUri dsUri; |
6037 |
if ( parts.contains( QStringLiteral( "dbname" ) ) ) |
6038 |
dsUri.setDatabase( parts.value( QStringLiteral( "dbname" ) ).toString() );
|
6039 |
if ( parts.contains( QStringLiteral( "port" ) ) ) |
6040 |
dsUri.setParam( QStringLiteral( "port" ), parts.value( QStringLiteral( "port" ) ).toString() ); |
6041 |
if ( parts.contains( QStringLiteral( "host" ) ) ) |
6042 |
dsUri.setParam( QStringLiteral( "host" ), parts.value( QStringLiteral( "host" ) ).toString() ); |
6043 |
if ( parts.contains( QStringLiteral( "service" ) ) ) |
6044 |
dsUri.setParam( QStringLiteral( "service" ), parts.value( QStringLiteral( "service" ) ).toString() ); |
6045 |
if ( parts.contains( QStringLiteral( "username" ) ) ) |
6046 |
dsUri.setUsername( parts.value( QStringLiteral( "username" ) ).toString() );
|
6047 |
if ( parts.contains( QStringLiteral( "password" ) ) ) |
6048 |
dsUri.setPassword( parts.value( QStringLiteral( "password" ) ).toString() );
|
6049 |
if ( parts.contains( QStringLiteral( "authcfg" ) ) ) |
6050 |
dsUri.setAuthConfigId( parts.value( QStringLiteral( "authcfg" ) ).toString() );
|
6051 |
if ( parts.contains( QStringLiteral( "type" ) ) ) |
6052 |
dsUri.setParam( QStringLiteral( "type" ), QgsWkbTypes::displayString( static_cast<Qgis::WkbType>( parts.value( QStringLiteral( "type" ) ).toInt() ) ) ); |
6053 |
if ( parts.contains( QStringLiteral( "selectatid" ) ) ) |
6054 |
dsUri.setParam( QStringLiteral( "selectatid" ), parts.value( QStringLiteral( "selectatid" ) ).toString() ); |
6055 |
if ( parts.contains( QStringLiteral( "table" ) ) ) |
6056 |
dsUri.setTable( parts.value( QStringLiteral( "table" ) ).toString() );
|
6057 |
if ( parts.contains( QStringLiteral( "schema" ) ) ) |
6058 |
dsUri.setSchema( parts.value( QStringLiteral( "schema" ) ).toString() );
|
6059 |
if ( parts.contains( QStringLiteral( "key" ) ) ) |
6060 |
dsUri.setParam( QStringLiteral( "key" ), parts.value( QStringLiteral( "key" ) ).toString() ); |
6061 |
if ( parts.contains( QStringLiteral( "srid" ) ) ) |
6062 |
dsUri.setSrid( parts.value( QStringLiteral( "srid" ) ).toString() );
|
6063 |
if ( parts.contains( QStringLiteral( "estimatedmetadata" ) ) ) |
6064 |
dsUri.setParam( QStringLiteral( "estimatedmetadata" ), parts.value( QStringLiteral( "estimatedmetadata" ) ).toString() ); |
6065 |
if ( parts.contains( QStringLiteral( "sslmode" ) ) ) |
6066 |
dsUri.setParam( QStringLiteral( "sslmode" ), QgsDataSourceUri::encodeSslMode( static_cast<QgsDataSourceUri::SslMode>( parts.value( QStringLiteral( "sslmode" ) ).toInt() ) ) ); |
6067 |
if ( parts.contains( QStringLiteral( "sql" ) ) ) |
6068 |
dsUri.setSql( parts.value( QStringLiteral( "sql" ) ).toString() );
|
6069 |
if ( parts.contains( QStringLiteral( "checkPrimaryKeyUnicity" ) ) ) |
6070 |
dsUri.setParam( QStringLiteral( "checkPrimaryKeyUnicity" ), parts.value( QStringLiteral( "checkPrimaryKeyUnicity" ) ).toString() ); |
6071 |
if ( parts.contains( QStringLiteral( "geometrycolumn" ) ) ) |
6072 |
dsUri.setGeometryColumn( parts.value( QStringLiteral( "geometrycolumn" ) ).toString() );
|
6073 |
return dsUri.uri( false ); |
6074 |
} |
6075 |
|
6076 |
QList<Qgis::LayerType> QgsPostgresProviderMetadata::supportedLayerTypes() const
|
6077 |
{ |
6078 |
return { Qgis::LayerType::Vector };
|
6079 |
} |
6080 |
|
6081 |
bool QgsPostgresProviderMetadata::saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage ) |
6082 |
{ |
6083 |
return QgsPostgresProviderMetadataUtils::saveLayerMetadata( Qgis::LayerType::Vector, uri, metadata, errorMessage );
|
6084 |
} |
6085 |
|
6086 |
|
6087 |
QgsProviderMetadata::ProviderCapabilities QgsPostgresProviderMetadata::providerCapabilities() const
|
6088 |
{ |
6089 |
return QgsProviderMetadata::ProviderCapability::SaveLayerMetadata | QgsProviderMetadata::ProviderCapability::ParallelCreateProvider;
|
6090 |
} |