summaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/yql/FieldFiller.java
blob: 2dfa6ef3e3a5533743e74a55245cbced2a67f9f4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.yql;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.yahoo.api.annotations.Beta;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig.Documentdb;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig.Documentdb.Summaryclass;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig.Documentdb.Summaryclass.Fields;
import static com.yahoo.prelude.fastsearch.VespaBackEndSearcher.SORTABLE_ATTRIBUTES_SUMMARY_CLASS;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.query.Presentation;
import com.yahoo.search.searchchain.Execution;

/**
 * Ensure the fields specified in {@link Presentation#getSummaryFields()} are
 * available after filling phase.
 *
 * @author stiankri
 * @author Steinar Knutsen
 */
@Beta
@After(MinimalQueryInserter.EXTERNAL_YQL)
public class FieldFiller extends Searcher {

    private final Set<String> intersectionOfAttributes;
    private final SummaryIntersections summaryDb = new SummaryIntersections();
    public static final CompoundName FIELD_FILLER_DISABLE = new CompoundName("FieldFiller.disable");

    private static class SummaryIntersections {
        private final Map<String, Map<String, Set<String>>> db = new HashMap<>();

        void add(String dbName, Summaryclass summary) {
            Map<String, Set<String>> docType = getOrCreateDocType(dbName);
            Set<String> fields = new HashSet<>(summary.fields().size());
            for (Fields f : summary.fields()) {
                fields.add(f.name());
            }
            docType.put(summary.name(), fields);
        }

        private Map<String, Set<String>> getOrCreateDocType(String dbName) {
            Map<String, Set<String>> docType = db.get(dbName);
            if (docType == null) {
                docType = new HashMap<>();
                db.put(dbName, docType);
            }
            return docType;
        }

        boolean hasAll(Set<String> requested, String summaryName, Set<String> restrict) {
            Set<String> explicitRestriction;
            Set<String> intersection = null;

            if (restrict.isEmpty()) {
                explicitRestriction = db.keySet();
            } else {
                explicitRestriction = restrict;
            }

            for (String docType : explicitRestriction) {
                Map<String, Set<String>> summaries = db.get(docType);
                Set<String> summary;

                if (summaries == null) {
                    continue;
                }
                summary = summaries.get(summaryName);
                if (summary == null) {
                    intersection = null;
                    break;
                }
                if (intersection == null) {
                    intersection = new HashSet<>(summary.size());
                    intersection.addAll(summary);
                } else {
                    intersection.retainAll(summary);
                }
            }
            return intersection != null && intersection.containsAll(requested);
        }
    }

    public FieldFiller(DocumentdbInfoConfig config) {
        intersectionOfAttributes = new HashSet<>();
        boolean first = true;

        for (Documentdb db : config.documentdb()) {
            for (Summaryclass summary : db.summaryclass()) {
                Set<String> attributes;
                if (SORTABLE_ATTRIBUTES_SUMMARY_CLASS.equals(summary.name())) {
                    attributes = new HashSet<>(summary.fields().size());
                    for (Fields f : summary.fields()) {
                        attributes.add(f.name());
                    }
                    if (first) {
                        first = false;
                        intersectionOfAttributes.addAll(attributes);
                    } else {
                        intersectionOfAttributes.retainAll(attributes);
                    }
                }
                // yes, we store attribute prefetch here as well, this is in
                // case we get a query where we have a restrict parameter which
                // makes filling with attribute prefetch possible even though it
                // wouldn't have been possible without restricting the set of
                // doctypes
                summaryDb.add(db.name(), summary);
            }
        }
    }

    @Override
    public Result search(Query query, Execution execution) {
        return execution.search(query);
    }

    @Override
    public void fill(Result result, String summaryClass, Execution execution) {
        execution.fill(result, summaryClass);

        Set<String> summaryFields = result.getQuery().getPresentation().getSummaryFields();

        if (summaryFields.isEmpty() || summaryClass == null ||
            result.getQuery().properties().getBoolean(FIELD_FILLER_DISABLE)) {
            return;
        }

        if (intersectionOfAttributes.containsAll(summaryFields)) {
            if (! SORTABLE_ATTRIBUTES_SUMMARY_CLASS.equals(summaryClass)) {
                execution.fill(result, SORTABLE_ATTRIBUTES_SUMMARY_CLASS);
            }
        } else {
            // Yes, summaryClass may be SORTABLE_ATTRIBUTES_SUMMARY_CLASS here
            if ( ! summaryDb.hasAll(summaryFields, summaryClass, result.getQuery().getModel().getRestrict())) {
                execution.fill(result, null);
            }
        }
    }

}