/*************************************************************************** qgspostgresconn.h - connection class to PostgreSQL/PostGIS ------------------- begin : 2011/01/28 copyright : (C) 2011 by Juergen E. Fischer email : jef at norbit dot de ***************************************************************************/ /*************************************************************************** * * * 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 QGSPOSTGRESCONN_H #define QGSPOSTGRESCONN_H #include #include #include #include #include #include "qgis.h" #include "qgsdatasourceuri.h" #include "qgswkbtypes.h" #include "qgsconfig.h" #include "qgsvectordataprovider.h" extern "C" { #include } class QgsField; //! Spatial column types enum QgsPostgresGeometryColumnType { SctNone, SctGeometry, SctGeography, SctTopoGeometry, SctPcPatch, SctRaster }; enum QgsPostgresPrimaryKeyType { PktUnknown, PktInt, PktInt64, PktUint64, PktTid, PktOid, PktFidMap }; //! Schema properties structure struct QgsPostgresSchemaProperty { QString name; QString description; QString owner; }; //! Layer Property structure // TODO: Fill to Postgres/PostGIS specifications struct QgsPostgresLayerProperty { // Postgres/PostGIS layer properties QList types; QString schemaName; QString tableName; QString geometryColName; QgsPostgresGeometryColumnType geometryColType; QStringList pkCols; QList srids; unsigned int nSpCols; QString sql; Qgis::PostgresRelKind relKind = Qgis::PostgresRelKind::Unknown; bool isRaster = false; QString tableComment; // TODO: rename this ! int size() const { Q_ASSERT( types.size() == srids.size() ); return types.size(); } QString defaultName() const { QString n = tableName; if ( nSpCols > 1 ) n += '.' + geometryColName; return n; } QgsPostgresLayerProperty at( int i ) const { QgsPostgresLayerProperty property; Q_ASSERT( i >= 0 && i < size() ); property.types << types[ i ]; property.srids << srids[ i ]; property.schemaName = schemaName; property.tableName = tableName; property.geometryColName = geometryColName; property.geometryColType = geometryColType; property.pkCols = pkCols; property.nSpCols = nSpCols; property.sql = sql; property.relKind = relKind; property.isRaster = isRaster; property.tableComment = tableComment; return property; } #ifdef QGISDEBUG QString toString() const { QString typeString; const auto constTypes = types; for ( const Qgis::WkbType type : constTypes ) { if ( !typeString.isEmpty() ) typeString += '|'; typeString += QString::number( static_cast< quint32>( type ) ); } QString sridString; const auto constSrids = srids; for ( const int srid : constSrids ) { if ( !sridString.isEmpty() ) sridString += '|'; sridString += QString::number( srid ); } return QStringLiteral( "%1.%2.%3 type=%4 srid=%5 pkCols=%6 sql=%7 nSpCols=%8" ) .arg( schemaName, tableName, geometryColName, typeString, sridString, pkCols.join( QLatin1Char( '|' ) ), sql ) .arg( nSpCols ); } #endif }; class QgsPostgresResult { public: explicit QgsPostgresResult( PGresult *result = nullptr ) : mRes( result ) {} ~QgsPostgresResult(); QgsPostgresResult &operator=( PGresult *result ); QgsPostgresResult &operator=( const QgsPostgresResult &src ); QgsPostgresResult( const QgsPostgresResult &rh ) = delete; ExecStatusType PQresultStatus(); QString PQresultErrorMessage(); int PQntuples(); QString PQgetvalue( int row, int col ); bool PQgetisnull( int row, int col ); int PQnfields(); QString PQfname( int col ); Oid PQftable( int col ); Oid PQftype( int col ); int PQfmod( int col ); int PQftablecol( int col ); Oid PQoidValue(); PGresult *result() const { return mRes; } private: PGresult *mRes = nullptr; }; struct PGException { explicit PGException( QgsPostgresResult &r ) : mWhat( r.PQresultErrorMessage() ) {} QString errorMessage() const { return mWhat; } private: QString mWhat; }; //! Wraps acquireConnection() and releaseConnection() from a QgsPostgresConnPool. // This can be used for creating std::shared_ptr. class QgsPoolPostgresConn { class QgsPostgresConn *mPgConn; public: QgsPoolPostgresConn( const QString &connInfo ); ~QgsPoolPostgresConn(); class QgsPostgresConn *get() const { return mPgConn; } }; #include "qgsconfig.h" constexpr int sPostgresConQueryLogFilePrefixLength = CMAKE_SOURCE_DIR[sizeof( CMAKE_SOURCE_DIR ) - 1] == '/' ? sizeof( CMAKE_SOURCE_DIR ) + 1 : sizeof( CMAKE_SOURCE_DIR ); #define LoggedPQexecNR(_class, query) PQexecNR( query, _class, QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) #define LoggedPQexec(_class, query) PQexec( query, true, true, _class, QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) #define LoggedPQexecNoLogError(_class, query ) PQexec( query, false, true, _class, QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) class QgsPostgresConn : public QObject { Q_OBJECT public: /** * Get a new PostgreSQL connection * * \param connInfo the QgsDataSourceUri connection info with username / password * \param readOnly is the connection read only ? * \param shared allow using a shared connection if called from the same thread as the main one. * \param allowRequestCredentials allow credentials request through current QgsCredentials instance if the provided ones are not valid * * \returns the PostgreSQL connection */ static QgsPostgresConn *connectDb( const QString &connInfo, bool readOnly, bool shared = true, bool transaction = false, bool allowRequestCredentials = true ); /** * Get a new PostgreSQL connection * * \param uri the QgsDataSourceUri with username / password * \param readOnly is the connection read only ? * \param shared allow using a shared connection if called from the same thread as the main one. * \param transaction is the connection a transaction ? * \param allowRequestCredentials allow credentials request through current QgsCredentials instance if the provided ones are not valid * * \returns the PostgreSQL connection */ static QgsPostgresConn *connectDb( const QgsDataSourceUri &uri, bool readOnly, bool shared = true, bool transaction = false, bool allowRequestCredentials = true ); QgsPostgresConn( const QString &conninfo, bool readOnly, bool shared, bool transaction, bool allowRequestCredentials = true ); ~QgsPostgresConn() override; void ref(); void unref(); /** * Returns the URI associated with the connection. */ const QgsDataSourceUri &uri() const { return mUri; } //! Gets postgis version string QString postgisVersion() const; //! Gets status of GEOS capability bool hasGEOS() const; //! Gets status of topology capability bool hasTopology() const; //! Gets status of Pointcloud capability bool hasPointcloud() const; //! Gets status of Raster capability bool hasRaster() const; //! encode wkb in hex bool useWkbHex() const { return mUseWkbHex; } //! major PostGIS version int majorVersion() const { return mPostgisVersionMajor; } //! minor PostGIS version int minorVersion() const { return mPostgisVersionMinor; } //! PostgreSQL version int pgVersion() const { return mPostgresqlVersion; } /** * Sets the current user identifier of the current PostgreSQL session * * \param sessionRole the PostgreSQL ROLE for the session, it must be a role that the current session user is a member of. * * \returns TRUE if successful * * \since QGIS 3.28.0 */ bool setSessionRole( const QString &sessionRole ); /** * Resets the current user identifier of the current PostgreSQL session * to the current session user identifier (user used to log in) * * \returns TRUE if successful * * \since QGIS 3.28.0 */ bool resetSessionRole(); //! run a query and free result buffer bool PQexecNR( const QString &query, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); //! cursor handling bool openCursor( const QString &cursorName, const QString &declare ); bool closeCursor( const QString &cursorName ); QString uniqueCursorName(); #if 0 PGconn *pgConnection() { return mConn; } #endif // // libpq wrapper // // run a query and check for errors, thread-safe PGresult *PQexec( const QString &query, bool logError = true, bool retry = true, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ) const; int PQCancel(); void PQfinish(); QString PQerrorMessage() const; int PQstatus() const; PGresult *PQprepare( const QString &stmtName, const QString &query, int nParams, const Oid *paramTypes, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); PGresult *PQexecPrepared( const QString &stmtName, const QStringList ¶ms, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); /** * PQsendQuery is used for asynchronous queries (with PQgetResult) * Thread safety must be ensured by the caller by calling QgsPostgresConn::lock() and QgsPostgresConn::unlock() */ int PQsendQuery( const QString &query ); /** * PQgetResult is used for asynchronous queries (with PQsendQuery) * Thread safety must be ensured by the caller by calling QgsPostgresConn::lock() and QgsPostgresConn::unlock() */ PGresult *PQgetResult(); bool begin(); bool commit(); bool rollback(); // cancel running query bool cancel(); /** * Double quote a PostgreSQL identifier for placement in a SQL string. */ static QString quotedIdentifier( const QString &ident ); /** * Quote a value for placement in a SQL string. */ static QString quotedValue( const QVariant &value ); /** * Quote a json(b) value for placement in a SQL string. * \note a null value will be represented as a NULL and not as a json null. */ static QString quotedJsonValue( const QVariant &value ); /** * Returns the RelKind associated with a value from the relkind column * in pg_class. */ static Qgis::PostgresRelKind relKindFromValue( const QString &value ); /** * Gets the list of supported layers * \param layers list to store layers in * \param searchGeometryColumnsOnly only look for geometry columns which are * contained in the geometry_columns metatable * \param searchPublicOnly * \param allowGeometrylessTables * \param schema restrict layers to layers within specified schema * \returns true if layers were fetched successfully */ bool supportedLayers( QVector &layers, bool searchGeometryColumnsOnly = true, bool searchPublicOnly = true, bool allowGeometrylessTables = false, const QString &schema = QString() ); /** * Gets the list of database schemas * \param schemas list to store schemas in * \returns true if schemas where fetched successfully * \since QGIS 2.7 */ bool getSchemas( QList &schemas ); /** * Determine type and srid of a layer from data (possibly estimated) */ void retrieveLayerTypes( QgsPostgresLayerProperty &layerProperty, bool useEstimatedMetadata, QgsFeedback *feedback = nullptr ); /** * Determine type and srid of a vector of layers from data (possibly estimated) */ void retrieveLayerTypes( QVector &layerProperties, bool useEstimatedMetadata, QgsFeedback *feedback = nullptr ); /** * Gets information about the spatial tables * \param searchGeometryColumnsOnly only look for geometry columns which are * contained in the geometry_columns metatable * \param searchPublicOnly * \param allowGeometrylessTables * \param schema restrict tables to those within specified schema * \returns true if tables were successfully queried */ bool getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema = QString() ); qint64 getBinaryInt( QgsPostgresResult &queryResult, int row, int col ); QString fieldExpressionForWhereClause( const QgsField &fld, QVariant::Type valueType = QVariant::LastType, QString expr = "%1" ); QString fieldExpression( const QgsField &fld, QString expr = "%1" ); QString connInfo() const { return mConnInfo; } /** * Returns a list of supported native types for this connection. * \since QGIS 3.16 */ QList nativeTypes(); /** * Returns the underlying database. * * \since QGIS 3.0 */ QString currentDatabase() const; static const int GEOM_TYPE_SELECT_LIMIT; static QString displayStringForWkbType( Qgis::WkbType wkbType ); static QString displayStringForGeomType( QgsPostgresGeometryColumnType geomType ); static Qgis::WkbType wkbTypeFromPostgis( const QString &dbType ); static QString postgisWkbTypeName( Qgis::WkbType wkbType ); static int postgisWkbTypeDim( Qgis::WkbType wkbType ); static void postgisWkbType( Qgis::WkbType wkbType, QString &geometryType, int &dim ); static QString postgisTypeFilter( QString geomCol, Qgis::WkbType wkbType, bool castToGeometry ); static Qgis::WkbType wkbTypeFromGeomType( Qgis::GeometryType geomType ); static Qgis::WkbType wkbTypeFromOgcWkbType( unsigned int ogcWkbType ); static QStringList connectionList(); static QString selectedConnection(); static void setSelectedConnection( const QString &connName ); static QgsDataSourceUri connUri( const QString &connName ); static bool publicSchemaOnly( const QString &connName ); static bool geometryColumnsOnly( const QString &connName ); static bool dontResolveType( const QString &connName ); static bool useEstimatedMetadata( const QString &connName ); static bool allowGeometrylessTables( const QString &connName ); static bool allowProjectsInDatabase( const QString &connName ); static void deleteConnection( const QString &connName ); static bool allowMetadataInDatabase( const QString &connName ); //! A connection needs to be locked when it uses transactions, see QgsPostgresConn::{begin,commit,rollback} void lock() { mLock.lock(); } void unlock() { mLock.unlock(); } QgsCoordinateReferenceSystem sridToCrs( int srsId ); int crsToSrid( const QgsCoordinateReferenceSystem &crs ); private: int mRef; int mOpenCursors; PGconn *mConn = nullptr; QString mConnInfo; QgsDataSourceUri mUri; //! GEOS capability mutable bool mGeosAvailable; //! PROJ capability mutable bool mProjAvailable; //! Topology capability mutable bool mTopologyAvailable; //! PostGIS version string mutable QString mPostgisVersionInfo; //! Are mPostgisVersionMajor, mPostgisVersionMinor, mGeosAvailable, mTopologyAvailable valid? mutable bool mGotPostgisVersion; //! PostgreSQL version mutable int mPostgresqlVersion; //! PostGIS major version mutable int mPostgisVersionMajor; //! PostGIS minor version mutable int mPostgisVersionMinor; //! pointcloud support available mutable bool mPointcloudAvailable; //! raster support available mutable bool mRasterAvailable; //! encode wkb in hex mutable bool mUseWkbHex; bool mReadOnly; QStringList supportedSpatialTypes() const; static QMap sConnectionsRW; static QMap sConnectionsRO; //! Count number of spatial columns in a given relation void addColumnInfo( QgsPostgresLayerProperty &layerProperty, const QString &schemaName, const QString &viewName, bool fetchPkCandidates ); //! List of the supported layers QVector mLayersSupported; /** * Flag indicating whether data from binary cursors must undergo an * endian conversion prior to use * \note * * XXX Umm, it'd be helpful to know what we're swapping from and to. * XXX Presumably this means swapping from big-endian (network) byte order * XXX to little-endian; but the inverse transaction is possible, too, and * XXX that's not reflected in this variable */ bool mSwapEndian; void deduceEndian(); static QAtomicInt sNextCursorId; bool mShared; //!< Whether the connection is shared by more providers (must not be if going to be used in worker threads) bool mTransaction; QString mCurrentSessionRole; mutable QRecursiveMutex mLock; /* Mutex protecting sCrsCache */ QMutex mCrsCacheMutex; /* Cache of SRID to CRS */ mutable QMap mCrsCache; }; // clazy:excludeall=qstring-allocations #endif