Merge pull request #4676 from jmarshallnz/dont_set_scraper_on_tvshow_on_nfo
[vuplus_xbmc] / xbmc / dbwrappers / DatabaseQuery.cpp
1 /*
2  *      Copyright (C) 2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "DatabaseQuery.h"
22 #include "Database.h"
23 #include "XBDateTime.h"
24 #include "guilib/LocalizeStrings.h"
25 #include "utils/CharsetConverter.h"
26 #include "utils/StringUtils.h"
27 #include "utils/Variant.h"
28 #include "utils/XBMCTinyXML.h"
29
30 using namespace std;
31
32 typedef struct
33 {
34   char string[15];
35   CDatabaseQueryRule::SEARCH_OPERATOR op;
36   int localizedString;
37 } operatorField;
38
39 static const operatorField operators[] = {
40   { "contains",        CDatabaseQueryRule::OPERATOR_CONTAINS,          21400 },
41   { "doesnotcontain",  CDatabaseQueryRule::OPERATOR_DOES_NOT_CONTAIN,  21401 },
42   { "is",              CDatabaseQueryRule::OPERATOR_EQUALS,            21402 },
43   { "isnot",           CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL,    21403 },
44   { "startswith",      CDatabaseQueryRule::OPERATOR_STARTS_WITH,       21404 },
45   { "endswith",        CDatabaseQueryRule::OPERATOR_ENDS_WITH,         21405 },
46   { "greaterthan",     CDatabaseQueryRule::OPERATOR_GREATER_THAN,      21406 },
47   { "lessthan",        CDatabaseQueryRule::OPERATOR_LESS_THAN,         21407 },
48   { "after",           CDatabaseQueryRule::OPERATOR_AFTER,             21408 },
49   { "before",          CDatabaseQueryRule::OPERATOR_BEFORE,            21409 },
50   { "inthelast",       CDatabaseQueryRule::OPERATOR_IN_THE_LAST,       21410 },
51   { "notinthelast",    CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST,   21411 },
52   { "true",            CDatabaseQueryRule::OPERATOR_TRUE,              20122 },
53   { "false",           CDatabaseQueryRule::OPERATOR_FALSE,             20424 },
54   { "between",         CDatabaseQueryRule::OPERATOR_BETWEEN,           21456 }
55 };
56
57 static const size_t NUM_OPERATORS = sizeof(operators) / sizeof(operatorField);
58
59 #define RULE_VALUE_SEPARATOR  " / "
60
61 CDatabaseQueryRule::CDatabaseQueryRule()
62 {
63   m_field = 0;
64   m_operator = OPERATOR_CONTAINS;
65 }
66
67 bool CDatabaseQueryRule::Load(const TiXmlNode *node, const std::string &encoding /* = "UTF-8" */)
68 {
69   if (node == NULL)
70     return false;
71
72   const TiXmlElement *element = node->ToElement();
73   if (element == NULL)
74     return false;
75
76   // format is:
77   // <rule field="Genre" operator="contains">parameter</rule>
78   // where parameter can either be a string or a list of
79   // <value> tags containing a string
80   const char *field = element->Attribute("field");
81   const char *oper = element->Attribute("operator");
82   if (field == NULL || oper == NULL)
83     return false;
84
85   m_field = TranslateField(field);
86   m_operator = TranslateOperator(oper);
87
88   if (m_operator == OPERATOR_TRUE || m_operator == OPERATOR_FALSE)
89     return true;
90
91   const TiXmlNode *parameter = element->FirstChild();
92   if (parameter == NULL)
93     return false;
94
95   if (parameter->Type() == TiXmlNode::TINYXML_TEXT)
96   {
97     CStdString utf8Parameter;
98     if (encoding.empty()) // utf8
99       utf8Parameter = parameter->ValueStr();
100     else
101       g_charsetConverter.ToUtf8(encoding, parameter->ValueStr(), utf8Parameter);
102
103     if (!utf8Parameter.empty())
104       m_parameter.push_back(utf8Parameter);
105   }
106   else if (parameter->Type() == TiXmlNode::TINYXML_ELEMENT)
107   {
108     const TiXmlNode *valueNode = element->FirstChild("value");
109     while (valueNode != NULL)
110     {
111       const TiXmlNode *value = valueNode->FirstChild();
112       if (value != NULL && value->Type() == TiXmlNode::TINYXML_TEXT)
113       {
114         CStdString utf8Parameter;
115         if (encoding.empty()) // utf8
116           utf8Parameter = value->ValueStr();
117         else
118           g_charsetConverter.ToUtf8(encoding, value->ValueStr(), utf8Parameter);
119
120         if (!utf8Parameter.empty())
121           m_parameter.push_back(utf8Parameter);
122       }
123
124       valueNode = valueNode->NextSibling("value");
125     }
126   }
127   else
128     return false;
129
130   return true;
131 }
132
133 bool CDatabaseQueryRule::Load(const CVariant &obj)
134 {
135   if (!obj.isObject() ||
136       !obj.isMember("field") || !obj["field"].isString() ||
137       !obj.isMember("operator") || !obj["operator"].isString())
138     return false;
139
140   m_field = TranslateField(obj["field"].asString().c_str());
141   m_operator = TranslateOperator(obj["operator"].asString().c_str());
142
143   if (m_operator == OPERATOR_TRUE || m_operator == OPERATOR_FALSE)
144     return true;
145
146   if (!obj.isMember("value") || (!obj["value"].isString() && !obj["value"].isArray()))
147     return false;
148
149   const CVariant &value = obj["value"];
150   if (value.isString())
151     m_parameter.push_back(value.asString());
152   else if (value.isArray())
153   {
154     for (CVariant::const_iterator_array val = value.begin_array(); val != value.end_array(); val++)
155     {
156       if (val->isString() && !val->asString().empty())
157         m_parameter.push_back(val->asString());
158     }
159     if (m_parameter.empty())
160       m_parameter.push_back("");
161   }
162   else
163     return false;
164
165   return true;
166 }
167
168 bool CDatabaseQueryRule::Save(TiXmlNode *parent) const
169 {
170   if (parent == NULL || (m_parameter.empty() && m_operator != OPERATOR_TRUE && m_operator != OPERATOR_FALSE))
171     return false;
172
173   TiXmlElement rule("rule");
174   rule.SetAttribute("field", TranslateField(m_field).c_str());
175   rule.SetAttribute("operator", TranslateOperator(m_operator).c_str());
176
177   for (vector<CStdString>::const_iterator it = m_parameter.begin(); it != m_parameter.end(); it++)
178   {
179     TiXmlElement value("value");
180     TiXmlText text(it->c_str());
181     value.InsertEndChild(text);
182     rule.InsertEndChild(value);
183   }
184
185   parent->InsertEndChild(rule);
186
187   return true;
188 }
189
190 bool CDatabaseQueryRule::Save(CVariant &obj) const
191 {
192   if (obj.isNull() || (m_parameter.empty() && m_operator != OPERATOR_TRUE && m_operator != OPERATOR_FALSE))
193     return false;
194
195   obj["field"] = TranslateField(m_field);
196   obj["operator"] = TranslateOperator(m_operator);
197
198   obj["value"] = CVariant(CVariant::VariantTypeArray);
199   for (vector<CStdString>::const_iterator it = m_parameter.begin(); it != m_parameter.end(); it++)
200     obj["value"].push_back(*it);
201
202   return true;
203 }
204
205 CDatabaseQueryRule::SEARCH_OPERATOR CDatabaseQueryRule::TranslateOperator(const char *oper)
206 {
207   for (unsigned int i = 0; i < NUM_OPERATORS; i++)
208     if (StringUtils::EqualsNoCase(oper, operators[i].string)) return operators[i].op;
209   return OPERATOR_CONTAINS;
210 }
211
212 CStdString CDatabaseQueryRule::TranslateOperator(SEARCH_OPERATOR oper)
213 {
214   for (unsigned int i = 0; i < NUM_OPERATORS; i++)
215     if (oper == operators[i].op) return operators[i].string;
216   return "contains";
217 }
218
219 CStdString CDatabaseQueryRule::GetLocalizedOperator(SEARCH_OPERATOR oper)
220 {
221   for (unsigned int i = 0; i < NUM_OPERATORS; i++)
222     if (oper == operators[i].op) return g_localizeStrings.Get(operators[i].localizedString);
223   return g_localizeStrings.Get(16018);
224 }
225
226 void CDatabaseQueryRule::GetAvailableOperators(std::vector<std::string> &operatorList)
227 {
228   for (unsigned int index = 0; index < NUM_OPERATORS; index++)
229     operatorList.push_back(operators[index].string);
230 }
231
232 CStdString CDatabaseQueryRule::GetParameter() const
233 {
234   return StringUtils::JoinString(m_parameter, RULE_VALUE_SEPARATOR);
235 }
236
237 void CDatabaseQueryRule::SetParameter(const CStdString &value)
238 {
239   m_parameter.clear();
240   StringUtils::SplitString(value, RULE_VALUE_SEPARATOR, m_parameter);
241 }
242
243 void CDatabaseQueryRule::SetParameter(const std::vector<CStdString> &values)
244 {
245   m_parameter.assign(values.begin(), values.end());
246 }
247
248 CStdString CDatabaseQueryRule::ValidateParameter(const CStdString &parameter) const
249 {
250   if ((GetFieldType(m_field) == NUMERIC_FIELD ||
251        GetFieldType(m_field) == SECONDS_FIELD) && parameter.empty())
252     return "0"; // interpret empty fields as 0
253   return parameter;
254 }
255
256 CStdString CDatabaseQueryRule::FormatParameter(const CStdString &operatorString, const CStdString &param, const CDatabase &db, const CStdString &strType) const
257 {
258   CStdString parameter;
259   if (GetFieldType(m_field) == TEXTIN_FIELD)
260   {
261     CStdStringArray split;
262     StringUtils::SplitString(param, ",", split);
263     for (CStdStringArray::iterator itIn = split.begin(); itIn != split.end(); ++itIn)
264     {
265       if (!parameter.empty())
266         parameter += ",";
267       parameter += db.PrepareSQL("'%s'", StringUtils::Trim(*itIn).c_str());
268     }
269     parameter = " IN (" + parameter + ")";
270   }
271   else
272     parameter = db.PrepareSQL(operatorString.c_str(), ValidateParameter(param).c_str());
273
274   if (GetFieldType(m_field) == DATE_FIELD)
275   {
276     if (m_operator == OPERATOR_IN_THE_LAST || m_operator == OPERATOR_NOT_IN_THE_LAST)
277     { // translate time period
278       CDateTime date=CDateTime::GetCurrentDateTime();
279       CDateTimeSpan span;
280       span.SetFromPeriod(param);
281       date-=span;
282       parameter = db.PrepareSQL(operatorString.c_str(), date.GetAsDBDate().c_str());
283     }
284   }
285   return parameter;
286 }
287
288 CStdString CDatabaseQueryRule::GetOperatorString(SEARCH_OPERATOR op) const
289 {
290   CStdString operatorString;
291   if (GetFieldType(m_field) != TEXTIN_FIELD)
292   {
293     // the comparison piece
294     switch (op)
295     {
296     case OPERATOR_CONTAINS:
297       operatorString = " LIKE '%%%s%%'"; break;
298     case OPERATOR_DOES_NOT_CONTAIN:
299       operatorString = " LIKE '%%%s%%'"; break;
300     case OPERATOR_EQUALS:
301       if (GetFieldType(m_field) == NUMERIC_FIELD || GetFieldType(m_field) == SECONDS_FIELD)
302         operatorString = " = %s";
303       else
304         operatorString = " LIKE '%s'";
305       break;
306     case OPERATOR_DOES_NOT_EQUAL:
307       if (GetFieldType(m_field) == NUMERIC_FIELD || GetFieldType(m_field) == SECONDS_FIELD)
308         operatorString = " != %s";
309       else
310         operatorString = " LIKE '%s'";
311       break;
312     case OPERATOR_STARTS_WITH:
313       operatorString = " LIKE '%s%%'"; break;
314     case OPERATOR_ENDS_WITH:
315       operatorString = " LIKE '%%%s'"; break;
316     case OPERATOR_AFTER:
317     case OPERATOR_GREATER_THAN:
318     case OPERATOR_IN_THE_LAST:
319       operatorString = " > ";
320       if (GetFieldType(m_field) == NUMERIC_FIELD || GetFieldType(m_field) == SECONDS_FIELD)
321         operatorString += "%s";
322       else
323         operatorString += "'%s'";
324       break;
325     case OPERATOR_BEFORE:
326     case OPERATOR_LESS_THAN:
327     case OPERATOR_NOT_IN_THE_LAST:
328       operatorString = " < ";
329       if (GetFieldType(m_field) == NUMERIC_FIELD || GetFieldType(m_field) == SECONDS_FIELD)
330         operatorString += "%s";
331       else
332         operatorString += "'%s'";
333       break;
334     case OPERATOR_TRUE:
335       operatorString = " = 1"; break;
336     case OPERATOR_FALSE:
337       operatorString = " = 0"; break;
338     default:
339       break;
340     }
341   }
342   return operatorString;
343 }
344
345 CStdString CDatabaseQueryRule::GetWhereClause(const CDatabase &db, const CStdString& strType) const
346 {
347   SEARCH_OPERATOR op = GetOperator(strType);
348
349   CStdString operatorString = GetOperatorString(op);
350   CStdString negate;
351   if (op == OPERATOR_DOES_NOT_CONTAIN || op == OPERATOR_FALSE ||
352      (op == OPERATOR_DOES_NOT_EQUAL && GetFieldType(m_field) != NUMERIC_FIELD && GetFieldType(m_field) != SECONDS_FIELD))
353     negate = " NOT";
354
355   // boolean operators don't have any values in m_parameter, they work on the operator
356   if (m_operator == OPERATOR_FALSE || m_operator == OPERATOR_TRUE)
357     return GetBooleanQuery(negate, strType);
358
359   // The BETWEEN operator is handled special
360   if (op == OPERATOR_BETWEEN)
361   {
362     if (m_parameter.size() != 2)
363       return "";
364
365     FIELD_TYPE fieldType = GetFieldType(m_field);
366     if (fieldType == NUMERIC_FIELD)
367       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());
368     else if (fieldType == SECONDS_FIELD)
369       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());
370     else
371       return db.PrepareSQL("%s BETWEEN '%s' AND '%s'", GetField(m_field, strType).c_str(), m_parameter[0].c_str(), m_parameter[1].c_str());
372   }
373
374   // now the query parameter
375   CStdString wholeQuery;
376   for (vector<CStdString>::const_iterator it = m_parameter.begin(); it != m_parameter.end(); ++it)
377   {
378     CStdString query = "(" + FormatWhereClause(negate, operatorString, *it, db, strType) + ")";
379
380     if (it+1 != m_parameter.end())
381       query += " OR ";
382
383     wholeQuery += query;
384   }
385
386   return wholeQuery;
387 }
388
389 CStdString CDatabaseQueryRule::FormatWhereClause(const CStdString &negate, const CStdString &oper, const CStdString &param,
390                                                  const CDatabase &db, const CStdString &strType) const
391 {
392   CStdString parameter = FormatParameter(oper, param, db, strType);
393
394   CStdString query;
395   if (m_field != 0)
396   {
397     string fmt = "%s";
398     if (GetFieldType(m_field) == NUMERIC_FIELD)
399       fmt = "CAST(%s as DECIMAL(5,1))";
400     else if (GetFieldType(m_field) == SECONDS_FIELD)
401       fmt = "CAST(%s as INTEGER)";
402
403     query = StringUtils::Format(fmt.c_str(), GetField(m_field,strType).c_str());
404     query += negate + parameter;
405
406     // special case for matching parameters in fields that might be either empty or NULL.
407     if ((  param.empty() &&  negate.empty() ) ||
408         ( !param.empty() && !negate.empty() ))
409       query += " OR " + GetField(m_field,strType) + " IS NULL";
410   }
411
412   if (query.Equals(negate + parameter))
413     query = "1";
414   return query;
415 }
416
417 CDatabaseQueryRuleCombination::CDatabaseQueryRuleCombination()
418   : m_type(CombinationAnd)
419 { }
420
421 void CDatabaseQueryRuleCombination::clear()
422 {
423   m_combinations.clear();
424   m_rules.clear();
425   m_type = CombinationAnd;
426 }
427
428 CStdString CDatabaseQueryRuleCombination::GetWhereClause(const CDatabase &db, const CStdString& strType) const
429 {
430   CStdString rule, currentRule;
431
432   // translate the combinations into SQL
433   for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
434   {
435     if (it != m_combinations.begin())
436       rule += m_type == CombinationAnd ? " AND " : " OR ";
437     rule += "(" + (*it)->GetWhereClause(db, strType) + ")";
438   }
439
440   // translate the rules into SQL
441   for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
442   {
443     if (!rule.empty())
444       rule += m_type == CombinationAnd ? " AND " : " OR ";
445     rule += "(";
446     CStdString currentRule = (*it)->GetWhereClause(db, strType);
447     // if we don't get a rule, we add '1' or '0' so the query is still valid and doesn't fail
448     if (currentRule.empty())
449       currentRule = m_type == CombinationAnd ? "'1'" : "'0'";
450     rule += currentRule;
451     rule += ")";
452   }
453
454   return rule;
455 }
456
457 bool CDatabaseQueryRuleCombination::Load(const CVariant &obj, const IDatabaseQueryRuleFactory *factory)
458 {
459   if (!obj.isObject() && !obj.isArray())
460     return false;
461   
462   CVariant child;
463   if (obj.isObject())
464   {
465     if (obj.isMember("and") && obj["and"].isArray())
466     {
467       m_type = CombinationAnd;
468       child = obj["and"];
469     }
470     else if (obj.isMember("or") && obj["or"].isArray())
471     {
472       m_type = CombinationOr;
473       child = obj["or"];
474     }
475     else
476       return false;
477   }
478   else
479     child = obj;
480
481   for (CVariant::const_iterator_array it = child.begin_array(); it != child.end_array(); it++)
482   {
483     if (!it->isObject())
484       continue;
485
486     if (it->isMember("and") || it->isMember("or"))
487     {
488       boost::shared_ptr<CDatabaseQueryRuleCombination> combo(factory->CreateCombination());
489       if (combo && combo->Load(*it, factory))
490         m_combinations.push_back(combo);
491     }
492     else
493     {
494       boost::shared_ptr<CDatabaseQueryRule> rule(factory->CreateRule());
495       if (rule && rule->Load(*it))
496         m_rules.push_back(rule);
497     }
498   }
499
500   return true;
501 }
502
503 bool CDatabaseQueryRuleCombination::Save(TiXmlNode *parent) const
504 {
505   for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
506     (*it)->Save(parent);
507   return true;
508 }
509
510 bool CDatabaseQueryRuleCombination::Save(CVariant &obj) const
511 {
512   if (!obj.isObject() || (m_combinations.empty() && m_rules.empty()))
513     return false;
514
515   CVariant comboArray(CVariant::VariantTypeArray);
516   if (!m_combinations.empty())
517   {
518     for (CDatabaseQueryRuleCombinations::const_iterator combo = m_combinations.begin(); combo != m_combinations.end(); combo++)
519     {
520       CVariant comboObj(CVariant::VariantTypeObject);
521       if ((*combo)->Save(comboObj))
522         comboArray.push_back(comboObj);
523     }
524
525   }
526   if (!m_rules.empty())
527   {
528     for (CDatabaseQueryRules::const_iterator rule = m_rules.begin(); rule != m_rules.end(); rule++)
529     {
530       CVariant ruleObj(CVariant::VariantTypeObject);
531       if ((*rule)->Save(ruleObj))
532         comboArray.push_back(ruleObj);
533     }
534   }
535
536   obj[TranslateCombinationType()] = comboArray;
537
538   return true;
539 }
540
541 std::string CDatabaseQueryRuleCombination::TranslateCombinationType() const
542 {
543   return m_type == CombinationAnd ? "and" : "or";
544 }