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
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,20 @@ QWidget *QgsPgSourceSelectDelegate::createEditor( QWidget *parent, const QStyleO
if ( values.size() > 0 )
{
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;
}
}
Expand All @@ -101,8 +114,25 @@ void QgsPgSourceSelectDelegate::setEditorData( QWidget *editor, const QModelInde
if ( index.column() == QgsPgTableModel::dbtmType )
cb->setCurrentIndex( cb->findData( index.data( Qt::UserRole + 2 ).toInt() ) );

if ( index.column() == QgsPgTableModel::dbtmPkCol && !index.data( Qt::UserRole + 2 ).toString().isEmpty() )
cb->setCurrentIndex( cb->findText( index.data( Qt::UserRole + 2 ).toString() ) );
if ( index.column() == QgsPgTableModel::dbtmPkCol && !index.data( Qt::UserRole + 2 ).toStringList().isEmpty() )
{
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 );
Expand Down Expand Up @@ -132,9 +162,17 @@ void QgsPgSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMode
}
else if ( index.column() == QgsPgTableModel::dbtmPkCol )
{
QString value( cb->currentText() );
model->setData( index, value.isEmpty() ? tr( "Select..." ) : value );
model->setData( index, value, Qt::UserRole + 2 );
QStandardItemModel *cbm = qobject_cast<QStandardItemModel*>( cb->model() );
QStringList cols;
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( "Type" ) );
mSearchColumnComboBox->addItem( tr( "Geometry column" ) );
mSearchColumnComboBox->addItem( tr( "Primary key column" ) );
mSearchColumnComboBox->addItem( tr( "Feature id" ) );
mSearchColumnComboBox->addItem( tr( "SRID" ) );
mSearchColumnComboBox->addItem( tr( "Sql" ) );

Expand Down Expand Up @@ -378,7 +416,7 @@ void QgsPgSourceSelect::on_mSearchColumnComboBox_currentIndexChanged( const QStr
{
mProxyModel.setFilterKeyColumn( QgsPgTableModel::dbtmGeomCol );
}
else if ( text == tr( "Primary key column" ) )
else if ( text == tr( "Feature id" ) )
{
mProxyModel.setFilterKeyColumn( QgsPgTableModel::dbtmPkCol );
}
Expand Down
53 changes: 36 additions & 17 deletions src/providers/postgres/qgspgtablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ QgsPgTableModel::QgsPgTableModel()
headerLabels << tr( "Data Type" );
headerLabels << tr( "Spatial Type" );
headerLabels << tr( "SRID" );
headerLabels << tr( "Primary Key" );
headerLabels << tr( "Feature id" );
headerLabels << tr( "Select at id" );
headerLabels << tr( "Sql" );
setHorizontalHeaderLabels( headerLabels );
Expand Down Expand Up @@ -62,15 +62,15 @@ void QgsPgTableModel::addTableEntry( const QgsPostgresLayerProperty& layerProper
QString tip;
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 )
{
tip = tr( "Enter a SRID" );
tip = tr( "Enter a SRID into the '%1' column" ).arg( tr( "SRID" ) );
}
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 );
Expand Down Expand Up @@ -127,16 +127,18 @@ void QgsPgTableModel::addTableEntry( const QgsPostgresLayerProperty& layerProper
{
if ( tip.isEmpty() )
{
item->setFlags( item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEnabled );
item->setFlags( item->flags() | Qt::ItemIsSelectable );
item->setToolTip( "" );
}
else
{
item->setFlags( item->flags() & ~Qt::ItemIsSelectable );

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

if ( item == schemaNameItem || item == tableItem || item == geomItem )
{
item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
item->setToolTip( tip );
}
}
Expand Down Expand Up @@ -267,38 +269,49 @@ bool QgsPgTableModel::setData( const QModelIndex &idx, const QVariant &value, in
QString tip;
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 )
{
bool ok;
int srid = idx.sibling( idx.row(), dbtmSrid ).data().toInt( &ok );

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();
if ( tip.isEmpty() && pkCols.size() > 0 )
{
if ( !pkCols.contains( idx.sibling( idx.row(), dbtmPkCol ).data().toString() ) )
tip = tr( "Select a primary key" );
QSet<QString> s0( idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 2 ).toStringList().toSet() );
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++ )
{
QStandardItem *item = itemFromIndex( idx.sibling( idx.row(), i ) );
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( "" );
}
else
{
item->setFlags( item->flags() & ~Qt::ItemIsSelectable );

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

if ( i == dbtmSchema || i == dbtmTable || i == dbtmGeomCol )
{
item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
item->setFlags( item->flags() );
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 ) );
QString pkColumnName = pkItem->data( Qt::UserRole + 2 ).toString();

if ( pkItem->data( Qt::UserRole + 1 ).toStringList().size() > 0 &&
!pkItem->data( Qt::UserRole + 1 ).toStringList().contains( pkColumnName ) )
QSet<QString> s0( pkItem->data( Qt::UserRole + 1 ).toStringList().toSet() );
QSet<QString> s1( pkItem->data( Qt::UserRole + 2 ).toStringList().toSet() );
if ( !s0.isEmpty() && s0.intersect( s1 ).isEmpty() )
{
// no valid primary 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();

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.setWkbType( wkbType );
uri.setSrid( srid );
Expand Down
89 changes: 72 additions & 17 deletions src/providers/postgres/qgspostgresprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1180,14 +1180,69 @@ bool QgsPostgresProvider::determinePrimaryKey()

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 ) )
{
mPrimaryKeyType = ( mAttributeFields[idx].type() == QVariant::Int || mAttributeFields[idx].type() == QVariant::LongLong ) ? pktInt : pktFidMap;
mPrimaryKeyAttrs << idx;
mPrimaryKeyType = ( mPrimaryKeyAttrs.size() == 1 && ( mAttributeFields[ mPrimaryKeyAttrs[0] ].type() == QVariant::Int || mAttributeFields[ mPrimaryKeyAttrs[0] ].type() == QVariant::LongLong ) ) ? pktInt : pktFidMap;
}
else
{
Expand All @@ -1196,7 +1251,7 @@ bool QgsPostgresProvider::determinePrimaryKey()
}
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
Expand Down Expand Up @@ -1269,12 +1324,12 @@ bool QgsPostgresProvider::determinePrimaryKey()
return mValid;
}

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

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

try
Expand Down
2 changes: 1 addition & 1 deletion src/ui/qgsdbsourceselectbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>592</width>
<width>773</width>
<height>476</height>
</rect>
</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.