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