Index: src/app/qgsdbsourceselect.h =================================================================== --- src/app/qgsdbsourceselect.h (revision 11360) +++ src/app/qgsdbsourceselect.h (working copy) @@ -36,11 +36,43 @@ #include #include #include +#include class QStringList; -class QTableWidgetItem; class QgsGeomColumnTypeThread; class QgisApp; + +class QgsDbSourceSelectDelegate : public QItemDelegate +{ + Q_OBJECT; + + public: + QgsDbSourceSelectDelegate( QObject *parent = NULL ) : QItemDelegate( parent ) + { + } + + /** Used to create an editor for when the user tries to + * change the contents of a cell */ + QWidget *createEditor( + QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index ) const + { + QComboBox *cb = new QComboBox( parent ); + cb->addItems( index.data( Qt::UserRole + 1 ).toStringList() ); + return cb; + } + + void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const + { + QComboBox *cb = dynamic_cast( editor ); + if ( !cb ) + return; + model->setData( index, cb->currentText() ); + } +}; + + /*! \class QgsDbSourceSelect * \brief Dialog to create connections and add tables from PostgresQL. * @@ -73,9 +105,6 @@ QString connectionInfo(); // Store the selected database void dbChanged(); - // Utility function to construct the query for finding out the - // geometry type of a column - static QString makeGeomQuery( QString schema, QString table, QString column ); public slots: /*! Connects to the database using the stored connection parameters. @@ -102,14 +131,6 @@ void setSearchExpression( const QString& regexp ); private: - enum columns - { - dbssType = 0, - dbssDetail, - dbssSql, - dbssColumns - }; - typedef std::pair geomPair; typedef std::list geomCol; @@ -121,6 +142,9 @@ /**Inserts information about the spatial tables into mTableModel*/ bool getTableInfo( PGconn *pg, bool searchGeometryColumnsOnly, bool searchPublicOnly ); + /** get primary key candidates (all int4 columns) */ + QStringList pkCandidates( PGconn *pg, QString schemaName, QString tableName ); + // queue another query for the thread void addSearchGeometryColumn( const QString &schema, const QString &table, const QString &column ); Index: src/app/qgsdbtablemodel.cpp =================================================================== --- src/app/qgsdbtablemodel.cpp (revision 11360) +++ src/app/qgsdbtablemodel.cpp (working copy) @@ -26,6 +26,7 @@ headerLabels << tr( "Table" ); headerLabels << tr( "Type" ); headerLabels << tr( "Geometry column" ); + headerLabels << tr( "Primary key column" ); headerLabels << tr( "Sql" ); setHorizontalHeaderLabels( headerLabels ); } @@ -35,7 +36,7 @@ } -void QgsDbTableModel::addTableEntry( QString type, QString schemaName, QString tableName, QString geometryColName, QString sql ) +void QgsDbTableModel::addTableEntry( QString type, QString schemaName, QString tableName, QString geometryColName, const QStringList &pkCols, QString sql ) { //is there already a root item with the given scheme Name? QStandardItem* schemaItem; @@ -68,6 +69,9 @@ tableItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); QStandardItem* geomItem = new QStandardItem( geometryColName ); geomItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + QStandardItem* pkItem = new QStandardItem( "" ); + pkItem->setData( pkCols ); + pkItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable ); QStandardItem* sqlItem = new QStandardItem( sql ); sqlItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); @@ -76,13 +80,14 @@ childItemList.push_back( tableItem ); childItemList.push_back( typeItem ); childItemList.push_back( geomItem ); + childItemList.push_back( pkItem ); childItemList.push_back( sqlItem ); schemaItem->appendRow( childItemList ); ++mTableCount; } -void QgsDbTableModel::setSql( const QModelIndex& index, const QString& sql ) +void QgsDbTableModel::setSql( const QModelIndex& index, const QString &sql ) { if ( !index.isValid() || !index.parent().isValid() ) { @@ -138,7 +143,7 @@ if ( itemFromIndex( currentTableIndex )->text() == tableName && itemFromIndex( currentGeomIndex )->text() == geomName ) { - QModelIndex sqlIndex = currentChildIndex.sibling( i, 4 ); + QModelIndex sqlIndex = currentChildIndex.sibling( i, dbtmSql ); if ( sqlIndex.isValid() ) { itemFromIndex( sqlIndex )->setText( sql ); @@ -168,6 +173,7 @@ QModelIndex currentTableIndex; QModelIndex currentTypeIndex; QModelIndex currentGeomColumnIndex; + QModelIndex currentPkColumnIndex; for ( int i = 0; i < numChildren; ++i ) { @@ -176,10 +182,12 @@ { continue; } - currentTableIndex = currentChildIndex.sibling( i, 1 ); - currentTypeIndex = currentChildIndex.sibling( i, 2 ); - currentGeomColumnIndex = currentChildIndex.sibling( i, 3 ); + currentTableIndex = currentChildIndex.sibling( i, dbtmTable ); + currentTypeIndex = currentChildIndex.sibling( i, dbtmType ); + currentGeomColumnIndex = currentChildIndex.sibling( i, dbtmGeomCol ); + currentPkColumnIndex = currentChildIndex.sibling( i, dbtmPkCol ); QString geomColText = itemFromIndex( currentGeomColumnIndex )->text(); + QStringList pkCols = itemFromIndex( currentPkColumnIndex )->data().toStringList(); if ( !currentTypeIndex.isValid() || !currentTableIndex.isValid() || !currentGeomColumnIndex.isValid() ) { @@ -207,7 +215,7 @@ for ( int j = 1; j < typeList.size(); ++j ) { //todo: add correct type - addTableEntry( typeList.at( j ), schema, table, geomColText + " AS " + typeList.at( j ), "" ); + addTableEntry( typeList.at( j ), schema, table, geomColText + " AS " + typeList.at( j ), pkCols, "" ); } } } Index: src/app/qgsdbtablemodel.h =================================================================== --- src/app/qgsdbtablemodel.h (revision 11360) +++ src/app/qgsdbtablemodel.h (working copy) @@ -29,7 +29,7 @@ QgsDbTableModel(); ~QgsDbTableModel(); /**Adds entry for one database table to the model*/ - void addTableEntry( QString type, QString schemaName, QString tableName, QString geometryColName, QString Sql ); + void addTableEntry( QString type, QString schemaName, QString tableName, QString geometryColName, const QStringList &pkCols, QString Sql ); /**Sets an sql statement that belongs to a cell specified by a model index*/ void setSql( const QModelIndex& index, const QString& sql ); /**Sets one or more geometry types to a row. In case of several types, additional rows are inserted. @@ -38,6 +38,17 @@ /**Returns the number of tables in the model*/ int tableCount() const {return mTableCount;} + enum columns + { + dbtmSchema = 0, + dbtmTable, + dbtmType, + dbtmGeomCol, + dbtmPkCol, + dbtmSql, + dbtmColumns + }; + private: /**Number of tables in the model*/ int mTableCount; Index: src/app/qgsdbsourceselect.cpp =================================================================== --- src/app/qgsdbsourceselect.cpp (revision 11360) +++ src/app/qgsdbsourceselect.cpp (working copy) @@ -31,13 +31,9 @@ #include #include #include -#include #include #include -#include -#include - #ifdef HAVE_PGCONFIG #include #endif @@ -57,6 +53,7 @@ mSearchColumnComboBox->addItem( tr( "Table" ) ); mSearchColumnComboBox->addItem( tr( "Type" ) ); mSearchColumnComboBox->addItem( tr( "Geometry column" ) ); + mSearchColumnComboBox->addItem( tr( "Primary key column" ) ); mSearchColumnComboBox->addItem( tr( "Sql" ) ); mProxyModel.setParent( this ); @@ -67,6 +64,10 @@ mTablesTreeView->setModel( &mProxyModel ); mTablesTreeView->setSortingEnabled( true ); + mTablesTreeView->setEditTriggers( QAbstractItemView::CurrentChanged ); + + mTablesTreeView->setItemDelegateForColumn( QgsDbTableModel::dbtmPkCol, new QgsDbSourceSelectDelegate( this ) ); + QSettings settings; mTablesTreeView->setSelectionMode( settings.value( "/qgis/addPostgisDC", false ).toBool() ? QAbstractItemView::ExtendedSelection : @@ -169,23 +170,27 @@ } else if ( text == tr( "Schema" ) ) { - mProxyModel.setFilterKeyColumn( 0 ); + mProxyModel.setFilterKeyColumn( QgsDbTableModel::dbtmSchema ); } else if ( text == tr( "Table" ) ) { - mProxyModel.setFilterKeyColumn( 1 ); + mProxyModel.setFilterKeyColumn( QgsDbTableModel::dbtmTable ); } else if ( text == tr( "Type" ) ) { - mProxyModel.setFilterKeyColumn( 2 ); + mProxyModel.setFilterKeyColumn( QgsDbTableModel::dbtmType ); } else if ( text == tr( "Geometry column" ) ) { - mProxyModel.setFilterKeyColumn( 3 ); + mProxyModel.setFilterKeyColumn( QgsDbTableModel::dbtmGeomCol ); } + else if ( text == tr( "Primay key column" ) ) + { + mProxyModel.setFilterKeyColumn( QgsDbTableModel::dbtmPkCol ); + } else if ( text == tr( "Sql" ) ) { - mProxyModel.setFilterKeyColumn( 4 ); + mProxyModel.setFilterKeyColumn( QgsDbTableModel::dbtmSql ); } } @@ -199,26 +204,15 @@ QString type ) { mTableModel.setGeometryTypesForTable( schema, table, column, type ); - mTablesTreeView->sortByColumn( 1, Qt::AscendingOrder ); - mTablesTreeView->sortByColumn( 0, Qt::AscendingOrder ); + mTablesTreeView->sortByColumn( QgsDbTableModel::dbtmTable, Qt::AscendingOrder ); + mTablesTreeView->sortByColumn( QgsDbTableModel::dbtmSchema, Qt::AscendingOrder ); } -QString QgsDbSourceSelect::makeGeomQuery( QString schema, - QString table, QString column ) -{ - return QString( "select distinct " - "case" - " when geometrytype(%1) IN ('POINT','MULTIPOINT') THEN 'POINT'" - " when geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'" - " when geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'" - " end " - "from \"%2\".\"%3\"" ).arg( "\"" + column + "\"" ).arg( schema ).arg( table ); -} - QgsDbSourceSelect::~QgsDbSourceSelect() { PQfinish( pd ); } + void QgsDbSourceSelect::populateConnectionList() { QSettings settings; @@ -234,6 +228,7 @@ settings.endGroup(); setConnectionListPosition(); } + void QgsDbSourceSelect::addNewConnection() { QgsNewConnection *nc = new QgsNewConnection( this ); @@ -243,6 +238,7 @@ populateConnectionList(); } } + void QgsDbSourceSelect::editConnection() { QgsNewConnection *nc = new QgsNewConnection( this, cmbConnections->currentText() ); @@ -278,6 +274,7 @@ setConnectionListPosition(); } } + void QgsDbSourceSelect::addTables() { m_selectedTables.clear(); @@ -310,16 +307,13 @@ if ( dbInfo[currentSchemaName][currentRow].size() == 0 ) { - dbInfo[currentSchemaName][currentRow].resize( 5 ); + dbInfo[currentSchemaName][currentRow].resize( QgsDbTableModel::dbtmColumns ); } dbInfo[currentSchemaName][currentRow][currentColumn] = currentItem->text(); } //now traverse all the schemas and table infos - QString schemaName, tableName, geomColumnName, sql; - QString query; - QMap::const_iterator schema_it = dbInfo.constBegin(); for ( ; schema_it != dbInfo.constEnd(); ++schema_it ) { @@ -327,16 +321,17 @@ schemaInfo::const_iterator entry_it = scheme.constBegin(); for ( ; entry_it != scheme.constEnd(); ++entry_it ) { - schemaName = entry_it->at( 0 ); - tableName = entry_it->at( 1 ); - geomColumnName = entry_it->at( 3 ); - sql = entry_it->at( 4 ); + QString schemaName = entry_it->at( QgsDbTableModel::dbtmSchema ); + QString tableName = entry_it->at( QgsDbTableModel::dbtmTable ); + QString geomColumnName = entry_it->at( QgsDbTableModel::dbtmGeomCol ); + QString pkColumnName = entry_it->at( QgsDbTableModel::dbtmPkCol ); + QString sql = entry_it->at( QgsDbTableModel::dbtmSql ); if ( geomColumnName.contains( " AS " ) ) { int a = geomColumnName.indexOf( " AS " ); QString typeName = geomColumnName.mid( a + 4 ); //only the type name - geomColumnName = geomColumnName.mid( 0, a ); //only the geom column name + geomColumnName = geomColumnName.left( a ); //only the geom column name if ( !sql.isEmpty() ) { @@ -360,8 +355,17 @@ } } - query = "\"" + schemaName + "\".\"" + tableName + "\" " + "(" + geomColumnName + ") sql=" + sql; + QString query; + if ( !pkColumnName.isEmpty() ) + { + query += QString( "key=\"%1\" " ).arg( pkColumnName ); + } + query += QString( "table=\"%1\".\"%2\" (%3) sql=%4" ) + .arg( schemaName ).arg( tableName ) + .arg( geomColumnName ) + .arg( sql ); + m_selectedTables.push_back( query ); } } @@ -493,8 +497,8 @@ .arg( QString::fromUtf8( PQerrorMessage( pd ) ) ) ); } - mTablesTreeView->sortByColumn( 1, Qt::AscendingOrder ); - mTablesTreeView->sortByColumn( 0, Qt::AscendingOrder ); + mTablesTreeView->sortByColumn( QgsDbTableModel::dbtmTable, Qt::AscendingOrder ); + mTablesTreeView->sortByColumn( QgsDbTableModel::dbtmSchema, Qt::AscendingOrder ); //if we have only one schema item, expand it by default int numTopLevelItems = mTableModel.invisibleRootItem()->rowCount(); @@ -532,8 +536,8 @@ } //create "Schema"."Table" and find out existing sql string - QModelIndex schemaSibling = index.sibling( index.row(), 0 ); - QModelIndex tableSibling = index.sibling( index.row(), 1 ); + QModelIndex schemaSibling = index.sibling( index.row(), QgsDbTableModel::dbtmSchema ); + QModelIndex tableSibling = index.sibling( index.row(), QgsDbTableModel::dbtmTable ); if ( !schemaSibling.isValid() || !tableSibling.isValid() ) { return; @@ -545,7 +549,7 @@ QgsDebugMsg( tableString ); QString currentSql; - QModelIndex sqlSibling = index.sibling( index.row(), 4 ); + QModelIndex sqlSibling = index.sibling( index.row(), QgsDbTableModel::dbtmSql ); if ( sqlSibling.isValid() ) { currentSql = mTableModel.itemFromIndex( mProxyModel.mapToSource( sqlSibling ) )->text(); @@ -576,6 +580,33 @@ mColumnTypeThread->addGeometryColumn( schema, table, column ); } +QStringList QgsDbSourceSelect::pkCandidates( PGconn *pg, QString schemaName, QString tableName ) +{ + QStringList cols; + cols << QString::null; + + QString sql = QString( "select attname from pg_attribute join pg_type on atttypid=pg_type.oid WHERE pg_type.typname='int4' AND attrelid=regclass('\"%1\".\"%2\"')" ).arg( schemaName ).arg( tableName ); + QgsDebugMsg( sql ); + PGresult *colRes = PQexec( pg, sql.toUtf8() ); + + if ( PQresultStatus( colRes ) == PGRES_TUPLES_OK ) + { + for ( int i = 0; i < PQntuples( colRes ); i++ ) + { + QgsDebugMsg( PQgetvalue( colRes, i, 0 ) ); + cols << QString::fromUtf8( PQgetvalue( colRes, i, 0 ) ); + } + } + else + { + QgsDebugMsg( QString( "SQL:%1\nresult:%2\nerror:%3\n" ).arg( sql ).arg( PQresultStatus( colRes ) ).arg( PQerrorMessage( pg ) ) ); + } + + PQclear( colRes ); + + return cols; +} + bool QgsDbSourceSelect::getTableInfo( PGconn *pg, bool searchGeometryColumnsOnly, bool searchPublicOnly ) { int n = 0; @@ -583,12 +614,23 @@ // The following query returns only tables that exist and the user has SELECT privilege on. // Can't use regclass here because table must exist, else error occurs. - QString sql = "select * from geometry_columns,pg_class,pg_namespace " - "where relname=f_table_name and f_table_schema=nspname " - "and pg_namespace.oid = pg_class.relnamespace " - "and has_schema_privilege(pg_namespace.nspname,'usage') " - "and has_table_privilege('\"'||pg_namespace.nspname||'\".\"'||pg_class.relname||'\"','select') " // user has select privilege - "order by f_table_schema,f_table_name,f_geometry_column"; + QString sql = "select " + "f_table_name," + "f_table_schema," + "f_geometry_column," + "type" + " from " + "geometry_columns," + "pg_class," + "pg_namespace" + " where " + "relname=f_table_name" + " and f_table_schema=nspname" + " and pg_namespace.oid=pg_class.relnamespace" + " and has_schema_privilege(pg_namespace.nspname,'usage')" + " and has_table_privilege('\"'||pg_namespace.nspname||'\".\"'||pg_class.relname||'\"','select')" // user has select privilege + " order by " + "f_table_schema,f_table_name,f_geometry_column"; PGresult *result = PQexec( pg, sql.toUtf8() ); if ( result ) @@ -606,12 +648,11 @@ { for ( int idx = 0; idx < PQntuples( result ); idx++ ) { - QString tableName = QString::fromUtf8( PQgetvalue( result, idx, PQfnumber( result, QString( "f_table_name" ).toUtf8() ) ) ); - QString schemaName = QString::fromUtf8( PQgetvalue( result, idx, PQfnumber( result, QString( "f_table_schema" ).toUtf8() ) ) ); + QString tableName = QString::fromUtf8( PQgetvalue( result, idx, 0 ) ); + QString schemaName = QString::fromUtf8( PQgetvalue( result, idx, 1 ) ); + QString column = QString::fromUtf8( PQgetvalue( result, idx, 2 ) ); + QString type = QString::fromUtf8( PQgetvalue( result, idx, 3 ) ); - QString column = QString::fromUtf8( PQgetvalue( result, idx, PQfnumber( result, QString( "f_geometry_column" ).toUtf8() ) ) ); - QString type = QString::fromUtf8( PQgetvalue( result, idx, PQfnumber( result, QString( "type" ).toUtf8() ) ) ); - QString as = ""; if ( type == "GEOMETRY" && !searchGeometryColumnsOnly ) { @@ -619,7 +660,7 @@ as = type = "WAITING"; } - mTableModel.addTableEntry( type, schemaName, tableName, column, "" ); + mTableModel.addTableEntry( type, schemaName, tableName, column, pkCandidates( pg, schemaName, tableName ), "" ); n++; } } @@ -636,31 +677,38 @@ // geometry_columns table. This code is specific to postgresql, // but an equivalent query should be possible in other // databases. - sql = "select pg_class.relname,pg_namespace.nspname,pg_attribute.attname,pg_class.relkind " - "from pg_attribute, pg_class, pg_namespace " - "where pg_namespace.oid = pg_class.relnamespace " - "and pg_attribute.attrelid = pg_class.oid " - "and (" + sql = "select " + "pg_class.relname," + "pg_namespace.nspname," + "pg_attribute.attname," + "pg_class.relkind" + " from " + "pg_attribute," + "pg_class," + "pg_namespace" + " where " + "pg_namespace.oid = pg_class.relnamespace" + " and pg_attribute.attrelid = pg_class.oid " + " and (" "pg_attribute.atttypid = regtype('geometry')" - " or " - "pg_attribute.atttypid IN (select oid FROM pg_type WHERE typbasetype=regtype('geometry'))" - ") " - "and has_schema_privilege(pg_namespace.nspname,'usage') " - "and has_table_privilege('\"'||pg_namespace.nspname||'\".\"'||pg_class.relname||'\"','select') "; + " or pg_attribute.atttypid IN (select oid FROM pg_type WHERE typbasetype=regtype('geometry'))" + ")" + " and has_schema_privilege(pg_namespace.nspname,'usage')" + " and has_table_privilege('\"'||pg_namespace.nspname||'\".\"'||pg_class.relname||'\"','select')"; // user has select privilege if ( searchPublicOnly ) - sql += "and pg_namespace.nspname = 'public' "; + sql += " and pg_namespace.nspname = 'public'"; if ( n > 0 ) { - sql += "and not exists (select * from geometry_columns WHERE pg_namespace.nspname=f_table_schema AND pg_class.relname=f_table_name) "; + sql += " and not exists (select * from geometry_columns WHERE pg_namespace.nspname=f_table_schema AND pg_class.relname=f_table_name)"; } else { n = 0; } - sql += "and pg_class.relkind in ('v', 'r')"; // only from views and relations (tables) + sql += " and pg_class.relkind in ('v', 'r')"; // only from views and relations (tables) result = PQexec( pg, sql.toUtf8() ); @@ -692,7 +740,7 @@ addSearchGeometryColumn( schema, table, column ); //details.push_back(geomPair(fullDescription(schema, table, column, "WAITING"), "WAITING")); - mTableModel.addTableEntry( "Waiting", schema, table, column, "" ); + mTableModel.addTableEntry( "Waiting", schema, table, column, pkCandidates( pg, schema, table ), "" ); n++; } } @@ -798,9 +846,14 @@ for ( uint i = 0; i < schemas.size(); i++ ) { - QString query = QgsDbSourceSelect::makeGeomQuery( schemas[i], - tables[i], - columns[i] ); + QString query = QString( "select distinct " + "case" + " when geometrytype(%1) IN ('POINT','MULTIPOINT') THEN 'POINT'" + " when geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'" + " when geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'" + " end " + "from " + "\"%2\".\"%3\"" ).arg( "\"" + columns[i] + "\"" ).arg( schemas[i] ).arg( tables[i] ); PGresult* gresult = PQexec( pd, query.toUtf8() ); QString type; if ( PQresultStatus( gresult ) == PGRES_TUPLES_OK ) Index: src/app/qgisapp.cpp =================================================================== --- src/app/qgisapp.cpp (revision 11360) +++ src/app/qgisapp.cpp (working copy) @@ -2583,7 +2583,7 @@ // create the layer //qWarning("creating layer"); - QgsVectorLayer *layer = new QgsVectorLayer( connectionInfo + " table=" + *it, *it, "postgres" ); + QgsVectorLayer *layer = new QgsVectorLayer( connectionInfo + " " + *it, *it, "postgres" ); if ( layer->isValid() ) { // register this layer with the central layers registry Index: src/core/qgsmaprenderer.cpp =================================================================== --- src/core/qgsmaprenderer.cpp (revision 11360) +++ src/core/qgsmaprenderer.cpp (working copy) @@ -637,6 +637,7 @@ } catch ( QgsCsException &cse ) { + Q_UNUSED( cse ); QgsDebugMsg( QString( "Transform error caught:%s" ).arg( cse.what() ) ); } }