aboutsummaryrefslogtreecommitdiff
path: root/icing/join/join-processor_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'icing/join/join-processor_test.cc')
-rw-r--r--icing/join/join-processor_test.cc399
1 files changed, 352 insertions, 47 deletions
diff --git a/icing/join/join-processor_test.cc b/icing/join/join-processor_test.cc
index f503442..a40d934 100644
--- a/icing/join/join-processor_test.cc
+++ b/icing/join/join-processor_test.cc
@@ -22,9 +22,13 @@
#include "icing/text_classifier/lib3/utils/base/statusor.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "icing/absl_ports/canonical_errors.h"
#include "icing/document-builder.h"
#include "icing/file/filesystem.h"
#include "icing/file/portable-file-backed-proto-log.h"
+#include "icing/join/join-children-fetcher.h"
+#include "icing/join/qualified-id-join-index-impl-v1.h"
+#include "icing/join/qualified-id-join-index-impl-v2.h"
#include "icing/join/qualified-id-join-index.h"
#include "icing/join/qualified-id-join-indexing-handler.h"
#include "icing/portable/platform.h"
@@ -58,6 +62,9 @@ namespace {
using ::testing::ElementsAre;
using ::testing::IsTrue;
+// TODO(b/275121148): remove template after deprecating
+// QualifiedIdJoinIndexImplV1.
+template <typename T>
class JoinProcessorTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -108,6 +115,25 @@ class JoinProcessorTest : public ::testing::Test {
.SetDataTypeJoinableString(
JOINABLE_VALUE_TYPE_QUALIFIED_ID)
.SetCardinality(CARDINALITY_OPTIONAL)))
+ .AddType(
+ SchemaTypeConfigBuilder()
+ .SetType("Message")
+ .AddProperty(PropertyConfigBuilder()
+ .SetName("content")
+ .SetDataTypeString(TERM_MATCH_EXACT,
+ TOKENIZER_PLAIN)
+ .SetCardinality(CARDINALITY_OPTIONAL))
+ .AddProperty(PropertyConfigBuilder()
+ .SetName("sender")
+ .SetDataTypeJoinableString(
+ JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+ .SetCardinality(CARDINALITY_OPTIONAL))
+ .AddProperty(PropertyConfigBuilder()
+ .SetName("receiver")
+ .SetDataTypeJoinableString(
+ JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+ .SetCardinality(CARDINALITY_OPTIONAL)))
+
.Build();
ASSERT_THAT(schema_store_->SetSchema(
schema, /*ignore_errors_and_delete_documents=*/false,
@@ -121,18 +147,15 @@ class JoinProcessorTest : public ::testing::Test {
DocumentStore::Create(
&filesystem_, doc_store_dir_, &fake_clock_, schema_store_.get(),
/*force_recovery_and_revalidate_documents=*/false,
- /*namespace_id_fingerprint=*/false, /*pre_mapping_fbv=*/false,
+ /*namespace_id_fingerprint=*/true, /*pre_mapping_fbv=*/false,
/*use_persistent_hash_map=*/false,
PortableFileBackedProtoLog<
DocumentWrapper>::kDeflateCompressionLevel,
/*initialize_stats=*/nullptr));
doc_store_ = std::move(create_result.document_store);
- ICING_ASSERT_OK_AND_ASSIGN(
- qualified_id_join_index_,
- QualifiedIdJoinIndex::Create(filesystem_, qualified_id_join_index_dir_,
- /*pre_mapping_fbv=*/false,
- /*use_persistent_hash_map=*/false));
+ ICING_ASSERT_OK_AND_ASSIGN(qualified_id_join_index_,
+ CreateQualifiedIdJoinIndex<T>());
}
void TearDown() override {
@@ -143,6 +166,28 @@ class JoinProcessorTest : public ::testing::Test {
filesystem_.DeleteDirectoryRecursively(test_dir_.c_str());
}
+ template <typename UnknownJoinIndexType>
+ libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>>
+ CreateQualifiedIdJoinIndex() {
+ return absl_ports::InvalidArgumentError("Unknown type");
+ }
+
+ template <>
+ libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>>
+ CreateQualifiedIdJoinIndex<QualifiedIdJoinIndexImplV1>() {
+ return QualifiedIdJoinIndexImplV1::Create(
+ filesystem_, qualified_id_join_index_dir_, /*pre_mapping_fbv=*/false,
+ /*use_persistent_hash_map=*/false);
+ }
+
+ template <>
+ libtextclassifier3::StatusOr<std::unique_ptr<QualifiedIdJoinIndex>>
+ CreateQualifiedIdJoinIndex<QualifiedIdJoinIndexImplV2>() {
+ return QualifiedIdJoinIndexImplV2::Create(filesystem_,
+ qualified_id_join_index_dir_,
+ /*pre_mapping_fbv=*/false);
+ }
+
libtextclassifier3::StatusOr<DocumentId> PutAndIndexDocument(
const DocumentProto& document) {
ICING_ASSIGN_OR_RETURN(DocumentId document_id, doc_store_->Put(document));
@@ -153,7 +198,7 @@ class JoinProcessorTest : public ::testing::Test {
ICING_ASSIGN_OR_RETURN(
std::unique_ptr<QualifiedIdJoinIndexingHandler> handler,
- QualifiedIdJoinIndexingHandler::Create(&fake_clock_,
+ QualifiedIdJoinIndexingHandler::Create(&fake_clock_, doc_store_.get(),
qualified_id_join_index_.get()));
ICING_RETURN_IF_ERROR(handler->Handle(tokenized_document, document_id,
/*recovery_mode=*/false,
@@ -163,8 +208,8 @@ class JoinProcessorTest : public ::testing::Test {
libtextclassifier3::StatusOr<std::vector<JoinedScoredDocumentHit>> Join(
const JoinSpecProto& join_spec,
- std::vector<ScoredDocumentHit>&& parent_scored_document_hits,
- std::vector<ScoredDocumentHit>&& child_scored_document_hits) {
+ std::vector<ScoredDocumentHit> parent_scored_document_hits,
+ std::vector<ScoredDocumentHit> child_scored_document_hits) {
JoinProcessor join_processor(
doc_store_.get(), schema_store_.get(), qualified_id_join_index_.get(),
/*current_time_ms=*/fake_clock_.GetSystemTimeMilliseconds());
@@ -191,7 +236,11 @@ class JoinProcessorTest : public ::testing::Test {
FakeClock fake_clock_;
};
-TEST_F(JoinProcessorTest, JoinByQualifiedId) {
+using TestTypes =
+ ::testing::Types<QualifiedIdJoinIndexImplV1, QualifiedIdJoinIndexImplV2>;
+TYPED_TEST_SUITE(JoinProcessorTest, TestTypes);
+
+TYPED_TEST(JoinProcessorTest, JoinByQualifiedId_allDocuments) {
DocumentProto person1 = DocumentBuilder()
.SetKey("pkg$db/namespace", "person1")
.SetSchema("Person")
@@ -227,15 +276,15 @@ TEST_F(JoinProcessorTest, JoinByQualifiedId) {
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
- PutAndIndexDocument(person1));
+ this->PutAndIndexDocument(person1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
- PutAndIndexDocument(person2));
+ this->PutAndIndexDocument(person2));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
- PutAndIndexDocument(email1));
+ this->PutAndIndexDocument(email1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id4,
- PutAndIndexDocument(email2));
+ this->PutAndIndexDocument(email2));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id5,
- PutAndIndexDocument(email3));
+ this->PutAndIndexDocument(email3));
ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
/*score=*/0.0);
@@ -267,8 +316,8 @@ TEST_F(JoinProcessorTest, JoinByQualifiedId) {
ICING_ASSERT_OK_AND_ASSIGN(
std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
- Join(join_spec, std::move(parent_scored_document_hits),
- std::move(child_scored_document_hits)));
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
EXPECT_THAT(
joined_result_document_hits,
ElementsAre(EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
@@ -282,7 +331,112 @@ TEST_F(JoinProcessorTest, JoinByQualifiedId) {
{scored_doc_hit5, scored_doc_hit3}))));
}
-TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithoutJoiningProperty) {
+TYPED_TEST(JoinProcessorTest, JoinByQualifiedId_partialDocuments) {
+ DocumentProto person1 = DocumentBuilder()
+ .SetKey("pkg$db/namespace", "person1")
+ .SetSchema("Person")
+ .AddStringProperty("Name", "Alice")
+ .Build();
+ DocumentProto person2 = DocumentBuilder()
+ .SetKey("pkg$db/namespace", "person2")
+ .SetSchema("Person")
+ .AddStringProperty("Name", "Bob")
+ .Build();
+ DocumentProto person3 = DocumentBuilder()
+ .SetKey("pkg$db/namespace", "person3")
+ .SetSchema("Person")
+ .AddStringProperty("Name", "Eve")
+ .Build();
+
+ DocumentProto email1 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email1")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 1")
+ .AddStringProperty("sender", "pkg$db/namespace#person1")
+ .Build();
+ DocumentProto email2 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email2")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 2")
+ .AddStringProperty("sender", "pkg$db/namespace#person2")
+ .Build();
+ DocumentProto email3 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email3")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 3")
+ .AddStringProperty("sender", "pkg$db/namespace#person3")
+ .Build();
+ DocumentProto email4 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email4")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 4")
+ .AddStringProperty("sender", "pkg$db/namespace#person1")
+ .Build();
+
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
+ this->PutAndIndexDocument(person1));
+ ICING_ASSERT_OK(/*document_id2 unused*/
+ this->PutAndIndexDocument(person2));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
+ this->PutAndIndexDocument(person3));
+ ICING_ASSERT_OK(/*document_id4 unused*/
+ this->PutAndIndexDocument(email1));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id5,
+ this->PutAndIndexDocument(email2));
+ ICING_ASSERT_OK(/*document_id6 unused*/
+ this->PutAndIndexDocument(email3));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id7,
+ this->PutAndIndexDocument(email4));
+
+ ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
+ /*score=*/0.0);
+ ScoredDocumentHit scored_doc_hit3(document_id3, kSectionIdMaskNone,
+ /*score=*/0.0);
+ ScoredDocumentHit scored_doc_hit5(document_id5, kSectionIdMaskNone,
+ /*score=*/4.0);
+ ScoredDocumentHit scored_doc_hit7(document_id7, kSectionIdMaskNone,
+ /*score=*/5.0);
+
+ // Only join person1, person3, email2 and email4.
+ // Parent ScoredDocumentHits: person1, person3
+ std::vector<ScoredDocumentHit> parent_scored_document_hits = {
+ scored_doc_hit3, scored_doc_hit1};
+
+ // Child ScoredDocumentHits: email2, email4
+ std::vector<ScoredDocumentHit> child_scored_document_hits = {scored_doc_hit7,
+ scored_doc_hit5};
+
+ JoinSpecProto join_spec;
+ join_spec.set_parent_property_expression(
+ std::string(JoinProcessor::kQualifiedIdExpr));
+ join_spec.set_child_property_expression("sender");
+ join_spec.set_aggregation_scoring_strategy(
+ JoinSpecProto::AggregationScoringStrategy::COUNT);
+ join_spec.mutable_nested_spec()->mutable_scoring_spec()->set_order_by(
+ ScoringSpecProto::Order::DESC);
+
+ ICING_ASSERT_OK_AND_ASSIGN(
+ std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
+ EXPECT_THAT(
+ joined_result_document_hits,
+ ElementsAre(EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
+ /*final_score=*/0.0,
+ /*parent_scored_document_hit=*/scored_doc_hit3,
+ /*child_scored_document_hits=*/{})),
+ EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
+ /*final_score=*/1.0,
+ /*parent_scored_document_hit=*/scored_doc_hit1,
+ /*child_scored_document_hits=*/{scored_doc_hit7}))));
+}
+
+TYPED_TEST(JoinProcessorTest,
+ ShouldIgnoreChildDocumentsWithoutJoiningProperty) {
DocumentProto person1 = DocumentBuilder()
.SetKey("pkg$db/namespace", "person1")
.SetSchema("Person")
@@ -303,11 +457,11 @@ TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithoutJoiningProperty) {
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
- PutAndIndexDocument(person1));
+ this->PutAndIndexDocument(person1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
- PutAndIndexDocument(email1));
+ this->PutAndIndexDocument(email1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
- PutAndIndexDocument(email2));
+ this->PutAndIndexDocument(email2));
ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
/*score=*/0.0);
@@ -335,8 +489,8 @@ TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithoutJoiningProperty) {
ICING_ASSERT_OK_AND_ASSIGN(
std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
- Join(join_spec, std::move(parent_scored_document_hits),
- std::move(child_scored_document_hits)));
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
// Since Email2 doesn't have "sender" property, it should be ignored.
EXPECT_THAT(
joined_result_document_hits,
@@ -345,7 +499,8 @@ TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithoutJoiningProperty) {
/*child_scored_document_hits=*/{scored_doc_hit2}))));
}
-TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithInvalidQualifiedId) {
+TYPED_TEST(JoinProcessorTest,
+ ShouldIgnoreChildDocumentsWithInvalidQualifiedId) {
DocumentProto person1 = DocumentBuilder()
.SetKey("pkg$db/namespace", "person1")
.SetSchema("Person")
@@ -379,13 +534,13 @@ TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithInvalidQualifiedId) {
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
- PutAndIndexDocument(person1));
+ this->PutAndIndexDocument(person1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
- PutAndIndexDocument(email1));
+ this->PutAndIndexDocument(email1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
- PutAndIndexDocument(email2));
+ this->PutAndIndexDocument(email2));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id4,
- PutAndIndexDocument(email3));
+ this->PutAndIndexDocument(email3));
ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
/*score=*/0.0);
@@ -415,8 +570,8 @@ TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithInvalidQualifiedId) {
ICING_ASSERT_OK_AND_ASSIGN(
std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
- Join(join_spec, std::move(parent_scored_document_hits),
- std::move(child_scored_document_hits)));
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
// Email 2 and email 3 (document id 3 and 4) contain invalid qualified ids.
// Join processor should ignore them.
EXPECT_THAT(joined_result_document_hits,
@@ -426,7 +581,7 @@ TEST_F(JoinProcessorTest, ShouldIgnoreChildDocumentsWithInvalidQualifiedId) {
/*child_scored_document_hits=*/{scored_doc_hit2}))));
}
-TEST_F(JoinProcessorTest, LeftJoinShouldReturnParentWithoutChildren) {
+TYPED_TEST(JoinProcessorTest, LeftJoinShouldReturnParentWithoutChildren) {
DocumentProto person1 = DocumentBuilder()
.SetKey("pkg$db/namespace", "person1")
.SetSchema("Person")
@@ -448,11 +603,11 @@ TEST_F(JoinProcessorTest, LeftJoinShouldReturnParentWithoutChildren) {
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
- PutAndIndexDocument(person1));
+ this->PutAndIndexDocument(person1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
- PutAndIndexDocument(person2));
+ this->PutAndIndexDocument(person2));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
- PutAndIndexDocument(email1));
+ this->PutAndIndexDocument(email1));
ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
/*score=*/0.0);
@@ -479,8 +634,8 @@ TEST_F(JoinProcessorTest, LeftJoinShouldReturnParentWithoutChildren) {
ICING_ASSERT_OK_AND_ASSIGN(
std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
- Join(join_spec, std::move(parent_scored_document_hits),
- std::move(child_scored_document_hits)));
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
// Person1 has no child documents, but left join should also include it.
EXPECT_THAT(
joined_result_document_hits,
@@ -494,7 +649,7 @@ TEST_F(JoinProcessorTest, LeftJoinShouldReturnParentWithoutChildren) {
/*child_scored_document_hits=*/{}))));
}
-TEST_F(JoinProcessorTest, ShouldSortChildDocumentsByRankingStrategy) {
+TYPED_TEST(JoinProcessorTest, ShouldSortChildDocumentsByRankingStrategy) {
DocumentProto person1 = DocumentBuilder()
.SetKey("pkg$db/namespace", "person1")
.SetSchema("Person")
@@ -524,13 +679,13 @@ TEST_F(JoinProcessorTest, ShouldSortChildDocumentsByRankingStrategy) {
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
- PutAndIndexDocument(person1));
+ this->PutAndIndexDocument(person1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
- PutAndIndexDocument(email1));
+ this->PutAndIndexDocument(email1));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
- PutAndIndexDocument(email2));
+ this->PutAndIndexDocument(email2));
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id4,
- PutAndIndexDocument(email3));
+ this->PutAndIndexDocument(email3));
ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
/*score=*/0.0);
@@ -560,8 +715,8 @@ TEST_F(JoinProcessorTest, ShouldSortChildDocumentsByRankingStrategy) {
ICING_ASSERT_OK_AND_ASSIGN(
std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
- Join(join_spec, std::move(parent_scored_document_hits),
- std::move(child_scored_document_hits)));
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
// Child documents should be sorted according to the (nested) ranking
// strategy.
EXPECT_THAT(
@@ -572,7 +727,7 @@ TEST_F(JoinProcessorTest, ShouldSortChildDocumentsByRankingStrategy) {
{scored_doc_hit3, scored_doc_hit4, scored_doc_hit2}))));
}
-TEST_F(JoinProcessorTest, ShouldAllowSelfJoining) {
+TYPED_TEST(JoinProcessorTest, ShouldAllowSelfJoining) {
DocumentProto email1 =
DocumentBuilder()
.SetKey("pkg$db/namespace", "email1")
@@ -582,7 +737,7 @@ TEST_F(JoinProcessorTest, ShouldAllowSelfJoining) {
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
- PutAndIndexDocument(email1));
+ this->PutAndIndexDocument(email1));
ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
/*score=*/0.0);
@@ -605,8 +760,8 @@ TEST_F(JoinProcessorTest, ShouldAllowSelfJoining) {
ICING_ASSERT_OK_AND_ASSIGN(
std::vector<JoinedScoredDocumentHit> joined_result_document_hits,
- Join(join_spec, std::move(parent_scored_document_hits),
- std::move(child_scored_document_hits)));
+ this->Join(join_spec, std::move(parent_scored_document_hits),
+ std::move(child_scored_document_hits)));
EXPECT_THAT(joined_result_document_hits,
ElementsAre(EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
/*final_score=*/1.0,
@@ -614,6 +769,156 @@ TEST_F(JoinProcessorTest, ShouldAllowSelfJoining) {
/*child_scored_document_hits=*/{scored_doc_hit1}))));
}
+TYPED_TEST(JoinProcessorTest, MultipleChildSchemasJoining) {
+ DocumentProto person1 = DocumentBuilder()
+ .SetKey("pkg$db/namespace", "person1")
+ .SetSchema("Person")
+ .AddStringProperty("Name", "Alice")
+ .Build();
+ DocumentProto person2 = DocumentBuilder()
+ .SetKey("pkg$db/namespace", "person2")
+ .SetSchema("Person")
+ .AddStringProperty("Name", "Bob")
+ .Build();
+
+ DocumentProto email1 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email1")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 1")
+ .AddStringProperty("sender", "pkg$db/namespace#person2")
+ .Build();
+ DocumentProto email2 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email2")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 2")
+ .AddStringProperty("sender", "pkg$db/namespace#person1")
+ .Build();
+ DocumentProto email3 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "email3")
+ .SetSchema("Email")
+ .AddStringProperty("subject", "test subject 3")
+ .AddStringProperty("sender", "pkg$db/namespace#person1")
+ .Build();
+ DocumentProto message1 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "message1")
+ .SetSchema("Message")
+ .AddStringProperty("content", "test content 1")
+ .AddStringProperty("sender", "pkg$db/namespace#person1")
+ .AddStringProperty("receiver", "pkg$db/namespace#person2")
+ .Build();
+ DocumentProto message2 =
+ DocumentBuilder()
+ .SetKey("pkg$db/namespace", "message2")
+ .SetSchema("Message")
+ .AddStringProperty("content", "test content 2")
+ .AddStringProperty("sender", "pkg$db/namespace#person2")
+ .AddStringProperty("receiver", "pkg$db/namespace#person1")
+ .Build();
+
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id1,
+ this->PutAndIndexDocument(person1));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id2,
+ this->PutAndIndexDocument(person2));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id3,
+ this->PutAndIndexDocument(email1));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id4,
+ this->PutAndIndexDocument(email2));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id5,
+ this->PutAndIndexDocument(email3));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id6,
+ this->PutAndIndexDocument(message1));
+ ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id7,
+ this->PutAndIndexDocument(message2));
+
+ ScoredDocumentHit scored_doc_hit1(document_id1, kSectionIdMaskNone,
+ /*score=*/0.0);
+ ScoredDocumentHit scored_doc_hit2(document_id2, kSectionIdMaskNone,
+ /*score=*/0.0);
+ ScoredDocumentHit scored_doc_hit3(document_id3, kSectionIdMaskNone,
+ /*score=*/5.0);
+ ScoredDocumentHit scored_doc_hit4(document_id4, kSectionIdMaskNone,
+ /*score=*/3.0);
+ ScoredDocumentHit scored_doc_hit5(document_id5, kSectionIdMaskNone,
+ /*score=*/2.0);
+ ScoredDocumentHit scored_doc_hit6(document_id6, kSectionIdMaskNone,
+ /*score=*/4.0);
+ ScoredDocumentHit scored_doc_hit7(document_id7, kSectionIdMaskNone,
+ /*score=*/1.0);
+
+ // Parent ScoredDocumentHits: all Person documents
+ std::vector<ScoredDocumentHit> parent_scored_document_hits = {
+ scored_doc_hit1, scored_doc_hit2};
+
+ // Child ScoredDocumentHits: all Email and Message documents
+ std::vector<ScoredDocumentHit> child_scored_document_hits = {
+ scored_doc_hit3, scored_doc_hit4, scored_doc_hit5, scored_doc_hit6,
+ scored_doc_hit7};
+
+ // Join by "sender".
+ // - Person1: [
+ // email2 (scored_doc_hit4),
+ // email3 (scored_doc_hit5),
+ // message1 (scored_doc_hit6),
+ // ]
+ // - Person2: [
+ // email1 (scored_doc_hit3),
+ // message2 (scored_doc_hit7),
+ // ]
+ JoinSpecProto join_spec;
+ join_spec.set_parent_property_expression(
+ std::string(JoinProcessor::kQualifiedIdExpr));
+ join_spec.set_child_property_expression("sender");
+ join_spec.set_aggregation_scoring_strategy(
+ JoinSpecProto::AggregationScoringStrategy::COUNT);
+ join_spec.mutable_nested_spec()->mutable_scoring_spec()->set_order_by(
+ ScoringSpecProto::Order::DESC);
+
+ ICING_ASSERT_OK_AND_ASSIGN(
+ std::vector<JoinedScoredDocumentHit> joined_result_document_hits1,
+ this->Join(join_spec, parent_scored_document_hits,
+ child_scored_document_hits));
+ EXPECT_THAT(
+ joined_result_document_hits1,
+ ElementsAre(EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
+ /*final_score=*/3.0,
+ /*parent_scored_document_hit=*/scored_doc_hit1,
+ /*child_scored_document_hits=*/
+ {scored_doc_hit6, scored_doc_hit4, scored_doc_hit5})),
+ EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
+ /*final_score=*/2.0,
+ /*parent_scored_document_hit=*/scored_doc_hit2,
+ /*child_scored_document_hits=*/
+ {scored_doc_hit3, scored_doc_hit7}))));
+
+ // Join by "receiver".
+ // - Person1: [
+ // message2 (scored_doc_hit7),
+ // ]
+ // - Person2: [
+ // message1 (scored_doc_hit6),
+ // ]
+ join_spec.set_child_property_expression("receiver");
+
+ ICING_ASSERT_OK_AND_ASSIGN(
+ std::vector<JoinedScoredDocumentHit> joined_result_document_hits2,
+ this->Join(join_spec, parent_scored_document_hits,
+ child_scored_document_hits));
+ EXPECT_THAT(
+ joined_result_document_hits2,
+ ElementsAre(EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
+ /*final_score=*/1.0,
+ /*parent_scored_document_hit=*/scored_doc_hit1,
+ /*child_scored_document_hits=*/{scored_doc_hit7})),
+ EqualsJoinedScoredDocumentHit(JoinedScoredDocumentHit(
+ /*final_score=*/1.0,
+ /*parent_scored_document_hit=*/scored_doc_hit2,
+ /*child_scored_document_hits=*/{scored_doc_hit6}))));
+}
+
// TODO(b/256022027): add unit tests for non-joinable property. If joinable
// value type is unset, then qualifed id join should not
// include the child document even if it contains a valid