src/database/search_handler.h
Rama master · réplica de lectura del repositorio original.
/*GRB*
Gerbera - https://gerbera.io/
search_handler.h - this file is part of Gerbera.
Copyright (C) 2018-2026 Gerbera Contributors
Gerbera is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
Gerbera is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gerbera. If not, see <http://www.gnu.org/licenses/>.
$Id$
*/
/// @file database/search_handler.h
/// @brief Definitions of classes supporting implementation of UPnP search
/// Implements functionality defined in "standardizeddcps/MediaServer_4 and MediaRenderer_3/UPnP-av-ContentDirectory-v4-Service-20150630.pdf"
/// Document available in upnpresources.zip, download from here:
/// https://openconnectivity.org/developer/specifications/upnp-resources/upnp
#ifndef __SEARCH_HANDLER_H__
#define __SEARCH_HANDLER_H__
#include "upnp/upnp_common.h"
#include "util/fmt_compat.h"
#include "util/tools.h"
#include <algorithm>
#include <map>
#include <memory>
#include <tuple>
#include <vector>
#define UPNP_SEARCH_ID "@id"
#define UPNP_SEARCH_REFID "@refID"
#define UPNP_SEARCH_PARENTID "@parentID"
#define UPNP_SEARCH_LAST_UPDATED "last_updated"
#define UPNP_SEARCH_LAST_MODIFIED "last_modified"
#define UPNP_SEARCH_PLAY_GROUP "play_group"
#define UPNP_SEARCH_PATH "path"
#define UPNP_SORT_KEY "sort_key"
#define META_NAME "name"
#define META_VALUE "value"
class Database;
class SearchParam;
enum class TokenType {
ASTERISK,
DQUOTE,
ESCAPEDSTRING,
PROPERTY,
BOOLVAL,
EXISTS,
STRINGOP,
COMPAREOP,
AND,
OR,
LPAREN,
RPAREN,
INVALID
};
class SearchToken {
public:
SearchToken(TokenType type, std::string&& value)
: type(type)
, value(std::move(value))
{
}
const TokenType& getType() const { return type; }
std::string getValue() const { return value; }
friend bool operator==(const SearchToken& lhs, const SearchToken& rhs)
{
return lhs.type == rhs.type && lhs.value == rhs.value;
}
protected:
TokenType type;
std::string value;
};
class SearchLexer {
public:
explicit SearchLexer(std::string input);
std::unique_ptr<SearchToken> nextToken();
protected:
std::string nextStringToken(const std::string& input);
static std::unique_ptr<SearchToken> makeToken(std::string tokenStr);
std::string getQuotedValue(const std::string& input);
std::string input;
unsigned currentPos {};
bool inQuotes {};
};
// NOTES
// ASTNode needs node type (new enum or will TokenType suffice?)
// Need ASTNode factory so that each node is built with relevant 'emitter'
// Will need an emitter for testing correct tree AST is getting built
//
class SQLEmitter;
class ASTNode {
public:
virtual ~ASTNode() = default;
ASTNode(const ASTNode&) = delete;
ASTNode& operator=(const ASTNode&) = delete;
std::pair<std::string, std::string> emitSQL() const;
virtual std::string emit() const = 0;
protected:
explicit ASTNode(const SQLEmitter& emitter)
: sqlEmitter(emitter)
{
}
const SQLEmitter& sqlEmitter;
};
class ASTAsterisk : public ASTNode {
public:
ASTAsterisk(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
protected:
std::string value;
};
class ASTProperty : public ASTNode {
public:
ASTProperty(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
protected:
std::string value;
};
class ASTBoolean : public ASTNode {
public:
ASTBoolean(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
protected:
std::string value;
};
class ASTParenthesis : public ASTNode {
public:
ASTParenthesis(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTNode> node);
std::string emit() const override;
protected:
std::unique_ptr<ASTNode> bracketedNode;
};
class ASTDQuote : public ASTNode {
public:
ASTDQuote(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
protected:
std::string value;
};
class ASTEscapedString : public ASTNode {
public:
ASTEscapedString(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
protected:
std::string value;
};
class ASTQuotedString : public ASTNode {
public:
ASTQuotedString(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTDQuote> openQuote,
std::unique_ptr<ASTEscapedString> escapedString, std::unique_ptr<ASTDQuote> closeQuote);
std::string emit() const override;
protected:
std::unique_ptr<ASTDQuote> openQuote;
std::unique_ptr<ASTEscapedString> escapedString;
std::unique_ptr<ASTDQuote> closeQuote;
};
/// @brief Represents a comparison operator such as =, >=, <
class ASTCompareOperator : public ASTNode {
public:
ASTCompareOperator(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
std::string emit(const std::string& property, const std::string& value) const;
std::string getValue() const { return value; }
protected:
std::string value;
};
/// @brief Represents an expression using a comparison operator
class ASTCompareExpression : public ASTNode {
public:
ASTCompareExpression(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTProperty> lhs,
std::unique_ptr<ASTCompareOperator> operatr, std::unique_ptr<ASTQuotedString> rhs);
std::string emit() const override;
protected:
std::unique_ptr<ASTProperty> lhs;
std::unique_ptr<ASTCompareOperator> operatr;
std::unique_ptr<ASTQuotedString> rhs;
};
/// @brief Represents an operator defined by a string such as contains, derivedFrom
class ASTStringOperator : public ASTNode {
public:
ASTStringOperator(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
std::string emit(const std::string& property, const std::string& value) const;
std::string getValue() const { return value; }
protected:
std::string value;
};
/// @brief Represents an expression using an operator defined by a string
class ASTStringExpression : public ASTNode {
public:
ASTStringExpression(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTProperty> lhs,
std::unique_ptr<ASTStringOperator> operatr, std::unique_ptr<ASTQuotedString> rhs);
std::string emit() const override;
protected:
std::unique_ptr<ASTProperty> lhs;
std::unique_ptr<ASTStringOperator> operatr;
std::unique_ptr<ASTQuotedString> rhs;
};
class ASTExistsOperator : public ASTNode {
public:
ASTExistsOperator(const SQLEmitter& sqlEmitter, std::string value);
std::string emit() const override;
std::string emit(const std::string& property, const std::string& value) const;
protected:
std::string value;
};
/// @brief Represents an expression using the exists operator
class ASTExistsExpression : public ASTNode {
public:
ASTExistsExpression(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTProperty> lhs,
std::unique_ptr<ASTExistsOperator> operatr, std::unique_ptr<ASTBoolean> rhs);
std::string emit() const override;
protected:
std::unique_ptr<ASTProperty> lhs;
std::unique_ptr<ASTExistsOperator> operatr;
std::unique_ptr<ASTBoolean> rhs;
};
class ASTAndOperator : public ASTNode {
public:
ASTAndOperator(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTNode> lhs, std::unique_ptr<ASTNode> rhs);
std::string emit() const override;
protected:
std::unique_ptr<ASTNode> lhs;
std::unique_ptr<ASTNode> rhs;
};
class ASTOrOperator : public ASTNode {
public:
ASTOrOperator(const SQLEmitter& sqlEmitter, std::unique_ptr<ASTNode> lhs, std::unique_ptr<ASTNode> rhs);
std::string emit() const override;
protected:
std::unique_ptr<ASTNode> lhs;
std::unique_ptr<ASTNode> rhs;
};
class SQLEmitter {
public:
SQLEmitter() = default;
virtual ~SQLEmitter() = default;
SQLEmitter(const SQLEmitter&) = delete;
SQLEmitter& operator=(const SQLEmitter&) = delete;
virtual std::pair<std::string, std::string> emitSQL(const ASTNode* node) const = 0;
virtual std::string emit(const ASTAsterisk* node) const = 0;
virtual std::string emit(const ASTParenthesis* node,
const std::string& bracketedNode) const
= 0;
virtual std::string emit(const ASTDQuote* node) const = 0;
virtual std::string emit(
const ASTCompareOperator* node,
const std::string& property,
const std::string& value) const
= 0;
virtual std::string emit(
const ASTStringOperator* node,
const std::string& property,
const std::string& value) const
= 0;
virtual std::string emit(
const ASTExistsOperator* node,
const std::string& property,
const std::string& value) const
= 0;
virtual std::string emit(
const ASTAndOperator* node,
const std::string& lhs,
const std::string& rhs) const
= 0;
virtual std::string emit(
const ASTOrOperator* node,
const std::string& lhs,
const std::string& rhs) const
= 0;
};
enum class FieldType {
String,
Integer,
Bool,
Date,
};
/// @brief base interface for templated column mapping and quoting operations
class ColumnMapper {
public:
ColumnMapper() = default;
virtual ~ColumnMapper() = default;
ColumnMapper(const ColumnMapper&) = delete;
ColumnMapper& operator=(const ColumnMapper&) = delete;
/// @brief check whether tag is valid colum
virtual bool hasEntry(const std::string& tag) const = 0;
/// @brief get the table name
virtual std::string getTableName() const = 0;
/// @brief get the table name quoted
virtual std::string tableQuoted() const = 0;
/// @brief quote column for statement
/// @param tag column name
/// @param noAlias generate no alias
virtual std::string mapQuoted(const std::string& tag, bool noAlias = false) const = 0;
virtual std::string mapQuotedTable(const std::string& tag) const = 0;
virtual bool mapQuotedList(std::vector<std::string>& sort, const std::string& tag, const std::string& desc) const = 0;
/// @brief quote column for statement with lowercase
virtual std::string mapQuotedLower(const std::string& tag) const = 0;
virtual std::string mapQuotedTableLower(const std::string& tag) const = 0;
/// @brief get type of column from tagMap
virtual FieldType getFieldType(const std::string& tag) const = 0;
/// @brief quote tag for statement
virtual std::string quote(const std::string& tag) const = 0;
virtual void setAlias(const std::string& prop) = 0;
virtual void addJoin(const std::string& join) = 0;
virtual void resetCnt() = 0;
virtual std::string getJoin() const = 0;
};
struct SearchProperty {
std::string alias;
std::string field;
FieldType type { FieldType::String };
std::size_t length { 0 };
std::string print(char table_quote_begin, char table_quote_end, bool noAlias) const
{
if (alias.empty() || noAlias) // no column alias
return fmt::format("{}{}{}", table_quote_begin, field, table_quote_end);
return fmt::format("{0}{1}{3}.{0}{2}{3}", table_quote_begin, alias, field, table_quote_end);
}
std::string print(char table_quote_begin, char table_quote_end, const std::string& tabAlias) const
{
if (tabAlias.empty()) // no column alias
return fmt::format("{}{}{}", table_quote_begin, field, table_quote_end);
return fmt::format("{0}{1}{3}.{0}{2}{3}", table_quote_begin, tabAlias, field, table_quote_end);
}
std::string convert(char table_quote_begin, char table_quote_end) const
{
if (alias.empty()) // no column
return fmt::format("LOWER({}{}{})", table_quote_begin, field, table_quote_end);
return fmt::format("LOWER({0}{1}{3}.{0}{2}{3})", table_quote_begin, alias, field, table_quote_end);
}
std::string convert(char table_quote_begin, char table_quote_end, const std::string& tabAlias) const
{
if (tabAlias.empty()) // no column
return fmt::format("LOWER({}{}{})", table_quote_begin, field, table_quote_end);
return fmt::format("LOWER({0}{1}{3}.{0}{2}{3})", table_quote_begin, tabAlias, field, table_quote_end);
}
};
class DefaultSQLEmitter : public SQLEmitter {
public:
DefaultSQLEmitter(
std::shared_ptr<Database> database,
std::shared_ptr<ColumnMapper> colMapper,
std::shared_ptr<ColumnMapper> metaMapper,
std::shared_ptr<ColumnMapper> resMapper,
std::shared_ptr<ColumnMapper> plyMapper);
std::pair<std::string, std::string> emitSQL(const ASTNode* node) const override;
std::string emit(const ASTAsterisk* node) const override { return {}; }
std::string emit(const ASTParenthesis* node, const std::string& bracketedNode) const override;
std::string emit(const ASTDQuote* node) const override { return {}; }
std::string emit(
const ASTCompareOperator* node,
const std::string& property,
const std::string& value) const override;
std::string emit(
const ASTStringOperator* node,
const std::string& property,
const std::string& value) const override;
std::string emit(
const ASTExistsOperator* node,
const std::string& property,
const std::string& value) const override;
std::string emit(const ASTAndOperator* node, const std::string& lhs, const std::string& rhs) const override;
std::string emit(const ASTOrOperator* node, const std::string& lhs, const std::string& rhs) const override;
private:
std::shared_ptr<Database> database;
std::shared_ptr<ColumnMapper> colMapper;
std::shared_ptr<ColumnMapper> metaMapper;
std::shared_ptr<ColumnMapper> resMapper;
std::shared_ptr<ColumnMapper> plyMapper;
std::tuple<std::string, std::string, FieldType> getPropertyStatement(const std::string& property) const;
};
/// @brief parser for UPnP search string
class SearchParser {
public:
SearchParser(
const SQLEmitter& sqlEmitter,
const std::string& searchCriteria,
unsigned maxParenthesisDepth = 32);
/// @brief entry point for parsing
std::unique_ptr<ASTNode> parse();
protected:
/// @brief get next token from search string
void getNextToken();
/// @brief parse complete search expression
std::unique_ptr<ASTNode> parseSearchExpression();
/// @brief parse complete comparison / relation operation
std::unique_ptr<ASTNode> parseRelationshipExpression();
/// @brief parse a parenthesized expression
/// @param depth current parenthesis nesting depth
std::unique_ptr<ASTNode> parseParenthesis(unsigned depth = 0);
/// @brief parse a quoted string
std::unique_ptr<ASTQuotedString> parseQuotedString();
private:
/// @brief currently active token in parser
std::unique_ptr<SearchToken> currentToken;
/// @brief reference to lexer
std::unique_ptr<SearchLexer> lexer;
/// @brief reference to sql emitter
const SQLEmitter& sqlEmitter;
/// @brief maximum parenthesis nesting depth to protect the parser from stack exhaustion
unsigned maxParenthesisDepth;
};
template <class En>
class EnumColumnMapper : public ColumnMapper {
public:
explicit EnumColumnMapper(const char tabQuoteBegin, const char tabQuoteEnd, std::string tableAlias, std::string tableName,
std::vector<std::pair<std::string, En>> keyMap, std::map<En, SearchProperty> colMap)
: table_quote_begin(tabQuoteBegin)
, table_quote_end(tabQuoteEnd)
, tableAlias(std::move(tableAlias))
, tableName(std::move(tableName))
, keyMap(std::move(keyMap))
, colMap(std::move(colMap))
{
}
bool hasEntry(const std::string& tag) const override
{
return std::any_of(keyMap.begin(), keyMap.end(), [=](auto&& map) { return map.first == tag; });
}
std::string getTableName() const override
{
return fmt::format("{}{}{}", table_quote_begin, tableName, table_quote_end);
}
std::string getAlias() const
{
return fmt::format("{}{}{}", table_quote_begin, tableAlias, table_quote_end);
}
void setAlias(const std::string& prop) override
{
tableAlias = fmt::format("{}{}", prop, metaCnt++);
}
void addJoin(const std::string& join) override
{
joins.push_back(join);
}
std::string getJoin() const override
{
return fmt::format("{}", fmt::join(joins, " "));
}
void resetCnt() override
{
joins.clear();
metaCnt = 0;
}
std::string mapQuoted(En tag, bool noAlias = false) const
{
auto it = std::find_if(colMap.begin(), colMap.end(), [=](auto&& map) { return map.first == tag; });
if (it != colMap.end()) {
return colMap.at(tag).print(table_quote_begin, table_quote_end, noAlias);
}
return {};
}
std::string tableQuoted() const override
{
return fmt::format("{0}{1}{3} {0}{2}{3}", table_quote_begin, tableName, tableAlias, table_quote_end);
}
bool mapQuotedList(std::vector<std::string>& sort, const std::string& tag, const std::string& desc) const override
{
bool result = false;
for (auto&& [key, value] : keyMap) {
if (key == tag) {
result = true;
auto sortCol = colMap.at(value).print(table_quote_begin, table_quote_end, false);
auto it = std::find_if(sort.begin(), sort.end(), [=](auto&& col) { return startswith(col, sortCol); });
if (it == sort.end())
sort.push_back(fmt::format("{} {}", sortCol, desc));
}
}
return result;
}
std::string mapQuoted(const std::string& tag, bool noAlias = false) const override
{
auto it = std::find_if(keyMap.begin(), keyMap.end(), [=](auto&& map) { return map.first == tag; });
if (it != keyMap.end()) {
return colMap.at(it->second).print(table_quote_begin, table_quote_end, noAlias);
}
return {};
}
std::string mapQuotedTable(const std::string& tag) const override
{
auto it = std::find_if(keyMap.begin(), keyMap.end(), [=](auto&& map) { return map.first == tag; });
if (it != keyMap.end()) {
return colMap.at(it->second).print(table_quote_begin, table_quote_end, tableAlias);
}
return {};
}
std::string quote(const std::string& tag) const override
{
if (!tag.empty()) {
return fmt::format("{0}{1}{2}", table_quote_begin, tag, table_quote_end);
}
return {};
}
std::string mapQuotedLower(const std::string& tag) const override
{
auto it = std::find_if(keyMap.begin(), keyMap.end(), [=](auto&& map) { return map.first == tag; });
if (it != keyMap.end()) {
return colMap.at(it->second).convert(table_quote_begin, table_quote_end);
}
return {};
}
std::string mapQuotedTableLower(const std::string& tag) const override
{
auto it = std::find_if(keyMap.begin(), keyMap.end(), [=](auto&& map) { return map.first == tag; });
if (it != keyMap.end()) {
return colMap.at(it->second).convert(table_quote_begin, table_quote_end, tableAlias);
}
return {};
}
FieldType getFieldType(const std::string& tag) const override
{
auto it = std::find_if(keyMap.begin(), keyMap.end(), [=](auto&& map) { return map.first == tag; });
if (it != keyMap.end()) {
return colMap.at(it->second).type;
}
return FieldType::String;
}
std::string getClause(En field, std::string value, bool noAlias = false)
{
return fmt::format("{} = {}", this->mapQuoted(field, noAlias), value);
}
std::string getClause(En field, long long value, bool noAlias = false)
{
return fmt::format("{} = {}", this->mapQuoted(field, noAlias), value);
}
private:
const char table_quote_begin;
const char table_quote_end;
std::string tableAlias;
std::string tableName;
std::vector<std::pair<std::string, En>> keyMap;
std::map<En, SearchProperty> colMap;
std::size_t metaCnt = 0;
std::vector<std::string> joins;
};
class SortParser {
public:
SortParser(std::shared_ptr<ColumnMapper> colMapper, std::shared_ptr<ColumnMapper> plyMapper, std::shared_ptr<ColumnMapper> metaMapper, std::string sortCriteria);
std::string parse(std::string& addColumns, std::string& addJoin);
private:
std::shared_ptr<ColumnMapper> colMapper;
std::shared_ptr<ColumnMapper> plyMapper;
std::shared_ptr<ColumnMapper> metaMapper;
std::string sortCrit;
};
#endif // __SEARCH_HANDLER_H__