forked from sqlitebrowser/sqlitebrowser
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDbStructureModel.cpp
More file actions
342 lines (290 loc) · 12.7 KB
/
DbStructureModel.cpp
File metadata and controls
342 lines (290 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#include "DbStructureModel.h"
#include "sqlitedb.h"
#include "sqlitetablemodel.h"
#include "Settings.h"
#include <QTreeWidgetItem>
#include <QMimeData>
#include <QMessageBox>
#include <QApplication>
DbStructureModel::DbStructureModel(DBBrowserDB& db, QObject* parent)
: QAbstractItemModel(parent),
m_db(db)
{
// Create root item and use its columns to store the header strings
QStringList header;
header << tr("Name") << tr("Object") << tr("Type") << tr("Schema") << tr("Database");
rootItem = new QTreeWidgetItem(header);
browsablesRootItem = nullptr;
}
DbStructureModel::~DbStructureModel()
{
delete rootItem;
}
int DbStructureModel::columnCount(const QModelIndex&) const
{
return rootItem->columnCount();
}
QVariant DbStructureModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
// Get the item the index points at
QTreeWidgetItem* item = static_cast<QTreeWidgetItem*>(index.internalPointer());
// Depending on the role either return the text or the icon
switch(role)
{
case Qt::DisplayRole:
// For the display role and the browsabled branch of the tree we want to show the column name including the schema name if necessary (i.e.
// for schemata != "main"). For the normal structure branch of the tree we don't want to add the schema name because it's already obvious from
// the position of the item in the tree.
if(index.column() == ColumnName && item->parent() == browsablesRootItem)
return sqlb::ObjectIdentifier(item->text(ColumnSchema), item->text(ColumnName)).toDisplayString();
else
return Settings::getValue("db", "hideschemalinebreaks").toBool() ? item->text(index.column()).replace("\n", " ").simplified() : item->text(index.column());
case Qt::EditRole:
case Qt::ToolTipRole: // Don't modify the text when it's supposed to be shown in a tooltip
return item->text(index.column());
case Qt::DecorationRole:
return item->icon(index.column());
default:
return QVariant();
}
}
Qt::ItemFlags DbStructureModel::flags(const QModelIndex &index) const
{
if(!index.isValid())
return Qt::ItemIsDropEnabled;
// All items are enabled and selectable
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
// Only enable dragging for entire table objects
QString type = data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString();
if(type == "table" || type == "view" || type == "index" || type == "trigger")
flags |= Qt::ItemIsDragEnabled;
return flags;
}
QVariant DbStructureModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// Get the header string from the root item
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
return rootItem->data(section, role);
return QVariant();
}
QModelIndex DbStructureModel::index(int row, int column, const QModelIndex& parent) const
{
if(!hasIndex(row, column, parent))
return QModelIndex();
QTreeWidgetItem *parentItem;
if(!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<QTreeWidgetItem*>(parent.internalPointer());
QTreeWidgetItem* childItem = parentItem->child(row);
if(childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex DbStructureModel::parent(const QModelIndex& index) const
{
if(!index.isValid())
return QModelIndex();
QTreeWidgetItem* childItem = static_cast<QTreeWidgetItem*>(index.internalPointer());
QTreeWidgetItem* parentItem = childItem->parent();
if(parentItem == rootItem)
return QModelIndex();
else
return createIndex(0, 0, parentItem);
}
int DbStructureModel::rowCount(const QModelIndex& parent) const
{
if(parent.column() > 0)
return 0;
if(!parent.isValid())
return rootItem->childCount();
else
return static_cast<QTreeWidgetItem*>(parent.internalPointer())->childCount();
}
void DbStructureModel::reloadData()
{
beginResetModel();
// Remove all data except for the root item
while(rootItem->childCount())
delete rootItem->child(0);
// Return here if no DB is opened
if(!m_db.isOpen())
{
endResetModel();
return;
}
// Create the nodes for browsables and for tables, indices, views and triggers. The idea here is to basically have two trees in one model:
// In the root node there are two nodes: 'browsables' and 'all'. The first node contains a list of a all browsable objects, i.e. views and tables.
// The seconds node contains four sub-nodes (tables, indices, views and triggers), each containing a list of objects of that type.
// This way we only have to have and only have to update one model and can use it in all sorts of places, just by setting a different root node.
browsablesRootItem = new QTreeWidgetItem(rootItem);
browsablesRootItem->setIcon(ColumnName, QIcon(QString(":/icons/view")));
browsablesRootItem->setText(ColumnName, tr("Browsables"));
// Make sure to always load the main schema first
QTreeWidgetItem* itemAll = new QTreeWidgetItem(rootItem);
itemAll->setIcon(ColumnName, QIcon(QString(":/icons/database")));
itemAll->setText(ColumnName, tr("All"));
buildTree(itemAll, "main");
// Add the temporary database as a node if it isn't empty. Make sure it's always second if it exists.
if(!m_db.schemata["temp"].isEmpty())
{
QTreeWidgetItem* itemTemp = new QTreeWidgetItem(itemAll);
itemTemp->setIcon(ColumnName, QIcon(QString(":/icons/database")));
itemTemp->setText(ColumnName, tr("Temporary"));
buildTree(itemTemp, "temp");
}
// Now load all the other schemata last
for(auto it=m_db.schemata.constBegin();it!=m_db.schemata.constEnd();++it)
{
// Don't load the main and temp schema again
if(it.key() != "main" && it.key() != "temp")
{
QTreeWidgetItem* itemSchema = new QTreeWidgetItem(itemAll);
itemSchema->setIcon(ColumnName, QIcon(QString(":/icons/database")));
itemSchema->setText(ColumnName, it.key());
buildTree(itemSchema, it.key());
}
}
// Refresh the view
endResetModel();
}
QStringList DbStructureModel::mimeTypes() const
{
QStringList types;
types << "text/plain";
return types;
}
QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const
{
// Loop through selected indices
QByteArray d;
for(const QModelIndex& index : indices)
{
// Only export data for valid indices and only for the SQL column, i.e. only once per row
if(index.isValid() && index.column() == ColumnSQL)
{
// Add the SQL code used to create the object
d = d.append(data(index, Qt::DisplayRole).toString() + ";\n");
// If it is a table also add the content
if(data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString() == "table")
{
SqliteTableModel tableModel(m_db);
sqlb::ObjectIdentifier objid(data(index.sibling(index.row(), ColumnSchema), Qt::DisplayRole).toString(),
data(index.sibling(index.row(), ColumnName), Qt::DisplayRole).toString());
tableModel.setTable(objid);
tableModel.waitForFetchingFinished();
for(int i=0; i < tableModel.rowCount(); ++i)
{
QString insertStatement = "INSERT INTO " + objid.toString() + " VALUES(";
for(int j=1; j < tableModel.columnCount(); ++j)
insertStatement += QString("'%1',").arg(tableModel.data(tableModel.index(i, j)).toString());
insertStatement.chop(1);
insertStatement += ");\n";
d = d.append(insertStatement);
}
}
}
}
// Create the MIME data object
QMimeData* mime = new QMimeData();
mime->setProperty("db_file", m_db.currentFile()); // Also save the file name to avoid dropping an object on the same database as it comes from
mime->setData("text/plain", d);
return mime;
}
bool DbStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
{
if(action == Qt::IgnoreAction)
return true;
if(!data->hasFormat("text/plain"))
return false;
if(data->property("db_file") == m_db.currentFile())
return false;
// Get data
QByteArray d = data->data("text/plain");
// Try to execute the SQL statement
if(m_db.executeMultiSQL(d, true, true))
{
m_db.updateSchema();
reloadData();
return true;
} else {
QMessageBox::warning(nullptr, QApplication::applicationName(), m_db.lastError());
return false;
}
}
void DbStructureModel::buildTree(QTreeWidgetItem* parent, const QString& schema)
{
// Build a map from object type to tree node to simplify finding the correct tree node later
QMap<QString, QTreeWidgetItem*> typeToParentItem;
// Get object map for the given schema
objectMap objmap = m_db.schemata[schema];
// Prepare tree
QTreeWidgetItem* itemTables = new QTreeWidgetItem(parent);
itemTables->setIcon(ColumnName, QIcon(QString(":/icons/table")));
itemTables->setText(ColumnName, tr("Tables (%1)").arg(objmap.values("table").count()));
typeToParentItem.insert("table", itemTables);
QTreeWidgetItem* itemIndices = new QTreeWidgetItem(parent);
itemIndices->setIcon(ColumnName, QIcon(QString(":/icons/index")));
itemIndices->setText(ColumnName, tr("Indices (%1)").arg(objmap.values("index").count()));
typeToParentItem.insert("index", itemIndices);
QTreeWidgetItem* itemViews = new QTreeWidgetItem(parent);
itemViews->setIcon(ColumnName, QIcon(QString(":/icons/view")));
itemViews->setText(ColumnName, tr("Views (%1)").arg(objmap.values("view").count()));
typeToParentItem.insert("view", itemViews);
QTreeWidgetItem* itemTriggers = new QTreeWidgetItem(parent);
itemTriggers->setIcon(ColumnName, QIcon(QString(":/icons/trigger")));
itemTriggers->setText(ColumnName, tr("Triggers (%1)").arg(objmap.values("trigger").count()));
typeToParentItem.insert("trigger", itemTriggers);
// Get all database objects and sort them by their name
QMultiMap<QString, sqlb::ObjectPtr> dbobjs;
for(auto it=objmap.constBegin(); it != objmap.constEnd(); ++it)
dbobjs.insert((*it)->name(), (*it));
// Add the database objects to the tree nodes
for(auto it=dbobjs.constBegin();it!=dbobjs.constEnd();++it)
{
// Object node
QTreeWidgetItem* item = addNode(typeToParentItem.value(sqlb::Object::typeToString((*it)->type())), *it, schema);
// If it is a table or view add the field nodes, add an extra node for the browsable section
if((*it)->type() == sqlb::Object::Types::Table || (*it)->type() == sqlb::Object::Types::View)
addNode(browsablesRootItem, *it, schema);
// Add field nodes if there are any
sqlb::FieldInfoList fieldList = (*it)->fieldInformation();
if(!fieldList.empty())
{
QStringList pk_columns;
if((*it)->type() == sqlb::Object::Types::Table)
{
sqlb::FieldVector pk = (*it).dynamicCast<sqlb::Table>()->primaryKey();
for(const sqlb::FieldPtr& pk_col : pk)
pk_columns.push_back(pk_col->name());
}
for(const sqlb::FieldInfo& field : fieldList)
{
QTreeWidgetItem *fldItem = new QTreeWidgetItem(item);
fldItem->setText(ColumnName, field.name);
fldItem->setText(ColumnObjectType, "field");
fldItem->setText(ColumnDataType, field.type);
fldItem->setText(ColumnSQL, field.sql);
fldItem->setText(ColumnSchema, schema);
if(pk_columns.contains(field.name))
fldItem->setIcon(ColumnName, QIcon(":/icons/field_key"));
else
fldItem->setIcon(ColumnName, QIcon(":/icons/field"));
}
}
}
}
QTreeWidgetItem* DbStructureModel::addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object, const QString& schema)
{
QString type = sqlb::Object::typeToString(object->type());
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setIcon(ColumnName, QIcon(QString(":/icons/%1").arg(type)));
item->setText(ColumnName, object->name());
item->setText(ColumnObjectType, type);
item->setText(ColumnSQL, object->originalSql());
item->setText(ColumnSchema, schema);
return item;
}