Skip to content

Commit

Permalink
postgres provider: add support for compound keys on views
Browse files Browse the repository at this point in the history
  • Loading branch information
jef-n committed Sep 4, 2015
1 parent e3b9a98 commit c5cce4b
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 43 deletions.
54 changes: 46 additions & 8 deletions src/providers/postgres/qgspgsourceselect.cpp
Expand Up @@ -76,7 +76,20 @@ QWidget *QgsPgSourceSelectDelegate::createEditor( QWidget *parent, const QStyleO
if ( values.size() > 0 ) if ( values.size() > 0 )
{ {
QComboBox *cb = new QComboBox( parent ); QComboBox *cb = new QComboBox( parent );
cb->addItems( values );
QStandardItemModel *model = new QStandardItemModel( values.size(), 1, cb );

int row = 0;
foreach ( QString value, values )
{
QStandardItem *item = new QStandardItem( value );
item->setFlags( Qt::ItemIsUserCheckable | Qt::ItemIsEnabled );
item->setData( Qt::Unchecked, Qt::CheckStateRole );
model->setItem( row++, 0, item );
}

cb->setModel( model );

return cb; return cb;
} }
} }
Expand All @@ -101,8 +114,25 @@ void QgsPgSourceSelectDelegate::setEditorData( QWidget *editor, const QModelInde
if ( index.column() == QgsPgTableModel::dbtmType ) if ( index.column() == QgsPgTableModel::dbtmType )
cb->setCurrentIndex( cb->findData( index.data( Qt::UserRole + 2 ).toInt() ) ); cb->setCurrentIndex( cb->findData( index.data( Qt::UserRole + 2 ).toInt() ) );


if ( index.column() == QgsPgTableModel::dbtmPkCol && !index.data( Qt::UserRole + 2 ).toString().isEmpty() ) if ( index.column() == QgsPgTableModel::dbtmPkCol && !index.data( Qt::UserRole + 2 ).toStringList().isEmpty() )
cb->setCurrentIndex( cb->findText( index.data( Qt::UserRole + 2 ).toString() ) ); {
QStringList cols = index.data( Qt::UserRole + 2 ).toStringList();

foreach ( QString col, cols )
{
QStandardItemModel *cbm = qobject_cast<QStandardItemModel*>( cb->model() );
for ( int idx = 0; idx < cbm->rowCount(); idx++ )
{
QStandardItem *item = cbm->item( idx, 0 );
if ( item->text() != col )
continue;

item->setData( Qt::Checked, Qt::CheckStateRole );
break;
}
}

}
} }


QLineEdit *le = qobject_cast<QLineEdit*>( editor ); QLineEdit *le = qobject_cast<QLineEdit*>( editor );
Expand Down Expand Up @@ -132,9 +162,17 @@ void QgsPgSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMode
} }
else if ( index.column() == QgsPgTableModel::dbtmPkCol ) else if ( index.column() == QgsPgTableModel::dbtmPkCol )
{ {
QString value( cb->currentText() ); QStandardItemModel *cbm = qobject_cast<QStandardItemModel*>( cb->model() );
model->setData( index, value.isEmpty() ? tr( "Select..." ) : value ); QStringList cols;
model->setData( index, value, Qt::UserRole + 2 ); for ( int idx = 0; idx < cbm->rowCount(); idx++ )
{
QStandardItem *item = cbm->item( idx, 0 );
if ( item->data( Qt::CheckStateRole ) == Qt::Checked )
cols << item->text();
}

model->setData( index, cols.isEmpty() ? tr( "Select..." ) : cols.join( ", " ) );
model->setData( index, cols, Qt::UserRole + 2 );
} }
} }


