aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/fs4/GetDocSumsPacket.java
blob: 7dabb52a233a286059074d3523eadfb67cda0528 (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.fs4;

import com.yahoo.document.GlobalId;
import com.yahoo.log.LogLevel;
import com.yahoo.prelude.fastsearch.FastHit;
import com.yahoo.prelude.query.Item;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.result.Hit;
import com.yahoo.text.Utf8;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.logging.Logger;

/**
 * <p>A packet for requesting a list of document summaries.
 * This packet can be encoded only.</p>
 *
 * @author bratseth
 */
public class GetDocSumsPacket extends Packet {

    /** Session id key. Yep, putting this here is ugly as hell */
    public static final String sessionIdKey = "sessionId";

    private static final Logger log = Logger.getLogger(GetDocSumsPacket.class.getName());
    private final Result result;
    private final Query query;
    private final String summaryClass;
    private QueryPacketData queryPacketData = null;
    private int flags = 0;

    /**
     * True if we should send the query with this docsum, false otherwise.
     * Sending the query is necessary if we need to return summary features or generate a dynamic summary
     */
    private final boolean sendQuery;

    private GetDocSumsPacket(Result result, String summaryClass, boolean sendQuery) {
        this.result = result;
        this.query = result.getQuery();
        this.summaryClass = summaryClass;
        this.sendQuery = sendQuery;
    }

    /**
     * Creates a get docsums packet for a certain result
     */
    public static GetDocSumsPacket create(Result result, String summaryClass, boolean sendQuery) {
        return new GetDocSumsPacket(result, summaryClass, sendQuery);
    }

    /**
     * features bits, as given in searchlib/src/searchlib/common/packets.h
     * definition of enum getdocsums_features
     */
    public static final int GDF_MLD = 0x00000001;
    public static final int GDF_QUERYSTACK = 0x00000004;
    public static final int GDF_RANKP_QFLAGS = 0x00000010;
    public static final int GDF_LOCATION = 0x00000080;
    public static final int GDF_RESCLASSNAME = 0x00000800;
    public static final int GDF_PROPERTIES = 0x00001000;
    public static final int GDF_FLAGS = 0x00002000;

    /**
     * flag bits, as given in fastserver4/src/network/transport.h
     * definition of enum getdocsums_flags
     */
    public static final int GDFLAG_IGNORE_ROW  = 0x00000001;

    public void encodeBody(ByteBuffer buffer) {
        setFieldsFromHits();

        boolean useQueryCache = query.getRanking().getQueryCache();
        // If feature cache is used we need to include the sessionId as key.
        if (useQueryCache) { // TODO: Move this decision (and the key) to ranking
            query.getRanking().getProperties().put(sessionIdKey, query.getSessionId().toString());
        }

        // set the default features
        long features = GDF_MLD;
        if (sendQuery)
            features |= GDF_QUERYSTACK;
        features |= GDF_RANKP_QFLAGS;

        // do we want a specific result class?
        if (summaryClass != null)
            features |= GDF_RESCLASSNAME;
        if (query.getRanking().getLocation() != null)
            features |= GDF_LOCATION;
        if (query.hasEncodableProperties())
            features |= GDF_PROPERTIES;
        if (flags != 0) {
            features |= GDF_FLAGS;
        }
        buffer.putInt((int)features);
        buffer.putInt(0);     //Unused, was docstamp
        long timeLeft = query.getTimeLeft();
        buffer.putInt(Math.max(1, (int)timeLeft)); // Safety to avoid sending down 0 or negative number
        if (log.isLoggable(LogLevel.DEBUG)) {
            log.log(LogLevel.DEBUG, "Timeout from query(" + query.getTimeout() + "), sent to backend: " + timeLeft);
        }

        if (queryPacketData != null)
            encodeQueryFromPacketData(buffer, useQueryCache);
        else
            encodeQuery(buffer);

        if (flags != 0)
            buffer.putInt(flags);
        encodeDocIds(buffer);
    }

    private void setFieldsFromHits() {
        for (Iterator<Hit> i = result.hits().unorderedDeepIterator(); i.hasNext(); ) {
            Hit h = i.next();
            if (h instanceof FastHit) {
                FastHit hit = (FastHit)h;
                QueryPacketData tag = hit.getQueryPacketData();
                if (tag != null) {
                    this.queryPacketData = tag;
                    break;
                }
            }
        }
    }

    private void encodeQueryFromPacketData(ByteBuffer buffer, boolean reencodePropertyMaps) {
        queryPacketData.encodeRankProfile(buffer);
        queryPacketData.encodeQueryFlags(buffer);

        encodeSummaryClass(buffer);

        if (reencodePropertyMaps || ! sendQuery) // re-encode when we're not sending query, to avoid resending all encoded properties
            query.encodeAsProperties(buffer, sendQuery);
        else
            queryPacketData.encodePropertyMaps(buffer);

        if (sendQuery)
            queryPacketData.encodeQueryStack(buffer);
        queryPacketData.encodeLocation(buffer);
    }

    private void encodeSummaryClass(ByteBuffer buffer) {
        if (summaryClass != null) {
            byte[] tmp = Utf8.toBytes(summaryClass);
            buffer.putInt(tmp.length);
            buffer.put(tmp);
        }
    }

    private void encodeQuery(ByteBuffer buffer) {
        Item.putString(query.getRanking().getProfile(), buffer);
        buffer.putInt(QueryPacket.getQueryFlags(query));

        encodeSummaryClass(buffer);

        query.encodeAsProperties(buffer, sendQuery);

        if (sendQuery) {
            // The stack must be resubmitted to generate dynamic docsums
            int itemCountPosition = buffer.position();
            buffer.putInt(0);
            int dumpLengthPosition = buffer.position();
            buffer.putInt(0);
            int count = query.encode(buffer);
            buffer.putInt(itemCountPosition, count);
            buffer.putInt(dumpLengthPosition, buffer.position() - dumpLengthPosition - 4);
        }

        if (query.getRanking().getLocation() != null) {
            int locationLengthPosition = buffer.position();
            buffer.putInt(0);
            int locationLength = query.getRanking().getLocation().encode(buffer);
            buffer.putInt(locationLengthPosition, locationLength);
        }
    }

    private void encodeDocIds(ByteBuffer buffer) {
        byte[] emptyGid = new byte[GlobalId.LENGTH];
        for (Iterator<Hit> i = result.hits().unorderedDeepIterator(); i.hasNext(); ) {
            Hit hit = i.next();
            if (hit instanceof FastHit && !hit.isFilled(summaryClass)) {
                FastHit fastHit = (FastHit)hit;
                buffer.put(fastHit.getGlobalId() != null ? fastHit.getGlobalId().getRawId() : emptyGid);
                buffer.putInt(fastHit.getPartId());
                buffer.putInt(0);  //Unused, was docstamp
            }
        }
    }

    public int getCode() {
        return 219;
    }

    public String toString() {
        return "Get docsums x packet fetching " + getNumDocsums() + " docsums and packet length of " + getLength() + " bytes.";
    }

    public int getNumDocsums() {
        int num = 0;
        for (Iterator<Hit> i = result.hits().unorderedDeepIterator(); i.hasNext(); ) {
            Hit hit = i.next();
            if (hit instanceof FastHit && !hit.isFilled(summaryClass)) {
                num++;
            }
        }
        return num;
    }

    /**
     * Return the document summary class we want the fdispatch
     * to use when replying to us
     */
    @SuppressWarnings("UnusedDeclaration")
    public String getSummaryClass() {
        return summaryClass;
    }
}