Changes: Use QSparqlQueryOptions in runQuery
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / src / engine / contactfetchrequest.cpp
1 /** This file is part of QtContacts tracker storage plugin
2  **
3  ** Copyright (c) 2010-2011 Nokia Corporation and/or its subsidiary(-ies).
4  **
5  ** Contact:  Nokia Corporation (info@qt.nokia.com)
6  **
7  ** GNU Lesser General Public License Usage
8  ** This file may be used under the terms of the GNU Lesser General Public License
9  ** version 2.1 as published by the Free Software Foundation and appearing in the
10  ** file LICENSE.LGPL included in the packaging of this file.  Please review the
11  ** following information to ensure the GNU Lesser General Public License version
12  ** 2.1 requirements will be met:
13  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
14  **
15  ** In addition, as a special exception, Nokia gives you certain additional rights.
16  ** These rights are described in the Nokia Qt LGPL Exception version 1.1, included
17  ** in the file LGPL_EXCEPTION.txt in this package.
18  **
19  ** Other Usage
20  ** Alternatively, this file may be used in accordance with the terms and
21  ** conditions contained in a signed written agreement between you and Nokia.
22  **/
23
24 #include "contactfetchrequest.h"
25
26 #include "engine.h"
27
28 #include <dao/contactdetail.h>
29 #include <dao/contactdetailschema.h>
30 #include <dao/resourcecache.h>
31 #include <dao/scalarquerybuilder.h>
32 #include <dao/sparqlconnectionmanager.h>
33 #include <dao/subject.h>
34 #include <dao/support.h>
35 #include <lib/logger.h>
36 #include <lib/requestextensions.h>
37
38 #include <QtSparql>
39
40 ///////////////////////////////////////////////////////////////////////////////////////////////////
41
42 CUBI_USE_NAMESPACE
43 CUBI_USE_NAMESPACE_RESOURCES
44
45 ///////////////////////////////////////////////////////////////////////////////////////////////////
46
47 class QTrackerContactFetchRequest::DetailContext
48 {
49 public:
50     explicit DetailContext(const QTrackerContactDetail &definition,
51                            int firstColumn, int lastColumn)
52         : m_definition(definition)
53         , m_firstColumn(firstColumn)
54         , m_lastColumn(lastColumn)
55     {
56     }
57
58     const QTrackerContactDetail & definition() const { return m_definition; }
59     int firstColumn() const { return m_firstColumn; }
60     int lastColumn() const { return m_lastColumn; }
61
62 private:
63     QTrackerContactDetail   m_definition;
64     int                     m_firstColumn;
65     int                     m_lastColumn;
66 };
67
68 ///////////////////////////////////////////////////////////////////////////////////////////////////
69
70 class QTrackerContactFetchRequest::QueryContext
71 {
72 public:
73     explicit QueryContext(const QTrackerContactDetailSchema &schema)
74         : customDetailColumn(-1)
75         , fetchAllDetails(false)
76         , m_schema(schema)
77     {
78     }
79
80 public: // attributes
81     const QTrackerContactDetailSchema & schema() const { return m_schema; }
82     const QString & contactType() const { return m_schema.contactType(); }
83
84 public: // fields
85     Select query;
86     QSparqlResult *result;
87
88     QList<DetailContext> details;
89     QSet<QString> definitionHints;
90     QSet<QString> customDetailHints;
91     int customDetailColumn;
92
93     bool fetchAllDetails : 1;
94
95 private: // fields
96     QTrackerContactDetailSchema m_schema;
97 };
98
99 ///////////////////////////////////////////////////////////////////////////////////////////////////
100
101 QTrackerContactFetchRequest::QTrackerContactFetchRequest(QContactAbstractRequest *request,
102                                                          QContactTrackerEngine *engine,
103                                                          QObject *parent)
104     : QTrackerBaseRequest<QContactFetchRequest>(engine, parent)
105     , m_filter(staticCast(request)->filter())
106     , m_fetchHint(engine->normalizedFetchHint(staticCast(request)->fetchHint(),
107                                               QctRequestExtensions::get(request)->nameOrder()))
108     , m_nameOrder(QctRequestExtensions::get(request)->nameOrder())
109     , m_sorting(staticCast(request)->sorting())
110 {
111 }
112
113 QTrackerContactFetchRequest::~QTrackerContactFetchRequest()
114 {
115 }
116
117 /// returns a query for the contact iri, the nco::contactLocalUID, the context iri, the rdfs::label of the context
118 Select
119 QTrackerContactFetchRequest::baseQuery(const QTrackerScalarContactQueryBuilder &queryBuilder) const
120 {
121     Variable contact(queryBuilder.contact());
122     Variable context(queryBuilder.context());
123
124     Select query;
125
126     query.addProjection(contact);
127     query.addProjection(Functions::trackerId.apply(contact));
128     query.addProjection(context);
129     query.addProjection(rdfs::label::function().apply(context));
130
131     foreach(const QString &classIri, queryBuilder.schema().contactClassIris()) {
132         query.addRestriction(contact, rdf::type::resource(), ResourceValue(classIri));
133     }
134
135     PatternGroup contextPattern;
136     contextPattern.setOptional(true);
137     contextPattern.addPattern(contact, nco::hasAffiliation::resource(), context);
138     query.addRestriction(contextPattern);
139
140     return query;
141 }
142
143 static QContactManager::Error
144 bindFilters(QTrackerScalarContactQueryBuilder &queryBuilder,
145             const QContactFilter &filter, Select &select)
146 {
147     Filter result;
148
149     const QContactManager::Error error = queryBuilder.bindFilter(filter, result);
150     select.setFilter(result);
151
152     return error;
153 }
154
155 /// Returns the @p rawValueString with the graphIri removed if present.
156 /// Sets @p isOtherGraph to @c true if the graphIri is not the one of qct and not emoty, @c false otherwise.
157 QString
158 QTrackerContactFetchRequest::fieldStringWithStrippedGraph(const QTrackerContactDetailField &field,
159                                                           const QString &rawValueString, bool &isOtherGraph) const
160 {
161     if (not field.hasOwner()) {
162         return rawValueString;
163     }
164
165     QString string = rawValueString;
166     const int s = string.indexOf(QTrackerScalarContactQueryBuilder::graphSeparator());
167     if (s < 0) {
168         qctWarn(QString::fromLatin1("Could not find graphIri added for field %1: %2").
169                 arg(field.name(), string));
170         isOtherGraph = false;
171     } else {
172         const QString fieldGraphIri = string.mid(s+1);
173         string.truncate(s);
174
175         isOtherGraph =
176             (not fieldGraphIri.isEmpty()) &&
177             (engine()->graphIri() != fieldGraphIri);
178
179         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
180             if (isOtherGraph) {
181                 qDebug() << "Read field from other graph:" << field.name() << string << fieldGraphIri;
182             }
183         }
184     }
185     return string;
186 }
187
188 /// Returns the @p rawValueString splitted into stringlist with the graphIris removed if present.
189 /// Sets @p isOtherGraph to @c true if any graphIri is not the one of qct and not emoty, @c false otherwise.
190 QStringList
191 QTrackerContactFetchRequest::fieldStringListWithStrippedGraph(const QTrackerContactDetailField &field,
192                                                               const QString &rawValueString,
193                                                               bool &isOtherGraph,
194                                                               bool cleanList
195                                                              ) const
196 {
197     QStringList list = rawValueString.split(QTrackerScalarContactQueryBuilder::listSeparator());
198     if (cleanList) {
199         list.removeAll(QString());
200         list.removeDuplicates();
201     }
202
203     if (not field.hasOwner()) {
204         return list;
205     }
206
207     isOtherGraph = false;
208     for (int i = 0; i<list.size(); ++i) {
209         QString &string = list[i];
210         const int s = string.indexOf(QTrackerScalarContactQueryBuilder::graphSeparator());
211         if (s < 0) {
212             qctWarn(QString::fromLatin1("Could not find graphIri added for field %1: %2").
213                     arg(field.name(), string));
214         } else {
215             const QString fieldGraphIri = string.mid(s+1);
216             string.truncate(s);
217
218             const bool isItemFromOtherGraph =
219                 (not fieldGraphIri.isEmpty()) &&
220                 (engine()->graphIri() != fieldGraphIri);
221             isOtherGraph |= isItemFromOtherGraph;
222             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
223                 if (isItemFromOtherGraph) {
224                     qDebug() << "Read field from other graph:" << field.name() << string << fieldGraphIri;
225                 }
226             }
227         }
228     }
229     return list;
230 }
231
232
233 QContactManager::Error
234 QTrackerContactFetchRequest::bindDetails(QueryContext &context) const
235 {
236     QTrackerScalarContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
237
238     context.query = baseQuery(queryBuilder);
239
240     foreach(const QTrackerContactDetail &detail, context.schema().details()) {
241         if (detail.isSynthesized()) {
242             continue;
243         }
244
245         // skip details which are not needed according to the fetch hints
246         if (not context.definitionHints.contains(detail.name()) &&
247                 not context.definitionHints.isEmpty()) {
248             continue;
249         }
250
251         if (detail.fields().isEmpty()) {
252             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
253                 qctWarn(QString::fromLatin1("Not implemented yet, skipping %1 detail").
254                         arg(detail.name()));
255             }
256
257             continue;
258         }
259
260         // bind this detail to the proper query
261         const int firstColumn = context.query.projections().count();
262         const QContactManager::Error error = queryBuilder.bindFields(detail, context.query);
263
264         // verify results
265         if (error != QContactManager::NoError) {
266             context.query = Select();
267             return error;
268         }
269
270         if (context.query.isEmpty()) {
271             qctWarn("No queries built");
272             return QContactManager::UnspecifiedError;
273         }
274
275         // store this detail's context
276         const int lastColumn = context.query.projections().count();
277         context.details += DetailContext(detail, firstColumn, lastColumn);
278     }
279
280     // bind the filters
281     const QContactManager::Error filterError = bindFilters(queryBuilder, m_filter, context.query);
282     if (filterError != QContactManager::NoError) {
283         return filterError;
284     }
285
286     // Create almost empty base query if actually no unique details where requested by the
287     // fetch hints. This is needed to let fetchBaseModel() populate the contact cache, and more
288     // importantly it is needed to avoid bogus "DoesNotExistError" errors if existing contacts
289     // are requested via local id filter, but those contacts don't have any of the requested
290     // details.
291     if (context.query.isEmpty()) {
292         QTrackerScalarContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
293
294         context.query = baseQuery(queryBuilder);
295         const QContactManager::Error error = bindFilters(queryBuilder, m_filter, context.query);
296
297         if (QContactManager::NoError != error) {
298             context.query = Select();
299             return error;
300         }
301     }
302
303     return QContactManager::NoError;
304 }
305
306 QContactManager::Error
307 QTrackerContactFetchRequest::buildQuery(QueryContext &context) const
308 {
309     if (not m_fetchHint.detailDefinitionsHint().isEmpty()) {
310         context.definitionHints = m_fetchHint.detailDefinitionsHint().toSet();
311
312         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
313             qDebug() << "explicit definition hints:" << m_fetchHint.detailDefinitionsHint();
314         }
315
316         // make sure we can synthesized all requested details
317         foreach(const QString &name, m_fetchHint.detailDefinitionsHint()) {
318             const QTrackerContactDetail *const detail = context.schema().detail(name);
319
320             if (0 != detail) {
321                 context.definitionHints += detail->dependencies();
322             } else if (not context.schema().isSyntheticDetail(name)) {
323                 context.customDetailHints += name;
324             }
325         }
326
327         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
328             qDebug() << "final definition hints:" << context.definitionHints;
329             qDebug() << "custom detail hints:" << context.customDetailHints;
330         }
331     } else {
332         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
333             qDebug() << "fetching all details";
334         }
335
336         context.fetchAllDetails = true;
337         context.definitionHints.clear();
338     }
339
340     const QContactManager::Error error = bindDetails(context);
341
342     if (QContactManager::NoError != error) {
343         return error;
344     }
345
346     // Build custom detail query when needed
347     if (not context.customDetailHints.isEmpty() || context.fetchAllDetails) {
348         QTrackerScalarContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
349         context.customDetailColumn = context.query.projections().count();
350         queryBuilder.bindCustomDetails(context.query, context.customDetailHints);
351     }
352
353     if (context.query.isEmpty()) {
354         qctWarn("No queries built");
355         return QContactManager::UnspecifiedError;
356     }
357
358     return QContactManager::NoError;
359 }
360
361 Select
362 QTrackerContactFetchRequest::query(const QString &contactType,
363                                    QContactManager::Error &error) const
364 {
365     QTrackerContactDetailSchemaMap::ConstIterator schema = engine()->schemas().find(contactType);
366
367     if (schema == engine()->schemas().constEnd()) {
368         error = QContactManager::BadArgumentError;
369         return Select();
370     }
371
372     QueryContext context(engine()->schema(contactType));
373     error = buildQuery(context);
374     return context.query;
375 }
376
377 /// Returns a list of integers created from the strings in @p stringList.
378 /// The integers are in the order of the string, if a string could not be converted,
379 /// the corresponding integer is @c 0.
380 static QList<int>
381 toIntList(const QStringList &stringList)
382 {
383     QList<int> intList;
384
385     foreach(const QString &element, stringList) {
386         intList.append(element.toInt());
387     }
388
389     return intList;
390 }
391
392 /// Returns a list of integers created from the content of @p string.
393 /// The string is separated using @p separator (defaults to QTrackerScalarContactQueryBuilder::listSeparator()),
394 /// the integers are in the order of the separated substrings.
395 /// If a substring could not be converted, the corresponding integer is @c 0.
396 static QList<int>
397 toIntList(const QString &string,
398           const QChar separator = QTrackerScalarContactQueryBuilder::listSeparator())
399 {
400     return toIntList(string.split(separator));
401 }
402
403 /// returns the subtype(s) for the given field as a QVariant.
404 /// Uses the default subtype(s) if the passed @p subTypes is empty.
405 static QVariant
406 fetchSubTypes(const QTrackerContactDetailField &field,
407               QSet<QString> subTypes)
408 {
409     // apply default value if no subtypes could be read
410     if (subTypes.isEmpty() && field.hasDefaultValue()) {
411         switch(field.defaultValue().type()) {
412         case QVariant::String:
413             subTypes.insert(field.defaultValue().toString());
414             break;
415         case QVariant::StringList:
416             subTypes = field.defaultValue().toStringList().toSet();
417             break;
418         default:
419             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %2").
420                     arg(QLatin1String(field.defaultValue().typeName()), field.name()));
421             break;
422         }
423     }
424
425     // set field if any subtypes could be identified
426     if (not subTypes.isEmpty()) {
427         switch(field.dataType()) {
428         case QVariant::String:
429             // TODO: clone this detail multiple times when |subTypes| > 1
430             return *subTypes.begin();
431             break;
432
433         case QVariant::StringList:
434             return QStringList(subTypes.toList());
435             break;
436
437         default:
438             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %2").
439                     arg(QLatin1String(QVariant::typeToName(field.dataType())), field.name()));
440             break;
441         }
442     }
443
444     return QVariant();
445 }
446
447 static int
448 trackerId(const ResourceInfo &resource)
449 {
450     return QctResourceCache::instance().trackerId(resource.iri());
451 }
452
453 /// Returns the subtypes encoded in @p rawValueString as string or stringlist in a QVariant.
454 /// Returns the default subtype(s) if there is no known subtype in @p rawValueString.
455 static QVariant
456 fetchSubTypesClasses(const QTrackerContactDetailField &field,
457                      const QString &rawValueString)
458 {
459     const QList<int> fetchedSubTypes = toIntList(rawValueString);
460     QSet<QString> subTypes;
461
462     foreach(const ClassInfoBase &ci, field.subTypeClasses()) {
463         if (fetchedSubTypes.contains(trackerId(ci))) {
464             subTypes.insert(ci.text());
465         }
466     }
467
468     return fetchSubTypes(field, subTypes);
469 }
470
471 /// Returns the list of details, with each detail having all subtypes collected and set.
472 /// If there was no subtype for a detail, it was set the default subtype(s).
473 static QList<QContactDetail>
474 unifyPropertySubTypes(const QMultiHash<QString, QContactDetail> &details,
475                       const QTrackerContactDetailField &subTypeField)
476 {
477     QList<QContactDetail> results;
478     QStringList subTypes = details.uniqueKeys();
479     subTypes.removeOne(QString());
480
481     foreach(QContactDetail detail, details.values(QString())) {
482         QSet<QString> detailSubTypes;
483
484         foreach(const QString &subType, subTypes) {
485             if(details.values(subType).contains(detail)) {
486                 detailSubTypes.insert(subType);
487             }
488         }
489
490         detail.setValue(subTypeField.name(), fetchSubTypes(subTypeField, detailSubTypes));
491
492         results.append(detail);
493     }
494
495     return results;
496 }
497
498 QVariant
499 QTrackerContactFetchRequest::fetchInstances(const QTrackerContactDetailField &field,
500                                             const QString &rawValueString, bool &isReadOnly) const
501 {
502     if (rawValueString.isEmpty()) {
503         return QVariant();
504     }
505
506     // apply string list when requested
507     if (QVariant::StringList == field.dataType()) {
508         const QStringList rawValueList = fieldStringListWithStrippedGraph(field, rawValueString, isReadOnly);
509         const QList<int> fetchedInstances = toIntList(rawValueList);
510
511         QStringList instances;
512
513         foreach(const InstanceInfoBase &ii, field.allowableInstances()) {
514             if (fetchedInstances.contains(trackerId(ii))) {
515                 instances.append(ii.text());
516             }
517         }
518
519         // append default value if instances could not be identified
520         if (instances.isEmpty() && field.hasDefaultValue()) {
521             instances.append(field.defaultValue().toString());
522         }
523
524         return instances;
525     }
526
527     // otherwise apply single value
528     bool hasInstanceId = false;
529     const int instanceId = fieldStringWithStrippedGraph(field, rawValueString, isReadOnly).toInt(&hasInstanceId);
530
531     if (hasInstanceId) {
532         foreach(const InstanceInfoBase &instance, field.allowableInstances()) {
533             if (trackerId(instance) == instanceId) {
534                 return instance.value();
535             }
536         }
537     }
538
539     qctWarn(QString::fromLatin1("Unknown instance id for field %1").
540             arg(field.name()));
541
542     // apply default value if instances could not be identified
543     if (field.hasDefaultValue()) {
544         return field.defaultValue();
545     } else {
546         return QVariant();
547     }
548 }
549
550 /// Returns @c true if @p variant is an empty string or an empty stringlist.
551 static bool
552 isEmptyStringOrStringList(const QVariant &variant)
553 {
554     return((QVariant::String == variant.type() && variant.toString().isEmpty()) ||
555            (QVariant::StringList == variant.type() && variant.toStringList().isEmpty()));
556 }
557
558 QVariant
559 QTrackerContactFetchRequest::fetchField(const QTrackerContactDetailField &field,
560                                         const QString &rawValueString,
561                                         bool &isReadOnly ) const
562 {
563     // set default assumption
564     isReadOnly = false;
565
566     if (field.hasSubTypeClasses()) {
567         return fetchSubTypesClasses(field, rawValueString);
568     }
569
570     if (rawValueString.isEmpty()) {
571         return QVariant();
572     }
573
574     if (field.hasSubTypeProperties()) {
575         return QVariant(fieldStringWithStrippedGraph(field, rawValueString, isReadOnly));
576     }
577
578     if (field.restrictsValues()) {
579         if (not field.allowableInstances().isEmpty()) {
580             return fetchInstances(field, rawValueString, isReadOnly);
581         }
582
583         if (not field.allowableValues().isEmpty()) {
584             if (QVariant::StringList == field.dataType()) {
585                 return fieldStringListWithStrippedGraph(field, rawValueString, isReadOnly);
586             } else {
587                 return fieldStringWithStrippedGraph(field, rawValueString, isReadOnly);
588             }
589         }
590     }
591
592     const QString valueString = fieldStringWithStrippedGraph(field, rawValueString, isReadOnly);
593
594     QVariant parsedValue;
595     if (not field.parseValue(valueString, parsedValue)) {
596         qctWarn(QString::fromLatin1("Cannot convert value to %1 for field %2: %3").
597                 arg(QLatin1String(QVariant::typeToName(field.dataType())),
598                     field.name(), valueString));
599
600         return QVariant();
601     }
602
603     if (isEmptyStringOrStringList(parsedValue)) {
604         return QVariant();
605     }
606
607     return parsedValue;
608 }
609
610 void
611 QTrackerContactFetchRequest::fetchCustomValues(const QTrackerContactDetailField &field,
612                                                QVariant &fieldValue,
613                                                const QString &rawValueString,
614                                                bool &isReadOnly) const
615 {
616     switch (field.dataType()) {
617     case QVariant::StringList: {
618         QStringList values =
619                 fieldValue.toStringList() +
620                 fieldStringListWithStrippedGraph(field, rawValueString, isReadOnly, true);
621
622         if (not values.isEmpty()) {
623             fieldValue = values;
624         } else {
625             fieldValue = QVariant();
626         }
627
628         break;
629     }
630
631     case QVariant::String: {
632         if (not rawValueString.isEmpty()) {
633             fieldValue = fieldStringWithStrippedGraph(field, rawValueString, isReadOnly);
634         }
635
636         break;
637     }
638
639     default:
640         qctWarn(QString::fromLatin1("Cannot fetch custom values for field %2: "
641                                     "Data type %3 is not supported yet.").
642                 arg(field.name(), QLatin1String(QVariant::typeToName(field.dataType()))));
643         break;
644     }
645 }
646
647 bool
648 QTrackerContactFetchRequest::saveDetail(ContactCache::iterator contact,
649                                         QContactDetail &detail,
650                                         const QTrackerContactDetail &definition)
651 {
652     if (not detail.detailUri().isEmpty()) {
653         QString detailUri;
654
655         // Detail URI scheme is different from resource IRI scheme
656         // for details like QContactPresence or online avatars.
657         if (definition.detailUriScheme() != definition.resourceIriScheme()) {
658             bool valid = false;
659             QVariant value;
660
661             value = QTrackerContactSubject::parseIri(definition.resourceIriScheme(),
662                                                      detail.detailUri(), &valid);
663
664             if (valid) {
665                 detailUri = QTrackerContactSubject::makeIri(definition.detailUriScheme(),
666                                                             QVariantList() << value);
667             }
668         }
669
670         if (not detailUri.isEmpty()) {
671             detail.setLinkedDetailUris(detail.detailUri());
672             detail.setDetailUri(detailUri);
673         }
674     }
675
676     return contact->saveDetail(&detail);
677 }
678
679 /// Returns in general the result of calling @code toString() @endcode on @p variant,
680 /// but ensures that for the types @c QVariant::DateTime and @c QVariant::Date the result
681 /// has a trailing Z if the time was given in UTC.
682 /// This is a workaround for http://bugreports.qt.nokia.com/browse/QTBUG-9698
683 static QString
684 toStringWithFixedUTCDateTime(const QVariant &variant)
685 {
686     if (QVariant::DateTime == variant.type() ||
687         QVariant::Date == variant.type()) {
688         const QDateTime dateTime = variant.toDateTime();
689         QString dateTimeString = dateTime.toString(Qt::ISODate);
690         if (dateTime.timeSpec() == Qt::UTC) {
691             dateTimeString.append(QLatin1Char('Z'));
692         }
693         return dateTimeString;
694     }
695
696     return variant.toString();
697 }
698
699 void
700 QTrackerContactFetchRequest::fetchUniqueDetail(QList<QContactDetail> &details,
701                                                const DetailContext &context,
702                                                const QString &affiliation,
703                                                const QSparqlResultRow &row)
704 {
705     QContactDetail detail(context.definition().name());
706
707     int lastColumn = context.firstColumn();
708
709     // FIXME: we don't support subtypes by class for unique details
710
711     bool empty = true;
712     bool isDetailReadOnly = false;
713     foreach(const QTrackerContactDetailField &field, context.definition().fields()) {
714         if (not field.hasPropertyChain()) {
715             continue;
716         }
717
718         const QString rawValueString = toStringWithFixedUTCDateTime(row.value(lastColumn++));
719
720         if (rawValueString.isEmpty()) {
721             continue;
722         }
723
724         bool isFieldReadOnly = false;
725         const QVariant fieldValue = fetchField(field, rawValueString, isFieldReadOnly);
726
727         if (fieldValue.isNull()) {
728             continue;
729         }
730
731         // if any field is not owned by qct, set whole detail readonly (can't do that on field level)
732         if (isFieldReadOnly) {
733             isDetailReadOnly = true;
734         }
735
736         if (not field.hasSubTypes()) {
737             detail.setValue(field.name(), fieldValue);
738             empty = false;
739         } else {
740             if (field.hasSubTypeProperties()) {
741                 // collect subtypes by those property subtype columns which have some string set
742                 QSet<QString> subTypes;
743
744                 foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
745                     if (lastColumn == row.count()) {
746                         qctWarn(QString::fromLatin1("Trying to fetch more detail fields than we have "
747                                                     "columns for field %1 subtypes").
748                                 arg(field.name()));
749                         return;
750                     }
751
752                     if (not row.value(lastColumn++).toString().isEmpty()) {
753                         subTypes.insert(pi.value().toString());
754                     }
755                 }
756
757                 detail.setValue(field.name(), fetchSubTypes(field, subTypes));
758             }
759         }
760     }
761
762     // if detail is empty, return as it is ignored - no need to update uri
763     if (empty) {
764         return;
765     }
766
767     // if any field is not owned by qct, set whole detail readonly (can't do that on field level)
768     if (isDetailReadOnly) {
769         QContactManagerEngine::setDetailAccessConstraints(&detail, detail.accessConstraints()|QContactDetail::ReadOnly);
770     }
771
772     // update uri
773     if (context.definition().hasDetailUri()) {
774         // If the detailUri was on nco:hasAffiliation, we don't have anything to
775         // do since we already know the IRI of the affiliation. If it was on
776         // another property, it is always stored in the first column
777         const QTrackerContactDetailField *detailUriField = context.definition().detailUriField();
778
779         foreach (const PropertyInfoBase &pi, detailUriField->propertyChain()) {
780             if (pi.hasDetailUri()) {
781                 if (pi.iri() == nco::hasAffiliation::iri()) {
782                     detail.setDetailUri(affiliation);
783                 } else {
784                     detail.setDetailUri(row.value(lastColumn++).toString());
785                 }
786                 break;
787             }
788         }
789     }
790
791     details.append(detail);
792 }
793
794 static void
795 fetchMultiDetailUri(QContactDetail &detail,
796                     const QTrackerContactDetail& definition,
797                     QStringList &fieldsData)
798 {
799     if (definition.hasDetailUri()) {
800         // If the detailUri was on nco:hasAffiliation, we don't have anything to
801         // do since we already know the IRI of the affiliation. If it was on
802         // another property, it is always stored in the first column
803         const QTrackerContactDetailField *detailUriField = definition.detailUriField();
804
805         PropertyInfoList::ConstIterator pi = detailUriField->propertyChain().constBegin();
806
807         for(; pi != detailUriField->propertyChain().constEnd(); ++pi) {
808             if (pi->hasDetailUri()) {
809                 detail.setDetailUri(fieldsData.takeFirst());
810                 break;
811             }
812         }
813     }
814 }
815
816 /// creates a copy of @p _fields with all fields with an inverted property in the chain at the end
817 static QList<QTrackerContactDetailField>
818 moveWithInversePropertyAtEnd(const QList<QTrackerContactDetailField>& fields)
819 {
820     QList<QTrackerContactDetailField> sortedFields;
821     QList<QTrackerContactDetailField> inverseFields;
822
823     foreach(const QTrackerContactDetailField &field, fields) {
824         if (not field.hasPropertyChain()) {
825             continue;
826         }
827
828         if (field.propertyChain().hasInverseProperty()) {
829             inverseFields.append(field);
830         } else {
831             sortedFields.append(field);
832         }
833     }
834
835     sortedFields += inverseFields;
836     return sortedFields;
837 }
838
839 void
840 QTrackerContactFetchRequest::fetchMultiDetail(QContactDetail &detail,
841                                               const DetailContext &context,
842                                               const QString &rawValueString)
843 {
844     QStringList fieldsData = rawValueString.split(QTrackerScalarContactQueryBuilder::fieldSeparator());
845
846     fetchMultiDetailUri(detail, context.definition(), fieldsData);
847
848     // Fields that have inverse properties are always stored last, so respect
849     // this order when fetching them
850     const QList<QTrackerContactDetailField> fields =
851         moveWithInversePropertyAtEnd(context.definition().fields());
852
853     bool isDetailReadOnly = false;
854     foreach(const QTrackerContactDetailField &field, fields) {
855         // Property subtypes are done in unifyPropertySubTypes
856         if (field.hasSubTypeProperties()) {
857             continue;
858         }
859
860         if (fieldsData.empty()) {
861             qctWarn(QString::fromLatin1("Trying to fetch more detail fields than we have "
862                                         "columns for detail %1").arg(context.definition().name()));
863             return;
864         }
865
866         QVariant fieldValue;
867         bool isFieldReadOnly = false;
868
869         if (not field.isWithoutMapping()) {
870             fieldValue = fetchField(field, fieldsData.takeFirst(), isFieldReadOnly);
871         }
872
873         if (field.permitsCustomValues()) {
874             if (fieldsData.empty()) {
875                 qctWarn(QString::fromLatin1("Missing custom values for field %1 of detail %2").
876                                             arg(field.name(), context.definition().name()));
877             }
878
879             fetchCustomValues(field, fieldValue, fieldsData.takeFirst(), isFieldReadOnly);
880         }
881
882         if (isFieldReadOnly) {
883             isDetailReadOnly = true;
884         }
885
886         if (not fieldValue.isNull()) {
887             detail.setValue(field.name(), fieldValue);
888         }
889     }
890     // if any field is not owned by qct, set whole detail readonly (can't do that on field level)
891     if (isDetailReadOnly) {
892         QContactManagerEngine::setDetailAccessConstraints(&detail, detail.accessConstraints()|QContactDetail::ReadOnly);
893     }
894 }
895
896 /* Enhances \sa QContactDetail::isEmpty by ignoring context and detail uri - stored also as values */
897 static bool
898 areContactDetailDataValuesEmpty(const QContactDetail &detail)
899 {
900     const QVariantMap variantValues = detail.variantValues();
901     if (variantValues.empty()) {
902         return true;
903     }
904     if (variantValues.size() <= 2) {
905         QStringList keys(variantValues.keys());
906         keys.removeOne(QContactDetail::FieldDetailUri);
907         keys.removeOne(QContactDetail::FieldLinkedDetailUris);
908         return (keys.size() == 0);
909     }
910     return false;
911 }
912
913 void
914 QTrackerContactFetchRequest::fetchMultiDetails(QList<QContactDetail> &details,
915                                                const DetailContext &detailContext,
916                                                const QSparqlResultRow &row)
917 {
918     int lastColumn = detailContext.firstColumn();
919
920     const QTrackerContactDetailField *subTypeField = detailContext.definition().subTypeField();
921
922     // First demarshall the raw data
923     const QString rawValueString = row.value(lastColumn++).toString();
924
925     if (rawValueString.isEmpty()) {
926         return;
927     }
928
929     QHash<QString, QString> subTypesDetailsData;
930     subTypesDetailsData.insert(QString(), rawValueString);
931
932     // Subtype properties are spread over various details (one for
933     // each subtype)
934     // If we have subtype properties, fetch as many details as we
935     // have subtypes
936     if (subTypeField && subTypeField->hasSubTypeProperties()) {
937         // Only used for debug message
938         const QString &definitionName = detailContext.definition().name();
939
940         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
941             if (lastColumn > detailContext.lastColumn()) {
942                 qctWarn(QString::fromLatin1("Trying to fetch more subtype details "
943                                             "than we have columns for detail %1").
944                         arg(definitionName));
945                 return;
946             }
947
948             const QString rawSubTypeData = row.value(lastColumn++).toString();
949
950             if (rawSubTypeData.isEmpty()) {
951                 continue;
952             }
953
954             subTypesDetailsData.insert(pi.value().toString(), rawSubTypeData);
955         }
956     }
957
958     // Now parse each fetched detail into a proper QContactDetail,
959     // keeping the info about its subtype
960     QMultiHash<QString, QContactDetail> subTypeDetails;
961     for (QHash<QString,QString>::ConstIterator it = subTypesDetailsData.constBegin();
962          it != subTypesDetailsData.constEnd(); ++it ) {
963         const QString &subType = it.key();
964         const QString &rawDetailsData = it.value();
965         const QStringList detailsData = rawDetailsData.split(QTrackerScalarContactQueryBuilder::detailSeparator());
966
967         foreach(const QString &detailData, detailsData) {
968             if (detailData.isEmpty()) {
969                 continue;
970             }
971
972             QContactDetail detail(detailContext.definition().name());
973
974             fetchMultiDetail(detail, detailContext, detailData);
975             subTypeDetails.insert(subType,detail);
976         }
977     }
978
979     // And finally unify the fetched details
980
981     // There is at least one key, the one of the default type
982     if (subTypeField && subTypeField->hasSubTypeProperties()) {
983         details.append(unifyPropertySubTypes(subTypeDetails, *subTypeField));
984     } else {
985         details.append(subTypeDetails.values());
986     }
987 }
988
989 void
990 QTrackerContactFetchRequest::fetchResults(ContactCache &results, const QueryContext &queryContext)
991 {
992     QSparqlResult *const result = queryContext.result;
993
994     for(bool hasRow = result->first(); not canceled() && hasRow; hasRow = result->next()) {
995         const QSparqlResultRow & row = result->current();
996
997         // identify the contact
998         const QString contactIri = row.value(0).toString();
999         const QContactLocalId localId = row.value(1).toUInt();
1000         const QString affiliation = row.value(2).toString();
1001         const QString affiliationContext = qctCamelCase(row.value(3).toString());
1002
1003         // create contact if not already existing
1004         ContactCache::Iterator contact(results.find(localId));
1005
1006         if(contact == results.end()) {
1007             QContact c;
1008             QContactId id;
1009             id.setLocalId(localId);
1010             id.setManagerUri(engine()->managerUri());
1011             c.setId(id);
1012             c.setType(queryContext.contactType());
1013
1014             contact = results.insert(localId, c);
1015         }
1016
1017         // read details
1018         for(QList<DetailContext>::ConstIterator detailContext = queryContext.details.constBegin();
1019             detailContext != queryContext.details.constEnd();
1020             ++detailContext) {
1021             QList<QContactDetail> details;
1022
1023             if (detailContext->definition().isUnique()) {
1024                 fetchUniqueDetail(details, *detailContext, affiliation, row);
1025             } else {
1026                 fetchMultiDetails(details, *detailContext, row);
1027             }
1028
1029             foreach(QContactDetail detail, details) {
1030                 // Ignore the detail if we fetched no fields for it (or just detailUri)
1031                 if (areContactDetailDataValuesEmpty(detail)) {
1032                     continue;
1033                 }
1034
1035                 if (detailContext->definition().hasContext()) {
1036                     if (affiliationContext.isEmpty()) {
1037                         detail.setContexts(QContactDetail::ContextOther);
1038                     } else {
1039                         detail.setContexts(affiliationContext);
1040                     }
1041                 }
1042
1043                 if (not saveDetail(contact, detail, detailContext->definition())) {
1044                     qctWarn(QString::fromLatin1("Could not save detail %1 on contact %2").
1045                             arg (detail.definitionName(), contactIri));
1046                 }
1047             }
1048         }
1049
1050         fetchCustomDetails(queryContext, contact, row);
1051     }
1052 }
1053
1054 QContactDetail
1055 QTrackerContactFetchRequest::fetchCustomDetail(const QString &rawValue, const QString &contactType)
1056 {
1057     QStringList tokens = rawValue.split(QTrackerScalarContactQueryBuilder::fieldSeparator());
1058
1059     // Minimum number of tokens is detail name + 1 field name/value tuple
1060     if (tokens.size() < 3) {
1061         return QContactDetail();
1062     }
1063
1064     QMultiHash<QString, QString> detailValues;
1065     QString detailName = tokens.takeFirst();
1066
1067     QContactDetail detail(detailName);
1068
1069     const QContactDetailDefinitionMap detailDefs = engine()->detailDefinitions(contactType, 0);
1070
1071
1072     while(tokens.size() >= 2) {
1073         QString fieldName = tokens.takeFirst();
1074         QStringList fieldValues = tokens.takeFirst().split(QTrackerScalarContactQueryBuilder::listSeparator());
1075         foreach(const QString &f, fieldValues) {
1076             detailValues.insert(fieldName, f);
1077         }
1078     }
1079
1080     foreach(const QString &fieldName, detailValues.uniqueKeys()) {
1081         QMap<uint, QString> orderedValues;
1082
1083         // Values are retrieved as "tracker-id:value" pairs.
1084         // Order values by tracker-id and extract the value.
1085         foreach(const QString &s, detailValues.values(fieldName)) {
1086             const int i = s.indexOf(QLatin1Char(':'));
1087             orderedValues.insert(s.left(i).toUInt(), s.mid(i + 1));
1088         }
1089
1090         // QVariant cannot deal with QList<QString> :-/
1091         const QStringList fieldValues = orderedValues.values();
1092         QVariant fieldValue;
1093
1094         if (fieldValues.size() == 1) {
1095             fieldValue = fieldValues.first();
1096         } else {
1097             fieldValue = fieldValues;
1098         }
1099
1100         detail.setValue(fieldName, fieldValue);
1101
1102         const QContactDetailFieldDefinitionMap fieldDefs = detailDefs[detail.definitionName()].fields();
1103         const QContactDetailFieldDefinition fieldDef = fieldDefs.value(fieldName);
1104
1105         if (QVariant::Invalid != fieldDef.dataType() && fieldValue.convert(fieldDef.dataType())) {
1106             detail.setValue(fieldName, fieldValue);
1107         }
1108     }
1109
1110     return detail;
1111 }
1112
1113 void
1114 QTrackerContactFetchRequest::fetchCustomDetails(const QueryContext &queryContext,
1115                                                 ContactCache::iterator contact,
1116                                                 const QSparqlResultRow &row)
1117 {
1118     if (queryContext.customDetailColumn < 0 ||
1119         queryContext.customDetailColumn >= row.count()) {
1120         return;
1121     }
1122
1123     const QString rawValue = row.value(queryContext.customDetailColumn).toString();
1124
1125     foreach(const QString &rawDetailValue, rawValue.split(QTrackerScalarContactQueryBuilder::detailSeparator())) {
1126         QContactDetail detail = fetchCustomDetail(rawDetailValue, contact->type());
1127
1128         if (not areContactDetailDataValuesEmpty(detail)) {
1129             contact->saveDetail(&detail);
1130         }
1131     }
1132 }
1133
1134 static void
1135 updateDetailLinks(QContact &contact)
1136 {
1137     typedef QHash<QString, QContactDetail> DetailHash;
1138
1139     DetailHash detailHash;
1140
1141     foreach(const QContactDetail &detail, contact.details()) {
1142         if (not detail.detailUri().isEmpty()) {
1143             detailHash.insert(detail.detailUri(), detail);
1144         }
1145     }
1146
1147     for(DetailHash::ConstIterator detail = detailHash.constBegin(); detail != detailHash.constEnd(); ++detail) {
1148         foreach(const QString &detailUri, detail->linkedDetailUris()) {
1149             DetailHash::Iterator target = detailHash.find(detailUri);
1150
1151             if (target == detailHash.constEnd()) {
1152                 continue;
1153             }
1154
1155             QStringList linkedDetailUris = target->linkedDetailUris();
1156
1157             if (linkedDetailUris.contains(detail->detailUri())) {
1158                 continue;
1159             }
1160
1161             linkedDetailUris.append(detail->detailUri());
1162             target->setLinkedDetailUris(linkedDetailUris);
1163             contact.saveDetail(&target.value());
1164         }
1165     }
1166 }
1167
1168 static void
1169 removeDuplicateDetails(QContact &contact)
1170 {
1171     QSet<QContactDetail> knownDetails;
1172
1173     foreach(QContactDetail detail, contact.details()) {
1174         if(knownDetails.contains(detail)) {
1175             contact.removeDetail(&detail);
1176         } else {
1177             knownDetails.insert(detail);
1178         }
1179     }
1180 }
1181
1182 void
1183 QTrackerContactFetchRequest::run()
1184 {
1185     if (canceled()) {
1186         emitResultLater();
1187         return;
1188     }
1189
1190     ContactCache results;
1191
1192     foreach(const QTrackerContactDetailSchema &schema, engine()->schemas())  {
1193         // build RDF query
1194         QueryContext context(schema);
1195
1196         const QContactManager::Error error = buildQuery(context);
1197
1198         if (QContactManager::NoError != error) {
1199             setLastError(error);
1200             emitResultLater();
1201         }
1202
1203         // run the query
1204         const QSparqlQuery query(context.query.sparql());
1205         QScopedPointer<QSparqlResult> result(runQuery(query, SyncQueryOptions));
1206
1207         if (result.isNull()) {
1208             return; // runQuery() called reportError()
1209         }
1210
1211         context.result = result.data();
1212         fetchResults(results, context);
1213     }
1214
1215     // Update synthetic details and detail links
1216     const QStringList &detailHint = m_fetchHint.detailDefinitionsHint();
1217     const bool calculateGlobalPresence(detailHint.contains(QContactGlobalPresence::DefinitionName) || detailHint.isEmpty());
1218     const bool calculateDisplayLabel(detailHint.contains(QContactDisplayLabel::DefinitionName) || detailHint.isEmpty());
1219     const bool calculateAvatar(detailHint.contains(QContactAvatar::DefinitionName) || detailHint.isEmpty());
1220
1221     for(ContactCache::Iterator c = results.begin(); not canceled() && c != results.end() ; ++c) {
1222         removeDuplicateDetails(c.value());
1223
1224         if (calculateGlobalPresence) {
1225             engine()->updateGlobalPresence(c.value());
1226         }
1227         if (calculateDisplayLabel) {
1228             engine()->updateDisplayLabel(c.value(), m_nameOrder);
1229         }
1230         if (calculateAvatar) {
1231             engine()->updateAvatar(c.value());
1232         }
1233
1234         updateDetailLinks(c.value());
1235     }
1236
1237     // Sort the contacts
1238     foreach(const QContactLocalId localId, QContactManagerEngine::sortContacts(results.values(), m_sorting)) {
1239         m_contacts.append(results.value(localId));
1240     }
1241
1242     // Report the result
1243     emitResultLater();
1244 }
1245
1246 void
1247 QTrackerContactFetchRequest::emitResult(QContactManager::Error error)
1248 {
1249     engine()->updateContactFetchRequest(staticCast(engine()->request(this).data()),
1250                                         m_contacts, error, QContactAbstractRequest::FinishedState);
1251 }
1252
1253 ///////////////////////////////////////////////////////////////////////////////////////////////////
1254
1255 #include "moc_contactfetchrequest.cpp"