• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2023 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/index/property-existence-indexing-handler.h"
16 
17 #include <cstdint>
18 #include <limits>
19 #include <memory>
20 #include <string>
21 #include <string_view>
22 #include <utility>
23 #include <vector>
24 
25 #include "icing/text_classifier/lib3/utils/base/status.h"
26 #include "icing/text_classifier/lib3/utils/base/statusor.h"
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "icing/absl_ports/str_cat.h"
30 #include "icing/document-builder.h"
31 #include "icing/feature-flags.h"
32 #include "icing/file/filesystem.h"
33 #include "icing/file/portable-file-backed-proto-log.h"
34 #include "icing/index/hit/doc-hit-info.h"
35 #include "icing/index/index.h"
36 #include "icing/index/iterator/doc-hit-info-iterator.h"
37 #include "icing/legacy/index/icing-filesystem.h"
38 #include "icing/portable/platform.h"
39 #include "icing/proto/document.pb.h"
40 #include "icing/proto/document_wrapper.pb.h"
41 #include "icing/proto/schema.pb.h"
42 #include "icing/proto/term.pb.h"
43 #include "icing/schema-builder.h"
44 #include "icing/schema/schema-store.h"
45 #include "icing/schema/section.h"
46 #include "icing/store/document-id.h"
47 #include "icing/store/document-store.h"
48 #include "icing/testing/common-matchers.h"
49 #include "icing/testing/fake-clock.h"
50 #include "icing/testing/test-data.h"
51 #include "icing/testing/test-feature-flags.h"
52 #include "icing/testing/tmp-directory.h"
53 #include "icing/tokenization/language-segmenter-factory.h"
54 #include "icing/tokenization/language-segmenter.h"
55 #include "icing/transform/normalizer-factory.h"
56 #include "icing/transform/normalizer-options.h"
57 #include "icing/transform/normalizer.h"
58 #include "icing/util/icu-data-file-helper.h"
59 #include "icing/util/tokenized-document.h"
60 #include "unicode/uloc.h"
61 
62 namespace icing {
63 namespace lib {
64 
65 namespace {
66 
67 using ::testing::ElementsAre;
68 using ::testing::IsTrue;
69 using ::testing::Test;
70 
71 static constexpr std::string_view kTreeType = "TreeNode";
72 static constexpr std::string_view kPropertyName = "name";
73 static constexpr std::string_view kPropertyValue = "value";
74 static constexpr std::string_view kPropertySubtrees = "subtrees";
75 
76 static constexpr std::string_view kValueType = "Value";
77 static constexpr std::string_view kPropertyBody = "body";
78 static constexpr std::string_view kPropertyTimestamp = "timestamp";
79 static constexpr std::string_view kPropertyScore = "score";
80 
81 class PropertyExistenceIndexingHandlerTest : public Test {
82  protected:
SetUp()83   void SetUp() override {
84     feature_flags_ = std::make_unique<FeatureFlags>(GetTestFeatureFlags());
85     if (!IsCfStringTokenization() && !IsReverseJniTokenization()) {
86       ICING_ASSERT_OK(
87           // File generated via icu_data_file rule in //icing/BUILD.
88           icu_data_file_helper::SetUpIcuDataFile(
89               GetTestFilePath("icing/icu.dat")));
90     }
91 
92     base_dir_ = GetTestTempDir() + "/icing_test";
93     ASSERT_THAT(filesystem_.CreateDirectoryRecursively(base_dir_.c_str()),
94                 IsTrue());
95 
96     index_dir_ = base_dir_ + "/index";
97     schema_store_dir_ = base_dir_ + "/schema_store";
98     document_store_dir_ = base_dir_ + "/document_store";
99 
100     language_segmenter_factory::SegmenterOptions segmenter_options(ULOC_US);
101     ICING_ASSERT_OK_AND_ASSIGN(
102         lang_segmenter_,
103         language_segmenter_factory::Create(std::move(segmenter_options)));
104 
105     NormalizerOptions normalizer_options(
106         /*max_term_byte_size=*/std::numeric_limits<int32_t>::max());
107     ICING_ASSERT_OK_AND_ASSIGN(normalizer_,
108                                normalizer_factory::Create(normalizer_options));
109 
110     ASSERT_THAT(
111         filesystem_.CreateDirectoryRecursively(schema_store_dir_.c_str()),
112         IsTrue());
113 
114     ICING_ASSERT_OK_AND_ASSIGN(
115         schema_store_, SchemaStore::Create(&filesystem_, schema_store_dir_,
116                                            &fake_clock_, feature_flags_.get()));
117     SchemaProto schema =
118         SchemaBuilder()
119             .AddType(
120                 SchemaTypeConfigBuilder()
121                     .SetType(kTreeType)
122                     .AddProperty(PropertyConfigBuilder()
123                                      .SetName(kPropertyName)
124                                      .SetDataTypeString(TERM_MATCH_EXACT,
125                                                         TOKENIZER_PLAIN)
126                                      .SetCardinality(CARDINALITY_OPTIONAL))
127                     .AddProperty(
128                         PropertyConfigBuilder()
129                             .SetName(kPropertyValue)
130                             .SetDataTypeDocument(
131                                 kValueType, /*index_nested_properties=*/true)
132                             .SetCardinality(CARDINALITY_OPTIONAL))
133                     .AddProperty(
134                         PropertyConfigBuilder()
135                             .SetName(kPropertySubtrees)
136                             .SetDataTypeDocument(
137                                 kTreeType, /*index_nested_properties=*/false)
138                             .SetCardinality(CARDINALITY_REPEATED)))
139             .AddType(
140                 SchemaTypeConfigBuilder()
141                     .SetType(kValueType)
142                     .AddProperty(PropertyConfigBuilder()
143                                      .SetName(kPropertyBody)
144                                      .SetDataTypeString(TERM_MATCH_EXACT,
145                                                         TOKENIZER_PLAIN)
146                                      .SetCardinality(CARDINALITY_REPEATED))
147                     .AddProperty(PropertyConfigBuilder()
148                                      .SetName(kPropertyTimestamp)
149                                      .SetDataType(TYPE_INT64)
150                                      .SetCardinality(CARDINALITY_OPTIONAL))
151                     .AddProperty(PropertyConfigBuilder()
152                                      .SetName(kPropertyScore)
153                                      .SetDataType(TYPE_DOUBLE)
154                                      .SetCardinality(CARDINALITY_OPTIONAL)))
155             .Build();
156     ICING_ASSERT_OK(schema_store_->SetSchema(
157         schema, /*ignore_errors_and_delete_documents=*/false));
158 
159     ASSERT_TRUE(
160         filesystem_.CreateDirectoryRecursively(document_store_dir_.c_str()));
161     ICING_ASSERT_OK_AND_ASSIGN(
162         DocumentStore::CreateResult doc_store_create_result,
163         DocumentStore::Create(&filesystem_, document_store_dir_, &fake_clock_,
164                               schema_store_.get(), feature_flags_.get(),
165                               /*force_recovery_and_revalidate_documents=*/false,
166                               /*pre_mapping_fbv=*/false,
167                               /*use_persistent_hash_map=*/true,
168                               PortableFileBackedProtoLog<
169                                   DocumentWrapper>::kDefaultCompressionLevel,
170                               /*initialize_stats=*/nullptr));
171     document_store_ = std::move(doc_store_create_result.document_store);
172   }
173 
TearDown()174   void TearDown() override {
175     document_store_.reset();
176     schema_store_.reset();
177     normalizer_.reset();
178     lang_segmenter_.reset();
179 
180     filesystem_.DeleteDirectoryRecursively(base_dir_.c_str());
181   }
182 
183   std::unique_ptr<FeatureFlags> feature_flags_;
184   Filesystem filesystem_;
185   IcingFilesystem icing_filesystem_;
186   FakeClock fake_clock_;
187   std::string base_dir_;
188   std::string index_dir_;
189   std::string schema_store_dir_;
190   std::string document_store_dir_;
191 
192   std::unique_ptr<LanguageSegmenter> lang_segmenter_;
193   std::unique_ptr<Normalizer> normalizer_;
194   std::unique_ptr<SchemaStore> schema_store_;
195   std::unique_ptr<DocumentStore> document_store_;
196 };
197 
198 libtextclassifier3::StatusOr<std::unique_ptr<DocHitInfoIterator>>
QueryExistence(Index * index,std::string_view property_path)199 QueryExistence(Index* index, std::string_view property_path) {
200   return index->GetIterator(
201       absl_ports::StrCat(kPropertyExistenceTokenPrefix, property_path),
202       /*term_start_index=*/0,
203       /*unnormalized_term_length=*/0, kSectionIdMaskAll,
204       TermMatchType::EXACT_ONLY,
205       /*need_hit_term_frequency=*/false);
206 }
207 
GetHits(std::unique_ptr<DocHitInfoIterator> iterator)208 std::vector<DocHitInfo> GetHits(std::unique_ptr<DocHitInfoIterator> iterator) {
209   std::vector<DocHitInfo> infos;
210   while (iterator->Advance().ok()) {
211     infos.push_back(iterator->doc_hit_info());
212   }
213   return infos;
214 }
215 
TEST_F(PropertyExistenceIndexingHandlerTest,HandlePropertyExistence)216 TEST_F(PropertyExistenceIndexingHandlerTest, HandlePropertyExistence) {
217   Index::Options options(index_dir_, /*index_merge_size=*/1024 * 1024,
218                          /*lite_index_sort_at_indexing=*/true,
219                          /*lite_index_sort_size=*/1024 * 8);
220   ICING_ASSERT_OK_AND_ASSIGN(
221       std::unique_ptr<Index> index,
222       Index::Create(options, &filesystem_, &icing_filesystem_));
223 
224   // Create a document with every property.
225   DocumentProto document0 =
226       DocumentBuilder()
227           .SetKey("icing", "uri0")
228           .SetSchema(std::string(kValueType))
229           .AddStringProperty(std::string(kPropertyBody), "foo")
230           .AddInt64Property(std::string(kPropertyTimestamp), 123)
231           .AddDoubleProperty(std::string(kPropertyScore), 456.789)
232           .Build();
233   // Create a document with missing body.
234   DocumentProto document1 =
235       DocumentBuilder()
236           .SetKey("icing", "uri1")
237           .SetSchema(std::string(kValueType))
238           .AddInt64Property(std::string(kPropertyTimestamp), 123)
239           .AddDoubleProperty(std::string(kPropertyScore), 456.789)
240           .Build();
241   // Create a document with missing timestamp.
242   DocumentProto document2 =
243       DocumentBuilder()
244           .SetKey("icing", "uri2")
245           .SetSchema(std::string(kValueType))
246           .AddStringProperty(std::string(kPropertyBody), "foo")
247           .AddDoubleProperty(std::string(kPropertyScore), 456.789)
248           .Build();
249 
250   ICING_ASSERT_OK_AND_ASSIGN(
251       TokenizedDocument tokenized_document0,
252       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
253                                 std::move(document0)));
254   ICING_ASSERT_OK_AND_ASSIGN(
255       TokenizedDocument tokenized_document1,
256       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
257                                 std::move(document1)));
258   ICING_ASSERT_OK_AND_ASSIGN(
259       TokenizedDocument tokenized_document2,
260       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
261                                 std::move(document2)));
262   ICING_ASSERT_OK_AND_ASSIGN(
263       DocumentStore::PutResult put_result0,
264       document_store_->Put(tokenized_document0.document()));
265   DocumentId document_id0 = put_result0.new_document_id;
266   ICING_ASSERT_OK_AND_ASSIGN(
267       DocumentStore::PutResult put_result1,
268       document_store_->Put(tokenized_document1.document()));
269   DocumentId document_id1 = put_result1.new_document_id;
270   ICING_ASSERT_OK_AND_ASSIGN(
271       DocumentStore::PutResult put_result2,
272       document_store_->Put(tokenized_document2.document()));
273   DocumentId document_id2 = put_result2.new_document_id;
274 
275   ICING_ASSERT_OK_AND_ASSIGN(
276       std::unique_ptr<PropertyExistenceIndexingHandler> handler,
277       PropertyExistenceIndexingHandler::Create(&fake_clock_, index.get()));
278 
279   // Handle all docs
280   EXPECT_THAT(handler->Handle(tokenized_document0, document_id0,
281                               put_result0.old_document_id,
282                               /*put_document_stats=*/nullptr),
283               IsOk());
284   EXPECT_THAT(handler->Handle(tokenized_document1, document_id1,
285                               put_result1.old_document_id,
286                               /*put_document_stats=*/nullptr),
287               IsOk());
288   EXPECT_THAT(handler->Handle(tokenized_document2, document_id2,
289                               put_result0.old_document_id,
290                               /*put_document_stats=*/nullptr),
291               IsOk());
292 
293   // Get all documents that have "body".
294   ICING_ASSERT_OK_AND_ASSIGN(std::unique_ptr<DocHitInfoIterator> itr,
295                              QueryExistence(index.get(), kPropertyBody));
296   EXPECT_THAT(
297       GetHits(std::move(itr)),
298       ElementsAre(EqualsDocHitInfo(document_id2, std::vector<SectionId>{0}),
299                   EqualsDocHitInfo(document_id0, std::vector<SectionId>{0})));
300 
301   // Get all documents that have "timestamp".
302   ICING_ASSERT_OK_AND_ASSIGN(itr,
303                              QueryExistence(index.get(), kPropertyTimestamp));
304   EXPECT_THAT(
305       GetHits(std::move(itr)),
306       ElementsAre(EqualsDocHitInfo(document_id1, std::vector<SectionId>{0}),
307                   EqualsDocHitInfo(document_id0, std::vector<SectionId>{0})));
308 
309   // Get all documents that have "score".
310   ICING_ASSERT_OK_AND_ASSIGN(itr, QueryExistence(index.get(), kPropertyScore));
311   EXPECT_THAT(
312       GetHits(std::move(itr)),
313       ElementsAre(EqualsDocHitInfo(document_id2, std::vector<SectionId>{0}),
314                   EqualsDocHitInfo(document_id1, std::vector<SectionId>{0}),
315                   EqualsDocHitInfo(document_id0, std::vector<SectionId>{0})));
316 }
317 
TEST_F(PropertyExistenceIndexingHandlerTest,HandleNestedPropertyExistence)318 TEST_F(PropertyExistenceIndexingHandlerTest, HandleNestedPropertyExistence) {
319   Index::Options options(index_dir_, /*index_merge_size=*/1024 * 1024,
320                          /*lite_index_sort_at_indexing=*/true,
321                          /*lite_index_sort_size=*/1024 * 8);
322   ICING_ASSERT_OK_AND_ASSIGN(
323       std::unique_ptr<Index> index,
324       Index::Create(options, &filesystem_, &icing_filesystem_));
325 
326   // Create a complex nested root_document with the following property paths.
327   // - name
328   // - subtrees
329   // - subtrees.name
330   // - subtrees.value
331   // - subtrees.value.timestamp
332   // - subtrees.subtrees
333   // - subtrees.subtrees.name
334   // - subtrees.subtrees.value
335   // - subtrees.subtrees.value.body
336   // - subtrees.subtrees.value.score
337   DocumentProto leaf_document =
338       DocumentBuilder()
339           .SetKey("icing", "uri")
340           .SetSchema(std::string(kTreeType))
341           .AddStringProperty(std::string(kPropertyName), "leaf")
342           .AddDocumentProperty(
343               std::string(kPropertyValue),
344               DocumentBuilder()
345                   .SetKey("icing", "uri")
346                   .SetSchema(std::string(kValueType))
347                   .AddStringProperty(std::string(kPropertyBody), "foo")
348                   .AddDoubleProperty(std::string(kPropertyScore), 456.789)
349                   .Build())
350           .Build();
351   DocumentProto intermediate_document1 =
352       DocumentBuilder()
353           .SetKey("icing", "uri")
354           .SetSchema(std::string(kTreeType))
355           .AddStringProperty(std::string(kPropertyName), "intermediate1")
356           .AddDocumentProperty(
357               std::string(kPropertyValue),
358               DocumentBuilder()
359                   .SetKey("icing", "uri")
360                   .SetSchema(std::string(kValueType))
361                   .AddInt64Property(std::string(kPropertyTimestamp), 123)
362                   .Build())
363           .AddDocumentProperty(std::string(kPropertySubtrees), leaf_document)
364           .Build();
365   DocumentProto intermediate_document2 =
366       DocumentBuilder()
367           .SetKey("icing", "uri")
368           .SetSchema(std::string(kTreeType))
369           .AddStringProperty(std::string(kPropertyName), "intermediate2")
370           .Build();
371   DocumentProto root_document =
372       DocumentBuilder()
373           .SetKey("icing", "uri")
374           .SetSchema(std::string(kTreeType))
375           .AddStringProperty(std::string(kPropertyName), "root")
376           .AddDocumentProperty(std::string(kPropertySubtrees),
377                                intermediate_document1, intermediate_document2)
378           .Build();
379 
380   // Handle root_document
381   ICING_ASSERT_OK_AND_ASSIGN(
382       TokenizedDocument tokenized_root_document,
383       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
384                                 std::move(root_document)));
385   ICING_ASSERT_OK_AND_ASSIGN(
386       DocumentStore::PutResult put_result,
387       document_store_->Put(tokenized_root_document.document()));
388   DocumentId document_id = put_result.new_document_id;
389   ICING_ASSERT_OK_AND_ASSIGN(
390       std::unique_ptr<PropertyExistenceIndexingHandler> handler,
391       PropertyExistenceIndexingHandler::Create(&fake_clock_, index.get()));
392   EXPECT_THAT(handler->Handle(tokenized_root_document, document_id,
393                               put_result.old_document_id,
394                               /*put_document_stats=*/nullptr),
395               IsOk());
396 
397   // Check that the above property paths can be found by query.
398   ICING_ASSERT_OK_AND_ASSIGN(std::unique_ptr<DocHitInfoIterator> itr,
399                              QueryExistence(index.get(), "name"));
400   EXPECT_THAT(
401       GetHits(std::move(itr)),
402       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
403 
404   ICING_ASSERT_OK_AND_ASSIGN(itr, QueryExistence(index.get(), "subtrees"));
405   EXPECT_THAT(
406       GetHits(std::move(itr)),
407       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
408 
409   ICING_ASSERT_OK_AND_ASSIGN(itr, QueryExistence(index.get(), "subtrees.name"));
410   EXPECT_THAT(
411       GetHits(std::move(itr)),
412       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
413 
414   ICING_ASSERT_OK_AND_ASSIGN(itr,
415                              QueryExistence(index.get(), "subtrees.value"));
416   EXPECT_THAT(
417       GetHits(std::move(itr)),
418       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
419 
420   ICING_ASSERT_OK_AND_ASSIGN(
421       itr, QueryExistence(index.get(), "subtrees.value.timestamp"));
422   EXPECT_THAT(
423       GetHits(std::move(itr)),
424       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
425 
426   ICING_ASSERT_OK_AND_ASSIGN(itr,
427                              QueryExistence(index.get(), "subtrees.subtrees"));
428   EXPECT_THAT(
429       GetHits(std::move(itr)),
430       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
431 
432   ICING_ASSERT_OK_AND_ASSIGN(
433       itr, QueryExistence(index.get(), "subtrees.subtrees.name"));
434   EXPECT_THAT(
435       GetHits(std::move(itr)),
436       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
437 
438   ICING_ASSERT_OK_AND_ASSIGN(
439       itr, QueryExistence(index.get(), "subtrees.subtrees.value"));
440   EXPECT_THAT(
441       GetHits(std::move(itr)),
442       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
443 
444   ICING_ASSERT_OK_AND_ASSIGN(
445       itr, QueryExistence(index.get(), "subtrees.subtrees.value.body"));
446   EXPECT_THAT(
447       GetHits(std::move(itr)),
448       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
449 
450   ICING_ASSERT_OK_AND_ASSIGN(
451       itr, QueryExistence(index.get(), "subtrees.subtrees.value.score"));
452   EXPECT_THAT(
453       GetHits(std::move(itr)),
454       ElementsAre(EqualsDocHitInfo(document_id, std::vector<SectionId>{0})));
455 }
456 
TEST_F(PropertyExistenceIndexingHandlerTest,SingleEmptyStringIsNonExisting)457 TEST_F(PropertyExistenceIndexingHandlerTest, SingleEmptyStringIsNonExisting) {
458   Index::Options options(index_dir_, /*index_merge_size=*/1024 * 1024,
459                          /*lite_index_sort_at_indexing=*/true,
460                          /*lite_index_sort_size=*/1024 * 8);
461   ICING_ASSERT_OK_AND_ASSIGN(
462       std::unique_ptr<Index> index,
463       Index::Create(options, &filesystem_, &icing_filesystem_));
464 
465   // Create a document with one empty body.
466   DocumentProto document0 =
467       DocumentBuilder()
468           .SetKey("icing", "uri0")
469           .SetSchema(std::string(kValueType))
470           .AddStringProperty(std::string(kPropertyBody), "")
471           .Build();
472   // Create a document with two empty body.
473   DocumentProto document1 =
474       DocumentBuilder()
475           .SetKey("icing", "uri1")
476           .SetSchema(std::string(kValueType))
477           .AddStringProperty(std::string(kPropertyBody), "", "")
478           .Build();
479   // Create a document with one non-empty body.
480   DocumentProto document2 =
481       DocumentBuilder()
482           .SetKey("icing", "uri2")
483           .SetSchema(std::string(kValueType))
484           .AddStringProperty(std::string(kPropertyBody), "foo")
485           .Build();
486 
487   ICING_ASSERT_OK_AND_ASSIGN(
488       TokenizedDocument tokenized_document0,
489       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
490                                 std::move(document0)));
491   ICING_ASSERT_OK_AND_ASSIGN(
492       TokenizedDocument tokenized_document1,
493       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
494                                 std::move(document1)));
495   ICING_ASSERT_OK_AND_ASSIGN(
496       TokenizedDocument tokenized_document2,
497       TokenizedDocument::Create(schema_store_.get(), lang_segmenter_.get(),
498                                 std::move(document2)));
499   ICING_ASSERT_OK_AND_ASSIGN(
500       DocumentStore::PutResult put_result0,
501       document_store_->Put(tokenized_document0.document()));
502   DocumentId document_id0 = put_result0.new_document_id;
503   ICING_ASSERT_OK_AND_ASSIGN(
504       DocumentStore::PutResult put_result1,
505       document_store_->Put(tokenized_document1.document()));
506   DocumentId document_id1 = put_result1.new_document_id;
507   ICING_ASSERT_OK_AND_ASSIGN(
508       DocumentStore::PutResult put_result2,
509       document_store_->Put(tokenized_document2.document()));
510   DocumentId document_id2 = put_result2.new_document_id;
511 
512   ICING_ASSERT_OK_AND_ASSIGN(
513       std::unique_ptr<PropertyExistenceIndexingHandler> handler,
514       PropertyExistenceIndexingHandler::Create(&fake_clock_, index.get()));
515 
516   // Handle all docs
517   EXPECT_THAT(handler->Handle(tokenized_document0, document_id0,
518                               put_result0.old_document_id,
519                               /*put_document_stats=*/nullptr),
520               IsOk());
521   EXPECT_THAT(handler->Handle(tokenized_document1, document_id1,
522                               put_result1.old_document_id,
523                               /*put_document_stats=*/nullptr),
524               IsOk());
525   EXPECT_THAT(handler->Handle(tokenized_document2, document_id2,
526                               put_result2.old_document_id,
527                               /*put_document_stats=*/nullptr),
528               IsOk());
529 
530   // Check that the documents that have one or two empty bodies will not be
531   // considered as having a body property.
532   ICING_ASSERT_OK_AND_ASSIGN(std::unique_ptr<DocHitInfoIterator> itr,
533                              QueryExistence(index.get(), kPropertyBody));
534   EXPECT_THAT(
535       GetHits(std::move(itr)),
536       ElementsAre(EqualsDocHitInfo(document_id2, std::vector<SectionId>{0})));
537 }
538 
539 }  // namespace
540 
541 }  // namespace lib
542 }  // namespace icing
543