diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/dbwrappers/DatabaseQuery.cpp | |
parent | Initial commit. (diff) | |
download | kodi-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.cpp | 571 |
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"; +} |