diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp')
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp new file mode 100644 index 0000000..d835dd1 --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp @@ -0,0 +1,468 @@ +#include "indexdialog.h" +#include "ui_indexdialog.h" +#include "schemaresolver.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include "db/chainexecutor.h" +#include "dbtree/dbtree.h" +#include "ddlpreviewdialog.h" +#include "uiconfig.h" +#include "services/config.h" +#include "uiutils.h" +#include "sqlite3.h" +#include "windows/editorwindow.h" +#include "services/codeformatter.h" +#include <QDebug> +#include <QGridLayout> +#include <QSignalMapper> +#include <QScrollBar> +#include <QPushButton> +#include <QMessageBox> + +IndexDialog::IndexDialog(Db* db, QWidget *parent) : + QDialog(parent), + db(db), + ui(new Ui::IndexDialog) +{ + init(); +} + +IndexDialog::IndexDialog(Db* db, const QString& index, QWidget* parent) : + QDialog(parent), + db(db), + index(index), + ui(new Ui::IndexDialog) +{ + existingIndex = true; + init(); +} + +IndexDialog::~IndexDialog() +{ + delete ui; +} + +void IndexDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void IndexDialog::init() +{ + ui->setupUi(this); + limitDialogWidth(this); + if (!db || !db->isOpen()) + { + qCritical() << "Created IndexDialog for null or closed database."; + notifyError(tr("Tried to open index dialog for closed or inexisting database.")); + reject(); + return; + } + + ui->columnsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + ui->partialIndexEdit->setDb(db); + + connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); + + columnStateSignalMapping = new QSignalMapper(this); + connect(columnStateSignalMapping, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int))); + + SchemaResolver resolver(db); + ui->tableCombo->addItem(QString::null); + ui->tableCombo->addItems(resolver.getTables()); + connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateTable(QString))); + connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateValidation())); + if (existingIndex) + ui->tableCombo->setEnabled(false); + + if (db->getDialect() == Dialect::Sqlite3) + { + connect(ui->partialIndexCheck, SIGNAL(toggled(bool)), this, SLOT(updatePartialConditionState())); + connect(ui->partialIndexEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation())); + connect(ui->partialIndexEdit, SIGNAL(textChanged()), this, SLOT(updateValidation())); + ui->partialIndexEdit->setVirtualSqlExpression("SELECT %1"); + updatePartialConditionState(); + ui->columnsTable->setColumnHidden(1, false); + } + else + { + ui->partialIndexCheck->setVisible(false); + ui->partialIndexEdit->setVisible(false); + ui->columnsTable->setColumnHidden(1, true); + } + + readCollations(); + + ui->ddlEdit->setSqliteVersion(db->getVersion()); + + if (index.isNull()) + createIndex = SqliteCreateIndexPtr::create(); + else + readIndex(); + + originalCreateIndex = SqliteCreateIndexPtr::create(*createIndex); + + ui->nameEdit->setText(index); + setTable(createIndex->table); + + if (!index.isNull()) + applyIndex(); + + updateValidation(); + + ui->nameEdit->setFocus(); +} + +void IndexDialog::readIndex() +{ + SchemaResolver resolver(db); + SqliteQueryPtr parsedObject = resolver.getParsedObject(index, SchemaResolver::INDEX); + if (!parsedObject.dynamicCast<SqliteCreateIndex>()) + { + notifyError(tr("Could not process index %1 correctly. Unable to open an index dialog.").arg(index)); + reject(); + return; + } + + createIndex = parsedObject.dynamicCast<SqliteCreateIndex>(); +} + +void IndexDialog::buildColumns() +{ + // Clean up + ui->columnsTable->setRowCount(0); + columnCheckBoxes.clear(); + sortComboBoxes.clear(); + collateComboBoxes.clear(); + + totalColumns = tableColumns.size(); + ui->columnsTable->setRowCount(totalColumns); + + int row = 0; + foreach (const QString& column, tableColumns) + buildColumn(column, row++); +} + +void IndexDialog::updateTable(const QString& value) +{ + table = value; + + SchemaResolver resolver(db); + tableColumns = resolver.getTableColumns(table); + + buildColumns(); +} + +void IndexDialog::updateValidation() +{ + bool tableOk = ui->tableCombo->currentIndex() > 0; + bool colSelected = false; + + if (tableOk) + { + foreach (QCheckBox* cb, columnCheckBoxes) + { + if (cb->isChecked()) + { + colSelected = true; + break; + } + } + } + + bool partialConditionOk = (!ui->partialIndexCheck->isChecked() || + (ui->partialIndexEdit->isSyntaxChecked() && !ui->partialIndexEdit->haveErrors())); + + setValidState(ui->tableCombo, tableOk, tr("Pick the table for the index.")); + setValidState(ui->columnsTable, colSelected, tr("Select at least one column.")); + setValidState(ui->partialIndexCheck, partialConditionOk, tr("Enter a valid condition.")); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(colSelected && partialConditionOk); +} + +void IndexDialog::setTable(const QString& value) +{ + ui->tableCombo->setCurrentText(value); +} + +void IndexDialog::readCollations() +{ + SchemaResolver resolver(db); + QStringList collList = resolver.getCollations(); + + if (collList.size() > 0) + collList.prepend(""); + + collations.setStringList(collList); +} + +void IndexDialog::buildColumn(const QString& name, int row) +{ + int col = 0; + + QWidget* checkParent = new QWidget(); + QHBoxLayout* layout = new QHBoxLayout(); + QMargins margins = layout->contentsMargins(); + margins.setTop(0); + margins.setBottom(0); + margins.setLeft(4); + margins.setRight(4); + layout->setContentsMargins(margins); + checkParent->setLayout(layout); + + QCheckBox* check = new QCheckBox(name); + checkParent->layout()->addWidget(check); + + ui->columnsTable->setCellWidget(row, col++, checkParent); + columnStateSignalMapping->setMapping(check, row); + connect(check, SIGNAL(toggled(bool)), columnStateSignalMapping, SLOT(map())); + connect(check, SIGNAL(toggled(bool)), this, SLOT(updateValidation())); + columnCheckBoxes << check; + + QComboBox* collation = nullptr; + if (db->getDialect() == Dialect::Sqlite3) + { + collation = new QComboBox(); + collation->setEditable(true); + collation->lineEdit()->setPlaceholderText(tr("default", "index dialog")); + collation->setModel(&collations); + ui->columnsTable->setCellWidget(row, col++, collation); + collateComboBoxes << collation; + } + else + { + col++; + } + + QComboBox* sortOrder = new QComboBox(); + sortOrder->setToolTip(tr("Sort order", "table constraints")); + ui->columnsTable->setCellWidget(row, col++, sortOrder); + sortComboBoxes << sortOrder; + + QStringList sortList = {"", sqliteSortOrder(SqliteSortOrder::ASC), sqliteSortOrder(SqliteSortOrder::DESC)}; + sortOrder->addItems(sortList); + + totalColumns++; + + updateColumnState(row); +} + +void IndexDialog::updateColumnState(int row) +{ + bool enabled = columnCheckBoxes[row]->isChecked(); + sortComboBoxes[row]->setEnabled(enabled); + if (db->getDialect() == Dialect::Sqlite3) + collateComboBoxes[row]->setEnabled(enabled); +} + +void IndexDialog::updatePartialConditionState() +{ + ui->partialIndexEdit->setEnabled(ui->partialIndexCheck->isChecked()); + updateValidation(); +} + +void IndexDialog::updateDdl() +{ + rebuildCreateIndex(); + QString formatted = FORMATTER->format("sql", createIndex->detokenize(), db); + ui->ddlEdit->setPlainText(formatted); +} + +void IndexDialog::tabChanged(int tab) +{ + if (tab == 1) + updateDdl(); +} + +void IndexDialog::applyColumnValues() +{ + Dialect dialect = db->getDialect(); + int row; + foreach (SqliteIndexedColumn* idxCol, createIndex->indexedColumns) + { + row = indexOf(tableColumns, idxCol->name, Qt::CaseInsensitive); + if (row == -1) + { + qCritical() << "Cannot find column from index in the table columns! Indexed column:" << idxCol->name + << ", table columns:" << tableColumns << ", index name:" << index << ", table name:" << table; + continue; + } + + columnCheckBoxes[row]->setChecked(true); + updateColumnState(row); + sortComboBoxes[row]->setCurrentText(sqliteSortOrder(idxCol->sortOrder)); + if (dialect == Dialect::Sqlite3) + collateComboBoxes[row]->setCurrentText(idxCol->collate); + } +} + +void IndexDialog::applyIndex() +{ + applyColumnValues(); + + ui->partialIndexCheck->setChecked(createIndex->where != nullptr); + if (createIndex->where) + ui->partialIndexEdit->setPlainText(createIndex->where->detokenize()); +} + +SqliteIndexedColumn* IndexDialog::addIndexedColumn(const QString& name) +{ + SqliteIndexedColumn* idxCol = new SqliteIndexedColumn(); + idxCol->name = name; + idxCol->setParent(createIndex.data()); + createIndex->indexedColumns << idxCol; + return idxCol; +} + +void IndexDialog::rebuildCreateIndex() +{ + createIndex = SqliteCreateIndexPtr::create(); + createIndex->index = ui->nameEdit->text(); + if (ui->tableCombo->currentIndex() > -1) + createIndex->table = ui->tableCombo->currentText(); + + createIndex->uniqueKw = ui->uniqueCheck->isChecked(); + + SqliteIndexedColumn* idxCol = nullptr; + int i = -1; + for (const QString& column : tableColumns) + { + i++; + + if (!columnCheckBoxes[i]->isChecked()) + continue; + + idxCol = addIndexedColumn(column); + if (!collateComboBoxes[i]->currentText().isEmpty()) + idxCol->collate = collateComboBoxes[i]->currentText(); + + if (sortComboBoxes[i]->currentIndex() > 0) + idxCol->sortOrder = sqliteSortOrder(sortComboBoxes[i]->currentText()); + } + + if (ui->partialIndexCheck->isChecked()) + { + if (createIndex->where) + delete createIndex->where; + + Parser parser(db->getDialect()); + SqliteExpr* expr = parser.parseExpr(ui->partialIndexEdit->toPlainText()); + + if (expr) + { + expr->setParent(createIndex.data()); + createIndex->where = expr; + } + else + { + qCritical() << "Could not parse expression from partial index condition: " << ui->partialIndexEdit->toPlainText() + << ", the CREATE INDEX statement will be incomplete."; + } + } + + createIndex->rebuildTokens(); +} + +void IndexDialog::queryDuplicates() +{ + static QString queryTpl = QStringLiteral("SELECT %1 FROM %2 GROUP BY %3 HAVING %4;\n"); + static QString countTpl = QStringLiteral("count(%1) AS %2"); + static QString countColNameTpl = QStringLiteral("count(%1)"); + static QString countConditionTpl = QStringLiteral("count(%1) > 1"); + + Dialect dialect = db->getDialect(); + + QStringList cols; + QStringList grpCols; + QStringList countCols; + QString wrappedCol; + QString countColName; + int i = 0; + for (const QString& column : tableColumns) + { + if (!columnCheckBoxes[i++]->isChecked()) + continue; + + wrappedCol = wrapObjIfNeeded(column, dialect); + cols << wrappedCol; + grpCols << wrappedCol; + countColName = wrapObjIfNeeded(countColNameTpl.arg(column), dialect); + cols << countTpl.arg(wrappedCol, countColName); + countCols << countConditionTpl.arg(wrappedCol); + } + + EditorWindow* editor = MAINWINDOW->openSqlEditor(); + editor->setCurrentDb(db); + + QString sqlCols = cols.join(", "); + QString sqlGrpCols = grpCols.join(", "); + QString sqlCntCols = countCols.join(" AND "); + QString sqlTable = wrapObjIfNeeded(ui->tableCombo->currentText(), dialect); + editor->setContents(queryTpl.arg(sqlCols, sqlTable, sqlGrpCols, sqlCntCols)); + editor->execute(); +} + +void IndexDialog::accept() +{ + rebuildCreateIndex(); + + Dialect dialect = db->getDialect(); + + QStringList sqls; + if (existingIndex) + sqls << QString("DROP INDEX %1").arg(wrapObjIfNeeded(originalCreateIndex->index, dialect)); + + sqls << createIndex->detokenize(); + + if (!CFG_UI.General.DontShowDdlPreview.get()) + { + DdlPreviewDialog dialog(db, this); + dialog.setDdl(sqls); + if (dialog.exec() != QDialog::Accepted) + return; + } + + ChainExecutor executor; + executor.setDb(db); + executor.setAsync(false); + executor.setQueries(sqls); + executor.exec(); + + if (executor.getSuccessfulExecution()) + { + CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath()); + + QDialog::accept(); + DBTREE->refreshSchema(db); + return; + } + + if (executor.getErrors().size() == 1 && executor.getErrors().first().first == SQLITE_CONSTRAINT) + { + int res = QMessageBox::critical(this, + tr("Error", "index dialog"), + tr("Cannot create unique index, because values in selected columns are not unique. " + "Would you like to execute SELECT query to see problematic values?"), + QMessageBox::Yes, + QMessageBox::No); + if (res == QMessageBox::Yes) + { + QDialog::reject(); + queryDuplicates(); + } + } + else + { + QMessageBox::critical(this, tr("Error", "index dialog"), tr("An error occurred while executing SQL statements:\n%1") + .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok); + } +} |
