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