1 // Copyright (C) 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "icing/query/advanced_query_parser/query-visitor.h"
16
17 #include <algorithm>
18 #include <cstdint>
19 #include <iterator>
20 #include <limits>
21 #include <memory>
22 #include <set>
23 #include <string>
24 #include <string_view>
25 #include <unordered_map>
26 #include <unordered_set>
27 #include <utility>
28 #include <vector>
29
30 #include "icing/text_classifier/lib3/utils/base/status.h"
31 #include "icing/text_classifier/lib3/utils/base/statusor.h"
32 #include "icing/absl_ports/annotate.h"
33 #include "icing/absl_ports/canonical_errors.h"
34 #include "icing/absl_ports/str_cat.h"
35 #include "icing/absl_ports/str_join.h"
36 #include "icing/index/embed/doc-hit-info-iterator-embedding.h"
37 #include "icing/index/embed/embedding-query-results.h"
38 #include "icing/index/iterator/doc-hit-info-iterator-all-document-id.h"
39 #include "icing/index/iterator/doc-hit-info-iterator-and.h"
40 #include "icing/index/iterator/doc-hit-info-iterator-filter.h"
41 #include "icing/index/iterator/doc-hit-info-iterator-match-score-expression.h"
42 #include "icing/index/iterator/doc-hit-info-iterator-none.h"
43 #include "icing/index/iterator/doc-hit-info-iterator-not.h"
44 #include "icing/index/iterator/doc-hit-info-iterator-or.h"
45 #include "icing/index/iterator/doc-hit-info-iterator-property-in-document.h"
46 #include "icing/index/iterator/doc-hit-info-iterator-property-in-schema.h"
47 #include "icing/index/iterator/doc-hit-info-iterator-section-restrict.h"
48 #include "icing/index/iterator/doc-hit-info-iterator.h"
49 #include "icing/index/property-existence-indexing-handler.h"
50 #include "icing/query/advanced_query_parser/abstract-syntax-tree.h"
51 #include "icing/query/advanced_query_parser/function.h"
52 #include "icing/query/advanced_query_parser/lexer.h"
53 #include "icing/query/advanced_query_parser/param.h"
54 #include "icing/query/advanced_query_parser/parser.h"
55 #include "icing/query/advanced_query_parser/pending-value.h"
56 #include "icing/query/advanced_query_parser/util/string-util.h"
57 #include "icing/query/query-features.h"
58 #include "icing/query/query-results.h"
59 #include "icing/schema/property-util.h"
60 #include "icing/schema/section.h"
61 #include "icing/scoring/advanced_scoring/score-expression-util.h"
62 #include "icing/scoring/advanced_scoring/score-expression.h"
63 #include "icing/tokenization/token.h"
64 #include "icing/tokenization/tokenizer.h"
65 #include "icing/transform/normalizer.h"
66 #include "icing/util/embedding-util.h"
67 #include "icing/util/status-macros.h"
68
69 namespace icing {
70 namespace lib {
71
72 namespace {
73
74 struct CreateList {
operator ()icing::lib::__anon413c4ecb0111::CreateList75 libtextclassifier3::StatusOr<PendingValue> operator()(
76 std::vector<PendingValue>&& args) const {
77 std::vector<std::string> values;
78 values.reserve(args.size());
79 for (PendingValue& arg : args) {
80 QueryTerm string_val = std::move(arg).string_val().ValueOrDie();
81 values.push_back(std::move(string_val.term));
82 }
83 return PendingValue(std::move(values));
84 }
85 };
86
IsNumericComparator(std::string_view operator_text)87 bool IsNumericComparator(std::string_view operator_text) {
88 if (operator_text.length() < 1 || operator_text.length() > 2) {
89 return false;
90 }
91 // TODO(tjbarron) decide how/if to support !=
92 return operator_text == "<" || operator_text == ">" ||
93 operator_text == "==" || operator_text == "<=" ||
94 operator_text == ">=";
95 }
96
IsSupportedNaryOperator(std::string_view operator_text)97 bool IsSupportedNaryOperator(std::string_view operator_text) {
98 return IsNumericComparator(operator_text) || operator_text == "AND" ||
99 operator_text == "OR" || operator_text == ":";
100 }
101
102 struct Int64Range {
103 int64_t low;
104 int64_t high;
105 };
106
GetInt64Range(std::string_view operator_text,int64_t int_value)107 libtextclassifier3::StatusOr<Int64Range> GetInt64Range(
108 std::string_view operator_text, int64_t int_value) {
109 Int64Range range = {std::numeric_limits<int64_t>::min(),
110 std::numeric_limits<int64_t>::max()};
111 if (operator_text == "<") {
112 if (int_value == std::numeric_limits<int64_t>::min()) {
113 return absl_ports::InvalidArgumentError(
114 "Cannot specify < INT64_MIN in query expression.");
115 }
116 range.high = int_value - 1;
117 } else if (operator_text == "<=") {
118 range.high = int_value;
119 } else if (operator_text == "==") {
120 range.high = int_value;
121 range.low = int_value;
122 } else if (operator_text == ">=") {
123 range.low = int_value;
124 } else if (operator_text == ">") {
125 if (int_value == std::numeric_limits<int64_t>::max()) {
126 return absl_ports::InvalidArgumentError(
127 "Cannot specify > INT64_MAX in query expression.");
128 }
129 range.low = int_value + 1;
130 }
131 return range;
132 }
133
134 } // namespace
135
AddValidRestricts(std::set<std::string> new_restricts)136 void QueryVisitor::PendingPropertyRestricts::AddValidRestricts(
137 std::set<std::string> new_restricts) {
138 if (!has_active_property_restricts()) {
139 pending_property_restricts_.push_back(std::move(new_restricts));
140 return;
141 }
142
143 // There is an active property restrict already in effect. To determine the
144 // updated active property restrict being applied at this level, we need to
145 // calculate the intersection of new_restricts and
146 // active_property_restricts.
147 const std::set<std::string>& active_restricts = active_property_restricts();
148 auto active_restricts_itr = active_restricts.begin();
149 for (auto new_restricts_itr = new_restricts.begin();
150 new_restricts_itr != new_restricts.end();) {
151 while (active_restricts_itr != active_restricts.end() &&
152 *active_restricts_itr < *new_restricts_itr) {
153 // new_restricts_itr is behind active_restricts_itr.
154 ++active_restricts_itr;
155 }
156 if (active_restricts_itr == active_restricts.end()) {
157 // There's nothing left in active restricts. Everything at
158 // new_restricts_itr and beyond should be removed
159 new_restricts_itr =
160 new_restricts.erase(new_restricts_itr, new_restricts.end());
161 } else if (*active_restricts_itr > *new_restricts_itr) {
162 // new_restricts_itr points to elements not present in
163 // active_restricts_itr
164 new_restricts_itr = new_restricts.erase(new_restricts_itr);
165 } else {
166 // the element that new_restricts_itr points to is present in
167 // active_restricts_itr.
168 ++new_restricts_itr;
169 }
170 }
171 pending_property_restricts_.push_back(std::move(new_restricts));
172 }
173
174 libtextclassifier3::StatusOr<std::unique_ptr<DocHitInfoIterator>>
CreateTermIterator(const QueryTerm & query_term)175 QueryVisitor::CreateTermIterator(const QueryTerm& query_term) {
176 if (query_term.is_prefix_val) {
177 // '*' prefix operator was added in list filters
178 features_.insert(kListFilterQueryLanguageFeature);
179 }
180 TermMatchType::Code match_type = GetTermMatchType(query_term.is_prefix_val);
181 int unnormalized_term_start =
182 query_term.raw_term.data() - search_spec_.query().c_str();
183 if (!processing_not_) {
184 // 1. Add term to property_query_terms_map
185 if (pending_property_restricts_.has_active_property_restricts()) {
186 for (const std::string& property_restrict :
187 pending_property_restricts_.active_property_restricts()) {
188 property_query_terms_map_[property_restrict].insert(query_term.term);
189 }
190 } else {
191 property_query_terms_map_[""].insert(query_term.term);
192 }
193
194 // 2. If needed add term iterator to query_term_iterators_ map.
195 if (needs_term_frequency_info_) {
196 ICING_ASSIGN_OR_RETURN(
197 std::unique_ptr<DocHitInfoIterator> term_iterator,
198 index_.GetIterator(query_term.term, unnormalized_term_start,
199 query_term.raw_term.length(), kSectionIdMaskAll,
200 search_spec_.term_match_type(),
201 needs_term_frequency_info_));
202 query_term_iterators_[query_term.term] =
203 std::make_unique<DocHitInfoIteratorFilter>(
204 std::move(term_iterator), &document_store_, &schema_store_,
205 filter_options_, current_time_ms_);
206 }
207 }
208
209 // 3. Add the term iterator.
210 return index_.GetIterator(query_term.term, unnormalized_term_start,
211 query_term.raw_term.length(), kSectionIdMaskAll,
212 match_type, needs_term_frequency_info_);
213 }
214
RegisterFunctions()215 void QueryVisitor::RegisterFunctions() {
216 // std::vector<std::string> createList(std::string...);
217 Function create_list_function_ =
218 Function::Create(DataType::kStringList, "createList",
219 {Param(DataType::kString, Cardinality::kRequired),
220 Param(DataType::kString, Cardinality::kVariable)},
221 CreateList())
222 .ValueOrDie();
223 registered_functions_.insert(
224 {create_list_function_.name(), std::move(create_list_function_)});
225
226 // DocHitInfoIterator search(std::string);
227 // DocHitInfoIterator search(std::string, std::vector<std::string>);
228 auto search_eval = [this](std::vector<PendingValue>&& args) {
229 return this->SearchFunction(std::move(args));
230 };
231 Function search_function =
232 Function::Create(DataType::kDocumentIterator, "search",
233 {Param(DataType::kString),
234 Param(DataType::kStringList, Cardinality::kOptional)},
235 std::move(search_eval))
236 .ValueOrDie();
237 registered_functions_.insert(
238 {search_function.name(), std::move(search_function)});
239
240 // DocHitInfoIterator propertyDefined(std::string);
241 auto property_defined = [this](std::vector<PendingValue>&& args) {
242 return this->PropertyDefinedFunction(std::move(args));
243 };
244 Function property_defined_function =
245 Function::Create(DataType::kDocumentIterator, "propertyDefined",
246 {Param(DataType::kString)}, std::move(property_defined))
247 .ValueOrDie();
248 registered_functions_.insert(
249 {property_defined_function.name(), std::move(property_defined_function)});
250
251 // DocHitInfoIterator hasProperty(std::string);
252 auto has_property = [this](std::vector<PendingValue>&& args) {
253 return this->HasPropertyFunction(std::move(args));
254 };
255 Function has_property_function =
256 Function::Create(DataType::kDocumentIterator, "hasProperty",
257 {Param(DataType::kString)}, std::move(has_property))
258 .ValueOrDie();
259 registered_functions_.insert(
260 {has_property_function.name(), std::move(has_property_function)});
261
262 // vector_index getEmbeddingParameter(long);
263 auto get_embedding_parameter = [](std::vector<PendingValue>&& args) {
264 return PendingValue::CreateVectorIndexPendingValue(
265 args.at(0).long_val().ValueOrDie());
266 };
267 Function get_embedding_parameter_function =
268 Function::Create(DataType::kVectorIndex, "getEmbeddingParameter",
269 {Param(DataType::kLong)},
270 std::move(get_embedding_parameter))
271 .ValueOrDie();
272 registered_functions_.insert({get_embedding_parameter_function.name(),
273 get_embedding_parameter_function});
274
275 // DocHitInfoIterator semanticSearch(vector_index, double, double, string);
276 auto semantic_search = [this](std::vector<PendingValue>&& args) {
277 return this->SemanticSearchFunction(std::move(args));
278 };
279 Function semantic_search_function =
280 Function::Create(DataType::kDocumentIterator, "semanticSearch",
281 {Param(DataType::kVectorIndex),
282 Param(DataType::kDouble, Cardinality::kOptional),
283 Param(DataType::kDouble, Cardinality::kOptional),
284 Param(DataType::kString, Cardinality::kOptional)},
285 std::move(semantic_search))
286 .ValueOrDie();
287 registered_functions_.insert(
288 {semantic_search_function.name(), std::move(semantic_search_function)});
289
290 // DocHitInfoIterator getSearchStringParameter(long);
291 auto get_search_string_parameter = [this](std::vector<PendingValue>&& args) {
292 return this->GetSearchStringParameterFunction(std::move(args));
293 };
294 Function get_search_string_parameter_function =
295 Function::Create(DataType::kDocumentIterator, "getSearchStringParameter",
296 {Param(DataType::kLong)},
297 std::move(get_search_string_parameter))
298 .ValueOrDie();
299 registered_functions_.insert(
300 {get_search_string_parameter_function.name(),
301 std::move(get_search_string_parameter_function)});
302
303 // DocHitInfoIterator matchScoreExpression(std::string, double, double)
304 auto match_score_expression = [this](std::vector<PendingValue>&& args) {
305 return this->MatchScoreExpressionFunction(std::move(args));
306 };
307 Function match_score_expression_function =
308 Function::Create(DataType::kDocumentIterator, "matchScoreExpression",
309 {Param(DataType::kString), Param(DataType::kDouble),
310 Param(DataType::kDouble, Cardinality::kOptional)},
311 std::move(match_score_expression))
312 .ValueOrDie();
313 registered_functions_.insert({match_score_expression_function.name(),
314 std::move(match_score_expression_function)});
315 }
316
SearchFunction(std::vector<PendingValue> && args)317 libtextclassifier3::StatusOr<PendingValue> QueryVisitor::SearchFunction(
318 std::vector<PendingValue>&& args) {
319 // The second arg (if present) is a list of sections to restrict to.
320 if (args.size() == 2) {
321 std::set<std::string> new_restricts;
322 std::vector<std::string> property_restricts =
323 std::move(args.at(1)).string_vals().ValueOrDie();
324 for (std::string& property_restrict : property_restricts) {
325 new_restricts.insert(std::move(property_restrict));
326 }
327 pending_property_restricts_.AddValidRestricts(std::move(new_restricts));
328 if (pending_property_restricts_.active_property_restricts().empty()) {
329 pending_property_restricts_.PopRestricts();
330 return PendingValue(std::make_unique<DocHitInfoIteratorNone>());
331 }
332 }
333
334 // The first arg is guaranteed to be a STRING at this point. It should be safe
335 // to call ValueOrDie.
336 const QueryTerm* query = args.at(0).string_val().ValueOrDie();
337 Lexer lexer(query->term, Lexer::Language::QUERY);
338 ICING_ASSIGN_OR_RETURN(std::vector<Lexer::LexerToken> lexer_tokens,
339 std::move(lexer).ExtractTokens());
340
341 Parser parser = Parser::Create(std::move(lexer_tokens));
342 ICING_ASSIGN_OR_RETURN(std::unique_ptr<Node> tree_root,
343 parser.ConsumeQuery());
344
345 std::unique_ptr<DocHitInfoIterator> iterator;
346 QueryResults query_result;
347 if (tree_root == nullptr) {
348 iterator = std::make_unique<DocHitInfoIteratorAllDocumentId>(
349 document_store_.last_added_document_id());
350 } else {
351 QueryVisitor query_visitor(
352 &index_, &numeric_index_, &embedding_index_, &document_store_,
353 &schema_store_, &normalizer_, &tokenizer_, join_children_fetcher_,
354 search_spec_, filter_options_, needs_term_frequency_info_,
355 &feature_flags_, pending_property_restricts_, processing_not_,
356 current_time_ms_);
357 tree_root->Accept(&query_visitor);
358 ICING_ASSIGN_OR_RETURN(query_result,
359 std::move(query_visitor).ConsumeResults());
360 iterator = std::move(query_result.root_iterator);
361 }
362
363 // Update members based on results of processing the query.
364 if (args.size() == 2 &&
365 pending_property_restricts_.has_active_property_restricts()) {
366 iterator = DocHitInfoIteratorSectionRestrict::ApplyRestrictions(
367 std::move(iterator), &document_store_, &schema_store_,
368 pending_property_restricts_.active_property_restricts(),
369 current_time_ms_);
370 pending_property_restricts_.PopRestricts();
371 }
372 if (!processing_not_) {
373 std::move(
374 query_result.query_term_iterators.begin(),
375 query_result.query_term_iterators.end(),
376 std::inserter(query_term_iterators_, query_term_iterators_.end()));
377
378 std::move(query_result.query_terms.begin(), query_result.query_terms.end(),
379 std::inserter(property_query_terms_map_,
380 property_query_terms_map_.end()));
381 }
382 std::move(query_result.features_in_use.begin(),
383 query_result.features_in_use.end(),
384 std::inserter(features_, features_.end()));
385 return PendingValue(std::move(iterator));
386 }
387
388 libtextclassifier3::StatusOr<PendingValue>
PropertyDefinedFunction(std::vector<PendingValue> && args)389 QueryVisitor::PropertyDefinedFunction(std::vector<PendingValue>&& args) {
390 // The first arg is guaranteed to be a STRING at this point. It should be safe
391 // to call ValueOrDie.
392 const QueryTerm* member = args.at(0).string_val().ValueOrDie();
393
394 std::unique_ptr<DocHitInfoIterator> all_docs_iterator =
395 std::make_unique<DocHitInfoIteratorAllDocumentId>(
396 document_store_.last_added_document_id());
397
398 std::set<std::string> target_sections = {std::move(member->term)};
399 std::unique_ptr<DocHitInfoIterator> property_in_schema_iterator =
400 std::make_unique<DocHitInfoIteratorPropertyInSchema>(
401 std::move(all_docs_iterator), &document_store_, &schema_store_,
402 std::move(target_sections), current_time_ms_);
403
404 features_.insert(kListFilterQueryLanguageFeature);
405
406 return PendingValue(std::move(property_in_schema_iterator));
407 }
408
HasPropertyFunction(std::vector<PendingValue> && args)409 libtextclassifier3::StatusOr<PendingValue> QueryVisitor::HasPropertyFunction(
410 std::vector<PendingValue>&& args) {
411 // The first arg is guaranteed to be a STRING at this point. It should be safe
412 // to call ValueOrDie.
413 const std::string& property_path = args.at(0).string_val().ValueOrDie()->term;
414
415 // Perform an exact search for the property existence metadata token.
416 ICING_ASSIGN_OR_RETURN(
417 std::unique_ptr<DocHitInfoIterator> meta_hit_iterator,
418 index_.GetIterator(
419 absl_ports::StrCat(kPropertyExistenceTokenPrefix, property_path),
420 /*term_start_index=*/0,
421 /*unnormalized_term_length=*/0, kSectionIdMaskAll,
422 TermMatchType::EXACT_ONLY,
423 /*need_hit_term_frequency=*/false));
424
425 std::unique_ptr<DocHitInfoIterator> property_in_document_iterator =
426 std::make_unique<DocHitInfoIteratorPropertyInDocument>(
427 std::move(meta_hit_iterator));
428
429 features_.insert(kHasPropertyFunctionFeature);
430
431 return PendingValue(std::move(property_in_document_iterator));
432 }
433
SemanticSearchFunction(std::vector<PendingValue> && args)434 libtextclassifier3::StatusOr<PendingValue> QueryVisitor::SemanticSearchFunction(
435 std::vector<PendingValue>&& args) {
436 int64_t vector_index = args.at(0).vector_index_val().ValueOrDie();
437 if (vector_index < 0 ||
438 vector_index >= search_spec_.embedding_query_vectors_size()) {
439 return absl_ports::OutOfRangeError("Got invalid vector search index!");
440 }
441
442 // Handle default values for the optional arguments.
443 double low = -std::numeric_limits<double>::infinity();
444 double high = std::numeric_limits<double>::infinity();
445 SearchSpecProto::EmbeddingQueryMetricType::Code metric_type =
446 search_spec_.embedding_query_metric_type();
447 if (args.size() >= 2) {
448 low = args.at(1).double_val().ValueOrDie();
449 }
450 if (args.size() >= 3) {
451 high = args.at(2).double_val().ValueOrDie();
452 }
453 if (low > high) {
454 return absl_ports::InvalidArgumentError(
455 "The lower bound cannot be greater than the upper bound.");
456 }
457 if (args.size() >= 4) {
458 const std::string& metric = args.at(3).string_val().ValueOrDie()->term;
459 ICING_ASSIGN_OR_RETURN(
460 metric_type,
461 embedding_util::GetEmbeddingQueryMetricTypeFromName(metric));
462 }
463
464 // Create and return iterator.
465 EmbeddingQueryResults::EmbeddingQueryScoreMap* score_map =
466 &embedding_query_results_.result_scores[vector_index][metric_type];
467 ICING_ASSIGN_OR_RETURN(
468 std::unique_ptr<DocHitInfoIterator> iterator,
469 DocHitInfoIteratorEmbedding::Create(
470 &search_spec_.embedding_query_vectors(vector_index), metric_type, low,
471 high, score_map, &embedding_index_, &document_store_, &schema_store_,
472 current_time_ms_));
473 return PendingValue(std::move(iterator));
474 }
475
476 libtextclassifier3::StatusOr<PendingValue>
GetSearchStringParameterFunction(std::vector<PendingValue> && args)477 QueryVisitor::GetSearchStringParameterFunction(
478 std::vector<PendingValue>&& args) {
479 int64_t string_index = args.at(0).long_val().ValueOrDie();
480 if (string_index < 0 ||
481 string_index >= search_spec_.query_parameter_strings_size()) {
482 return absl_ports::OutOfRangeError("Got invalid string search index!");
483 }
484 const std::string& string_value =
485 search_spec_.query_parameter_strings(string_index);
486 // the prefix operator cannot be used here.
487 QueryTerm text_value = {string_value, string_value, /*is_prefix_val=*/false};
488 ICING_ASSIGN_OR_RETURN(std::unique_ptr<DocHitInfoIterator> iterator,
489 ProduceTextTokenIterators(std::move(text_value)));
490 return PendingValue(std::move(iterator));
491 }
492
493 libtextclassifier3::StatusOr<PendingValue>
MatchScoreExpressionFunction(std::vector<PendingValue> && args)494 QueryVisitor::MatchScoreExpressionFunction(std::vector<PendingValue>&& args) {
495 const std::string& scoring_expression_str =
496 args.at(0).string_val().ValueOrDie()->term;
497 libtextclassifier3::StatusOr<std::unique_ptr<ScoreExpression>>
498 scoring_expression = score_expression_util::GetScoreExpression(
499 scoring_expression_str,
500 /*default_score=*/-std::numeric_limits<double>::infinity(),
501 SearchSpecProto::EmbeddingQueryMetricType::UNKNOWN, &document_store_,
502 &schema_store_, current_time_ms_, join_children_fetcher_,
503 /*embedding_query_results=*/nullptr, /*section_weights=*/nullptr,
504 /*bm25f_calculator=*/nullptr, /*schema_type_alias_map=*/nullptr,
505 &feature_flags_, &scoring_feature_types_enabled_);
506 if (!scoring_expression.ok()) {
507 return absl_ports::Annotate(
508 scoring_expression.status(),
509 absl_ports::StrCat(
510 "matchScoreExpression: Failed to handle the score expression ",
511 scoring_expression_str));
512 }
513 double low = args.at(1).double_val().ValueOrDie();
514 double high = std::numeric_limits<double>::infinity();
515 if (args.size() == 3) {
516 high = args.at(2).double_val().ValueOrDie();
517 }
518 if (low > high) {
519 return absl_ports::InvalidArgumentError(
520 "The lower bound cannot be greater than the upper bound.");
521 }
522
523 std::unique_ptr<DocHitInfoIterator> iterator =
524 std::make_unique<DocHitInfoIteratorMatchScoreExpression>(
525 document_store_.last_added_document_id(),
526 std::move(scoring_expression).ValueOrDie(), low, high);
527 features_.insert(kMatchScoreExpressionFunctionFeature);
528 return PendingValue(std::move(iterator));
529 }
530
PopPendingIntValue()531 libtextclassifier3::StatusOr<int64_t> QueryVisitor::PopPendingIntValue() {
532 if (pending_values_.empty()) {
533 return absl_ports::InvalidArgumentError("Unable to retrieve int value.");
534 }
535 ICING_ASSIGN_OR_RETURN(int64_t int_value, pending_values_.top().long_val());
536 pending_values_.pop();
537 return int_value;
538 }
539
PopPendingStringValue()540 libtextclassifier3::StatusOr<QueryTerm> QueryVisitor::PopPendingStringValue() {
541 if (pending_values_.empty()) {
542 return absl_ports::InvalidArgumentError("Unable to retrieve string value.");
543 }
544 ICING_ASSIGN_OR_RETURN(QueryTerm string_value,
545 std::move(pending_values_.top()).string_val());
546 pending_values_.pop();
547 return string_value;
548 }
549
PopPendingTextValue()550 libtextclassifier3::StatusOr<QueryTerm> QueryVisitor::PopPendingTextValue() {
551 if (pending_values_.empty()) {
552 return absl_ports::InvalidArgumentError("Unable to retrieve text value.");
553 }
554 ICING_ASSIGN_OR_RETURN(QueryTerm text_value,
555 std::move(pending_values_.top()).text_val());
556 pending_values_.pop();
557 return text_value;
558 }
559
560 libtextclassifier3::StatusOr<std::unique_ptr<DocHitInfoIterator>>
ProduceTextTokenIterators(QueryTerm text_value)561 QueryVisitor::ProduceTextTokenIterators(QueryTerm text_value) {
562 ICING_ASSIGN_OR_RETURN(std::unique_ptr<Tokenizer::Iterator> token_itr,
563 tokenizer_.Tokenize(text_value.term));
564 Normalizer::NormalizedTerm normalized_term;
565 std::vector<std::unique_ptr<DocHitInfoIterator>> iterators;
566 // raw_text is the portion of text_value.raw_term that hasn't yet been
567 // matched to any of the tokens that we've processed. escaped_token will
568 // hold the portion of raw_text that corresponds to the current token that
569 // is being processed.
570 std::string_view raw_text = text_value.raw_term;
571 std::string_view raw_token;
572 bool reached_final_token = !token_itr->Advance();
573 // If the term is different then the raw_term, then there must have been some
574 // escaped characters that we will need to handle.
575 while (!reached_final_token) {
576 std::vector<Token> tokens = token_itr->GetTokens();
577 if (tokens.size() > 1) {
578 // The tokenizer iterator iterates between token groups. In practice,
579 // the tokenizer used with QueryVisitor (PlainTokenizer) will always
580 // only produce a single token per token group.
581 return absl_ports::InvalidArgumentError(
582 "Encountered unexpected token group with >1 tokens.");
583 }
584
585 reached_final_token = !token_itr->Advance();
586 const Token& token = tokens.at(0);
587 if (reached_final_token && token.text.length() == raw_text.length()) {
588 // Unescaped tokens are strictly smaller than their escaped counterparts
589 // This means that if we're at the final token and token.length equals
590 // raw_text, then all of raw_text must correspond to this token.
591 raw_token = raw_text;
592 } else {
593 ICING_ASSIGN_OR_RETURN(
594 raw_token, string_util::FindEscapedToken(raw_text, token.text));
595 }
596 normalized_term = normalizer_.NormalizeTerm(token.text);
597 bool should_prefix_match = reached_final_token && text_value.is_prefix_val;
598 QueryTerm term_value{std::move(normalized_term.text), raw_token,
599 should_prefix_match};
600 ICING_ASSIGN_OR_RETURN(std::unique_ptr<DocHitInfoIterator> iterator,
601 CreateTermIterator(std::move(term_value)));
602 iterators.push_back(std::move(iterator));
603
604 // Remove escaped_token from raw_text now that we've processed
605 // raw_text.
606 const char* escaped_token_end = raw_token.data() + raw_token.length();
607 raw_text = raw_text.substr(escaped_token_end - raw_text.data());
608 }
609 // Finally, create an And Iterator. If there's only a single term here, then
610 // it will just return that term iterator. Otherwise, segmented text is
611 // treated as a group of terms AND'd together.
612 return CreateAndIterator(std::move(iterators));
613 }
614
615 libtextclassifier3::StatusOr<std::unique_ptr<DocHitInfoIterator>>
PopPendingIterator()616 QueryVisitor::PopPendingIterator() {
617 if (pending_values_.empty() || pending_values_.top().is_placeholder()) {
618 return absl_ports::InvalidArgumentError("Unable to retrieve iterator.");
619 }
620 if (pending_values_.top().data_type() == DataType::kDocumentIterator) {
621 std::unique_ptr<DocHitInfoIterator> iterator =
622 std::move(pending_values_.top()).iterator().ValueOrDie();
623 pending_values_.pop();
624 return iterator;
625 } else if (pending_values_.top().data_type() == DataType::kString) {
626 features_.insert(kVerbatimSearchFeature);
627 ICING_ASSIGN_OR_RETURN(QueryTerm string_value, PopPendingStringValue());
628 return CreateTermIterator(std::move(string_value));
629 } else {
630 ICING_ASSIGN_OR_RETURN(QueryTerm text_value, PopPendingTextValue());
631 return ProduceTextTokenIterators(std::move(text_value));
632 }
633 }
634
635 libtextclassifier3::StatusOr<std::vector<std::unique_ptr<DocHitInfoIterator>>>
PopAllPendingIterators()636 QueryVisitor::PopAllPendingIterators() {
637 std::vector<std::unique_ptr<DocHitInfoIterator>> iterators;
638 while (!pending_values_.empty() && !pending_values_.top().is_placeholder()) {
639 ICING_ASSIGN_OR_RETURN(std::unique_ptr<DocHitInfoIterator> itr,
640 PopPendingIterator());
641 iterators.push_back(std::move(itr));
642 }
643 if (pending_values_.empty()) {
644 return absl_ports::InvalidArgumentError(
645 "Unable to retrieve expected iterators.");
646 }
647 // Iterators will be in reverse order because we retrieved them from the
648 // stack. Reverse them to get back to the original ordering.
649 std::reverse(iterators.begin(), iterators.end());
650 return iterators;
651 }
652
ProcessNumericComparator(const NaryOperatorNode * node)653 libtextclassifier3::Status QueryVisitor::ProcessNumericComparator(
654 const NaryOperatorNode* node) {
655 if (node->children().size() != 2) {
656 return absl_ports::InvalidArgumentError("Expected 2 children.");
657 }
658
659 // 1. Put in a placeholder PendingValue
660 pending_values_.push(PendingValue());
661
662 // 2. The first child is the property to restrict by.
663 node->children().at(0)->Accept(this);
664 if (has_pending_error()) {
665 return std::move(pending_error_);
666 }
667 ICING_ASSIGN_OR_RETURN(QueryTerm text_value, PopPendingTextValue());
668
669 if (text_value.is_prefix_val) {
670 return absl_ports::InvalidArgumentError(
671 "Cannot use prefix operator '*' with a property name!");
672 }
673
674 // If there is an active property restrict and this property is not present in
675 // in the active restrict set, then it's not satisfiable.
676 if (pending_property_restricts_.has_active_property_restricts() &&
677 pending_property_restricts_.active_property_restricts().find(
678 text_value.term) ==
679 pending_property_restricts_.active_property_restricts().end()) {
680 // The property restrict can't be satisfiable. Pop the placeholder that was
681 // just added and push a FALSE iterator.
682 pending_property_restricts_.PopRestricts();
683 pending_values_.pop();
684 pending_values_.push(
685 PendingValue(std::make_unique<DocHitInfoIteratorNone>()));
686 return libtextclassifier3::Status::OK;
687 }
688
689 // 3. The second child should be parseable as an integer value.
690 expecting_numeric_arg_ = true;
691 node->children().at(1)->Accept(this);
692 expecting_numeric_arg_ = false;
693 ICING_ASSIGN_OR_RETURN(int64_t int_value, PopPendingIntValue());
694
695 // 4. Check for the placeholder.
696 if (!pending_values_.top().is_placeholder()) {
697 return absl_ports::InvalidArgumentError(
698 "Error processing arguments for node.");
699 }
700 pending_values_.pop();
701
702 // 5. Create the iterator and push it onto pending_values_.
703 ICING_ASSIGN_OR_RETURN(Int64Range range,
704 GetInt64Range(node->operator_text(), int_value));
705 ICING_ASSIGN_OR_RETURN(std::unique_ptr<DocHitInfoIterator> iterator,
706 numeric_index_.GetIterator(
707 text_value.term, range.low, range.high,
708 document_store_, schema_store_, current_time_ms_));
709
710 features_.insert(kNumericSearchFeature);
711 pending_values_.push(PendingValue(std::move(iterator)));
712 return libtextclassifier3::Status::OK;
713 }
714
ProcessAndOperator(const NaryOperatorNode * node)715 libtextclassifier3::StatusOr<PendingValue> QueryVisitor::ProcessAndOperator(
716 const NaryOperatorNode* node) {
717 ICING_ASSIGN_OR_RETURN(
718 std::vector<std::unique_ptr<DocHitInfoIterator>> iterators,
719 PopAllPendingIterators());
720 return PendingValue(CreateAndIterator(std::move(iterators)));
721 }
722
ProcessOrOperator(const NaryOperatorNode * node)723 libtextclassifier3::StatusOr<PendingValue> QueryVisitor::ProcessOrOperator(
724 const NaryOperatorNode* node) {
725 ICING_ASSIGN_OR_RETURN(
726 std::vector<std::unique_ptr<DocHitInfoIterator>> iterators,
727 PopAllPendingIterators());
728 return PendingValue(CreateOrIterator(std::move(iterators)));
729 }
730
ProcessNegationOperator(const UnaryOperatorNode * node)731 libtextclassifier3::Status QueryVisitor::ProcessNegationOperator(
732 const UnaryOperatorNode* node) {
733 // 1. Put in a placeholder PendingValue
734 pending_values_.push(PendingValue());
735
736 // 2. Visit child
737 node->child()->Accept(this);
738 if (has_pending_error()) {
739 return std::move(pending_error_);
740 }
741
742 if (pending_values_.size() < 2) {
743 return absl_ports::InvalidArgumentError(
744 "Visit unary operator child didn't correctly add pending values.");
745 }
746
747 // 3. We want to preserve the original text of the numeric value, append our
748 // minus to the text. It will be parsed as either an int or a double later.
749 ICING_ASSIGN_OR_RETURN(QueryTerm numeric_text_val, PopPendingTextValue());
750 numeric_text_val.term = absl_ports::StrCat("-", numeric_text_val.term);
751 PendingValue pending_value =
752 PendingValue::CreateTextPendingValue(std::move(numeric_text_val));
753
754 // We've parsed our numeric value successfully. Pop our placeholder, push it
755 // on to the stack and return successfully.
756 if (!pending_values_.top().is_placeholder()) {
757 return absl_ports::InvalidArgumentError(
758 "Error processing arguments for node.");
759 }
760 pending_values_.pop();
761 pending_values_.push(std::move(pending_value));
762 return libtextclassifier3::Status::OK;
763 }
764
ProcessNotOperator(const UnaryOperatorNode * node)765 libtextclassifier3::Status QueryVisitor::ProcessNotOperator(
766 const UnaryOperatorNode* node) {
767 // TODO(b/265312785) Consider implementing query optimization when we run into
768 // nested NOTs. This would allow us to simplify a query like "NOT (-foo)" to
769 // just "foo". This would also require more complicate rewrites as we would
770 // need to do things like rewrite "NOT (-a OR b)" as "a AND -b" and
771 // "NOT (price < 5)" as "price >= 5".
772 // 1. Put in a placeholder PendingValue
773 pending_values_.push(PendingValue());
774 // Toggle whatever the current value of 'processing_not_' is before visiting
775 // the children.
776 processing_not_ = !processing_not_;
777
778 // 2. Visit child
779 node->child()->Accept(this);
780 if (has_pending_error()) {
781 return std::move(pending_error_);
782 }
783
784 if (pending_values_.size() < 2) {
785 return absl_ports::InvalidArgumentError(
786 "Visit unary operator child didn't correctly add pending values.");
787 }
788
789 // 3. Retrieve the delegate iterator
790 ICING_ASSIGN_OR_RETURN(std::unique_ptr<DocHitInfoIterator> delegate,
791 PopPendingIterator());
792
793 // 4. Check for the placeholder.
794 if (!pending_values_.top().is_placeholder()) {
795 return absl_ports::InvalidArgumentError(
796 "Error processing arguments for node.");
797 }
798 pending_values_.pop();
799
800 pending_values_.push(PendingValue(std::make_unique<DocHitInfoIteratorNot>(
801 std::move(delegate), document_store_.last_added_document_id())));
802
803 // Untoggle whatever the current value of 'processing_not_' is now that we've
804 // finished processing this NOT.
805 processing_not_ = !processing_not_;
806 return libtextclassifier3::Status::OK;
807 }
808
ProcessHasOperator(const NaryOperatorNode * node)809 libtextclassifier3::Status QueryVisitor::ProcessHasOperator(
810 const NaryOperatorNode* node) {
811 if (node->children().size() != 2) {
812 return absl_ports::InvalidArgumentError("Expected 2 children.");
813 }
814
815 // 1. Put in a placeholder PendingValue
816 pending_values_.push(PendingValue());
817
818 // 2. Visit the first child - the property.
819 node->children().at(0)->Accept(this);
820 if (has_pending_error()) {
821 return pending_error_;
822 }
823 ICING_ASSIGN_OR_RETURN(QueryTerm text_value, PopPendingTextValue());
824 if (text_value.is_prefix_val) {
825 return absl_ports::InvalidArgumentError(
826 "Cannot use prefix operator '*' with a property name!");
827 }
828 pending_property_restricts_.AddValidRestricts({text_value.term});
829
830 // Just added a restrict - if there are no active property restricts then that
831 // be because this restrict is unsatisfiable.
832 if (pending_property_restricts_.active_property_restricts().empty()) {
833 // The property restrict can't be satisfiable. Pop the placeholder that was
834 // just added and push a FALSE iterator.
835 pending_property_restricts_.PopRestricts();
836 pending_values_.pop();
837 pending_values_.push(
838 PendingValue(std::make_unique<DocHitInfoIteratorNone>()));
839 return libtextclassifier3::Status::OK;
840 }
841
842 // 3. Visit the second child - the argument.
843 node->children().at(1)->Accept(this);
844 if (has_pending_error()) {
845 return pending_error_;
846 }
847 ICING_ASSIGN_OR_RETURN(std::unique_ptr<DocHitInfoIterator> delegate,
848 PopPendingIterator());
849
850 // 4. Check for the placeholder.
851 if (!pending_values_.top().is_placeholder()) {
852 return absl_ports::InvalidArgumentError(
853 "Error processing arguments for node.");
854 }
855 pending_values_.pop();
856 pending_property_restricts_.PopRestricts();
857
858 std::set<std::string> property_restricts = {std::move(text_value.term)};
859 pending_values_.push(
860 PendingValue(DocHitInfoIteratorSectionRestrict::ApplyRestrictions(
861 std::move(delegate), &document_store_, &schema_store_,
862 std::move(property_restricts), current_time_ms_)));
863 return libtextclassifier3::Status::OK;
864 }
865
VisitString(const StringNode * node)866 void QueryVisitor::VisitString(const StringNode* node) {
867 // A STRING node can only be a term. Create the iterator now.
868 auto unescaped_string_or = string_util::UnescapeStringValue(node->value());
869 if (!unescaped_string_or.ok()) {
870 pending_error_ = std::move(unescaped_string_or).status();
871 return;
872 }
873 std::string unescaped_string = std::move(unescaped_string_or).ValueOrDie();
874 QueryTerm val{std::move(unescaped_string), node->raw_value(),
875 node->is_prefix()};
876 pending_values_.push(PendingValue::CreateStringPendingValue(std::move(val)));
877 }
878
VisitText(const TextNode * node)879 void QueryVisitor::VisitText(const TextNode* node) {
880 // TEXT nodes could either be a term (and will become DocHitInfoIteratorTerm)
881 // or a property name. As such, we just push the TEXT value into pending
882 // values and determine which it is at a later point.
883 QueryTerm val{std::move(node->value()), node->raw_value(), node->is_prefix()};
884 pending_values_.push(PendingValue::CreateTextPendingValue(std::move(val)));
885 }
886
VisitMember(const MemberNode * node)887 void QueryVisitor::VisitMember(const MemberNode* node) {
888 if (node->children().empty()) {
889 pending_error_ =
890 absl_ports::InvalidArgumentError("Encountered malformed member node.");
891 return;
892 }
893
894 // 1. Put in a placeholder PendingValue
895 pending_values_.push(PendingValue());
896
897 // 2. Visit the children.
898 for (const std::unique_ptr<TextNode>& child : node->children()) {
899 child->Accept(this);
900 if (has_pending_error()) {
901 return;
902 }
903 }
904
905 // 3. Now process the results of the children and produce a single pending
906 // value representing this member.
907 PendingValue pending_value;
908 if (node->children().size() == 1) {
909 // 3a. This member only has a single child, then the pending value produced
910 // by that child is the final value produced by this member.
911 pending_value = std::move(pending_values_.top());
912 pending_values_.pop();
913 } else {
914 // 3b. Retrieve the values of all children and concatenate them into a
915 // single value.
916 libtextclassifier3::StatusOr<QueryTerm> member_or;
917 std::vector<std::string> members;
918 QueryTerm text_val;
919 const char* start = nullptr;
920 const char* end = nullptr;
921 while (!pending_values_.empty() &&
922 !pending_values_.top().is_placeholder()) {
923 member_or = PopPendingTextValue();
924 if (!member_or.ok()) {
925 pending_error_ = std::move(member_or).status();
926 return;
927 }
928 text_val = std::move(member_or).ValueOrDie();
929 if (text_val.is_prefix_val) {
930 pending_error_ = absl_ports::InvalidArgumentError(
931 "Cannot use prefix operator '*' within a property name!");
932 return;
933 }
934 if (start == nullptr) {
935 start = text_val.raw_term.data();
936 end = text_val.raw_term.data() + text_val.raw_term.length();
937 } else {
938 start = std::min(start, text_val.raw_term.data());
939 end = std::max(end,
940 text_val.raw_term.data() + text_val.raw_term.length());
941 }
942 members.push_back(std::move(text_val.term));
943 }
944 QueryTerm member;
945 member.term = absl_ports::StrJoin(members.rbegin(), members.rend(),
946 property_util::kPropertyPathSeparator);
947 member.raw_term = std::string_view(start, end - start);
948 member.is_prefix_val = false;
949 pending_value = PendingValue::CreateTextPendingValue(std::move(member));
950 }
951
952 // 4. If pending_values_ is empty somehow, then our placeholder disappeared
953 // somehow.
954 if (pending_values_.empty()) {
955 pending_error_ = absl_ports::InvalidArgumentError(
956 "Error processing arguments for member node.");
957 return;
958 }
959 pending_values_.pop();
960
961 pending_values_.push(std::move(pending_value));
962 }
963
VisitFunction(const FunctionNode * node)964 void QueryVisitor::VisitFunction(const FunctionNode* node) {
965 // 1. Get the associated function.
966 auto itr = registered_functions_.find(node->function_name());
967 if (itr == registered_functions_.end()) {
968 pending_error_ = absl_ports::InvalidArgumentError(absl_ports::StrCat(
969 "Function ", node->function_name(), " is not supported."));
970 return;
971 }
972 const Function& function = itr->second;
973
974 // 2. Put in a placeholder PendingValue
975 pending_values_.push(PendingValue());
976
977 // 3. Visit the children.
978 expecting_numeric_arg_ = true;
979 for (int i = 0; i < node->args().size(); ++i) {
980 const std::unique_ptr<Node>& arg = node->args()[i];
981 libtextclassifier3::StatusOr<DataType> arg_type_or =
982 function.get_param_type(i);
983 bool current_level_expecting_numeric_arg = expecting_numeric_arg_;
984 // If arg_type_or has an error, we should ignore it for now, since
985 // function.Eval should do the type check and return better error messages.
986 if (arg_type_or.ok() && (arg_type_or.ValueOrDie() == DataType::kLong ||
987 arg_type_or.ValueOrDie() == DataType::kDouble)) {
988 expecting_numeric_arg_ = true;
989 }
990 arg->Accept(this);
991 expecting_numeric_arg_ = current_level_expecting_numeric_arg;
992 if (has_pending_error()) {
993 return;
994 }
995 }
996
997 // 4. Collect the arguments and evaluate the function.
998 std::vector<PendingValue> args;
999 while (!pending_values_.empty() && !pending_values_.top().is_placeholder()) {
1000 args.push_back(std::move(pending_values_.top()));
1001 pending_values_.pop();
1002 }
1003 std::reverse(args.begin(), args.end());
1004 auto eval_result = function.Eval(std::move(args));
1005 if (!eval_result.ok()) {
1006 pending_error_ = std::move(eval_result).status();
1007 return;
1008 }
1009
1010 // 5. Pop placeholder in pending_values and add the result of our function.
1011 pending_values_.pop();
1012 pending_values_.push(std::move(eval_result).ValueOrDie());
1013
1014 // Support for custom functions was added in list filters.
1015 features_.insert(kListFilterQueryLanguageFeature);
1016 }
1017
1018 // TODO(b/265312785) Clarify handling of the interaction between HAS and NOT.
1019 // Currently, `prop1:(NOT foo bar)` will not match any documents. Likewise,
1020 // `search("NOT foo bar", createList("prop1"))` will not match any documents.
1021 //
1022 // We should either confirm that this is the desired behavior or consider
1023 // rewriting these queries so that they're interpreted as
1024 // `NOT prop1:foo AND prop1:bar` and
1025 // `NOT search("foo", createList("prop1"))
1026 // AND search("bar", createList("prop1"))`
VisitUnaryOperator(const UnaryOperatorNode * node)1027 void QueryVisitor::VisitUnaryOperator(const UnaryOperatorNode* node) {
1028 bool is_minus = node->operator_text() == "MINUS";
1029 if (node->operator_text() != "NOT" && !is_minus) {
1030 pending_error_ = absl_ports::UnimplementedError(
1031 absl_ports::StrCat("Visiting for unary operator ",
1032 node->operator_text(), " not implemented yet."));
1033 return;
1034 }
1035
1036 libtextclassifier3::Status status;
1037 if (expecting_numeric_arg_ && is_minus) {
1038 // If the operator is a MINUS ('-') and we're at the child of a numeric
1039 // comparator, then this must be a negation ('-3')
1040 status = ProcessNegationOperator(node);
1041 } else {
1042 status = ProcessNotOperator(node);
1043 }
1044
1045 if (!status.ok()) {
1046 pending_error_ = std::move(status);
1047 }
1048
1049 if (!is_minus ||
1050 pending_property_restricts_.has_active_property_restricts() ||
1051 processing_not_) {
1052 // 'NOT' operator was added in list filters.
1053 // Likewise, mixing property restricts and NOTs were made valid in list
1054 // filters.
1055 features_.insert(kListFilterQueryLanguageFeature);
1056 }
1057 }
1058
VisitNaryOperator(const NaryOperatorNode * node)1059 void QueryVisitor::VisitNaryOperator(const NaryOperatorNode* node) {
1060 if (!IsSupportedNaryOperator(node->operator_text())) {
1061 pending_error_ = absl_ports::UnimplementedError(
1062 "No support for any non-numeric operators.");
1063 return;
1064 }
1065
1066 if (pending_property_restricts_.has_active_property_restricts() ||
1067 processing_not_) {
1068 // Likewise, mixing property restricts and NOT with compound statements was
1069 // added in list filters.
1070 features_.insert(kListFilterQueryLanguageFeature);
1071 }
1072
1073 if (node->operator_text() == ":") {
1074 libtextclassifier3::Status status = ProcessHasOperator(node);
1075 if (!status.ok()) {
1076 pending_error_ = std::move(status);
1077 }
1078 return;
1079 } else if (IsNumericComparator(node->operator_text())) {
1080 libtextclassifier3::Status status = ProcessNumericComparator(node);
1081 if (!status.ok()) {
1082 pending_error_ = std::move(status);
1083 }
1084 return;
1085 }
1086
1087 // 1. Put in a placeholder PendingValue
1088 pending_values_.push(PendingValue());
1089
1090 // 2. Visit the children.
1091 for (int i = 0; i < node->children().size(); ++i) {
1092 node->children().at(i)->Accept(this);
1093 if (has_pending_error()) {
1094 return;
1095 }
1096 }
1097
1098 // 3. Retrieve the pending value for this node.
1099 libtextclassifier3::StatusOr<PendingValue> pending_value_or;
1100 if (node->operator_text() == "AND") {
1101 pending_value_or = ProcessAndOperator(node);
1102 } else if (node->operator_text() == "OR") {
1103 pending_value_or = ProcessOrOperator(node);
1104 }
1105 if (!pending_value_or.ok()) {
1106 pending_error_ = std::move(pending_value_or).status();
1107 return;
1108 }
1109 PendingValue pending_value = std::move(pending_value_or).ValueOrDie();
1110
1111 // 4. Check for the placeholder.
1112 if (!pending_values_.top().is_placeholder()) {
1113 pending_error_ = absl_ports::InvalidArgumentError(
1114 "Error processing arguments for node.");
1115 return;
1116 }
1117 pending_values_.pop();
1118
1119 pending_values_.push(std::move(pending_value));
1120 }
1121
ConsumeResults()1122 libtextclassifier3::StatusOr<QueryResults> QueryVisitor::ConsumeResults() && {
1123 if (has_pending_error()) {
1124 return std::move(pending_error_);
1125 }
1126 if (pending_values_.size() != 1) {
1127 return absl_ports::InvalidArgumentError(
1128 "Visitor does not contain a single root iterator.");
1129 }
1130 auto iterator_or = PopPendingIterator();
1131 if (!iterator_or.ok()) {
1132 return std::move(iterator_or).status();
1133 }
1134
1135 QueryResults results;
1136 results.root_iterator = std::move(iterator_or).ValueOrDie();
1137 results.query_term_iterators = std::move(query_term_iterators_);
1138 results.query_terms = std::move(property_query_terms_map_);
1139 results.embedding_query_results = std::move(embedding_query_results_);
1140 results.features_in_use = std::move(features_);
1141 return results;
1142 }
1143
1144 } // namespace lib
1145 } // namespace icing
1146