Changes: remove unused "QMap<QUrl, Select> selects"
[qtcontacts-tracker:qtcontacts-tracker.git] / src / dao / scalarquerybuilder.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 "scalarquerybuilder.h"
43
44 #include "contactdetailschema.h"
45 #include "logger.h"
46 #include "resourceinfo.h"
47 #include "support.h"
48
49 #include <dao/cubidefines.h>
50 #include <dao/ontologies/maemo.h>
51 #include <lib/settings.h>
52
53 ////////////////////////////////////////////////////////////////////////////////////////////////////
54
55 CUBI_USE_NAMESPACE
56 CUBI_USE_NAMESPACE_RESOURCES
57
58 ////////////////////////////////////////////////////////////////////////////////////////////////////
59
60 template<bool> class StaticAssert;
61 template<> class StaticAssert<true> {};
62
63 ////////////////////////////////////////////////////////////////////////////////////////////////////
64
65 static const QContactFilter::MatchFlags MatchFunctionFlags
66         (QContactFilter::MatchExactly | QContactFilter::MatchContains |
67          QContactFilter::MatchStartsWith | QContactFilter::MatchEndsWith);
68 static const QContactFilter::MatchFlags StringCompareFlags
69         (QContactFilter::MatchFixedString | MatchFunctionFlags);
70
71 ////////////////////////////////////////////////////////////////////////////////////////////////////
72
73 QTrackerScalarContactQueryBuilder::QTrackerScalarContactQueryBuilder(const QTrackerContactDetailSchema &schema,
74                                                                      const QString &managerUri)
75     : m_error(QContactManager::NoError)
76     , m_schema(schema)
77     , m_managerUri(managerUri)
78 {
79 }
80
81 QTrackerScalarContactQueryBuilder::~QTrackerScalarContactQueryBuilder()
82 {
83 }
84
85 const Variable &
86 QTrackerScalarContactQueryBuilder::contact()
87 {
88     static const Variable s(QLatin1String("contact"));
89     return s;
90 }
91
92 const Variable &
93 QTrackerScalarContactQueryBuilder::context()
94 {
95     static const Variable c(QLatin1String("context"));
96     return c;
97 }
98
99 const QChar
100 QTrackerScalarContactQueryBuilder::detailSeparator()
101 {
102     static const QChar separator(0x1E);
103     return separator;
104 }
105
106 const QChar
107 QTrackerScalarContactQueryBuilder::fieldSeparator()
108 {
109     static const QChar separator(0x1F);
110     return separator;
111 }
112
113 const QChar
114 QTrackerScalarContactQueryBuilder::listSeparator()
115 {
116     static const QChar separator(0x1D);
117     return separator;
118 }
119
120 ////////////////////////////////////////////////////////////////////////////////////////////////////
121
122 /// Returns a chain of predicate functions from the property @p chain on @p subject.
123 /// If @p withGraph is @c true, the last predicate function will be configured to also deliver the graph.
124 static Function
125 predicateFunctionChain(const PropertyInfoList& chain, const Variable &subject)
126 {
127     if (chain.empty()) {
128         return Function();
129     }
130
131     PropertyInfoList::ConstIterator it = chain.constBegin();
132
133     Function current = PredicateFunction(it->iri()).apply(subject);
134
135     for(it++; it != chain.constEnd(); it++) {
136         current = PredicateFunction(it->iri()).apply(current);
137     }
138
139     return current;
140 }
141
142 static ValueChain
143 intercalate(const ValueChain &list, const Value &inter)
144 {
145     ValueChain result = list;
146
147     for(int i = list.size() - 1; i > 0; --i) {
148         result.insert(i, inter);
149     }
150
151     return result;
152 }
153
154 void
155 QTrackerScalarContactQueryBuilder::bindUniqueDetailField(const QTrackerContactDetailField &field,
156                                                          Select &query)
157 {
158     Variable subject = contact();
159
160     PropertyInfoList chain = field.propertyChain();
161     PropertyInfoList detailUriChain;
162
163     if (chain.first().iri() == nco::hasAffiliation::iri()) {
164         chain.removeFirst();
165         subject = context();
166     }
167
168     if (field.hasDetailUri()) {
169         PropertyInfoList::ConstIterator it = chain.constBegin();
170         for(; it != chain.constEnd(); ++it) {
171             detailUriChain.append(*it);
172
173             if (it->hasDetailUri()) {
174                 break;
175             }
176         }
177
178         // If property was not found, it was the nco:hasAffiliation we might
179         // have deleted from the chain just before, and is already fetched in
180         // the base query
181         if (it == chain.constEnd()) {
182             detailUriChain.clear();
183         }
184
185         if (not detailUriChain.empty()) {
186             if (detailUriChain.isSingleValued()) {
187                 query.addProjection(predicateFunctionChain(detailUriChain, subject));
188             } else {
189                 qctWarn(QString::fromLatin1("DetailUri for field %1 on non single"
190                                             "valued property is not supported").
191                         arg(field.name()));
192                 query.addProjection(LiteralValue(QString()));
193             }
194         }
195     }
196
197     if (field.hasSubTypeClasses()) {
198         qctWarn(QString::fromLatin1("Subtypes by class not supported for field %1").
199                 arg(field.name()));
200     }
201
202     if (field.hasSubTypeProperties()) {
203         qctWarn(QString::fromLatin1("Subtypes by property not supported for field %1").
204                 arg(field.name()));
205     }
206
207     if (chain.isSingleValued(true) && not field.isWithoutMapping()) {
208         Function p = predicateFunctionChain(chain, subject);
209
210         if (field.restrictsValues() && not field.allowableInstances().empty()) {
211             p = Functions::trackerId.apply(p);
212         }
213
214         query.addProjection(p);
215     } else {
216         // FIXME: we don't handle inverse properties nor detailUri here
217
218         if (field.isWithoutMapping()) {
219             // Because we append a mutlivalued property at the end of the
220             // chain, we know that no predicate functions will be generated
221             chain.append(PropertyInfo<nao::hasProperty>());
222         }
223
224         // Walk the chain backwards to find the largest part we can bind
225         // with predicate functions
226         PropertyInfoList::ConstIterator it = chain.constEnd();
227         PropertyInfoList projectionChain;
228         ValueChain restrictionPredicates;
229
230         while(it != chain.begin()) {
231             it--;
232
233             if (it->singleValued()) {
234                 projectionChain.append(*it);
235             } else {
236                 it ++;
237                 break;
238             }
239         }
240
241         while(it != chain.begin()) {
242             it--;
243             restrictionPredicates.push_front(ResourceValue(it->iri()));
244         }
245
246         Select s;
247         Variable last;
248         s.addRestriction(subject, restrictionPredicates, last);
249
250         Function projection = predicateFunctionChain(projectionChain, last);
251
252         if (projection.isValid()) {
253             if (not field.allowableInstances().empty()) {
254                 projection = Functions::trackerId.apply(projection);
255
256             }
257
258             projection = Functions::groupConcat.apply(projection, LiteralValue(listSeparator()));
259
260             s.addProjection(projection);
261         } else {
262             if (not field.isWithoutMapping()) {
263                 s.addProjection(last);
264             } else {
265                 s.addRestriction(last, nao::propertyName::resource(), LiteralValue(field.name()));
266                 s.addProjection(nao::propertyValue::function().apply(last));
267             }
268         }
269
270         query.addProjection(s);
271     }
272 }
273
274 void
275 QTrackerScalarContactQueryBuilder::bindUniqueDetail(const QTrackerContactDetail &detail,
276                                                     Select &query,
277                                                     const QSet<QString> &fieldFilter)
278 {
279     if (not detail.isUnique()) {
280         qctWarn(QString::fromLatin1("Calling bindUniqueDetail on a non unique detail: %1").
281                 arg(detail.name()));
282         return;
283     }
284
285     foreach(const QTrackerContactDetailField &field, detail.fields()) {
286         if (not fieldFilter.empty() && not fieldFilter.contains(field.name())) {
287             continue;
288         }
289
290         if (not field.hasPropertyChain()) {
291             continue;
292         }
293
294         bindUniqueDetailField(field, query);
295
296         // For subtype properties we bind the field once with each property, and
297         // we'll check what we have when we fetch
298         if (field.hasSubTypeProperties()) {
299             foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
300                 QTrackerContactDetailField f = field;
301
302                 if (f.propertyChain().empty()) {
303                     qctWarn(QString::fromLatin1("Can't apply property subtype without property "
304                                                 "chain for field %1").arg(field.name()));
305                     continue;
306                 }
307
308                 PropertyInfoList chain = f.propertyChain();
309                 chain.removeLast();
310                 chain.append(pi);
311                 f.setPropertyChain(chain);
312
313                 bindUniqueDetailField(field, query);
314             }
315         }
316     }
317 }
318
319 Value
320 QTrackerScalarContactQueryBuilder::bindRestrictedValuesField(const QTrackerContactDetailField &field,
321                                                              const PropertyInfoList &chain,
322                                                              const Value &subject)
323 {
324     Select innerSelect;
325     ValueChain predicates;
326
327     foreach(const PropertyInfoBase& pi, chain) {
328         predicates.append(ResourceValue(pi.iri()));
329     }
330
331     Variable instance;
332
333     // For instances (=resources) we use tracker:id, but for
334     // literal we just retrieve them
335     if (not field.allowableInstances().empty()) {
336         innerSelect.addProjection(Functions::groupConcat.
337                                   apply(Functions::trackerId.apply(instance),
338                                         LiteralValue(listSeparator())));
339     } else {
340         innerSelect.addProjection(Functions::groupConcat.
341                                   apply(instance,
342                                         LiteralValue(listSeparator())));
343     }
344
345     innerSelect.addRestriction(subject, predicates , instance);
346
347     return Filter(innerSelect);
348 }
349
350 Value
351 QTrackerScalarContactQueryBuilder::bindCustomValues(const QTrackerContactDetailField &field,
352                                                     const Cubi::Value &subject)
353 {
354     Select innerSelect;
355     Variable property(QLatin1String("p"));
356     innerSelect.addProjection(Functions::groupConcat.
357                               apply(nao::propertyValue::function().
358                                     apply(property), LiteralValue(listSeparator())));
359     innerSelect.addRestriction(subject, nao::hasProperty::resource(), property);
360     innerSelect.addRestriction(property,
361                                nao::propertyName::resource(),
362                                LiteralValue(field.name()));
363
364     return Filter(innerSelect);
365 }
366
367 Value
368 QTrackerScalarContactQueryBuilder::bindInverseFields(const QList<QTrackerContactDetailField> &fields,
369                                                      const QHash<QString, PropertyInfoList> &fieldChains,
370                                                      const Variable &subject)
371 {
372     ValueChain innerFields;
373     Select innerSelect;
374     QSet<PropertyInfoList> generatedRestrictions;
375     QHash<QUrl, Variable> inverseObjects;
376
377     QList<QTrackerContactDetailField>::ConstIterator field = fields.constEnd();
378     while(field != fields.constBegin()) {
379         field--;
380         PropertyInfoList restrictionChain, projectionChain;
381         PropertyInfoList chain = fieldChains.value(field->name());
382         PropertyInfoList::ConstIterator it = chain.constBegin();
383
384         for(; it != chain.constEnd(); ++it) {
385             if (it->isInverse()) {
386                 break;
387             }
388
389             restrictionChain.append(*it);
390         }
391
392         if (it == chain.constEnd()) {
393             // What? hasInverseProperty returned true and we didn't find one?
394             continue;
395         }
396
397         Variable last = subject;
398         Variable inverseObject;
399
400         if (not generatedRestrictions.contains(restrictionChain)) {
401             ValueChain straightPredicates;
402
403             foreach(const PropertyInfoBase &pi, restrictionChain) {
404                 straightPredicates.append(ResourceValue(pi.iri()));
405             }
406
407             if (not straightPredicates.empty()) {
408                 Variable inverseSubject;
409                 innerSelect.addRestriction(last, straightPredicates, inverseSubject);
410                 last = inverseSubject;
411             }
412
413             innerSelect.addRestriction(inverseObject, ResourceValue(it->iri()), last);
414
415             generatedRestrictions.insert(restrictionChain);
416             inverseObjects.insert(it->iri(), inverseObject);
417         }
418
419         inverseObject = inverseObjects.value(it->iri());
420
421         it++;
422
423         if (it == chain.constEnd()) {
424             // Applying str is needed, else we get the tracker:id (tracker bug)
425             // NB#213419
426             innerFields.append(Functions::str.apply(inverseObject));
427         } else {
428             for(; it != chain.constEnd(); ++it) {
429                 projectionChain.append(*it);
430             }
431
432             // FIXME: SubTypes, refactor
433             innerFields.append(Functions::coalesce.
434                                apply(predicateFunctionChain(projectionChain, inverseObject),
435                                      LiteralValue(QString())));
436         }
437     }
438
439     QString coalesceString;
440     for(int i = 0; i < innerFields.size() - 1; ++i) {
441         coalesceString.append(fieldSeparator());
442     }
443
444     innerFields = intercalate(innerFields, LiteralValue(fieldSeparator()));
445
446     if (innerFields.size() > 1) {
447         innerSelect.addProjection(Functions::concat.apply(innerFields));
448     } else if (innerFields.size() == 1 ){
449         const Function &f = static_cast<const Function&>(innerFields.first());
450         innerSelect.addProjection(f);
451     }
452
453     return Functions::coalesce.apply(Filter(innerSelect),
454                                      LiteralValue(coalesceString));
455 }
456
457 void
458 QTrackerScalarContactQueryBuilder::bindMultiDetail(const QTrackerContactDetail &detail,
459                                                    Select &query,
460                                                    const Cubi::Variable &subject,
461                                                    const QSet<QString> &fieldFilter)
462 {
463     // A non-unique detail can contain fields attached to one or more non-unique
464     // resources (ie a detail can aggregate properties from various RDF resources).
465     // We first list those different resources, and then for each of them we fetch
466     // all the properties' values grouped together using fn:concat.
467
468     QList<QUrl> prefixes;
469     QMultiHash<QUrl, QTrackerContactDetailField> fields;
470     // This hash stores the modified property chains for each field (key = field name)
471     QHash<QString, PropertyInfoList> fieldChains;
472
473     bool hasDetailUri = false;
474     PropertyInfoList detailUriChain;
475     QUrl detailUriPrefix;
476     Variable detailUriSubject;
477
478     QMultiMap<QUrl, QTrackerContactDetailField> inverseFields;
479
480     foreach (const QTrackerContactDetailField &field, detail.fields()) {
481         if (not fieldFilter.empty() && not fieldFilter.contains(field.name())) {
482             continue;
483         }
484
485         if (not field.hasPropertyChain()) {
486             continue;
487         }
488
489         PropertyInfoList chain = field.propertyChain();
490
491         // If needed, store information on how to retrieve the detailUri later.
492         // It will be prepended to the normal properties
493         if (not hasDetailUri && field.hasDetailUri()) {
494             hasDetailUri = true;
495             detailUriSubject = subject;
496
497             PropertyInfoList::ConstIterator pi = field.propertyChain().constBegin();
498
499             for(; pi != field.propertyChain().constEnd(); ++pi) {
500                 detailUriChain.append (*pi);
501
502                 if (pi->hasDetailUri()) {
503                     break;
504                 }
505             }
506
507             if (pi == field.propertyChain().constEnd()) {
508                 detailUriChain.clear();
509             }
510         }
511
512         // FIXME: This probably breaks if chain.first is an inverse property
513         const QUrl prefix = chain.takeFirst().iri();
514
515         fieldChains.insert(field.name(), chain);
516
517         if (field.hasDetailUri()) {
518             detailUriPrefix = prefix;
519         }
520
521         if (chain.hasInverseProperty()) {
522             inverseFields.insert(prefix, field);
523             continue;
524         }
525
526         if (not chain.isSingleValued()) {
527             qctWarn(QString::fromLatin1("Field %1 from detail %2 can't be generated "
528                                         "because it uses multivalued properties").
529                     arg(field.name(), detail.name()));
530             continue;
531         }
532
533         if (field.hasSubTypeProperties()) {
534             if (field.propertyChain().size() != 1) {
535                 qctWarn(QString::fromLatin1("Subtype properties with a property chain "
536                                             "longer than 1 are not supported for field %1").
537                         arg(field.name()));
538             } else {
539                 foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
540                     prefixes.append(pi.iri());
541                     fields.insert(pi.iri(), field);
542                 }
543             }
544         }
545
546         if (not prefixes.contains(prefix)) {
547             prefixes.append(prefix);
548         }
549         fields.insert(prefix, field);
550     }
551
552     foreach(const QUrl &prefix, prefixes) {
553         ValueChain tokens;
554         Variable object;
555         Select s;
556         s.addRestriction(subject, ResourceValue(prefix), object);
557
558         if (hasDetailUri && detailUriPrefix == prefix) {
559             if (not detailUriChain.empty() && prefix == detailUriChain.first().iri()) {
560                 detailUriChain.removeFirst();
561                 detailUriSubject = object;
562             }
563
564             if (detailUriChain.empty()) {
565                 // Applying str is needed, else we get the tracker:id (tracker bug)
566                 // NB#213419
567                 tokens.append(Functions::str.apply(detailUriSubject));
568             } else {
569                 tokens.append(predicateFunctionChain(detailUriChain, detailUriSubject));
570             }
571         }
572
573         // We walk the list in reverse order to preserve field order
574         // Fields containing inverse properties are bound in the end anyway
575         // since they use a nested scalar select
576         QList<QTrackerContactDetailField> prefixFields = fields.values(prefix);
577         QList<QTrackerContactDetailField>::ConstIterator field = prefixFields.constEnd();
578         while(field != prefixFields.constBegin()) {
579             field--;
580             Value v;
581             PropertyInfoList chain = fieldChains.value(field->name(), PropertyInfoList());
582
583             if (not field->isWithoutMapping()) {
584                 if (not chain.isEmpty()) {
585                     // FIXME: What happens if we have instances but no property chain?
586                     // We don't use IRIs but trackerId for instances
587                     // In this case, we have to generate a scalar select, because we
588                     // can't apply tracker:id if we have various instances (v would not be
589                     // an IRI but a comma separated list of IRIs)
590                     if (field->restrictsValues()) {
591                         v = bindRestrictedValuesField(*field, chain, object);
592                     } else {
593                         v = predicateFunctionChain(chain, object);
594                     }
595
596                     // We need to coalesce to make the fields "optional" (ie assign
597                     // empty string if there are no results)
598                     v = Functions::coalesce.apply(v, LiteralValue(QString()));
599                 } else {
600                     v = object;
601                 }
602
603                 if (field->hasSubTypes()) {
604                     if (field->hasSubTypeClasses()) {
605                         Select innerSelect;
606                         Variable type(QLatin1String("t"));
607                         innerSelect.addProjection(Functions::groupConcat.
608                                                   apply(Functions::trackerId.apply(type),
609                                                         LiteralValue(listSeparator())));
610                         innerSelect.addRestriction(v, rdf::type::resource(), type);
611                         tokens.append(Functions::coalesce.apply(Filter(innerSelect),
612                                                                 LiteralValue(QString())));
613                     }
614                 } else {
615                     // Applying str because of
616                     // NB#213419
617                     tokens.append(v == object ? Functions::str.apply(v) : v);
618                 }
619             }
620
621             if (field->permitsCustomValues()) {
622                 tokens.append(Functions::coalesce.apply(bindCustomValues(*field, object),
623                                                         LiteralValue(QString())));
624             }
625
626         }
627
628         // FIXME: this can probably be factorized better
629         // FIXME: we don't handle DetailUri there
630         if (inverseFields.contains(prefix)) {
631             QList<QTrackerContactDetailField> inversePrefixFields = inverseFields.values(prefix);
632
633             tokens.append(bindInverseFields(inversePrefixFields, fieldChains, object));
634         }
635
636         tokens = intercalate(tokens, LiteralValue(fieldSeparator()));
637
638         if (tokens.size() > 1) {
639             s.addProjection(Functions::groupConcat.apply(Functions::concat.apply(tokens),
640                                                          LiteralValue(detailSeparator())));
641         } else if (tokens.size() == 1 ){
642             s.addProjection(Functions::groupConcat.apply(tokens.first(),
643                                                          LiteralValue(detailSeparator())));
644         } else {
645             // We can have no fields if there was only one field, and it was pointing
646             // to a value and not a resource
647             s.addProjection(Functions::groupConcat.apply(object,
648                                                          LiteralValue(detailSeparator())));
649         }
650
651         query.addProjection(s);
652     }
653 }
654
655 void
656 QTrackerScalarContactQueryBuilder::bindFields(const QTrackerContactDetail &detail, Select &query,
657                                               const QSet<QString> &fieldFilter)
658 {
659     if (detail.isUnique()) {
660         bindUniqueDetail(detail, query, fieldFilter);
661     } else {
662         if (detail.hasContext()) {
663             bindMultiDetail(detail, query, context(), fieldFilter);
664         } else {
665             bindMultiDetail(detail, query, contact(), fieldFilter);
666         }
667     }
668 }
669
670 void
671 QTrackerScalarContactQueryBuilder::bindCustomDetails(Cubi::Select &query,
672                                                      const QSet<QString> &detailFilter)
673 {
674     Select customDetailsQuery;
675
676     Variable detail(QLatin1String("customDetail"));
677     Function detailName(nao::propertyName::function().apply(detail));
678
679     Variable field(QLatin1String("customField"));
680     Function fieldName(nao::propertyName::function().apply(field));
681
682
683     ValueChain valueConcatParams;
684     Variable fieldValue(QLatin1String("value"));
685     valueConcatParams.append(Functions::trackerId.apply(field));
686     valueConcatParams.append(LiteralValue(QLatin1String(":")));
687     valueConcatParams.append(fieldValue);
688
689     Select fieldValuesQuery;
690     fieldValuesQuery.addProjection(Functions::groupConcat.
691                                    apply(Functions::concat.apply(valueConcatParams),
692                                          LiteralValue(listSeparator())));
693     fieldValuesQuery.addRestriction(field, nao::propertyValue::resource(), fieldValue);
694
695     Select propertySelect;
696     propertySelect.addProjection(Functions::groupConcat.
697                                  apply(Functions::concat.apply(fieldName,
698                                                                LiteralValue(fieldSeparator()),
699                                                                Filter(fieldValuesQuery)),
700                                        LiteralValue(fieldSeparator())));
701     propertySelect.addRestriction(detail, nao::hasProperty::resource(), field);
702
703     ValueChain groupConcatParams;
704     groupConcatParams.append(detailName);
705     groupConcatParams.append(LiteralValue(fieldSeparator()));
706     groupConcatParams.append(Filter(propertySelect));
707     customDetailsQuery.addProjection(Functions::groupConcat.
708                                      apply(Functions::concat.apply(groupConcatParams),
709                                            LiteralValue(detailSeparator())));
710     customDetailsQuery.addRestriction(contact(), nao::hasProperty::resource(), detail);
711
712     if (not detailFilter.empty()) {
713         ValueList detailsToFetch;
714
715         foreach(const QString &filter, detailFilter) {
716             LiteralValue name(qVariantFromValue(filter));
717             detailsToFetch.addValue(name);
718         }
719
720         customDetailsQuery.setFilter(Functions::in.apply(detailName, detailsToFetch));
721     }
722
723     query.addProjection(customDetailsQuery);
724 }
725
726 ////////////////////////////////////////////////////////////////////////////////////////////////////
727
728 #define DO_ENUM_VALUE(Type) \
729     case Type: return QLatin1String(#Type)
730
731 static QString
732 filterName(QContactFilter::FilterType type)
733 {
734     switch(type) {
735         DO_ENUM_VALUE(QContactFilter::InvalidFilter);
736         DO_ENUM_VALUE(QContactFilter::ContactDetailFilter);
737         DO_ENUM_VALUE(QContactFilter::ContactDetailRangeFilter);
738         DO_ENUM_VALUE(QContactFilter::ChangeLogFilter);
739         DO_ENUM_VALUE(QContactFilter::ActionFilter);
740         DO_ENUM_VALUE(QContactFilter::RelationshipFilter);
741         DO_ENUM_VALUE(QContactFilter::IntersectionFilter);
742         DO_ENUM_VALUE(QContactFilter::UnionFilter);
743         DO_ENUM_VALUE(QContactFilter::LocalIdFilter);
744         DO_ENUM_VALUE(QContactFilter::DefaultFilter);
745     }
746
747     return QString::fromLatin1("QContactFilter::FilterType(%1)").arg(type);
748 }
749
750 static QString
751 eventName(QContactChangeLogFilter::EventType type)
752 {
753     switch(type) {
754         DO_ENUM_VALUE(QContactChangeLogFilter::EventAdded);
755         DO_ENUM_VALUE(QContactChangeLogFilter::EventChanged);
756         DO_ENUM_VALUE(QContactChangeLogFilter::EventRemoved);
757     }
758
759     return QString::fromLatin1("QContactChangeLogFilter::EventType(%1)").arg(type);
760 }
761
762 #undef DO_ENUM_VALUE
763
764 ////////////////////////////////////////////////////////////////////////////////////////////////////
765
766 static QContactFilter::MatchFlags
767 matchFunctionFlags(QContactFilter::MatchFlags flags)
768 {
769     return flags & MatchFunctionFlags;
770 }
771
772 static QContactFilter::MatchFlags
773 stringCompareFlags(QContactFilter::MatchFlags flags)
774 {
775     return flags & StringCompareFlags;
776 }
777
778 Function
779 QTrackerScalarContactQueryBuilder::matchFunction(QContactDetailFilter::MatchFlags flags,
780                                                  Value variable, QVariant value)
781 {
782     Value param = variable;
783
784     // Convert variable and search value into lower-case for case-insensitive matching.
785     // Must convert the value locally, instead of also using fn:lower-case because
786     // fn:starts-with only takes literals as second argument, but not return values.
787     if (QVariant::String == value.type() && 0 == (flags & QContactFilter::MatchCaseSensitive)) {
788         param = Functions::lowerCase.apply(param);
789         value.setValue(value.toString().toLower());
790     }
791
792     Value valueObject = LiteralValue(value);
793
794     // If exact matching is requested for non-string literal still do case sensitive matching,
795     // as case insensitive matching of numbers or URLs doesn't make much sense.
796     if (QVariant::String != value.type() && 0 == stringCompareFlags(flags)) {
797         return Functions::equal.apply(param, valueObject);
798     }
799
800     // for case sensitive matching there are specialized functions...
801     switch(matchFunctionFlags(flags)) {
802     case QContactFilter::MatchContains:
803         return Functions::contains.apply(param, valueObject);
804     case QContactFilter::MatchStartsWith:
805         return Functions::startsWith.apply(param, valueObject);
806     case QContactFilter::MatchEndsWith:
807         return Functions::endsWith.apply(param, valueObject);
808     }
809
810     return Functions::equal.apply(param, valueObject);
811 }
812
813 ////////////////////////////////////////////////////////////////////////////////////////////////////
814
815 template <typename T> static bool
816 isCompoundFilterSupported(const QContactFilter &filter)
817 {
818     foreach(const QContactFilter &childFilter, static_cast<const T&>(filter).filters()) {
819         if (not QTrackerScalarContactQueryBuilder::isFilterSupported(childFilter)) {
820             return false;
821         }
822     }
823
824     return true;
825 }
826
827 static bool
828 isCanonicalFilterSupported(const QContactFilter &filter)
829 {
830     switch(filter.type()) {
831     case QContactFilter::DefaultFilter:
832     case QContactFilter::LocalIdFilter:
833     case QContactFilter::ContactDetailFilter:
834     case QContactFilter::ContactDetailRangeFilter:
835     case QContactFilter::ChangeLogFilter:
836     case QContactFilter::RelationshipFilter:
837         return true;
838
839     case QContactFilter::IntersectionFilter:
840         return isCompoundFilterSupported<QContactIntersectionFilter>(filter);
841     case QContactFilter::UnionFilter:
842         return isCompoundFilterSupported<QContactUnionFilter>(filter);
843
844     case QContactFilter::ActionFilter:
845     case QContactFilter::InvalidFilter:
846         break;
847     }
848
849     return false;
850 }
851
852 static inline ClassInfoList::ConstIterator
853 findClassInfoWithText(const ClassInfoList &list, const QString &text)
854 {
855     ClassInfoList::ConstIterator it;
856     for (it = list.constBegin(); it != list.constEnd(); ++it) {
857         if (it->text() == text) {
858             break;
859         }
860     }
861     return it;
862 }
863
864 static inline PropertyInfoList::ConstIterator
865 findPropertyInfoWithText(const PropertyInfoList &list, const QString &text)
866 {
867     PropertyInfoList::ConstIterator it;
868     for (it = list.constBegin(); it != list.constEnd(); ++it) {
869         if (it->text() == text) {
870             break;
871         }
872     }
873     return it;
874 }
875
876 static QUrl
877 findClassSubTypeIri(const QTrackerContactDetailField *field, const QVariant &value)
878 {
879         // lookup class for given subtype
880         const QString subType = value.toString();
881         const ClassInfoList &subTypeClasses = field->subTypeClasses();
882         ClassInfoList::ConstIterator it = findClassInfoWithText(subTypeClasses, subType);
883
884         // standard subtype found?
885         if (it != subTypeClasses.constEnd()) {
886             const QUrl &subTypeIri = it->iri();
887
888             return subTypeIri;
889         }
890
891         return QUrl();
892 }
893
894 bool
895 QTrackerScalarContactQueryBuilder::isFilterSupported(const QContactFilter &filter)
896 {
897     return isCanonicalFilterSupported(QContactManagerEngine::canonicalizedFilter(filter));
898 }
899
900 static PatternGroup
901 createPredicateChain(const Variable &subject,
902                      const PropertyInfoList &properties,
903                      const Variable &object)
904 {
905     Variable last = subject;
906     PatternGroup patterns;
907
908     for(PropertyInfoList::ConstIterator it = properties.constBegin();
909         it != properties.constEnd();
910         ++it) {
911         Variable o;
912
913         if (it == properties.constEnd() - 1) {
914             o = object;
915         }
916
917         if (it->isInverse()) {
918             patterns.addPattern(o, ResourceValue(it->iri()), last);
919         } else {
920             patterns.addPattern(last, ResourceValue(it->iri()), o);
921         }
922
923         last = o;
924     }
925
926     return patterns;
927 }
928
929 static bool
930 applyTypeCast(QContactFilter::MatchFlags flags,
931               const QTrackerContactDetailField *field, QVariant &value)
932 {
933     if (0 != stringCompareFlags(flags)) {
934         return value.convert(QVariant::String);
935     }
936
937     return field->makeValue(value, value);
938 }
939
940 static Filter
941 createSubTypePropertyFilter(const Variable &subject,
942                             const Variable &object,
943                             const PropertyInfoList &subtypes,
944                             const PropertyInfoBase &type)
945 {
946     ValueChain operands;
947
948     // Filtering is only needed if the filtered value corresponds to a RDF
949     // property that has sub-properties. Because we don't really have this
950     // information in the schema, we assume the default value is the base
951     // property (that's at least how it works for QContactUrl).
952
953     foreach(const PropertyInfoBase &subtype, subtypes) {
954         if (subtype.iri() == type.iri()) {
955             return Filter();
956         }
957     }
958
959     foreach(const PropertyInfoBase &subtype, subtypes) {
960         Exists exists;
961         exists.addPattern(subject, ResourceValue(subtype.iri()), object);
962         operands.append(Functions::not_.apply(Filter(exists)));
963     }
964
965     return Filter(Functions::and_.apply(operands));
966 }
967
968 void
969 QTrackerScalarContactQueryBuilder::bindCustomDetailFilter(const QContactDetailFilter &filter,
970                                                           Exists &exists,
971                                                           Variable &subject)
972 {
973         Variable customDetail(QString::fromLatin1("customDetail"));
974         Variable customField(QString::fromLatin1("customField"));
975         Variable fieldValue(QString::fromLatin1("fieldValue"));
976
977         exists.addPattern(contact(), nao::hasProperty::resource(), customDetail);
978         exists.addPattern(customDetail, rdf::type::resource(), nao::Property::resource());
979         exists.addPattern(customDetail, nao::propertyName::resource(),
980                               LiteralValue(filter.detailDefinitionName()));
981         exists.addPattern(customDetail, nao::hasProperty::resource(), customField);
982         if (not filter.detailFieldName().isEmpty()) {
983             exists.addPattern(customField, nao::propertyName::resource(),
984                               LiteralValue(filter.detailFieldName()));
985         }
986         exists.addPattern(customField, nao::propertyValue::resource(), fieldValue);
987
988         subject = fieldValue;
989 }
990
991 PatternGroup
992 QTrackerScalarContactQueryBuilder::bindWithoutMappingFilter(const QTrackerContactDetailField *field,
993                                                             const Variable &subject,
994                                                             Variable &value)
995 {
996         PatternGroup patterns;
997         Variable customField(QString::fromLatin1("customField"));
998         value = Variable(QString::fromLatin1("fieldValue"));
999
1000         patterns.addPattern(subject, nao::hasProperty::resource(), customField);
1001         patterns.addPattern(customField, nao::propertyName::resource(), LiteralValue(field->name()));
1002         patterns.addPattern(customField, nao::propertyValue::resource(), value);
1003
1004         return patterns;
1005 }
1006
1007 template <class T> bool
1008 QTrackerScalarContactQueryBuilder::bindFilterDetail(const T &filter,
1009                                                     QList<Exists> &exists,
1010                                                     QList<Variable> &subjects,
1011                                                     bool hasCustomSubType,
1012                                                     const PropertyInfoBase &propertySubtype)
1013 {
1014     static const uint unsupportedMatchFlags =
1015             // FIXME: add support for those flags
1016             QContactDetailFilter::MatchKeypadCollation;
1017
1018     if (filter.matchFlags() & unsupportedMatchFlags) {
1019         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
1020                 arg(filterName(filter.type())).
1021                 arg(filter.matchFlags() & unsupportedMatchFlags));
1022     }
1023
1024     // find detail and field definition
1025     const QTrackerContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1026
1027     if (0 == detail) {
1028         Exists e;
1029         Variable subject;
1030         bindCustomDetailFilter(filter, e, subject);
1031         exists.append(e);
1032         subjects.append(subject);
1033         return true;
1034     }
1035
1036     bool success = false;
1037
1038     if (filter.detailFieldName().isEmpty()) {
1039         success = true;
1040
1041         foreach(const QTrackerContactDetailField &field, detail->fields()) {
1042             Exists e;
1043             Variable subject;
1044             T fieldFilter = filter;
1045             fieldFilter.setDetailDefinitionName(filter.detailDefinitionName(), field.name());
1046
1047             success &= bindFilterDetailField(fieldFilter, e, subject, hasCustomSubType, propertySubtype);
1048             exists.append(e);
1049             subjects.append(subject);
1050         }
1051     } else {
1052         Exists e;
1053         Variable subject;
1054         success = bindFilterDetailField(filter, e, subject, hasCustomSubType, propertySubtype);
1055
1056         exists.append(e);
1057         subjects.append(subject);
1058     }
1059
1060     return success;
1061 }
1062
1063 template <class T> bool
1064 QTrackerScalarContactQueryBuilder::bindFilterDetailField(const T &filter,
1065                                                          Exists &exists,
1066                                                          Variable &subject,
1067                                                          bool hasCustomSubType,
1068                                                          const PropertyInfoBase &propertySubtype)
1069 {
1070
1071     // find detail and field definition
1072     const QTrackerContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1073     const QTrackerContactDetailField *const field = findField(*detail, filter.detailFieldName());
1074
1075     if (0 == field) {
1076         return false;
1077     }
1078
1079     PropertyInfoList properties = field->propertyChain();
1080
1081     // Custom subtypes are stored in nao:Property objects
1082     if (field->hasSubTypeClasses() && not hasCustomSubType) {
1083         properties.append(PropertyInfo<rdf::type>());
1084     }
1085
1086     if (not propertySubtype.iri().isEmpty()) {
1087         properties.removeLast();
1088     }
1089
1090     // do special processing for phone numbers
1091     if (not properties.empty() && properties.last().iri() == nco::phoneNumber::iri()) {
1092         if (filter.matchFlags().testFlag(QContactFilter::MatchPhoneNumber)) {
1093             // use the normalized phone number, stored in maemo:localPhoneNumber resource
1094             properties.removeLast();
1095             properties.append(field->computedProperties().first());
1096
1097             // this hack only works for maemo:localPhoneNumber
1098             Q_ASSERT(properties.last().iri() == maemo::localPhoneNumber::iri());
1099         }
1100     }
1101
1102     Variable filterValue;
1103
1104     if (detail->hasContext()) {
1105         properties.prepend(piHasAffiliation);
1106     }
1107
1108     exists.addPattern(createPredicateChain(contact(), properties, filterValue));
1109
1110     if (not propertySubtype.iri().isEmpty()) {
1111         Variable newValue;
1112         exists.addPattern(filterValue, ResourceValue(propertySubtype.iri()), newValue);
1113
1114         exists.setFilter(createSubTypePropertyFilter(filterValue,
1115                                                      newValue,
1116                                                      field->subTypeProperties(),
1117                                                      propertySubtype));
1118         filterValue = newValue;
1119     }
1120
1121     if (field->isWithoutMapping() ||
1122         hasCustomSubType ||
1123         (field->hasSubTypeProperties() && propertySubtype == 0)) {
1124         Variable newValue;
1125         exists.addPattern(bindWithoutMappingFilter(field, filterValue, newValue));
1126         filterValue = newValue;
1127     }
1128
1129     subject = filterValue;
1130
1131     return true;
1132 }
1133
1134 QVariant
1135 QTrackerScalarContactQueryBuilder::normalizeFilterValue(const QTrackerContactDetailField *field,
1136                                                         const QContactDetailFilter::MatchFlags &flags,
1137                                                         const QVariant &value)
1138 {
1139     if (value.isNull()) {
1140         return value;
1141     }
1142
1143     QVariant v = value;
1144
1145     // try casting the filter value
1146     if (not applyTypeCast(flags, field, v)) {
1147         qctWarn(QString::fromLatin1("Cannot apply required casts to filter value "
1148                                     "for field %1").arg(field->name()));
1149         m_error = QContactManager::BadArgumentError;
1150         return v;
1151     }
1152
1153     // do special processing for phone numbers
1154     // FIXME: find a more generic pattern for doing this modifications
1155     if (not field->propertyChain().empty() &&
1156             field->propertyChain().last().iri() == nco::phoneNumber::iri()) {
1157         // Phone numbers are almost impossible to parse (TODO: reference?)
1158         // So phone number handling is done based on heuristic:
1159         // it turned out that matching the last 7 digits works good enough (tm).
1160         // The actual number of digits to use is stored in QctSettings().localPhoneNumberLength().
1161         // The phone number's digits are stored in tracker using a maemo:localPhoneNumber property.
1162         const int localPhoneNumberLength = QctSettings().localPhoneNumberLength();
1163
1164         if (QContactFilter::MatchEndsWith == matchFunctionFlags(flags)) {
1165             return v.toString().right(localPhoneNumberLength);
1166         }
1167     }
1168
1169     if (field->hasSubTypeClasses()) {
1170         const QUrl subTypeIri = findClassSubTypeIri(field, v);
1171
1172         // standard subtype found?
1173         if (subTypeIri.isValid()) {
1174             return subTypeIri;
1175         } else {
1176             if (not field->permitsCustomValues()) {
1177                 qctWarn(QString::fromLatin1("Unknown subtype %2 for field %1").
1178                         arg(field->name(), v.toString()));
1179                 m_error = QContactManager::BadArgumentError;
1180                 return QVariant();
1181             }
1182
1183             return v;
1184         }
1185     }
1186
1187     return v;
1188 }
1189
1190 QContactFilter::MatchFlags
1191 QTrackerScalarContactQueryBuilder::normalizeFilterMatchFlags(const QTrackerContactDetailField *field,
1192                                                              const QContactFilter::MatchFlags &matchFlags,
1193                                                              const QVariant &value)
1194 {
1195     QContactFilter::MatchFlags flags = matchFlags;
1196
1197     // do special processing for phone numbers
1198     // FIXME: find a more generic pattern for doing this modifications
1199     if (not field->propertyChain().empty() &&
1200             field->propertyChain().last().iri() == nco::phoneNumber::iri()) {
1201         // Phone numbers are almost impossible to parse (TODO: reference?)
1202         // So phone number handling is done based on heuristic:
1203         // it turned out that matching the last 7 digits works good enough (tm).
1204         // The actual number of digits to use is stored in QctSettings().localPhoneNumberLength().
1205         // The phone number's digits are stored in tracker using a maemo:localPhoneNumber property.
1206
1207         if (flags.testFlag(QContactFilter::MatchPhoneNumber)) {
1208             const int localPhoneNumberLength = QctSettings().localPhoneNumberLength();
1209             const QString phoneNumberReference = value.toString();
1210
1211             // remove existing function flag and replace with normal string oriented one
1212             flags &= ~MatchFunctionFlags;
1213             if (phoneNumberReference.length() >= localPhoneNumberLength) {
1214                 flags |= QContactFilter::MatchEndsWith;
1215             } else {
1216                 flags |= QContactFilter::MatchExactly;
1217             }
1218         }
1219     }
1220
1221     return flags;
1222 }
1223
1224 bool
1225 QTrackerScalarContactQueryBuilder::bindFilter(QContactDetailFilter filter, Filter &result)
1226 {
1227     if (QContactManager::NoError != error()) {
1228         return false; // abort early if there already is an error
1229     }
1230
1231     QList<Exists> exists;
1232     QList<Variable> subjects;
1233     bool hasSubTypeClasses = false;
1234     bool hasCustomSubTypeClasses = false;
1235     PropertyInfoList subTypeProperties;
1236
1237     const QTrackerContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1238     PropertyInfoBase propertySubtype;
1239
1240     if (detail != 0) {
1241         const QTrackerContactDetailField *const field = findField(*detail, filter.detailFieldName());
1242
1243         if (field != 0) {
1244             hasSubTypeClasses = field->hasSubTypeClasses();
1245             hasCustomSubTypeClasses = (hasSubTypeClasses &&
1246                                        not findClassSubTypeIri(field, filter.value()).isValid());
1247
1248             filter.setMatchFlags(normalizeFilterMatchFlags(field, filter.matchFlags(), filter.value()));
1249             filter.setValue(normalizeFilterValue(field, filter.matchFlags(), filter.value()));
1250
1251             subTypeProperties = field->subTypeProperties();
1252
1253             if (field->hasSubTypeProperties()) {
1254                 const QString subType = filter.value().toString();
1255
1256                 if (not field->propertyChain().empty() && field->defaultValue() == subType) {
1257                     propertySubtype = field->propertyChain().last();
1258                 } else {
1259                     PropertyInfoList::ConstIterator it = findPropertyInfoWithText(subTypeProperties, subType);
1260
1261                     if (it != subTypeProperties.constEnd()) {
1262                         propertySubtype = *it;
1263                     }
1264                 }
1265             }
1266         }
1267     }
1268
1269     if (not bindFilterDetail<QContactDetailFilter>(filter,
1270                                                    exists,
1271                                                    subjects,
1272                                                    hasCustomSubTypeClasses,
1273                                                    propertySubtype)) {
1274         return false;
1275     }
1276
1277     ValueChain orOperands;
1278
1279     // This if block works for both the wildcard filter (a field was bound in
1280     // an EXISTS block, but null value = wildcard filter) and "exists" filter
1281     // (all detail fields were bound in bindFilterDetail, but no check on value
1282     // since we just check for detail presence).
1283     // We also don't set a filter for subtype properties, since filtering is
1284     // done on the predicate (and not the object), in bindFilterDetail.
1285     if (filter.value().isNull() ||
1286         (not subTypeProperties.empty() && not propertySubtype.iri().isEmpty())) {
1287         foreach(const Exists &e, exists) {
1288             orOperands.append(Filter(e));
1289         }
1290
1291         result = Functions::or_.apply(orOperands);
1292
1293         return true;
1294     }
1295
1296     for(int i = 0; i < exists.size(); ++i) {
1297         Exists e = exists.takeFirst();
1298
1299         if (hasSubTypeClasses) {
1300             e.setFilter(Functions::equal.apply(subjects.at(i), LiteralValue(filter.value())));
1301         } else {
1302             e.setFilter(matchFunction(filter.matchFlags(), subjects.at(i), filter.value()));
1303         }
1304
1305         orOperands.append(Filter(e));
1306     }
1307
1308     result = Functions::or_.apply(orOperands);
1309
1310     return true;
1311 }
1312
1313 bool
1314 QTrackerScalarContactQueryBuilder::bindFilter(QContactDetailRangeFilter filter, Filter &result)
1315 {
1316     if (QContactManager::NoError != error()) {
1317         return false; // abort early if there already is an error
1318     }
1319
1320     QList<Exists> exists;
1321     QList<Variable> subjects;
1322
1323     const QTrackerContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1324
1325     if (detail != 0) {
1326         const QTrackerContactDetailField *const field = findField(*detail, filter.detailFieldName());
1327
1328         if (field != 0) {
1329             filter.setMatchFlags(normalizeFilterMatchFlags(field, filter.matchFlags()));
1330             filter.setRange(normalizeFilterValue(field, filter.matchFlags(), filter.minValue()),
1331                             normalizeFilterValue(field, filter.matchFlags(), filter.maxValue()));
1332         }
1333     }
1334
1335     if (not bindFilterDetail<QContactDetailRangeFilter>(filter, exists, subjects)) {
1336         return false;
1337     }
1338
1339
1340     ValueChain orOperands;
1341
1342     for(int i = 0; i < exists.size(); ++i) {
1343         ValueChain operands;
1344         Exists e = exists.takeFirst();
1345
1346         if (not filter.minValue().isNull()) {
1347             if (filter.rangeFlags().testFlag(QContactDetailRangeFilter::IncludeLower)) {
1348                 operands.append(Functions::lessThanOrEqual.apply(LiteralValue(filter.minValue()),
1349                                                                  subjects.at(i)));
1350             } else {
1351                 operands.append(Functions::lessThan.apply(LiteralValue(filter.minValue()),
1352                                                           subjects.at(i)));
1353             }
1354         }
1355
1356         if (not filter.maxValue().isNull()) {
1357             if (filter.rangeFlags().testFlag(QContactDetailRangeFilter::IncludeUpper)) {
1358                 operands.append(Functions::lessThanOrEqual.apply(subjects.at(i),
1359                                                                  LiteralValue(filter.maxValue())));
1360             } else {
1361                 operands.append(Functions::lessThan.apply(subjects.at(i),
1362                                                           LiteralValue(filter.maxValue())));
1363             }
1364         }
1365
1366         e.setFilter(Functions::and_.apply(operands));
1367
1368         orOperands.append(Filter(e));
1369     }
1370
1371     result = Functions::or_.apply(orOperands);
1372
1373     return true;
1374 }
1375
1376 bool
1377 QTrackerScalarContactQueryBuilder::bindFilter(const QContactLocalIdFilter &filter,
1378                                               Cubi::Filter &result)
1379 {
1380     if (QContactManager::NoError != error()) {
1381         return false; // abort early if there already is an error
1382     }
1383
1384     if (filter.ids().isEmpty()) {
1385         qctWarn(QString::fromLatin1("%1: Local contact id list cannot be empty").
1386                 arg(filterName(filter.type())));
1387         m_error = QContactManager::BadArgumentError;
1388         return false;
1389     }
1390
1391     ValueList ids;
1392
1393     foreach(QContactLocalId id, filter.ids()) {
1394         ids.addValue(LiteralValue(qVariantFromValue(QString::number(id))));
1395     }
1396
1397     result = Functions::in.apply(Functions::trackerId.apply(contact()), ids);
1398     return true;
1399 }
1400
1401 bool
1402 QTrackerScalarContactQueryBuilder::bindFilter(const QContactIntersectionFilter &filter,
1403                                               Cubi::Filter &result)
1404 {
1405     if (QContactManager::NoError != error()) {
1406         return false; // abort early if there already is an error
1407     }
1408
1409     ValueChain filters;
1410     bool restrictionsAdded = false;
1411
1412     foreach(const QContactFilter &childFilter, filter.filters()) {
1413         Filter filter;
1414         restrictionsAdded |= bindFilter(childFilter, filter);
1415
1416         if (QContactManager::NoError != error()) {
1417             return false;
1418         }
1419
1420         filters.append(filter);
1421     }
1422
1423     result = Functions::and_.apply(filters);
1424
1425     return restrictionsAdded;
1426 }
1427
1428 bool
1429 QTrackerScalarContactQueryBuilder::bindFilter(const QContactUnionFilter &filter,
1430                                               Cubi::Filter &result)
1431 {
1432     if (QContactManager::NoError != error()) {
1433         return false; // abort early if there already is an error
1434     }
1435
1436     ValueChain filters;
1437     bool restrictionsAdded = false;
1438
1439     foreach(const QContactFilter &childFilter, filter.filters()) {
1440         Filter filter;
1441         restrictionsAdded |= bindFilter(childFilter, filter);
1442
1443         if (QContactManager::NoError != error()) {
1444             return false;
1445         }
1446
1447         filters.append(filter);
1448     }
1449
1450     result = Functions::or_.apply(filters);
1451
1452     return restrictionsAdded;
1453 }
1454
1455 bool
1456 QTrackerScalarContactQueryBuilder::bindFilter(const QContactChangeLogFilter &filter,
1457                                               Filter &result)
1458 {
1459     if (QContactManager::NoError != error()) {
1460         return false; // abort early if there already is an error
1461     }
1462
1463     Exists exists;
1464     Variable contentCreated;
1465     Variable contentLastModified;
1466
1467     switch(filter.eventType()) {
1468     case QContactChangeLogFilter::EventAdded:
1469         exists.addPattern(contact(), nie::contentCreated::resource(), contentCreated);
1470         exists.setFilter(Functions::greaterThanOrEqual.apply(contentCreated,
1471                                                              LiteralValue(qVariantFromValue(filter.since()))));
1472         result = exists;
1473         return true;
1474
1475     case QContactChangeLogFilter::EventChanged:
1476         exists.addPattern(contact(), nie::contentLastModified::resource(), contentLastModified);
1477         exists.setFilter(Functions::greaterThanOrEqual.apply(contentLastModified,
1478                                                              LiteralValue(qVariantFromValue(filter.since()))));
1479         result = exists;
1480         return true;
1481
1482     case QContactChangeLogFilter::EventRemoved:
1483         break;
1484     }
1485
1486     qctWarn(QString::fromLatin1("%1: Unsupported event type: %2").
1487             arg(filterName(filter.type()), eventName(filter.eventType())));
1488     m_error = QContactManager::NotSupportedError;
1489
1490     return false;
1491 }
1492
1493 bool
1494 QTrackerScalarContactQueryBuilder::bindFilter(const QContactRelationshipFilter &filter,
1495                                               Filter &result)
1496 {
1497     if (QContactManager::NoError != error()) {
1498         return false; // abort early if there already is an error
1499     }
1500
1501     if (filter.relationshipType() == QContactRelationship::HasMember) {
1502         const QContactId relatedContactId = filter.relatedContactId();
1503
1504         // only HasMember relationships between local groups and local contacts are stored ATM
1505         if (relatedContactId.managerUri() == m_managerUri) {
1506             LiteralValue rdfLocalId(qVariantFromValue(QString::number(relatedContactId.localId())));
1507
1508             const QContactRelationship::Role relatedContactRole = filter.relatedContactRole();
1509             ValueChain restrictions;
1510
1511             // filter for all contacts in a given group?
1512             if (relatedContactRole == QContactRelationship::First
1513                 || relatedContactRole == QContactRelationship::Either) {
1514                 const Variable rdfGroupContact(QString::fromLatin1("group"));
1515                 const Value rdfGroupContactId = Functions::trackerId.apply(rdfGroupContact);
1516
1517                 Exists exists;
1518                 exists.addPattern(rdfGroupContact, rdf::type::resource(), nco::Contact::resource());
1519                 exists.addPattern(rdfGroupContact, rdf::type::resource(), nco::ContactGroup::resource());
1520                 exists.addPattern(contact(), nco::belongsToGroup::resource(), rdfGroupContact);
1521                 exists.setFilter(Functions::equal.apply(rdfGroupContactId, rdfLocalId));
1522                 restrictions += Filter(exists);
1523             }
1524
1525             // filter for all groups a given contact is in?
1526             if (relatedContactRole == QContactRelationship::Second
1527                 || relatedContactRole == QContactRelationship::Either) {
1528                 const Variable rdfContact(QString::fromLatin1("member"));
1529                 const Value rdfContactId = Functions::trackerId.apply(rdfContact);
1530
1531                 Exists exists;
1532                 // not PersonContact, groups could also be part of other groups
1533                 exists.addPattern(rdfContact, rdf::type::resource(), nco::Contact::resource());
1534                 exists.addPattern(rdfContact, nco::belongsToGroup::resource(), contact());
1535                 exists.setFilter(Functions::equal.apply(rdfContactId, rdfLocalId));
1536                 restrictions += Filter(exists);
1537             }
1538
1539             if (not restrictions.isEmpty()) {
1540                 result = Functions::or_.apply(restrictions);
1541             } else {
1542                 result = Filter();
1543             }
1544         } else {
1545             qctWarn(QString::fromLatin1("Relationships to contacts of the %1 "
1546                                         "contact manager are not stored here (%2).").
1547                     arg(relatedContactId.managerUri(), m_managerUri));
1548
1549             // none at all available for relations to ones from other manager ATM
1550             Exists exists;
1551             exists.setFilter(LiteralValue(QVariant(false)));
1552             result = exists;
1553         }
1554
1555         return true;
1556     }
1557
1558     qctWarn(QString::fromLatin1("%1: Unsupported relationship type: %2").
1559             arg(filterName(filter.type()), filter.relationshipType()));
1560     m_error = QContactManager::NotSupportedError;
1561     return false;
1562 }
1563
1564 bool
1565 QTrackerScalarContactQueryBuilder::bindFilter(const QContactFilter &filter, Cubi::Filter &result)
1566 {
1567     if (QContactManager::NoError != error()) {
1568         return false; // abort early if there already is an error
1569     }
1570
1571     QContactFilter canonicalFilter(QContactManagerEngine::canonicalizedFilter(filter));
1572
1573     switch(canonicalFilter.type()) {
1574     case QContactFilter::LocalIdFilter:
1575         return bindFilter(static_cast<const QContactLocalIdFilter&>(canonicalFilter), result);
1576     case QContactFilter::IntersectionFilter:
1577         return bindFilter(static_cast<const QContactIntersectionFilter&>(canonicalFilter), result);
1578     case QContactFilter::UnionFilter:
1579         return bindFilter(static_cast<const QContactUnionFilter&>(canonicalFilter), result);
1580     case QContactFilter::ContactDetailFilter:
1581         return bindFilter(static_cast<const QContactDetailFilter&>(canonicalFilter), result);
1582     case QContactFilter::ContactDetailRangeFilter:
1583         return bindFilter(static_cast<const QContactDetailRangeFilter&>(canonicalFilter), result);
1584     case QContactFilter::ChangeLogFilter:
1585         return bindFilter(static_cast<const QContactChangeLogFilter&>(canonicalFilter), result);
1586     case QContactFilter::RelationshipFilter:
1587         return bindFilter(static_cast<const QContactRelationshipFilter&>(canonicalFilter), result);
1588     case QContactFilter::DefaultFilter:
1589         // We didn't apply any explicit restrictions, but the purpose of this filter is to
1590         // apply no restrictions at all. Therefore all restrictions implied by this filter
1591         // have been applied and we return true.
1592         return true;
1593
1594     case QContactFilter::InvalidFilter:
1595         if (isCanonicalFilterSupported(filter)) {
1596             qctWarn(QString::fromLatin1("%1: Bad arguments").arg(filterName(filter.type())));
1597             m_error = QContactManager::BadArgumentError;
1598             return false;
1599         }
1600
1601         break;
1602
1603     case QContactFilter::ActionFilter:
1604         break;
1605     }
1606
1607     qctWarn(QString::fromLatin1("%1: Unsupported filter type").arg(filterName(filter.type())));
1608     m_error = QContactManager::NotSupportedError;
1609     return false;
1610 }
1611
1612 ////////////////////////////////////////////////////////////////////////////////////////////////////
1613
1614 /// Returns a pointer to the field of @p detail with the name @p fieldName, or @c 0 if not found.
1615 const QTrackerContactDetailField *
1616 QTrackerScalarContactQueryBuilder::findField(const QTrackerContactDetail &detail,
1617                                              const QString &fieldName)
1618 {
1619     if (fieldName.isEmpty()) {
1620         return 0;
1621     }
1622
1623     const QTrackerContactDetailField *field = detail.field(fieldName);
1624
1625     if (0 == field) {
1626         qctWarn(QString::fromLatin1("Unsupported field %2 for %1 detail").
1627                 arg(detail.name(), fieldName));
1628     }
1629
1630     return field;
1631 }