From 569d8c4138ae9b4fb4cab1a65be67e34af6132a6 Mon Sep 17 00:00:00 2001 From: Albert S Date: Thu, 25 Apr 2019 10:27:54 +0200 Subject: [PATCH] Begin sort: statement implementation, use bindvalues in all search filters, gui: save current scale --- gui/mainwindow.cpp | 7 +- gui/searchworker.cpp | 146 ------------------------------- shared/sqlitesearch.cpp | 187 +++++++++++++++++++++++++++++++--------- shared/sqlitesearch.h | 17 ++-- 4 files changed, 163 insertions(+), 194 deletions(-) diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 3ea2ae4..7820df5 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -13,6 +13,7 @@ #include "mainwindow.h" #include "ui_mainwindow.h" #include "clicklabel.h" +#include "../shared/sqlitesearch.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); @@ -29,6 +30,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi ui->statusBar->addWidget(ui->lblSearchResults); ui->statusBar->addWidget(ui->pdfProcessBar); ui->pdfProcessBar->hide(); + QSettings settings; + ui->comboScale->setCurrentText(settings.value("currentScale").toString()); } void MainWindow::connectSignals() @@ -51,6 +54,8 @@ void MainWindow::connectSignals() void MainWindow::comboScaleChanged(QString text) { + QSettings scaleSetting; + scaleSetting.setValue("currentScale", ui->comboScale->currentText()); makePdfPreview(); } bool MainWindow::pdfTabActive() @@ -129,7 +134,7 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview) void MainWindow::lineEditReturnPressed() { QString q = ui->txtSearch->text(); - if(!searchWorker->checkParanthesis(q)) + if(!SqliteSearch::checkParanthesis(q)) { ui->lblSearchResults->setText("Invalid paranthesis"); return; diff --git a/gui/searchworker.cpp b/gui/searchworker.cpp index 53aa600..2c1f45e 100644 --- a/gui/searchworker.cpp +++ b/gui/searchworker.cpp @@ -18,152 +18,6 @@ SearchWorker::SearchWorker(const QString &dbpath) } } -QVector SearchWorker::tokenize(QString expression) -{ - if(!checkParanthesis(expression)) - { - throw std::invalid_argument("Invalid paranthesis"); - } - // TODO: merge lonewords - QVector result; - QRegularExpression rx("((?(\\.|\\w)+):(?\\((?[^\\)]+)\\)|(\\w)+)|(?AND|OR|!)|" - "(?\\(|\\))|(?\\w+))"); - QRegularExpressionMatchIterator i = rx.globalMatch(expression); - bool wasbool = true; - while(i.hasNext()) - { - QRegularExpressionMatch m = i.next(); - QString boolean = m.captured("boolean"); - QString filtername = m.captured("filtername"); - QString bracket = m.captured("bracket"); - QString loneword = m.captured("loneword"); - if(boolean != "") - { - /* if(wasbool) - { - throw new std::runtime_error("Bool after Bool is invalid"); - }*/ - wasbool = true; - result.append(Command(boolean)); - } - - if(bracket != "") - { - if(!wasbool) - { - if(bracket == "(") - { - result.append(Command("AND")); - } - } - result.append(Command(bracket)); - } - - if(loneword != "") - { - if(!wasbool) - { - result.append(Command("AND")); - } - wasbool = false; - result.append(Command("path.contains", loneword)); - } - if(filtername != "") - { - if(!wasbool) - { - result.append(Command("AND")); - } - wasbool = false; - QString value = m.captured("innerargs"); - if(value == "") - value = m.captured("args"); - result.append(Command(filtername, value)); - } - } - return result; -} - -QString SearchWorker::createSql(const SearchWorker::Command &cmd) -{ - QString key = cmd.key; - QString value = cmd.value; - value = value.replace("'", "\\'"); - if(key == "AND" || key == "OR" || key == "(" || key == ")") - { - return " " + key + " "; - } - if(key == "!") - { - return " NOT "; - } - if(key == "path.starts") - { - return " file.path LIKE '" + value + "%' "; - } - if(key == "path.ends") - { - return " file.path LIKE '%" + value + "' "; - } - if(key == "path.contains" || key == "inpath") - { - return " file.path LIKE '%" + value + "%' "; - } - if(key == "page") - { - return " content.page = " + value; - } - if(key == "contains" || key == "c") - { - return " content.id IN (SELECT content_fts.ROWID FROM content_fts WHERE content_fts.content MATCH '" + value + - "' )"; - } - - throw std::invalid_argument("Unknown filter: " + key.toStdString()); -} - -QString SearchWorker::makeSql(const QVector &tokens) -{ - QString result; - for(const Command &c : tokens) - { - result += createSql(c); - } - return result; -} - -bool SearchWorker::checkParanthesis(QString expression) -{ - QStack open; - QStack close; - - for(QChar &c : expression) - { - if(c == '(') - { - open.push(c); - } - if(c == ')') - { - close.push(c); - } - } - if(open.size() != close.size()) - { - return false; - } - while(!open.empty() && !close.empty()) - { - QChar o = open.pop(); - QChar c = close.pop(); - if(o != '(' && c != ')') - { - return false; - } - } - return true; -} - void SearchWorker::search(const QString &query) { SqliteSearch searcher(db); diff --git a/shared/sqlitesearch.cpp b/shared/sqlitesearch.cpp index b6df053..09289aa 100644 --- a/shared/sqlitesearch.cpp +++ b/shared/sqlitesearch.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "sqlitesearch.h" #include "qssgeneralexception.h" @@ -77,89 +78,195 @@ QVector SqliteSearch::tokenize(QString expression) return result; } -QString SqliteSearch::createSql(const SqliteSearch::Token &token) +QString SqliteSearch::fieldToColumn(QString field) { + if(field == "mtime" || field == "file.mtime") + { + return "file.mtime"; + } + else if(field == "page" || field == "content.page") + { + return "content.page"; + } + else if(field == "path" || field == "file.path") + { + return "file.path"; + } + else if(field == "size" || field == "file.size") + { + return "file.size"; + } + return ""; +} + +QString SqliteSearch::createSortSql(const SqliteSearch::Token &token) +{ + // sort:(mtime desc, page asc) + if(token.key == "sort") + { + QString sortsql = " ORDER BY "; + QStringList splitted_inner = token.value.split(","); + for(int i = 0; i < splitted_inner.length(); i++) + { + QStringList splitted = splitted_inner[i].split(" "); + if(splitted.length() == 2) + { + QString field = splitted[0]; + QString order = splitted[1]; + if(order.compare("asc", Qt::CaseInsensitive)) + { + order = "ASC"; + } + else if(order.compare("desc", Qt::CaseInsensitive)) + { + order = "DESC"; + } + else + { + throw QSSGeneralException("Unknown order specifier: " + order); + } + + field = fieldToColumn(field); + if(field == "") + { + throw QSSGeneralException("Unknown field:" + field); + } + + sortsql += field + " " + order; + if(splitted_inner.length() - i > 1) + { + sortsql += ", "; + } + } + else if(splitted.length() == 1) + { + sortsql += splitted[0] + " ASC "; + } + else + { + throw QSSGeneralException("sort specifier must have format [field] (asc|desc)"); + } + } + return sortsql; + } + return ""; +} + +QPair> SqliteSearch::createSql(const SqliteSearch::Token &token) +{ + QPair> result; + QString key = token.key; QString value = token.value; value = value.replace("'", "\\'"); if(key == "AND" || key == "OR" || key == "(" || key == ")") { - return " " + key + " "; + return {" " + key + " ", QVector()}; } if(key == "!") { - return " NOT "; + return {" NOT ", QVector()}; } if(key == "path.starts") { - return " file.path LIKE '" + value + "%' "; + return {" file.path LIKE ? || '%' ", {value}}; } if(key == "path.ends") { - return " file.path LIKE '%" + value + "' "; + return {" file.path LIKE '%' || ? ", {value}}; } if(key == "path.contains" || key == "inpath") { - return " file.path LIKE '%" + value + "%' "; + return {" file.path LIKE '%' || ? || '%' ", {value}}; } if(key == "page") { - return " content.page = " + value; + return {" content.page = ?", {value}}; } if(key == "contains" || key == "c") { - return " content.id IN (SELECT content_fts.ROWID FROM content_fts WHERE content_fts.content MATCH '" + value + - "' )"; + return {" content.id IN (SELECT content_fts.ROWID FROM content_fts WHERE content_fts.content MATCH ?) ", + {value}}; } throw QSSGeneralException("Unknown token: " + key); } -QString SqliteSearch::makeSql(const QVector &tokens) +QSqlQuery SqliteSearch::makeSqlQuery(const QVector &tokens) { - QString result; + QString whereSql; + QString sortSql; + QString limitSql; + QVector bindValues; + bool isContentSearch = false; for(const Token &c : tokens) { - result += createSql(c); + if(c.key == "sort") + { + if(sortSql != "") + { + throw QSSGeneralException("Invalid input: Two seperate sort statements are invalid"); + } + sortSql = createSortSql(c); + } + else + { + if(c.key == "c" || c.key == "contains") + { + isContentSearch = true; + } + auto sql = createSql(c); + whereSql += sql.first; + bindValues.append(sql.second); + } } - return result; + + QString prepSql; + if(isContentSearch) + { + prepSql = "SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, " + "file.filetype AS filetype FROM file INNER JOIN content ON file.id = content.fileid WHERE 1=1 AND " + + whereSql + " " + sortSql; + } + else + { + prepSql = "SELECT file.path AS path, 0 as page, file.mtime AS mtime, file.size AS size, file.filetype AS " + "filetype FROM file WHERE 1=1 AND " + + whereSql + " " + sortSql; + } + + QSqlQuery dbquery(*db); + dbquery.prepare(prepSql); + + for(const QString &value : bindValues) + { + if(value != "") + { + dbquery.addBindValue(value); + } + } + return dbquery; } QVector SqliteSearch::search(const QString &query) { QVector results; - QString whereSql = makeSql(tokenize(query)); - QString prep; - // TODO: hack, as we don't wanna look into content and get redundant results, when we don't even care about content - if(whereSql.contains("content.")) - { - prep = "SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, file.filetype " - "AS filetype FROM file INNER JOIN content ON file.id = content.fileid WHERE 1=1 AND " + - whereSql + " ORDER By file.mtime DESC, content.page ASC"; - } - else - { - prep = "SELECT file.path AS path, 0 as page, file.mtime AS mtime, file.size AS size, file.filetype AS " - "filetype FROM file WHERE " + - whereSql + " ORDER by file.mtime DESC"; - } - QSqlQuery dbquery(*db); - dbquery.prepare(prep); - bool success = dbquery.exec(); + QSqlQuery dbQuery = makeSqlQuery(tokenize(query)); + bool success = dbQuery.exec(); if(!success) { - qDebug() << "prepped: " << prep; - qDebug() << dbquery.lastError(); - throw QSSGeneralException("SQL Error: " + dbquery.lastError().text()); + + qDebug() << dbQuery.lastError(); + throw QSSGeneralException("SQL Error: " + dbQuery.lastError().text()); } - while(dbquery.next()) + while(dbQuery.next()) { SearchResult result; - result.fileData.absPath = dbquery.value("path").toString(); - result.fileData.mtime = dbquery.value("mtime").toUInt(); - result.fileData.size = dbquery.value("size").toUInt(); - result.fileData.filetype = dbquery.value("filetype").toChar(); - result.page = dbquery.value("page").toUInt(); + result.fileData.absPath = dbQuery.value("path").toString(); + result.fileData.mtime = dbQuery.value("mtime").toUInt(); + result.fileData.size = dbQuery.value("size").toUInt(); + result.fileData.filetype = dbQuery.value("filetype").toChar(); + result.page = dbQuery.value("page").toUInt(); results.append(result); } return results; diff --git a/shared/sqlitesearch.h b/shared/sqlitesearch.h index 0a16399..ccdfe82 100644 --- a/shared/sqlitesearch.h +++ b/shared/sqlitesearch.h @@ -1,6 +1,7 @@ #ifndef SQLITESEARCH_H #define SQLITESEARCH_H #include +#include #include "searchresult.h" class SqliteSearch @@ -18,16 +19,18 @@ class SqliteSearch } }; - private: - QSqlDatabase *db; - QVector tokenize(QString expression); - QString createSql(const Token &token); - QString makeSql(const QVector &tokens); - bool checkParanthesis(QString expression); - public: SqliteSearch(QSqlDatabase &db); QVector search(const QString &query); + static bool checkParanthesis(QString expression); + + private: + QSqlDatabase *db; + QVector tokenize(QString expression); + QSqlQuery makeSqlQuery(const QVector &tokens); + QString fieldToColumn(QString col); + QPair> createSql(const Token &token); + QString createSortSql(const SqliteSearch::Token &token); }; #endif // SQLITESEARCH_H