Refactor search queries: Introduced QSSQuery
Purpose is to seperate certain logic from SQLite and generalize it more. Even though we only have Sqlite atm, in general the database layers must be stupid as possible, while QSSQuery should do most of the hard work. Fixes in Tokenizer logic. Switched to C++17.
This commit is contained in:
292
shared/qssquery.cpp
Normal file
292
shared/qssquery.cpp
Normal file
@ -0,0 +1,292 @@
|
||||
#include <QStack>
|
||||
#include <QRegularExpression>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QStringList>
|
||||
#include <QDebug>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include "qssquery.h"
|
||||
|
||||
|
||||
const QVector<Token> &QSSQuery::getTokens() const
|
||||
{
|
||||
return tokens;
|
||||
}
|
||||
|
||||
const QVector<SortCondition> &QSSQuery::getSortConditions() const
|
||||
{
|
||||
return sortConditions;
|
||||
}
|
||||
|
||||
QueryType QSSQuery::getQueryType()
|
||||
{
|
||||
return static_cast<QueryType>(tokensMask & COMBINED);
|
||||
}
|
||||
|
||||
bool QSSQuery::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;
|
||||
}
|
||||
|
||||
std::optional<QueryField> fromString(QString fieldString)
|
||||
{
|
||||
if(fieldString == "path" || fieldString == "file.path")
|
||||
{
|
||||
return FILE_PATH;
|
||||
}
|
||||
else if(fieldString == "mtime" || fieldString == "file.mtime")
|
||||
{
|
||||
return FILE_MTIME;
|
||||
}
|
||||
else if(fieldString == "size" || fieldString == "file.size")
|
||||
{
|
||||
return FILE_SIZE;
|
||||
}
|
||||
else if(fieldString == "content.text")
|
||||
{
|
||||
return CONTENT_TEXT;
|
||||
}
|
||||
else if(fieldString == "content.page" || fieldString == "page")
|
||||
{
|
||||
return CONTENT_TEXT_PAGE;
|
||||
}
|
||||
return { };
|
||||
}
|
||||
|
||||
//sort:(mtime desc, page asc)
|
||||
QVector<SortCondition> createSortConditions(QString sortExpression)
|
||||
{
|
||||
QVector<SortCondition> result;
|
||||
QStringList splitted_inner = sortExpression.split(",");
|
||||
for(int i = 0; i < splitted_inner.length(); i++)
|
||||
{
|
||||
QStringList splitted = splitted_inner[i].split(" ");
|
||||
if(splitted.length() < 1 || splitted.length() > 2)
|
||||
{
|
||||
throw QSSGeneralException("sort specifier must have format [field] (asc|desc)");
|
||||
}
|
||||
|
||||
|
||||
QString field = splitted[0];
|
||||
auto queryField = fromString(field);
|
||||
if(!queryField)
|
||||
{
|
||||
throw QSSGeneralException("Unknown sort field supplied");
|
||||
}
|
||||
|
||||
SortOrder order;
|
||||
if(splitted.length() == 2)
|
||||
{
|
||||
QString orderstr = splitted[1];
|
||||
if(orderstr.compare("asc", Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
order = ASC;
|
||||
}
|
||||
else if(orderstr.compare("desc", Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
order = DESC;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw QSSGeneralException("Unknown order specifier: " + order);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
order = ASC;
|
||||
}
|
||||
|
||||
SortCondition condition;
|
||||
condition.field = queryField.value();
|
||||
condition.order = order;
|
||||
result.append(condition);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void QSSQuery::addToken(Token t)
|
||||
{
|
||||
tokens.append(t);
|
||||
tokensMask |= t.type;
|
||||
}
|
||||
|
||||
/* Builds the query from the supplied expression
|
||||
*
|
||||
* AND is the default boolean operator, when the user does not provide any
|
||||
* thus, "Downloads zip" becomes essentailly "path.contains:(Downloads) AND path.contains:(zip)"
|
||||
*
|
||||
* TODO: It's a bit ugly still*/
|
||||
QSSQuery QSSQuery::build(QString expression)
|
||||
{
|
||||
if(!checkParanthesis(expression))
|
||||
{
|
||||
throw QSSGeneralException("Invalid paranthesis");
|
||||
}
|
||||
|
||||
QSSQuery result;
|
||||
//TODO: merge lonewords
|
||||
QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([\\w,])+)|(?<boolean>AND|OR)|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>\\w+))");
|
||||
QRegularExpressionMatchIterator i = rx.globalMatch(expression);
|
||||
auto previousWasBool = [&result]{ return ! result.tokens.empty() && ( (result.tokens.last().type & BOOL) == BOOL); };
|
||||
auto previousWas = [&result](TokenType t) { return ! result.tokens.empty() && ( result.tokens.last().type == t); };
|
||||
|
||||
while(i.hasNext())
|
||||
{
|
||||
QRegularExpressionMatch m = i.next();
|
||||
QString boolean = m.captured("boolean");
|
||||
QString negation = m.captured("negation");
|
||||
QString filtername = m.captured("filtername");
|
||||
QString bracket = m.captured("bracket");
|
||||
QString loneword = m.captured("loneword");
|
||||
|
||||
if(boolean != "")
|
||||
{
|
||||
if(previousWasBool())
|
||||
{
|
||||
throw QSSGeneralException("Can't have two booleans following each other");
|
||||
}
|
||||
if(previousWas(NEGATION))
|
||||
{
|
||||
throw QSSGeneralException("Can't have a negation preceeding a boolean");
|
||||
}
|
||||
if(boolean == "AND")
|
||||
{
|
||||
result.addToken(Token(BOOL_AND));
|
||||
}
|
||||
else if(boolean == "OR")
|
||||
{
|
||||
result.addToken(Token(BOOL_OR));
|
||||
}
|
||||
}
|
||||
if(negation != "")
|
||||
{
|
||||
if(previousWas(NEGATION))
|
||||
{
|
||||
throw QSSGeneralException("Can't have two negations following each other");
|
||||
}
|
||||
if(!previousWasBool())
|
||||
{
|
||||
result.addToken(Token(BOOL_AND)); //Implicit and, our default operation
|
||||
}
|
||||
result.addToken(Token(NEGATION));
|
||||
}
|
||||
if(!result.tokens.isEmpty() && !previousWasBool() && !previousWas(NEGATION) && !previousWas(BRACKET_OPEN) && bracket != ")")
|
||||
{
|
||||
//the current token isn't a negation, isn't a boolean. Thus, implicit AND is required
|
||||
result.addToken(Token(BOOL_AND));
|
||||
}
|
||||
|
||||
if(bracket != "")
|
||||
{
|
||||
if(bracket == "(")
|
||||
{
|
||||
result.addToken(Token(BRACKET_OPEN));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.addToken(Token(BRACKET_CLOSE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(loneword != "")
|
||||
{
|
||||
result.addToken(Token(FILTER_PATH_CONTAINS, loneword));
|
||||
}
|
||||
|
||||
if(filtername != "")
|
||||
{
|
||||
TokenType tokenType;
|
||||
QString value = m.captured("innerargs");
|
||||
if(value == "")
|
||||
{
|
||||
value = m.captured("args");
|
||||
}
|
||||
|
||||
if(filtername == "path.contains")
|
||||
{
|
||||
tokenType = FILTER_PATH_CONTAINS;
|
||||
}
|
||||
else if(filtername == "path.starts")
|
||||
{
|
||||
tokenType = FILTER_PATH_STARTS;
|
||||
}
|
||||
else if(filtername == "path.ends")
|
||||
{
|
||||
tokenType = FILTER_PATH_ENDS;
|
||||
}
|
||||
else if(filtername == "file.size" || filtername == "size")
|
||||
{
|
||||
tokenType = FILTER_PATH_SIZE;
|
||||
}
|
||||
else if(filtername == "c" || filtername == "contains")
|
||||
{
|
||||
tokenType = FILTER_CONTENT_CONTAINS;
|
||||
}
|
||||
else if(filtername == "page" || filtername == "content.page")
|
||||
{
|
||||
tokenType = FILTER_CONTENT_PAGE;
|
||||
}
|
||||
else if(filtername == "sort") //TODO: given this is not really a "filter", this feels slightly misplaced here
|
||||
{
|
||||
if(!result.sortConditions.empty())
|
||||
{
|
||||
throw QSSGeneralException("Two sort statements are illegal");
|
||||
}
|
||||
result.sortConditions = createSortConditions(value);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw QSSGeneralException("Unknown filter provided!");
|
||||
}
|
||||
result.addToken(Token(tokenType, value));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool contentsearch = result.getTokensMask() & FILTER_CONTENT == FILTER_CONTENT;
|
||||
bool sortsForContent = std::any_of(result.sortConditions.begin(), result.sortConditions.end(), [](SortCondition c){ return c.field == CONTENT_TEXT; });
|
||||
|
||||
if(!contentsearch && sortsForContent)
|
||||
{
|
||||
throw QSSGeneralException("We cannot sort by text if we don't search for it");
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
60
shared/qssquery.h
Normal file
60
shared/qssquery.h
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef QSSQUERY_H
|
||||
#define QSSQUERY_H
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include "qssgeneralexception.h"
|
||||
#include "token.h"
|
||||
/* Fields that can be queried or sorted */
|
||||
enum QueryField
|
||||
{
|
||||
FILE_PATH,
|
||||
FILE_MTIME,
|
||||
FILE_SIZE,
|
||||
CONTENT_TEXT,
|
||||
CONTENT_TEXT_PAGE
|
||||
};
|
||||
|
||||
enum SortOrder
|
||||
{
|
||||
ASC,
|
||||
DESC
|
||||
};
|
||||
|
||||
struct SortCondition
|
||||
{
|
||||
QueryField field;
|
||||
SortOrder order;
|
||||
};
|
||||
|
||||
enum QueryType
|
||||
{
|
||||
NOTHING = 0,
|
||||
PATH_ONLY = FILTER_PATH,
|
||||
CONTENT_ONLY = FILTER_CONTENT,
|
||||
COMBINED = PATH_ONLY | CONTENT_ONLY
|
||||
};
|
||||
|
||||
class QSSQuery
|
||||
{
|
||||
private:
|
||||
/* Helper field to determine quertype as well as to quickly check what kind of filters etc.
|
||||
* are being used in this query*/
|
||||
int tokensMask;
|
||||
QVector<Token> tokens;
|
||||
QVector<SortCondition> sortConditions;
|
||||
void addToken(Token t);
|
||||
public:
|
||||
const QVector<Token> & getTokens() const;
|
||||
const QVector<SortCondition> & getSortConditions() const;
|
||||
QueryType getQueryType();
|
||||
int getTokensMask() const { return tokensMask; }
|
||||
static bool checkParanthesis(QString query);
|
||||
static QSSQuery build(QString query);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // QSSQUERY_H
|
@ -11,6 +11,7 @@ QT -= gui
|
||||
TARGET = shared
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
CONFIG += c++17
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
||||
@ -24,12 +25,15 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += sqlitesearch.cpp \
|
||||
qssgeneralexception.cpp
|
||||
qssgeneralexception.cpp \
|
||||
qssquery.cpp
|
||||
|
||||
HEADERS += sqlitesearch.h \
|
||||
filedata.h \
|
||||
searchresult.h \
|
||||
qssgeneralexception.h
|
||||
qssgeneralexception.h \
|
||||
token.h \
|
||||
qssquery.h
|
||||
unix {
|
||||
target.path = /usr/lib
|
||||
INSTALLS += target
|
||||
|
@ -12,221 +12,134 @@ SqliteSearch::SqliteSearch(QSqlDatabase &db)
|
||||
this->db = &db;
|
||||
}
|
||||
|
||||
QVector<SqliteSearch::Token> SqliteSearch::tokenize(QString expression)
|
||||
QString SqliteSearch::fieldToColumn(QueryField field)
|
||||
{
|
||||
if(!checkParanthesis(expression))
|
||||
{
|
||||
throw QSSGeneralException("Invalid paranthesis");
|
||||
}
|
||||
//TODO: merge lonewords
|
||||
QVector<SqliteSearch::Token> result;
|
||||
QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([\\w,])+)|(?<boolean>AND|OR|!)|(?<bracket>\\(|\\))|(?<loneword>\\w+))");
|
||||
QRegularExpressionMatchIterator i = rx.globalMatch(expression);
|
||||
auto isSort = [](QString &key) { return key == "sort"; };
|
||||
auto isBool = [](QString &key) { return key == "AND" || key == "OR" || key == "!"; };
|
||||
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 != "")
|
||||
{
|
||||
result.append(Token(boolean));
|
||||
}
|
||||
|
||||
if(!result.empty())
|
||||
{
|
||||
QString &lastKey = result.last().key;
|
||||
if(!isBool(lastKey) && !isSort(lastKey) && !isSort(filtername))
|
||||
{
|
||||
result.append(Token("AND"));
|
||||
}
|
||||
}
|
||||
|
||||
if(bracket != "")
|
||||
{
|
||||
if(bracket == "(")
|
||||
{
|
||||
result.append(Token("AND"));
|
||||
}
|
||||
result.append(Token(bracket));
|
||||
}
|
||||
|
||||
|
||||
if(loneword != "")
|
||||
{
|
||||
result.append(Token("path.contains", loneword));
|
||||
}
|
||||
|
||||
if(filtername != "")
|
||||
{
|
||||
QString value = m.captured("innerargs");
|
||||
if(value == "")
|
||||
{
|
||||
value = m.captured("args");
|
||||
}
|
||||
result.append(Token(filtername, value));
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QString SqliteSearch::fieldToColumn(QString field)
|
||||
{
|
||||
if(field == "mtime" || field == "file.mtime")
|
||||
if(field == FILE_MTIME)
|
||||
{
|
||||
return "file.mtime";
|
||||
}
|
||||
else if(field == "page" || field == "content.page")
|
||||
{
|
||||
return "content.page";
|
||||
}
|
||||
else if(field == "path" || field == "file.path")
|
||||
else if(field == FILE_PATH)
|
||||
{
|
||||
return "file.path";
|
||||
}
|
||||
else if(field == "size" || field == "file.size")
|
||||
else if(field == FILE_SIZE)
|
||||
{
|
||||
return "file.size";
|
||||
}
|
||||
else if(field == CONTENT_TEXT_PAGE)
|
||||
{
|
||||
return "content.page";
|
||||
}
|
||||
else if(field == CONTENT_TEXT)
|
||||
{
|
||||
return "content.text";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
QString SqliteSearch::createSortSql(const SqliteSearch::Token &token)
|
||||
QString SqliteSearch::createSortSql(const QVector<SortCondition> sortConditions)
|
||||
{
|
||||
//sort:(mtime desc, page asc)
|
||||
if(token.key == "sort")
|
||||
{
|
||||
QString sortsql;
|
||||
QStringList splitted_inner = token.value.split(",");
|
||||
for(int i = 0; i < splitted_inner.length(); i++)
|
||||
for(const SortCondition &sc : sortConditions)
|
||||
{
|
||||
QStringList splitted = splitted_inner[i].split(" ");
|
||||
if(splitted.length() < 1 || splitted.length() > 2)
|
||||
{
|
||||
throw QSSGeneralException("sort specifier must have format [field] (asc|desc)");
|
||||
}
|
||||
|
||||
QString field = splitted[0];
|
||||
field = fieldToColumn(field);
|
||||
QString order;
|
||||
QString field = fieldToColumn(sc.field);
|
||||
if(field == "")
|
||||
{
|
||||
throw QSSGeneralException("Unknown sort field supplied");
|
||||
}
|
||||
|
||||
QString order;
|
||||
if(splitted.length() == 2)
|
||||
if(sc.order == DESC)
|
||||
{
|
||||
order = splitted[1];
|
||||
if(order.compare("asc", Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
order = "ASC";
|
||||
}
|
||||
else if(order.compare("desc", Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
order = "DESC";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw QSSGeneralException("Unknown order specifier: " + order);
|
||||
}
|
||||
|
||||
order = "DESC";
|
||||
}
|
||||
else
|
||||
{
|
||||
order = "ASC";
|
||||
}
|
||||
|
||||
sortsql += field + " " + order;
|
||||
if(splitted_inner.length() - i > 1)
|
||||
{
|
||||
sortsql += ", ";
|
||||
}
|
||||
sortsql += field + " " + order + ", ";
|
||||
}
|
||||
return " ORDER BY " + sortsql;
|
||||
|
||||
}
|
||||
return "";
|
||||
sortsql.chop(2);
|
||||
if(!sortsql.isEmpty())
|
||||
{
|
||||
return " ORDER BY " + sortsql;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QPair<QString, QVector<QString>> SqliteSearch::createSql(const SqliteSearch::Token &token)
|
||||
QPair<QString, QVector<QString>> createNonArgPair(QString key)
|
||||
{
|
||||
return { " " + key + " ", QVector<QString>() };
|
||||
}
|
||||
|
||||
QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
|
||||
{
|
||||
QPair<QString, QVector<QString>> result;
|
||||
|
||||
QString key = token.key;
|
||||
QString value = token.value;
|
||||
value = value.replace("'", "\\'");
|
||||
if(key == "AND" || key == "OR" || key == "(" || key == ")")
|
||||
|
||||
if(token.type == BOOL_AND)
|
||||
{
|
||||
return { " " + key + " ", QVector<QString>() };
|
||||
return createNonArgPair("AND");
|
||||
}
|
||||
if(key == "!")
|
||||
if(token.type == BOOL_OR)
|
||||
{
|
||||
return { " NOT ", QVector<QString>() };
|
||||
return createNonArgPair("OR");
|
||||
}
|
||||
if(key == "path.starts")
|
||||
if(token.type == NEGATION)
|
||||
{
|
||||
return createNonArgPair("NOT");
|
||||
}
|
||||
if(token.type == BRACKET_OPEN)
|
||||
{
|
||||
return createNonArgPair("(");
|
||||
}
|
||||
if(token.type == BRACKET_CLOSE)
|
||||
{
|
||||
return createNonArgPair(")");
|
||||
}
|
||||
if(token.type == FILTER_PATH_STARTS)
|
||||
{
|
||||
return { " file.path LIKE ? || '%' ", { value } };
|
||||
}
|
||||
if(key == "path.ends")
|
||||
if(token.type == FILTER_PATH_ENDS)
|
||||
{
|
||||
return { " file.path LIKE '%' || ? ", { value } };
|
||||
}
|
||||
if(key == "path.contains" || key == "inpath")
|
||||
if(token.type == FILTER_PATH_CONTAINS)
|
||||
{
|
||||
return { " file.path LIKE '%' || ? || '%' ", { value } } ;
|
||||
}
|
||||
if(key == "page")
|
||||
if(token.type == FILTER_CONTENT_PAGE)
|
||||
{
|
||||
return { " content.page = ?", { value } };
|
||||
}
|
||||
if(key == "contains" || key == "c")
|
||||
if(token.type == FILTER_CONTENT_CONTAINS)
|
||||
{
|
||||
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 passed (should not happen)");
|
||||
}
|
||||
|
||||
QSqlQuery SqliteSearch::makeSqlQuery(const QVector<SqliteSearch::Token> &tokens)
|
||||
QSqlQuery SqliteSearch::makeSqlQuery(const QSSQuery &query)
|
||||
{
|
||||
QString whereSql;
|
||||
QString limitSql;
|
||||
QString sortSql;
|
||||
QVector<QString> bindValues;
|
||||
bool isContentSearch = false;
|
||||
for(const Token &c : tokens)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
QString prepSql;
|
||||
if(whereSql.isEmpty())
|
||||
bool isContentSearch = query.getTokensMask() & FILTER_CONTENT == FILTER_CONTENT;
|
||||
if(query.getTokens().isEmpty())
|
||||
{
|
||||
throw QSSGeneralException("Nothing to search for supplied");
|
||||
}
|
||||
|
||||
for(const Token &token : query.getTokens())
|
||||
{
|
||||
auto sql = createSql(token);
|
||||
whereSql += sql.first;
|
||||
bindValues.append(sql.second);
|
||||
}
|
||||
|
||||
QString prepSql;
|
||||
QString sortSql = createSortSql(query.getSortConditions());
|
||||
if(isContentSearch)
|
||||
{
|
||||
if(sortSql.isEmpty())
|
||||
@ -241,17 +154,11 @@ QSqlQuery SqliteSearch::makeSqlQuery(const QVector<SqliteSearch::Token> &tokens)
|
||||
{
|
||||
sortSql = "ORDER BY file.mtime DESC";
|
||||
}
|
||||
if(sortSql.contains("content."))
|
||||
{
|
||||
throw QSSGeneralException("Cannot sort for content fields when not doing a content search");
|
||||
}
|
||||
|
||||
prepSql = "SELECT file.path AS path, '0' as pages, 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 != "")
|
||||
@ -264,10 +171,10 @@ QSqlQuery SqliteSearch::makeSqlQuery(const QVector<SqliteSearch::Token> &tokens)
|
||||
|
||||
|
||||
|
||||
QVector<SearchResult> SqliteSearch::search(const QString &query)
|
||||
QVector<SearchResult> SqliteSearch::search(const QSSQuery &query)
|
||||
{
|
||||
QVector<SearchResult> results;
|
||||
QSqlQuery dbQuery = makeSqlQuery(tokenize(query));
|
||||
QSqlQuery dbQuery = makeSqlQuery(query);
|
||||
bool success = dbQuery.exec();
|
||||
if(!success)
|
||||
{
|
||||
@ -297,35 +204,3 @@ QVector<SearchResult> SqliteSearch::search(const QString &query)
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
bool SqliteSearch::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;
|
||||
}
|
||||
|
@ -3,38 +3,21 @@
|
||||
#include <QSqlDatabase>
|
||||
#include <QPair>
|
||||
#include "searchresult.h"
|
||||
|
||||
#include "token.h"
|
||||
#include "../shared/qssquery.h"
|
||||
class SqliteSearch
|
||||
{
|
||||
class Token
|
||||
{
|
||||
public:
|
||||
QString key;
|
||||
QString value;
|
||||
|
||||
Token(QString key="", QString value="")
|
||||
{
|
||||
this->key = key;
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
|
||||
SqliteSearch(QSqlDatabase &db);
|
||||
QVector<SearchResult> search(const QString &query);
|
||||
static bool checkParanthesis(QString expression);
|
||||
QVector<SearchResult> search(const QSSQuery &query);
|
||||
|
||||
private:
|
||||
QSqlDatabase *db;
|
||||
QVector<Token> tokenize(QString expression);
|
||||
QSqlQuery makeSqlQuery(const QVector<Token> &tokens);
|
||||
QString fieldToColumn(QString col);
|
||||
QSqlQuery makeSqlQuery(const QSSQuery &query);
|
||||
QString fieldToColumn(QueryField field);
|
||||
QPair<QString, QVector<QString> > createSql(const Token &token);
|
||||
QString createSortSql(const SqliteSearch::Token &token);
|
||||
QString createSortSql(const QVector<SortCondition> sortConditions);
|
||||
};
|
||||
|
||||
#endif // SQLITESEARCH_H
|
||||
|
6
shared/token.cpp
Normal file
6
shared/token.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "token.h"
|
||||
|
||||
Token::Token()
|
||||
{
|
||||
|
||||
}
|
51
shared/token.h
Normal file
51
shared/token.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef TOKEN_H
|
||||
#define TOKEN_H
|
||||
#include <QString>
|
||||
|
||||
|
||||
enum TokenType
|
||||
{
|
||||
WORD,
|
||||
NEGATION = 2,
|
||||
BOOL = 4,
|
||||
BOOL_AND,
|
||||
BOOL_OR,
|
||||
GROUP = 8,
|
||||
BRACKET_OPEN,
|
||||
BRACKET_CLOSE,
|
||||
SORT = 16,
|
||||
FILTER_PATH = 32,
|
||||
FILTER_PATH_MTIME,
|
||||
FILTER_PATH_CONTAINS,
|
||||
FILTER_PATH_SIZE,
|
||||
FILTER_PATH_ENDS,
|
||||
FILTER_PATH_STARTS,
|
||||
FILTER_CONTENT = 64,
|
||||
FILTER_CONTENT_CONTAINS,
|
||||
FILTER_CONTENT_PAGE,
|
||||
|
||||
};
|
||||
|
||||
class Token
|
||||
{
|
||||
public:
|
||||
Token()
|
||||
{
|
||||
|
||||
}
|
||||
Token(TokenType type)
|
||||
{
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
Token(TokenType type, QString value)
|
||||
{
|
||||
this->type = type;
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
TokenType type;
|
||||
QString value;
|
||||
};
|
||||
|
||||
#endif // TOKEN_H
|
Reference in New Issue
Block a user