Changes: add support for relationshipFilter with HasMember
[qtcontacts-tracker:qtcontacts-tracker.git] / src / dao / querybuilder.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "querybuilder.h"
43
44 #include "contactdetail.h"
45 #include "contactdetailschema.h"
46 #include "logger.h"
47 #include "settings.h"
48 #include "support.h"
49
50 using namespace SopranoLive;
51 using namespace SopranoLive::Ontologies;
52
53 ////////////////////////////////////////////////////////////////////////////////////////////////////
54
55 template<bool> class StaticAssert;
56 template<> class StaticAssert<true> {};
57
58 ////////////////////////////////////////////////////////////////////////////////////////////////////
59
60 static const PredicateChain WorkContextChain
61         (PredicateChain() << nco::hasAffiliation::iri());
62 static const QContactFilter::MatchFlags MatchFunctionFlags
63         (QContactFilter::MatchExactly | QContactFilter::MatchContains |
64          QContactFilter::MatchStartsWith | QContactFilter::MatchEndsWith);
65 static const QContactFilter::MatchFlags StringCompareFlags
66         (QContactFilter::MatchFixedString | MatchFunctionFlags);
67
68 static const QUrl fnTrackerId(tracker::iri(QLatin1String("id")));
69
70 ////////////////////////////////////////////////////////////////////////////////////////////////////
71
72 static QContactFilter::MatchFlags
73 matchFunctionFlags(QContactFilter::MatchFlags flags)
74 {
75     return flags & MatchFunctionFlags;
76 }
77
78 static QContactFilter::MatchFlags
79 stringCompareFlags(QContactFilter::MatchFlags flags)
80 {
81     return flags & StringCompareFlags;
82 }
83
84 static RDFFilter
85 matchFunction(QContactDetailFilter::MatchFlags flags, RDFVariable &expr, QVariant value)
86 {
87     static const QUrl
88             contains(QLatin1String("http://www.w3.org/2005/xpath-functions#contains")),
89             startsWith(QLatin1String("http://www.w3.org/2005/xpath-functions#starts-with")),
90             endsWith(QLatin1String("http://www.w3.org/2005/xpath-functions#ends-with"));
91
92     // for case sensitive matching there are specialized functions...
93     if (flags & QContactFilter::MatchCaseSensitive) {
94         switch(matchFunctionFlags(flags)) {
95         case QContactFilter::MatchContains:
96             return expr.function(contains, QTrackerContactQueryBuilder::value(value));
97         case QContactFilter::MatchStartsWith:
98             return expr.function(startsWith, QTrackerContactQueryBuilder::value(value));
99         case QContactFilter::MatchEndsWith:
100             return expr.function(endsWith, QTrackerContactQueryBuilder::value(value));
101         }
102
103         return expr.equal(QTrackerContactQueryBuilder::value(value));
104     }
105
106     // If exact matching is requested for non-string literal still do case sensitive matching,
107     // as case insensitive matching of numbers or URLs doesn't make much sense.
108     if (QVariant::String != value.type() && 0 == stringCompareFlags(flags)) {
109         return expr.equal(QTrackerContactQueryBuilder::value(value));
110     }
111
112     // ...in any other case regular expressions must be used.
113     QString escapedValue(QRegExp::escape(value.toString()));
114
115     switch(matchFunctionFlags(flags)) {
116     case QContactFilter::MatchContains:
117         value.setValue(escapedValue);
118         break;
119     case QContactFilter::MatchStartsWith:
120         value.setValue(QLatin1Char('^') + escapedValue);
121         break;
122     case QContactFilter::MatchEndsWith:
123         value.setValue(escapedValue + QLatin1Char('$'));
124         break;
125     default:
126         value.setValue(QLatin1Char('^') + escapedValue + QLatin1Char('$'));
127         break;
128     }
129
130     return expr.filter(QLatin1String("regex"),
131                        QTrackerContactQueryBuilder::value(value),
132                        LiteralValue(QLatin1String("i")));
133 }
134
135 static RDFFilter
136 lowerRangeFunction(QContactDetailRangeFilter::RangeFlags flags,
137                    const RDFVariable &lhs, const RDFVariable &rhs)
138 {
139     StaticAssert<0 != QContactDetailRangeFilter::ExcludeLower>();
140     StaticAssert<0 == QContactDetailRangeFilter::IncludeLower>();
141
142     if (flags.testFlag(QContactDetailRangeFilter::ExcludeLower)) {
143         return lhs < rhs;
144     }
145
146     return lhs <= rhs;
147 }
148
149 static RDFFilter
150 upperRangeFunction(QContactDetailRangeFilter::RangeFlags flags,
151                    const RDFVariable &lhs, const RDFVariable &rhs)
152 {
153     StaticAssert<0 == QContactDetailRangeFilter::ExcludeUpper>();
154     StaticAssert<0 != QContactDetailRangeFilter::IncludeUpper>();
155
156     if (flags.testFlag(QContactDetailRangeFilter::IncludeUpper)) {
157         return lhs <= rhs;
158     }
159
160     return lhs < rhs;
161 }
162
163 static RDFFilter
164 rangeFunction(QContactDetailRangeFilter::RangeFlags flags,
165               const RDFVariable &expr, const QVariant &minimum, const QVariant &maximum)
166 {
167     return (lowerRangeFunction(flags, QTrackerContactQueryBuilder::value(minimum), expr) &&
168             upperRangeFunction(flags, expr, QTrackerContactQueryBuilder::value(maximum)));
169 }
170
171 static void
172 applyTypeCast(QContactFilter::MatchFlags flags,
173               const QTrackerContactDetailField *field, RDFVariable &object)
174 {
175     if (0 != stringCompareFlags(flags) &&
176         QVariant::String != field->dataType()) {
177         object.metaAssign(object.function<xsd::string>());
178     }
179 }
180
181 static bool
182 applyTypeCast(QContactFilter::MatchFlags flags,
183               const QTrackerContactDetailField *field, QVariant &value)
184 {
185     if (0 != stringCompareFlags(flags)) {
186         return value.convert(QVariant::String);
187     }
188
189     return field->makeValue(value, value);
190 }
191
192 static bool
193 isContext(QTrackerContactQueryBuilder::SubjectRole role)
194 {
195     switch(role) {
196     case QTrackerContactQueryBuilder::HomeContext:
197     case QTrackerContactQueryBuilder::WorkContext:
198         return true;
199
200     case QTrackerContactQueryBuilder::Contact:
201         return false;
202     }
203
204     qctFail("Unexpected subject role");
205     return false;
206 }
207
208 static RDFVariableList
209 makeVariableChain(const RDFVariable &subject, const PropertyInfoList &properties,
210                   const PredicateChain &context = PredicateChain())
211 {
212     RDFVariableList chain(RDFVariableList() << subject);
213
214     foreach(const QUrl &iri, context) {
215         chain.append(chain.last().property(iri));
216     }
217
218     foreach(const PropertyInfoBase &p, properties) {
219         chain.append(p.bindProperty(chain.last(), RDFVariable()));
220     }
221
222     return chain;
223 }
224
225 PredicateChain::PredicateChain(const PropertyInfoList::ConstIterator &first,
226                              const PropertyInfoList::ConstIterator &last,
227                              const PredicateChain &context)
228     : QList<QUrl>(context)
229 {
230     for(PropertyInfoList::ConstIterator i = first; i != last; ++i) {
231         QUrl propertyIri(i->iri());
232
233         if (i->inverse()) {
234             propertyIri.setFragment(propertyIri.fragment() + QLatin1String(";inverse"));
235         }
236
237         append(propertyIri);
238     }
239 }
240
241 ////////////////////////////////////////////////////////////////////////////////////////////////////
242
243 QTrackerContactQueryBuilder::QTrackerContactQueryBuilder(const QTrackerContactDetailSchema &schema)
244     : m_error(QContactManager::NoError), m_schema(schema)
245 {
246     m_context.setSharable(false);
247 }
248
249 QTrackerContactQueryBuilder::~QTrackerContactQueryBuilder()
250 {
251 }
252
253 ////////////////////////////////////////////////////////////////////////////////////////////////////
254
255 QString
256 QTrackerContactQueryBuilder::name(const QString &prefix, const QString &suffix)
257 {
258     if (not suffix.isEmpty()) {
259         return prefix + QLatin1Char('_') + suffix;
260     }
261
262     return prefix;
263 }
264
265 QString
266 QTrackerContactQueryBuilder::name(const RDFVariable &variable, const QString &suffix)
267 {
268     return name(variable.metaIdentifier(), suffix);
269 }
270
271 QString
272 QTrackerContactQueryBuilder::name(const QContactDetail &detail, const QString &suffix)
273 {
274     return name(detail.definitionName(), suffix);
275 }
276
277 QString
278 QTrackerContactQueryBuilder::name(const QTrackerContactDetail &detail, const QString &suffix)
279 {
280     return name(detail.name(), suffix);
281 }
282
283 QString
284 QTrackerContactQueryBuilder::name(const QTrackerContactDetail &detail,
285                                   const QTrackerContactDetailField &field)
286 {
287     return name(detail.name(), field.name());
288 }
289
290 ////////////////////////////////////////////////////////////////////////////////////////////////////
291
292 RDFVariable
293 QTrackerContactQueryBuilder::value(const QVariant &value)
294 {
295     switch(value.type()) {
296     case QVariant::Url:
297         return value.toUrl();
298
299     // Tracker only supports xsd:dateTime, but not xsd:date or xsd:time.
300     // Ideally QtTracker would hide this implementation detail, but actually
301     // it doesn't. Therefore we must take measures here.
302     case QVariant::Date:
303         return LiteralValue(QDateTime(value.toDate(), QTime(), Qt::UTC));
304     case QVariant::Time:
305         return LiteralValue(value.toDateTime());
306
307     default:
308         break;
309     }
310
311     LiteralValue literalValue(value);
312
313     if (literalValue.isValid()) {
314         return literalValue;
315     }
316
317     // store unsupported variant types as plain string
318     return LiteralValue(value.toString());
319 }
320
321 ////////////////////////////////////////////////////////////////////////////////////////////////////
322
323 static void
324 bindMultiValueColumn(RDFSelect &query, const RDFVariable &subject,
325                     const QUrl &propertyIri, const QString &fieldId)
326 {
327     RDFSubSelect subQuery;
328
329     subQuery.addColumn(QString(), // request anonymous name to workaround QtTracker bug
330                        subQuery.pattern().variable(query.pattern().variable(subject)).
331                        property(propertyIri).function(fnTrackerId).
332                        filter(QLatin1String("GROUP_CONCAT"),
333                               LiteralValue(QLatin1String(":"))));
334
335     query.addColumn(fieldId, subQuery.asExpression());
336 }
337
338 static void
339 bindColumn(RDFSelect &query, const QTrackerContactDetailField &field,
340            const RDFVariable &variable, const QString &fieldId)
341 {
342     if (field.isWithoutMapping()) {
343         return;
344     }
345
346     if (not field.restrictsValues()) {
347         query.addColumn(fieldId, variable);
348     } else if (QVariant::StringList == field.dataType()) {
349         bindMultiValueColumn(query, variable, field.propertyChain().last().iri(), fieldId);
350     } else {
351         query.addColumn(fieldId, variable.function(fnTrackerId));
352     }
353 }
354
355 void
356 QTrackerContactQueryBuilder::bindColumns(const QTrackerContactDetail &detail,
357                                          const QTrackerContactDetailField &field,
358                                          RDFSelect &query)
359 {
360     const QString fieldId = name(detail, field);
361
362     if (field.hasSubTypeClasses()) {
363         VariableHash::Iterator entity = lookupVariable(detail.name());
364         bindMultiValueColumn(query, *entity, rdf::type::iri(), fieldId);
365     } else if (field.hasSubTypeProperties()) {
366         foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
367             const QString subTypeId = name(fieldId, pi.text());
368             const QString columnName = name(subTypeId, QLatin1String("IsBound"));
369             query.addColumn(columnName, lookupVariable(subTypeId)->isBound());
370         }
371     } else if (field.hasPropertyChain()) {
372         if (detail.isUnique() && 1 == field.propertyChain().count()) {
373             // use property function to bind simple unique details
374             RDFVariable &contact(query.columns().first().variable());
375             RDFFilter filter(contact.function(field.propertyChain().first().iri()));
376             bindColumn(query, field, filter, fieldId);
377         } else if (field.allowsMultipleValues()) {
378             bindColumn(query, field, *lookupVariable(detail.name()), fieldId);
379         } else {
380             bindColumn(query, field, *lookupVariable(fieldId), fieldId);
381         }
382     }
383
384     if (field.permitsCustomValues()) {
385         RDFSubSelect subQuery;
386
387         RDFVariable subject = *lookupVariable(detail.name());
388         RDFVariable property = subQuery.variable(subject).property<nao::hasProperty>();
389         property.property<nao::propertyName>().merge(LiteralValue(field.name()));
390         RDFVariable value = property.property<nao::propertyValue>();
391
392         switch(field.dataType()) {
393         case QVariant::List:
394         case QVariant::StringList:
395             subQuery.addColumnAs(value.filter(QLatin1String("GROUP_CONCAT"),
396                                               LiteralValue(QLatin1String(":"))),
397                                  RDFVariable());
398             break;
399
400         default:
401             subQuery.addColumnAs(value, RDFVariable());
402         }
403
404
405         query.addColumn(name(fieldId, QLatin1String("CustomValues")),
406                         subQuery.asExpression());
407     }
408 }
409
410 ////////////////////////////////////////////////////////////////////////////////////////////////////
411
412 void
413 QTrackerContactQueryBuilder::bindFields(const QTrackerContactDetail &detail,
414                                         SubjectRole role, RDFVariable &subject)
415 {
416     if (isContext(role) != detail.hasContext()) {
417         return;
418     }
419
420     if (detail.isUnique() && detail.hasContext()) {
421         qctWarn(QString::fromLatin1("Unique detail %1 cannot have a context").arg(detail.name()));
422         return;
423     }
424
425     foreach(const QTrackerContactDetailField &field, detail.fields()) {
426         if (field.hasSubTypes()) {
427             bindSubTypes(detail, field, subject);
428             continue;
429         }
430
431         if (field.isWithoutMapping()) {
432             continue;
433         }
434
435         const QString fieldId = field.allowsMultipleValues() ? detail.name() : name(detail, field);
436         bindProperties(detail, field, subject, *lookupVariable(fieldId));
437     }
438
439     foreach(const QTrackerContactDetailField &field, detail.fields()) {
440         if (field.hasDetailUri()) {
441             bindDetailUri(detail, field);
442         }
443     }
444 }
445
446 void
447 QTrackerContactQueryBuilder::bindFields(const QTrackerContactDetail &detail, RDFSelect &query)
448 {
449     RDFSelect::Columns &columns(query.columns());
450
451     if (columns.isEmpty()) {
452         qctWarn("Cannot find any columns. There should be at least contact IRI and local id.");
453         return;
454     }
455
456     // create entity column
457     if (detail.hasSubjectScheme()) {
458         VariableHash::Iterator subject = lookupVariable(name(detail));
459         query.addColumn(subject->metaIdentifier(), *subject);
460     }
461
462     // bind fields to RDF properties
463     RDFVariable &contact(columns.first().variable());
464     VariableHash::Iterator affiliation = m_variables.end();
465
466     if (detail.hasContext()) {
467         // create a union binding them...
468
469         // ...to the contact...
470         RDFVariable homeContext(contact.child());
471         bindFields(detail, HomeContext, homeContext);
472
473         if (QContactManager::NoError != error()) {
474             return;
475         }
476
477         // ...and the contact's affiliation for details with context field
478         RDFVariable workContext(homeContext.union_());
479
480         affiliation = lookupVariable(QLatin1String("affiliation"));
481         affiliation->merge(workContext.property<nco::hasAffiliation>());
482
483         m_context.append(nco::hasAffiliation::iri());
484         m_properties.insert(m_context, *affiliation);
485         bindFields(detail, WorkContext, *affiliation);
486         m_properties.take(m_context);
487         m_context.takeLast();
488
489         // cache this bound affiliation
490         PredicateVariableHash::Iterator pi = m_properties.find(WorkContextChain);
491
492         if (pi == m_properties.end()) {
493             pi = m_properties.insert(WorkContextChain, *affiliation);
494         } else {
495             pi->merge(*affiliation);
496         }
497     } else {
498         // only bind them to the contact for details without context field
499         bindFields(detail, Contact, contact);
500     }
501
502     // finally create columns to project the graph
503     foreach(const QTrackerContactDetailField &field, detail.fields()) {
504         bindColumns(detail, field, query);
505     }
506
507     if (affiliation != m_variables.end()) {
508         query.addColumn(name(*affiliation, QLatin1String("IsBound")), affiliation->isBound());
509     }
510 }
511
512 ////////////////////////////////////////////////////////////////////////////////////////////////////
513
514 void
515 QTrackerContactQueryBuilder::bindProperties(const QTrackerContactDetail &detail,
516                                             const QTrackerContactDetailField &field,
517                                             RDFVariable subject, RDFVariable &object)
518 {
519     // simple properties of unique details are bound via property function in bindColumns()
520     if (detail.isUnique() && 1 == field.propertyChain().count()) {
521         return;
522     }
523
524     // the local variable chain
525     RDFVariableList localVariables;
526     localVariables.append(detail.isUnique() ? subject.optional() : subject);
527
528     // the property chain
529     PredicateChain chain(m_context);
530
531     // don't bind last property of multi-value instance properties (flags)
532     PropertyInfoList::ConstIterator end = field.propertyChain().end();
533
534     if (field.allowsMultipleValues()) {
535         --end;
536     }
537
538     // finally bind the properties
539     for(PropertyInfoList::ConstIterator pi = field.propertyChain().begin(); pi != end; ++pi) {
540         // create optional pattern when needed
541         if (pi->optional()) {
542             localVariables.append(localVariables.last().optional());
543         }
544
545         // figure out the property's IRI
546         QUrl propertyIri(pi->iri());
547
548         if (pi->inverse()) {
549             propertyIri.setFragment(propertyIri.fragment() + QLatin1String(";inverse"));
550         }
551
552         // figure out if we already have a variable for this property edge
553         chain.append(propertyIri);
554
555         const PredicateVariableHash::ConstIterator p(m_properties.find(chain));
556
557         if (p != m_properties.end()) {
558             localVariables.append(p.value());
559             continue;
560         }
561
562         // create a new property edge
563         localVariables.append(pi->bindProperty(localVariables.last()));
564         m_properties.insert(chain, localVariables.last());
565     }
566
567     object.merge(localVariables.last());
568 }
569
570 void
571 QTrackerContactQueryBuilder::bindDetailUri(const QTrackerContactDetail &detail,
572                                            const QTrackerContactDetailField &field)
573 {
574     if (not field.hasDetailUri()) {
575         qctWarn(QString::fromLatin1("Field %2 of %1 detail doesn't have a detail URI").
576                 arg(detail.name(), field.name()));
577         return;
578     }
579
580     VariableHash::ConstIterator detailSubject(m_variables.find(detail.name()));
581
582     if (m_variables.end() == detailSubject) {
583         const QTrackerContactDetailField *const subjectField(detail.subjectField());
584
585         if (0 != subjectField) {
586             detailSubject = m_variables.find(name(detail, *subjectField));
587         }
588     }
589
590     if (m_variables.end() == detailSubject) {
591         qctWarn(QString::fromLatin1("Cannot find detail URI column for %2 field of %1 detail in %3").
592                 arg(detail.name(), field.name(), QStringList(m_variables.keys()).join(QLatin1String(", "))));
593         return;
594     }
595
596     // create and cache property chain when needed
597     if (not detail.isUnique() || field.propertyChain().count() > 1) {
598         const PredicateChain chain(field.propertyChain().begin(),
599                                    field.propertyChain().end() - 1,
600                                    m_context);
601
602         const PredicateVariableHash::ConstIterator p(m_properties.find(chain));
603
604         if (m_properties.end() == p) {
605             qctWarn(QString::fromLatin1("Cannot find property chain for URI column of %1 detail's %2 field").
606                     arg(detail.name(), field.name()));
607             return;
608         }
609
610         detailSubject->merge(*p);
611     }
612 }
613
614 void
615 QTrackerContactQueryBuilder::bindSubTypes(const QTrackerContactDetail &detail,
616                                           const QTrackerContactDetailField &field,
617                                           RDFVariable &subject)
618 {
619     const QString fieldId = name(detail, field);
620
621     if (field.hasSubTypeProperties()) {
622 #if 0 // NB#179134
623         RDFSubSelect subSelect(subject.pattern().subQuery());
624         RDFVariable innerSubject(subSelect.pattern().variable(subject));
625 #endif
626
627         foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
628             const QString subTypeId = name(fieldId, pi.text());
629             VariableHash::ConstIterator subType = lookupVariable(subTypeId);
630
631 #if 0 // NB#179134
632             RDFVariable innerVar(subSelect.pattern().variable(*subType));
633             innerVar.merge(innerSubject.function(subType->iri()));
634             subSelect.addColumn(innerVar);
635 #else
636             subject.optional().property(pi.iri()).merge(*subType);
637 #endif
638         }
639     }
640 }
641
642 ////////////////////////////////////////////////////////////////////////////////////////////////////
643
644 template <typename T> static bool
645 isCompoundFilterSupported(const QContactFilter &filter)
646 {
647     foreach(const QContactFilter &childFilter, static_cast<const T&>(filter).filters()) {
648         if (not QTrackerContactQueryBuilder::isFilterSupported(childFilter)) {
649             return false;
650         }
651     }
652
653     return true;
654 }
655
656 static bool
657 isCanonicalFilterSupported(const QContactFilter &filter)
658 {
659     switch(filter.type()) {
660     case QContactFilter::DefaultFilter:
661     case QContactFilter::LocalIdFilter:
662     case QContactFilter::ContactDetailFilter:
663     case QContactFilter::ContactDetailRangeFilter:
664     case QContactFilter::ChangeLogFilter:
665     case QContactFilter::RelationshipFilter:
666         return true;
667
668     case QContactFilter::IntersectionFilter:
669         return isCompoundFilterSupported<QContactIntersectionFilter>(filter);
670     case QContactFilter::UnionFilter:
671         return isCompoundFilterSupported<QContactUnionFilter>(filter);
672
673     case QContactFilter::ActionFilter:
674     case QContactFilter::InvalidFilter:
675         break;
676     }
677
678     return false;
679 }
680
681 bool
682 QTrackerContactQueryBuilder::isFilterSupported(const QContactFilter &filter)
683 {
684     return isCanonicalFilterSupported(QContactManagerEngine::canonicalizedFilter(filter));
685 }
686
687 ////////////////////////////////////////////////////////////////////////////////////////////////////
688
689 #define DO_ENUM_VALUE(Type) \
690     case Type: return QLatin1String(#Type)
691
692 static QString
693 filterName(QContactFilter::FilterType type)
694 {
695     switch(type) {
696         DO_ENUM_VALUE(QContactFilter::InvalidFilter);
697         DO_ENUM_VALUE(QContactFilter::ContactDetailFilter);
698         DO_ENUM_VALUE(QContactFilter::ContactDetailRangeFilter);
699         DO_ENUM_VALUE(QContactFilter::ChangeLogFilter);
700         DO_ENUM_VALUE(QContactFilter::ActionFilter);
701         DO_ENUM_VALUE(QContactFilter::RelationshipFilter);
702         DO_ENUM_VALUE(QContactFilter::IntersectionFilter);
703         DO_ENUM_VALUE(QContactFilter::UnionFilter);
704         DO_ENUM_VALUE(QContactFilter::LocalIdFilter);
705         DO_ENUM_VALUE(QContactFilter::DefaultFilter);
706     }
707
708     return QString::fromLatin1("QContactFilter::FilterType(%1)").arg(type);
709 }
710
711 static QString
712 eventName(QContactChangeLogFilter::EventType type)
713 {
714     switch(type) {
715         DO_ENUM_VALUE(QContactChangeLogFilter::EventAdded);
716         DO_ENUM_VALUE(QContactChangeLogFilter::EventChanged);
717         DO_ENUM_VALUE(QContactChangeLogFilter::EventRemoved);
718     }
719
720     return QString::fromLatin1("QContactChangeLogFilter::EventType(%1)").arg(type);
721 }
722
723 #undef DO_ENUM_VALUE
724
725 ////////////////////////////////////////////////////////////////////////////////////////////////////
726
727 static bool
728 isEmpty(const QVariant &value)
729 {
730     if (value.isNull()) {
731         return true;
732     }
733
734     switch(value.type()) {
735     case QVariant::Bool:
736     case QVariant::Int:
737     case QVariant::UInt:
738     case QVariant::LongLong:
739     case QVariant::ULongLong:
740     case QVariant::Double:
741     case QVariant::Char:
742     case QVariant::Locale:
743     case QVariant::Rect:
744     case QVariant::RectF:
745     case QVariant::Size:
746     case QVariant::SizeF:
747     case QVariant::Line:
748     case QVariant::LineF:
749     case QVariant::Point:
750     case QVariant::PointF:
751         return false;
752
753     case QVariant::String:
754         return value.toString().isEmpty();
755     case QVariant::RegExp:
756         return value.toRegExp().isEmpty();
757     case QVariant::Url:
758         return value.toUrl().isEmpty();
759
760     case QVariant::Date:
761         return not value.toDate().isValid();
762     case QVariant::Time:
763         return not value.toTime().isValid();
764     case QVariant::DateTime:
765         return not value.toDateTime().isValid();
766
767     case QVariant::ByteArray:
768         return value.toByteArray().isEmpty();
769     case QVariant::BitArray:
770         return value.toBitArray().isEmpty();
771     case QVariant::List:
772         return value.toList().isEmpty();
773     case QVariant::Hash:
774         return value.toHash().isEmpty();
775     case QVariant::Map:
776         return value.toMap().isEmpty();
777     case QVariant::StringList:
778         return value.toStringList().isEmpty();
779
780     default:
781         break;
782     }
783
784     return value.toString().isEmpty();
785 }
786
787 template<class T>
788 QContactUnionFilter buildWildcardFilter(const T &filter, const QTrackerContactDetail &detail)
789 {
790     QContactUnionFilter unionFilter;
791
792     foreach(const QTrackerContactDetailField &field, detail.fields()) {
793         T fieldFilter(filter);
794         fieldFilter.setDetailDefinitionName(detail.name(), field.name());
795         unionFilter.append(fieldFilter);
796     }
797
798     return unionFilter;
799 }
800
801 /*
802  * RDFVariable describes all contacts in tracker before filter is applied.
803  * This method translates QContactFilter to tracker rdf filter. When query to tracker is made
804  * after this method, it would return only contacts that fit the filter.
805  */
806 void
807 QTrackerContactQueryBuilder::bindLocalIdFilter(const QList<QContactLocalId> &localIds,
808                                                RDFVariable &subject)
809 {
810     RDFVariableList contactIriList;
811
812     foreach(const QContactLocalId &localId, localIds) {
813         contactIriList.append(LiteralValue(QString::number(localId)));
814     }
815
816     if (not contactIriList.isEmpty()) {
817         PredicateChain chain(PredicateChain() << m_context << nco::contactLocalUID::iri());
818         PredicateVariableHash::Iterator p(m_properties.find(chain));
819
820         if (p == m_properties.end()) {
821             RDFVariable cid(subject.property<nco::contactLocalUID>());
822             cid.metaSetIdentifier(QLatin1String("cid"));
823             p = m_properties.insert(chain, cid);
824         }
825
826         p->isMemberOf(contactIriList);
827     }
828 }
829
830 ////////////////////////////////////////////////////////////////////////////////////////////////////
831
832 bool
833 QTrackerContactQueryBuilder::bindFilter(const QContactLocalIdFilter &filter,
834                                         RDFVariable &subject)
835 {
836     if (QContactManager::NoError != error()) {
837         return false; // abort early if there already is an error
838     }
839
840     if (filter.ids().isEmpty()) {
841         qctWarn(QString::fromLatin1("%1: Local contact id list cannot be empty").
842                 arg(filterName(filter.type())));
843         m_error = QContactManager::BadArgumentError;
844         return false;
845     }
846
847     // cache the nco:contactLocalUID variable ("cid")
848     PredicateChain chain(PredicateChain() << nco::contactLocalUID::iri());
849     PredicateVariableHash::Iterator p(m_properties.find(chain));
850
851     if (p == m_properties.end()) {
852         p = m_properties.insert(chain, subject.property<nco::contactLocalUID>());
853         p->metaSetIdentifier("cid");
854     }
855
856     // build the filter
857     bindLocalIdFilter(filter.ids(), subject);
858
859     return true;
860 }
861
862 bool
863 QTrackerContactQueryBuilder::bindFilter(const QContactIntersectionFilter &filter,
864                                         RDFVariable &subject)
865 {
866     if (QContactManager::NoError != error()) {
867         return false; // abort early if there already is an error
868     }
869
870     QTrackerContactQueryBuilder builder(m_schema);
871     RDFVariable child = subject.child();
872     bool restrictionsAdded = false;
873
874     foreach(const QContactFilter &childFilter, filter.filters()) {
875         restrictionsAdded |= builder.bindFilter(childFilter, child);
876
877         if (QContactManager::NoError != error()) {
878             return false;
879         }
880     }
881
882     return restrictionsAdded;
883 }
884
885 bool
886 QTrackerContactQueryBuilder::bindFilter(const QContactUnionFilter &filter,
887                                         RDFVariable &subject)
888 {
889     if (QContactManager::NoError != error()) {
890         return false; // abort early if there already is an error
891     }
892
893     RDFVariable child = subject.child();
894     bool restrictionsAdded = false;
895
896     foreach(const QContactFilter &childFilter, filter.filters()) {
897         QTrackerContactQueryBuilder builder(m_schema);
898
899         restrictionsAdded |= builder.bindFilter(childFilter, child);
900
901         if (QContactManager::NoError != error()) {
902             return false;
903         }
904
905         child.metaAssign(child.union_());
906     }
907
908     return restrictionsAdded;
909 }
910
911 bool
912 QTrackerContactQueryBuilder::bindFilter(const QContactDetailFilter &filter,
913                                         RDFVariable &subject)
914 {
915     if (QContactManager::NoError != error()) {
916         return false; // abort early if there already is an error
917     }
918
919     // check arguments
920     QContactFilter::MatchFlags matchFlags(filter.matchFlags());
921     QVariant value(filter.value());
922
923     if (isEmpty(value)) {
924         // FIXME: null value must be handled as wildcard value
925         qctWarn(QString::fromLatin1("%1: Wildcard value is not supported yet").
926                 arg(filterName(filter.type())));
927         m_error = QContactManager::BadArgumentError;
928         return false;
929     }
930
931     static const uint unsupportedMatchFlags =
932             // FIXME: add support for those flags
933             QContactDetailFilter::MatchKeypadCollation;
934
935     if (matchFlags & unsupportedMatchFlags) {
936         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
937                 arg(filterName(filter.type())).
938                 arg(matchFlags & unsupportedMatchFlags));
939     }
940
941     // find detail and field definition
942     const QTrackerContactDetail *const detail =
943             findDetail(filter.detailDefinitionName());
944
945     if (0 == detail) {
946         return false;
947     }
948
949     if (filter.detailFieldName().isEmpty()) {
950         return bindFilter(buildWildcardFilter(filter, *detail), subject);
951     }
952
953     const QTrackerContactDetailField *const field =
954             findField(*detail, filter.detailFieldName());
955
956     if (0 == field) {
957         return false;
958     }
959
960     // bind detail variable
961     PropertyInfoList properties(field->propertyChain());
962
963     if (properties.isEmpty()) {
964         // Nothing to do for this field, I hope.
965         // We might have to consider subtype classes and properties.
966         return false;
967     }
968
969     // do special processing for phone numbers
970     // FIXME: find a more generic pattern for doing this modifications
971     if (properties.last().iri() == nco::phoneNumber::iri()) {
972         if (matchFlags.testFlag(QContactFilter::MatchPhoneNumber)) {
973             matchFlags &= ~MatchFunctionFlags;
974             matchFlags |= QContactFilter::MatchEndsWith;
975
976             properties.takeLast();
977             properties.append(field->computedProperties().first());
978
979             // this hack only works for maemo:localPhoneNumber
980             Q_ASSERT(properties.last().iri() == maemo::localPhoneNumber::iri());
981         }
982
983         if (QContactFilter::MatchEndsWith == matchFunctionFlags(matchFlags)) {
984             const int suffixLength(QTrackerContactSettings().localPhoneNumberLength());
985             value.setValue(value.toString().right(suffixLength));
986         }
987     }
988
989     // try casting the filter value
990     if (not applyTypeCast(matchFlags, field, value)) {
991         qctWarn(QString::fromLatin1("%1: Cannot apply required casts to filter value "
992                                     "for %3 field of %2 detail").
993                 arg(filterName(filter.type()), detail->name(), field->name()));
994         m_error = QContactManager::BadArgumentError;
995         return false;
996     }
997
998     // glue everything together
999     if (detail->hasContext()) {
1000         RDFVariable child(subject.child());
1001         RDFVariable home(makeVariableChain(child, properties).last());
1002         applyTypeCast(matchFlags, field, home);
1003         matchFunction(matchFlags, home, value);
1004
1005         RDFVariable work(makeVariableChain(child.union_(), properties, WorkContextChain).last());
1006         applyTypeCast(matchFlags, field, work);
1007         matchFunction(matchFlags, work, value);
1008     } else {
1009         RDFVariable expr(makeVariableChain(subject, properties).last());
1010         applyTypeCast(matchFlags, field, expr);
1011         matchFunction(matchFlags, expr, value);
1012     }
1013
1014     return true;
1015 }
1016
1017 bool
1018 QTrackerContactQueryBuilder::bindFilter(const QContactDetailRangeFilter &filter,
1019                                         RDFVariable &subject)
1020 {
1021     if (QContactManager::NoError != error()) {
1022         return false; // abort early if there already is an error
1023     }
1024
1025     // check arguments
1026     QVariant minimum = filter.minValue();
1027     QVariant maximum = filter.maxValue();
1028
1029     if (minimum.isNull() || maximum.isNull()) {
1030         // FIXME: null value must be handled as wildcard value
1031         qctWarn(QString::fromLatin1("%1: Wildcards for range values are not supported yet").
1032                 arg(filterName(filter.type())));
1033         m_error = QContactManager::BadArgumentError;
1034         return false;
1035     }
1036
1037     static const uint unsupportedMatchFlags = ~(QContactDetailFilter::MatchFixedString);
1038
1039     if (0 != (filter.matchFlags() & unsupportedMatchFlags)) {
1040         // QTMOBILITY-222 - Match flags cannot be implemented for range filter
1041         qctWarn(QString::fromLatin1("%1: Ignoring nonapplyable match flags: %2").
1042                 arg(filterName(filter.type())).arg(filter.matchFlags() & unsupportedMatchFlags));
1043     }
1044
1045     // find detail and field definition
1046     const QTrackerContactDetail *const detail = findDetail(filter.detailDefinitionName());
1047
1048     if (0 == detail) {
1049         return false;
1050     }
1051
1052     if (filter.detailFieldName().isEmpty()) {
1053         return bindFilter(buildWildcardFilter(filter, *detail), subject);
1054     }
1055
1056     const QTrackerContactDetailField *const field = findField(*detail, filter.detailFieldName());
1057
1058     if (0 == field) {
1059         return false;
1060     }
1061
1062     // try casting the filter value
1063     if (not applyTypeCast(filter.matchFlags(), field, minimum)) {
1064         qctWarn(QString::fromLatin1("%1: Cannot cast minimum value to required type: %2").
1065                 arg(filterName(filter.type()), QVariant::typeToName(field->dataType())));
1066         m_error = QContactManager::BadArgumentError;
1067         return false;
1068     }
1069
1070     if (not applyTypeCast(filter.matchFlags(), field, maximum)) {
1071         qctWarn(QString::fromLatin1("%1: Cannot cast maximum value to required type: %2").
1072                 arg(filterName(filter.type()), QVariant::typeToName(field->dataType())));
1073         m_error = QContactManager::BadArgumentError;
1074         return false;
1075     }
1076
1077     // glue everything together
1078     if (detail->hasContext()) {
1079         RDFVariable child(subject.child());
1080         RDFVariable home(makeVariableChain(child, field->propertyChain()).last());
1081         applyTypeCast(filter.matchFlags(), field, home);
1082         rangeFunction(filter.rangeFlags(), home, minimum, maximum);
1083
1084         RDFVariable work(makeVariableChain(child.union_(), field->propertyChain(), WorkContextChain).last());
1085         applyTypeCast(filter.matchFlags(), field, work);
1086         rangeFunction(filter.rangeFlags(), work, minimum, maximum);
1087     } else {
1088         RDFVariable expr(makeVariableChain(subject, field->propertyChain()).last());
1089         applyTypeCast(filter.matchFlags(), field, expr);
1090         rangeFunction(filter.rangeFlags(), expr, minimum, maximum);
1091     }
1092
1093     return true;
1094 }
1095
1096 bool
1097 QTrackerContactQueryBuilder::bindFilter(const QContactChangeLogFilter &filter,
1098                                         RDFVariable &subject)
1099 {
1100     if (QContactManager::NoError != error()) {
1101         return false; // abort early if there already is an error
1102     }
1103
1104     QScopedPointer<RDFFilter> eventFilter;
1105
1106     switch(filter.eventType()) {
1107     case QContactChangeLogFilter::EventAdded:
1108         eventFilter.reset(new RDFFilter(subject.property<nie::contentCreated>().
1109                                         greaterOrEqual(value(filter.since()))));
1110         break;
1111
1112     case QContactChangeLogFilter::EventChanged:
1113         eventFilter.reset(new RDFFilter(subject.property<nie::contentLastModified>().
1114                                         greaterOrEqual(value(filter.since()))));
1115         break;
1116
1117     case QContactChangeLogFilter::EventRemoved:
1118         break;
1119     }
1120
1121     if (eventFilter.isNull()) {
1122         qctWarn(QString::fromLatin1("%1: Unsupported event type: %2").
1123                 arg(filterName(filter.type()), eventName(filter.eventType())));
1124         m_error = QContactManager::NotSupportedError;
1125         return false;
1126     }
1127
1128     return true;
1129 }
1130
1131 bool
1132 QTrackerContactQueryBuilder::bindFilter(const QContactRelationshipFilter& filter,
1133                                         RDFVariable& subject)
1134 {
1135     if (QContactManager::NoError != error()) {
1136         return false; // abort early if there already is an error
1137     }
1138
1139     bool isFilterCreated = false;
1140
1141     if (filter.relationshipType() == QContactRelationship::HasMember) {
1142         const QContactId relatedContactId = filter.relatedContactId();
1143         if (true) { //relatedContactId.managerUri() == QLatin1String("social") ) { //TODO: need to get actual engine name here
1144             LiteralValue rdfLocalUid = QString::number(relatedContactId.localId());
1145
1146             const QContactRelationship::Role relatedContactRole = filter.relatedContactRole();
1147
1148             RDFVariable firstFilterSubject;
1149             if (relatedContactRole == QContactRelationship::Either) {
1150                 firstFilterSubject = subject.child();
1151             } else {
1152                 firstFilterSubject = subject;
1153             }
1154
1155             // filter for all contacts in a given group?
1156             if (relatedContactRole == QContactRelationship::First
1157                 || relatedContactRole == QContactRelationship::Either) {
1158                 RDFVariable rdfGroupContact;
1159                 rdfGroupContact.isOfType<nco::Contact>();
1160                 rdfGroupContact.isOfType<nco::ContactGroup>();
1161                 rdfGroupContact.property<nco::contactLocalUID>().equal(rdfLocalUid);
1162                 firstFilterSubject.property<nco::belongsToGroup>() = rdfGroupContact;
1163             }
1164
1165             RDFVariable secondFilterSubject;
1166             if (relatedContactRole == QContactRelationship::Either) {
1167                 secondFilterSubject = firstFilterSubject.union_();
1168             } else {
1169                 secondFilterSubject = subject;
1170             }
1171
1172             // filter for all groups a given contact is in?
1173             if (relatedContactRole == QContactRelationship::Second
1174                 || relatedContactRole == QContactRelationship::Either) {
1175                 RDFVariable rdfContact;
1176                 rdfContact.isOfType<nco::Contact>(); // not PersonContact, groups could also be part of other groups
1177                 rdfContact.property<nco::contactLocalUID>().equal(rdfLocalUid);
1178                 rdfContact.property<nco::belongsToGroup>() = secondFilterSubject;
1179             }
1180             isFilterCreated = true;
1181         }
1182     }
1183
1184     if (not isFilterCreated) {
1185         qctWarn(QString::fromLatin1("%1: Unsupported relationship type: %2").
1186                 arg(filterName(filter.type()), filter.relationshipType()));
1187         m_error = QContactManager::NotSupportedError;
1188         return false;
1189     }
1190
1191     return true;
1192 }
1193
1194
1195
1196 bool
1197 QTrackerContactQueryBuilder::bindFilter(const QContactFilter &filter,
1198                                         RDFVariable &subject)
1199 {
1200     if (QContactManager::NoError != error()) {
1201         return false; // abort early if there already is an error
1202     }
1203
1204     QContactFilter canonicalFilter(QContactManagerEngine::canonicalizedFilter(filter));
1205
1206     switch(canonicalFilter.type()) {
1207     case QContactFilter::LocalIdFilter:
1208         return bindFilter(static_cast<const QContactLocalIdFilter&>(canonicalFilter), subject);
1209     case QContactFilter::IntersectionFilter:
1210         return bindFilter(static_cast<const QContactIntersectionFilter&>(canonicalFilter), subject);
1211     case QContactFilter::UnionFilter:
1212         return bindFilter(static_cast<const QContactUnionFilter&>(canonicalFilter), subject);
1213     case QContactFilter::ContactDetailFilter:
1214         return bindFilter(static_cast<const QContactDetailFilter&>(canonicalFilter), subject);
1215     case QContactFilter::ContactDetailRangeFilter:
1216         return bindFilter(static_cast<const QContactDetailRangeFilter&>(canonicalFilter), subject);
1217     case QContactFilter::ChangeLogFilter:
1218         return bindFilter(static_cast<const QContactChangeLogFilter&>(canonicalFilter), subject);
1219     case QContactFilter::RelationshipFilter:
1220         return bindFilter(static_cast<const QContactRelationshipFilter&>(canonicalFilter), subject);
1221     case QContactFilter::DefaultFilter:
1222         // We didn't apply any explicit restrictions, but the purpose of this filter is to
1223         // apply no restrictions at all. Therefore all restrictions implied by this filter
1224         // have been applied and we return true.
1225         return true;
1226
1227     case QContactFilter::InvalidFilter:
1228         if (isCanonicalFilterSupported(filter)) {
1229             qctWarn(QString::fromLatin1("%1: Bad arguments").arg(filterName(filter.type())));
1230             m_error = QContactManager::BadArgumentError;
1231             return false;
1232         }
1233
1234         break;
1235
1236     case QContactFilter::ActionFilter:
1237         break;
1238     }
1239
1240     qctWarn(QString::fromLatin1("%1: Unsupported filter type").arg(filterName(filter.type())));
1241     m_error = QContactManager::NotSupportedError;
1242     return false;
1243 }
1244
1245 ////////////////////////////////////////////////////////////////////////////////////////////////////
1246
1247 const QTrackerContactDetail *
1248 QTrackerContactQueryBuilder::findDetail(const QString &detailName)
1249 {
1250     const QTrackerContactDetail *detail = m_schema.detail(detailName);
1251
1252     if (0 == detail) {
1253         qctWarn(QString::fromLatin1("Unsupported detail %1").arg(detailName));
1254     }
1255
1256     return detail;
1257 }
1258
1259
1260 const QTrackerContactDetailField *
1261 QTrackerContactQueryBuilder::findField(const QTrackerContactDetail &detail,
1262                                        const QString &fieldName)
1263 {
1264     const QTrackerContactDetailField *field = detail.field(fieldName);
1265
1266     if (0 == field) {
1267         qctWarn(QString::fromLatin1("Unsupported field %2 for %1 detail").
1268                 arg(detail.name(), fieldName));
1269         m_error = QContactManager::NotSupportedError;
1270     }
1271
1272     return field;
1273 }
1274
1275 VariableHash::Iterator
1276 QTrackerContactQueryBuilder::lookupVariable(const QString &name)
1277 {
1278     VariableHash::Iterator var = m_variables.find(name);
1279
1280     if (var == m_variables.end()) {
1281         var = m_variables.insert(name, RDFVariable(name));
1282     }
1283
1284     return var;
1285 }