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