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