Changes: Tidy MR#78 a bit.
[qtcontacts-tracker:qtcontacts-tracker.git] / src / dao / cubiquerybuilder.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "cubiquerybuilder.h"
43
44 #include "contactdetailschema.h"
45 #include "logger.h"
46 #include "resourceinfo.h"
47 #include "support.h"
48
49 #include <dao/cubidefines.h>
50 #include <dao/ontologies/maemo.h>
51 #include <lib/settings.h>
52
53 ////////////////////////////////////////////////////////////////////////////////////////////////////
54
55 CUBI_USE_NAMESPACE;
56 CUBI_USE_NAMESPACE_RESOURCES;
57
58 ////////////////////////////////////////////////////////////////////////////////////////////////////
59
60 template<bool> class StaticAssert;
61 template<> class StaticAssert<true> {};
62
63 ////////////////////////////////////////////////////////////////////////////////////////////////////
64
65 static const PropertyInfoList workContextChain(piHasAffiliation);
66 static const QContactFilter::MatchFlags MatchFunctionFlags
67         (QContactFilter::MatchExactly | QContactFilter::MatchContains |
68          QContactFilter::MatchStartsWith | QContactFilter::MatchEndsWith);
69 static const QContactFilter::MatchFlags StringCompareFlags
70         (QContactFilter::MatchFixedString | MatchFunctionFlags);
71
72 ////////////////////////////////////////////////////////////////////////////////////////////////////
73
74 static QContactFilter::MatchFlags
75 matchFunctionFlags(QContactFilter::MatchFlags flags)
76 {
77     return flags & MatchFunctionFlags;
78 }
79
80 static QContactFilter::MatchFlags
81 stringCompareFlags(QContactFilter::MatchFlags flags)
82 {
83     return flags & StringCompareFlags;
84 }
85
86 Function
87 QTrackerCubiContactQueryBuilder::matchFunction(QContactDetailFilter::MatchFlags flags,
88                                                Value variable, QVariant value)
89 {
90     Value param = variable;
91
92     // Convert variable and search value into lower-case for case-insensitive matching.
93     // Must convert the value locally, instead of also using fn:lower-case because
94     // fn:starts-with only takes literals as second argument, but not return values.
95     if (QVariant::String == value.type() && 0 == (flags & QContactFilter::MatchCaseSensitive)) {
96         param = Functions::lowerCase.apply(param);
97         value.setValue(value.toString().toLower());
98     }
99
100     Value valueObject = LiteralValue(value);
101
102     // If exact matching is requested for non-string literal still do case sensitive matching,
103     // as case insensitive matching of numbers or URLs doesn't make much sense.
104     if (QVariant::String != value.type() && 0 == stringCompareFlags(flags)) {
105         return Functions::equal.apply(param, valueObject);
106     }
107
108     // for case sensitive matching there are specialized functions...
109     switch(matchFunctionFlags(flags)) {
110     case QContactFilter::MatchContains:
111         return Functions::contains.apply(param, valueObject);
112     case QContactFilter::MatchStartsWith:
113         return Functions::startsWith.apply(param, valueObject);
114     case QContactFilter::MatchEndsWith:
115         return Functions::endsWith.apply(param, valueObject);
116     }
117
118     return Functions::equal.apply(param, valueObject);
119 }
120
121 Function
122 QTrackerCubiContactQueryBuilder::lowerRangeFunction(QContactDetailRangeFilter::RangeFlags flags,
123                                                     const Value &lhs, const Value &rhs)
124 {
125     StaticAssert<0 != QContactDetailRangeFilter::ExcludeLower>();
126     StaticAssert<0 == QContactDetailRangeFilter::IncludeLower>();
127
128     if (flags.testFlag(QContactDetailRangeFilter::ExcludeLower)) {
129         return Functions::lessThan.apply(lhs, rhs);
130     }
131
132     return Functions::lessThanOrEqual.apply(lhs, rhs);
133 }
134
135 Function
136 QTrackerCubiContactQueryBuilder::upperRangeFunction(QContactDetailRangeFilter::RangeFlags flags,
137                                                     const Value &lhs, const Value &rhs)
138 {
139     StaticAssert<0 == QContactDetailRangeFilter::ExcludeUpper>();
140     StaticAssert<0 != QContactDetailRangeFilter::IncludeUpper>();
141
142     if (flags.testFlag(QContactDetailRangeFilter::IncludeUpper)) {
143         return Functions::lessThanOrEqual.apply(lhs, rhs);
144     }
145
146     return Functions::lessThan.apply(lhs, rhs);
147 }
148
149 Function
150 QTrackerCubiContactQueryBuilder::rangeFunction(QContactDetailRangeFilter::RangeFlags flags,
151                                                const Value &variable,
152                                                const QVariant &minimum,
153                                                const QVariant &maximum)
154 {
155     return (Functions::and_.apply(lowerRangeFunction(flags, LiteralValue(minimum), variable),
156                                   upperRangeFunction(flags, variable, LiteralValue(maximum))));
157 }
158
159 static bool
160 applyTypeCast(QContactFilter::MatchFlags flags,
161               const QTrackerContactDetailField *field, QVariant &value)
162 {
163     if (0 != stringCompareFlags(flags)) {
164         return value.convert(QVariant::String);
165     }
166
167     return field->makeValue(value, value);
168 }
169
170 ////////////////////////////////////////////////////////////////////////////////////////////////////
171
172 QTrackerCubiContactQueryBuilder::QTrackerCubiContactQueryBuilder(const QTrackerContactDetailSchema &schema,
173                                                          const QString &managerUri)
174     : m_error(QContactManager::NoError)
175     , m_schema(schema)
176     , m_managerUri(managerUri)
177 {
178     m_context.setSharable(false);
179 }
180
181 QTrackerCubiContactQueryBuilder::~QTrackerCubiContactQueryBuilder()
182 {
183 }
184
185 ////////////////////////////////////////////////////////////////////////////////////////////////////
186
187 QString
188 QTrackerCubiContactQueryBuilder::name(const QString &prefix, const QString &suffix)
189 {
190     if (not suffix.isEmpty()) {
191         return prefix + QLatin1Char('_') + suffix;
192     }
193
194     return prefix;
195 }
196
197 QString
198 QTrackerCubiContactQueryBuilder::name(const Variable &variable, const QString &suffix)
199 {
200     return name(variable.name(), suffix);
201 }
202
203 QString
204 QTrackerCubiContactQueryBuilder::name(const QContactDetail &detail, const QString &suffix)
205 {
206     return name(detail.definitionName(), suffix);
207 }
208
209 QString
210 QTrackerCubiContactQueryBuilder::name(const QTrackerContactDetail &detail, const QString &suffix)
211 {
212     return name(detail.name(), suffix);
213 }
214
215 QString
216 QTrackerCubiContactQueryBuilder::name(const QTrackerContactDetail &detail,
217                                       const QTrackerContactDetailField &field)
218 {
219     return name(detail.name(), field.name());
220 }
221
222 ////////////////////////////////////////////////////////////////////////////////////////////////////
223
224 static void
225 bindMultiValueColumn(Select &query, const Value &subject,
226                      const ResourceValue &property, const QString &fieldId, bool instances)
227 {
228     Select subQuery;
229     Variable object;
230
231     if (instances) {
232         subQuery.addProjection(Functions::groupConcat.
233                                apply(Functions::trackerId.apply(object),
234                                      LiteralValue(QLatin1String(":"))));
235     } else {
236         subQuery.addProjection(Functions::groupConcat.
237                                apply(object, LiteralValue(QLatin1String("\x1f"))));
238     }
239
240     subQuery.addRestriction(subject, property, object);
241
242     query.addProjection(subQuery, Variable(fieldId));
243 }
244
245 static void
246 bindMultiValueColumn(Select &query, const Value &subject,
247                      const QUrl &propertyIri, const QString &fieldId, bool instances)
248 {
249     bindMultiValueColumn(query, subject, ResourceValue(propertyIri), fieldId, instances);
250 }
251
252 template<class T> static void
253 addProjection(Select &query, const T &value, const QString &fieldId)
254 {
255     query.addProjection(value, Variable(fieldId));
256 }
257
258 template<> void
259 addProjection<Variable>(Select &query, const Variable &variable, const QString &/*fieldId*/)
260 {
261     query.addProjection(variable);
262 }
263
264 template <class T> static void
265 bindColumnImpl(Select &query, const QTrackerContactDetailField &field,
266                const T &value, const QString &fieldId)
267 {
268     if (field.isWithoutMapping()) {
269         return;
270     }
271
272     if (field.restrictsValues()) {
273         if (QVariant::StringList == field.dataType()) {
274             bindMultiValueColumn(query, value, field.propertyChain().last().iri(), fieldId,
275                                  not field.allowableInstances().isEmpty());
276         } else {
277             addProjection(query, Functions::trackerId.apply(value), fieldId);
278         }
279     } else  {
280         if (QVariant::StringList == field.dataType()) {
281             Select subQuery;
282
283             subQuery.addProjection(Functions::groupConcat.
284                                    apply(value, LiteralValue(QLatin1String("\x1f"))));
285
286             query.addProjection(subQuery, Variable(fieldId));
287         } else {
288             addProjection(query, value, fieldId);
289         }
290     }
291 }
292
293 void
294 QTrackerCubiContactQueryBuilder::bindColumn(Select &query, const QTrackerContactDetailField &field,
295                                             const Function &function, const QString &fieldId)
296 {
297     bindColumnImpl(query, field, function, fieldId);
298 }
299
300 void
301 QTrackerCubiContactQueryBuilder::bindColumn(Select &query, const QTrackerContactDetailField &field,
302                                             const Variable &variable, const QString &fieldId)
303 {
304     bindColumnImpl(query, field, variable, fieldId);
305 }
306
307 void
308 QTrackerCubiContactQueryBuilder::bindColumns(const QTrackerContactDetail &detail,
309                                              const QTrackerContactDetailField &field,
310                                              Select &query)
311 {
312     const QString fieldId = name(detail, field);
313
314     if (field.hasSubTypeClasses()) {
315         VariablesByName::Iterator entity = lookupVariable(detail.name());
316         bindMultiValueColumn(query, *entity, rdf::type::resource(), fieldId, true);
317     } else if (field.hasSubTypeProperties()) {
318         foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
319             const QString subTypeId = name(fieldId, pi.text());
320             const QString columnName = name(subTypeId, QLatin1String("IsBound"));
321             query.addProjection(Functions::bound.apply(*lookupVariable(subTypeId)), Variable(columnName));
322         }
323     } else if (field.hasPropertyChain()) {
324         if (detail.isUnique() && 1 == field.propertyChain().count()) {
325             // use property function to bind simple unique details
326             Variable contact(query.projections().first().variable());
327             // FIXME: Should this QString::arg be in cubi ? For example Function(ResourceValue)
328             PredicateFunction property(field.propertyChain().first().iri());
329             bindColumn(query, field, property.apply(contact), fieldId);
330         } else if (field.allowsMultipleValues()) {
331             bindColumn(query, field, *lookupVariable(detail.name()), fieldId);
332         } else {
333             bindColumn(query, field, *lookupVariable(fieldId), fieldId);
334         }
335     }
336
337     if (field.permitsCustomValues()) {
338         Select subQuery;
339         Variable property;
340         Variable propertyValue;
341         Variable detailVar = *lookupVariable(detail.name());
342         Variable subject = detailVar;
343         PropertyInfoList properties = field.propertyChain();
344         PropertyInfoList chain;
345         PropertyInfoList::ConstIterator pi = properties.begin();
346
347         /* The property is not necessarly attached to the variable having the detail URI
348          * so we have to look for the predicates in the chain after it and add them to
349          * the subselect restrictions */
350
351         for(; pi != properties.end(); ++pi) {
352             chain.append(*pi);
353
354             const VariablesByChain::ConstIterator p = m_properties.find(chain);
355
356             if (p != m_properties.end() && *p == detailVar) {
357                 ++pi;
358                 break;
359             }
360         }
361
362         for(; pi != properties.end(); ++pi) {
363             Variable object;
364             subQuery.addRestriction(subject, ResourceValue(pi->iri()), object);
365             subject = object;
366         }
367
368         subQuery.addRestriction(subject, nao::hasProperty::resource(), property);
369         subQuery.addRestriction(property, nao::propertyName::resource(), LiteralValue(field.name()));
370         subQuery.addRestriction(property, nao::propertyValue::resource(), propertyValue);
371
372         switch(field.dataType()) {
373         case QVariant::List:
374         case QVariant::StringList:
375             subQuery.addProjection(Functions::groupConcat.apply(propertyValue,
376                                                                 LiteralValue(QLatin1String(":"))));
377             // tracker:id() grows monotonic, therefore sorting by it restores original list order
378             subQuery.setOrderBy(Functions::trackerId.apply(property));
379             break;
380
381         default:
382             subQuery.addProjection(propertyValue);
383         }
384
385         Variable alias(name(fieldId, QLatin1String("CustomValues")));
386         query.addProjection(subQuery, alias);
387     }
388 }
389
390 ////////////////////////////////////////////////////////////////////////////////////////////////////
391
392 static bool
393 chainIncludedIn(const QTrackerContactDetailField &included,
394                 const QTrackerContactDetailField &includer)
395 {
396     return includer.propertyChain().startsWith(included.propertyChain());
397 }
398
399 PatternGroup
400 QTrackerCubiContactQueryBuilder::bindFields(const QTrackerContactDetail &detail,
401                                             const Variable &subject,
402                                             bool useComputedProperties)
403 {
404     if (detail.isUnique() && detail.hasContext()) {
405         qctWarn(QString::fromLatin1("Unique detail %1 cannot have a context").arg(detail.name()));
406         return PatternGroup();
407     }
408
409     PatternGroup patterns;
410
411     /*
412       Process first fields that have a property with a detailUri in their chain,
413       or those that have a property chain included in the chain of another field
414       (eg. FieldLinkedDetailUris of QContactOnlineAvatar).
415       This is because we cannot rename variables once they have been put in a
416       pattern (they are implicitely shared).
417       In bindProperties, if a field with a property chain A B C is processed
418       first, and we then have a field with property chain A B D and detailUri
419       set on B, the common part of both chains is reused and we would have to
420       rename B to something like detailName. Another solution would be to
421       make the classes in Cubi explicitely shared.
422
423       Fields with subTypes are generated last, because they generate optional
424       clauses, and beginning a pattern group with an optional means
425       (empty pattern) && optional {...}. Putting optional clauses at the end
426       of the pattern group is not enforced at cubi level, because it is still
427       valid sparql.
428     */
429     QList<QTrackerContactDetailField> fields = detail.fields();
430     qSort(fields.begin(), fields.end(), chainIncludedIn);
431     QList<QTrackerContactDetailField> fieldsWithDetailUri;
432     QList<QTrackerContactDetailField> fieldsWithOptionalAndDetailUri;
433     QList<QTrackerContactDetailField> fieldsWithOptional;
434
435     for(int i = 0; i < fields.size(); ++i) {
436         bool hasOptional = false;
437         bool hasDetailUri = false;
438
439         if (fields.at(i).hasSubTypes()) {
440             fieldsWithOptional << fields.at(i);
441             fields.removeAt(i--);
442             continue;
443         }
444
445         foreach(const PropertyInfoBase &pi, fields.at(i).propertyChain()) {
446             if (pi.isOptional()) {
447                 hasOptional = true;
448             }
449
450             if (pi.hasDetailUri()) {
451                 hasDetailUri = true;
452             }
453         }
454
455         if (hasOptional && hasDetailUri) {
456             fieldsWithOptionalAndDetailUri << fields.at(i);
457             fields.removeAt(i--);
458             continue;
459         }
460
461         if (hasOptional) {
462             fieldsWithOptional << fields.at(i);
463             fields.removeAt(i--);
464             continue;
465         }
466
467         if (hasDetailUri) {
468             fieldsWithDetailUri << fields.at(i);
469             fields.removeAt(i--);
470             continue;
471         }
472     }
473
474     foreach(const QTrackerContactDetailField &field,
475             fieldsWithDetailUri + fields + fieldsWithOptionalAndDetailUri + fieldsWithOptional) {
476         if (field.hasSubTypes()) {
477             patterns.addPattern(bindSubTypes(detail, field, subject));
478             continue;
479         }
480
481         if (field.isWithoutMapping()) {
482             continue;
483         }
484
485         const QString fieldId = field.allowsMultipleValues() ? detail.name() : name(detail, field);
486         const Variable fieldValue = *lookupVariable(fieldId, useComputedProperties);
487
488         PatternGroup group = bindProperties(detail, field, subject, fieldValue, useComputedProperties);
489         group.setOptional(detail.isUnique());
490
491         patterns.addPattern(group);
492     }
493
494     return patterns;
495 }
496
497 static bool
498 noDetailFieldsAreOptional(const QTrackerContactDetail &detail)
499 {
500     foreach(const QTrackerContactDetailField &field, detail.fields()) {
501         bool optional = false;
502
503         if (field.hasPropertyChain() && not field.propertyChain().empty()) {
504             optional |= field.propertyChain().first().isOptional();
505         }
506
507         if (optional) {
508             return false;
509         }
510     }
511
512     return true;
513 }
514
515 PatternGroup
516 QTrackerCubiContactQueryBuilder::bindFields(const QTrackerContactDetail &detail, Select &query,
517                                             bool useComputedProperties)
518 {
519     if (query.projections().isEmpty()) {
520         qctWarn("Cannot find any columns. There should be at least contact IRI and local id.");
521         return PatternGroup();
522     }
523
524     PatternGroup patterns;
525     VariablesByChain *const propertyCache = useComputedProperties ? &m_computedProperties
526                                                                   : &m_properties;
527
528     // bind fields to RDF properties
529     Variable contact(query.projections().first().variable());
530     VariablesByName::Iterator affiliation = m_variables.end();
531
532     if (detail.hasContext()) {
533         Variable contextVar(QLatin1String("context"));
534         Union u;
535
536         // ...to the contact...
537         u.setLhs(bindFields(detail, contact, useComputedProperties));
538
539         if (QContactManager::NoError != error()) {
540             return PatternGroup();
541         }
542
543         // ...and the contact's affiliation for details with context field
544         PatternGroup affiliationPatterns;
545         affiliation = lookupVariable(QLatin1String("affiliation"), useComputedProperties);
546         affiliationPatterns.addPattern(contact, nco::hasAffiliation::resource(), *affiliation);
547
548         m_context.append(piHasAffiliation);
549         propertyCache->insert(m_context, *affiliation);
550         affiliationPatterns.addPattern(bindFields(detail, *affiliation, useComputedProperties));
551         propertyCache->take(m_context);
552         m_context.takeLast();
553         u.setRhs(affiliationPatterns);
554
555         // cache this bound affiliation
556         VariablesByChain::Iterator pi = m_properties.find(workContextChain);
557
558         if (pi == propertyCache->end()) {
559             pi = propertyCache->insert(workContextChain, *affiliation);
560         }
561
562         query.addProjection(rdfs::label::function().apply(*affiliation), contextVar);
563         patterns.addPattern(u);
564     } else {
565         // only bind them to the contact for details without context field
566         patterns.addPattern(bindFields(detail, contact, useComputedProperties));
567         patterns.setOptional(detail.isUnique() && not noDetailFieldsAreOptional(detail));
568     }
569
570     // create entity column
571     if (detail.hasDetailUri()) {
572         VariablesByName::Iterator subject = lookupVariable(name(detail));
573         query.addProjection(*subject);
574     }
575
576     // finally create columns to project the graph
577     foreach(const QTrackerContactDetailField &field, detail.fields()) {
578         bindColumns(detail, field, query);
579     }
580
581     return patterns;
582 }
583
584 ////////////////////////////////////////////////////////////////////////////////////////////////////
585
586 PatternGroup
587 QTrackerCubiContactQueryBuilder::bindProperties(const QTrackerContactDetail &detail,
588                                                 const QTrackerContactDetailField &field,
589                                                 Variable subject, Variable object,
590                                                 bool useComputedProperties)
591 {
592     // simple properties of unique details are bound via property function in bindColumns()
593     if (detail.isUnique() && 1 == field.propertyChain().count()) {
594         return PatternGroup();
595     }
596
597     // FIXME: With some bad luck, we could have name conflicts... Maybe we can use
598     // the s3cr3t class Cubi::GeneratedVariable for this.
599     QString computedNameTemplate = useComputedProperties ? QString::fromLatin1("%1_Computed") :
600                                                            QString::fromLatin1("%1");
601
602     VariablesByChain &propertyCache = useComputedProperties ? m_computedProperties
603                                                             : m_properties;
604
605     PatternGroup patterns;
606
607     // the local variable chain
608     QList<Variable> localVariables;
609     localVariables.append(subject);
610
611     // the property chain
612     PropertyInfoList chain(m_context);
613     PropertyInfoList properties = field.propertyChain();
614
615     if (useComputedProperties) {
616         // FIXME: handle fields with more than one computed property if needed
617         if (1 != field.computedProperties().count()) {
618             qctWarn(QString::fromLatin1("%2 field of %1 detail must have exactly one computed property").
619                     arg(detail.name(), field.name()));
620             return PatternGroup();
621         }
622
623         properties.takeLast();
624         properties.append(field.computedProperties().first());
625     }
626
627     // don't bind last property of multi-value instance properties (flags)
628     PropertyInfoList::ConstIterator end = properties.end();
629
630     if (field.allowsMultipleValues()) {
631         --end;
632     }
633
634     // The pattern chain is split in two parts, the non optional part and the optional part
635     PatternGroup nonOptionalPatterns, optionalPatterns;
636     optionalPatterns.setOptional(true);
637     PatternGroup *currentPatterns = &nonOptionalPatterns;
638
639     // finally bind the properties
640     for(PropertyInfoList::ConstIterator pi = properties.begin(); pi != end; ++pi) {
641         PatternGroup group;
642         Pattern pattern;
643
644         if (pi->isOptional() && not detail.isUnique()) {
645             currentPatterns = &optionalPatterns;
646         }
647
648         pattern.setPredicate(ResourceValue(pi->iri()));
649
650         // figure out if we already have a variable for this property edge
651         chain.append(*pi);
652
653         const VariablesByChain::ConstIterator p = propertyCache.find(chain);
654
655         // If pi has a detailUri, we need to create a new variable with the right
656         // name (we can't rename variables). This situation should be avoided though,
657         // see the comment in bindFields
658         if (p != propertyCache.end() && not pi->hasDetailUri()) {
659             localVariables.append(p.value());
660         } else {
661             // create a new property edge
662             if (pi->hasDetailUri()) {
663                 localVariables.append(Variable(computedNameTemplate.arg(detail.name())));
664             } else {
665                 localVariables.append(Variable());
666             }
667
668             propertyCache.insert(chain, localVariables.last());
669         }
670
671         if (pi == end-1) {
672             localVariables.takeLast();
673             localVariables.append(object);
674             propertyCache.insert(chain, localVariables.last());
675         }
676
677         if (pi->isInverse()) {
678             pattern.setObject(localVariables.at(localVariables.size() - 2));
679             pattern.setSubject(localVariables.last());
680         } else {
681             pattern.setSubject(localVariables.at(localVariables.size() - 2));
682             pattern.setObject(localVariables.last());
683         }
684         group.addPattern(pattern);
685         currentPatterns->addPattern(group);
686     }
687
688     if (not nonOptionalPatterns.patterns().isEmpty()) {
689         patterns.addPattern(nonOptionalPatterns);
690     }
691
692     if (not optionalPatterns.patterns().isEmpty()) {
693         /*
694           If detail is unique and there is only one optional group in patterns
695           because the other predicates were already cached in m_properties, we
696           will return an optional { optional { } }, which is not what we want.
697           So in this special case we mark patterns as non optional.
698         */
699         if (patterns.patterns().isEmpty()) {
700             patterns.setOptional(false);
701         }
702
703         patterns.addPattern(optionalPatterns);
704     }
705
706     return patterns;
707 }
708
709 PatternGroup
710 QTrackerCubiContactQueryBuilder::bindSubTypes(const QTrackerContactDetail &detail,
711                                               const QTrackerContactDetailField &field,
712                                               const Variable &subject)
713 {
714     PatternGroup patterns;
715     const QString fieldId = name(detail, field);
716
717     if (field.hasSubTypeProperties()) {
718         foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
719             const QString subTypeId = name(fieldId, pi.text());
720             VariablesByName::ConstIterator subType = lookupVariable(subTypeId);
721
722             PatternGroup group;
723             group.addPattern(subject, ResourceValue(pi.iri()), *subType);
724             group.setOptional(true);
725             patterns.addPattern(group);
726         }
727     }
728
729     return patterns;
730 }
731
732 ////////////////////////////////////////////////////////////////////////////////////////////////////
733
734 template <typename T> static bool
735 isCompoundFilterSupported(const QContactFilter &filter)
736 {
737     foreach(const QContactFilter &childFilter, static_cast<const T&>(filter).filters()) {
738         if (not QTrackerCubiContactQueryBuilder::isFilterSupported(childFilter)) {
739             return false;
740         }
741     }
742
743     return true;
744 }
745
746 static bool
747 isCanonicalFilterSupported(const QContactFilter &filter)
748 {
749     switch(filter.type()) {
750     case QContactFilter::DefaultFilter:
751     case QContactFilter::LocalIdFilter:
752     case QContactFilter::ContactDetailFilter:
753     case QContactFilter::ContactDetailRangeFilter:
754     case QContactFilter::ChangeLogFilter:
755     case QContactFilter::RelationshipFilter:
756         return true;
757
758     case QContactFilter::IntersectionFilter:
759         return isCompoundFilterSupported<QContactIntersectionFilter>(filter);
760     case QContactFilter::UnionFilter:
761         return isCompoundFilterSupported<QContactUnionFilter>(filter);
762
763     case QContactFilter::ActionFilter:
764     case QContactFilter::InvalidFilter:
765         break;
766     }
767
768     return false;
769 }
770
771 bool
772 QTrackerCubiContactQueryBuilder::isFilterSupported(const QContactFilter &filter)
773 {
774     return isCanonicalFilterSupported(QContactManagerEngine::canonicalizedFilter(filter));
775 }
776
777 ////////////////////////////////////////////////////////////////////////////////////////////////////
778
779 #define DO_ENUM_VALUE(Type) \
780     case Type: return QLatin1String(#Type)
781
782 static QString
783 filterName(QContactFilter::FilterType type)
784 {
785     switch(type) {
786         DO_ENUM_VALUE(QContactFilter::InvalidFilter);
787         DO_ENUM_VALUE(QContactFilter::ContactDetailFilter);
788         DO_ENUM_VALUE(QContactFilter::ContactDetailRangeFilter);
789         DO_ENUM_VALUE(QContactFilter::ChangeLogFilter);
790         DO_ENUM_VALUE(QContactFilter::ActionFilter);
791         DO_ENUM_VALUE(QContactFilter::RelationshipFilter);
792         DO_ENUM_VALUE(QContactFilter::IntersectionFilter);
793         DO_ENUM_VALUE(QContactFilter::UnionFilter);
794         DO_ENUM_VALUE(QContactFilter::LocalIdFilter);
795         DO_ENUM_VALUE(QContactFilter::DefaultFilter);
796     }
797
798     return QString::fromLatin1("QContactFilter::FilterType(%1)").arg(type);
799 }
800
801 static QString
802 eventName(QContactChangeLogFilter::EventType type)
803 {
804     switch(type) {
805         DO_ENUM_VALUE(QContactChangeLogFilter::EventAdded);
806         DO_ENUM_VALUE(QContactChangeLogFilter::EventChanged);
807         DO_ENUM_VALUE(QContactChangeLogFilter::EventRemoved);
808     }
809
810     return QString::fromLatin1("QContactChangeLogFilter::EventType(%1)").arg(type);
811 }
812
813 #undef DO_ENUM_VALUE
814
815 ////////////////////////////////////////////////////////////////////////////////////////////////////
816
817 static bool
818 isEmpty(const QVariant &value)
819 {
820     if (value.isNull()) {
821         return true;
822     }
823
824     switch(value.type()) {
825     case QVariant::Bool:
826     case QVariant::Int:
827     case QVariant::UInt:
828     case QVariant::LongLong:
829     case QVariant::ULongLong:
830     case QVariant::Double:
831     case QVariant::Char:
832     case QVariant::Locale:
833     case QVariant::Rect:
834     case QVariant::RectF:
835     case QVariant::Size:
836     case QVariant::SizeF:
837     case QVariant::Line:
838     case QVariant::LineF:
839     case QVariant::Point:
840     case QVariant::PointF:
841         return false;
842
843     case QVariant::String:
844         return value.toString().isEmpty();
845     case QVariant::RegExp:
846         return value.toRegExp().isEmpty();
847     case QVariant::Url:
848         return value.toUrl().isEmpty();
849
850     case QVariant::Date:
851         return not value.toDate().isValid();
852     case QVariant::Time:
853         return not value.toTime().isValid();
854     case QVariant::DateTime:
855         return not value.toDateTime().isValid();
856
857     case QVariant::ByteArray:
858         return value.toByteArray().isEmpty();
859     case QVariant::BitArray:
860         return value.toBitArray().isEmpty();
861     case QVariant::List:
862         return value.toList().isEmpty();
863     case QVariant::Hash:
864         return value.toHash().isEmpty();
865     case QVariant::Map:
866         return value.toMap().isEmpty();
867     case QVariant::StringList:
868         return value.toStringList().isEmpty();
869
870     default:
871         break;
872     }
873
874     return value.toString().isEmpty();
875 }
876
877 template<class T>
878 QContactUnionFilter buildWildcardFilter(const T &filter, const QTrackerContactDetail &detail)
879 {
880     QContactUnionFilter unionFilter;
881
882     foreach(const QTrackerContactDetailField &field, detail.fields()) {
883         T fieldFilter(filter);
884         fieldFilter.setDetailDefinitionName(detail.name(), field.name());
885         unionFilter.append(fieldFilter);
886     }
887
888     return unionFilter;
889 }
890
891 /*
892  * RDFVariable describes all contacts in tracker before filter is applied.
893  * This method translates QContactFilter to tracker rdf filter. When query to tracker is made
894  * after this method, it would return only contacts that fit the filter.
895  */
896 void
897 QTrackerCubiContactQueryBuilder::bindLocalIdFilter(const QList<QContactLocalId> &localIds,
898                                                    const Variable &subject, PatternGroup &patterns)
899 {
900     ValueList contactIriList;
901
902     foreach(const QContactLocalId &localId, localIds) {
903         contactIriList.addValue(LiteralValue(qVariantFromValue(QString::number(localId))));
904     }
905
906     if (not contactIriList.values().isEmpty()) {
907         PropertyInfoList chain = PropertyInfoList() << m_context << piContactLocalUID;
908         VariablesByChain::Iterator p(m_properties.find(chain));
909         Variable cid(QLatin1String("cid"));
910
911         if (p == m_properties.end()) {
912             patterns.addPattern(subject, nco::contactLocalUID::resource(), cid);
913             p = m_properties.insert(chain, cid);
914         }
915
916         patterns.setFilter(Functions::in.apply(cid, contactIriList));
917     }
918 }
919
920 ////////////////////////////////////////////////////////////////////////////////////////////////////
921
922 bool
923 QTrackerCubiContactQueryBuilder::bindFilter(const QContactLocalIdFilter &filter,
924                                             const Variable &subject, PatternGroup &patterns)
925 {
926     if (QContactManager::NoError != error()) {
927         return false; // abort early if there already is an error
928     }
929
930     if (filter.ids().isEmpty()) {
931         qctWarn(QString::fromLatin1("%1: Local contact id list cannot be empty").
932                 arg(filterName(filter.type())));
933         m_error = QContactManager::BadArgumentError;
934         return false;
935     }
936
937     // build the filter
938     bindLocalIdFilter(filter.ids(), subject, patterns);
939
940     return true;
941 }
942
943 static void
944 collectFilters(PatternGroup &base, ValueChain &filters)
945 {
946     PatternGroup reduced;
947
948     foreach(PatternBase pattern, base.patterns()) {
949         if (not pattern.isPatternGroup()) {
950             reduced.addPattern(pattern);
951             continue;
952         }
953
954         PatternGroup &group = static_cast<PatternGroup &>(pattern);
955
956         collectFilters(group, filters);
957
958         if (group.filter().isValid()) {
959             filters.append(group.filter());
960         }
961
962         group.setFilter(Function());
963         reduced.addPattern(group);
964     }
965
966     base.setPatterns(reduced.patterns());
967 }
968
969 bool
970 QTrackerCubiContactQueryBuilder::bindFilter(const QContactIntersectionFilter &filter,
971                                             const Variable &subject, PatternGroup &patterns)
972 {
973     if (QContactManager::NoError != error()) {
974         return false; // abort early if there already is an error
975     }
976
977     QTrackerCubiContactQueryBuilder builder(m_schema, m_managerUri);
978     bool restrictionsAdded = false;
979
980     foreach(const QContactFilter &childFilter, filter.filters()) {
981         PatternGroup group;
982         restrictionsAdded |= builder.bindFilter(childFilter, subject, group);
983
984         if (QContactManager::NoError != error()) {
985             return false;
986         }
987
988         patterns.addPattern(group);
989     }
990
991     // TODO: move to collectFilters to get a nicer SPARQL?
992
993     return restrictionsAdded;
994 }
995
996 bool
997 QTrackerCubiContactQueryBuilder::bindFilter(const QContactUnionFilter &filter,
998                                             const Variable &subject, PatternGroup &patterns)
999 {
1000     if (QContactManager::NoError != error()) {
1001         return false; // abort early if there already is an error
1002     }
1003
1004
1005     if (filter.filters().isEmpty()) {
1006         return false;
1007     }
1008
1009     bool restrictionsAdded = false;
1010     PatternGroup groups;
1011
1012     foreach(const QContactFilter &childFilter, filter.filters()) {
1013         QTrackerCubiContactQueryBuilder builder(m_schema, m_managerUri);
1014         PatternGroup group;
1015
1016         restrictionsAdded |= builder.bindFilter(childFilter, subject, group);
1017
1018         if (QContactManager::NoError != error()) {
1019             return false;
1020         }
1021
1022         groups.addPattern(group);
1023     }
1024
1025     ValueChain filters;
1026     collectFilters(groups, filters);
1027     groups.setFilter(Functions::or_.apply(filters));
1028     patterns.addPattern(groups);
1029
1030     return restrictionsAdded;
1031 }
1032
1033 bool
1034 QTrackerCubiContactQueryBuilder::bindFilter(const QContactDetailFilter &filter,
1035                                             const Variable &subject, PatternGroup &patterns)
1036 {
1037     bool useComputedProperties = false;
1038     VariablesByChain *propertyCache = &m_properties;
1039
1040     if (QContactManager::NoError != error()) {
1041         return false; // abort early if there already is an error
1042     }
1043
1044     // check arguments
1045     QContactFilter::MatchFlags matchFlags(filter.matchFlags());
1046     QVariant value(filter.value());
1047
1048     if (isEmpty(value)) {
1049         // FIXME: null value must be handled as wildcard value
1050         qctWarn(QString::fromLatin1("%1: Wildcard value is not supported yet").
1051                 arg(filterName(filter.type())));
1052         m_error = QContactManager::BadArgumentError;
1053         return false;
1054     }
1055
1056     static const uint unsupportedMatchFlags =
1057             // FIXME: add support for those flags
1058             QContactDetailFilter::MatchKeypadCollation;
1059
1060     if (matchFlags & unsupportedMatchFlags) {
1061         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
1062                 arg(filterName(filter.type())).
1063                 arg(matchFlags & unsupportedMatchFlags));
1064     }
1065
1066     // find detail and field definition
1067     const QTrackerContactDetail *const detail =
1068             findDetail(filter.detailDefinitionName());
1069
1070     if (0 == detail) {
1071         return bindCustomDetailFilter(filter, subject, patterns);
1072     }
1073
1074     if (filter.detailFieldName().isEmpty()) {
1075         return bindFilter(buildWildcardFilter(filter, *detail), subject, patterns);
1076     }
1077
1078     const QTrackerContactDetailField *const field =
1079             findField(*detail, filter.detailFieldName());
1080
1081     if (0 == field) {
1082         return false;
1083     }
1084
1085     if (field->hasSubTypeClasses()) {
1086         return bindSubTypeClassesFilter(filter, subject, detail, field, patterns);
1087     }
1088
1089     if (field->isWithoutMapping()) {
1090         return bindWithoutMappingFilter(filter, subject, field, value, patterns);
1091     }
1092
1093     // bind detail variable
1094     PropertyInfoList properties = field->propertyChain();
1095
1096     if (properties.isEmpty()) {
1097         // Nothing to do for this field, I hope.
1098         // We might have to consider subtype classes and properties.
1099         return false;
1100     }
1101
1102     // do special processing for phone numbers
1103     // FIXME: find a more generic pattern for doing this modifications
1104     if (properties.last().iri() == nco::phoneNumber::iri()) {
1105         // Phone numbers are almost impossible to parse (TODO: reference?)
1106         // So phone number handling is done based on heuristic:
1107         // it turned out that matching the last 7 digits works good enough (tm).
1108         // The actual number of digits to use is stored in QctSettings().localPhoneNumberLength().
1109         // The phone number's digits are stored in tracker using a maemo:localPhoneNumber property.
1110         const int localPhoneNumberLength = QctSettings().localPhoneNumberLength();
1111         const QString phoneNumberReference = value.toString();
1112
1113         if (matchFlags.testFlag(QContactFilter::MatchPhoneNumber)) {
1114             // remove existing function flag and replace with normal string oriented one
1115             matchFlags &= ~MatchFunctionFlags;
1116             if (phoneNumberReference.length() >= localPhoneNumberLength) {
1117                 matchFlags |= QContactFilter::MatchEndsWith;
1118             } else {
1119                 matchFlags |= QContactFilter::MatchExactly;
1120             }
1121
1122             // use the normalized phone number, stored in maemo:localPhoneNumber resource
1123             useComputedProperties = true;
1124             propertyCache = &m_computedProperties;
1125             properties.takeLast();
1126             properties.append(field->computedProperties().first());
1127
1128             // this hack only works for maemo:localPhoneNumber
1129             Q_ASSERT(properties.last().iri() == maemo::localPhoneNumber::iri());
1130         }
1131         // restrict reference to assumed localPhoneNumber, cmp. note above
1132         if (QContactFilter::MatchEndsWith == matchFunctionFlags(matchFlags)) {
1133             const QString localPhoneNumber = phoneNumberReference.right(localPhoneNumberLength);
1134             value.setValue(localPhoneNumber);
1135         }
1136     }
1137
1138     if (not propertyCache->contains(properties)) {
1139         // If we don't have a predicate chain, generate one
1140         // We discard the projections in the Select, since we only use the
1141         // variables for filtering.
1142         // For simple predicates we just use predicate functions
1143
1144         if (detail->isUnique() && field->propertyChain().count() == 1) {
1145             // In this case, bindFields would create the binding using a
1146             // predicate function, so there is no variable to filter.
1147             // Therefore, we do the binding manually.
1148             PatternGroup group;
1149             Variable object;
1150             group.setOptional(true);
1151             group.addPattern(subject, ResourceValue(field->propertyChain().first().iri()), object);
1152             patterns.addPattern(group);
1153             propertyCache->insert(field->propertyChain(), object);
1154         } else {
1155             Select s;
1156             s.addProjection(subject);
1157             s.addProjection(Variable(QLatin1String("cid")));
1158             PatternGroup group = bindFields(*detail, s, useComputedProperties);
1159             group.setOptional(true);
1160
1161             patterns.addPattern(group);
1162         }
1163     }
1164
1165     // try casting the filter value
1166     if (not applyTypeCast(matchFlags, field, value)) {
1167         qctWarn(QString::fromLatin1("%1: Cannot apply required casts to filter value "
1168                                     "for %3 field of %2 detail").
1169                 arg(filterName(filter.type()), detail->name(), field->name()));
1170         m_error = QContactManager::BadArgumentError;
1171         return false;
1172     }
1173
1174     // glue everything together
1175     PatternGroup group;
1176     Value filterVar;
1177
1178     const VariablesByChain::ConstIterator p = propertyCache->find(properties);
1179
1180     if (p == propertyCache->end()) {
1181         // FIXME: So we were filtering on a detail we didn't generate a chain for... Is that possible?
1182         // hasselmm: Yes, of course you can filter on details not covered by the fetch hint’s definition mask!
1183         qctWarn(QString::fromLatin1("No cached predicate chain found for %2 field of %1 detail").
1184                 arg(detail->name(), field->name()));
1185         return false;
1186     }
1187
1188     filterVar = p.value();
1189     group.setFilter(matchFunction(matchFlags, filterVar, value));
1190
1191     patterns.addPattern(group);
1192
1193     return true;
1194 }
1195
1196 bool
1197 QTrackerCubiContactQueryBuilder::bindFilter(const QContactDetailRangeFilter &filter,
1198                                             const Variable &subject, PatternGroup &patterns)
1199 {
1200     if (QContactManager::NoError != error()) {
1201         return false; // abort early if there already is an error
1202     }
1203
1204     // check arguments
1205     QVariant minimum = filter.minValue();
1206     QVariant maximum = filter.maxValue();
1207
1208     if (minimum.isNull() || maximum.isNull()) {
1209         // FIXME: null value must be handled as wildcard value
1210         qctWarn(QString::fromLatin1("%1: Wildcards for range values are not supported yet").
1211                 arg(filterName(filter.type())));
1212         m_error = QContactManager::BadArgumentError;
1213         return false;
1214     }
1215
1216     static const uint unsupportedMatchFlags = ~(QContactDetailFilter::MatchFixedString);
1217
1218     if (0 != (filter.matchFlags() & unsupportedMatchFlags)) {
1219         // QTMOBILITY-222 - Match flags cannot be implemented for range filter
1220         qctWarn(QString::fromLatin1("%1: Ignoring nonapplyable match flags: %2").
1221                 arg(filterName(filter.type())).arg(filter.matchFlags() & unsupportedMatchFlags));
1222     }
1223
1224     // find detail and field definition
1225     const QTrackerContactDetail *const detail = findDetail(filter.detailDefinitionName());
1226
1227     if (0 == detail) {
1228         return false;
1229     }
1230
1231     if (filter.detailFieldName().isEmpty()) {
1232         return bindFilter(buildWildcardFilter(filter, *detail), subject, patterns);
1233     }
1234
1235     const QTrackerContactDetailField *const field = findField(*detail, filter.detailFieldName());
1236
1237     if (0 == field) {
1238         return false;
1239     }
1240
1241     PropertyInfoList properties(field->propertyChain());
1242
1243     // FIXME: We don't do the computed properties dance here, since applying
1244     // range on localPhoneNumber is not so common. But should we be prepared?
1245
1246     if (not m_properties.contains(properties)) {
1247         // If we don't have a predicate chain, generate one
1248         // We discard the projections in the Select, since we only use the
1249         // variables for filtering.
1250         // For simple predicates we just use predicate functions
1251         if (detail->isUnique() && field->propertyChain().count() == 1) {
1252             // In this case, bindFields would create the binding using a
1253             // predicate function, so there is no variable to filter.
1254             // Therefore, we do the binding manually.
1255             Variable object;
1256             patterns.addPattern(subject, ResourceValue(field->propertyChain().first().iri()), object);
1257             m_properties.insert(field->propertyChain(), object);
1258         } else {
1259             Select s;
1260             s.addProjection(subject);
1261             s.addProjection(Variable(QLatin1String("cid")));
1262             PatternGroup group = bindFields(*detail, s);
1263             patterns.addPattern(group);
1264         }
1265     }
1266
1267     // try casting the filter value
1268     if (not applyTypeCast(filter.matchFlags(), field, minimum)) {
1269         qctWarn(QString::fromLatin1("%1: Cannot cast minimum value to required type: %2").
1270                 arg(filterName(filter.type()), QLatin1String(QVariant::typeToName(field->dataType()))));
1271         m_error = QContactManager::BadArgumentError;
1272         return false;
1273     }
1274
1275     if (not applyTypeCast(filter.matchFlags(), field, maximum)) {
1276         qctWarn(QString::fromLatin1("%1: Cannot cast maximum value to required type: %2").
1277                 arg(filterName(filter.type()), QLatin1String(QVariant::typeToName(field->dataType()))));
1278         m_error = QContactManager::BadArgumentError;
1279         return false;
1280     }
1281
1282     // glue everything together
1283     PatternGroup group;
1284     Value filterVar;
1285
1286     if(detail->isUnique() && properties.count() == 1) {
1287         PredicateFunction property(properties.first().iri());
1288
1289         filterVar = property.apply(subject);
1290     } else {
1291         const VariablesByChain::ConstIterator p(m_properties.find(properties));
1292
1293         if (p == m_properties.end()) {
1294             // FIXME: So we were filtering on a detail we didn't generate a chain for... Is that possible?
1295             // hasselmm: Yes, of course you can filter on details not covered by the fetch hint’s definition mask!
1296             qctWarn(QString::fromLatin1("No cached predicate chain found for %2 field of %1 detail").
1297                     arg(detail->name(), field->name()));
1298             return false;
1299         }
1300
1301         filterVar = p.value();
1302     }
1303
1304     group.setFilter(rangeFunction(filter.rangeFlags(), filterVar, minimum, maximum));
1305
1306     patterns.addPattern(group);
1307
1308     return true;
1309 }
1310
1311 bool
1312 QTrackerCubiContactQueryBuilder::bindFilter(const QContactChangeLogFilter &filter,
1313                                             const Variable &subject, PatternGroup &patterns)
1314 {
1315     if (QContactManager::NoError != error()) {
1316         return false; // abort early if there already is an error
1317     }
1318
1319     PatternGroup group;
1320     Variable contentCreated;
1321     Variable contentLastModified;
1322
1323     switch(filter.eventType()) {
1324     case QContactChangeLogFilter::EventAdded:
1325         group.addPattern(subject, nie::contentCreated::resource(), contentCreated);
1326         group.setFilter(Functions::greaterThanOrEqual.apply(contentCreated,
1327                                                             LiteralValue(qVariantFromValue(filter.since()))));
1328         patterns.addPattern(group);
1329         return true;
1330
1331     case QContactChangeLogFilter::EventChanged:
1332         group.addPattern(subject, nie::contentLastModified::resource(), contentLastModified);
1333         group.setFilter(Functions::greaterThanOrEqual.apply(contentLastModified,
1334                                                             LiteralValue(qVariantFromValue(filter.since()))));
1335         patterns.addPattern(group);
1336         return true;
1337
1338     case QContactChangeLogFilter::EventRemoved:
1339         break;
1340     }
1341
1342     qctWarn(QString::fromLatin1("%1: Unsupported event type: %2").
1343             arg(filterName(filter.type()), eventName(filter.eventType())));
1344     m_error = QContactManager::NotSupportedError;
1345
1346     return false;
1347 }
1348
1349 bool
1350 QTrackerCubiContactQueryBuilder::bindFilter(const QContactRelationshipFilter &filter,
1351                                             const Variable &subject, PatternGroup &patterns)
1352 {
1353     if (QContactManager::NoError != error()) {
1354         return false; // abort early if there already is an error
1355     }
1356
1357     if (filter.relationshipType() == QContactRelationship::HasMember) {
1358         const QContactId relatedContactId = filter.relatedContactId();
1359         // only HasMember relationships between local groups and local contacts are stored ATM
1360         if (relatedContactId.managerUri() == m_managerUri) {
1361             LiteralValue rdfLocalUid(qVariantFromValue(QString::number(relatedContactId.localId())));
1362
1363             const QContactRelationship::Role relatedContactRole = filter.relatedContactRole();
1364
1365             // filter for all contacts in a given group?
1366             if (relatedContactRole == QContactRelationship::First
1367                 || relatedContactRole == QContactRelationship::Either) {
1368                 PatternGroup group;
1369                 Variable rdfGroupContact(QString::fromLatin1("group"));
1370                 Variable localUID;
1371                 group.addPattern(rdfGroupContact, rdf::type::resource(), nco::Contact::resource());
1372                 group.addPattern(rdfGroupContact, rdf::type::resource(), nco::ContactGroup::resource());
1373                 group.addPattern(rdfGroupContact, nco::contactLocalUID::resource(), localUID);
1374                 group.addPattern(subject, nco::belongsToGroup::resource(), rdfGroupContact);
1375                 group.setFilter(Functions::equal.apply(localUID, rdfLocalUid));
1376
1377                 patterns.addPattern(group);
1378             }
1379
1380             // filter for all groups a given contact is in?
1381             if (relatedContactRole == QContactRelationship::Second
1382                 || relatedContactRole == QContactRelationship::Either) {
1383                 PatternGroup group;
1384                 Variable rdfContact(QString::fromLatin1("member"));
1385                 Variable localUID;
1386                 // not PersonContact, groups could also be part of other groups
1387                 group.addPattern(rdfContact, rdf::type::resource(), nco::Contact::resource());
1388                 group.addPattern(rdfContact, nco::contactLocalUID::resource(), localUID);
1389                 group.addPattern(rdfContact, nco::belongsToGroup::resource(), subject);
1390                 group.setFilter(Functions::equal.apply(localUID, rdfLocalUid));
1391
1392                 patterns.addPattern(group);
1393             }
1394         }
1395         // none at all available for relations to ones from other manager ATM
1396         else {
1397             // TODO: ideally would be "filter(false)", but libqttracker does not support that yet, nb#193973
1398             // so this is a temp. replacement which also yields no results for this filter.
1399             PatternGroup group;
1400             group.setFilter(Functions::in.apply(subject, ValueList()));
1401
1402             patterns.addPattern(group);
1403
1404             qctWarn(QString::fromLatin1("Relationships to contacts of the QContactManagerEngine %1 are not stored here.").
1405                     arg(relatedContactId.managerUri()));
1406         }
1407         return true;
1408     }
1409
1410     qctWarn(QString::fromLatin1("%1: Unsupported relationship type: %2").
1411             arg(filterName(filter.type()), filter.relationshipType()));
1412     m_error = QContactManager::NotSupportedError;
1413     return false;
1414 }
1415
1416
1417
1418 bool
1419 QTrackerCubiContactQueryBuilder::bindFilter(const QContactFilter &filter,
1420                                             const Variable &subject, PatternGroup &patterns)
1421 {
1422     if (QContactManager::NoError != error()) {
1423         return false; // abort early if there already is an error
1424     }
1425
1426     QContactFilter canonicalFilter(QContactManagerEngine::canonicalizedFilter(filter));
1427
1428     switch(canonicalFilter.type()) {
1429     case QContactFilter::LocalIdFilter:
1430         return bindFilter(static_cast<const QContactLocalIdFilter&>(canonicalFilter), subject, patterns);
1431     case QContactFilter::IntersectionFilter:
1432         return bindFilter(static_cast<const QContactIntersectionFilter&>(canonicalFilter), subject, patterns);
1433     case QContactFilter::UnionFilter:
1434         return bindFilter(static_cast<const QContactUnionFilter&>(canonicalFilter), subject, patterns);
1435     case QContactFilter::ContactDetailFilter:
1436         return bindFilter(static_cast<const QContactDetailFilter&>(canonicalFilter), subject, patterns);
1437     case QContactFilter::ContactDetailRangeFilter:
1438         return bindFilter(static_cast<const QContactDetailRangeFilter&>(canonicalFilter), subject, patterns);
1439     case QContactFilter::ChangeLogFilter:
1440         return bindFilter(static_cast<const QContactChangeLogFilter&>(canonicalFilter), subject, patterns);
1441     case QContactFilter::RelationshipFilter:
1442         return bindFilter(static_cast<const QContactRelationshipFilter&>(canonicalFilter), subject, patterns);
1443     case QContactFilter::DefaultFilter:
1444         // We didn't apply any explicit restrictions, but the purpose of this filter is to
1445         // apply no restrictions at all. Therefore all restrictions implied by this filter
1446         // have been applied and we return true.
1447         return true;
1448
1449     case QContactFilter::InvalidFilter:
1450         if (isCanonicalFilterSupported(filter)) {
1451             qctWarn(QString::fromLatin1("%1: Bad arguments").arg(filterName(filter.type())));
1452             m_error = QContactManager::BadArgumentError;
1453             return false;
1454         }
1455
1456         break;
1457
1458     case QContactFilter::ActionFilter:
1459         break;
1460     }
1461
1462     qctWarn(QString::fromLatin1("%1: Unsupported filter type").arg(filterName(filter.type())));
1463     m_error = QContactManager::NotSupportedError;
1464     return false;
1465 }
1466
1467 ////////////////////////////////////////////////////////////////////////////////////////////////////
1468
1469 static inline ClassInfoList::ConstIterator
1470 findClassInfoWithText(const ClassInfoList &list, const QString &text)
1471 {
1472     ClassInfoList::ConstIterator it;
1473     for (it = list.constBegin(); it != list.constEnd(); ++it) {
1474         if (it->text() == text) {
1475             break;
1476         }
1477     }
1478     return it;
1479 }
1480
1481 bool
1482 QTrackerCubiContactQueryBuilder::bindSubTypeClassesFilter(const QContactDetailFilter &filter,
1483                                                           const Variable &subject,
1484                                                           const QTrackerContactDetail *detail,
1485                                                           const QTrackerContactDetailField *field,
1486                                                           PatternGroup &patterns)
1487 {
1488     QVariant value = filter.value();
1489     QContactFilter::MatchFlags matchFlags = filter.matchFlags();
1490
1491     // check for supported match type
1492     // QContactFilter::MatchFlags is a mixture of values and flags, see NB#202529
1493     // only bits 4.. seem to be real flags. As we do not support any of those real flags,
1494     // do here explicit comparison for each supported match type.
1495     // FIXME: add support for QContactFilter::MatchExactly
1496     if (QContactFilter::MatchContains != matchFlags) {
1497         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
1498                 arg(filterName(filter.type())).
1499                 arg(matchFlags));
1500         m_error = QContactManager::BadArgumentError;
1501         return false;
1502     }
1503     // FIXME: add support for QContactFilter::MatchContains with more than one list item (e.g. QStringList)
1504
1505     // bind detail variable
1506     PropertyInfoList propertyChain = field->propertyChain();
1507
1508     if (propertyChain.isEmpty()) {
1509         qctWarn(QString::fromLatin1("%1: No property (chain) set for %3 field of %2 detail, "
1510                                     "but needed for subtype by class.").
1511                 arg(filterName(filter.type()), detail->name(), field->name()));
1512         m_error = QContactManager::BadArgumentError;
1513         return false;
1514     }
1515
1516     // lookup class for given subtype
1517     const QString subType = value.toString();
1518     const ClassInfoList &subTypeClasses = field->subTypeClasses();
1519     ClassInfoList::ConstIterator it = findClassInfoWithText(subTypeClasses, subType);
1520
1521     PatternGroup group;
1522     Value lastObject = subject;
1523     const VariablesByChain::ConstIterator p(m_properties.find(propertyChain));
1524
1525     if (p == m_properties.end()) {
1526         foreach(const PropertyInfoBase &pi, field->propertyChain()) {
1527             Variable object;
1528             group.addPattern(lastObject, ResourceValue(pi.iri()), object);
1529             lastObject = object;
1530         }
1531     } else {
1532         lastObject = p.value();
1533     }
1534
1535     // standard subtype found?
1536     if (it != subTypeClasses.constEnd()) {
1537         const QUrl &subTypeIri = it->iri();
1538
1539         // glue everything together
1540         group.addPattern(lastObject, rdf::type::resource(), ResourceValue(subTypeIri));
1541         patterns.addPattern(group);
1542     } else {
1543         if (not field->permitsCustomValues()) {
1544             qctWarn(QString::fromLatin1("%1: Unknown subtype %4 for %3 field of %2 detail").
1545                     arg(filterName(filter.type()), detail->name(), field->name(), subType));
1546             m_error = QContactManager::BadArgumentError;
1547             return false;
1548         }
1549
1550         // glue everything together
1551         Variable customSubType;
1552         group.addPattern(customSubType, rdf::type::resource(), nao::Property::resource());
1553         group.addPattern(customSubType, nao::propertyName::resource(),
1554                          LiteralValue(qVariantFromValue(field->name())));
1555         group.addPattern(customSubType, nao::propertyValue::resource(),
1556                          LiteralValue(qVariantFromValue(subType)));
1557         group.addPattern(lastObject, nao::hasProperty::resource(), customSubType);
1558         patterns.addPattern(group);
1559     }
1560
1561     return true;
1562 }
1563
1564 bool
1565 QTrackerCubiContactQueryBuilder::bindWithoutMappingFilter(const QContactDetailFilter &filter,
1566                                                           const Variable &subject,
1567                                                           const QTrackerContactDetailField *field,
1568                                                           const QVariant &value,
1569                                                           PatternGroup & patterns)
1570 {
1571     QContactFilter::MatchFlags matchFlags = filter.matchFlags();
1572     PatternGroup group;
1573
1574     // check for supported match type
1575     // QContactFilter::MatchFlags is a mixture of values and flags, see NB#202529
1576     // only bits 4.. seem to be real flags. As we do not support any of those real flags,
1577     // do here explicit comparison for each supported match type.
1578     // FIXME: add support for QContactFilter::MatchExactly
1579     if (QContactFilter::MatchContains != matchFlags) {
1580         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
1581                 arg(filterName(filter.type())).
1582                 arg(matchFlags));
1583         m_error = QContactManager::BadArgumentError;
1584         return false;
1585     }
1586     // FIXME: add support for QContactFilter::MatchContains with more than one list item (e.g. QStringList)
1587
1588     Value lastObject = subject;
1589     const VariablesByChain::ConstIterator p(m_properties.find(field->propertyChain()));
1590
1591     if (p == m_properties.end()) {
1592         foreach(const PropertyInfoBase &pi, field->propertyChain()) {
1593             Variable object;
1594             group.addPattern(lastObject, ResourceValue(pi.iri()), object);
1595             lastObject = object;
1596         }
1597     } else {
1598         lastObject = p.value();
1599     }
1600
1601     Variable property;
1602     Variable propertyValue;
1603
1604     group.addPattern(property, rdf::type::resource(), nao::Property::resource());
1605     group.addPattern(property, nao::propertyName::resource(),
1606                      LiteralValue(qVariantFromValue(field->name())));
1607     group.addPattern(property, nao::propertyValue::resource(), propertyValue);
1608     group.setFilter(matchFunction(matchFlags, propertyValue, value));
1609     group.addPattern(lastObject, nao::hasProperty::resource(), property);
1610
1611     patterns.addPattern(group);
1612
1613     return true;
1614 }
1615
1616 bool
1617 QTrackerCubiContactQueryBuilder::bindCustomDetailFilter(const QContactDetailFilter &filter,
1618                                                         const Variable &subject,
1619                                                         PatternGroup &patterns)
1620 {
1621     QContactFilter::MatchFlags matchFlags = filter.matchFlags();
1622     // bits 0..2 are an enum for the type, so separate that. See NB#202529
1623     QContactFilter::MatchFlags matchType = matchFlags & MatchFunctionFlags;
1624
1625     // check for supported match type
1626     static const QContactFilter::MatchFlags UnsupportedMatchFlags =
1627         QContactFilter::MatchFixedString | QContactFilter::MatchCaseSensitive |
1628         QContactFilter::MatchPhoneNumber;
1629
1630     // check arguments
1631     if (UnsupportedMatchFlags & matchFlags) {
1632         qctWarn(QString::fromLatin1("%1: Unsupported match flags: %2").
1633                 arg(filterName(filter.type())).
1634                 arg(UnsupportedMatchFlags & matchFlags));
1635         m_error = QContactManager::BadArgumentError;
1636         return false;
1637     }
1638
1639     QVariant value = filter.value();
1640
1641     if (QVariant::StringList == value.type()) {
1642         qctWarn(QString::fromLatin1("%1: Unsupported data type: StringList").
1643                 arg(filterName(filter.type())));
1644         m_error = QContactManager::BadArgumentError;
1645         return false;
1646     }
1647
1648     if (QContactFilter::MatchContains == matchType
1649         || QContactFilter::MatchStartsWith == matchType
1650         || QContactFilter::MatchEndsWith == matchType) {
1651         value.convert(QVariant::String);
1652     }
1653
1654     // build SPARQL filter
1655     PatternGroup group;
1656
1657     Variable customPropertyDetail, customPropertyField, customPropertyValue;
1658
1659     group.addPattern(subject, nao::hasProperty::resource(), customPropertyDetail);
1660     group.addPattern(customPropertyDetail, nao::hasProperty::resource(), customPropertyField);
1661     group.addPattern(customPropertyField, nao::propertyValue::resource(), customPropertyValue);
1662
1663     group.addPattern(customPropertyDetail, nao::propertyName::resource(),
1664                      LiteralValue(qVariantFromValue(filter.detailDefinitionName())));
1665     group.addPattern(customPropertyField, nao::propertyName::resource(),
1666                      LiteralValue(qVariantFromValue(filter.detailFieldName())));
1667
1668     group.setFilter(matchFunction(matchFlags, customPropertyValue, value));
1669
1670     patterns.addPattern(group);
1671
1672     return true;
1673 }
1674
1675 ////////////////////////////////////////////////////////////////////////////////////////////////////
1676
1677 const QTrackerContactDetail *
1678 QTrackerCubiContactQueryBuilder::findDetail(const QString &detailName)
1679 {
1680     const QTrackerContactDetail *detail = m_schema.detail(detailName);
1681
1682     if (0 == detail) {
1683         qctWarn(QString::fromLatin1("Unsupported detail %1").arg(detailName));
1684     }
1685
1686     return detail;
1687 }
1688
1689
1690 const QTrackerContactDetailField *
1691 QTrackerCubiContactQueryBuilder::findField(const QTrackerContactDetail &detail,
1692                                        const QString &fieldName)
1693 {
1694     const QTrackerContactDetailField *field = detail.field(fieldName);
1695
1696     if (0 == field) {
1697         qctWarn(QString::fromLatin1("Unsupported field %2 for %1 detail").
1698                 arg(detail.name(), fieldName));
1699         m_error = QContactManager::NotSupportedError;
1700     }
1701
1702     return field;
1703 }
1704
1705 VariablesByName::Iterator
1706 QTrackerCubiContactQueryBuilder::lookupVariable(const QString &name, bool computedProperty)
1707 {
1708     static const QString computedTemplate = QLatin1String("%1_Computed");
1709     const QString realName = computedProperty ? computedTemplate.arg(name) : name;
1710     VariablesByName::Iterator var = m_variables.find(realName);
1711
1712     if (var == m_variables.end()) {
1713         var = m_variables.insert(name, Variable(realName));
1714     }
1715
1716     return var;
1717 }