1 /***************************************************************************
2   qgshanatablemodel.cpp
3   --------------------------------------
4   Date      : 31-05-2019
5   Copyright : (C) SAP SE
6   Author    : Maxim Rylov
7 ***************************************************************************/
8 
9 /***************************************************************************
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  ***************************************************************************/
17 #include "qgsapplication.h"
18 #include "qgsdatasourceuri.h"
19 #include "qgshanatablemodel.h"
20 #include "qgshanasettings.h"
21 #include "qgshanautils.h"
22 #include "qgslogger.h"
23 
QgsHanaTableModel()24 QgsHanaTableModel::QgsHanaTableModel()
25 {
26   QStringList headerLabels;
27   headerLabels << tr( "Schema" );
28   headerLabels << tr( "Table" );
29   headerLabels << tr( "Comment" );
30   headerLabels << tr( "Column" );
31   headerLabels << tr( "Type" );
32   headerLabels << tr( "SRID" );
33   headerLabels << tr( "Feature id" );
34   headerLabels << tr( "Select at id" );
35   headerLabels << tr( "Sql" );
36   setHorizontalHeaderLabels( headerLabels );
37 }
38 
addTableEntry(const QString & connName,const QgsHanaLayerProperty & layerProperty)39 void QgsHanaTableModel::addTableEntry( const QString &connName, const QgsHanaLayerProperty &layerProperty )
40 {
41   QgsWkbTypes::Type wkbType = layerProperty.type;
42   int srid = layerProperty.srid;
43 
44   if ( wkbType == QgsWkbTypes::Unknown && layerProperty.geometryColName.isEmpty() )
45     wkbType = QgsWkbTypes::NoGeometry;
46 
47   bool withTipButSelectable = false;
48   QString tip;
49   if ( wkbType == QgsWkbTypes::Unknown )
50     tip = tr( "Specify a geometry type in the '%1' column" ).arg( tr( "Data Type" ) );
51   else if ( wkbType != QgsWkbTypes::NoGeometry && srid == std::numeric_limits<int>::min() )
52     tip = tr( "Enter a SRID into the '%1' column" ).arg( tr( "SRID" ) );
53   else if ( !layerProperty.pkCols.empty() )
54   {
55     tip = tr( "Select columns in the '%1' column that uniquely identify features of this layer" ).arg( tr( "Feature id" ) );
56     withTipButSelectable = true;
57   }
58 
59   QStandardItem *schemaNameItem = new QStandardItem( layerProperty.schemaName );
60   QStandardItem *typeItem = new QStandardItem( iconForWkbType( wkbType ),
61       wkbType == QgsWkbTypes::Unknown ? tr( "Select…" ) : QgsWkbTypes::displayString( wkbType ) );
62   typeItem->setData( wkbType == QgsWkbTypes::Unknown, Qt::UserRole + 1 );
63   typeItem->setData( wkbType, Qt::UserRole + 2 );
64   if ( wkbType == QgsWkbTypes::Unknown )
65     typeItem->setFlags( typeItem->flags() | Qt::ItemIsEditable );
66 
67   QStandardItem *tableItem = new QStandardItem( layerProperty.tableName );
68   QStandardItem *commentItem = new QStandardItem( layerProperty.tableComment );
69   QStandardItem *geomItem = new QStandardItem( layerProperty.geometryColName );
70   QStandardItem *sridItem = new QStandardItem( wkbType != QgsWkbTypes::NoGeometry ? QString::number( srid ) : QString() );
71   sridItem->setEditable( wkbType != QgsWkbTypes::NoGeometry && srid < 0 );
72   if ( sridItem->isEditable() )
73   {
74     sridItem->setText( tr( "Enter…" ) );
75     sridItem->setFlags( sridItem->flags() | Qt::ItemIsEditable );
76   }
77 
78   QStandardItem *pkItem = new QStandardItem( QString() );
79   if ( !layerProperty.pkCols.isEmpty() )
80   {
81     pkItem->setText( tr( "Select…" ) );
82     pkItem->setFlags( pkItem->flags() | Qt::ItemIsEditable );
83   }
84   else
85     pkItem->setFlags( pkItem->flags() & ~Qt::ItemIsEditable );
86 
87   pkItem->setData( layerProperty.pkCols, Qt::UserRole + 1 );
88 
89   QgsHanaSettings settings( connName, true );
90   QStringList pkColumns;
91   if ( !layerProperty.pkCols.isEmpty() )
92   {
93     QStringList pkColumnsStored = settings.keyColumns( layerProperty.schemaName, layerProperty.tableName );
94     if ( !pkColumnsStored.empty() )
95     {
96       // We check whether the primary key columns still exist.
97       auto intersection = qgis::listToSet( pkColumnsStored ).intersect( qgis::listToSet( layerProperty.pkCols ) );
98       if ( intersection.size() == pkColumnsStored.size() )
99         pkColumns = pkColumnsStored;
100     }
101   }
102 
103   pkItem->setData( pkColumns, Qt::UserRole + 2 );
104   if ( !pkColumns.isEmpty() )
105     pkItem->setText( pkColumns.join( ',' ) );
106 
107   QStandardItem *selItem = new QStandardItem( QString( ) );
108   selItem->setFlags( selItem->flags() | Qt::ItemIsUserCheckable );
109   selItem->setCheckState( Qt::Checked );
110   selItem->setToolTip( tr( "Disable 'Fast Access to Features at ID' capability to force keeping "
111                            "the attribute table in memory (e.g. in case of expensive views)." ) );
112 
113   QStandardItem *sqlItem = new QStandardItem( layerProperty.sql );
114 
115   QList<QStandardItem *> childItemList;
116 
117   childItemList << schemaNameItem;
118   childItemList << tableItem;
119   childItemList << commentItem;
120   childItemList << geomItem;
121   childItemList << typeItem;
122   childItemList << sridItem;
123   childItemList << pkItem;
124   childItemList << selItem;
125   childItemList << sqlItem;
126 
127   for ( QStandardItem *item :  std::as_const( childItemList ) )
128   {
129     if ( tip.isEmpty() || withTipButSelectable )
130       item->setFlags( item->flags() | Qt::ItemIsSelectable );
131     else
132       item->setFlags( item->flags() & ~Qt::ItemIsSelectable );
133 
134     if ( tip.isEmpty() )
135     {
136       item->setToolTip( QString( ) );
137     }
138     else
139     {
140       if ( item == schemaNameItem )
141         item->setData( QgsApplication::getThemeIcon( QStringLiteral( "/mIconWarning.svg" ) ), Qt::DecorationRole );
142 
143       if ( item == schemaNameItem || item == tableItem || item == geomItem )
144         item->setToolTip( tip );
145     }
146   }
147 
148   QStandardItem *schemaItem = nullptr;
149   QList<QStandardItem *> schemaItems = findItems( layerProperty.schemaName, Qt::MatchExactly, DbtmSchema );
150 
151   // there is already an item for this schema
152   if ( !schemaItems.isEmpty() )
153   {
154     schemaItem = schemaItems.at( DbtmSchema );
155   }
156   else
157   {
158     // create a new toplevel item for this schema
159     schemaItem = new QStandardItem( layerProperty.schemaName );
160     schemaItem->setFlags( Qt::ItemIsEnabled );
161     invisibleRootItem()->setChild( invisibleRootItem()->rowCount(), schemaItem );
162   }
163 
164   schemaItem->appendRow( childItemList );
165 
166   ++mTableCount;
167 }
168 
setSql(const QModelIndex & index,const QString & sql)169 void QgsHanaTableModel::setSql( const QModelIndex &index, const QString &sql )
170 {
171   if ( !index.isValid() || !index.parent().isValid() )
172     return;
173 
174   //find out schema name and table name
175   QModelIndex schemaSibling = index.sibling( index.row(), DbtmSchema );
176   QModelIndex tableSibling = index.sibling( index.row(), DbtmTable );
177   QModelIndex geomSibling = index.sibling( index.row(), DbtmGeomCol );
178 
179   if ( !schemaSibling.isValid() || !tableSibling.isValid() || !geomSibling.isValid() )
180     return;
181 
182   QString schemaName = itemFromIndex( schemaSibling )->text();
183   QString tableName = itemFromIndex( tableSibling )->text();
184   QString geomName = itemFromIndex( geomSibling )->text();
185 
186   QList<QStandardItem *> schemaItems = findItems( schemaName, Qt::MatchExactly, DbtmSchema );
187   if ( schemaItems.size() < 1 )
188     return;
189 
190   QStandardItem *schemaItem = schemaItems.at( DbtmSchema );
191 
192   int n = schemaItem->rowCount();
193   for ( int i = 0; i < n; i++ )
194   {
195     QModelIndex currentChildIndex = indexFromItem( schemaItem->child( i, DbtmSchema ) );
196     if ( !currentChildIndex.isValid() )
197       continue;
198 
199     QModelIndex currentTableIndex = currentChildIndex.sibling( i, DbtmTable );
200     if ( !currentTableIndex.isValid() )
201       continue;
202 
203     QModelIndex currentGeomIndex = currentChildIndex.sibling( i, DbtmGeomCol );
204     if ( !currentGeomIndex.isValid() )
205       continue;
206 
207     if ( itemFromIndex( currentTableIndex )->text() == tableName && itemFromIndex( currentGeomIndex )->text() == geomName )
208     {
209       QModelIndex sqlIndex = currentChildIndex.sibling( i, DbtmSql );
210       if ( sqlIndex.isValid() )
211       {
212         itemFromIndex( sqlIndex )->setText( sql );
213         break;
214       }
215     }
216   }
217 }
218 
iconForWkbType(QgsWkbTypes::Type type)219 QIcon QgsHanaTableModel::iconForWkbType( QgsWkbTypes::Type type )
220 {
221   switch ( QgsWkbTypes::geometryType( type ) )
222   {
223     case QgsWkbTypes::PointGeometry:
224       return QgsApplication::getThemeIcon( QStringLiteral( "/mIconPointLayer.svg" ) );
225     case QgsWkbTypes::LineGeometry:
226       return QgsApplication::getThemeIcon( QStringLiteral( "/mIconLineLayer.svg" ) );
227     case QgsWkbTypes::PolygonGeometry:
228       return QgsApplication::getThemeIcon( QStringLiteral( "/mIconPolygonLayer.svg" ) );
229     case QgsWkbTypes::NullGeometry:
230       return QgsApplication::getThemeIcon( QStringLiteral( "/mIconTableLayer.svg" ) );
231     case QgsWkbTypes::UnknownGeometry:
232       return QgsApplication::getThemeIcon( QStringLiteral( "/mIconLayer.png" ) );
233   }
234   return QgsApplication::getThemeIcon( QStringLiteral( "/mIconLayer.png" ) );
235 }
236 
setData(const QModelIndex & idx,const QVariant & value,int role)237 bool QgsHanaTableModel::setData( const QModelIndex &idx, const QVariant &value, int role )
238 {
239   if ( !QStandardItemModel::setData( idx, value, role ) )
240     return false;
241 
242   if ( idx.column() == DbtmGeomType || idx.column() == DbtmSrid || idx.column() == DbtmPkCol )
243   {
244     QgsWkbTypes::Type wkbType = static_cast<QgsWkbTypes::Type>( idx.sibling( idx.row(), DbtmGeomType ).data( Qt::UserRole + 2 ).toInt() );
245 
246     QString tip;
247     if ( wkbType == QgsWkbTypes::Unknown )
248     {
249       tip = tr( "Specify a geometry type in the '%1' column" ).arg( tr( "Data Type" ) );
250     }
251     else if ( wkbType != QgsWkbTypes::NoGeometry )
252     {
253       bool ok;
254       int srid = idx.sibling( idx.row(), DbtmSrid ).data().toInt( &ok );
255 
256       if ( !ok || srid == std::numeric_limits<int>::min() )
257         tip = tr( "Enter a SRID into the '%1' column" ).arg( tr( "SRID" ) );
258     }
259 
260     QStringList pkCols = idx.sibling( idx.row(), DbtmPkCol ).data( Qt::UserRole + 1 ).toStringList();
261     if ( tip.isEmpty() && !pkCols.isEmpty() )
262     {
263       QSet<QString> s0( qgis::listToSet( idx.sibling( idx.row(), DbtmPkCol ).data( Qt::UserRole + 2 ).toStringList() ) );
264       QSet<QString> s1( qgis::listToSet( pkCols ) );
265       if ( !s0.intersects( s1 ) )
266         tip = tr( "Select columns in the '%1' column that uniquely identify features of this layer" ).arg( tr( "Feature id" ) );
267     }
268 
269     for ( int i = 0; i < DbtmColumns; i++ )
270     {
271       QStandardItem *item = itemFromIndex( idx.sibling( idx.row(), i ) );
272       if ( tip.isEmpty() )
273       {
274         if ( i == DbtmSchema )
275         {
276           item->setData( QVariant(), Qt::DecorationRole );
277         }
278 
279         item->setFlags( item->flags() | Qt::ItemIsSelectable );
280         item->setToolTip( QString( ) );
281       }
282       else
283       {
284         item->setFlags( item->flags() & ~Qt::ItemIsSelectable );
285 
286         if ( i == DbtmSchema )
287           item->setData( QgsApplication::getThemeIcon( QStringLiteral( "/mIconWarning.svg" ) ), Qt::DecorationRole );
288 
289         if ( i == DbtmSchema || i == DbtmTable || i == DbtmGeomCol )
290         {
291           item->setFlags( item->flags() );
292           item->setToolTip( tip );
293         }
294       }
295     }
296   }
297 
298   return true;
299 }
300 
layerURI(const QModelIndex & index,const QString & connName,const QString & connInfo)301 QString QgsHanaTableModel::layerURI( const QModelIndex &index, const QString &connName, const QString &connInfo )
302 {
303   if ( !index.isValid() )
304     return QString();
305 
306   QgsWkbTypes::Type wkbType = static_cast<QgsWkbTypes::Type>( itemFromIndex( index.sibling( index.row(), DbtmGeomType ) )->data( Qt::UserRole + 2 ).toInt() );
307   if ( wkbType == QgsWkbTypes::Unknown )
308     // no geometry type selected
309     return QString();
310 
311   QStandardItem *pkItem = itemFromIndex( index.sibling( index.row(), DbtmPkCol ) );
312   const QSet<QString> pkColumnsAll( qgis::listToSet( pkItem->data( Qt::UserRole + 1 ).toStringList() ) );
313   const QSet<QString> pkColumnsSelected( qgis::listToSet( pkItem->data( Qt::UserRole + 2 ).toStringList() ) );
314   if ( !pkColumnsAll.isEmpty() && !pkColumnsAll.intersects( pkColumnsSelected ) )
315   {
316     QgsDebugMsg( QStringLiteral( "no pk candidate selected" ) );
317     return QString();
318   }
319 
320   QString schemaName = index.sibling( index.row(), DbtmSchema ).data( Qt::DisplayRole ).toString();
321   QString tableName = index.sibling( index.row(), DbtmTable ).data( Qt::DisplayRole ).toString();
322 
323   QgsHanaSettings settings( connName, true );
324   settings.setKeyColumns( schemaName, tableName, qgis::setToList( pkColumnsSelected ) );
325   settings.save();
326 
327   QStringList pkColumns;
328   pkColumns.reserve( pkColumnsSelected.size() );
329   for ( const QString &column : pkColumnsSelected )
330     pkColumns <<  QgsHanaUtils::quotedIdentifier( column );
331 
332   QString geomColumnName;
333   QString srid;
334   if ( wkbType != QgsWkbTypes::NoGeometry )
335   {
336     geomColumnName = index.sibling( index.row(), DbtmGeomCol ).data( Qt::DisplayRole ).toString();
337 
338     srid = index.sibling( index.row(), DbtmSrid ).data( Qt::DisplayRole ).toString();
339     bool ok;
340     ( void )srid.toInt( &ok );
341     if ( !ok )
342       return QString();
343   }
344 
345   bool selectAtId = itemFromIndex( index.sibling( index.row(), DbtmSelectAtId ) )->checkState() == Qt::Checked;
346   QString sql = index.sibling( index.row(), DbtmSql ).data( Qt::DisplayRole ).toString();
347 
348   QgsDataSourceUri uri( connInfo );
349   uri.setDataSource( schemaName, tableName, geomColumnName, sql,  pkColumns.join( ',' ) );
350   uri.setWkbType( wkbType );
351   uri.setSrid( srid );
352   uri.disableSelectAtId( !selectAtId );
353 
354   return uri.uri();
355 }
356