|
|
@@ -0,0 +1,895 @@
|
|
|
+#include "databasemodule.h"
|
|
|
+
|
|
|
+#include "databaseconnectiondock.h"
|
|
|
+#include "databasepage.h"
|
|
|
+#include "databasesearchdialog.h"
|
|
|
+#include "toolbar/toolbar.h"
|
|
|
+
|
|
|
+#include <QAction>
|
|
|
+#include <QIcon>
|
|
|
+#include <QMainWindow>
|
|
|
+#include <QSet>
|
|
|
+#include <QSqlDatabase>
|
|
|
+#include <QSqlDriver>
|
|
|
+#include <QSqlError>
|
|
|
+#include <QSqlQuery>
|
|
|
+#include <QSqlRecord>
|
|
|
+#include <QSqlTableModel>
|
|
|
+#include <QStatusBar>
|
|
|
+#include <QVariant>
|
|
|
+#include <QUuid>
|
|
|
+#include <algorithm>
|
|
|
+#include <utility>
|
|
|
+
|
|
|
+namespace {
|
|
|
+class PagedSqlTableModel : public QSqlTableModel
|
|
|
+{
|
|
|
+public:
|
|
|
+ PagedSqlTableModel(QObject *parent, const QSqlDatabase &db)
|
|
|
+ : QSqlTableModel(parent, db)
|
|
|
+ {
|
|
|
+ setEditStrategy(QSqlTableModel::OnManualSubmit);
|
|
|
+ }
|
|
|
+
|
|
|
+ void setPagination(int offset, int limit)
|
|
|
+ {
|
|
|
+ m_offset = std::max(0, offset);
|
|
|
+ m_limit = std::max(1, limit);
|
|
|
+ }
|
|
|
+
|
|
|
+protected:
|
|
|
+ QString selectStatement() const override
|
|
|
+ {
|
|
|
+ QString statement = QSqlTableModel::selectStatement();
|
|
|
+ if (m_limit > 0) {
|
|
|
+ statement += QStringLiteral(" LIMIT %1 OFFSET %2").arg(m_limit).arg(m_offset);
|
|
|
+ }
|
|
|
+ return statement;
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+ int m_offset = 0;
|
|
|
+ int m_limit = 1;
|
|
|
+};
|
|
|
+
|
|
|
+constexpr int kDefaultPageSize = 20;
|
|
|
+
|
|
|
+} // namespace
|
|
|
+
|
|
|
+DatabaseModule::DatabaseModule(QMainWindow *owner, QObject *parent)
|
|
|
+ : QObject(parent)
|
|
|
+{
|
|
|
+ m_sortOrder = Qt::AscendingOrder;
|
|
|
+ m_pageSize = kDefaultPageSize;
|
|
|
+ setupUi(owner);
|
|
|
+}
|
|
|
+
|
|
|
+DatabaseModule::~DatabaseModule()
|
|
|
+{
|
|
|
+ disconnectFromServer();
|
|
|
+
|
|
|
+ if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
|
|
|
+ QSqlDatabase::removeDatabase(m_connectionName);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+Toolbar *DatabaseModule::toolbar() const
|
|
|
+{
|
|
|
+ return m_toolbar;
|
|
|
+}
|
|
|
+
|
|
|
+QWidget *DatabaseModule::pageWidget() const
|
|
|
+{
|
|
|
+ return m_page;
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::setupUi(QMainWindow *owner)
|
|
|
+{
|
|
|
+ Q_ASSERT(owner);
|
|
|
+
|
|
|
+ m_page = new DatabasePage(owner);
|
|
|
+
|
|
|
+ m_connectionDock = new DatabaseConnectionDock(m_page);
|
|
|
+ const QString connectionDockTitle = tr("服务器配置");
|
|
|
+ m_page->addDockWidget(m_connectionDock, connectionDockTitle, ads::RightDockWidgetArea);
|
|
|
+ m_connectionDockWidget = m_page->getDockWidget(connectionDockTitle);
|
|
|
+ if (m_connectionDockWidget) {
|
|
|
+ m_connectionDockWidget->closeDockWidget();
|
|
|
+ }
|
|
|
+
|
|
|
+ connect(m_connectionDock, &DatabaseConnectionDock::connectRequested,
|
|
|
+ this, &DatabaseModule::handleConnectRequested);
|
|
|
+ connect(m_connectionDock, &DatabaseConnectionDock::disconnectRequested,
|
|
|
+ this, &DatabaseModule::handleDisconnectRequested);
|
|
|
+
|
|
|
+ connect(m_page, &DatabasePage::tableActivated,
|
|
|
+ this, &DatabaseModule::handleTableActivated);
|
|
|
+ connect(m_page, &DatabasePage::refreshRequested,
|
|
|
+ this, &DatabaseModule::handleRefreshRequested);
|
|
|
+ connect(m_page, &DatabasePage::submitRequested,
|
|
|
+ this, &DatabaseModule::handleSubmitRequested);
|
|
|
+ connect(m_page, &DatabasePage::revertRequested,
|
|
|
+ this, &DatabaseModule::handleRevertRequested);
|
|
|
+ connect(m_page, &DatabasePage::deleteRowsRequested,
|
|
|
+ this, &DatabaseModule::handleDeleteRowsRequested);
|
|
|
+ connect(m_page, &DatabasePage::columnSearchRequested,
|
|
|
+ this, &DatabaseModule::handleColumnSearchRequested);
|
|
|
+
|
|
|
+ connect(m_page, &DatabasePage::pageChangeRequested,
|
|
|
+ this, &DatabaseModule::handlePageChangeRequested);
|
|
|
+ connect(m_page, &DatabasePage::pageSizeChangeRequested,
|
|
|
+ this, &DatabaseModule::handlePageSizeChanged);
|
|
|
+ connect(m_page, &DatabasePage::sortChangeRequested,
|
|
|
+ this, &DatabaseModule::handleSortChanged);
|
|
|
+
|
|
|
+ m_toolbar = new Toolbar(tr("数据库工具栏"), owner);
|
|
|
+ m_toolbar->hide();
|
|
|
+ QAction *configAction = m_toolbar->addAction(QIcon(":/qrc/icons/custom-menu-button.svg"),
|
|
|
+ tr("服务器配置"));
|
|
|
+ QAction *refreshAction = m_toolbar->addAction(QIcon(":/qrc/icons/restore.svg"),
|
|
|
+ tr("刷新"));
|
|
|
+ QAction *submitAction = m_toolbar->addAction(QIcon(":/qrc/icons/save.svg"),
|
|
|
+ tr("提交"));
|
|
|
+ QAction *revertAction = m_toolbar->addAction(QIcon(":/qrc/icons/lock_outline.svg"),
|
|
|
+ tr("撤销"));
|
|
|
+
|
|
|
+ connect(configAction, &QAction::triggered, this, [this]() {
|
|
|
+ if (m_connectionDockWidget) {
|
|
|
+ m_connectionDockWidget->toggleView(true);
|
|
|
+ if (m_connectionDock) {
|
|
|
+ m_connectionDock->setFocus();
|
|
|
+ } else if (QWidget *content = m_connectionDockWidget->widget()) {
|
|
|
+ content->setFocus();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ connect(refreshAction, &QAction::triggered,
|
|
|
+ this, &DatabaseModule::handleRefreshRequested);
|
|
|
+ connect(submitAction, &QAction::triggered,
|
|
|
+ this, &DatabaseModule::handleSubmitRequested);
|
|
|
+ connect(revertAction, &QAction::triggered,
|
|
|
+ this, &DatabaseModule::handleRevertRequested);
|
|
|
+
|
|
|
+ if (owner && owner->statusBar()) {
|
|
|
+ connect(this, &DatabaseModule::statusMessage,
|
|
|
+ owner->statusBar(), qOverload<const QString &, int>(&QStatusBar::showMessage));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleConnectRequested(const DatabaseConnectionParams ¶ms)
|
|
|
+{
|
|
|
+ if (!m_connectionDock) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ m_connectionDock->setBusy(true);
|
|
|
+ m_connectionDock->setStatusMessage(tr("正在连接 %1...").arg(params.host), true);
|
|
|
+
|
|
|
+ disconnectFromServer();
|
|
|
+
|
|
|
+ m_connectionName = QStringLiteral("dbmodule_%1")
|
|
|
+ .arg(QUuid::createUuid().toString(QUuid::Id128));
|
|
|
+
|
|
|
+ if (QSqlDatabase::contains(m_connectionName)) {
|
|
|
+ QSqlDatabase::removeDatabase(m_connectionName);
|
|
|
+ }
|
|
|
+
|
|
|
+ m_database = QSqlDatabase::addDatabase(params.driver, m_connectionName);
|
|
|
+ if (!params.host.isEmpty()) {
|
|
|
+ m_database.setHostName(params.host);
|
|
|
+ }
|
|
|
+ if (params.port > 0) {
|
|
|
+ m_database.setPort(params.port);
|
|
|
+ }
|
|
|
+ if (!params.database.isEmpty()) {
|
|
|
+ m_database.setDatabaseName(params.database);
|
|
|
+ }
|
|
|
+ if (!params.user.isEmpty()) {
|
|
|
+ m_database.setUserName(params.user);
|
|
|
+ }
|
|
|
+ if (!params.password.isEmpty()) {
|
|
|
+ m_database.setPassword(params.password);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_database.open()) {
|
|
|
+ const QString errorText = m_database.lastError().text();
|
|
|
+ updateStatus(tr("连接失败:%1").arg(errorText), false, 6000);
|
|
|
+ setConnected(false);
|
|
|
+ m_database = QSqlDatabase();
|
|
|
+ if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
|
|
|
+ QSqlDatabase::removeDatabase(m_connectionName);
|
|
|
+ }
|
|
|
+ m_connectionName.clear();
|
|
|
+ m_connectionDock->setBusy(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setConnected(true);
|
|
|
+ updateStatus(tr("数据库连接成功"), true, 3000);
|
|
|
+ loadTables();
|
|
|
+ m_connectionDock->setBusy(false);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleDisconnectRequested()
|
|
|
+{
|
|
|
+ disconnectFromServer();
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::disconnectFromServer()
|
|
|
+{
|
|
|
+ if (m_model) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->setTableModel(nullptr);
|
|
|
+ }
|
|
|
+ delete m_model;
|
|
|
+ m_model = nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ m_currentTable.clear();
|
|
|
+ m_totalRows = 0;
|
|
|
+ m_currentPage = 1;
|
|
|
+ m_sortColumn = 0;
|
|
|
+ m_sortOrder = Qt::AscendingOrder;
|
|
|
+ m_pageSize = kDefaultPageSize;
|
|
|
+
|
|
|
+ if (m_database.isValid() && m_database.isOpen()) {
|
|
|
+ m_database.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
|
|
|
+ QSqlDatabase::removeDatabase(m_connectionName);
|
|
|
+ }
|
|
|
+ m_database = QSqlDatabase();
|
|
|
+ m_connectionName.clear();
|
|
|
+
|
|
|
+ if (m_page) {
|
|
|
+ m_page->clear();
|
|
|
+ m_page->setConnectionActive(false);
|
|
|
+ }
|
|
|
+ if (m_connectionDock) {
|
|
|
+ m_connectionDock->setConnected(false);
|
|
|
+ m_connectionDock->setBusy(false);
|
|
|
+ m_connectionDock->setStatusMessage(tr("未连接"), false);
|
|
|
+ }
|
|
|
+ emit statusMessage(tr("数据库连接已断开"), 4000);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleTableActivated(const QString &tableName)
|
|
|
+{
|
|
|
+ if (!m_database.isValid() || !m_database.isOpen()) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("尚未连接到数据库"), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (tableName.isEmpty()) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->setCurrentTableName(QString());
|
|
|
+ m_page->setTableModel(nullptr);
|
|
|
+ }
|
|
|
+ if (m_model) {
|
|
|
+ m_model->clear();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ loadTable(tableName);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleRefreshRequested()
|
|
|
+{
|
|
|
+ if (m_currentTable.isEmpty()) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("请先选择要刷新的表"), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ refreshCurrentPage(true);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleSubmitRequested()
|
|
|
+{
|
|
|
+ if (!m_model || m_currentTable.isEmpty()) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("没有可提交的修改"), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions)) {
|
|
|
+ m_database.transaction();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_model->submitAll()) {
|
|
|
+ const QString errorText = m_model->lastError().text();
|
|
|
+ if (m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions)) {
|
|
|
+ m_database.rollback();
|
|
|
+ }
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("提交失败:%1").arg(errorText), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions)) {
|
|
|
+ m_database.commit();
|
|
|
+ }
|
|
|
+
|
|
|
+ refreshCurrentPage(true);
|
|
|
+
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("已提交 %1 的修改").arg(m_currentTable), true);
|
|
|
+ }
|
|
|
+ emit statusMessage(tr("表 %1 的修改已保存").arg(m_currentTable), 3000);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleRevertRequested()
|
|
|
+{
|
|
|
+ if (!m_model) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("没有可以撤销的修改"), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_model->revertAll();
|
|
|
+ refreshCurrentPage(true);
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("已撤销未提交的修改"), true);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleDeleteRowsRequested(const QModelIndexList &rows, bool batch)
|
|
|
+{
|
|
|
+ if (!m_model || m_currentTable.isEmpty()) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("当前没有可删除的数据"), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (rows.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QSet<int> uniqueRows;
|
|
|
+ for (const QModelIndex &index : rows) {
|
|
|
+ if (index.isValid()) {
|
|
|
+ uniqueRows.insert(index.row());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (uniqueRows.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QList<int> rowNumbers = uniqueRows.values();
|
|
|
+ std::sort(rowNumbers.begin(), rowNumbers.end());
|
|
|
+
|
|
|
+ for (auto it = rowNumbers.crbegin(); it != rowNumbers.crend(); ++it) {
|
|
|
+ const int row = *it;
|
|
|
+ if (!m_model->removeRow(row)) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("无法标记第 %1 行删除").arg(row + 1), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const bool supportsTransactions = m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions);
|
|
|
+ if (supportsTransactions) {
|
|
|
+ m_database.transaction();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_model->submitAll()) {
|
|
|
+ const QString errorText = m_model->lastError().text();
|
|
|
+ if (supportsTransactions) {
|
|
|
+ m_database.rollback();
|
|
|
+ }
|
|
|
+ m_model->revertAll();
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("删除失败:%1").arg(errorText), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (supportsTransactions) {
|
|
|
+ m_database.commit();
|
|
|
+ }
|
|
|
+
|
|
|
+ refreshCurrentPage(true);
|
|
|
+
|
|
|
+ const int deletedCount = rowNumbers.size();
|
|
|
+ if (m_page) {
|
|
|
+ const QString message = batch && deletedCount > 1
|
|
|
+ ? tr("已批量删除 %1 行数据").arg(deletedCount)
|
|
|
+ : tr("已删除 1 行数据");
|
|
|
+ m_page->showInfoMessage(message, true);
|
|
|
+ }
|
|
|
+ emit statusMessage(tr("从表 %1 删除了 %2 行").arg(m_currentTable).arg(deletedCount), 3000);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleColumnSearchRequested(int column, const QString &header)
|
|
|
+{
|
|
|
+ if (!m_page) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!m_database.isValid() || !m_database.isOpen()) {
|
|
|
+ m_page->showInfoMessage(tr("尚未连接到数据库"), false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!m_model || m_currentTable.isEmpty()) {
|
|
|
+ m_page->showInfoMessage(tr("请先选择要搜索的数据表"), false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (column < 0 || column >= m_model->columnCount()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_searchDialog) {
|
|
|
+ m_searchDialog = new DatabaseSearchDialog(m_page);
|
|
|
+ connect(m_searchDialog, &DatabaseSearchDialog::requestSearch,
|
|
|
+ this, &DatabaseModule::handleSearchRequest);
|
|
|
+ }
|
|
|
+
|
|
|
+ const QStringList headers = currentHeaderLabels();
|
|
|
+ QString displayHeader = header;
|
|
|
+ if (displayHeader.isEmpty() && column >= 0 && column < headers.size()) {
|
|
|
+ displayHeader = headers.at(column);
|
|
|
+ }
|
|
|
+ if (displayHeader.isEmpty()) {
|
|
|
+ const QString fieldName = m_model->record().fieldName(column);
|
|
|
+ displayHeader = fieldName.isEmpty() ? tr("列 %1").arg(column + 1) : fieldName;
|
|
|
+ }
|
|
|
+
|
|
|
+ m_searchColumn = column;
|
|
|
+ m_searchDialog->setBusy(false);
|
|
|
+ m_searchDialog->setTableContext(m_currentTable, displayHeader);
|
|
|
+ m_searchDialog->setAvailableColumns(headers);
|
|
|
+ m_searchDialog->setInitialSortColumn(column);
|
|
|
+ m_searchDialog->resetSearch();
|
|
|
+ m_searchDialog->show();
|
|
|
+ m_searchDialog->raise();
|
|
|
+ m_searchDialog->activateWindow();
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleSearchRequest(const QString &keyword,
|
|
|
+ int page,
|
|
|
+ int pageSize,
|
|
|
+ int sortColumn,
|
|
|
+ Qt::SortOrder order)
|
|
|
+{
|
|
|
+ if (!m_searchDialog) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!m_model || m_currentTable.isEmpty()) {
|
|
|
+ showSearchError(tr("当前没有可搜索的表"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!m_database.isValid() || !m_database.isOpen()) {
|
|
|
+ showSearchError(tr("尚未连接到数据库"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (m_searchColumn < 0 || m_searchColumn >= m_model->columnCount()) {
|
|
|
+ showSearchError(tr("搜索列无效"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QStringList fieldNames = currentFieldNames();
|
|
|
+ if (fieldNames.isEmpty()) {
|
|
|
+ showSearchError(tr("无法获取列信息"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (sortColumn < 0 || sortColumn >= fieldNames.size()) {
|
|
|
+ sortColumn = m_searchColumn;
|
|
|
+ }
|
|
|
+ const QString searchField = fieldNames.at(m_searchColumn);
|
|
|
+ const QString sortField = fieldNames.at(sortColumn);
|
|
|
+ if (searchField.isEmpty() || sortField.isEmpty()) {
|
|
|
+ showSearchError(tr("列名为空,无法执行搜索"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QSqlDriver *driver = m_database.driver();
|
|
|
+ if (!driver) {
|
|
|
+ showSearchError(tr("数据库驱动不可用"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const QString tableIdentifier = driver->escapeIdentifier(m_currentTable, QSqlDriver::TableName);
|
|
|
+ const QString searchIdentifier = driver->escapeIdentifier(searchField, QSqlDriver::FieldName);
|
|
|
+ const QString sortIdentifier = driver->escapeIdentifier(sortField, QSqlDriver::FieldName);
|
|
|
+
|
|
|
+ QStringList escapedFields;
|
|
|
+ escapedFields.reserve(fieldNames.size());
|
|
|
+ for (const QString &name : std::as_const(fieldNames)) {
|
|
|
+ escapedFields.append(driver->escapeIdentifier(name, QSqlDriver::FieldName));
|
|
|
+ }
|
|
|
+
|
|
|
+ const int safePageSize = pageSize > 0 ? pageSize : 1;
|
|
|
+ const QString likePattern = keyword.isEmpty()
|
|
|
+ ? QStringLiteral("%")
|
|
|
+ : QStringLiteral("%") + keyword + QStringLiteral("%");
|
|
|
+
|
|
|
+ const QString whereClause = QStringLiteral("%1 LIKE ?").arg(searchIdentifier);
|
|
|
+ const QString countSql = QStringLiteral("SELECT COUNT(*) FROM %1 WHERE %2")
|
|
|
+ .arg(tableIdentifier, whereClause);
|
|
|
+
|
|
|
+ QSqlQuery countQuery(m_database);
|
|
|
+ if (!countQuery.prepare(countSql)) {
|
|
|
+ showSearchError(tr("统计匹配数量失败:%1").arg(countQuery.lastError().text()));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ countQuery.addBindValue(likePattern);
|
|
|
+ if (!countQuery.exec() || !countQuery.next()) {
|
|
|
+ showSearchError(tr("统计匹配数量失败:%1").arg(countQuery.lastError().text()));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const int totalRows = countQuery.value(0).toInt();
|
|
|
+ const int totalPages = (totalRows > 0 && safePageSize > 0)
|
|
|
+ ? (totalRows + safePageSize - 1) / safePageSize
|
|
|
+ : 0;
|
|
|
+
|
|
|
+ int safePage = page;
|
|
|
+ if (safePage < 1) {
|
|
|
+ safePage = 1;
|
|
|
+ }
|
|
|
+ if (totalPages > 0 && safePage > totalPages) {
|
|
|
+ safePage = totalPages;
|
|
|
+ }
|
|
|
+ if (totalPages == 0) {
|
|
|
+ safePage = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int offset = (safePage - 1) * safePageSize;
|
|
|
+ QString selectSql = QStringLiteral("SELECT %1 FROM %2 WHERE %3")
|
|
|
+ .arg(escapedFields.join(QStringLiteral(", ")),
|
|
|
+ tableIdentifier,
|
|
|
+ whereClause);
|
|
|
+ const QString orderToken = order == Qt::AscendingOrder ? QStringLiteral("ASC") : QStringLiteral("DESC");
|
|
|
+ selectSql += QStringLiteral(" ORDER BY %1 %2").arg(sortIdentifier, orderToken);
|
|
|
+ selectSql += QStringLiteral(" LIMIT %1 OFFSET %2")
|
|
|
+ .arg(QString::number(safePageSize), QString::number(offset));
|
|
|
+
|
|
|
+ QSqlQuery selectQuery(m_database);
|
|
|
+ if (!selectQuery.prepare(selectSql)) {
|
|
|
+ showSearchError(tr("执行查询失败:%1").arg(selectQuery.lastError().text()));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ selectQuery.addBindValue(likePattern);
|
|
|
+ if (!selectQuery.exec()) {
|
|
|
+ showSearchError(tr("执行查询失败:%1").arg(selectQuery.lastError().text()));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QList<QVariantList> rows;
|
|
|
+ rows.reserve(safePageSize);
|
|
|
+
|
|
|
+ while (selectQuery.next()) {
|
|
|
+ QVariantList row;
|
|
|
+ row.reserve(fieldNames.size());
|
|
|
+ for (int columnIndex = 0; columnIndex < fieldNames.size(); ++columnIndex) {
|
|
|
+ row.append(selectQuery.value(columnIndex));
|
|
|
+ }
|
|
|
+ rows.append(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ const QStringList headers = currentHeaderLabels();
|
|
|
+ m_searchDialog->setResults(rows, headers, totalRows, safePage, safePageSize);
|
|
|
+ if (totalRows > 0) {
|
|
|
+ m_searchDialog->setStatusMessage(tr("共找到 %1 条记录").arg(totalRows), false);
|
|
|
+ }
|
|
|
+ m_searchDialog->setBusy(false);
|
|
|
+}
|
|
|
+
|
|
|
+QStringList DatabaseModule::currentHeaderLabels() const
|
|
|
+{
|
|
|
+ QStringList headers;
|
|
|
+ if (!m_model) {
|
|
|
+ return headers;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int columnCount = m_model->columnCount();
|
|
|
+ headers.reserve(columnCount);
|
|
|
+ for (int column = 0; column < columnCount; ++column) {
|
|
|
+ QString header = m_model->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString();
|
|
|
+ if (header.isEmpty()) {
|
|
|
+ header = tr("列 %1").arg(column + 1);
|
|
|
+ }
|
|
|
+ headers.append(header);
|
|
|
+ }
|
|
|
+ return headers;
|
|
|
+}
|
|
|
+
|
|
|
+QStringList DatabaseModule::currentFieldNames() const
|
|
|
+{
|
|
|
+ QStringList fieldNames;
|
|
|
+ if (!m_model) {
|
|
|
+ return fieldNames;
|
|
|
+ }
|
|
|
+ const QSqlRecord record = m_model->record();
|
|
|
+ const int columnCount = record.count();
|
|
|
+ fieldNames.reserve(columnCount);
|
|
|
+ for (int column = 0; column < columnCount; ++column) {
|
|
|
+ fieldNames.append(record.fieldName(column));
|
|
|
+ }
|
|
|
+ return fieldNames;
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::showSearchError(const QString &message)
|
|
|
+{
|
|
|
+ if (m_searchDialog) {
|
|
|
+ m_searchDialog->setStatusMessage(message, true);
|
|
|
+ m_searchDialog->setBusy(false);
|
|
|
+ }
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(message, false);
|
|
|
+ }
|
|
|
+ emit statusMessage(message, 4000);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::resetModel()
|
|
|
+{
|
|
|
+ if (m_model) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->setTableModel(nullptr);
|
|
|
+ }
|
|
|
+ delete m_model;
|
|
|
+ m_model = nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_database.isValid()) {
|
|
|
+ m_model = new PagedSqlTableModel(m_page, m_database);
|
|
|
+ if (m_page) {
|
|
|
+ m_page->setTableModel(m_model);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::loadTables()
|
|
|
+{
|
|
|
+ if (!m_database.isValid() || !m_database.isOpen() || !m_page) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const QStringList baseTables = m_database.tables(QSql::Tables);
|
|
|
+ QSet<QString> uniqueTables(baseTables.cbegin(), baseTables.cend());
|
|
|
+ const QStringList views = m_database.tables(QSql::Views);
|
|
|
+ for (const QString &view : views) {
|
|
|
+ uniqueTables.insert(view);
|
|
|
+ }
|
|
|
+
|
|
|
+ QStringList tableList = uniqueTables.values();
|
|
|
+ std::sort(tableList.begin(), tableList.end(), [](const QString &a, const QString &b) {
|
|
|
+ return QString::localeAwareCompare(a, b) < 0;
|
|
|
+ });
|
|
|
+
|
|
|
+ m_page->setTableNames(tableList);
|
|
|
+ m_page->showInfoMessage(tr("已加载 %1 张表").arg(tableList.size()), true);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::loadTable(const QString &tableName)
|
|
|
+{
|
|
|
+ if (!m_database.isValid() || !m_database.isOpen()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_model) {
|
|
|
+ resetModel();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!m_model) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("无法创建数据模型"), false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tableName.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ m_currentTable = tableName;
|
|
|
+ m_currentPage = 1;
|
|
|
+ m_sortColumn = 0;
|
|
|
+ m_sortOrder = Qt::AscendingOrder;
|
|
|
+
|
|
|
+ m_model->setTable(tableName);
|
|
|
+
|
|
|
+ if (!calculateTotalRowCount()) {
|
|
|
+ updatePaginationUi();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!refreshCurrentPage(false)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_page) {
|
|
|
+ m_page->setCurrentTableName(tableName);
|
|
|
+ m_page->showInfoMessage(tr("已加载表 %1").arg(tableName), true);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::setConnected(bool connected)
|
|
|
+{
|
|
|
+ if (m_connectionDock) {
|
|
|
+ m_connectionDock->setConnected(connected);
|
|
|
+ }
|
|
|
+ if (m_page) {
|
|
|
+ m_page->setConnectionActive(connected);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::updateStatus(const QString &message, bool success, int timeout)
|
|
|
+{
|
|
|
+ if (m_connectionDock) {
|
|
|
+ m_connectionDock->setStatusMessage(message, success);
|
|
|
+ }
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(message, success);
|
|
|
+ }
|
|
|
+ emit statusMessage(message, timeout);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void DatabaseModule::handlePageChangeRequested(int page)
|
|
|
+{
|
|
|
+ if (page <= 0 || !m_model || m_currentTable.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int targetPage = page;
|
|
|
+ const int pages = totalPages();
|
|
|
+ if (pages > 0 && targetPage > pages) {
|
|
|
+ targetPage = pages;
|
|
|
+ }
|
|
|
+ if (targetPage == m_currentPage) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_currentPage = targetPage;
|
|
|
+ refreshCurrentPage(false);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handlePageSizeChanged(int pageSize)
|
|
|
+{
|
|
|
+ if (pageSize <= 0 || !m_model || m_currentTable.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (pageSize == m_pageSize) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_pageSize = pageSize;
|
|
|
+ m_currentPage = 1;
|
|
|
+ refreshCurrentPage(true);
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::handleSortChanged(int column, Qt::SortOrder order)
|
|
|
+{
|
|
|
+ if (!m_model || m_currentTable.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int targetColumn = column;
|
|
|
+ const int columnCount = m_model->columnCount();
|
|
|
+ if (targetColumn < 0 || targetColumn >= columnCount) {
|
|
|
+ targetColumn = 0;
|
|
|
+ }
|
|
|
+ if (m_sortColumn == targetColumn && m_sortOrder == order) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ m_sortColumn = targetColumn;
|
|
|
+ m_sortOrder = order;
|
|
|
+ m_currentPage = 1;
|
|
|
+ refreshCurrentPage(false);
|
|
|
+}
|
|
|
+
|
|
|
+bool DatabaseModule::refreshCurrentPage(bool recalcRowCount)
|
|
|
+{
|
|
|
+ if (!m_model || m_currentTable.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (recalcRowCount && !calculateTotalRowCount()) {
|
|
|
+ updatePaginationUi();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m_pageSize <= 0) {
|
|
|
+ m_pageSize = kDefaultPageSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ int pages = totalPages();
|
|
|
+ if (pages > 0) {
|
|
|
+ if (m_currentPage < 1) {
|
|
|
+ m_currentPage = 1;
|
|
|
+ } else if (m_currentPage > pages) {
|
|
|
+ m_currentPage = pages;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ m_currentPage = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto *pagedModel = static_cast<PagedSqlTableModel *>(m_model);
|
|
|
+
|
|
|
+ const int columnCount = m_model->columnCount();
|
|
|
+ if (columnCount > 0) {
|
|
|
+ if (m_sortColumn < 0 || m_sortColumn >= columnCount) {
|
|
|
+ m_sortColumn = 0;
|
|
|
+ }
|
|
|
+ m_model->setSort(m_sortColumn, m_sortOrder);
|
|
|
+ }
|
|
|
+
|
|
|
+ const int offset = (m_currentPage - 1) * m_pageSize;
|
|
|
+ pagedModel->setPagination(offset, std::max(1, m_pageSize));
|
|
|
+
|
|
|
+ if (!pagedModel->select()) {
|
|
|
+ const QString errorText = pagedModel->lastError().text();
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("加载表失败:%1").arg(errorText), false);
|
|
|
+ m_page->setPaginationEnabled(false);
|
|
|
+ m_page->updatePaginationInfo(0, 0, 0);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ updatePaginationUi();
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void DatabaseModule::updatePaginationUi()
|
|
|
+{
|
|
|
+ if (!m_page) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const QStringList headers = currentHeaderLabels();
|
|
|
+ if (!headers.isEmpty()) {
|
|
|
+ if (m_sortColumn < 0 || m_sortColumn >= headers.size()) {
|
|
|
+ m_sortColumn = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ m_page->setSortOptions(headers);
|
|
|
+ m_page->setCurrentSort(m_sortColumn, m_sortOrder);
|
|
|
+ m_page->setPageSizeValue(m_pageSize);
|
|
|
+
|
|
|
+ const int pages = totalPages();
|
|
|
+ const int displayPage = (m_totalRows > 0 && pages > 0) ? m_currentPage : 0;
|
|
|
+ m_page->setPaginationEnabled(!m_currentTable.isEmpty());
|
|
|
+ m_page->updatePaginationInfo(displayPage, pages, m_totalRows);
|
|
|
+}
|
|
|
+
|
|
|
+bool DatabaseModule::calculateTotalRowCount()
|
|
|
+{
|
|
|
+ if (!m_database.isValid() || !m_database.isOpen() || m_currentTable.isEmpty()) {
|
|
|
+ m_totalRows = 0;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ QSqlDriver *driver = m_database.driver();
|
|
|
+ if (!driver) {
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("数据库驱动不可用"), false);
|
|
|
+ }
|
|
|
+ m_totalRows = 0;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const QString tableIdentifier = driver->escapeIdentifier(m_currentTable, QSqlDriver::TableName);
|
|
|
+ QSqlQuery query(m_database);
|
|
|
+ const QString countSql = QStringLiteral("SELECT COUNT(*) FROM %1").arg(tableIdentifier);
|
|
|
+ if (!query.exec(countSql) || !query.next()) {
|
|
|
+ const QString errorText = query.lastError().text();
|
|
|
+ if (m_page) {
|
|
|
+ m_page->showInfoMessage(tr("统计表行数失败:%1").arg(errorText), false);
|
|
|
+ }
|
|
|
+ m_totalRows = 0;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ m_totalRows = query.value(0).toInt();
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+int DatabaseModule::totalPages() const
|
|
|
+{
|
|
|
+ if (m_pageSize <= 0 || m_totalRows <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return (m_totalRows + m_pageSize - 1) / m_pageSize;
|
|
|
+}
|