Expand Down Expand Up @@ -196,7 +234,7 @@ QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, bool
mSearchColumnComboBox->addItem( tr( "Table" ) ); mSearchColumnComboBox->addItem( tr( "Table" ) );
mSearchColumnComboBox->addItem( tr( "Type" ) ); mSearchColumnComboBox->addItem( tr( "Type" ) );
mSearchColumnComboBox->addItem( tr( "Geometry column" ) ); mSearchColumnComboBox->addItem( tr( "Geometry column" ) );
mSearchColumnComboBox->addItem( tr( "Primary key column" ) ); mSearchColumnComboBox->addItem( tr( "Feature id" ) );
mSearchColumnComboBox->addItem( tr( "SRID" ) ); mSearchColumnComboBox->addItem( tr( "SRID" ) );
mSearchColumnComboBox->addItem( tr( "Sql" ) ); mSearchColumnComboBox->addItem( tr( "Sql" ) );


Expand Down Expand Up @@ -378,7 +416,7 @@ void QgsPgSourceSelect::on_mSearchColumnComboBox_currentIndexChanged( const QStr
{ {
mProxyModel.setFilterKeyColumn( QgsPgTableModel::dbtmGeomCol ); mProxyModel.setFilterKeyColumn( QgsPgTableModel::dbtmGeomCol );
} }
else if ( text == tr( "Primary key column" ) ) else if ( text == tr( "Feature id" ) )
{ {
mProxyModel.setFilterKeyColumn( QgsPgTableModel::dbtmPkCol ); mProxyModel.setFilterKeyColumn( QgsPgTableModel::dbtmPkCol );
} }
Expand Down
53 changes: 36 additions & 17 deletions src/providers/postgres/qgspgtablemodel.cpp
Expand Up @@ -32,7 +32,7 @@ QgsPgTableModel::QgsPgTableModel()
headerLabels << tr( "Data Type" ); headerLabels << tr( "Data Type" );
headerLabels << tr( "Spatial Type" ); headerLabels << tr( "Spatial Type" );
headerLabels << tr( "SRID" ); headerLabels << tr( "SRID" );
headerLabels << tr( "Primary Key" ); headerLabels << tr( "Feature id" );
headerLabels << tr( "Select at id" ); headerLabels << tr( "Select at id" );
headerLabels << tr( "Sql" ); headerLabels << tr( "Sql" );
setHorizontalHeaderLabels( headerLabels ); setHorizontalHeaderLabels( headerLabels );
Expand Down Expand Up @@ -62,15 +62,15 @@ void QgsPgTableModel::addTableEntry( const QgsPostgresLayerProperty& layerProper
QString tip; QString tip;
if ( wkbType == QGis::WKBUnknown ) if ( wkbType == QGis::WKBUnknown )
{ {
tip = tr( "Specify a geometry type" ); tip = tr( "Specify a geometry type in the '%1' column" ).arg( tr( "Data Type" ) );
} }
else if ( wkbType != QGis::WKBNoGeometry && srid == INT_MIN ) else if ( wkbType != QGis::WKBNoGeometry && srid == INT_MIN )
{ {
tip = tr( "Enter a SRID" ); tip = tr( "Enter a SRID into the '%1' column" ).arg( tr( "SRID" ) );
} }
else if ( layerProperty.pkCols.size() > 0 ) else if ( layerProperty.pkCols.size() > 0 )
{ {
tip = tr( "Select a primary key" ); tip = tr( "Select columns in the '%1' column that uniquely identify features of this layer" ).arg( tr( "Feature id" ) );
} }


QStandardItem *schemaNameItem = new QStandardItem( layerProperty.schemaName ); QStandardItem *schemaNameItem = new QStandardItem( layerProperty.schemaName );
Expand Down Expand Up @@ -127,16 +127,18 @@ void QgsPgTableModel::addTableEntry( const QgsPostgresLayerProperty& layerProper
{ {
if ( tip.isEmpty() ) if ( tip.isEmpty() )
{ {
item->setFlags( item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); item->setFlags( item->flags() | Qt::ItemIsSelectable );
item->setToolTip( "" ); item->setToolTip( "" );
} }
else else
{ {
item->setFlags( item->flags() & ~Qt::ItemIsSelectable ); item->setFlags( item->flags() & ~Qt::ItemIsSelectable );


if ( item == schemaNameItem )
item->setData( QgsApplication::getThemeIcon( "/mIconWarn.png" ), Qt::DecorationRole );

if ( item == schemaNameItem || item == tableItem || item == geomItem ) if ( item == schemaNameItem || item == tableItem || item == geomItem )
{ {
item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
item->setToolTip( tip ); item->setToolTip( tip );
} }
} }
Expand Down Expand Up @@ -267,38 +269,49 @@ bool QgsPgTableModel::setData( const QModelIndex &idx, const QVariant &value, in
QString tip; QString tip;
if ( wkbType == QGis::WKBUnknown ) if ( wkbType == QGis::WKBUnknown )
{ {
tip = tr( "Specify a geometry type" ); tip = tr( "Specify a geometry type in the '%1' column" ).arg( tr( "Data Type" ) );
} }
else if ( wkbType != QGis::WKBNoGeometry ) else if ( wkbType != QGis::WKBNoGeometry )
{ {
bool ok; bool ok;
int srid = idx.sibling( idx.row(), dbtmSrid ).data().toInt( &ok ); int srid = idx.sibling( idx.row(), dbtmSrid ).data().toInt( &ok );


if ( !ok || srid == INT_MIN ) if ( !ok || srid == INT_MIN )
tip = tr( "Enter a SRID" ); tip = tr( "Enter a SRID into the '%1' column" ).arg( tr( "SRID" ) );
} }


QStringList pkCols = idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 1 ).toStringList(); QStringList pkCols = idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 1 ).toStringList();
if ( tip.isEmpty() && pkCols.size() > 0 ) if ( tip.isEmpty() && pkCols.size() > 0 )
{ {
if ( !pkCols.contains( idx.sibling( idx.row(), dbtmPkCol ).data().toString() ) ) QSet<QString> s0( idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 2 ).toStringList().toSet() );
tip = tr( "Select a primary key" ); QSet<QString> s1( pkCols.toSet() );
if ( s0.intersect( s1 ).isEmpty() )
tip = tr( "Select columns in the '%1' column that uniquely identify features of this layer" ).arg( tr( "Feature id" ) );
} }


for ( int i = 0; i < dbtmColumns; i++ ) for ( int i = 0; i < dbtmColumns; i++ )
{ {
QStandardItem *item = itemFromIndex( idx.sibling( idx.row(), i ) ); QStandardItem *item = itemFromIndex( idx.sibling( idx.row(), i ) );
if ( tip.isEmpty() ) if ( tip.isEmpty() )
{ {
item->setFlags( item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); if ( i == dbtmSchema )
{
item->setData( QVariant(), Qt::DecorationRole );
}

item->setFlags( item->flags() | Qt::ItemIsSelectable );
item->setToolTip( "" ); item->setToolTip( "" );
} }
else else
{ {
item->setFlags( item->flags() & ~Qt::ItemIsSelectable ); item->setFlags( item->flags() & ~Qt::ItemIsSelectable );

if ( i == dbtmSchema )
item->setData( QgsApplication::getThemeIcon( "/mIconWarn.png" ), Qt::DecorationRole );

if ( i == dbtmSchema || i == dbtmTable || i == dbtmGeomCol ) if ( i == dbtmSchema || i == dbtmTable || i == dbtmGeomCol )
{ {
item->setFlags( item->flags() & ~Qt::ItemIsEnabled ); item->setFlags( item->flags() );
item->setToolTip( tip ); item->setToolTip( tip );
} }
} }
Expand All @@ -325,10 +338,9 @@ QString QgsPgTableModel::layerURI( const QModelIndex &index, const QString& conn
} }


QStandardItem *pkItem = itemFromIndex( index.sibling( index.row(), dbtmPkCol ) ); QStandardItem *pkItem = itemFromIndex( index.sibling( index.row(), dbtmPkCol ) );
QString pkColumnName = pkItem->data( Qt::UserRole + 2 ).toString(); QSet<QString> s0( pkItem->data( Qt::UserRole + 1 ).toStringList().toSet() );

QSet<QString> s1( pkItem->data( Qt::UserRole + 2 ).toStringList().toSet() );
if ( pkItem->data( Qt::UserRole + 1 ).toStringList().size() > 0 && if ( !s0.isEmpty() && s0.intersect( s1 ).isEmpty() )
!pkItem->data( Qt::UserRole + 1 ).toStringList().contains( pkColumnName ) )
{ {
// no valid primary candidate selected // no valid primary candidate selected
QgsDebugMsg( "no pk candidate selected" ); QgsDebugMsg( "no pk candidate selected" );
Expand Down Expand Up @@ -358,7 +370,14 @@ QString QgsPgTableModel::layerURI( const QModelIndex &index, const QString& conn
QString sql = index.sibling( index.row(), dbtmSql ).data( Qt::DisplayRole ).toString(); QString sql = index.sibling( index.row(), dbtmSql ).data( Qt::DisplayRole ).toString();


QgsDataSourceURI uri( connInfo ); QgsDataSourceURI uri( connInfo );
uri.setDataSource( schemaName, tableName, geomColumnName, sql, pkColumnName );
QStringList cols;
foreach ( QString col, s1 )
{
cols << QgsPostgresConn::quotedIdentifier( col );
}

uri.setDataSource( schemaName, tableName, geomColumnName, sql, cols.join( "," ) );
uri.setUseEstimatedMetadata( useEstimatedMetadata ); uri.setUseEstimatedMetadata( useEstimatedMetadata );
uri.setWkbType( wkbType ); uri.setWkbType( wkbType );
uri.setSrid( srid ); uri.setSrid( srid );
Expand Down
89 changes: 72 additions & 17 deletions src/providers/postgres/qgspostgresprovider.cpp
Expand Up @@ -1180,14 +1180,69 @@ bool QgsPostgresProvider::determinePrimaryKey()


if ( !primaryKey.isEmpty() ) if ( !primaryKey.isEmpty() )
{ {
int idx = fieldNameIndex( primaryKey ); QStringList cols;


if ( idx >= 0 ) // remove quotes from key list
if ( primaryKey.startsWith( '"' ) && primaryKey.endsWith( '"' ) )
{
int i = 1;
QString col;
while ( i < primaryKey.size() )
{
if ( primaryKey[i] == '"' )
{
if ( i + 1 < primaryKey.size() && primaryKey[i+1] == '"' )
{
i++;
}
else
{
cols << col;
col = "";

if ( ++i == primaryKey.size() )
break;

Q_ASSERT( primaryKey[i] == ',' );
i++;
Q_ASSERT( primaryKey[i] == '"' );
i++;
col = "";
continue;
}
}

col += primaryKey[i++];
}
}
else if ( primaryKey.contains( "," ) )
{
cols = primaryKey.split( "," );
}
else
{
cols << primaryKey;
primaryKey = quotedIdentifier( primaryKey );
}

foreach ( QString col, cols )
{
int idx = fieldNameIndex( col );
if ( idx < 0 )
{
QgsMessageLog::logMessage( tr( "Key field '%1' for view not found." ).arg( col ), tr( "PostGIS" ) );
mPrimaryKeyAttrs.clear();
break;
}

mPrimaryKeyAttrs << idx;
}

if ( mPrimaryKeyAttrs.size() > 0 )
{ {
if ( mUseEstimatedMetadata || uniqueData( mQuery, primaryKey ) ) if ( mUseEstimatedMetadata || uniqueData( mQuery, primaryKey ) )
{ {
mPrimaryKeyType = ( mAttributeFields[idx].type() == QVariant::Int || mAttributeFields[idx].type() == QVariant::LongLong ) ? pktInt : pktFidMap; mPrimaryKeyType = ( mPrimaryKeyAttrs.size() == 1 && ( mAttributeFields[ mPrimaryKeyAttrs[0] ].type() == QVariant::Int || mAttributeFields[ mPrimaryKeyAttrs[0] ].type() == QVariant::LongLong ) ) ? pktInt : pktFidMap;
mPrimaryKeyAttrs << idx;
} }
else else
{ {
Expand All @@ -1196,7 +1251,7 @@ bool QgsPostgresProvider::determinePrimaryKey()
} }
else else
{ {
QgsMessageLog::logMessage( tr( "Key field '%1' for view not found." ).arg( primaryKey ), tr( "PostGIS" ) ); QgsMessageLog::logMessage( tr( "Keys for view undefined." ).arg( primaryKey ), tr( "PostGIS" ) );
} }
} }
else else
Expand Down Expand Up @@ -1269,12 +1324,12 @@ bool QgsPostgresProvider::determinePrimaryKey()
return mValid; return mValid;
} }


bool QgsPostgresProvider::uniqueData( QString query, QString colName ) bool QgsPostgresProvider::uniqueData( QString query, QString quotedColName )
{ {
Q_UNUSED( query ); Q_UNUSED( query );
// Check to see if the given column contains unique data // Check to see if the given column contains unique data
QString sql = QString( "SELECT count(distinct %1)=count(%1) FROM %2%3" ) QString sql = QString( "SELECT count(distinct %1)=count(%1) FROM %2%3" )
.arg( quotedIdentifier( colName ) ) .arg( quotedColName )
.arg( mQuery ) .arg( mQuery )
.arg( filterWhereClause() ); .arg( filterWhereClause() );


Expand Down Expand Up @@ -3062,16 +3117,16 @@ QgsVectorLayerImport::ImportError QgsPostgresProvider::createEmptyLayer(
} }
else else
{ {
// if the pk field's type is one of the postgres integer types, // if the pk field's type is one of the postgres integer types,
// use the equivalent autoincremental type (serialN) // use the equivalent autoincremental type (serialN)
if ( primaryKeyType == "int2" || primaryKeyType == "int4" ) if ( primaryKeyType == "int2" || primaryKeyType == "int4" )
{ {
primaryKeyType = "serial"; primaryKeyType = "serial";
} }
else if ( primaryKeyType == "int8" ) else if ( primaryKeyType == "int8" )
{ {
primaryKeyType = "serial8"; primaryKeyType = "serial8";
} }
} }


try try
Expand Down
2 changes: 1 addition & 1 deletion src/ui/qgsdbsourceselectbase.ui
Expand Up @@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>592</width> <width>773</width>
<height>476</height> <height>476</height>
</rect> </rect>
</property> </property>
Expand Down

14 comments on commit c5cce4b

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n testing it and items are not selectable in the feature id combobox.

@jef-n
Copy link
Member Author

@jef-n jef-n commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slarosa don't the selection just show up after the combobox looses focus?

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n not, I can see the dropdown menu with all items but I can't select any field there. The cursor does nothing by clicking up the item.

@jef-n
Copy link
Member Author

@jef-n jef-n commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slarosa odd, works for me on debian and windows.

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n it looks like an issue with the checkable state. Shouldn't I see the checkbox inside the dropdown menu?

@jef-n
Copy link
Member Author

@jef-n jef-n commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slarosa does e6f4fa6 help?

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n nope :(

@jef-n
Copy link
Member Author

@jef-n jef-n commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it looks like this:
shot

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what it shows up here
senzanome

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n also testing this [0] I am getting no checkbox in dropdown menu. My qt version is 4.8.2.

[0] - http://stackoverflow.com/a/8423904

@jef-n
Copy link
Member Author

@jef-n jef-n commented on c5cce4b Sep 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to apply to the GTK+ and Cleanlooks theme only. Plastique, CDE, Motif and Windows work.

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 5, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n thanks for your time, right it is the GTK+ style, I had saw somewhere a bug report but it was unresolved.

@slarosa
Copy link
Member

@slarosa slarosa commented on c5cce4b Sep 5, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jef-n using a QStyledItemDelegate for the combobox fixes the issue with GTK+ and Cleanlooks styles. But I am not sure if the below patch is correct.

https://gist.github.com/slarosa/8659f6bc1dda3477d275

@jef-n
Copy link
Member Author

@jef-n jef-n commented on c5cce4b Sep 5, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slarosa Works fine for me - nice catch.

Please sign in to comment.