diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 22008565cdb8..c7a0208006a9 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -758,7 +758,7 @@ bool QgsPostgresProvider::loadFields() } - QMap > fmtFieldTypeMap, descrMap, defValMap; + QMap > fmtFieldTypeMap, descrMap, defValMap, identityMap; QMap > attTypeIdMap; QMap > notNullMap, uniqueMap; if ( result.PQnfields() > 0 ) @@ -785,14 +785,17 @@ bool QgsPostgresProvider::loadFields() QString tableoidsFilter = '(' + tableoidsList.join( QStringLiteral( "," ) ) + ')'; // Collect formatted field types - sql = "SELECT attrelid, attnum, pg_catalog.format_type(atttypid,atttypmod), pg_catalog.col_description(attrelid,attnum), pg_catalog.pg_get_expr(adbin,adrelid), atttypid, attnotnull::int, indisunique::int" - " FROM pg_attribute" - " LEFT OUTER JOIN pg_attrdef ON attrelid=adrelid AND attnum=adnum" + sql = QStringLiteral( + "SELECT attrelid, attnum, pg_catalog.format_type(atttypid,atttypmod), pg_catalog.col_description(attrelid,attnum), pg_catalog.pg_get_expr(adbin,adrelid), atttypid, attnotnull::int, indisunique::int%1" + " FROM pg_attribute" + " LEFT OUTER JOIN pg_attrdef ON attrelid=adrelid AND attnum=adnum" - // find unique constraints if present. Text cast required to handle int2vector comparison. Distinct required as multiple unique constraints may exist - " LEFT OUTER JOIN ( SELECT DISTINCT indrelid, indkey, indisunique FROM pg_index WHERE indisunique ) uniq ON attrelid=indrelid AND attnum::text=indkey::text " + // find unique constraints if present. Text cast required to handle int2vector comparison. Distinct required as multiple unique constraints may exist + " LEFT OUTER JOIN ( SELECT DISTINCT indrelid, indkey, indisunique FROM pg_index WHERE indisunique ) uniq ON attrelid=indrelid AND attnum::text=indkey::text " + + " WHERE attrelid IN %2" + ).arg( connectionRO()->pgVersion() >= 100000 ? QStringLiteral( ", attidentity" ) : QString() ).arg( tableoidsFilter ); - " WHERE attrelid IN " + tableoidsFilter; QgsPostgresResult fmtFieldTypeResult( connectionRO()->PQexec( sql ) ); for ( int i = 0; i < fmtFieldTypeResult.PQntuples(); ++i ) { @@ -804,18 +807,21 @@ bool QgsPostgresProvider::loadFields() int attType = fmtFieldTypeResult.PQgetvalue( i, 5 ).toInt(); bool attNotNull = fmtFieldTypeResult.PQgetvalue( i, 6 ).toInt(); bool uniqueConstraint = fmtFieldTypeResult.PQgetvalue( i, 7 ).toInt(); + QString attIdentity = connectionRO()->pgVersion() >= 100000 ? fmtFieldTypeResult.PQgetvalue( i, 8 ) : " "; fmtFieldTypeMap[attrelid][attnum] = formatType; descrMap[attrelid][attnum] = descr; defValMap[attrelid][attnum] = defVal; attTypeIdMap[attrelid][attnum] = attType; notNullMap[attrelid][attnum] = attNotNull; uniqueMap[attrelid][attnum] = uniqueConstraint; + identityMap[attrelid][attnum] = attIdentity.isEmpty() ? " " : attIdentity; } } } QSet fields; mAttributeFields.clear(); + mIdentityFields.clear(); for ( int i = 0; i < result.PQnfields(); i++ ) { QString fieldName = result.PQfname( i ); @@ -1054,6 +1060,7 @@ bool QgsPostgresProvider::loadFields() constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider ); newField.setConstraints( constraints ); + mIdentityFields.insert( mAttributeFields.size(), identityMap[tableoid][attnum][0].toLatin1() ); mAttributeFields.append( newField ); } @@ -1329,19 +1336,40 @@ bool QgsPostgresProvider::determinePrimaryKey() QgsDebugMsg( QStringLiteral( "Relation is a table. Checking to see if it has an oid column." ) ); mPrimaryKeyAttrs.clear(); + mPrimaryKeyType = PktUnknown; - // If there is an oid on the table, use that instead, - sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) ); + if ( connectionRO()->pgVersion() >= 100000 ) + { + // If there is an generated id on the table, use that instead, + sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attidentity IN ('a','d') AND attrelid=regclass(%1) LIMIT 1" ).arg( quotedValue( mQuery ) ); + res = connectionRO()->PQexec( sql ); + if ( res.PQntuples() == 1 ) + { + // Could warn the user here that performance will suffer if + // attribute isn't indexed (and that they may want to add a + // primary key to the table) + int idx = fieldNameIndex( res.PQgetvalue( 0, 0 ) ); + mPrimaryKeyType = pkType( mAttributeFields.at( idx ) ); + mPrimaryKeyAttrs << idx; + } + } - res = connectionRO()->PQexec( sql ); - if ( res.PQntuples() == 1 ) + if ( mPrimaryKeyType == PktUnknown ) { - // Could warn the user here that performance will suffer if - // oid isn't indexed (and that they may want to add a - // primary key to the table) - mPrimaryKeyType = PktOid; + // If there is an oid on the table, use that instead, + sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) ); + + res = connectionRO()->PQexec( sql ); + if ( res.PQntuples() == 1 ) + { + // Could warn the user here that performance will suffer if + // oid isn't indexed (and that they may want to add a + // primary key to the table) + mPrimaryKeyType = PktOid; + } } - else + + if ( mPrimaryKeyType == PktUnknown ) { sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) ); @@ -1353,10 +1381,11 @@ bool QgsPostgresProvider::determinePrimaryKey() QgsMessageLog::logMessage( tr( "Primary key is ctid - changing of existing features disabled (%1; %2)" ).arg( mGeometryColumn, mQuery ) ); mEnabledCapabilities &= ~( QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::ChangeFeatures ); } - else - { - QgsMessageLog::logMessage( tr( "The table has no column suitable for use as a key. QGIS requires a primary key, a PostgreSQL oid column or a ctid for tables." ), tr( "PostGIS" ) ); - } + } + + if ( mPrimaryKeyType == PktUnknown ) + { + QgsMessageLog::logMessage( tr( "The table has no column suitable for use as a key. QGIS requires a primary key, a PostgreSQL oid column or a ctid for tables." ), tr( "PostGIS" ) ); } } else if ( type == Relkind::View || type == Relkind::MaterializedView ) @@ -2026,7 +2055,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) // Prepare the INSERT statement QString insert = QStringLiteral( "INSERT INTO %1(" ).arg( mQuery ); - QString values = QStringLiteral( ") VALUES (" ); + QString values; QString delim; int offset = 1; @@ -2046,6 +2075,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) // is a sequence, and that none of the features have a value set for that // column, then we can completely omit inserting it. bool skipSinglePKField = false; + bool overrideIdentity = false; if ( ( mPrimaryKeyType == PktInt || mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktUint64 ) ) { @@ -2074,6 +2104,8 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { for ( int idx : mPrimaryKeyAttrs ) { + if ( mIdentityFields[idx] == 'a' ) + overrideIdentity = true; insert += delim + quotedIdentifier( field( idx ).name() ); values += delim + QStringLiteral( "$%1" ).arg( defaultValues.size() + offset ); delim = ','; @@ -2118,6 +2150,9 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) insert += delim + quotedIdentifier( fieldname ); + if ( mIdentityFields[idx] == 'a' ) + overrideIdentity = true; + QString defVal = defaultValueClause( idx ); if ( i == flist.size() ) @@ -2182,7 +2217,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) delim = ','; } - insert += values + ')'; + insert += QStringLiteral( ") %1VALUES (%2)" ).arg( overrideIdentity ? "OVERRIDING SYSTEM VALUE " : "" ).arg( values ); if ( !( flags & QgsFeatureSink::FastInsert ) ) { diff --git a/src/providers/postgres/qgspostgresprovider.h b/src/providers/postgres/qgspostgresprovider.h index f2b2af599292..2d73729d2a88 100644 --- a/src/providers/postgres/qgspostgresprovider.h +++ b/src/providers/postgres/qgspostgresprovider.h @@ -323,6 +323,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider QgsAttrPalIndexNameHash mAttrPalIndexName; QgsFields mAttributeFields; + QHash mIdentityFields; QString mDataComment; //! Data source URI struct for this layer