Changes: Build with CUBI_EXPLICIT_CONSTRUCTORS
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / src / dao / querybuilder.cpp
1 /*********************************************************************************
2  ** This file is part of QtContacts tracker storage plugin
3  **
4  ** Copyright (c) 2010-2011 Nokia Corporation and/or its subsidiary(-ies).
5  **
6  ** Contact:  Nokia Corporation (info@qt.nokia.com)
7  **
8  ** GNU Lesser General Public License Usage
9  ** This file may be used under the terms of the GNU Lesser General Public License
10  ** version 2.1 as published by the Free Software Foundation and appearing in the
11  ** file LICENSE.LGPL included in the packaging of this file.  Please review the
12  ** following information to ensure the GNU Lesser General Public License version
13  ** 2.1 requirements will be met:
14  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
15  **
16  ** In addition, as a special exception, Nokia gives you certain additional rights.
17  ** These rights are described in the Nokia Qt LGPL Exception version 1.1, included
18  ** in the file LGPL_EXCEPTION.txt in this package.
19  **
20  ** Other Usage
21  ** Alternatively, this file may be used in accordance with the terms and
22  ** conditions contained in a signed written agreement between you and Nokia.
23  *********************************************************************************/
24
25 #include "querybuilder.h"
26
27 #include "contactdetailschema.h"
28 #include "resourceinfo.h"
29 #include "support.h"
30
31 #include <lib/logger.h>
32 #include <lib/settings.h>
33 #include <lib/phoneutils.h>
34 #include <lib/threadlocaldata.h>
35
36 #include <ontologies/maemo.h>
37
38 ////////////////////////////////////////////////////////////////////////////////////////////////////
39
40 CUBI_USE_NAMESPACE
41 CUBI_USE_NAMESPACE_RESOURCES
42
43 ////////////////////////////////////////////////////////////////////////////////////////////////////
44
45 static const QContactFilter::MatchFlags MatchFunctionFlags
46         (QContactFilter::MatchExactly | QContactFilter::MatchContains |
47          QContactFilter::MatchStartsWith | QContactFilter::MatchEndsWith);
48 static const QContactFilter::MatchFlags StringCompareFlags
49         (QContactFilter::MatchFixedString | MatchFunctionFlags);
50
51 ////////////////////////////////////////////////////////////////////////////////////////////////////
52
53 QctQueryBuilder::QctQueryBuilder(const QctContactDetailSchema &schema, const QString &managerUri)
54     : m_schema(schema)
55     , m_managerUri(managerUri)
56 {
57 }
58
59 QctQueryBuilder::~QctQueryBuilder()
60 {
61 }
62
63 const Variable &
64 QctQueryBuilder::contact()
65 {
66     static const Variable s(QLatin1String("contact"));
67     return s;
68 }
69
70 const Variable &
71 QctQueryBuilder::context()
72 {
73     static const Variable c(QLatin1String("context"));
74     return c;
75 }
76
77 const QChar
78 QctQueryBuilder::graphSeparator()
79 {
80     static const QChar separator(0x1C);
81     return separator;
82 }
83
84 const QChar
85 QctQueryBuilder::detailSeparator()
86 {
87     static const QChar separator(0x1E);
88     return separator;
89 }
90
91 const QChar
92 QctQueryBuilder::fieldSeparator()
93 {
94     static const QChar separator(0x1F);
95     return separator;
96 }
97
98 const QChar
99 QctQueryBuilder::listSeparator()
100 {
101     static const QChar separator(0x1D);
102     return separator;
103 }
104
105 ////////////////////////////////////////////////////////////////////////////////////////////////////
106
107 /// Returns the first part of the @p propertyChain including the property giving the detail uri.
108 /// If there is no property giving the detail uri, the result is empty.
109 static QctPropertyInfoList
110 chainToDetailUri(const QctPropertyInfoList &propertyChain)
111 {
112     QctPropertyInfoList result;
113     QctPropertyInfoList::ConstIterator it = propertyChain.constBegin();
114     QctPropertyInfoList::ConstIterator end = propertyChain.constEnd();
115     for(; it != end; ++it) {
116         result.append(*it);
117
118         if (it->hasDetailUri()) {
119             break;
120         }
121     }
122     if (it == end) {
123         result.clear();
124     }
125     return result;
126 }
127
128 /// Returns a ValueChain from the @p propertyInfoList
129 static ValueChain
130 valueChain(const QctPropertyInfoList &propertyInfoList)
131 {
132     ValueChain result;
133     foreach(const QctPropertyInfoBase &propertyInfo, propertyInfoList) {
134         result.append(propertyInfo.resource());
135     }
136     return result;
137 }
138
139 /// Returns a chain of predicate functions from the property @p chain on @p subject.
140 static Function
141 predicateFunctionChain(const QctPropertyInfoList& chain, const Value &subject, bool withGraph = false)
142 {
143     if (chain.empty()) {
144         return Function();
145     }
146
147     QctPropertyInfoList::ConstIterator it = chain.constBegin();
148
149     PredicateFunction current = it->predicateFunction().apply(subject);
150
151     for(it++; it != chain.constEnd(); it++) {
152         current = it->predicateFunction().apply(current);
153     }
154
155     if (not chain.last().singleValued()) {
156         current.setValueSeparator(QctQueryBuilder::listSeparator());
157     }
158     if (withGraph) {
159         current.setGraphSeparator(QctQueryBuilder::graphSeparator());
160     }
161     return current;
162 }
163
164 static PatternGroup
165 createPatternForPredicateChain(const Variable &subject,
166                                const QctPropertyInfoList &properties,
167                                const Variable &object)
168 {
169     Variable last = subject;
170     PatternGroup patterns;
171
172     for(QctPropertyInfoList::ConstIterator it = properties.constBegin();
173         it != properties.constEnd();
174         ++it) {
175         Variable o;
176
177         if (it == properties.constEnd() - 1) {
178             o = object;
179         }
180
181         if (it->isInverse()) {
182             patterns.addPattern(o, it->resource(), last);
183         } else {
184             patterns.addPattern(last, it->resource(), o);
185         }
186
187         last = o;
188     }
189
190     return patterns;
191 }
192
193 static PatternGroup
194 createPatternForCustomField(const Variable &subject,
195                             const QString &fieldName,
196                             const Variable &object)
197 {
198     PatternGroup patterns;
199
200     Variable field;
201
202     patterns.addPattern(subject, nao::hasProperty::resource(), field);
203
204     if (not fieldName.isEmpty()) {
205         patterns.addPattern(field, nao::propertyName::resource(), LiteralValue(fieldName));
206     }
207
208     patterns.addPattern(field, nao::propertyValue::resource(), object);
209
210     return patterns;
211 }
212
213 static PatternGroup
214 createPatternForCustomDetail(const Variable &subject,
215                              const QString &detailName,
216                              const QString &fieldName,
217                              const Variable &object)
218 {
219     PatternGroup patterns;
220
221     Variable detail;
222
223     patterns.addPattern(subject, nao::hasProperty::resource(), detail);
224     patterns.addPattern(detail, nao::propertyName::resource(), LiteralValue(detailName));
225     patterns.addPattern(createPatternForCustomField(detail, fieldName, object));
226
227     return patterns;
228 }
229
230 /// Returns a PrefixFunction which appends the iri of the graph of @p predicate to the @p value,
231 /// using the default graph separator.
232 /// Also works if @p predicate is in the default graph.
233 static PrefixFunction
234 applyConcatWithGraph(const Value &value, const PatternBase &predicate)
235 {
236     // to get the graph for predicates in a named graph as well as in the default graph
237     // a subselect like this is needed, with ?foo ?bar ?baz being the final predicate to the value:
238     // "tracker:coalesce((SELECT ?g WHERE { GRAPH ?g { ?foo ?bar ?baz }), "")
239     const Variable graph;
240     Select graphSelect;
241     graphSelect.addProjection(graph);
242     Graph graphPattern(graph);
243     graphPattern.addPattern(predicate);
244     graphSelect.addRestriction(graphPattern);
245
246     const PrefixFunction graphCoalesce = Functions::coalesce.apply(Filter(graphSelect), LiteralValue(QString()));
247
248     ValueChain valueChain;
249     valueChain
250         << value
251         << LiteralValue(QctQueryBuilder::graphSeparator())
252         << graphCoalesce;
253     return Functions::concat.apply(valueChain);
254 }
255
256 /// Inserts a copy of the value @p inter between all values of @p list.
257 static void
258 intercalate(ValueChain &list, const Value &inter)
259 {
260     for (int i = list.size() - 1; i > 0; --i) {
261         list.insert(i, inter);
262     }
263 }
264
265 /// Walks the @p chain backwards to find the part that can be bound with with predicate functions,
266 /// then returns that as @p projectionChain, the rest as @p restrictionPredicates.
267 /// If there is a predicate which defines ownership and it is not the last one in the chain, this and all those before
268 /// will be the added to the @p restrictionPredicates, even if they could be expressed by predicate functions.
269 /// This is done to be able to bind the subject and the object of that predicate to variables in the restriction part,
270 /// so the graph for that triple can be queried.
271 /// The last property in the chain is always set to be used as predicate function.
272 static void
273 splitPropertiesIntoProjectionsAndRestrictions(const QctPropertyInfoList &chain,
274                                               QctPropertyInfoList &projectionChain,
275                                               QctPropertyInfoList &restrictionChain)
276 {
277     QctPropertyInfoList::ConstIterator it = chain.constEnd();
278
279     // always add the last property
280     if (it != chain.constBegin()) {
281         projectionChain += *(--it);
282     }
283
284     while(it != chain.constBegin()) {
285         it--;
286
287         if (not it->singleValued() || it->definesOwnership()) {
288             it++;
289             break;
290         }
291
292         projectionChain += *it;
293     }
294
295     while(it != chain.constBegin()) {
296         restrictionChain.prepend(*(--it));
297     }
298 }
299
300 /// Adds a projection to @p query for the detail uri of @p field, using the @p chain on the @p subject.
301 /// If there is no such property, no projection will be added.
302 static void
303 bindDetailUri(const QctContactDetailField &field,
304               const Variable &subject,
305               const QctPropertyInfoList &chain,
306               Select &query)
307 {
308     const QctPropertyInfoList detailUriChain = chainToDetailUri(chain);
309
310     if (not detailUriChain.isEmpty()) {
311         if (detailUriChain.isSingleValued()) {
312             query.addProjection(predicateFunctionChain(detailUriChain, subject, field.hasOwner()));
313         } else {
314             qctWarn(QString::fromLatin1("DetailUri for field %1 on non single"
315                                         "valued property is not supported").
316                     arg(field.name()));
317             query.addProjection(LiteralValue(QString()));
318         }
319     }
320 }
321
322 static Select
323 bindUniqueFieldWithSubquery(const QctContactDetailField& field,
324                             const Variable &subject,
325                             const QctPropertyInfoList &chain,
326                             const QctSparqlTransform *transform)
327 {
328     Select subquery;
329     const Variable instance;
330     const PatternGroup propertyPattern = createPatternForPredicateChain(subject, chain, instance);
331
332     subquery.addRestriction(propertyPattern);
333
334     Function valueFunction = transform->apply(instance);
335
336     if (field.hasOwner()) {
337         valueFunction = applyConcatWithGraph(valueFunction, propertyPattern);
338     }
339     if (not chain.first().singleValued()) {
340         valueFunction = Functions::groupConcat.apply(valueFunction, LiteralValue(QctQueryBuilder::listSeparator()));
341     }
342
343     subquery.addProjection(valueFunction);
344
345     return subquery;
346 }
347
348 static QContactManager::Error
349 bindInstanceValues(const QctContactDetailField &field,
350                    const Variable &subject,
351                    const QctPropertyInfoList &chain,
352                    Select &query)
353 {
354     if (field.hasOwner()) {
355         Select select;
356         if (chain.count() != 1) {
357             qctWarn(QString::fromLatin1("Values-by-instances with a property chain "
358                                         "longer than 1 are not supported for field %1").
359                     arg(field.name()));
360             return QContactManager::NotSupportedError;
361         }
362         query.addProjection(bindUniqueFieldWithSubquery(field, subject, chain, QctIdTransform::instance()));
363     } else {
364         Function function = predicateFunctionChain(chain, subject);
365
366         function = Functions::trackerId.apply(function);
367         if (not chain.last().singleValued()) {
368             function = Functions::groupConcat.apply(function, LiteralValue(QctQueryBuilder::listSeparator()));
369         }
370         query.addProjection(function);
371     }
372
373     return QContactManager::NoError;
374 }
375
376 QContactManager::Error
377 QctQueryBuilder::bindUniqueDetailField(const QctContactDetailField &field, Select &query)
378 {
379     Variable subject = contact();
380
381     QctPropertyInfoList chain = field.propertyChain();
382
383     // if bound to an affiliation, switch subject to the query-wide context value
384     if (chain.first().iri() == nco::hasAffiliation::iri()) {
385         chain.removeFirst();
386         subject = context();
387     }
388
389     // will also not add a projection if the detailUri is bound by the nco:hasAffiliation
390     // which was removed from the chain just before. That affiliation is already fetched in the base query.
391     if (field.hasDetailUri()) {
392         bindDetailUri(field, subject, chain, query);
393     }
394
395     if (field.hasSubTypeClasses()) {
396         qctWarn(QString::fromLatin1("Subtypes by class not supported for field %1").
397                 arg(field.name()));
398         return QContactManager::NotSupportedError;
399     }
400
401     if (field.hasSubTypeProperties()) {
402         qctWarn(QString::fromLatin1("Subtypes by property not supported for field %1").
403                 arg(field.name()));
404         return QContactManager::NotSupportedError;
405     }
406
407     // FIXME: we don't handle inverse properties nor detailUri here
408     if (field.isInverse()) {
409         qctWarn(QString::fromLatin1("Inverse properties not supported for field %1").
410                 arg(field.name()));
411         return QContactManager::NotSupportedError;
412     }
413
414     const bool isRestrictedToInstances = (not field.allowableInstances().isEmpty());
415     const bool withGraph = (field.hasOwner() && not isRestrictedToInstances);
416     const bool definesOwnership = field.definesOwnership();
417
418     if (chain.isSingleValued(false) && not field.isWithoutMapping() && not definesOwnership) {
419         const QctSparqlTransform *transform = field.sparqlTransform();
420         if (isRestrictedToInstances) {
421             const QContactManager::Error error = bindInstanceValues(field, subject, chain, query);
422
423             if (error != QContactManager::NoError) {
424                 return error;
425             }
426         } else if (transform != 0) {
427             // field has a sparql-level transform function so we can't use the
428             // extended property function syntax to get the graph
429             query.addProjection (bindUniqueFieldWithSubquery(field, subject, chain, transform));
430         } else {
431             // get value(s) by predicate function chain
432             Function p = predicateFunctionChain(chain, subject, withGraph);
433
434             query.addProjection(p);
435         }
436     } else {
437         // get value(s) by subquery
438         Select s;
439         Variable last;
440         Variable lastForNormalRestrictions = last;
441         Pattern patternForGraph;
442         // split property chain into predicate function chain for projection and rest as restrictions
443         QctPropertyInfoList projectionChain;
444         ValueChain restrictionPredicates;
445
446         if (not field.isWithoutMapping()) {
447             QctPropertyInfoList restrictionChain;
448             splitPropertiesIntoProjectionsAndRestrictions(chain, projectionChain, restrictionChain);
449             restrictionPredicates = valueChain(restrictionChain);
450             if (definesOwnership) {
451                 // insert the last restriction predicate, which is the ownership defining one,
452                 // manually into the restriction part and by this define patternForGraph
453                 Value ownershipPredicate = restrictionPredicates.takeLast();
454                 // for restrictions with more than one predicate define a new variable for the pivot resource
455                 lastForNormalRestrictions = restrictionPredicates.isEmpty() ? subject : Variable();
456                 patternForGraph = Pattern(lastForNormalRestrictions, ownershipPredicate, last);
457                 s.addRestriction(patternForGraph);
458             }
459         } else {
460             restrictionPredicates = valueChain(chain);
461             restrictionPredicates.append(nao::hasProperty::resource());
462             projectionChain.append(QctPropertyInfo<nao::propertyValue>());
463             s.addRestriction(last, nao::propertyName::resource(), LiteralValue(field.name()));
464         }
465
466         // add restrictions to innerSelect
467         s.addRestriction(subject, restrictionPredicates, lastForNormalRestrictions);
468
469         // add predicate function chain as projection to subselect
470         Function projection;
471         if (definesOwnership) {
472             projection = applyConcatWithGraph(predicateFunctionChain(projectionChain, last), patternForGraph);
473         } else {
474             projection = predicateFunctionChain(projectionChain, last, withGraph);
475         }
476
477         if (projection.isValid()) {
478             if (isRestrictedToInstances) {
479                 if (field.hasOwner()) {
480                     qctWarn(QString::fromLatin1("Values-by-instance not supported for field %1").
481                             arg(field.name()));
482                     return QContactManager::NotSupportedError;
483                 } else {
484                     projection = Functions::trackerId.apply(projection);
485                     if (not projectionChain.last().singleValued()) {
486                         projection = Functions::groupConcat.apply(projection, LiteralValue(listSeparator()));
487                     }
488                 }
489             }
490
491             s.addProjection(projection);
492         } else {
493             s.addProjection(last);
494         }
495
496         query.addProjection(s);
497     }
498
499     return QContactManager::NoError;
500 }
501
502 QContactManager::Error
503 QctQueryBuilder::bindUniqueDetail(const QctContactDetail &detail, Select &query,
504                                   const QSet<QString> &fieldFilter)
505 {
506     if (not detail.isUnique()) {
507         qctWarn(QString::fromLatin1("Calling bindUniqueDetail on a non unique detail: %1").
508                 arg(detail.name()));
509         return QContactManager::UnspecifiedError;
510     }
511
512     foreach(const QctContactDetailField &field, detail.fields()) {
513         if (not fieldFilter.empty() && not fieldFilter.contains(field.name())) {
514             continue;
515         }
516
517         if (not field.hasPropertyChain()) {
518             continue;
519         }
520
521         const QContactManager::Error error = bindUniqueDetailField(field, query);
522
523         if (error != QContactManager::NoError) {
524             return error;
525         }
526
527         // For subtype properties we bind the field once with each property, and
528         // we'll check what we have when we fetch
529         if (field.hasSubTypeProperties()) {
530             foreach(const QctPropertyInfoBase &pi, field.subTypeProperties()) {
531                 QctContactDetailField f = field;
532
533                 if (f.propertyChain().empty()) {
534                     qctWarn(QString::fromLatin1("Can't apply property subtype without property "
535                                                 "chain for field %1").arg(field.name()));
536                     continue;
537                 }
538
539                 QctPropertyInfoList chain = f.propertyChain();
540                 chain.last() = pi;
541                 f.setPropertyChain(chain);
542
543                 // TODO: replace this with some few projections and restrictions as needed if possible
544                 const QContactManager::Error error = bindUniqueDetailField(f, query);
545
546                 if (error != QContactManager::NoError) {
547                     return error;
548                 }
549             }
550         }
551     }
552
553     return QContactManager::NoError;
554 }
555
556 static Function
557 bindMultivaluePropertyChainWithSubquery(const QctPropertyInfoList &chain,
558                                         const Variable &subject,
559                                         Select &innerSelect,
560                                         const QctSparqlTransform *transform)
561 {
562     const Variable instance;
563     Value value;
564
565     QctPropertyInfoList baseChain = chain;
566     const QctPropertyInfoBase postfix = baseChain.takeLast();
567
568     Variable last;
569     if (baseChain.isEmpty()) {
570         last = subject;
571     } else {
572         innerSelect.addRestriction(subject, valueChain(baseChain), last);
573     }
574     const Pattern postfixPattern(last, postfix.resource(), instance);
575     innerSelect.addRestriction(postfixPattern);
576
577     return applyConcatWithGraph(transform->apply(instance), postfixPattern);
578 }
579
580
581 Value
582 QctQueryBuilder::bindRestrictedValuesField(const QctContactDetailField &field,
583                                            const QctPropertyInfoList &chain,
584                                            const Variable &subject)
585 {
586     if (chain.isSingleValued(false) && field.allowableInstances().isEmpty()) {
587         // get value(s) by predicate function chain
588         Function projection = predicateFunctionChain(chain, subject, field.hasOwner());
589
590         return Filter(projection);
591     }
592     Select innerSelect;
593     Value value;
594
595     if (field.hasOwner()) {
596         value = bindMultivaluePropertyChainWithSubquery(chain, subject, innerSelect, QctIdTransform::instance());
597     } else {
598         const Variable instance;
599
600         // For instances (=resources) we use tracker:id, but for
601         // literal we just retrieve them
602         if (not field.allowableInstances().empty()) {
603             value = Functions::trackerId.apply(instance);
604         } else {
605             value = instance;
606         }
607         innerSelect.addRestriction(subject, valueChain(chain), instance);
608     }
609
610     innerSelect.addProjection(Functions::groupConcat.apply(value, LiteralValue(listSeparator())));
611     return Filter(innerSelect);
612 }
613
614 QContactManager::Error
615 QctQueryBuilder::bindCustomValues(const QctContactDetailField &field,
616                                   const Cubi::Variable &subject,
617                                   QctPropertyInfoList chain,
618                                   Value &result)
619 {
620     // If the field has a mapping but allows custom values, then the property is attached
621     // to the resource which is the subject of the last property in the chain, so the object
622     // of the penultimate. That is of course only valid if the value of the field is expressed
623     // by a literal, not if it's determined by a subclass of subproperty type (in which case
624     // the value depends on the class or the property)
625     if (not field.isWithoutMapping() && not field.hasSubTypeClasses() && not field.hasSubTypeProperties()) {
626         if (chain.isEmpty()) {
627             qctWarn(QString::fromLatin1("Empty prefix chain is not permitted for custom value field %1").
628                     arg(field.name()));
629             return QContactManager::UnspecifiedError;
630         }
631
632         if (chain.last().isInverse()) {
633             qctWarn(QString::fromLatin1("Custom values for fields with the last property inverse are"
634                                         "not supported for field %1").arg(field.name()));
635             return QContactManager::UnspecifiedError;
636         }
637
638         chain.removeLast();
639     }
640
641     if (not chain.isEmpty() && not chain.isSingleValued()) {
642         qctWarn(QString::fromLatin1("Custom fields on resources with multi valued chains not supported"
643                                     "for field %1").arg(field.name()));
644         return QContactManager::UnspecifiedError;
645     }
646
647     const Variable property(QLatin1String("p"));
648     chain += piHasProperty;
649     const PatternGroup propertyPatterns = createPatternForPredicateChain(subject, chain, property);
650
651     Value value = nao::propertyValue::function().apply(property);
652     if (field.hasOwner()) {
653         value = applyConcatWithGraph(value, propertyPatterns);
654     }
655
656     Select innerSelect;
657     innerSelect.addRestriction(propertyPatterns);
658     innerSelect.addRestriction(property,
659                                nao::propertyName::resource(),
660                                LiteralValue(field.name()));
661     innerSelect.addProjection(Functions::groupConcat.apply(value, LiteralValue(listSeparator())));
662
663     result = Filter(innerSelect);
664     return QContactManager::NoError;
665 }
666
667 Value
668 QctQueryBuilder::bindInverseFields(const QList<QctContactDetailField> &fields,
669                                    const QHash<QString, QctPropertyInfoList> &fieldChains,
670                                    const Variable &subject, const Pattern &prefixPattern)
671 {
672     ValueChain innerFields;
673     Select innerSelect;
674     QSet<QctPropertyInfoList> generatedRestrictions;
675     QHash<QString, Variable> inverseObjects;
676     // Maps the IRI of a PropertyInfoBase and the Pattern which is binding it
677     QHash<QString, Pattern> ownershipPatterns;
678
679     QList<QctContactDetailField>::ConstIterator field = fields.constEnd();
680     while(field != fields.constBegin()) {
681         field--;
682         QctPropertyInfoList restrictionChain, projectionChain;
683         QctPropertyInfoList chain = fieldChains.value(field->name());
684         QctPropertyInfoList::ConstIterator pi = chain.constBegin();
685         Pattern ownershipPattern = (field->hasOwner() ? prefixPattern : Pattern());
686
687         for(; pi != chain.constEnd(); ++pi) {
688             if (pi->isInverse()) {
689                 break;
690             }
691
692             restrictionChain.append(*pi);
693         }
694
695         if (pi == chain.constEnd()) {
696             // What? hasInverseProperty returned true and we didn't find one?
697             continue;
698         }
699
700         Variable inverseObject;
701
702         if (not generatedRestrictions.contains(restrictionChain)) {
703             Variable lastSubject = subject;
704             if (not restrictionChain.isEmpty()) {
705                 Variable inverseSubject;
706
707                 // We need to build the statement list manually here to store the
708                 // ownership statement (if any)
709                 const QctPropertyInfoList::ConstIterator lastProperty = restrictionChain.constEnd() - 1;
710                 for (QctPropertyInfoList::ConstIterator base = restrictionChain.constBegin();
711                      base != restrictionChain.constEnd(); ++base) {
712                     const Variable object = (base == lastProperty ? inverseSubject : Variable());
713                     const Pattern p(lastSubject, base->resource(), object);
714
715                     if (base->definesOwnership()) {
716                         ownershipPattern = p;
717                     }
718
719                     innerSelect.addRestriction(p);
720                     lastSubject = object;
721                 }
722             }
723
724             const Pattern lastRestriction(inverseObject, pi->resource(), lastSubject);
725
726             if (pi->definesOwnership()) {
727                 ownershipPattern = lastRestriction;
728             }
729
730             innerSelect.addRestriction(lastRestriction);
731
732             generatedRestrictions.insert(restrictionChain);
733             inverseObjects.insert(pi->iri(), inverseObject);
734             ownershipPatterns.insert(pi->iri(), ownershipPattern);
735         } else {
736             inverseObject = inverseObjects.value(pi->iri());
737             ownershipPattern = ownershipPatterns.value(pi->iri());
738         }
739
740         pi++;
741
742         if (pi == chain.constEnd()) {
743             if (field->hasOwner()) {
744                 innerFields.append(applyConcatWithGraph(inverseObject,
745                                                         ownershipPattern));
746             } else {
747                 innerFields.append(inverseObject);
748             }
749         } else {
750             QctPropertyInfoList ownershipChain;
751
752             for(; pi != chain.constEnd(); ++pi) {
753                 projectionChain.append(*pi);
754                 ownershipChain.append(*pi);
755
756                 if (pi->definesOwnership()) {
757                     break;
758                 }
759             }
760
761             for(; pi != chain.constEnd(); ++pi) {
762                 projectionChain.append(*pi);
763             }
764
765             Value v;
766
767             // FIXME: SubTypes, refactor
768
769             if ((field->hasOwner() && projectionChain.last().definesOwnership()) ||
770                 not field->hasOwner()) {
771                 // If ownership was on last property in the chain, we can fetch it
772                 // using extended function properties
773                 v = predicateFunctionChain(projectionChain, inverseObject, field->hasOwner());
774             } else if (projectionChain.length() == ownershipChain.length() &&
775                        field->hasOwner() && ownershipPattern != Pattern()) {
776                 // In that case the ownership statement belongs to the "straight" part, which
777                 // is in the "WHERE" of the scalar select for that inverse chain. We can
778                 // thus reuse the Pattern to fetch the graph.
779                 Function f = predicateFunctionChain(projectionChain, inverseObject, false);
780                 v = applyConcatWithGraph(f, ownershipPattern);
781             } else {
782                 // Here, it gets complicated, because the statement determining the ownership
783                 // is in the chain after the inverse property. So we'd probably need another
784                 // scalar select but that gets a bit insane, and the schema has no such cases
785                 // so far, leaving unimplemented
786                 qctWarn(QString::fromLatin1("Unsupported ownership for inverse field %1").
787                         arg(field->name()));
788                 continue;
789             }
790
791             innerFields.append(Functions::coalesce.apply(v, LiteralValue(QString())));
792         }
793     }
794
795     const QString coalesceString = QString(innerFields.size()-1, fieldSeparator());
796
797     if (innerFields.size() > 1) {
798         intercalate(innerFields, LiteralValue(fieldSeparator()));
799         innerSelect.addProjection(Functions::concat.apply(innerFields));
800     } else if (innerFields.size() == 1) {
801         const Function &f = static_cast<const Function&>(innerFields.first());
802         innerSelect.addProjection(f);
803     }
804
805     return Functions::coalesce.apply(Filter(innerSelect),
806                                      LiteralValue(coalesceString));
807 }
808
809 // FIXME: cut this monster into smaller pieces!
810 QContactManager::Error
811 QctQueryBuilder::bindMultiDetail(const QctContactDetail &detail, Select &query,
812                                  const Cubi::Variable &subject,
813                                  const QSet<QString> &fieldFilter)
814 {
815     // A non-unique detail can contain fields attached to one or more non-unique
816     // resources (ie a detail can aggregate properties from various RDF resources).
817     // We first list those different resources, and then for each of them we fetch
818     // all the properties' values grouped together using fn:concat.
819
820     QctPropertyInfoList prefixes;
821     QMultiHash<QctPropertyInfoBase, QctContactDetailField> fields;
822     // This hash stores the modified property chains for each field (key = field name)
823     QHash<QString, QctPropertyInfoList> fieldChains;
824
825     bool hasDetailUri = false;
826     QctPropertyInfoList detailUriChain;
827     QctPropertyInfoBase detailUriPrefix;
828     Variable detailUriSubject;
829
830     QMultiMap<QctPropertyInfoBase, QctContactDetailField> inverseFields;
831
832     foreach (const QctContactDetailField &field, detail.fields()) {
833         if (not fieldFilter.empty() && not fieldFilter.contains(field.name())) {
834             continue;
835         }
836
837         if (not field.hasPropertyChain()) {
838             continue;
839         }
840
841         QctPropertyInfoList chain = field.propertyChain();
842         const QctPropertyInfoBase prefix = chain.first();
843
844         // If needed, store information on how to retrieve the detailUri later.
845         // It will be prepended to the normal properties
846         if (not hasDetailUri && field.hasDetailUri()) {
847             hasDetailUri = true;
848             detailUriSubject = subject;
849             detailUriChain = chainToDetailUri(chain);
850             detailUriPrefix = prefix;
851         }
852
853         chain.removeFirst(); //TODO: rather add a baseChain to detailDefinition
854
855         // FIXME: This probably breaks if chain.first is an inverse property
856         fieldChains.insert(field.name(), chain);
857         if (chain.hasInverseProperty()) {
858             inverseFields.insert(prefix, field);
859             continue;
860         }
861
862         if (field.hasSubTypeProperties()) {
863             if (field.propertyChain().size() != 1) {
864                 qctWarn(QString::fromLatin1("Subtype properties with a property chain "
865                                             "longer than 1 are not supported for field %1").
866                         arg(field.name()));
867                 return QContactManager::NotSupportedError;
868             } else {
869                 foreach(const QctPropertyInfoBase &pi, field.subTypeProperties()) {
870                     prefixes.append(pi);
871                     fields.insert(pi, field);
872                 }
873             }
874         }
875
876         if (not prefixes.contains(prefix)) {
877             prefixes.append(prefix);
878         }
879         fields.insert(prefix, field);
880     }
881
882     foreach(const QctPropertyInfoBase &prefix, prefixes) {
883         // This "hack" is needed, else for details which are on affiliations but don't
884         // have context, we fetch the complete list for each affiliation. As a result
885         // we would end up with as many duplicates as we have affiliations.
886         const Variable object = (prefix == piHasAffiliation) ? context() : Variable();
887         const Pattern prefixPattern(subject, prefix.resource(), object);
888
889         ValueChain tokens; // TODO: the class name confuses, is a list and not a chain!
890
891         if (hasDetailUri && detailUriPrefix == prefix) {
892             if (not detailUriChain.empty() && prefix == detailUriChain.first()) {
893                 detailUriChain.removeFirst();
894                 detailUriSubject = object;
895             }
896
897             // no graph added for detail uris
898             if (detailUriChain.empty()) {
899                 tokens.append(detailUriSubject);
900             } else {
901                 tokens.append(predicateFunctionChain(detailUriChain, detailUriSubject));
902             }
903         }
904
905         // We walk the list in reverse order to preserve field order
906         // Fields containing inverse properties are bound in the end anyway
907         // since they use a nested scalar select
908         const QList<QctContactDetailField> prefixFields = fields.values(prefix);
909
910         // express fields with unshared prefix property and no other property in chain by predicate function
911         if (prefixFields.size() == 1) {
912             const QctContactDetailField &field = prefixFields.first();
913
914             QHash<QString,QctPropertyInfoList>::ConstIterator it = fieldChains.find(field.name());
915             if (it != fieldChains.constEnd() && it->isEmpty()) {
916                 PredicateFunction predicateFunction = prefix.predicateFunction().apply(subject);
917                 predicateFunction.setValueSeparator(QctQueryBuilder::detailSeparator());
918                 if (field.hasOwner()) {
919                     predicateFunction.setGraphSeparator(QctQueryBuilder::graphSeparator());
920                 }
921                 query.addProjection(predicateFunction);
922                 continue;
923             }
924         }
925         // normal algorithm
926         QList<QctContactDetailField>::ConstIterator field = prefixFields.constEnd();
927         while(field != prefixFields.constBegin()) {
928             field--;
929             Value v;
930             QctPropertyInfoList chain = fieldChains.value(field->name(), QctPropertyInfoList());
931
932             if (not field->isWithoutMapping()) {
933                 if (not chain.isEmpty()) {
934                     // flag whether the an empy result (null) needs to be replaced with an empty
935                     // string as it can happen with optional fields. not initializing by purpose
936                     // to let the compiler help us if we forget considering it in one of the many
937                     // branches following here
938                     bool needsEmptyStringCoalesce;
939
940                     // FIXME: What happens if we have instances but no property chain?
941                     // We don't use IRIs but trackerId for instances
942                     // In this case, we have to generate a scalar select, because we
943                     // can't apply tracker:id if we have various instances (v would not be
944                     // an IRI but a comma separated list of IRIs)
945                     if (field->restrictsValues()) {
946                         v = bindRestrictedValuesField(*field, chain, object);
947                         needsEmptyStringCoalesce = true;
948                     } else {
949                         QctPropertyInfoList projectionChain;
950                         QctPropertyInfoList restrictionChain;
951                         // This call will return the parts of the chain that can be bound
952                         // with predicate functions, however, in the part that is in the
953                         // scalar select restrictions, we still have to differentiate between
954                         // the part defining ownership, and the part that is here because of
955                         // multi-valued properties. The part that defines ownership will be
956                         // stored in patternForGraph, and the complete chain will go in the
957                         // scalar select restrictions.
958                         //
959                         // We also need a sub-select if there's a transform on
960                         // SPARQL-level; some of the functions can't operate
961                         // on predicate functions, only on sparql variables.
962                         splitPropertiesIntoProjectionsAndRestrictions(chain, projectionChain, restrictionChain);
963
964                         const QctSparqlTransform *transform = field->sparqlTransform();
965                         if (restrictionChain.isEmpty() && transform == 0) {
966                             const bool ownershipFetchWithPredicate = (not field->propertyChain().isEmpty()) &&
967                                                                      (field->propertyChain().last().definesOwnership());
968                             if (field->hasOwner() && field->definesOwnership() && not ownershipFetchWithPredicate) {
969                                 v = applyConcatWithGraph(predicateFunctionChain(projectionChain, object),
970                                                          prefixPattern);
971                                 needsEmptyStringCoalesce = false;
972                             } else {
973                                 v = predicateFunctionChain(chain, object, field->hasOwner());
974                                 needsEmptyStringCoalesce = true;
975                             }
976                         } else {
977                             // need to do subselect
978                             Select subselect;
979
980                             Variable last;
981                             Variable subject = object;
982                             Pattern ownershipPattern;
983                             for (QctPropertyInfoList::ConstIterator base = restrictionChain.constBegin();
984                                  base != restrictionChain.constEnd(); ++base) {
985                                 last = Variable();
986                                 const Pattern p(subject, base->resource(), last);
987
988                                 if (base->definesOwnership()) {
989                                     ownershipPattern = p;
990                                 }
991
992                                 subselect.addRestriction(p);
993                                 subject = last;
994                             }
995
996                             Function f;
997                             if (field->hasOwner() && field->definesOwnership()) {
998                                 f = applyConcatWithGraph(predicateFunctionChain(projectionChain, last),
999                                                          ownershipPattern);
1000                             } else {
1001                                 if (transform == 0) {
1002                                     f = predicateFunctionChain(projectionChain, last, field->hasOwner());
1003                                 } else {
1004                                     f = bindMultivaluePropertyChainWithSubquery(chain, object, subselect, transform);
1005                                 }
1006                             }
1007
1008                             subselect.addProjection(f);
1009                             v = Filter(subselect);
1010                             needsEmptyStringCoalesce = true;
1011                         }
1012                     }
1013
1014                     if (needsEmptyStringCoalesce) {
1015                         v = Functions::coalesce.apply(v, LiteralValue(QString()));
1016                     }
1017                 } else {
1018                     v = object;
1019                 }
1020
1021                 if (field->hasSubTypes()) {
1022                     if (field->hasSubTypeClasses()) {
1023                         // no graph added to subtypes by class
1024                         Select innerSelect;
1025                         Variable type(QLatin1String("t"));
1026                         innerSelect.addProjection(Functions::groupConcat.
1027                                                   apply(Functions::trackerId.apply(type),
1028                                                         LiteralValue(listSeparator())));
1029                         innerSelect.addRestriction(v, rdf::type::resource(), type);
1030                         tokens.append(Functions::coalesce.apply(Filter(innerSelect),
1031                                                                 LiteralValue(QString())));
1032                     }
1033                 } else {
1034                     if (v == object) {
1035                         if (field->hasOwner()) {
1036                             v = applyConcatWithGraph(v, prefixPattern);
1037                         }
1038                     }
1039                     tokens.append(v);
1040                 }
1041             }
1042
1043             if (field->permitsCustomValues()) {
1044                 Value customFieldSelect;
1045                 const QContactManager::Error error = bindCustomValues(*field, object, chain, customFieldSelect);
1046
1047                 if (error != QContactManager::NoError) {
1048                     return error;
1049                 }
1050
1051                 tokens.append(Functions::coalesce.apply(customFieldSelect, LiteralValue(QString())));
1052             }
1053
1054         }
1055
1056         // FIXME: this can probably be factorized better
1057         // FIXME: we don't handle DetailUri there
1058         if (inverseFields.contains(prefix)) {
1059             QList<QctContactDetailField> inversePrefixFields = inverseFields.values(prefix);
1060
1061             tokens.append(bindInverseFields(inversePrefixFields, fieldChains, object, prefixPattern));
1062         }
1063
1064
1065         Value jointTokens;
1066         if (tokens.size() > 1) {
1067             intercalate(tokens, LiteralValue(fieldSeparator()));
1068             jointTokens = Functions::concat.apply(tokens);
1069         } else if (tokens.size() == 1 ){
1070             jointTokens = tokens.first();
1071         } else {
1072             // We can have no fields if there was only one field, and it was pointing
1073             // to a value and not a resource
1074             jointTokens = object;
1075         }
1076         Select s;
1077         if (prefix != piHasAffiliation) {
1078             s.addRestriction(prefixPattern);
1079         }
1080         s.addProjection(Functions::groupConcat.apply(jointTokens, LiteralValue(detailSeparator())));
1081
1082         query.addProjection(s);
1083     }
1084
1085     return QContactManager::NoError;
1086 }
1087
1088 QContactManager::Error
1089 QctQueryBuilder::bindFields(const QctContactDetail &detail, Select &query,
1090                             const QSet<QString> &fieldFilter)
1091 {
1092     if (detail.isUnique()) {
1093         return bindUniqueDetail(detail, query, fieldFilter);
1094     } else {
1095         const Variable &subject = detail.hasContext() ? context() : contact();
1096         return bindMultiDetail(detail, query, subject, fieldFilter);
1097     }
1098 }
1099
1100 void
1101 QctQueryBuilder::bindCustomDetails(Cubi::Select &query, const QSet<QString> &detailFilter)
1102 {
1103     Select customDetailsQuery;
1104
1105     Variable detail(QLatin1String("customDetail"));
1106     Function detailName(nao::propertyName::function().apply(detail));
1107
1108     Variable field(QLatin1String("customField"));
1109     Function fieldName(nao::propertyName::function().apply(field));
1110
1111
1112     ValueChain valueConcatParams;
1113     Variable fieldValue(QLatin1String("value"));
1114     valueConcatParams.append(Functions::trackerId.apply(field));
1115     valueConcatParams.append(LiteralValue(QLatin1String(":")));
1116     valueConcatParams.append(fieldValue);
1117
1118     Select fieldValuesQuery;
1119     fieldValuesQuery.addProjection(Functions::groupConcat.
1120                                    apply(Functions::concat.apply(valueConcatParams),
1121                                          LiteralValue(listSeparator())));
1122     fieldValuesQuery.addRestriction(field, nao::propertyValue::resource(), fieldValue);
1123
1124     Select propertySelect;
1125     propertySelect.addProjection(Functions::groupConcat.
1126                                  apply(Functions::concat.apply(fieldName,
1127                                                                LiteralValue(fieldSeparator()),
1128                                                                Filter(fieldValuesQuery)),
1129                                        LiteralValue(fieldSeparator())));
1130     propertySelect.addRestriction(detail, nao::hasProperty::resource(), field);
1131
1132     ValueChain groupConcatParams;
1133     groupConcatParams.append(detailName);
1134     groupConcatParams.append(LiteralValue(fieldSeparator()));
1135     groupConcatParams.append(Filter(propertySelect));
1136     customDetailsQuery.addProjection(Functions::groupConcat.
1137                                      apply(Functions::concat.apply(groupConcatParams),
1138                                            LiteralValue(detailSeparator())));
1139     customDetailsQuery.addRestriction(contact(), nao::hasProperty::resource(), detail);
1140
1141     if (not detailFilter.empty()) {
1142         ValueList detailsToFetch;
1143
1144         foreach(const QString &filter, detailFilter) {
1145             LiteralValue name(qVariantFromValue(filter));
1146             detailsToFetch.addValue(name);
1147         }
1148
1149         customDetailsQuery.setFilter(Filter(Functions::in.apply(detailName, detailsToFetch)));
1150     }
1151
1152     query.addProjection(customDetailsQuery);
1153 }
1154
1155
1156 void QctQueryBuilder::bindHasMemberRelationships(Select& query)
1157 {
1158     // HasMember with contact in Second role, valid for both group and normal contacts
1159     Variable groupContact;
1160     const PrefixFunction groupContactGroupConcat =
1161         Functions::groupConcat.apply(Functions::trackerId.apply(groupContact),
1162                                      LiteralValue(QctQueryBuilder::listSeparator()));
1163     Select groupContactsSelect;
1164     groupContactsSelect.addProjection(groupContactGroupConcat);
1165     groupContactsSelect.addRestriction(contact(), nco::belongsToGroup::resource(), groupContact);
1166
1167     query.addProjection(groupContactsSelect);
1168
1169     if (m_schema.contactType() == QContactType::TypeGroup) {
1170         // HasMember with contact in First role, only applyable for group contacts
1171         Variable memberContact;
1172         const PrefixFunction memberContactGroupConcat =
1173             Functions::groupConcat.apply(Functions::trackerId.apply(memberContact),
1174                                         LiteralValue(QctQueryBuilder::listSeparator()));
1175         Select memberContactsSelect;
1176         memberContactsSelect.addProjection(memberContactGroupConcat);
1177         memberContactsSelect.addRestriction(memberContact, nco::belongsToGroup::resource(), contact());
1178
1179         query.addProjection(memberContactsSelect);
1180     }
1181 }
1182
1183 ////////////////////////////////////////////////////////////////////////////////////////////////////
1184
1185 #define DO_ENUM_VALUE(Type) \
1186     case Type: return QLatin1String(#Type)
1187
1188 static QString
1189 filterName(QContactFilter::FilterType type)
1190 {
1191     switch(type) {
1192         DO_ENUM_VALUE(QContactFilter::InvalidFilter);
1193         DO_ENUM_VALUE(QContactFilter::ContactDetailFilter);
1194         DO_ENUM_VALUE(QContactFilter::ContactDetailRangeFilter);
1195         DO_ENUM_VALUE(QContactFilter::ChangeLogFilter);
1196         DO_ENUM_VALUE(QContactFilter::ActionFilter);
1197         DO_ENUM_VALUE(QContactFilter::RelationshipFilter);
1198         DO_ENUM_VALUE(QContactFilter::IntersectionFilter);
1199         DO_ENUM_VALUE(QContactFilter::UnionFilter);
1200         DO_ENUM_VALUE(QContactFilter::LocalIdFilter);
1201         DO_ENUM_VALUE(QContactFilter::DefaultFilter);
1202     }
1203
1204     return QString::fromLatin1("QContactFilter::FilterType(%1)").arg(type);
1205 }
1206
1207 static QString
1208 eventName(QContactChangeLogFilter::EventType type)
1209 {
1210     switch(type) {
1211         DO_ENUM_VALUE(QContactChangeLogFilter::EventAdded);
1212         DO_ENUM_VALUE(QContactChangeLogFilter::EventChanged);
1213         DO_ENUM_VALUE(QContactChangeLogFilter::EventRemoved);
1214     }
1215
1216     return QString::fromLatin1("QContactChangeLogFilter::EventType(%1)").arg(type);
1217 }
1218
1219 #undef DO_ENUM_VALUE
1220
1221 ////////////////////////////////////////////////////////////////////////////////////////////////////
1222
1223 static QContactFilter::MatchFlags
1224 matchFunctionFlags(QContactFilter::MatchFlags flags)
1225 {
1226     return flags & MatchFunctionFlags;
1227 }
1228
1229 static QContactFilter::MatchFlags
1230 stringCompareFlags(QContactFilter::MatchFlags flags)
1231 {
1232     return flags & StringCompareFlags;
1233 }
1234
1235 Function
1236 QctQueryBuilder::matchFunction(QContactDetailFilter::MatchFlags flags,
1237                                Value variable, QVariant value)
1238 {
1239     Value param = variable;
1240
1241     // Convert variable and search value into lower-case for case-insensitive matching.
1242     // Must convert the value locally, instead of also using fn:lower-case because
1243     // fn:starts-with only takes literals as second argument, but not return values.
1244     if (QVariant::String == value.type() && 0 == (flags & QContactFilter::MatchCaseSensitive)) {
1245         param = Functions::lowerCase.apply(param);
1246         value.setValue(value.toString().toLower());
1247     }
1248
1249     Value valueObject = qctMakeCubiValue(value);
1250
1251     // If exact matching is requested for non-string literal still do case sensitive matching,
1252     // as case insensitive matching of numbers or URLs doesn't make much sense.
1253     if (QVariant::String != value.type() && 0 == stringCompareFlags(flags)) {
1254         return Functions::equal.apply(param, valueObject);
1255     }
1256
1257     // for case sensitive matching there are specialized functions...
1258     switch(matchFunctionFlags(flags)) {
1259     case QContactFilter::MatchContains:
1260         return Functions::contains.apply(param, valueObject);
1261     case QContactFilter::MatchStartsWith:
1262         return Functions::startsWith.apply(param, valueObject);
1263     case QContactFilter::MatchEndsWith:
1264         return Functions::endsWith.apply(param, valueObject);
1265     }
1266
1267     return Functions::equal.apply(param, valueObject);
1268 }
1269
1270 ////////////////////////////////////////////////////////////////////////////////////////////////////
1271
1272 template <typename T> static bool
1273 isCompoundFilterSupported(const QContactFilter &filter)
1274 {
1275     foreach(const QContactFilter &childFilter, static_cast<const T&>(filter).filters()) {
1276         if (not QctQueryBuilder::isFilterSupported(childFilter)) {
1277             return false;
1278         }
1279     }
1280
1281     return true;
1282 }
1283
1284 static bool
1285 isCanonicalFilterSupported(const QContactFilter &filter)
1286 {
1287     switch(filter.type()) {
1288     case QContactFilter::DefaultFilter:
1289     case QContactFilter::LocalIdFilter:
1290     case QContactFilter::ContactDetailFilter:
1291     case QContactFilter::ContactDetailRangeFilter:
1292     case QContactFilter::ChangeLogFilter:
1293     case QContactFilter::RelationshipFilter:
1294     case QContactFilter::InvalidFilter:
1295         return true;
1296
1297     case QContactFilter::IntersectionFilter:
1298         return isCompoundFilterSupported<QContactIntersectionFilter>(filter);
1299     case QContactFilter::UnionFilter:
1300         return isCompoundFilterSupported<QContactUnionFilter>(filter);
1301
1302     case QContactFilter::ActionFilter:
1303         break;
1304     }
1305
1306     return false;
1307 }
1308
1309 static inline QctClassInfoList::ConstIterator
1310 findClassInfoWithText(const QctClassInfoList &list, const QString &text)
1311 {
1312     QctClassInfoList::ConstIterator it;
1313     for (it = list.constBegin(); it != list.constEnd(); ++it) {
1314         if (it->text() == text) {
1315             break;
1316         }
1317     }
1318     return it;
1319 }
1320
1321 static inline QctPropertyInfoList::ConstIterator
1322 findPropertyInfoWithText(const QctPropertyInfoList &list, const QString &text)
1323 {
1324     QctPropertyInfoList::ConstIterator it;
1325     for (it = list.constBegin(); it != list.constEnd(); ++it) {
1326         if (it->text() == text) {
1327             break;
1328         }
1329     }
1330     return it;
1331 }
1332
1333 static QString
1334 findClassSubTypeIri(const QctContactDetailField *field, const QVariant &value)
1335 {
1336         // lookup class for given subtype
1337         const QString subType = value.toString();
1338         const QctClassInfoList &subTypeClasses = field->subTypeClasses();
1339         QctClassInfoList::ConstIterator it = findClassInfoWithText(subTypeClasses, subType);
1340
1341         // standard subtype found?
1342         if (it != subTypeClasses.constEnd()) {
1343             return it->iri();
1344         }
1345
1346         return QString();
1347 }
1348
1349 bool
1350 QctQueryBuilder::isFilterSupported(const QContactFilter &filter)
1351 {
1352     return isCanonicalFilterSupported(QContactManagerEngine::canonicalizedFilter(filter));
1353 }
1354
1355 static bool
1356 applyTypeCast(QContactFilter::MatchFlags flags,
1357               const QctContactDetailField *field, QVariant &value)
1358 {
1359     if (0 != stringCompareFlags(flags)) {
1360         return value.convert(QVariant::String);
1361     }
1362
1363     return field->makeValue(value, value);
1364 }
1365
1366 static Filter
1367 createSubTypePropertyFilter(const Variable &subject,
1368                             const Variable &object,
1369                             const QctPropertyInfoList &subtypes,
1370                             const QctPropertyInfoBase &type)
1371 {
1372     ValueChain operands;
1373
1374     // Filtering is only needed if the filtered value corresponds to a RDF
1375     // property that has sub-properties. Because we don't really have this
1376     // information in the schema, we assume the default value is the base
1377     // property (that's at least how it works for QContactUrl).
1378
1379     foreach(const QctPropertyInfoBase &subtype, subtypes) {
1380         if (subtype.iri() == type.iri()) {
1381             return Filter();
1382         }
1383     }
1384
1385     foreach(const QctPropertyInfoBase &subtype, subtypes) {
1386         Exists exists;
1387         exists.addPattern(subject, subtype.resource(), object);
1388         operands.append(Functions::not_.apply(Filter(exists)));
1389     }
1390
1391     return Filter(Functions::and_.apply(operands));
1392 }
1393
1394 QContactManager::Error
1395 QctQueryBuilder::bindTypeFilter(const QContactDetailFilter &filter, Filter &result)
1396 {
1397     const QString contactType(filter.value().toString());
1398
1399     // We don't need to repeat a filter with rdf:type here, since there is one base
1400     // query per contact type, and that base query already filters on the type:
1401     // ?_contact a rdf:PersonContact ...
1402     result = Filter(LiteralValue(schema().contactType() == contactType));
1403
1404     return QContactManager::NoError;
1405 }
1406
1407 void
1408 QctQueryBuilder::bindCustomDetailFilter(const QContactDetailFilter &filter,
1409                                         Exists &exists, Variable &subject)
1410 {
1411         Variable fieldValue(QString::fromLatin1("fieldValue"));
1412
1413         exists.addPattern(createPatternForCustomDetail(contact(),
1414                                                        filter.detailDefinitionName(),
1415                                                        filter.detailFieldName(),
1416                                                        fieldValue));
1417
1418         subject = fieldValue;
1419 }
1420
1421 PatternGroup
1422 QctQueryBuilder::bindWithoutMappingFilter(const QctContactDetailField *field,
1423                                           const Variable &subject, Variable &value)
1424 {
1425         value = Variable(QString::fromLatin1("fieldValue"));
1426
1427         return createPatternForCustomField(subject, field->name(), value);
1428 }
1429
1430 static bool
1431 phoneNumberHasDTMFCodes(const QString &number)
1432 {
1433     return number.contains(qctPhoneNumberDTMFChars());
1434 }
1435
1436 void
1437 QctQueryBuilder::bindDTMFNumberFilter(const QContactDetailFilter &filter,
1438                                       Exists &exists, const Variable &subject)
1439 {
1440     static const ValueChain phoneNumberChain = ValueChain() << nco::hasAffiliation::resource()
1441                                                             << nco::hasPhoneNumber::resource();
1442     Select mainSelect;
1443     QStringList numbers;
1444     ValueChain numberSelects;
1445
1446     const QString &value = filter.value().toString();
1447     numbers.append(value);
1448
1449     const int dtmfIndex = value.indexOf(qctPhoneNumberDTMFChars());
1450
1451     // If there was no DTMF code, or the DTMF code was the first character, then we just
1452     // do the exact match. That would be an unfortunate/silly case though.
1453     if (dtmfIndex > 0) {
1454         numbers.append(value.left(dtmfIndex));
1455     }
1456
1457     Variable phoneNumberResource;
1458
1459     foreach (const QString &number, numbers) {
1460         Select select;
1461         Variable contact;
1462
1463         select.addProjection(maemo::localPhoneNumber::function().apply(phoneNumberResource));
1464         select.addRestriction(contact, rdf::type::resource(), nco::PersonContact::resource());
1465         select.addRestriction(contact, phoneNumberChain, phoneNumberResource);
1466         select.addRestriction(phoneNumberResource, maemo::localPhoneNumber::resource(), LiteralValue(number));
1467
1468         numberSelects.append(Filter(select));
1469     }
1470
1471     if (numberSelects.size() > 1) {
1472         mainSelect.addProjection(Functions::coalesce.apply(numberSelects), subject);
1473     } else {
1474         const Select &select = static_cast<const Filter&>(numberSelects.first()).select();
1475         mainSelect.addProjection(select, subject);
1476     }
1477
1478     exists.addPattern(CompositionalSelect(mainSelect));
1479 }
1480
1481 template <class T> QContactManager::Error
1482 QctQueryBuilder::bindFilterDetail(const T &filter, Exists &exists,
1483                                   Variable &subject, bool hasCustomSubType,
1484                                   const QctPropertyInfoBase &propertySubtype)
1485 {
1486     static const uint unsupportedMatchFlags =
1487             // FIXME: add support for those flags
1488             QContactDetailFilter::MatchKeypadCollation;
1489
1490     if (filter.matchFlags() & unsupportedMatchFlags) {
1491         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
1492                 arg(filterName(filter.type())).
1493                 arg(filter.matchFlags() & unsupportedMatchFlags));
1494     }
1495
1496     // find detail and field definition
1497     const QctContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1498
1499     if (0 == detail) {
1500         bindCustomDetailFilter(filter, exists, subject);
1501         return QContactManager::NoError;
1502     }
1503
1504     if (filter.detailFieldName().isEmpty()) {
1505         qctWarn("field name must not be empty");
1506         return QContactManager::NotSupportedError;
1507     }
1508
1509     return bindFilterDetailField(filter, exists, subject, hasCustomSubType, propertySubtype);
1510 }
1511
1512 template <class T> QContactManager::Error
1513 QctQueryBuilder::bindFilterDetailField(const T &filter, Exists &exists,
1514                                        Variable &subject, bool hasCustomSubType,
1515                                        const QctPropertyInfoBase &propertySubtype)
1516 {
1517
1518     // find detail and field definition
1519     const QctContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1520     const QctContactDetailField *const field = findField(*detail, filter.detailFieldName());
1521
1522     if (0 == field) {
1523         qctWarn(QString::fromLatin1("Could not find schema for field %1 in detail %2").
1524                 arg(filter.detailFieldName(), filter.detailDefinitionName()));
1525         return QContactManager::NotSupportedError;
1526     }
1527
1528     QctPropertyInfoList properties = field->propertyChain();
1529
1530     // Custom subtypes are stored in nao:Property objects
1531     if (field->hasSubTypeClasses() && not hasCustomSubType) {
1532         properties.append(QctPropertyInfo<rdf::type>());
1533     }
1534
1535     if (not propertySubtype.iri().isEmpty()) {
1536         properties.removeLast();
1537     }
1538
1539     // do special processing for phone numbers
1540     if (not properties.empty() && properties.last().iri() == nco::phoneNumber::iri()) {
1541         if (filter.matchFlags().testFlag(QContactFilter::MatchPhoneNumber)) {
1542             // use the normalized phone number, stored in maemo:localPhoneNumber resource
1543             properties.removeLast();
1544             properties.append(field->computedProperties().first());
1545
1546             // this hack only works for maemo:localPhoneNumber
1547             Q_ASSERT(properties.last().iri() == maemo::localPhoneNumber::iri());
1548         }
1549     }
1550
1551     Variable filterValue;
1552
1553     if (detail->hasContext()) {
1554         properties.prepend(piHasAffiliation);
1555     }
1556
1557     exists.addPattern(createPatternForPredicateChain(contact(), properties, filterValue));
1558
1559     if (not propertySubtype.iri().isEmpty()) {
1560         Variable newValue;
1561         exists.addPattern(filterValue, propertySubtype.resource(), newValue);
1562
1563         exists.setFilter(createSubTypePropertyFilter(filterValue,
1564                                                      newValue,
1565                                                      field->subTypeProperties(),
1566                                                      propertySubtype));
1567         filterValue = newValue;
1568     }
1569
1570     if (field->isWithoutMapping() ||
1571         hasCustomSubType ||
1572         (field->hasSubTypeProperties() && propertySubtype == 0)) {
1573         Variable newValue;
1574         exists.addPattern(bindWithoutMappingFilter(field, filterValue, newValue));
1575         filterValue = newValue;
1576     }
1577
1578     subject = filterValue;
1579
1580     return QContactManager::NoError;
1581 }
1582
1583 QVariant
1584 QctQueryBuilder::normalizeFilterValue(const QctContactDetailField *field,
1585                                       const QContactDetailFilter::MatchFlags &flags,
1586                                       const QVariant &value, QContactManager::Error &error)
1587 {
1588     error = QContactManager::NoError;
1589
1590     if (value.isNull()) {
1591         return value;
1592     }
1593
1594     QVariant v = value;
1595
1596     // try casting the filter value
1597     if (not applyTypeCast(flags, field, v)) {
1598         qctWarn(QString::fromLatin1("Cannot apply required casts to filter value "
1599                                     "for field %1").arg(field->name()));
1600         error = QContactManager::BadArgumentError;
1601         return v;
1602     }
1603
1604     // do special processing for phone numbers
1605     // FIXME: find a more generic pattern for doing this modifications
1606     if (not field->propertyChain().empty() &&
1607             field->propertyChain().last().iri() == nco::phoneNumber::iri()) {
1608         // Phone numbers are almost impossible to parse (TODO: reference?)
1609         // So phone number handling is done based on heuristic:
1610         // it turned out that matching the last 7 digits works good enough (tm).
1611         // The actual number of digits to use is stored in QctSettings().localPhoneNumberLength().
1612         // The phone number's digits are stored in tracker using a maemo:localPhoneNumber property.
1613         if (QContactFilter::MatchEndsWith == matchFunctionFlags(flags)) {
1614             return qctMakeLocalPhoneNumber(v.toString());
1615         }
1616     }
1617
1618     if (field->hasSubTypeClasses()) {
1619         // FIXME: keep QUrl here. LiteralValue needs that to work properly,
1620         // need to find proper solution for this.
1621         const QUrl subTypeIri = findClassSubTypeIri(field, v);
1622
1623         // standard subtype found?
1624         if (subTypeIri.isValid()) {
1625             return subTypeIri;
1626         } else {
1627             if (not field->permitsCustomValues()) {
1628                 qctWarn(QString::fromLatin1("Unknown subtype %2 for field %1").
1629                         arg(field->name(), v.toString()));
1630                 error = QContactManager::BadArgumentError;
1631                 return QVariant();
1632             }
1633
1634             return v;
1635         }
1636     }
1637
1638     return v;
1639 }
1640
1641 QContactFilter::MatchFlags
1642 QctQueryBuilder::normalizeFilterMatchFlags(const QctContactDetailField *field,
1643                                            const QContactFilter::MatchFlags &matchFlags,
1644                                            const QVariant &value)
1645 {
1646     QContactFilter::MatchFlags flags = matchFlags;
1647
1648     // do special processing for phone numbers
1649     // FIXME: find a more generic pattern for doing this modifications
1650     if (not field->propertyChain().empty() &&
1651             field->propertyChain().last().iri() == nco::phoneNumber::iri()) {
1652         // Phone numbers are almost impossible to parse (TODO: reference?)
1653         // So phone number handling is done based on heuristic:
1654         // it turned out that matching the last 7 digits works good enough (tm).
1655         // The actual number of digits to use is stored in QctSettings().localPhoneNumberLength().
1656         // The phone number's digits are stored in tracker using a maemo:localPhoneNumber property.
1657
1658         if (flags.testFlag(QContactFilter::MatchPhoneNumber)) {
1659             const int localPhoneNumberLength = QctThreadLocalData::instance()->settings()->localPhoneNumberLength();
1660             const QString phoneNumberReference = value.toString();
1661
1662             // remove existing function flag and replace with normal string oriented one
1663             flags &= ~MatchFunctionFlags;
1664             if (phoneNumberReference.length() >= localPhoneNumberLength) {
1665                 flags |= QContactFilter::MatchEndsWith;
1666             } else {
1667                 flags |= QContactFilter::MatchExactly;
1668             }
1669         }
1670     }
1671
1672     return flags;
1673 }
1674
1675 template<class T> QContactManager::Error
1676 QctQueryBuilder::bindDetailFilterForAnyField(const T &filter,
1677                                              const QctContactDetail *const detail,
1678                                              Filter &result)
1679 {
1680     // Rewrite filter into union filter. This avoids bugs by reusing existing code.
1681     // Without such habit it is hard if not impossible to consider all special cases
1682     // in each code path.
1683     QContactUnionFilter unionFilter;
1684
1685     foreach(const QctContactDetailField &field, detail->fields()) {
1686         T subFilter = filter;
1687         subFilter.setDetailDefinitionName(detail->name(), field.name());
1688         unionFilter.append(subFilter);
1689     }
1690
1691     return bindFilter(unionFilter, result);
1692 }
1693
1694 template<class T> QContactManager::Error
1695 QctQueryBuilder::bindDetailContextFilter(const T &filter,
1696                                          const QctContactDetail * const detail,
1697                                          Filter &result)
1698 {
1699     // A detail can be composed of many fields, and they don't necessarily have
1700     // to be properties of a same resource. So what we do here is to check each
1701     // property of the detail without checking its value, and we check the label
1702     // on the affiliation.
1703     // Result is of the form: FILTER(rdfs:label(?affiliation) == "Foobar" && (EXISTS { prop1 } ||
1704     //                                                                        EXISTS { prop2 } ||
1705     //                                                                        ...
1706     //                                                                        EXISTS { propN }))
1707
1708     if (not detail->hasContext()) {
1709         // We return a false filter rather than an error here, since the group
1710         // schema has no details with a context.
1711         result = Filter(LiteralValue(QVariant(false)));
1712         return QContactManager::NoError;
1713     }
1714
1715     ValueChain andOperands;
1716     ValueChain orOperands;
1717
1718     Exists mainExists;
1719     Variable affiliation;
1720
1721     mainExists.addPattern(contact(), nco::hasAffiliation::resource(), affiliation);
1722
1723     if (not filter.value().isNull()) {
1724         andOperands.append(matchFunction(filter.matchFlags(),
1725                                          rdfs::label::function().apply(affiliation),
1726                                          filter.value()));
1727     }
1728
1729     foreach (const QctContactDetailField &field, detail->fields()) {
1730         if (field.isWithoutMapping()) {
1731             continue;
1732         }
1733
1734         Exists e;
1735
1736         e.addPattern(createPatternForPredicateChain(affiliation, field.propertyChain(), Variable()));
1737
1738         orOperands.append(Filter(e));
1739     }
1740
1741     if (orOperands.isEmpty()) {
1742         qctWarn(QString::fromLatin1("Filtering on context is not supported for detail without mapping %1").
1743                 arg(detail->name()));
1744         return QContactManager::NotSupportedError;
1745     }
1746
1747     andOperands.append(Functions::or_.apply(orOperands));
1748     mainExists.setFilter(Filter(Functions::and_.apply(andOperands)));
1749     result = Filter(mainExists);
1750
1751     return QContactManager::NoError;
1752 }
1753
1754 static bool
1755 qctMatches(const QString &sample, const QString &pattern, QContactFilter::MatchFlags flags)
1756 {
1757     if (not flags.testFlag(QContactFilter::MatchCaseSensitive)) {
1758         return qctMatches(sample.toLower(), pattern.toLower(),
1759                           flags | QContactFilter::MatchCaseSensitive);
1760     }
1761
1762     switch(matchFunctionFlags(flags)) {
1763     case QContactFilter::MatchContains:
1764         return sample.contains(pattern);
1765     case QContactFilter::MatchStartsWith:
1766         return sample.startsWith(pattern);
1767     case QContactFilter::MatchEndsWith:
1768         return sample.endsWith(pattern);
1769     }
1770
1771     return sample == pattern;
1772 }
1773
1774 static bool
1775 qctMatches(const QVariant &sample, const QVariant &pattern, QContactFilter::MatchFlags flags)
1776 {
1777     return qctMatches(sample.toString(), pattern.toString(), flags);
1778 }
1779
1780 QContactManager::Error
1781 QctQueryBuilder::bindFilter(QContactDetailFilter filter, Filter &result)
1782 {
1783     if (filter.detailDefinitionName() == QContactType::DefinitionName &&
1784         filter.detailFieldName() == QContactType::FieldType) {
1785         return bindTypeFilter(filter, result);
1786     }
1787
1788     bool hasSubTypeClasses = false;
1789     bool hasCustomSubTypeClasses = false;
1790     QctPropertyInfoList subTypeProperties;
1791
1792     const QctContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1793     QctPropertyInfoBase propertySubtype;
1794
1795     ValueList instanceValues;
1796     bool hasInstances = false;
1797
1798     if (detail != 0) {
1799         if (filter.detailFieldName().isEmpty()) {
1800             return bindDetailFilterForAnyField(filter, detail, result);
1801         }
1802
1803         if (filter.detailFieldName() == QContactDetail::FieldContext) {
1804             return bindDetailContextFilter(filter, detail, result);
1805         }
1806
1807         const QctContactDetailField *const field = findField(*detail, filter.detailFieldName());
1808
1809         if (field != 0) {
1810             hasSubTypeClasses = field->hasSubTypeClasses();
1811             hasCustomSubTypeClasses = (hasSubTypeClasses &&
1812                                        findClassSubTypeIri(field, filter.value()).isEmpty());
1813
1814             filter.setMatchFlags(normalizeFilterMatchFlags(field, filter.matchFlags(), filter.value()));
1815
1816             if (not field->allowableInstances().isEmpty()) {
1817                 static const QContactFilter::MatchFlags supportedFlags =
1818                         QContactFilter::MatchCaseSensitive | StringCompareFlags;
1819
1820                 if (0 != (filter.matchFlags() & ~supportedFlags)) {
1821                     qctWarn("Unsupported match flags for instance field");
1822                     return QContactManager::NotSupportedError;
1823                 }
1824
1825                 foreach(const QctInstanceInfoBase &instance, field->allowableInstances()) {
1826                     if (qctMatches(instance.value(), filter.value(), filter.matchFlags())) {
1827                         instanceValues.addValue(instance.resource());
1828                     }
1829                 }
1830
1831                 hasInstances = true;
1832             } else {
1833                 QContactManager::Error error = QContactManager::UnspecifiedError;
1834                 filter.setValue(normalizeFilterValue(field, filter.matchFlags(), filter.value(), error));
1835
1836                 if (error != QContactManager::NoError) {
1837                     return error;
1838                 }
1839             }
1840
1841             subTypeProperties = field->subTypeProperties();
1842
1843             if (field->hasSubTypeProperties()) {
1844                 const QString subType = filter.value().toString();
1845
1846                 if (not field->propertyChain().empty() && field->defaultValue() == subType) {
1847                     propertySubtype = field->propertyChain().last();
1848                 } else {
1849                     QctPropertyInfoList::ConstIterator it = findPropertyInfoWithText(subTypeProperties, subType);
1850
1851                     if (it != subTypeProperties.constEnd()) {
1852                         propertySubtype = *it;
1853                     }
1854                 }
1855             }
1856
1857         }
1858     }
1859
1860     Exists exists;
1861     Variable subject;
1862
1863     const QContactManager::Error error =
1864             bindFilterDetail<QContactDetailFilter>(filter, exists, subject,
1865                                                    hasCustomSubTypeClasses, propertySubtype);
1866
1867     if (error != QContactManager::NoError) {
1868         return error;
1869     }
1870
1871     // This if block works for both the wildcard filter (a field was bound in
1872     // an EXISTS block, but null value = wildcard filter) and "exists" filter
1873     // (all detail fields were bound in bindFilterDetail, but no check on value
1874     // since we just check for detail presence).
1875     // We also don't set a filter for subtype properties, since filtering is
1876     // done on the predicate (and not the object), in bindFilterDetail.
1877     if (filter.value().isNull() ||
1878         (not subTypeProperties.empty() && not propertySubtype.iri().isEmpty())) {
1879         result = Filter(exists);
1880         return QContactManager::NoError;
1881     }
1882
1883     // Phone numbers with DTMF code need a special query for the fallback to the non-DTMF version
1884     const bool hasDTMFCodes =
1885             (filter.matchFlags().testFlag(QContactDetailFilter::MatchPhoneNumber) &&
1886              filter.detailDefinitionName() == QContactPhoneNumber::DefinitionName &&
1887              filter.detailFieldName() == QContactPhoneNumber::FieldNumber &&
1888              phoneNumberHasDTMFCodes(filter.value().toString()));
1889
1890     if (hasSubTypeClasses) {
1891         exists.setFilter(Filter(Functions::equal.apply(subject, qctMakeCubiValue(filter.value()))));
1892     } else if (hasDTMFCodes) {
1893         bindDTMFNumberFilter(filter, exists, subject);
1894     } else if (hasInstances) {
1895         const QList<Value> valueList = instanceValues.values();
1896         switch(valueList.count()) {
1897         case 0:
1898             exists.setFilter(Filter(LiteralValue(false)));
1899             break;
1900         case 1:
1901             exists.setFilter(Filter(Functions::equal.apply(subject, valueList.first())));
1902             break;
1903         default:
1904             exists.setFilter(Filter(Functions::in.apply(subject, instanceValues)));
1905             break;
1906         }
1907     } else {
1908         exists.setFilter(Filter(matchFunction(filter.matchFlags(), subject, filter.value())));
1909     }
1910
1911     result = Filter(exists);
1912     return QContactManager::NoError;
1913 }
1914
1915 QContactManager::Error
1916 QctQueryBuilder::bindFilter(QContactDetailRangeFilter filter, Filter &result)
1917 {
1918     const QctContactDetail *const detail = m_schema.detail(filter.detailDefinitionName());
1919
1920     if (detail != 0) {
1921         if (filter.detailFieldName().isEmpty()) {
1922             return bindDetailFilterForAnyField(filter, detail, result);
1923         }
1924
1925         const QctContactDetailField *const field = findField(*detail, filter.detailFieldName());
1926
1927         if (field != 0) {
1928             filter.setMatchFlags(normalizeFilterMatchFlags(field, filter.matchFlags()));
1929             QContactManager::Error error = QContactManager::UnspecifiedError;
1930             const QVariant value1 = normalizeFilterValue(field, filter.matchFlags(),
1931                                                          filter.minValue(), error);
1932
1933             if (error != QContactManager::NoError) {
1934                 return error;
1935             }
1936
1937             error = QContactManager::UnspecifiedError;
1938             const QVariant value2 = normalizeFilterValue(field, filter.matchFlags(),
1939                                                          filter.maxValue(), error);
1940
1941             if (error != QContactManager::NoError) {
1942                 return error;
1943             }
1944
1945             filter.setRange(value1, value2, filter.rangeFlags());
1946         }
1947     }
1948
1949     Exists exists;
1950     Variable subject;
1951
1952     const QContactManager::Error error =
1953             bindFilterDetail<QContactDetailRangeFilter>(filter, exists, subject);
1954
1955     if (error != QContactManager::NoError) {
1956         return error;
1957     }
1958
1959     ValueChain operands;
1960
1961     if (not filter.minValue().isNull()) {
1962         // IncludeLower = 0, ExcludeLower = 2
1963         if (not filter.rangeFlags().testFlag(QContactDetailRangeFilter::ExcludeLower)) {
1964             operands.append(Functions::lessThanOrEqual.apply(qctMakeCubiValue(filter.minValue()), subject));
1965         } else {
1966             operands.append(Functions::lessThan.apply(qctMakeCubiValue(filter.minValue()), subject));
1967         }
1968     }
1969
1970     if (not filter.maxValue().isNull()) {
1971         // IncludeUpper = 1, ExcludeUpper = 0
1972         if (filter.rangeFlags().testFlag(QContactDetailRangeFilter::IncludeUpper)) {
1973             operands.append(Functions::lessThanOrEqual.apply(subject, qctMakeCubiValue(filter.maxValue())));
1974         } else {
1975             operands.append(Functions::lessThan.apply(subject, qctMakeCubiValue(filter.maxValue())));
1976         }
1977     }
1978
1979     exists.setFilter(Filter(Functions::and_.apply(operands)));
1980     result = Filter(exists);
1981
1982     return QContactManager::NoError;
1983 }
1984
1985 QContactManager::Error
1986 QctQueryBuilder::bindFilter(const QContactLocalIdFilter &filter, Cubi::Filter &result)
1987 {
1988     if (filter.ids().isEmpty()) {
1989         qctWarn(QString::fromLatin1("%1: Local contact id list cannot be empty").
1990                 arg(filterName(filter.type())));
1991         return QContactManager::BadArgumentError;
1992     }
1993
1994     ValueList ids;
1995
1996     foreach(QContactLocalId id, filter.ids()) {
1997         ids.addValue(LiteralValue(qVariantFromValue(id)));
1998     }
1999
2000     result = Filter(Functions::in.apply(Functions::trackerId.apply(contact()), ids));
2001     return QContactManager::NoError;
2002 }
2003
2004 QContactManager::Error
2005 QctQueryBuilder::bindFilter(const QContactIntersectionFilter &filter, Cubi::Filter &result)
2006 {
2007     ValueChain filters;
2008
2009     foreach(const QContactFilter &childFilter, filter.filters()) {
2010         Filter filter;
2011         const QContactManager::Error error = bindFilter(childFilter, filter);
2012
2013         if (QContactManager::NoError != error) {
2014             return error;
2015         }
2016
2017         filters.append(filter);
2018     }
2019
2020     result = Filter(Functions::and_.apply(filters));
2021
2022     return QContactManager::NoError;
2023 }
2024
2025 QContactManager::Error
2026 QctQueryBuilder::bindFilter(const QContactUnionFilter &filter, Cubi::Filter &result)
2027 {
2028     ValueChain filters;
2029
2030     foreach(const QContactFilter &childFilter, filter.filters()) {
2031         Filter filter;
2032         const QContactManager::Error error = bindFilter(childFilter, filter);
2033
2034         if (QContactManager::NoError != error) {
2035             return error;
2036         }
2037
2038         filters.append(filter);
2039     }
2040
2041     result = Filter(Functions::or_.apply(filters));
2042
2043     return QContactManager::NoError;
2044 }
2045
2046 QContactManager::Error
2047 QctQueryBuilder::bindFilter(const QContactChangeLogFilter &filter, Filter &result)
2048 {
2049     Exists exists;
2050     Variable contentCreated;
2051     Variable contentLastModified;
2052
2053     switch(filter.eventType()) {
2054     case QContactChangeLogFilter::EventAdded:
2055         exists.addPattern(contact(), nie::contentCreated::resource(), contentCreated);
2056         exists.setFilter(Filter(Functions::greaterThanOrEqual.apply(contentCreated, LiteralValue(filter.since()))));
2057         result = Filter(exists);
2058         return QContactManager::NoError;
2059
2060     case QContactChangeLogFilter::EventChanged:
2061         exists.addPattern(contact(), nie::contentLastModified::resource(), contentLastModified);
2062         exists.setFilter(Filter(Functions::greaterThanOrEqual.apply(contentLastModified, LiteralValue(filter.since()))));
2063         result = Filter(exists);
2064         return QContactManager::NoError;
2065
2066     case QContactChangeLogFilter::EventRemoved:
2067         break;
2068     }
2069
2070     qctWarn(QString::fromLatin1("%1: Unsupported event type: %2").
2071             arg(filterName(filter.type()), eventName(filter.eventType())));
2072
2073     return QContactManager::NotSupportedError;
2074 }
2075
2076 QContactManager::Error
2077 QctQueryBuilder::bindFilter(const QContactRelationshipFilter &filter, Filter &result)
2078 {
2079     if (filter.relationshipType() == QContactRelationship::HasMember) {
2080         const QContactId relatedContactId = filter.relatedContactId();
2081
2082         // only HasMember relationships between local groups and local contacts are stored ATM
2083         if (relatedContactId.managerUri() == m_managerUri) {
2084             LiteralValue rdfLocalId(qVariantFromValue(relatedContactId.localId()));
2085
2086             const QContactRelationship::Role relatedContactRole = filter.relatedContactRole();
2087             ValueChain restrictions;
2088
2089             // filter for all contacts in a given group?
2090             if (relatedContactRole == QContactRelationship::First
2091                 || relatedContactRole == QContactRelationship::Either) {
2092                 const Variable rdfGroupContact(QString::fromLatin1("group"));
2093                 const Value rdfGroupContactId = Functions::trackerId.apply(rdfGroupContact);
2094
2095                 Exists exists;
2096                 exists.addPattern(rdfGroupContact, rdf::type::resource(), nco::Contact::resource());
2097                 exists.addPattern(rdfGroupContact, rdf::type::resource(), nco::ContactGroup::resource());
2098                 exists.addPattern(contact(), nco::belongsToGroup::resource(), rdfGroupContact);
2099                 exists.setFilter(Filter(Functions::equal.apply(rdfGroupContactId, rdfLocalId)));
2100                 restrictions += Filter(exists);
2101             }
2102
2103             // filter for all groups a given contact is in?
2104             if (relatedContactRole == QContactRelationship::Second
2105                 || relatedContactRole == QContactRelationship::Either) {
2106                 const Variable rdfContact(QString::fromLatin1("member"));
2107                 const Value rdfContactId = Functions::trackerId.apply(rdfContact);
2108
2109                 Exists exists;
2110                 // not PersonContact, groups could also be part of other groups
2111                 exists.addPattern(rdfContact, rdf::type::resource(), nco::Contact::resource());
2112                 exists.addPattern(rdfContact, nco::belongsToGroup::resource(), contact());
2113                 exists.setFilter(Filter(Functions::equal.apply(rdfContactId, rdfLocalId)));
2114                 restrictions += Filter(exists);
2115             }
2116
2117             if (not restrictions.isEmpty()) {
2118                 result = Filter(Functions::or_.apply(restrictions));
2119             } else {
2120                 result = Filter();
2121             }
2122         } else {
2123             qctWarn(QString::fromLatin1("Relationships to contacts of the %1 "
2124                                         "contact manager are not stored here (%2).").
2125                     arg(relatedContactId.managerUri(), m_managerUri));
2126
2127             // none at all available for relations to ones from other manager ATM
2128             Exists exists;
2129             exists.setFilter(Filter(LiteralValue(QVariant(false))));
2130             result = Filter(exists);
2131         }
2132
2133         return QContactManager::NoError;
2134     }
2135
2136     qctWarn(QString::fromLatin1("%1: Unsupported relationship type: %2").
2137             arg(filterName(filter.type()), filter.relationshipType()));
2138
2139     return QContactManager::NotSupportedError;
2140 }
2141
2142 void
2143 QctQueryBuilder::bindInvalidFilter(Cubi::Filter &result)
2144 {
2145     result = Filter(LiteralValue(QVariant(false)));
2146 }
2147
2148 QContactManager::Error
2149 QctQueryBuilder::bindFilter(const QContactFilter &filter, Cubi::Filter &result)
2150 {
2151     QContactFilter canonicalFilter(QContactManagerEngine::canonicalizedFilter(filter));
2152
2153     switch(canonicalFilter.type()) {
2154     case QContactFilter::LocalIdFilter:
2155         return bindFilter(static_cast<const QContactLocalIdFilter&>(canonicalFilter), result);
2156     case QContactFilter::IntersectionFilter:
2157         return bindFilter(static_cast<const QContactIntersectionFilter&>(canonicalFilter), result);
2158     case QContactFilter::UnionFilter:
2159         return bindFilter(static_cast<const QContactUnionFilter&>(canonicalFilter), result);
2160     case QContactFilter::ContactDetailFilter:
2161         return bindFilter(static_cast<const QContactDetailFilter&>(canonicalFilter), result);
2162     case QContactFilter::ContactDetailRangeFilter:
2163         return bindFilter(static_cast<const QContactDetailRangeFilter&>(canonicalFilter), result);
2164     case QContactFilter::ChangeLogFilter:
2165         return bindFilter(static_cast<const QContactChangeLogFilter&>(canonicalFilter), result);
2166     case QContactFilter::RelationshipFilter:
2167         return bindFilter(static_cast<const QContactRelationshipFilter&>(canonicalFilter), result);
2168     case QContactFilter::DefaultFilter:
2169         // We didn't apply any explicit restrictions, but the purpose of this filter is to
2170         // apply no restrictions at all. Therefore all restrictions implied by this filter
2171         // have been applied and we return true.
2172         return QContactManager::NoError;
2173     case QContactFilter::InvalidFilter:
2174         bindInvalidFilter(result);
2175         return QContactManager::NoError;
2176
2177     case QContactFilter::ActionFilter:
2178         break;
2179     }
2180
2181     qctWarn(QString::fromLatin1("%1: Unsupported filter type").arg(filterName(filter.type())));
2182
2183     return QContactManager::NotSupportedError;
2184 }
2185
2186 QContactManager::Error
2187 QctQueryBuilder::bindSortOrders(const QList<QContactSortOrder> &orders,
2188                                 QList<Cubi::OrderComparator> &result)
2189 {
2190     foreach (const QContactSortOrder &o, orders) {
2191         if (o.blankPolicy() != QContactSortOrder::BlanksFirst) {
2192             qctWarn("Only BlanksFirst policy is supported. Falling back to in memory sorting");
2193             return QContactManager::NotSupportedError;
2194         }
2195
2196         if (o.caseSensitivity() != Qt::CaseInsensitive) {
2197             qctWarn("Only case insensitive sorting is supported. Falling back to in memory sorting");
2198             return QContactManager::NotSupportedError;
2199         }
2200
2201         Select orderSelect;
2202
2203         const Variable object;
2204
2205         orderSelect.addProjection(object);
2206
2207         if (schema().isSyntheticDetail(o.detailDefinitionName())) {
2208             qctWarn(QString::fromLatin1("Sorting on synthesized detail %1 is not supported."
2209                                         "Falling back to in memory sorting").
2210                     arg(o.detailDefinitionName()));
2211             return QContactManager::NotSupportedError;
2212         }
2213
2214         const QctContactDetail *detail = schema().detail(o.detailDefinitionName());
2215
2216         if (detail == 0) {
2217             orderSelect.addRestriction(createPatternForCustomDetail(contact(),
2218                                                                     o.detailDefinitionName(),
2219                                                                     o.detailFieldName(),
2220                                                                     object));
2221         } else {
2222             const Variable subject = detail->hasContext() ? context() : contact();
2223             const QctContactDetailField *field = findField(*detail, o.detailFieldName());
2224
2225             if (field == 0) {
2226                 qctWarn(QString::fromLatin1("Sorting on field %1 of detail %2 is not supported: it "
2227                                             "is not in the schema. Falling back to in memory sorting").
2228                         arg(o.detailFieldName(), o.detailDefinitionName()));
2229                 return QContactManager::NotSupportedError;
2230             }
2231
2232             if (field->isSynthesized()) {
2233                 qctWarn(QString::fromLatin1("Sorting on field %1 of detail %2 is not supported: it "
2234                                             "is synthesized. Falling back to in memory sorting").
2235                         arg(o.detailFieldName(), o.detailDefinitionName()));
2236                 return QContactManager::NotSupportedError;
2237             }
2238
2239             if (field->hasSubTypes()) {
2240                 qctWarn(QString::fromLatin1("Sorting on field %1 of detail %2 is not supported: it "
2241                                             "is a subtype field. Falling back to in memory sorting").
2242                         arg(o.detailFieldName(), o.detailDefinitionName()));
2243                 return QContactManager::NotSupportedError;
2244             }
2245
2246             const QctPropertyInfoList &chain = field->propertyChain();
2247             const Variable last = (field->isWithoutMapping() ? Variable() : object);
2248
2249             orderSelect.addRestriction(createPatternForPredicateChain(subject, chain, last));
2250
2251             if (field->isWithoutMapping()) {
2252                 orderSelect.addRestriction(createPatternForCustomField(last,
2253                                                                        o.detailFieldName(),
2254                                                                        object));
2255             }
2256         }
2257
2258         orderSelect.setLimit(1);
2259
2260         result.append(OrderComparator(Filter(orderSelect),
2261                                       o.direction() == Qt::AscendingOrder ? OrderComparator::Ascending
2262                                                                           : OrderComparator::Descending));
2263     }
2264
2265     return QContactManager::NoError;
2266 }
2267
2268 ////////////////////////////////////////////////////////////////////////////////////////////////////
2269
2270 const QctContactDetailField *
2271 QctQueryBuilder::findField(const QctContactDetail &detail, const QString &fieldName)
2272 {
2273     if (fieldName.isEmpty()) {
2274         return 0;
2275     }
2276
2277     const QctContactDetailField *field = detail.field(fieldName);
2278
2279     if (0 == field) {
2280         qctWarn(QString::fromLatin1("Unsupported field %2 for %1 detail").
2281                 arg(detail.name(), fieldName));
2282     }
2283
2284     return field;
2285 }