Begin sort: statement implementation, use bindvalues in all search filters, gui: save current scale
This commit is contained in:
parent
131ab2e4e8
commit
e2f07d2357
@ -13,6 +13,7 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "clicklabel.h"
|
#include "clicklabel.h"
|
||||||
|
#include "../shared/sqlitesearch.h"
|
||||||
MainWindow::MainWindow(QWidget *parent) :
|
MainWindow::MainWindow(QWidget *parent) :
|
||||||
QMainWindow(parent),
|
QMainWindow(parent),
|
||||||
ui(new Ui::MainWindow)
|
ui(new Ui::MainWindow)
|
||||||
@ -31,6 +32,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||||||
ui->statusBar->addWidget(ui->lblSearchResults);
|
ui->statusBar->addWidget(ui->lblSearchResults);
|
||||||
ui->statusBar->addWidget(ui->pdfProcessBar);
|
ui->statusBar->addWidget(ui->pdfProcessBar);
|
||||||
ui->pdfProcessBar->hide();
|
ui->pdfProcessBar->hide();
|
||||||
|
QSettings settings;
|
||||||
|
ui->comboScale->setCurrentText(settings.value("currentScale").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::connectSignals()
|
void MainWindow::connectSignals()
|
||||||
@ -51,6 +54,8 @@ void MainWindow::connectSignals()
|
|||||||
|
|
||||||
void MainWindow::comboScaleChanged(QString text)
|
void MainWindow::comboScaleChanged(QString text)
|
||||||
{
|
{
|
||||||
|
QSettings scaleSetting;
|
||||||
|
scaleSetting.setValue("currentScale", ui->comboScale->currentText());
|
||||||
makePdfPreview();
|
makePdfPreview();
|
||||||
}
|
}
|
||||||
bool MainWindow::pdfTabActive()
|
bool MainWindow::pdfTabActive()
|
||||||
@ -126,7 +131,7 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview)
|
|||||||
void MainWindow::lineEditReturnPressed()
|
void MainWindow::lineEditReturnPressed()
|
||||||
{
|
{
|
||||||
QString q = ui->txtSearch->text();
|
QString q = ui->txtSearch->text();
|
||||||
if(!searchWorker->checkParanthesis(q))
|
if(!SqliteSearch::checkParanthesis(q))
|
||||||
{
|
{
|
||||||
ui->lblSearchResults->setText("Invalid paranthesis");
|
ui->lblSearchResults->setText("Invalid paranthesis");
|
||||||
return;
|
return;
|
||||||
|
@ -19,157 +19,9 @@ SearchWorker::SearchWorker(const QString &dbpath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<SearchWorker::Command> SearchWorker::tokenize(QString expression)
|
|
||||||
{
|
|
||||||
if(!checkParanthesis(expression))
|
|
||||||
{
|
|
||||||
throw std::invalid_argument("Invalid paranthesis");
|
|
||||||
}
|
|
||||||
//TODO: merge lonewords
|
|
||||||
QVector<Command> result;
|
|
||||||
QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|(\\w)+)|(?<boolean>AND|OR|!)|(?<bracket>\\(|\\))|(?<loneword>\\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<SearchWorker::Command> &tokens)
|
|
||||||
{
|
|
||||||
QString result;
|
|
||||||
for(const Command &c : tokens)
|
|
||||||
{
|
|
||||||
result += createSql(c);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SearchWorker::checkParanthesis(QString expression)
|
|
||||||
{
|
|
||||||
QStack<QChar> open;
|
|
||||||
QStack<QChar> 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)
|
void SearchWorker::search(const QString &query)
|
||||||
{
|
{
|
||||||
SqliteSearch searcher(db);
|
SqliteSearch searcher(db);
|
||||||
emit searchResultsReady(searcher.search(query));
|
emit searchResultsReady(searcher.search(query));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
|
#include <QStringList>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "sqlitesearch.h"
|
#include "sqlitesearch.h"
|
||||||
#include "qssgeneralexception.h"
|
#include "qssgeneralexception.h"
|
||||||
@ -79,85 +80,195 @@ QVector<SqliteSearch::Token> SqliteSearch::tokenize(QString expression)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString SqliteSearch::fieldToColumn(QString field)
|
||||||
QString SqliteSearch::createSql(const SqliteSearch::Token &token)
|
|
||||||
{
|
{
|
||||||
|
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<QString, QVector<QString>> SqliteSearch::createSql(const SqliteSearch::Token &token)
|
||||||
|
{
|
||||||
|
QPair<QString, QVector<QString>> result;
|
||||||
|
|
||||||
QString key = token.key;
|
QString key = token.key;
|
||||||
QString value = token.value;
|
QString value = token.value;
|
||||||
value = value.replace("'", "\\'");
|
value = value.replace("'", "\\'");
|
||||||
if(key == "AND" || key == "OR" || key == "(" || key == ")")
|
if(key == "AND" || key == "OR" || key == "(" || key == ")")
|
||||||
{
|
{
|
||||||
return " " + key + " ";
|
return { " " + key + " ", QVector<QString>() };
|
||||||
}
|
}
|
||||||
if(key == "!")
|
if(key == "!")
|
||||||
{
|
{
|
||||||
return " NOT ";
|
return { " NOT ", QVector<QString>() };
|
||||||
}
|
}
|
||||||
if(key == "path.starts")
|
if(key == "path.starts")
|
||||||
{
|
{
|
||||||
return " file.path LIKE '" + value + "%' ";
|
return { " file.path LIKE ? || '%' ", { value } };
|
||||||
}
|
}
|
||||||
if(key == "path.ends")
|
if(key == "path.ends")
|
||||||
{
|
{
|
||||||
return " file.path LIKE '%" + value + "' ";
|
return { " file.path LIKE '%' || ? ", { value } };
|
||||||
}
|
}
|
||||||
if(key == "path.contains" || key == "inpath")
|
if(key == "path.contains" || key == "inpath")
|
||||||
{
|
{
|
||||||
return " file.path LIKE '%" + value + "%' ";
|
return { " file.path LIKE '%' || ? || '%' ", { value } } ;
|
||||||
}
|
}
|
||||||
if(key == "page")
|
if(key == "page")
|
||||||
{
|
{
|
||||||
return " content.page = " + value;
|
return { " content.page = ?", { value } };
|
||||||
}
|
}
|
||||||
if(key == "contains" || key == "c")
|
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);
|
throw QSSGeneralException("Unknown token: " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SqliteSearch::makeSql(const QVector<SqliteSearch::Token> &tokens)
|
QSqlQuery SqliteSearch::makeSqlQuery(const QVector<SqliteSearch::Token> &tokens)
|
||||||
{
|
{
|
||||||
QString result;
|
QString whereSql;
|
||||||
|
QString sortSql;
|
||||||
|
QString limitSql;
|
||||||
|
QVector<QString> bindValues;
|
||||||
|
bool isContentSearch = false;
|
||||||
for(const Token &c : tokens)
|
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<SearchResult> SqliteSearch::search(const QString &query)
|
QVector<SearchResult> SqliteSearch::search(const QString &query)
|
||||||
{
|
{
|
||||||
QVector<SearchResult> results;
|
QVector<SearchResult> results;
|
||||||
QString whereSql = makeSql(tokenize(query));
|
QSqlQuery dbQuery = makeSqlQuery(tokenize(query));
|
||||||
QString prep;
|
bool success = dbQuery.exec();
|
||||||
//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();
|
|
||||||
if(!success)
|
if(!success)
|
||||||
{
|
{
|
||||||
qDebug() << "prepped: " << prep;
|
|
||||||
qDebug() << dbquery.lastError();
|
qDebug() << dbQuery.lastError();
|
||||||
throw QSSGeneralException("SQL Error: " + dbquery.lastError().text());
|
throw QSSGeneralException("SQL Error: " + dbQuery.lastError().text());
|
||||||
}
|
}
|
||||||
|
|
||||||
while(dbquery.next())
|
while(dbQuery.next())
|
||||||
{
|
{
|
||||||
SearchResult result;
|
SearchResult result;
|
||||||
result.fileData.absPath = dbquery.value("path").toString();
|
result.fileData.absPath = dbQuery.value("path").toString();
|
||||||
result.fileData.mtime = dbquery.value("mtime").toUInt();
|
result.fileData.mtime = dbQuery.value("mtime").toUInt();
|
||||||
result.fileData.size = dbquery.value("size").toUInt();
|
result.fileData.size = dbQuery.value("size").toUInt();
|
||||||
result.fileData.filetype = dbquery.value("filetype").toChar();
|
result.fileData.filetype = dbQuery.value("filetype").toChar();
|
||||||
result.page = dbquery.value("page").toUInt();
|
result.page = dbQuery.value("page").toUInt();
|
||||||
results.append(result);
|
results.append(result);
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#ifndef SQLITESEARCH_H
|
#ifndef SQLITESEARCH_H
|
||||||
#define SQLITESEARCH_H
|
#define SQLITESEARCH_H
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
|
#include <QPair>
|
||||||
#include "searchresult.h"
|
#include "searchresult.h"
|
||||||
|
|
||||||
class SqliteSearch
|
class SqliteSearch
|
||||||
@ -19,18 +20,21 @@ class SqliteSearch
|
|||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
private:
|
|
||||||
QSqlDatabase *db;
|
|
||||||
QVector<SqliteSearch::Token> tokenize(QString expression);
|
|
||||||
QString createSql(const Token &token);
|
|
||||||
QString makeSql(const QVector<Token> &tokens);
|
|
||||||
bool checkParanthesis(QString expression);
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
||||||
SqliteSearch(QSqlDatabase &db);
|
SqliteSearch(QSqlDatabase &db);
|
||||||
QVector<SearchResult> search(const QString &query);
|
QVector<SearchResult> search(const QString &query);
|
||||||
|
static bool checkParanthesis(QString expression);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSqlDatabase *db;
|
||||||
|
QVector<Token> tokenize(QString expression);
|
||||||
|
QSqlQuery makeSqlQuery(const QVector<Token> &tokens);
|
||||||
|
QString fieldToColumn(QString col);
|
||||||
|
QPair<QString, QVector<QString> > createSql(const Token &token);
|
||||||
|
QString createSortSql(const SqliteSearch::Token &token);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SQLITESEARCH_H
|
#endif // SQLITESEARCH_H
|
||||||
|
Loading…
Reference in New Issue
Block a user