File indexing completed on 2024-04-28 05:48:41

0001 /*
0002    SPDX-FileCopyrightText: 2010 Marco Mentasti <marcomentasti@gmail.com>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "schemawidget.h"
0008 #include "sqlmanager.h"
0009 
0010 #include <KLocalizedString>
0011 #include <ktexteditor/application.h>
0012 #include <ktexteditor/editor.h>
0013 #include <ktexteditor/mainwindow.h>
0014 #include <ktexteditor/view.h>
0015 
0016 #include <QApplication>
0017 #include <QDrag>
0018 #include <QMenu>
0019 #include <QMimeData>
0020 #include <QMouseEvent>
0021 #include <QSqlDatabase>
0022 #include <QSqlField>
0023 #include <QSqlIndex>
0024 #include <QSqlRecord>
0025 #include <QStringList>
0026 
0027 SchemaWidget::SchemaWidget(QWidget *parent, SQLManager *manager)
0028     : QTreeWidget(parent)
0029     , m_manager(manager)
0030 {
0031     m_tablesLoaded = false;
0032     m_viewsLoaded = false;
0033 
0034     setHeaderLabels(QStringList() << i18nc("@title:column", "Database schema"));
0035 
0036     setContextMenuPolicy(Qt::CustomContextMenu);
0037     setDragDropMode(QAbstractItemView::DragOnly);
0038     setDragEnabled(true);
0039     setAcceptDrops(false);
0040 
0041     connect(this, &SchemaWidget::customContextMenuRequested, this, &SchemaWidget::slotCustomContextMenuRequested);
0042     connect(this, &SchemaWidget::itemExpanded, this, &SchemaWidget::slotItemExpanded);
0043 }
0044 
0045 SchemaWidget::~SchemaWidget()
0046 {
0047 }
0048 
0049 bool SchemaWidget::isConnectionValidAndOpen()
0050 {
0051     return m_manager->isValidAndOpen(m_connectionName);
0052 }
0053 
0054 void SchemaWidget::deleteChildren(QTreeWidgetItem *item)
0055 {
0056     const QList<QTreeWidgetItem *> items = item->takeChildren();
0057 
0058     for (QTreeWidgetItem *i : items) {
0059         delete i;
0060     }
0061 }
0062 
0063 void SchemaWidget::buildTree(const QString &connection)
0064 {
0065     m_connectionName = connection;
0066 
0067     clear();
0068 
0069     m_tablesLoaded = false;
0070     m_viewsLoaded = false;
0071 
0072     if (!m_connectionName.isEmpty()) {
0073         buildDatabase(new QTreeWidgetItem(this));
0074     }
0075 }
0076 
0077 void SchemaWidget::refresh()
0078 {
0079     buildTree(m_connectionName);
0080 }
0081 
0082 void SchemaWidget::buildDatabase(QTreeWidgetItem *databaseItem)
0083 {
0084     QSqlDatabase db = QSqlDatabase::database(m_connectionName);
0085     QString dbname = (db.isValid() ? db.databaseName() : m_connectionName);
0086 
0087     databaseItem->setText(0, dbname);
0088     databaseItem->setIcon(0, QIcon::fromTheme(QStringLiteral("server-database")));
0089 
0090     QTreeWidgetItem *tablesItem = new QTreeWidgetItem(databaseItem, TablesFolderType);
0091     tablesItem->setText(0, i18nc("@title Folder name", "Tables"));
0092     tablesItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder")));
0093     tablesItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0094 
0095     QTreeWidgetItem *viewsItem = new QTreeWidgetItem(databaseItem, ViewsFolderType);
0096     viewsItem->setText(0, i18nc("@title Folder name", "Views"));
0097     viewsItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder")));
0098     viewsItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0099 
0100     databaseItem->setExpanded(true);
0101 }
0102 
0103 void SchemaWidget::buildTables(QTreeWidgetItem *tablesItem)
0104 {
0105     if (!isConnectionValidAndOpen()) {
0106         return;
0107     }
0108 
0109     QTreeWidgetItem *systemTablesItem = new QTreeWidgetItem(tablesItem, SystemTablesFolderType);
0110     systemTablesItem->setText(0, i18nc("@title Folder name", "System Tables"));
0111     systemTablesItem->setIcon(0, QIcon::fromTheme(QStringLiteral("folder")));
0112     systemTablesItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0113 
0114     QSqlDatabase db = QSqlDatabase::database(m_connectionName);
0115     QStringList tables = db.tables(QSql::SystemTables);
0116 
0117     for (const QString &table : qAsConst(tables)) {
0118         QTreeWidgetItem *item = new QTreeWidgetItem(systemTablesItem, SystemTableType);
0119         item->setText(0, table);
0120         item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-table.png")));
0121         item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0122     }
0123 
0124     tables = db.tables(QSql::Tables);
0125 
0126     for (const QString &table : qAsConst(tables)) {
0127         QTreeWidgetItem *item = new QTreeWidgetItem(tablesItem, TableType);
0128         item->setText(0, table);
0129         item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-table.png")));
0130         item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0131     }
0132 
0133     m_tablesLoaded = true;
0134 }
0135 
0136 void SchemaWidget::buildViews(QTreeWidgetItem *viewsItem)
0137 {
0138     if (!isConnectionValidAndOpen()) {
0139         return;
0140     }
0141 
0142     QSqlDatabase db = QSqlDatabase::database(m_connectionName);
0143 
0144     const QStringList views = db.tables(QSql::Views);
0145 
0146     for (const QString &view : views) {
0147         QTreeWidgetItem *item = new QTreeWidgetItem(viewsItem, ViewType);
0148         item->setText(0, view);
0149         item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-view.png")));
0150         item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0151     }
0152 
0153     m_viewsLoaded = true;
0154 }
0155 
0156 void SchemaWidget::buildFields(QTreeWidgetItem *tableItem)
0157 {
0158     if (!isConnectionValidAndOpen()) {
0159         return;
0160     }
0161 
0162     QSqlDatabase db = QSqlDatabase::database(m_connectionName);
0163 
0164     QString tableName = tableItem->text(0);
0165 
0166     QSqlIndex pk = db.primaryIndex(tableName);
0167     QSqlRecord rec = db.record(tableName);
0168 
0169     for (int i = 0; i < rec.count(); ++i) {
0170         QSqlField f = rec.field(i);
0171 
0172         QString fieldName = f.name();
0173 
0174         QTreeWidgetItem *item = new QTreeWidgetItem(tableItem, FieldType);
0175         item->setText(0, fieldName);
0176 
0177         if (pk.contains(fieldName)) {
0178             item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-field-pk.png")));
0179         } else {
0180             item->setIcon(0, QIcon(QLatin1String(":/katesql/pics/16-actions-sql-field.png")));
0181         }
0182     }
0183 }
0184 
0185 void SchemaWidget::mousePressEvent(QMouseEvent *event)
0186 {
0187     if (event->button() == Qt::LeftButton) {
0188         m_dragStartPosition = event->pos();
0189     }
0190     QTreeWidget::mousePressEvent(event);
0191 }
0192 
0193 void SchemaWidget::mouseMoveEvent(QMouseEvent *event)
0194 {
0195     if (!(event->buttons() & Qt::LeftButton)) {
0196         return;
0197     }
0198     if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
0199         return;
0200     }
0201 
0202     //   QTreeWidgetItem *item = currentItem();
0203     QTreeWidgetItem *item = itemAt(event->pos());
0204 
0205     if (!item) {
0206         return;
0207     }
0208 
0209     if (item->type() != SchemaWidget::SystemTableType && item->type() != SchemaWidget::TableType && item->type() != SchemaWidget::ViewType
0210         && item->type() != SchemaWidget::FieldType) {
0211         return;
0212     }
0213 
0214     QDrag *drag = new QDrag(this);
0215     QMimeData *mimeData = new QMimeData;
0216 
0217     if (item->type() == SchemaWidget::FieldType) {
0218         mimeData->setText(QStringLiteral("%1.%2").arg(item->parent()->text(0), item->text(0)));
0219     } else {
0220         mimeData->setText(item->text(0));
0221     }
0222 
0223     drag->setMimeData(mimeData);
0224     drag->exec(Qt::CopyAction);
0225 
0226     QTreeWidget::mouseMoveEvent(event);
0227 }
0228 
0229 void SchemaWidget::slotItemExpanded(QTreeWidgetItem *item)
0230 {
0231     if (!item) {
0232         return;
0233     }
0234 
0235     switch (item->type()) {
0236     case SchemaWidget::TablesFolderType: {
0237         if (!m_tablesLoaded) {
0238             buildTables(item);
0239         }
0240     } break;
0241 
0242     case SchemaWidget::ViewsFolderType: {
0243         if (!m_viewsLoaded) {
0244             buildViews(item);
0245         }
0246     } break;
0247 
0248     case SchemaWidget::TableType:
0249     case SchemaWidget::SystemTableType:
0250     case SchemaWidget::ViewType: {
0251         if (item->childCount() == 0) {
0252             buildFields(item);
0253         }
0254     } break;
0255 
0256     default:
0257         break;
0258     }
0259 }
0260 
0261 void SchemaWidget::slotCustomContextMenuRequested(const QPoint &pos)
0262 {
0263     QMenu menu(this);
0264     QTreeWidgetItem *item = itemAt(pos);
0265 
0266     if (item) {
0267         if (item->type() == SchemaWidget::SystemTableType || item->type() == SchemaWidget::TableType || item->type() == SchemaWidget::ViewType
0268             || item->type() == SchemaWidget::FieldType) {
0269             menu.addAction(QIcon::fromTheme(QStringLiteral("view-sort-descending")),
0270                            i18nc("@action:inmenu Context menu", "Select Data"),
0271                            this,
0272                            &SchemaWidget::executeSelect);
0273             QMenu *submenu = menu.addMenu(QIcon::fromTheme(QStringLiteral("tools-wizard")), i18nc("@action:inmenu Submenu title", "Generate"));
0274 
0275             submenu->addAction(i18n("SELECT"), this, &SchemaWidget::generateSelectIntoView);
0276             submenu->addAction(i18n("UPDATE"), this, &SchemaWidget::generateUpdateIntoView);
0277             submenu->addAction(i18n("INSERT"), this, &SchemaWidget::generateInsertIntoView);
0278             submenu->addAction(i18n("DELETE"), this, &SchemaWidget::generateDeleteIntoView);
0279             menu.addSeparator();
0280         }
0281     }
0282     menu.addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu Context menu", "Refresh"), this, &SchemaWidget::refresh);
0283 
0284     menu.exec(mapToGlobal(pos));
0285 }
0286 
0287 QString SchemaWidget::generateStatement(QSqlDriver::StatementType statementType)
0288 {
0289     if (!isConnectionValidAndOpen()) {
0290         return {};
0291     }
0292 
0293     QSqlDatabase db = QSqlDatabase::database(m_connectionName);
0294 
0295     QSqlDriver *drv = db.driver();
0296 
0297     if (!drv) {
0298         return {};
0299     }
0300 
0301     QTreeWidgetItem *item = currentItem();
0302 
0303     if (!item) {
0304         return {};
0305     }
0306 
0307     QString statement;
0308 
0309     switch (item->type()) {
0310     case TableType:
0311     case SystemTableType:
0312     case ViewType: {
0313         QString tableName = item->text(0);
0314 
0315         QSqlRecord rec = db.record(tableName);
0316 
0317         // set all fields to a value (NULL)
0318         // values are needed to generate update and insert statements
0319         if (statementType == QSqlDriver::UpdateStatement || statementType == QSqlDriver::InsertStatement) {
0320             for (int i = 0, n = rec.count(); i < n; ++i) {
0321                 rec.setNull(i);
0322             }
0323         }
0324 
0325         statement = drv->sqlStatement(statementType, tableName, rec, false);
0326     } break;
0327 
0328     case FieldType: {
0329         QString tableName = item->parent()->text(0);
0330         QSqlRecord rec = db.record(tableName);
0331 
0332         // get the selected column...
0333         QSqlField field = rec.field(item->text(0));
0334 
0335         // ...and set its value to NULL
0336         field.clear();
0337 
0338         // clear all columns and re-append the selected one
0339         rec.clear();
0340         rec.append(field);
0341 
0342         statement = drv->sqlStatement(statementType, tableName, rec, false);
0343 
0344         if (statementType == QSqlDriver::DeleteStatement) {
0345             statement +=
0346                 QLatin1Char(' ') + drv->sqlStatement(QSqlDriver::WhereStatement, tableName, rec, false).replace(QLatin1String(" IS NULL"), QLatin1String("=?"));
0347         }
0348     } break;
0349     }
0350 
0351     // replace NULL with a more generic '?'
0352     statement.replace(QLatin1String("NULL"), QLatin1String("?"));
0353     return statement;
0354 }
0355 
0356 void SchemaWidget::pasteStatementIntoActiveView(const QString &statement)
0357 {
0358     KTextEditor::MainWindow *mw = KTextEditor::Editor::instance()->application()->activeMainWindow();
0359     KTextEditor::View *kv = mw->activeView();
0360     qDebug() << "Generated statement:" << statement;
0361 
0362     if (!kv) {
0363         return;
0364     }
0365     // paste statement in the active view
0366     kv->insertText(statement);
0367     kv->setFocus();
0368 }
0369 void SchemaWidget::executeStatement(QSqlDriver::StatementType statementType)
0370 {
0371     const QString statement = generateStatement(statementType);
0372     if (statement.length()) {
0373         m_manager->runQuery(statement, m_connectionName);
0374     }
0375 }
0376 void SchemaWidget::executeSelect()
0377 {
0378     executeStatement(QSqlDriver::SelectStatement);
0379 }
0380 
0381 void SchemaWidget::generateAndPasteStatement(QSqlDriver::StatementType statementType)
0382 {
0383     QString statement = generateStatement(statementType);
0384     pasteStatementIntoActiveView(statement);
0385 }
0386 void SchemaWidget::generateSelectIntoView()
0387 {
0388     generateAndPasteStatement(QSqlDriver::SelectStatement);
0389 }
0390 
0391 void SchemaWidget::generateUpdateIntoView()
0392 {
0393     generateAndPasteStatement(QSqlDriver::UpdateStatement);
0394 }
0395 
0396 void SchemaWidget::generateInsertIntoView()
0397 {
0398     generateAndPasteStatement(QSqlDriver::InsertStatement);
0399 }
0400 
0401 void SchemaWidget::generateDeleteIntoView()
0402 {
0403     generateAndPasteStatement(QSqlDriver::DeleteStatement);
0404 }
0405 
0406 #include "moc_schemawidget.cpp"