summaryrefslogtreecommitdiffstats
path: root/xbmc/dbwrappers/DatabaseQuery.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/dbwrappers/DatabaseQuery.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/dbwrappers/DatabaseQuery.cpp')
-rw-r--r--xbmc/dbwrappers/DatabaseQuery.cpp571
1 files changed, 571 insertions, 0 deletions
diff --git a/xbmc/dbwrappers/DatabaseQuery.cpp b/xbmc/dbwrappers/DatabaseQuery.cpp
new file mode 100644
index 0000000..c1432be
--- /dev/null
+++ b/xbmc/dbwrappers/DatabaseQuery.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DatabaseQuery.h"
+
+#include "Database.h"
+#include "XBDateTime.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/XBMCTinyXML.h"
+
+typedef struct
+{
+ char string[15];
+ CDatabaseQueryRule::SEARCH_OPERATOR op;
+ int localizedString;
+} operatorField;
+
+static const operatorField operators[] = {
+ {"contains", CDatabaseQueryRule::OPERATOR_CONTAINS, 21400},
+ {"doesnotcontain", CDatabaseQueryRule::OPERATOR_DOES_NOT_CONTAIN, 21401},
+ {"is", CDatabaseQueryRule::OPERATOR_EQUALS, 21402},
+ {"isnot", CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL, 21403},
+ {"startswith", CDatabaseQueryRule::OPERATOR_STARTS_WITH, 21404},
+ {"endswith", CDatabaseQueryRule::OPERATOR_ENDS_WITH, 21405},
+ {"greaterthan", CDatabaseQueryRule::OPERATOR_GREATER_THAN, 21406},
+ {"lessthan", CDatabaseQueryRule::OPERATOR_LESS_THAN, 21407},
+ {"after", CDatabaseQueryRule::OPERATOR_AFTER, 21408},
+ {"before", CDatabaseQueryRule::OPERATOR_BEFORE, 21409},
+ {"inthelast", CDatabaseQueryRule::OPERATOR_IN_THE_LAST, 21410},
+ {"notinthelast", CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST, 21411},
+ {"true", CDatabaseQueryRule::OPERATOR_TRUE, 20122},
+ {"false", CDatabaseQueryRule::OPERATOR_FALSE, 20424},
+ {"between", CDatabaseQueryRule::OPERATOR_BETWEEN, 21456}};
+
+CDatabaseQueryRule::CDatabaseQueryRule()
+{
+ m_field = 0;
+ m_operator = OPERATOR_CONTAINS;
+}
+
+bool CDatabaseQueryRule::Load(const TiXmlNode* node, const std::string& encoding /* = "UTF-8" */)
+{
+ if (node == NULL)
+ return false;
+
+ const TiXmlElement* element = node->ToElement();
+ if (element == NULL)
+ return false;
+
+ // format is:
+ // <rule field="Genre" operator="contains">parameter</rule>
+ // where parameter can either be a string or a list of
+ // <value> tags containing a string
+ const char* field = element->Attribute("field");
+ const char* oper = element->Attribute("operator");
+ if (field == NULL || oper == NULL)
+ return false;
+
+ m_field = TranslateField(field);
+ m_operator = TranslateOperator(oper);
+
+ if (m_operator == OPERATOR_TRUE || m_operator == OPERATOR_FALSE)
+ return true;
+
+ const TiXmlNode* parameter = element->FirstChild();
+ if (parameter == NULL)
+ return false;
+
+ if (parameter->Type() == TiXmlNode::TINYXML_TEXT)
+ {
+ std::string utf8Parameter;
+ if (encoding.empty()) // utf8
+ utf8Parameter = parameter->ValueStr();
+ else
+ g_charsetConverter.ToUtf8(encoding, parameter->ValueStr(), utf8Parameter);
+
+ if (!utf8Parameter.empty())
+ m_parameter.push_back(utf8Parameter);
+ }
+ else if (parameter->Type() == TiXmlNode::TINYXML_ELEMENT)
+ {
+ const TiXmlNode* valueNode = element->FirstChild("value");
+ while (valueNode != NULL)
+ {
+ const TiXmlNode* value = valueNode->FirstChild();
+ if (value != NULL && value->Type() == TiXmlNode::TINYXML_TEXT)
+ {
+ std::string utf8Parameter;
+ if (encoding.empty()) // utf8
+ utf8Parameter = value->ValueStr();
+ else
+ g_charsetConverter.ToUtf8(encoding, value->ValueStr(), utf8Parameter);
+
+ if (!utf8Parameter.empty())
+ m_parameter.push_back(utf8Parameter);
+ }
+
+ valueNode = valueNode->NextSibling("value");
+ }
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CDatabaseQueryRule::Load(const CVariant& obj)
+{
+ if (!obj.isMember("field") || !obj["field"].isString() || !obj.isMember("operator") ||
+ !obj["operator"].isString())
+ return false;
+
+ m_field = TranslateField(obj["field"].asString().c_str());
+ m_operator = TranslateOperator(obj["operator"].asString().c_str());
+
+ if (m_operator == OPERATOR_TRUE || m_operator == OPERATOR_FALSE)
+ return true;
+
+ if (!obj.isMember("value") || (!obj["value"].isString() && !obj["value"].isArray()))
+ return false;
+
+ const CVariant& value = obj["value"];
+ if (value.isString())
+ m_parameter.push_back(value.asString());
+ else if (value.isArray())
+ {
+ for (CVariant::const_iterator_array val = value.begin_array(); val != value.end_array(); ++val)
+ {
+ if (val->isString() && !val->asString().empty())
+ m_parameter.push_back(val->asString());
+ }
+ if (m_parameter.empty())
+ m_parameter.emplace_back("");
+ }
+ else
+ return false;
+
+ return true;
+}
+
+bool CDatabaseQueryRule::Save(TiXmlNode* parent) const
+{
+ if (parent == NULL ||
+ (m_parameter.empty() && m_operator != OPERATOR_TRUE && m_operator != OPERATOR_FALSE))
+ return false;
+
+ TiXmlElement rule("rule");
+ rule.SetAttribute("field", TranslateField(m_field).c_str());
+ rule.SetAttribute("operator", TranslateOperator(m_operator).c_str());
+
+ for (const auto& it : m_parameter)
+ {
+ TiXmlElement value("value");
+ TiXmlText text(it);
+ value.InsertEndChild(text);
+ rule.InsertEndChild(value);
+ }
+
+ parent->InsertEndChild(rule);
+
+ return true;
+}
+
+bool CDatabaseQueryRule::Save(CVariant& obj) const
+{
+ if (obj.isNull() ||
+ (m_parameter.empty() && m_operator != OPERATOR_TRUE && m_operator != OPERATOR_FALSE))
+ return false;
+
+ obj["field"] = TranslateField(m_field);
+ obj["operator"] = TranslateOperator(m_operator);
+ obj["value"] = m_parameter;
+
+ return true;
+}
+
+CDatabaseQueryRule::SEARCH_OPERATOR CDatabaseQueryRule::TranslateOperator(const char* oper)
+{
+ for (const operatorField& o : operators)
+ if (StringUtils::EqualsNoCase(oper, o.string))
+ return o.op;
+ return OPERATOR_CONTAINS;
+}
+
+std::string CDatabaseQueryRule::TranslateOperator(SEARCH_OPERATOR oper)
+{
+ for (const operatorField& o : operators)
+ if (oper == o.op)
+ return o.string;
+ return "contains";
+}
+
+std::string CDatabaseQueryRule::GetLocalizedOperator(SEARCH_OPERATOR oper)
+{
+ for (const operatorField& o : operators)
+ if (oper == o.op)
+ return g_localizeStrings.Get(o.localizedString);
+ return g_localizeStrings.Get(16018);
+}
+
+void CDatabaseQueryRule::GetAvailableOperators(std::vector<std::string>& operatorList)
+{
+ for (const operatorField& o : operators)
+ operatorList.emplace_back(o.string);
+}
+
+std::string CDatabaseQueryRule::GetParameter() const
+{
+ return StringUtils::Join(m_parameter, DATABASEQUERY_RULE_VALUE_SEPARATOR);
+}
+
+void CDatabaseQueryRule::SetParameter(const std::string& value)
+{
+ m_parameter = StringUtils::Split(value, DATABASEQUERY_RULE_VALUE_SEPARATOR);
+}
+
+void CDatabaseQueryRule::SetParameter(const std::vector<std::string>& values)
+{
+ m_parameter.assign(values.begin(), values.end());
+}
+
+std::string CDatabaseQueryRule::ValidateParameter(const std::string& parameter) const
+{
+ if ((GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD) &&
+ parameter.empty())
+ return "0"; // interpret empty fields as 0
+ return parameter;
+}
+
+std::string CDatabaseQueryRule::FormatParameter(const std::string& operatorString,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& strType) const
+{
+ std::string parameter;
+ if (GetFieldType(m_field) == TEXTIN_FIELD)
+ {
+ std::vector<std::string> split = StringUtils::Split(param, ',');
+ for (std::string& itIn : split)
+ {
+ if (!parameter.empty())
+ parameter += ",";
+ parameter += db.PrepareSQL("'%s'", StringUtils::Trim(itIn).c_str());
+ }
+ parameter = " IN (" + parameter + ")";
+ }
+ else
+ parameter = db.PrepareSQL(operatorString, ValidateParameter(param).c_str());
+
+ if (GetFieldType(m_field) == DATE_FIELD)
+ {
+ if (m_operator == OPERATOR_IN_THE_LAST || m_operator == OPERATOR_NOT_IN_THE_LAST)
+ { // translate time period
+ CDateTime date = CDateTime::GetCurrentDateTime();
+ CDateTimeSpan span;
+ span.SetFromPeriod(param);
+ date -= span;
+ parameter = db.PrepareSQL(operatorString, date.GetAsDBDate().c_str());
+ }
+ }
+ return parameter;
+}
+
+std::string CDatabaseQueryRule::GetOperatorString(SEARCH_OPERATOR op) const
+{
+ std::string operatorString;
+ if (GetFieldType(m_field) != TEXTIN_FIELD)
+ {
+ // the comparison piece
+ switch (op)
+ {
+ case OPERATOR_CONTAINS:
+ operatorString = " LIKE '%%%s%%'";
+ break;
+ case OPERATOR_DOES_NOT_CONTAIN:
+ operatorString = " LIKE '%%%s%%'";
+ break;
+ case OPERATOR_EQUALS:
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString = " = %s";
+ else
+ operatorString = " LIKE '%s'";
+ break;
+ case OPERATOR_DOES_NOT_EQUAL:
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString = " != %s";
+ else
+ operatorString = " LIKE '%s'";
+ break;
+ case OPERATOR_STARTS_WITH:
+ operatorString = " LIKE '%s%%'";
+ break;
+ case OPERATOR_ENDS_WITH:
+ operatorString = " LIKE '%%%s'";
+ break;
+ case OPERATOR_AFTER:
+ case OPERATOR_GREATER_THAN:
+ case OPERATOR_IN_THE_LAST:
+ operatorString = " > ";
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString += "%s";
+ else
+ operatorString += "'%s'";
+ break;
+ case OPERATOR_BEFORE:
+ case OPERATOR_LESS_THAN:
+ case OPERATOR_NOT_IN_THE_LAST:
+ operatorString = " < ";
+ if (GetFieldType(m_field) == REAL_FIELD || GetFieldType(m_field) == NUMERIC_FIELD ||
+ GetFieldType(m_field) == SECONDS_FIELD)
+ operatorString += "%s";
+ else
+ operatorString += "'%s'";
+ break;
+ case OPERATOR_TRUE:
+ operatorString = " = 1";
+ break;
+ case OPERATOR_FALSE:
+ operatorString = " = 0";
+ break;
+ default:
+ break;
+ }
+ }
+ return operatorString;
+}
+
+std::string CDatabaseQueryRule::GetWhereClause(const CDatabase& db,
+ const std::string& strType) const
+{
+ SEARCH_OPERATOR op = GetOperator(strType);
+
+ std::string operatorString = GetOperatorString(op);
+ std::string negate;
+ if (op == OPERATOR_DOES_NOT_CONTAIN || op == OPERATOR_FALSE ||
+ (op == OPERATOR_DOES_NOT_EQUAL && GetFieldType(m_field) != REAL_FIELD &&
+ GetFieldType(m_field) != NUMERIC_FIELD && GetFieldType(m_field) != SECONDS_FIELD))
+ negate = " NOT ";
+
+ // boolean operators don't have any values in m_parameter, they work on the operator
+ if (m_operator == OPERATOR_FALSE || m_operator == OPERATOR_TRUE)
+ return GetBooleanQuery(negate, strType);
+
+ // Process boolean field with (not) EQUAL/CONTAINS "true"/"false" parameter too
+ if (GetFieldType(m_field) == BOOLEAN_FIELD &&
+ (m_parameter[0] == "true" || m_parameter[0] == "false") &&
+ (op == OPERATOR_CONTAINS || op == OPERATOR_EQUALS || op == OPERATOR_DOES_NOT_CONTAIN ||
+ op == OPERATOR_DOES_NOT_EQUAL))
+ {
+ if (m_parameter[0] == "false")
+ {
+ if (!negate.empty())
+ negate.clear();
+ else
+ negate = " NOT ";
+ }
+ return GetBooleanQuery(negate, strType);
+ }
+
+ // The BETWEEN operator is handled special
+ if (op == OPERATOR_BETWEEN)
+ {
+ if (m_parameter.size() != 2)
+ return "";
+
+ FIELD_TYPE fieldType = GetFieldType(m_field);
+ if (fieldType == REAL_FIELD)
+ return db.PrepareSQL("%s BETWEEN %s AND %s", GetField(m_field, strType).c_str(),
+ m_parameter[0].c_str(), m_parameter[1].c_str());
+ else if (fieldType == NUMERIC_FIELD)
+ return db.PrepareSQL("CAST(%s as DECIMAL(5,1)) BETWEEN %s AND %s",
+ GetField(m_field, strType).c_str(), m_parameter[0].c_str(),
+ m_parameter[1].c_str());
+ else if (fieldType == SECONDS_FIELD)
+ return db.PrepareSQL("CAST(%s as INTEGER) BETWEEN %s AND %s",
+ GetField(m_field, strType).c_str(), m_parameter[0].c_str(),
+ m_parameter[1].c_str());
+ else
+ return db.PrepareSQL("%s BETWEEN '%s' AND '%s'", GetField(m_field, strType).c_str(),
+ m_parameter[0].c_str(), m_parameter[1].c_str());
+ }
+
+ // now the query parameter
+ std::string wholeQuery;
+ for (std::vector<std::string>::const_iterator it = m_parameter.begin(); it != m_parameter.end();
+ ++it)
+ {
+ std::string query = '(' + FormatWhereClause(negate, operatorString, *it, db, strType) + ')';
+
+ if (it + 1 != m_parameter.end())
+ {
+ if (negate.empty())
+ query += " OR ";
+ else
+ query += " AND ";
+ }
+
+ wholeQuery += query;
+ }
+
+ return wholeQuery;
+}
+
+std::string CDatabaseQueryRule::FormatWhereClause(const std::string& negate,
+ const std::string& oper,
+ const std::string& param,
+ const CDatabase& db,
+ const std::string& strType) const
+{
+ std::string parameter = FormatParameter(oper, param, db, strType);
+
+ std::string query;
+ if (m_field != 0)
+ {
+ std::string fmt = "{}";
+ if (GetFieldType(m_field) == NUMERIC_FIELD)
+ fmt = "CAST({} as DECIMAL(6,1))";
+ else if (GetFieldType(m_field) == SECONDS_FIELD)
+ fmt = "CAST({} as INTEGER)";
+
+ query = StringUtils::Format(fmt, GetField(m_field, strType));
+ query += negate + parameter;
+
+ // special case for matching parameters in fields that might be either empty or NULL.
+ if ((param.empty() && negate.empty()) || (!param.empty() && !negate.empty()))
+ query += " OR " + GetField(m_field, strType) + " IS NULL";
+ }
+
+ if (query == negate + parameter)
+ query = "1";
+ return query;
+}
+
+void CDatabaseQueryRuleCombination::clear()
+{
+ m_combinations.clear();
+ m_rules.clear();
+ m_type = CombinationAnd;
+}
+
+std::string CDatabaseQueryRuleCombination::GetWhereClause(const CDatabase& db,
+ const std::string& strType) const
+{
+ std::string rule;
+
+ // translate the combinations into SQL
+ for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin();
+ it != m_combinations.end(); ++it)
+ {
+ if (it != m_combinations.begin())
+ rule += m_type == CombinationAnd ? " AND " : " OR ";
+ rule += "(" + (*it)->GetWhereClause(db, strType) + ")";
+ }
+
+ // translate the rules into SQL
+ for (const auto& it : m_rules)
+ {
+ if (!rule.empty())
+ rule += m_type == CombinationAnd ? " AND " : " OR ";
+ rule += "(";
+ std::string currentRule = it->GetWhereClause(db, strType);
+ // if we don't get a rule, we add '1' or '0' so the query is still valid and doesn't fail
+ if (currentRule.empty())
+ currentRule = m_type == CombinationAnd ? "'1'" : "'0'";
+ rule += currentRule;
+ rule += ")";
+ }
+
+ return rule;
+}
+
+bool CDatabaseQueryRuleCombination::Load(const CVariant& obj,
+ const IDatabaseQueryRuleFactory* factory)
+{
+ if (!obj.isObject() && !obj.isArray())
+ return false;
+
+ CVariant child;
+ if (obj.isObject())
+ {
+ if (obj.isMember("and") && obj["and"].isArray())
+ {
+ m_type = CombinationAnd;
+ child = obj["and"];
+ }
+ else if (obj.isMember("or") && obj["or"].isArray())
+ {
+ m_type = CombinationOr;
+ child = obj["or"];
+ }
+ else
+ return false;
+ }
+ else
+ child = obj;
+
+ for (CVariant::const_iterator_array it = child.begin_array(); it != child.end_array(); ++it)
+ {
+ if (!it->isObject())
+ continue;
+
+ if (it->isMember("and") || it->isMember("or"))
+ {
+ std::shared_ptr<CDatabaseQueryRuleCombination> combo(factory->CreateCombination());
+ if (combo && combo->Load(*it, factory))
+ m_combinations.push_back(combo);
+ }
+ else
+ {
+ std::shared_ptr<CDatabaseQueryRule> rule(factory->CreateRule());
+ if (rule && rule->Load(*it))
+ m_rules.push_back(rule);
+ }
+ }
+
+ return true;
+}
+
+bool CDatabaseQueryRuleCombination::Save(TiXmlNode* parent) const
+{
+ for (const auto& it : m_rules)
+ it->Save(parent);
+ return true;
+}
+
+bool CDatabaseQueryRuleCombination::Save(CVariant& obj) const
+{
+ if (!obj.isObject() || (m_combinations.empty() && m_rules.empty()))
+ return false;
+
+ CVariant comboArray(CVariant::VariantTypeArray);
+ if (!m_combinations.empty())
+ {
+ for (const auto& combo : m_combinations)
+ {
+ CVariant comboObj(CVariant::VariantTypeObject);
+ if (combo->Save(comboObj))
+ comboArray.push_back(comboObj);
+ }
+ }
+ if (!m_rules.empty())
+ {
+ for (const auto& rule : m_rules)
+ {
+ CVariant ruleObj(CVariant::VariantTypeObject);
+ if (rule->Save(ruleObj))
+ comboArray.push_back(ruleObj);
+ }
+ }
+
+ obj[TranslateCombinationType()] = comboArray;
+
+ return true;
+}
+
+std::string CDatabaseQueryRuleCombination::TranslateCombinationType() const
+{
+ return m_type == CombinationAnd ? "and" : "or";
+}