From 5ed7c2c986be874cc9cf0723f98278da0bce31f0 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 13 Dec 2024 18:06:17 +0100 Subject: [PATCH 01/10] split QgsPostgresSharedData and QgsPostgresUtils to separate file, to allow their use in qgspostgresrasterprovider --- src/providers/postgres/CMakeLists.txt | 3 + .../qgspostgresdataitemguiprovider.cpp | 1 + .../postgres/qgspostgresdataitems.cpp | 144 +----- .../postgres/qgspostgresfeatureiterator.cpp | 1 + .../postgres/qgspostgresprovider.cpp | 307 +----------- src/providers/postgres/qgspostgresprovider.h | 75 +-- src/providers/postgres/qgspostgresutils.cpp | 466 ++++++++++++++++++ src/providers/postgres/qgspostgresutils.h | 96 ++++ tests/src/providers/testqgspostgresconn.cpp | 1 + 9 files changed, 571 insertions(+), 523 deletions(-) create mode 100644 src/providers/postgres/qgspostgresutils.cpp create mode 100644 src/providers/postgres/qgspostgresutils.h diff --git a/src/providers/postgres/CMakeLists.txt b/src/providers/postgres/CMakeLists.txt index 64a2287a01b7..1c073b07c260 100644 --- a/src/providers/postgres/CMakeLists.txt +++ b/src/providers/postgres/CMakeLists.txt @@ -2,6 +2,7 @@ # Files set(PG_SRCS + qgspostgresutils.cpp qgspostgresprovider.cpp qgspostgresconn.cpp qgspostgresconnpool.cpp @@ -26,6 +27,7 @@ if (WITH_GUI) qgspgnewconnection.cpp qgspostgresprojectstoragedialog.cpp raster/qgspostgresrastertemporalsettingswidget.cpp + qgspostgresutils.cpp ) set(PG_UIS ${CMAKE_SOURCE_DIR}/src/ui/qgspostgresrastertemporalsettingswidgetbase.ui) @@ -96,6 +98,7 @@ endif() # Postgres Raster set(PGRASTER_SRCS + qgspostgresutils.cpp raster/qgspostgresrasterprovider.cpp raster/qgspostgresrastershareddata.cpp raster/qgspostgresrasterutils.cpp diff --git a/src/providers/postgres/qgspostgresdataitemguiprovider.cpp b/src/providers/postgres/qgspostgresdataitemguiprovider.cpp index e6366984703d..e4578bb8f9d1 100644 --- a/src/providers/postgres/qgspostgresdataitemguiprovider.cpp +++ b/src/providers/postgres/qgspostgresdataitemguiprovider.cpp @@ -25,6 +25,7 @@ #include "qgsdataitemguiproviderutils.h" #include "qgssettings.h" #include "qgspostgresconn.h" +#include "qgspostgresutils.h" #include #include diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index 46ec9265c0b6..697f84770e8b 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -33,149 +33,7 @@ #include #include #include - -bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause ) -{ - QgsDebugMsgLevel( "deleting layer " + uri, 2 ); - - QgsDataSourceUri dsUri( uri ); - QString schemaName = dsUri.schema(); - QString tableName = dsUri.table(); - QString geometryCol = dsUri.geometryColumn(); - - QString schemaTableName; - if ( !schemaName.isEmpty() ) - { - schemaTableName = QgsPostgresConn::quotedIdentifier( schemaName ) + '.'; - } - schemaTableName += QgsPostgresConn::quotedIdentifier( tableName ); - - QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false ); - if ( !conn ) - { - errCause = QObject::tr( "Connection to database failed" ); - return false; - } - - // handle deletion of views - QString sqlViewCheck = QStringLiteral( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" ) - .arg( QgsPostgresConn::quotedValue( schemaTableName ) ); - QgsPostgresResult resViewCheck( conn->LoggedPQexec( "QgsPostgresUtils", sqlViewCheck ) ); - const QString type = resViewCheck.PQgetvalue( 0, 0 ); - const Qgis::PostgresRelKind relKind = QgsPostgresConn::relKindFromValue( type ); - - switch ( relKind ) - { - case Qgis::PostgresRelKind::View: - case Qgis::PostgresRelKind::MaterializedView: - { - QString sql = QStringLiteral( "DROP %1VIEW %2" ).arg( type == QLatin1String( "m" ) ? QStringLiteral( "MATERIALIZED " ) : QString(), schemaTableName ); - QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); - if ( result.PQresultStatus() != PGRES_COMMAND_OK ) - { - errCause = QObject::tr( "Unable to delete view %1: \n%2" ) - .arg( schemaTableName, result.PQresultErrorMessage() ); - conn->unref(); - return false; - } - conn->unref(); - return true; - } - - case Qgis::PostgresRelKind::NotSet: - case Qgis::PostgresRelKind::Unknown: - case Qgis::PostgresRelKind::OrdinaryTable: - case Qgis::PostgresRelKind::Index: - case Qgis::PostgresRelKind::Sequence: - case Qgis::PostgresRelKind::CompositeType: - case Qgis::PostgresRelKind::ToastTable: - case Qgis::PostgresRelKind::ForeignTable: - case Qgis::PostgresRelKind::PartitionedTable: - { - // TODO -- this logic is being applied to a whole bunch - // of potentially non-table items, eg indexes and sequences. - // These should have special handling! - - // check the geometry column count - QString sql = QString( "SELECT count(*) " - "FROM geometry_columns, pg_class, pg_namespace " - "WHERE f_table_name=relname AND f_table_schema=nspname " - "AND pg_class.relnamespace=pg_namespace.oid " - "AND f_table_schema=%1 AND f_table_name=%2" ) - .arg( QgsPostgresConn::quotedValue( schemaName ), QgsPostgresConn::quotedValue( tableName ) ); - QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); - if ( result.PQresultStatus() != PGRES_TUPLES_OK ) - { - errCause = QObject::tr( "Unable to delete layer %1: \n%2" ) - .arg( schemaTableName, result.PQresultErrorMessage() ); - conn->unref(); - return false; - } - - int count = result.PQgetvalue( 0, 0 ).toInt(); - - if ( !geometryCol.isEmpty() && count > 1 ) - { - // the table has more geometry columns, drop just the geometry column - sql = QStringLiteral( "SELECT DropGeometryColumn(%1,%2,%3)" ) - .arg( QgsPostgresConn::quotedValue( schemaName ), QgsPostgresConn::quotedValue( tableName ), QgsPostgresConn::quotedValue( geometryCol ) ); - } - else - { - // drop the table - sql = QStringLiteral( "SELECT DropGeometryTable(%1,%2)" ) - .arg( QgsPostgresConn::quotedValue( schemaName ), QgsPostgresConn::quotedValue( tableName ) ); - } - - result = conn->LoggedPQexec( "QgsPostgresUtils", sql ); - if ( result.PQresultStatus() != PGRES_TUPLES_OK ) - { - errCause = QObject::tr( "Unable to delete layer %1: \n%2" ) - .arg( schemaTableName, result.PQresultErrorMessage() ); - conn->unref(); - return false; - } - - conn->unref(); - return true; - } - } - BUILTIN_UNREACHABLE -} - -bool QgsPostgresUtils::deleteSchema( const QString &schema, const QgsDataSourceUri &uri, QString &errCause, bool cascade ) -{ - QgsDebugMsgLevel( "deleting schema " + schema, 2 ); - - if ( schema.isEmpty() ) - return false; - - QString schemaName = QgsPostgresConn::quotedIdentifier( schema ); - - QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri, false ); - if ( !conn ) - { - errCause = QObject::tr( "Connection to database failed" ); - return false; - } - - // drop the schema - QString sql = QStringLiteral( "DROP SCHEMA %1 %2" ) - .arg( schemaName, cascade ? QStringLiteral( "CASCADE" ) : QString() ); - - QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); - if ( result.PQresultStatus() != PGRES_COMMAND_OK ) - { - errCause = QObject::tr( "Unable to delete schema %1: \n%2" ) - .arg( schemaName, result.PQresultErrorMessage() ); - conn->unref(); - return false; - } - - conn->unref(); - return true; -} - +#include "qgspostgresutils.h" // --------------------------------------------------------------------------- QgsPGConnectionItem::QgsPGConnectionItem( QgsDataItem *parent, const QString &name, const QString &path ) diff --git a/src/providers/postgres/qgspostgresfeatureiterator.cpp b/src/providers/postgres/qgspostgresfeatureiterator.cpp index cb32b7de0b25..d11334c06e8f 100644 --- a/src/providers/postgres/qgspostgresfeatureiterator.cpp +++ b/src/providers/postgres/qgspostgresfeatureiterator.cpp @@ -24,6 +24,7 @@ #include "qgsmessagelog.h" #include "qgsexception.h" #include "qgsgeometryengine.h" +#include "qgspostgresutils.h" #include #include diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 1ad7f21dba48..0b6dd42c7100 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -49,6 +49,7 @@ #include "qgsprovidermetadata.h" #include "qgspostgresproviderconnection.h" #include "qgspostgresprovidermetadatautils.h" +#include "qgspostgresutils.h" #include const QString QgsPostgresProvider::POSTGRES_KEY = QStringLiteral( "postgres" ); @@ -639,193 +640,6 @@ QString QgsPostgresProvider::whereClause( QgsFeatureIds featureIds ) const return QgsPostgresUtils::whereClause( featureIds, mAttributeFields, connectionRO(), mPrimaryKeyType, mPrimaryKeyAttrs, mShared ); } - -QString QgsPostgresUtils::whereClause( QgsFeatureId featureId, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ) -{ - QString whereClause; - - switch ( pkType ) - { - case PktTid: - whereClause = QStringLiteral( "ctid='(%1,%2)'" ) - .arg( FID_TO_NUMBER( featureId ) >> 16 ) - .arg( FID_TO_NUMBER( featureId ) & 0xffff ); - break; - - case PktOid: - whereClause = QStringLiteral( "oid=%1" ).arg( featureId ); - break; - - case PktInt: - Q_ASSERT( pkAttrs.size() == 1 ); - whereClause = QStringLiteral( "%1=%2" ).arg( QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ).arg( FID2PKINT( featureId ) ); - break; - - case PktInt64: - case PktUint64: - { - Q_ASSERT( pkAttrs.size() == 1 ); - QVariantList pkVals = sharedData->lookupKey( featureId ); - if ( !pkVals.isEmpty() ) - { - QgsField fld = fields.at( pkAttrs[0] ); - whereClause = conn->fieldExpression( fld ); - if ( !QgsVariantUtils::isNull( pkVals[0] ) ) - whereClause += '=' + pkVals[0].toString(); - else - whereClause += QLatin1String( " IS NULL" ); - } - } - break; - - case PktFidMap: - { - QVariantList pkVals = sharedData->lookupKey( featureId ); - if ( !pkVals.isEmpty() ) - { - Q_ASSERT( pkVals.size() == pkAttrs.size() ); - - QString delim; - for ( int i = 0; i < pkAttrs.size(); i++ ) - { - int idx = pkAttrs[i]; - QgsField fld = fields.at( idx ); - - whereClause += delim + conn->fieldExpressionForWhereClause( fld, static_cast( pkVals[i].userType() ) ); - if ( QgsVariantUtils::isNull( pkVals[i] ) ) - whereClause += QLatin1String( " IS NULL" ); - else - whereClause += '=' + QgsPostgresConn::quotedValue( pkVals[i] ); // remove toString as it must be handled by quotedValue function - - delim = QStringLiteral( " AND " ); - } - } - else - { - QgsDebugError( QStringLiteral( "FAILURE: Key values for feature %1 not found." ).arg( featureId ) ); - whereClause = QStringLiteral( "NULL" ); - } - } - break; - - case PktUnknown: - Q_ASSERT( !"FAILURE: Primary key unknown" ); - whereClause = QStringLiteral( "NULL" ); - break; - } - - return whereClause; -} - -QString QgsPostgresUtils::whereClause( const QgsFeatureIds &featureIds, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ) -{ - auto lookupKeyWhereClause = [=] { - if ( featureIds.isEmpty() ) - return QString(); - - //simple primary key, so prefer to use an "IN (...)" query. These are much faster then multiple chained ...OR... clauses - QString delim; - QString expr = QStringLiteral( "%1 IN (" ).arg( QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ); - - for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) - { - const QVariantList pkVals = sharedData->lookupKey( featureId ); - if ( !pkVals.isEmpty() ) - { - expr += delim + QgsPostgresConn::quotedValue( pkVals.at( 0 ) ); - delim = ','; - } - } - expr += ')'; - - return expr; - }; - - switch ( pkType ) - { - case PktOid: - case PktInt: - { - QString expr; - - //simple primary key, so prefer to use an "IN (...)" query. These are much faster then multiple chained ...OR... clauses - if ( !featureIds.isEmpty() ) - { - QString delim; - expr = QStringLiteral( "%1 IN (" ).arg( ( pkType == PktOid ? QStringLiteral( "oid" ) : QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ) ); - - for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) - { - expr += delim + FID_TO_STRING( ( pkType == PktOid ? featureId : FID2PKINT( featureId ) ) ); - delim = ','; - } - expr += ')'; - } - - return expr; - } - case PktInt64: - case PktUint64: - return lookupKeyWhereClause(); - - case PktFidMap: - case PktTid: - case PktUnknown: - { - // on simple string primary key we can use IN - if ( pkType == PktFidMap && pkAttrs.count() == 1 && fields.at( pkAttrs[0] ).type() == QMetaType::Type::QString ) - return lookupKeyWhereClause(); - - //complex primary key, need to build up where string - QStringList whereClauses; - for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) - { - whereClauses << whereClause( featureId, fields, conn, pkType, pkAttrs, sharedData ); - } - return whereClauses.isEmpty() ? QString() : whereClauses.join( QLatin1String( " OR " ) ).prepend( '(' ).append( ')' ); - } - } - return QString(); //avoid warning -} - -QString QgsPostgresUtils::andWhereClauses( const QString &c1, const QString &c2 ) -{ - if ( c1.isEmpty() ) - return c2; - if ( c2.isEmpty() ) - return c1; - - return QStringLiteral( "(%1) AND (%2)" ).arg( c1, c2 ); -} - -void QgsPostgresUtils::replaceInvalidXmlChars( QString &xml ) -{ - static const QRegularExpression replaceRe { QStringLiteral( "([\\x00-\\x08\\x0B-\\x1F\\x7F])" ) }; - QRegularExpressionMatchIterator it { replaceRe.globalMatch( xml ) }; - while ( it.hasNext() ) - { - const QRegularExpressionMatch match { it.next() }; - const QChar c { match.captured( 1 ).at( 0 ) }; - xml.replace( c, QStringLiteral( "UTF-8[%1]" ).arg( c.unicode() ) ); - } -} - -void QgsPostgresUtils::restoreInvalidXmlChars( QString &xml ) -{ - static const QRegularExpression replaceRe { QStringLiteral( R"raw(UTF-8\[(\d+)\])raw" ) }; - QRegularExpressionMatchIterator it { replaceRe.globalMatch( xml ) }; - while ( it.hasNext() ) - { - const QRegularExpressionMatch match { it.next() }; - bool ok; - const ushort code { match.captured( 1 ).toUShort( &ok ) }; - if ( ok ) - { - xml.replace( QStringLiteral( "UTF-8[%1]" ).arg( code ), QChar( code ) ); - } - } -} - QString QgsPostgresProvider::filterWhereClause() const { QString where; @@ -5854,124 +5668,6 @@ void QgsPostgresProviderMetadata::cleanupProvider() } -// ---------- - -void QgsPostgresSharedData::addFeaturesCounted( long long diff ) -{ - QMutexLocker locker( &mMutex ); - - if ( mFeaturesCounted >= 0 ) - mFeaturesCounted += diff; -} - -void QgsPostgresSharedData::ensureFeaturesCountedAtLeast( long long fetched ) -{ - QMutexLocker locker( &mMutex ); - - /* only updates the feature count if it was already once. - * Otherwise, this would lead to false feature count if - * an existing project is open at a restrictive extent. - */ - if ( mFeaturesCounted > 0 && mFeaturesCounted < fetched ) - { - QgsDebugMsgLevel( QStringLiteral( "feature count adjusted from %1 to %2" ).arg( mFeaturesCounted ).arg( fetched ), 2 ); - mFeaturesCounted = fetched; - } -} - -long long QgsPostgresSharedData::featuresCounted() -{ - QMutexLocker locker( &mMutex ); - return mFeaturesCounted; -} - -void QgsPostgresSharedData::setFeaturesCounted( long long count ) -{ - QMutexLocker locker( &mMutex ); - mFeaturesCounted = count; -} - - -QgsFeatureId QgsPostgresSharedData::lookupFid( const QVariantList &v ) -{ - QMutexLocker locker( &mMutex ); - - QMap::const_iterator it = mKeyToFid.constFind( v ); - - if ( it != mKeyToFid.constEnd() ) - { - return it.value(); - } - - mFidToKey.insert( ++mFidCounter, v ); - mKeyToFid.insert( v, mFidCounter ); - - return mFidCounter; -} - - -QVariantList QgsPostgresSharedData::removeFid( QgsFeatureId fid ) -{ - QMutexLocker locker( &mMutex ); - - QVariantList v = mFidToKey[fid]; - mFidToKey.remove( fid ); - mKeyToFid.remove( v ); - return v; -} - -void QgsPostgresSharedData::insertFid( QgsFeatureId fid, const QVariantList &k ) -{ - QMutexLocker locker( &mMutex ); - - mFidToKey.insert( fid, k ); - mKeyToFid.insert( k, fid ); -} - -QVariantList QgsPostgresSharedData::lookupKey( QgsFeatureId featureId ) -{ - QMutexLocker locker( &mMutex ); - - QMap::const_iterator it = mFidToKey.constFind( featureId ); - if ( it != mFidToKey.constEnd() ) - return it.value(); - return QVariantList(); -} - -void QgsPostgresSharedData::clear() -{ - QMutexLocker locker( &mMutex ); - mFidToKey.clear(); - mKeyToFid.clear(); - mFeaturesCounted = -1; - mFidCounter = 0; -} - -void QgsPostgresSharedData::clearSupportsEnumValuesCache() -{ - QMutexLocker locker( &mMutex ); - mFieldSupportsEnumValues.clear(); -} - -bool QgsPostgresSharedData::fieldSupportsEnumValuesIsSet( int index ) -{ - QMutexLocker locker( &mMutex ); - return mFieldSupportsEnumValues.contains( index ); -} - -bool QgsPostgresSharedData::fieldSupportsEnumValues( int index ) -{ - QMutexLocker locker( &mMutex ); - return mFieldSupportsEnumValues.contains( index ) && mFieldSupportsEnumValues[index]; -} - -void QgsPostgresSharedData::setFieldSupportsEnumValues( int index, bool isSupported ) -{ - QMutexLocker locker( &mMutex ); - mFieldSupportsEnumValues[index] = isSupported; -} - - QgsPostgresProviderMetadata::QgsPostgresProviderMetadata() : QgsProviderMetadata( QgsPostgresProvider::POSTGRES_KEY, QgsPostgresProvider::POSTGRES_DESCRIPTION ) { @@ -6038,7 +5734,6 @@ QVariantMap QgsPostgresProviderMetadata::decodeUri( const QString &uri ) const return uriParts; } - QString QgsPostgresProviderMetadata::encodeUri( const QVariantMap &parts ) const { QgsDataSourceUri dsUri; diff --git a/src/providers/postgres/qgspostgresprovider.h b/src/providers/postgres/qgspostgresprovider.h index 01b54cc9b64b..abbf727cda8b 100644 --- a/src/providers/postgres/qgspostgresprovider.h +++ b/src/providers/postgres/qgspostgresprovider.h @@ -26,6 +26,7 @@ #include "qgsreferencedgeometry.h" #include #include +#include "qgspostgresutils.h" class QgsFeature; class QgsField; @@ -496,80 +497,6 @@ class QgsPostgresProvider final : public QgsVectorDataProvider bool computeExtent3D() const; }; - -//! Assorted Postgres utility functions -class QgsPostgresUtils -{ - public: - static bool deleteLayer( const QString &uri, QString &errCause ); - static bool deleteSchema( const QString &schema, const QgsDataSourceUri &uri, QString &errCause, bool cascade = false ); - - static QString whereClause( QgsFeatureId featureId, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ); - - static QString whereClause( const QgsFeatureIds &featureIds, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ); - - static QString andWhereClauses( const QString &c1, const QString &c2 ); - - static const qint64 INT32PK_OFFSET = 4294967296; // 2^32 - - // We shift negative 32bit integers to above the max 32bit - // positive integer to support the whole range of int32 values - // See https://github.com/qgis/QGIS/issues/22258 - static qint64 int32pk_to_fid( qint32 x ) - { - return x >= 0 ? x : x + INT32PK_OFFSET; - } - - static qint32 fid_to_int32pk( qint64 x ) - { - return x <= ( ( INT32PK_OFFSET ) / 2.0 ) ? x : -( INT32PK_OFFSET - x ); - } - - //! Replaces invalid XML chars with UTF-8[] - static void replaceInvalidXmlChars( QString &xml ); - - //! Replaces UTF-8[] with the actual unicode char - static void restoreInvalidXmlChars( QString &xml ); -}; - -/** - * Data shared between provider class and its feature sources. Ideally there should - * be as few members as possible because there could be simultaneous reads/writes - * from different threads and therefore locking has to be involved. -*/ -class QgsPostgresSharedData -{ - public: - QgsPostgresSharedData() = default; - - long long featuresCounted(); - void setFeaturesCounted( long long count ); - void addFeaturesCounted( long long diff ); - void ensureFeaturesCountedAtLeast( long long fetched ); - - // FID lookups - QgsFeatureId lookupFid( const QVariantList &v ); // lookup existing mapping or add a new one - QVariantList removeFid( QgsFeatureId fid ); - void insertFid( QgsFeatureId fid, const QVariantList &k ); - QVariantList lookupKey( QgsFeatureId featureId ); - void clear(); - - void clearSupportsEnumValuesCache(); - bool fieldSupportsEnumValuesIsSet( int index ); - bool fieldSupportsEnumValues( int index ); - void setFieldSupportsEnumValues( int index, bool isSupported ); - - protected: - QMutex mMutex; //!< Access to all data members is guarded by the mutex - - long long mFeaturesCounted = -1; //!< Number of features in the layer - - QgsFeatureId mFidCounter = 0; // next feature id if map is used - QMap mKeyToFid; // map key values to feature id - QMap mFidToKey; // map feature id back to key values - QMap mFieldSupportsEnumValues; // map field index to bool flag supports enum values -}; - class QgsPostgresProviderMetadata final : public QgsProviderMetadata { Q_OBJECT diff --git a/src/providers/postgres/qgspostgresutils.cpp b/src/providers/postgres/qgspostgresutils.cpp new file mode 100644 index 000000000000..b98dc273780f --- /dev/null +++ b/src/providers/postgres/qgspostgresutils.cpp @@ -0,0 +1,466 @@ +/*************************************************************************** + qgspostgresutils.cpp - Utils for PostgreSQL/PostGIS + ------------------- + begin : Jan 2, 2004 + copyright : (C) 2003 by Gary E.Sherman + email : sherman at mrcc.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgslogger.h" +#include "qgspostgresutils.h" + +// ---------- + +void QgsPostgresSharedData::addFeaturesCounted( long long diff ) +{ + QMutexLocker locker( &mMutex ); + + if ( mFeaturesCounted >= 0 ) + mFeaturesCounted += diff; +} + +void QgsPostgresSharedData::ensureFeaturesCountedAtLeast( long long fetched ) +{ + QMutexLocker locker( &mMutex ); + + /* only updates the feature count if it was already once. + * Otherwise, this would lead to false feature count if + * an existing project is open at a restrictive extent. + */ + if ( mFeaturesCounted > 0 && mFeaturesCounted < fetched ) + { + QgsDebugMsgLevel( QStringLiteral( "feature count adjusted from %1 to %2" ).arg( mFeaturesCounted ).arg( fetched ), 2 ); + mFeaturesCounted = fetched; + } +} + +long long QgsPostgresSharedData::featuresCounted() +{ + QMutexLocker locker( &mMutex ); + return mFeaturesCounted; +} + +void QgsPostgresSharedData::setFeaturesCounted( long long count ) +{ + QMutexLocker locker( &mMutex ); + mFeaturesCounted = count; +} + + +QgsFeatureId QgsPostgresSharedData::lookupFid( const QVariantList &v ) +{ + QMutexLocker locker( &mMutex ); + + QMap::const_iterator it = mKeyToFid.constFind( v ); + + if ( it != mKeyToFid.constEnd() ) + { + return it.value(); + } + + mFidToKey.insert( ++mFidCounter, v ); + mKeyToFid.insert( v, mFidCounter ); + + return mFidCounter; +} + + +QVariantList QgsPostgresSharedData::removeFid( QgsFeatureId fid ) +{ + QMutexLocker locker( &mMutex ); + + QVariantList v = mFidToKey[fid]; + mFidToKey.remove( fid ); + mKeyToFid.remove( v ); + return v; +} + +void QgsPostgresSharedData::insertFid( QgsFeatureId fid, const QVariantList &k ) +{ + QMutexLocker locker( &mMutex ); + + mFidToKey.insert( fid, k ); + mKeyToFid.insert( k, fid ); +} + +QVariantList QgsPostgresSharedData::lookupKey( QgsFeatureId featureId ) +{ + QMutexLocker locker( &mMutex ); + + QMap::const_iterator it = mFidToKey.constFind( featureId ); + if ( it != mFidToKey.constEnd() ) + return it.value(); + return QVariantList(); +} + +void QgsPostgresSharedData::clear() +{ + QMutexLocker locker( &mMutex ); + mFidToKey.clear(); + mKeyToFid.clear(); + mFeaturesCounted = -1; + mFidCounter = 0; +} + +void QgsPostgresSharedData::clearSupportsEnumValuesCache() +{ + QMutexLocker locker( &mMutex ); + mFieldSupportsEnumValues.clear(); +} + +bool QgsPostgresSharedData::fieldSupportsEnumValuesIsSet( int index ) +{ + QMutexLocker locker( &mMutex ); + return mFieldSupportsEnumValues.contains( index ); +} + +bool QgsPostgresSharedData::fieldSupportsEnumValues( int index ) +{ + QMutexLocker locker( &mMutex ); + return mFieldSupportsEnumValues.contains( index ) && mFieldSupportsEnumValues[index]; +} + +void QgsPostgresSharedData::setFieldSupportsEnumValues( int index, bool isSupported ) +{ + QMutexLocker locker( &mMutex ); + mFieldSupportsEnumValues[index] = isSupported; +} + +// ---------- + +QString QgsPostgresUtils::whereClause( QgsFeatureId featureId, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ) +{ + QString whereClause; + + switch ( pkType ) + { + case PktTid: + whereClause = QStringLiteral( "ctid='(%1,%2)'" ) + .arg( FID_TO_NUMBER( featureId ) >> 16 ) + .arg( FID_TO_NUMBER( featureId ) & 0xffff ); + break; + + case PktOid: + whereClause = QStringLiteral( "oid=%1" ).arg( featureId ); + break; + + case PktInt: + Q_ASSERT( pkAttrs.size() == 1 ); + whereClause = QStringLiteral( "%1=%2" ).arg( QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ).arg( QgsPostgresUtils::fid_to_int32pk( featureId ) ); + break; + + case PktInt64: + case PktUint64: + { + Q_ASSERT( pkAttrs.size() == 1 ); + QVariantList pkVals = sharedData->lookupKey( featureId ); + if ( !pkVals.isEmpty() ) + { + QgsField fld = fields.at( pkAttrs[0] ); + whereClause = conn->fieldExpression( fld ); + if ( !QgsVariantUtils::isNull( pkVals[0] ) ) + whereClause += '=' + pkVals[0].toString(); + else + whereClause += QLatin1String( " IS NULL" ); + } + } + break; + + case PktFidMap: + { + QVariantList pkVals = sharedData->lookupKey( featureId ); + if ( !pkVals.isEmpty() ) + { + Q_ASSERT( pkVals.size() == pkAttrs.size() ); + + QString delim; + for ( int i = 0; i < pkAttrs.size(); i++ ) + { + int idx = pkAttrs[i]; + QgsField fld = fields.at( idx ); + + whereClause += delim + conn->fieldExpressionForWhereClause( fld, static_cast( pkVals[i].userType() ) ); + if ( QgsVariantUtils::isNull( pkVals[i] ) ) + whereClause += QLatin1String( " IS NULL" ); + else + whereClause += '=' + QgsPostgresConn::quotedValue( pkVals[i] ); // remove toString as it must be handled by quotedValue function + + delim = QStringLiteral( " AND " ); + } + } + else + { + QgsDebugError( QStringLiteral( "FAILURE: Key values for feature %1 not found." ).arg( featureId ) ); + whereClause = QStringLiteral( "NULL" ); + } + } + break; + + case PktUnknown: + Q_ASSERT( !"FAILURE: Primary key unknown" ); + whereClause = QStringLiteral( "NULL" ); + break; + } + + return whereClause; +} + +QString QgsPostgresUtils::whereClause( const QgsFeatureIds &featureIds, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ) +{ + auto lookupKeyWhereClause = [=] { + if ( featureIds.isEmpty() ) + return QString(); + + //simple primary key, so prefer to use an "IN (...)" query. These are much faster then multiple chained ...OR... clauses + QString delim; + QString expr = QStringLiteral( "%1 IN (" ).arg( QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ); + + for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) + { + const QVariantList pkVals = sharedData->lookupKey( featureId ); + if ( !pkVals.isEmpty() ) + { + expr += delim + QgsPostgresConn::quotedValue( pkVals.at( 0 ) ); + delim = ','; + } + } + expr += ')'; + + return expr; + }; + + switch ( pkType ) + { + case PktOid: + case PktInt: + { + QString expr; + + //simple primary key, so prefer to use an "IN (...)" query. These are much faster then multiple chained ...OR... clauses + if ( !featureIds.isEmpty() ) + { + QString delim; + expr = QStringLiteral( "%1 IN (" ).arg( ( pkType == PktOid ? QStringLiteral( "oid" ) : QgsPostgresConn::quotedIdentifier( fields.at( pkAttrs[0] ).name() ) ) ); + + for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) + { + expr += delim + FID_TO_STRING( ( pkType == PktOid ? featureId : QgsPostgresUtils::fid_to_int32pk( featureId ) ) ); + delim = ','; + } + expr += ')'; + } + + return expr; + } + case PktInt64: + case PktUint64: + return lookupKeyWhereClause(); + + case PktFidMap: + case PktTid: + case PktUnknown: + { + // on simple string primary key we can use IN + if ( pkType == PktFidMap && pkAttrs.count() == 1 && fields.at( pkAttrs[0] ).type() == QMetaType::Type::QString ) + return lookupKeyWhereClause(); + + //complex primary key, need to build up where string + QStringList whereClauses; + for ( const QgsFeatureId featureId : std::as_const( featureIds ) ) + { + whereClauses << whereClause( featureId, fields, conn, pkType, pkAttrs, sharedData ); + } + return whereClauses.isEmpty() ? QString() : whereClauses.join( QLatin1String( " OR " ) ).prepend( '(' ).append( ')' ); + } + } + return QString(); //avoid warning +} + +QString QgsPostgresUtils::andWhereClauses( const QString &c1, const QString &c2 ) +{ + if ( c1.isEmpty() ) + return c2; + if ( c2.isEmpty() ) + return c1; + + return QStringLiteral( "(%1) AND (%2)" ).arg( c1, c2 ); +} + +void QgsPostgresUtils::replaceInvalidXmlChars( QString &xml ) +{ + static const QRegularExpression replaceRe { QStringLiteral( "([\\x00-\\x08\\x0B-\\x1F\\x7F])" ) }; + QRegularExpressionMatchIterator it { replaceRe.globalMatch( xml ) }; + while ( it.hasNext() ) + { + const QRegularExpressionMatch match { it.next() }; + const QChar c { match.captured( 1 ).at( 0 ) }; + xml.replace( c, QStringLiteral( "UTF-8[%1]" ).arg( c.unicode() ) ); + } +} + +void QgsPostgresUtils::restoreInvalidXmlChars( QString &xml ) +{ + static const QRegularExpression replaceRe { QStringLiteral( R"raw(UTF-8\[(\d+)\])raw" ) }; + QRegularExpressionMatchIterator it { replaceRe.globalMatch( xml ) }; + while ( it.hasNext() ) + { + const QRegularExpressionMatch match { it.next() }; + bool ok; + const ushort code { match.captured( 1 ).toUShort( &ok ) }; + if ( ok ) + { + xml.replace( QStringLiteral( "UTF-8[%1]" ).arg( code ), QChar( code ) ); + } + } +} + +bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause ) +{ + QgsDebugMsgLevel( "deleting layer " + uri, 2 ); + + QgsDataSourceUri dsUri( uri ); + QString schemaName = dsUri.schema(); + QString tableName = dsUri.table(); + QString geometryCol = dsUri.geometryColumn(); + + QString schemaTableName; + if ( !schemaName.isEmpty() ) + { + schemaTableName = QgsPostgresConn::quotedIdentifier( schemaName ) + '.'; + } + schemaTableName += QgsPostgresConn::quotedIdentifier( tableName ); + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed" ); + return false; + } + + // handle deletion of views + QString sqlViewCheck = QStringLiteral( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" ) + .arg( QgsPostgresConn::quotedValue( schemaTableName ) ); + QgsPostgresResult resViewCheck( conn->LoggedPQexec( "QgsPostgresUtils", sqlViewCheck ) ); + const QString type = resViewCheck.PQgetvalue( 0, 0 ); + const Qgis::PostgresRelKind relKind = QgsPostgresConn::relKindFromValue( type ); + + switch ( relKind ) + { + case Qgis::PostgresRelKind::View: + case Qgis::PostgresRelKind::MaterializedView: + { + QString sql = QStringLiteral( "DROP %1VIEW %2" ).arg( type == QLatin1String( "m" ) ? QStringLiteral( "MATERIALIZED " ) : QString(), schemaTableName ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); + if ( result.PQresultStatus() != PGRES_COMMAND_OK ) + { + errCause = QObject::tr( "Unable to delete view %1: \n%2" ) + .arg( schemaTableName, result.PQresultErrorMessage() ); + conn->unref(); + return false; + } + conn->unref(); + return true; + } + + case Qgis::PostgresRelKind::NotSet: + case Qgis::PostgresRelKind::Unknown: + case Qgis::PostgresRelKind::OrdinaryTable: + case Qgis::PostgresRelKind::Index: + case Qgis::PostgresRelKind::Sequence: + case Qgis::PostgresRelKind::CompositeType: + case Qgis::PostgresRelKind::ToastTable: + case Qgis::PostgresRelKind::ForeignTable: + case Qgis::PostgresRelKind::PartitionedTable: + { + // TODO -- this logic is being applied to a whole bunch + // of potentially non-table items, eg indexes and sequences. + // These should have special handling! + + // check the geometry column count + QString sql = QString( "SELECT count(*) " + "FROM geometry_columns, pg_class, pg_namespace " + "WHERE f_table_name=relname AND f_table_schema=nspname " + "AND pg_class.relnamespace=pg_namespace.oid " + "AND f_table_schema=%1 AND f_table_name=%2" ) + .arg( QgsPostgresConn::quotedValue( schemaName ), QgsPostgresConn::quotedValue( tableName ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); + if ( result.PQresultStatus() != PGRES_TUPLES_OK ) + { + errCause = QObject::tr( "Unable to delete layer %1: \n%2" ) + .arg( schemaTableName, result.PQresultErrorMessage() ); + conn->unref(); + return false; + } + + int count = result.PQgetvalue( 0, 0 ).toInt(); + + if ( !geometryCol.isEmpty() && count > 1 ) + { + // the table has more geometry columns, drop just the geometry column + sql = QStringLiteral( "SELECT DropGeometryColumn(%1,%2,%3)" ) + .arg( QgsPostgresConn::quotedValue( schemaName ), QgsPostgresConn::quotedValue( tableName ), QgsPostgresConn::quotedValue( geometryCol ) ); + } + else + { + // drop the table + sql = QStringLiteral( "SELECT DropGeometryTable(%1,%2)" ) + .arg( QgsPostgresConn::quotedValue( schemaName ), QgsPostgresConn::quotedValue( tableName ) ); + } + + result = conn->LoggedPQexec( "QgsPostgresUtils", sql ); + if ( result.PQresultStatus() != PGRES_TUPLES_OK ) + { + errCause = QObject::tr( "Unable to delete layer %1: \n%2" ) + .arg( schemaTableName, result.PQresultErrorMessage() ); + conn->unref(); + return false; + } + + conn->unref(); + return true; + } + } + BUILTIN_UNREACHABLE +} + +bool QgsPostgresUtils::deleteSchema( const QString &schema, const QgsDataSourceUri &uri, QString &errCause, bool cascade ) +{ + QgsDebugMsgLevel( "deleting schema " + schema, 2 ); + + if ( schema.isEmpty() ) + return false; + + QString schemaName = QgsPostgresConn::quotedIdentifier( schema ); + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri, false ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed" ); + return false; + } + + // drop the schema + QString sql = QStringLiteral( "DROP SCHEMA %1 %2" ) + .arg( schemaName, cascade ? QStringLiteral( "CASCADE" ) : QString() ); + + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); + if ( result.PQresultStatus() != PGRES_COMMAND_OK ) + { + errCause = QObject::tr( "Unable to delete schema %1: \n%2" ) + .arg( schemaName, result.PQresultErrorMessage() ); + conn->unref(); + return false; + } + + conn->unref(); + return true; +} \ No newline at end of file diff --git a/src/providers/postgres/qgspostgresutils.h b/src/providers/postgres/qgspostgresutils.h new file mode 100644 index 000000000000..b9cce3a60371 --- /dev/null +++ b/src/providers/postgres/qgspostgresutils.h @@ -0,0 +1,96 @@ +/*************************************************************************** + qgspostgresutils.h - Utils for PostgreSQL/PostGIS + ------------------- + begin : Jan 2, 2004 + copyright : (C) 2003 by Gary E.Sherman + email : sherman at mrcc.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSPOSTGRESUTILS_H +#define QGSPOSTGRESUTILS_H + +#include "qgspostgresconn.h" + +/** + * Data shared between provider class and its feature sources. Ideally there should + * be as few members as possible because there could be simultaneous reads/writes + * from different threads and therefore locking has to be involved. +*/ +class QgsPostgresSharedData +{ + public: + QgsPostgresSharedData() = default; + + long long featuresCounted(); + void setFeaturesCounted( long long count ); + void addFeaturesCounted( long long diff ); + void ensureFeaturesCountedAtLeast( long long fetched ); + + // FID lookups + QgsFeatureId lookupFid( const QVariantList &v ); // lookup existing mapping or add a new one + QVariantList removeFid( QgsFeatureId fid ); + void insertFid( QgsFeatureId fid, const QVariantList &k ); + QVariantList lookupKey( QgsFeatureId featureId ); + void clear(); + + void clearSupportsEnumValuesCache(); + bool fieldSupportsEnumValuesIsSet( int index ); + bool fieldSupportsEnumValues( int index ); + void setFieldSupportsEnumValues( int index, bool isSupported ); + + protected: + QMutex mMutex; //!< Access to all data members is guarded by the mutex + + long long mFeaturesCounted = -1; //!< Number of features in the layer + + QgsFeatureId mFidCounter = 0; // next feature id if map is used + QMap mKeyToFid; // map key values to feature id + QMap mFidToKey; // map feature id back to key values + QMap mFieldSupportsEnumValues; // map field index to bool flag supports enum values +}; + +//! Assorted Postgres utility functions +class QgsPostgresUtils +{ + public: + static bool deleteLayer( const QString &uri, QString &errCause ); + static bool deleteSchema( const QString &schema, const QgsDataSourceUri &uri, QString &errCause, bool cascade = false ); + + static QString whereClause( QgsFeatureId featureId, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ); + + static QString whereClause( const QgsFeatureIds &featureIds, const QgsFields &fields, QgsPostgresConn *conn, QgsPostgresPrimaryKeyType pkType, const QList &pkAttrs, const std::shared_ptr &sharedData ); + + static QString andWhereClauses( const QString &c1, const QString &c2 ); + + static const qint64 INT32PK_OFFSET = 4294967296; // 2^32 + + // We shift negative 32bit integers to above the max 32bit + // positive integer to support the whole range of int32 values + // See https://github.com/qgis/QGIS/issues/22258 + static qint64 int32pk_to_fid( qint32 x ) + { + return x >= 0 ? x : x + INT32PK_OFFSET; + } + + static qint32 fid_to_int32pk( qint64 x ) + { + return x <= ( ( INT32PK_OFFSET ) / 2.0 ) ? x : -( INT32PK_OFFSET - x ); + } + + //! Replaces invalid XML chars with UTF-8[] + static void replaceInvalidXmlChars( QString &xml ); + + //! Replaces UTF-8[] with the actual unicode char + static void restoreInvalidXmlChars( QString &xml ); +}; + +#endif diff --git a/tests/src/providers/testqgspostgresconn.cpp b/tests/src/providers/testqgspostgresconn.cpp index b5629da68fe0..80dbe55d4cf3 100644 --- a/tests/src/providers/testqgspostgresconn.cpp +++ b/tests/src/providers/testqgspostgresconn.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "qgspostgresutils.h" // Helper function for QCOMPARE char *toString( const QgsPostgresGeometryColumnType &t ) From 1c7e2f383156698183b59318dd9ad8d0691ff52f Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 13 Dec 2024 22:36:40 +0100 Subject: [PATCH 02/10] add rasterColumnName --- src/providers/postgres/qgspostgresconn.cpp | 26 ++++++++++++++++++++++ src/providers/postgres/qgspostgresconn.h | 9 ++++++++ 2 files changed, 35 insertions(+) diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp index 6ae7845272a8..84403faaa733 100644 --- a/src/providers/postgres/qgspostgresconn.cpp +++ b/src/providers/postgres/qgspostgresconn.cpp @@ -2898,3 +2898,29 @@ int QgsPostgresConn::crsToSrid( const QgsCoordinateReferenceSystem &crs ) return -1; } + +QString QgsPostgresConn::rasterColumnName( const QString &schema, const QString &table ) +{ + QMutexLocker locker( &mLock ); + QString rasterColum; + QString sql = QStringLiteral( "SELECT r_raster_column" + " FROM public.raster_columns" + " WHERE" + " r_table_schema =%1" + " AND r_table_name =%2" ) + .arg( quotedString( schema ) ) + .arg( quotedString( table ) ); + + QgsPostgresResult res( LoggedPQexec( QStringLiteral( "QgsPostgresConn" ), sql ) ); + + if ( res.PQresultStatus() == PGRES_TUPLES_OK ) + { + rasterColum = res.PQgetvalue( 0, 0 ); + } + else + { + QgsMessageLog::logMessage( tr( "SQL: %1\nresult: %2\nerror: %3\n" ).arg( sql ).arg( res.PQresultStatus() ).arg( res.PQresultErrorMessage() ), tr( "PostGIS" ) ); + } + + return rasterColum; +} diff --git a/src/providers/postgres/qgspostgresconn.h b/src/providers/postgres/qgspostgresconn.h index ea38d2806626..e54bb50ea0b2 100644 --- a/src/providers/postgres/qgspostgresconn.h +++ b/src/providers/postgres/qgspostgresconn.h @@ -424,6 +424,15 @@ class QgsPostgresConn : public QObject */ bool getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema = QString(), const QString &name = QString() ); + /** + * Gets information about rater column name for raster table + * \param schema + * \param table + * \returns raster column name or empty string + * \since QGIS 3.42 + */ + QString rasterColumnName( const QString &schema, const QString &table ); + qint64 getBinaryInt( QgsPostgresResult &queryResult, int row, int col ); QString fieldExpressionForWhereClause( const QgsField &fld, QMetaType::Type valueType = QMetaType::Type::UnknownType, QString expr = "%1" ); From 34627413e5711111509525f8672d6f6c337d38fa Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Mon, 16 Dec 2024 21:30:14 +0100 Subject: [PATCH 03/10] move tableExists and columnExists to QgsPostgresUtils, add createStylesTable there --- .../postgres/qgspostgresprovider.cpp | 48 ++++--------------- src/providers/postgres/qgspostgresutils.cpp | 36 +++++++++++++- src/providers/postgres/qgspostgresutils.h | 6 +++ 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 0b6dd42c7100..68d2f6ac9ebd 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -67,18 +67,6 @@ inline qint32 FID2PKINT( qint64 x ) return QgsPostgresUtils::fid_to_int32pk( x ); } -static bool tableExists( QgsPostgresConn &conn, const QString &name ) -{ - QgsPostgresResult res( conn.LoggedPQexec( QStringLiteral( "tableExists" ), "SELECT EXISTS ( SELECT oid FROM pg_catalog.pg_class WHERE relname=" + QgsPostgresConn::quotedValue( name ) + ")" ) ); - return res.PQgetvalue( 0, 0 ).startsWith( 't' ); -} - -static bool columnExists( QgsPostgresConn &conn, const QString &table, const QString &column ) -{ - QgsPostgresResult res( conn.LoggedPQexec( QStringLiteral( "columnExists" ), "SELECT COUNT(*) FROM information_schema.columns WHERE table_name=" + QgsPostgresConn::quotedValue( table ) + " and column_name=" + QgsPostgresConn::quotedValue( column ) ) ); - return res.PQgetvalue( 0, 0 ).toInt() > 0; -} - QgsPostgresPrimaryKeyType QgsPostgresProvider::pkType( const QgsField &f ) const { @@ -1214,7 +1202,7 @@ bool QgsPostgresProvider::loadFields() void QgsPostgresProvider::setEditorWidgets() { - if ( !tableExists( *connectionRO(), EDITOR_WIDGET_STYLES_TABLE ) ) + if ( !QgsPostgresUtils::tableExists( connectionRO(), EDITOR_WIDGET_STYLES_TABLE ) ) { return; } @@ -5159,11 +5147,11 @@ bool QgsPostgresProviderMetadata::styleExists( const QString &uri, const QString return false; } - if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) { return false; } - else if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) + else if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) { return false; } @@ -5219,25 +5207,9 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString & return false; } - if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) - { - QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), "CREATE TABLE layer_styles(" - "id SERIAL PRIMARY KEY" - ",f_table_catalog varchar" - ",f_table_schema varchar" - ",f_table_name varchar" - ",f_geometry_column varchar" - ",styleName text" - ",styleQML xml" - ",styleSLD xml" - ",useAsDefault boolean" - ",description text" - ",owner varchar(63) DEFAULT CURRENT_USER" - ",ui xml" - ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" - ",type varchar" - ")" ) ); - if ( res.PQresultStatus() != PGRES_COMMAND_OK ) + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) + { + if ( !QgsPostgresUtils::createStylesTable( conn, QStringLiteral( "QgsPostgresProviderMetadata" ) ) ) { 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() ); conn->unref(); @@ -5246,7 +5218,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString & } else { - if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) + if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) { QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), "ALTER TABLE layer_styles ADD COLUMN type varchar NULL" ) ); if ( res.PQresultStatus() != PGRES_COMMAND_OK ) @@ -5394,7 +5366,7 @@ QString QgsPostgresProviderMetadata::loadStoredStyle( const QString &uri, QStrin dsUri.setDatabase( conn->currentDatabase() ); } - if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) { conn->unref(); return QString(); @@ -5413,7 +5385,7 @@ QString QgsPostgresProviderMetadata::loadStoredStyle( const QString &uri, QStrin QString wkbTypeString = QgsPostgresConn::quotedValue( QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( dsUri.wkbType() ) ) ); // support layer_styles without type column < 3.14 - if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) + if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) { selectQmlQuery = QString( "SELECT styleName, styleQML" " FROM layer_styles" @@ -5469,7 +5441,7 @@ int QgsPostgresProviderMetadata::listStyles( const QString &uri, QStringList &id return -1; } - if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) { return -1; } diff --git a/src/providers/postgres/qgspostgresutils.cpp b/src/providers/postgres/qgspostgresutils.cpp index b98dc273780f..0fc8fab2990f 100644 --- a/src/providers/postgres/qgspostgresutils.cpp +++ b/src/providers/postgres/qgspostgresutils.cpp @@ -463,4 +463,38 @@ bool QgsPostgresUtils::deleteSchema( const QString &schema, const QgsDataSourceU conn->unref(); return true; -} \ No newline at end of file +} + +bool QgsPostgresUtils::tableExists( QgsPostgresConn *conn, const QString &name ) +{ + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "tableExists" ), "SELECT EXISTS ( SELECT oid FROM pg_catalog.pg_class WHERE relname=" + QgsPostgresConn::quotedValue( name ) + ")" ) ); + return res.PQgetvalue( 0, 0 ).startsWith( 't' ); +} + +bool QgsPostgresUtils::columnExists( QgsPostgresConn *conn, const QString &table, const QString &column ) +{ + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "columnExists" ), "SELECT COUNT(*) FROM information_schema.columns WHERE table_name=" + QgsPostgresConn::quotedValue( table ) + " and column_name=" + QgsPostgresConn::quotedValue( column ) ) ); + return res.PQgetvalue( 0, 0 ).toInt() > 0; +} + +bool QgsPostgresUtils::createStylesTable( QgsPostgresConn *conn, QString loggedClass ) +{ + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), "CREATE TABLE layer_styles(" + "id SERIAL PRIMARY KEY" + ",f_table_catalog varchar" + ",f_table_schema varchar" + ",f_table_name varchar" + ",f_geometry_column varchar" + ",styleName text" + ",styleQML xml" + ",styleSLD xml" + ",useAsDefault boolean" + ",description text" + ",owner varchar(63) DEFAULT CURRENT_USER" + ",ui xml" + ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" + ",type varchar" + ",r_raster_column varchar" + ")" ) ); + return res.PQresultStatus() == PGRES_COMMAND_OK; +} diff --git a/src/providers/postgres/qgspostgresutils.h b/src/providers/postgres/qgspostgresutils.h index b9cce3a60371..465a0229aeb8 100644 --- a/src/providers/postgres/qgspostgresutils.h +++ b/src/providers/postgres/qgspostgresutils.h @@ -91,6 +91,12 @@ class QgsPostgresUtils //! Replaces UTF-8[] with the actual unicode char static void restoreInvalidXmlChars( QString &xml ); + + static bool createStylesTable( QgsPostgresConn *conn, QString loggedClass ); + + static bool columnExists( QgsPostgresConn *conn, const QString &table, const QString &column ); + + static bool tableExists( QgsPostgresConn *conn, const QString &name ); }; #endif From cfa4c3c521d9e5de9c338498e0a1570ecf0ac6ba Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Mon, 16 Dec 2024 21:31:41 +0100 Subject: [PATCH 04/10] functions to create, load and save styles in DB --- .../raster/qgspostgresrasterprovider.cpp | 488 +++++++++++++++++- .../raster/qgspostgresrasterprovider.h | 12 + 2 files changed, 499 insertions(+), 1 deletion(-) diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp index 071cd6b3a3b6..1de043341911 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp @@ -25,13 +25,13 @@ #include "qgsstringutils.h" #include "qgsapplication.h" #include "qgsraster.h" +#include "qgspostgresutils.h" #include const QString QgsPostgresRasterProvider::PG_RASTER_PROVIDER_KEY = QStringLiteral( "postgresraster" ); const QString QgsPostgresRasterProvider::PG_RASTER_PROVIDER_DESCRIPTION = QStringLiteral( "Postgres raster provider" ); - QgsPostgresRasterProvider::QgsPostgresRasterProvider( const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions, Qgis::DataProviderReadFlags flags ) : QgsRasterDataProvider( uri, providerOptions, flags ) , mShared( new QgsPostgresRasterSharedData ) @@ -2447,3 +2447,489 @@ QgsLayerMetadata QgsPostgresRasterProvider::layerMetadata() const { return mLayerMetadata; } + +Qgis::ProviderStyleStorageCapabilities QgsPostgresRasterProvider::styleStorageCapabilities() const +{ + Qgis::ProviderStyleStorageCapabilities storageCapabilities; + if ( isValid() ) + { + storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; + storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; + storageCapabilities |= Qgis::ProviderStyleStorageCapability::DeleteFromDatabase; + } + return storageCapabilities; +} + + +bool QgsPostgresRasterProviderMetadata::styleExists( const QString &uri, const QString &styleId, QString &errorCause ) +{ + errorCause.clear(); + + QgsDataSourceUri dsUri( uri ); + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true ); + if ( !conn ) + { + errorCause = QObject::tr( "Connection to database failed" ); + return false; + } + + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) + { + return false; + } + else if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) + { + return false; + } + else if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "r_raster_column" ) ) ) + { + return false; + } + + if ( dsUri.database().isEmpty() ) // typically when a service file is used + { + dsUri.setDatabase( conn->currentDatabase() ); + } + + QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + + const QString checkQuery = QString( "SELECT styleName" + " FROM layer_styles" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column IS NULL" + " AND (type=%4 OR type IS NULL)" + " AND styleName=%5" + " AND r_raster_column=%6" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), checkQuery ) ); + if ( res.PQresultStatus() == PGRES_TUPLES_OK ) + { + return res.PQntuples() > 0; + } + else + { + errorCause = res.PQresultErrorMessage(); + return false; + } +} + +bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QString &qmlStyleIn, const QString &sldStyleIn, const QString &styleName, const QString &styleDescription, const QString &uiFileContent, bool useAsDefault, QString &errCause ) +{ + QgsDataSourceUri dsUri( uri ); + + // Replace invalid XML characters + QString qmlStyle { qmlStyleIn }; + QgsPostgresUtils::replaceInvalidXmlChars( qmlStyle ); + QString sldStyle { sldStyleIn }; + QgsPostgresUtils::replaceInvalidXmlChars( sldStyle ); + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed" ); + return false; + } + + QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) + { + if ( !QgsPostgresUtils::createStylesTable( conn, QStringLiteral( "QgsPostgresRasterProviderMetadata" ) ) ) + { + 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() ); + conn->unref(); + return false; + } + } + else + { + if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) + { + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), "ALTER TABLE layer_styles ADD COLUMN type varchar NULL" ) ); + if ( res.PQresultStatus() != PGRES_COMMAND_OK ) + { + 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() ); + conn->unref(); + return false; + } + } + } + + if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "r_raster_column" ) ) ) + { + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), "ALTER TABLE layer_styles ADD COLUMN r_raster_column varchar NULL" ) ); + if ( res.PQresultStatus() != PGRES_COMMAND_OK ) + { + errCause = QObject::tr( "Unable to add column r_raster_column to layer_styles table. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() ); + conn->unref(); + return false; + } + } + + if ( dsUri.database().isEmpty() ) // typically when a service file is used + { + dsUri.setDatabase( conn->currentDatabase() ); + } + + QString uiFileColumn; + QString uiFileValue; + if ( !uiFileContent.isEmpty() ) + { + uiFileColumn = QStringLiteral( ",ui" ); + uiFileValue = QStringLiteral( ",XMLPARSE(DOCUMENT %1)" ).arg( QgsPostgresConn::quotedValue( uiFileContent ) ); + } + + // Note: in the construction of the INSERT and UPDATE strings the qmlStyle and sldStyle values + // can contain user entered strings, which may themselves include %## values that would be + // replaced by the QString.arg function. To ensure that the final SQL string is not corrupt these + // two values are both replaced in the final .arg call of the string construction. + + QString sql = QString( "INSERT INTO layer_styles(" + "f_table_catalog,f_table_schema,f_table_name,f_geometry_column,styleName,styleQML,styleSLD,useAsDefault,description,owner,type%12,r_raster_column" + ") VALUES (" + "%1,%2,%3,%4,%5,XMLPARSE(DOCUMENT %16),XMLPARSE(DOCUMENT %17),%8,%9,%10,%11%13,%14" + ")" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QStringLiteral( "NULL" ) ) + .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) + .arg( useAsDefault ? "true" : "false" ) + .arg( QgsPostgresConn::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) ) + .arg( "CURRENT_USER" ) + .arg( uiFileColumn ) + .arg( uiFileValue ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ) + // Must be the final .arg replacement - see above + .arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ); + + + QString checkQuery = QString( "SELECT styleName" + " FROM layer_styles" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column IS NULL" + " AND (type=%4 OR type IS NULL)" + " AND styleName=%5" + " AND r_raster_column=%6" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + + QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresRasterProviderMetadata", checkQuery ) ); + if ( res.PQntuples() > 0 ) + { + sql = QString( "UPDATE layer_styles" + " SET useAsDefault=%1" + ",styleQML=XMLPARSE(DOCUMENT %12)" + ",styleSLD=XMLPARSE(DOCUMENT %13)" + ",description=%4" + ",owner=%5" + ",type=%2" + " WHERE f_table_catalog=%6" + " AND f_table_schema=%7" + " AND f_table_name=%8" + " AND f_geometry_column IS NULL" + " AND styleName=%9" + " AND (type=%2 OR type IS NULL)" + " AND r_raster_column=%14" ) + .arg( useAsDefault ? "true" : "false" ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) ) + .arg( "CURRENT_USER" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) + // Must be the final .arg replacement - see above + .arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + } + + if ( useAsDefault ) + { + QString removeDefaultSql = QString( "UPDATE layer_styles" + " SET useAsDefault=false" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column IS NULL" + " AND (type=%4 OR type IS NULL)" + " AND r_raster_column=%5" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + + sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql ); + } + + res = conn->LoggedPQexec( "QgsPostgresRasterProviderMetadata", sql ); + + bool saved = res.PQresultStatus() == PGRES_COMMAND_OK; + if ( !saved ) + 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() ); + + conn->unref(); + + return saved; +} + + +QString QgsPostgresRasterProviderMetadata::loadStyle( const QString &uri, QString &errCause ) +{ + QString styleName; + return loadStoredStyle( uri, styleName, errCause ); +} + +QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, QString &styleName, QString &errCause ) +{ + QgsDataSourceUri dsUri( uri ); + QString selectQmlQuery; + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed" ); + return QString(); + } + + if ( dsUri.database().isEmpty() ) // typically when a service file is used + { + dsUri.setDatabase( conn->currentDatabase() ); + } + + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) + { + conn->unref(); + return QString(); + } + else if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "r_raster_column" ) ) ) + { + return QString(); + } + + QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + + // support layer_styles without type column < 3.14 + if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) + { + selectQmlQuery = QString( "SELECT styleName, styleQML" + " FROM layer_styles" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column IS NULL" + " AND r_raster_column=%4" + " ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END" + ",update_time DESC LIMIT 1" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + } + else + { + selectQmlQuery = QString( "SELECT styleName, styleQML" + " FROM layer_styles" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column IS NULL" + " AND (type=%4 OR type IS NULL)" + " AND r_raster_column=%5" + " ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END" + ",update_time DESC LIMIT 1" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + } + + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectQmlQuery ) ); + + styleName = result.PQntuples() == 1 ? result.PQgetvalue( 0, 0 ) : QString(); + QString style = result.PQntuples() == 1 ? result.PQgetvalue( 0, 1 ) : QString(); + conn->unref(); + + QgsPostgresUtils::restoreInvalidXmlChars( style ); + + return style; +} + +int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause ) +{ + errCause.clear(); + QgsDataSourceUri dsUri( uri ); + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed using username: %1" ).arg( dsUri.username() ); + return -1; + } + + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) + { + return -1; + } + + if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "r_raster_column" ) ) ) + { + return false; + } + + if ( dsUri.database().isEmpty() ) // typically when a service file is used + { + dsUri.setDatabase( conn->currentDatabase() ); + } + + QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + + QString selectRelatedQuery = QString( "SELECT id,styleName,description" + " FROM layer_styles" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column is NULL" + " AND (type=%4 OR type IS NULL)" + " AND r_raster_column=%5" + " ORDER BY useasdefault DESC, update_time DESC" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectRelatedQuery ) ); + if ( result.PQresultStatus() != PGRES_TUPLES_OK ) + { + QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectRelatedQuery ) ); + errCause = QObject::tr( "Error executing the select query for related styles. The query was logged" ); + conn->unref(); + return -1; + } + + int numberOfRelatedStyles = result.PQntuples(); + for ( int i = 0; i < numberOfRelatedStyles; i++ ) + { + ids.append( result.PQgetvalue( i, 0 ) ); + names.append( result.PQgetvalue( i, 1 ) ); + descriptions.append( result.PQgetvalue( i, 2 ) ); + } + + QString selectOthersQuery = QString( "SELECT id,styleName,description" + " FROM layer_styles" + " WHERE NOT (f_table_catalog=%1 AND f_table_schema=%2 AND f_table_name=%3 AND f_geometry_column IS NULL AND type=%4 AND r_raster_column=%5)" + " ORDER BY update_time DESC" ) + .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) + .arg( QgsPostgresConn::quotedValue( mType ) ) + .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + + result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectOthersQuery ); + if ( result.PQresultStatus() != PGRES_TUPLES_OK ) + { + QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectOthersQuery ) ); + errCause = QObject::tr( "Error executing the select query for unrelated styles. The query was logged" ); + conn->unref(); + return -1; + } + + for ( int i = 0; i < result.PQntuples(); i++ ) + { + ids.append( result.PQgetvalue( i, 0 ) ); + names.append( result.PQgetvalue( i, 1 ) ); + descriptions.append( result.PQgetvalue( i, 2 ) ); + } + + conn->unref(); + + return numberOfRelatedStyles; +} + +bool QgsPostgresRasterProviderMetadata::deleteStyleById( const QString &uri, const QString &styleId, QString &errCause ) +{ + QgsDataSourceUri dsUri( uri ); + bool deleted; + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, false ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed using username: %1" ).arg( dsUri.username() ); + deleted = false; + } + else + { + QString deleteStyleQuery = QStringLiteral( "DELETE FROM layer_styles WHERE id=%1" ).arg( QgsPostgresConn::quotedValue( styleId ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), deleteStyleQuery ) ); + if ( result.PQresultStatus() != PGRES_COMMAND_OK ) + { + QgsDebugError( + QString( "PQexec of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" ) + .arg( result.PQresultStatus() ) + .arg( PGRES_COMMAND_OK ) + .arg( deleteStyleQuery ) + ); + QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( deleteStyleQuery ) ); + errCause = QObject::tr( "Error executing the delete query. The query was logged" ); + deleted = false; + } + else + { + deleted = true; + } + conn->unref(); + } + return deleted; +} + +QString QgsPostgresRasterProviderMetadata::getStyleById( const QString &uri, const QString &styleId, QString &errCause ) +{ + QgsDataSourceUri dsUri( uri ); + + QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri, true ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed using username: %1" ).arg( dsUri.username() ); + return QString(); + } + + QString style; + QString selectQmlQuery = QStringLiteral( "SELECT styleQml FROM layer_styles WHERE id=%1" ).arg( QgsPostgresConn::quotedValue( styleId ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectQmlQuery ) ); + if ( result.PQresultStatus() == PGRES_TUPLES_OK ) + { + if ( result.PQntuples() == 1 ) + style = result.PQgetvalue( 0, 0 ); + else + errCause = QObject::tr( "Consistency error in table '%1'. Style id should be unique" ).arg( QLatin1String( "layer_styles" ) ); + } + else + { + QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectQmlQuery ) ); + errCause = QObject::tr( "Error executing the select query. The query was logged" ); + } + + conn->unref(); + + QgsPostgresUtils::restoreInvalidXmlChars( style ); + + return style; +} diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.h b/src/providers/postgres/raster/qgspostgresrasterprovider.h index 17673de36f5e..132366d4637e 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.h +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.h @@ -47,6 +47,7 @@ class QgsPostgresRasterProvider : public QgsRasterDataProvider virtual QString description() const override; bool readBlock( int bandNo, QgsRectangle const &viewExtent, int width, int height, void *data, QgsRasterBlockFeedback *feedback = nullptr ) override; + Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; // QgsRasterInterface interface virtual Qgis::DataType dataType( int bandNo ) const override; @@ -263,6 +264,17 @@ class QgsPostgresRasterProviderMetadata : public QgsProviderMetadata QList supportedLayerTypes() const override; bool saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage ) override; QgsProviderMetadata::ProviderCapabilities providerCapabilities() const override; + + // These functions are very + bool styleExists( const QString &uri, const QString &styleId, QString &errorCause ) override; + bool saveStyle( const QString &uri, const QString &qmlStyle, const QString &sldStyle, const QString &styleName, const QString &styleDescription, const QString &uiFileContent, bool useAsDefault, QString &errCause ) override; + QString loadStyle( const QString &uri, QString &errCause ) override; + virtual QString loadStoredStyle( const QString &uri, QString &styleName, QString &errCause ) override; + int listStyles( const QString &uri, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause ) override; + bool deleteStyleById( const QString &uri, const QString &styleId, QString &errCause ) override; + QString getStyleById( const QString &uri, const QString &styleId, QString &errCause ) override; + + const QString mType = "Raster"; }; From a63e855ff72ce5aa08b9f5a157b6f8cc274773b4 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 17 Dec 2024 14:44:46 +0100 Subject: [PATCH 05/10] rename variable with typo --- src/providers/postgres/qgspostgresconn.cpp | 6 ++--- .../raster/qgspostgresrasterprovider.cpp | 26 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp index 84403faaa733..f0160f3f0f86 100644 --- a/src/providers/postgres/qgspostgresconn.cpp +++ b/src/providers/postgres/qgspostgresconn.cpp @@ -2902,7 +2902,7 @@ int QgsPostgresConn::crsToSrid( const QgsCoordinateReferenceSystem &crs ) QString QgsPostgresConn::rasterColumnName( const QString &schema, const QString &table ) { QMutexLocker locker( &mLock ); - QString rasterColum; + QString rasterColumn; QString sql = QStringLiteral( "SELECT r_raster_column" " FROM public.raster_columns" " WHERE" @@ -2915,12 +2915,12 @@ QString QgsPostgresConn::rasterColumnName( const QString &schema, const QString if ( res.PQresultStatus() == PGRES_TUPLES_OK ) { - rasterColum = res.PQgetvalue( 0, 0 ); + rasterColumn = res.PQgetvalue( 0, 0 ); } else { QgsMessageLog::logMessage( tr( "SQL: %1\nresult: %2\nerror: %3\n" ).arg( sql ).arg( res.PQresultStatus() ).arg( res.PQresultErrorMessage() ), tr( "PostGIS" ) ); } - return rasterColum; + return rasterColumn; } diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp index 1de043341911..02d616c52aa3 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp @@ -2491,7 +2491,7 @@ bool QgsPostgresRasterProviderMetadata::styleExists( const QString &uri, const Q dsUri.setDatabase( conn->currentDatabase() ); } - QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); const QString checkQuery = QString( "SELECT styleName" " FROM layer_styles" @@ -2507,7 +2507,7 @@ bool QgsPostgresRasterProviderMetadata::styleExists( const QString &uri, const Q .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) .arg( QgsPostgresConn::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), checkQuery ) ); if ( res.PQresultStatus() == PGRES_TUPLES_OK ) @@ -2538,7 +2538,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt return false; } - QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) { @@ -2608,7 +2608,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( uiFileColumn ) .arg( uiFileValue ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ) + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ) // Must be the final .arg replacement - see above .arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ); @@ -2627,7 +2627,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresRasterProviderMetadata", checkQuery ) ); if ( res.PQntuples() > 0 ) @@ -2656,7 +2656,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) // Must be the final .arg replacement - see above .arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); } if ( useAsDefault ) @@ -2673,7 +2673,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql ); } @@ -2723,7 +2723,7 @@ QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, return QString(); } - QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); // support layer_styles without type column < 3.14 if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) @@ -2740,7 +2740,7 @@ QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); } else { @@ -2758,7 +2758,7 @@ QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); } QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectQmlQuery ) ); @@ -2799,7 +2799,7 @@ int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringLi dsUri.setDatabase( conn->currentDatabase() ); } - QString rasterColum = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); + QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); QString selectRelatedQuery = QString( "SELECT id,styleName,description" " FROM layer_styles" @@ -2814,7 +2814,7 @@ int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringLi .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectRelatedQuery ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) @@ -2841,7 +2841,7 @@ int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringLi .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColum ) ); + .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectOthersQuery ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) From fe407be667cc68b133e66485eaebb58774bce312 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 17 Dec 2024 15:08:26 +0100 Subject: [PATCH 06/10] fix function --- src/providers/postgres/qgspostgresutils.cpp | 35 +++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/providers/postgres/qgspostgresutils.cpp b/src/providers/postgres/qgspostgresutils.cpp index 0fc8fab2990f..5a9c6d8e4122 100644 --- a/src/providers/postgres/qgspostgresutils.cpp +++ b/src/providers/postgres/qgspostgresutils.cpp @@ -479,22 +479,23 @@ bool QgsPostgresUtils::columnExists( QgsPostgresConn *conn, const QString &table bool QgsPostgresUtils::createStylesTable( QgsPostgresConn *conn, QString loggedClass ) { - QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), "CREATE TABLE layer_styles(" - "id SERIAL PRIMARY KEY" - ",f_table_catalog varchar" - ",f_table_schema varchar" - ",f_table_name varchar" - ",f_geometry_column varchar" - ",styleName text" - ",styleQML xml" - ",styleSLD xml" - ",useAsDefault boolean" - ",description text" - ",owner varchar(63) DEFAULT CURRENT_USER" - ",ui xml" - ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" - ",type varchar" - ",r_raster_column varchar" - ")" ) ); + QgsPostgresResult res( conn->LoggedPQexec( loggedClass, "CREATE TABLE layer_styles(" + "id SERIAL PRIMARY KEY" + ",f_table_catalog varchar" + ",f_table_schema varchar" + ",f_table_name varchar" + ",f_geometry_column varchar" + ",styleName text" + ",styleQML xml" + ",styleSLD xml" + ",useAsDefault boolean" + ",description text" + ",owner varchar(63) DEFAULT CURRENT_USER" + ",ui xml" + ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" + ",type varchar" + ",r_raster_column varchar" + ")" ) ); + return res.PQresultStatus() == PGRES_COMMAND_OK; } From 66d8bcaf8471d0872ee56d6172ed2592dbed188b Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 9 Jan 2025 09:15:23 +0100 Subject: [PATCH 07/10] drop function that is not needed --- src/providers/postgres/qgspostgresconn.cpp | 26 ---------------------- src/providers/postgres/qgspostgresconn.h | 9 -------- 2 files changed, 35 deletions(-) diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp index f0160f3f0f86..6ae7845272a8 100644 --- a/src/providers/postgres/qgspostgresconn.cpp +++ b/src/providers/postgres/qgspostgresconn.cpp @@ -2898,29 +2898,3 @@ int QgsPostgresConn::crsToSrid( const QgsCoordinateReferenceSystem &crs ) return -1; } - -QString QgsPostgresConn::rasterColumnName( const QString &schema, const QString &table ) -{ - QMutexLocker locker( &mLock ); - QString rasterColumn; - QString sql = QStringLiteral( "SELECT r_raster_column" - " FROM public.raster_columns" - " WHERE" - " r_table_schema =%1" - " AND r_table_name =%2" ) - .arg( quotedString( schema ) ) - .arg( quotedString( table ) ); - - QgsPostgresResult res( LoggedPQexec( QStringLiteral( "QgsPostgresConn" ), sql ) ); - - if ( res.PQresultStatus() == PGRES_TUPLES_OK ) - { - rasterColumn = res.PQgetvalue( 0, 0 ); - } - else - { - QgsMessageLog::logMessage( tr( "SQL: %1\nresult: %2\nerror: %3\n" ).arg( sql ).arg( res.PQresultStatus() ).arg( res.PQresultErrorMessage() ), tr( "PostGIS" ) ); - } - - return rasterColumn; -} diff --git a/src/providers/postgres/qgspostgresconn.h b/src/providers/postgres/qgspostgresconn.h index e54bb50ea0b2..ea38d2806626 100644 --- a/src/providers/postgres/qgspostgresconn.h +++ b/src/providers/postgres/qgspostgresconn.h @@ -424,15 +424,6 @@ class QgsPostgresConn : public QObject */ bool getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema = QString(), const QString &name = QString() ); - /** - * Gets information about rater column name for raster table - * \param schema - * \param table - * \returns raster column name or empty string - * \since QGIS 3.42 - */ - QString rasterColumnName( const QString &schema, const QString &table ); - qint64 getBinaryInt( QgsPostgresResult &queryResult, int row, int col ); QString fieldExpressionForWhereClause( const QgsField &fld, QMetaType::Type valueType = QMetaType::Type::UnknownType, QString expr = "%1" ); From 27a26d947d202c7e1e668bfc51252b4e80b125c9 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 9 Jan 2025 09:15:50 +0100 Subject: [PATCH 08/10] avoid clang warning --- .../postgres/raster/qgspostgresrasterprovider.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp index 02d616c52aa3..7190c736ae13 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp @@ -2473,15 +2473,7 @@ bool QgsPostgresRasterProviderMetadata::styleExists( const QString &uri, const Q return false; } - if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) - { - return false; - } - else if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) - { - return false; - } - else if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "r_raster_column" ) ) ) + if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) || !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) || !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "r_raster_column" ) ) ) { return false; } From c71b445e409d4bebc720ccfd915e650675e16423 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 9 Jan 2025 09:16:23 +0100 Subject: [PATCH 09/10] use uri geometryColumn() instead of custom function --- .../raster/qgspostgresrasterprovider.cpp | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp index 7190c736ae13..a7db889b95ea 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.cpp +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.cpp @@ -2483,8 +2483,6 @@ bool QgsPostgresRasterProviderMetadata::styleExists( const QString &uri, const Q dsUri.setDatabase( conn->currentDatabase() ); } - QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); - const QString checkQuery = QString( "SELECT styleName" " FROM layer_styles" " WHERE f_table_catalog=%1" @@ -2499,7 +2497,7 @@ bool QgsPostgresRasterProviderMetadata::styleExists( const QString &uri, const Q .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) .arg( QgsPostgresConn::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), checkQuery ) ); if ( res.PQresultStatus() == PGRES_TUPLES_OK ) @@ -2530,8 +2528,6 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt return false; } - QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); - if ( !QgsPostgresUtils::tableExists( conn, QStringLiteral( "layer_styles" ) ) ) { if ( !QgsPostgresUtils::createStylesTable( conn, QStringLiteral( "QgsPostgresRasterProviderMetadata" ) ) ) @@ -2600,7 +2596,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( uiFileColumn ) .arg( uiFileValue ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ) + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) // Must be the final .arg replacement - see above .arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ); @@ -2619,7 +2615,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresRasterProviderMetadata", checkQuery ) ); if ( res.PQntuples() > 0 ) @@ -2648,7 +2644,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) // Must be the final .arg replacement - see above .arg( QgsPostgresConn::quotedValue( qmlStyle ), QgsPostgresConn::quotedValue( sldStyle ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); } if ( useAsDefault ) @@ -2665,7 +2661,7 @@ bool QgsPostgresRasterProviderMetadata::saveStyle( const QString &uri, const QSt .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql ); } @@ -2715,8 +2711,6 @@ QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, return QString(); } - QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); - // support layer_styles without type column < 3.14 if ( !QgsPostgresUtils::columnExists( conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) { @@ -2732,7 +2726,7 @@ QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, .arg( QgsPostgresConn::quotedValue( dsUri.database() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); } else { @@ -2750,7 +2744,7 @@ QString QgsPostgresRasterProviderMetadata::loadStoredStyle( const QString &uri, .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); } QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectQmlQuery ) ); @@ -2791,8 +2785,6 @@ int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringLi dsUri.setDatabase( conn->currentDatabase() ); } - QString rasterColumn = conn->rasterColumnName( dsUri.schema(), dsUri.table() ); - QString selectRelatedQuery = QString( "SELECT id,styleName,description" " FROM layer_styles" " WHERE f_table_catalog=%1" @@ -2806,7 +2798,7 @@ int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringLi .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectRelatedQuery ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) @@ -2833,7 +2825,7 @@ int QgsPostgresRasterProviderMetadata::listStyles( const QString &uri, QStringLi .arg( QgsPostgresConn::quotedValue( dsUri.schema() ) ) .arg( QgsPostgresConn::quotedValue( dsUri.table() ) ) .arg( QgsPostgresConn::quotedValue( mType ) ) - .arg( QgsPostgresConn::quotedValue( rasterColumn ) ); + .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ); result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresRasterProviderMetadata" ), selectOthersQuery ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) From 5d0fe6fb299dd6d2b9edf654606b6bc6bdc42ecd Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 17 Jan 2025 11:37:10 +0100 Subject: [PATCH 10/10] fix docstring --- src/providers/postgres/raster/qgspostgresrasterprovider.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.h b/src/providers/postgres/raster/qgspostgresrasterprovider.h index 132366d4637e..63de717e8d53 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.h +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.h @@ -265,7 +265,7 @@ class QgsPostgresRasterProviderMetadata : public QgsProviderMetadata bool saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage ) override; QgsProviderMetadata::ProviderCapabilities providerCapabilities() const override; - // These functions are very + // These functions are very similar to functions in QgsPostgresProviderMetadata with some minor adjustments bool styleExists( const QString &uri, const QString &styleId, QString &errorCause ) override; bool saveStyle( const QString &uri, const QString &qmlStyle, const QString &sldStyle, const QString &styleName, const QString &styleDescription, const QString &uiFileContent, bool useAsDefault, QString &errCause ) override; QString loadStyle( const QString &uri, QString &errCause ) override;