From 2e7d93695608bf77b8220f1156e5fa0f198e9201 Mon Sep 17 00:00:00 2001 From: Mike McMahon Date: Fri, 18 Nov 2022 11:04:19 -0800 Subject: [PATCH] Scott's test case for #1927: Unable to construct a covering plan for a Lucene index over a Synthetic Record Type --- .../test/proto/test_records_join_index.proto | 1 + .../lucene/LuceneSyntheticPlannerTest.java | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneSyntheticPlannerTest.java diff --git a/fdb-record-layer-core/src/test/proto/test_records_join_index.proto b/fdb-record-layer-core/src/test/proto/test_records_join_index.proto index 04d83a8a78..413e731631 100644 --- a/fdb-record-layer-core/src/test/proto/test_records_join_index.proto +++ b/fdb-record-layer-core/src/test/proto/test_records_join_index.proto @@ -123,6 +123,7 @@ message OrderWithHeader { optional int32 order_no = 3 [(field).primary_key = true]; optional int32 quantity = 4; repeated Ref cc = 5; + optional string order_desc = 6; } message RecordTypeUnion { diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneSyntheticPlannerTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneSyntheticPlannerTest.java new file mode 100644 index 0000000000..1c6e8dab35 --- /dev/null +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneSyntheticPlannerTest.java @@ -0,0 +1,123 @@ +/* + * LuceneSyntheticPlannerTest.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2022 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.record.lucene; + +import com.apple.foundationdb.record.RecordMetaData; +import com.apple.foundationdb.record.RecordMetaDataBuilder; +import com.apple.foundationdb.record.TestRecordsJoinIndexProto; +import com.apple.foundationdb.record.TestRecordsTextProto; +import com.apple.foundationdb.record.metadata.Index; +import com.apple.foundationdb.record.metadata.IndexTypes; +import com.apple.foundationdb.record.metadata.JoinedRecordTypeBuilder; +import com.apple.foundationdb.record.metadata.Key; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreTestBase; +import com.apple.foundationdb.record.provider.foundationdb.indexes.TextIndexTestUtils; +import com.apple.foundationdb.record.query.RecordQuery; +import com.apple.foundationdb.record.query.expressions.AndComponent; +import com.apple.foundationdb.record.query.expressions.Comparisons; +import com.apple.foundationdb.record.query.expressions.FieldWithComparison; +import com.apple.foundationdb.record.query.expressions.NestedField; +import com.apple.foundationdb.record.query.expressions.QueryComponent; +import com.apple.foundationdb.record.query.plan.PlannableIndexTypes; +import com.apple.foundationdb.record.query.plan.plans.RecordQueryCoveringIndexPlan; +import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan; +import com.google.common.collect.Sets; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.apple.foundationdb.record.metadata.Key.Expressions.concat; +import static com.apple.foundationdb.record.metadata.Key.Expressions.concatenateFields; +import static com.apple.foundationdb.record.metadata.Key.Expressions.field; +import static com.apple.foundationdb.record.metadata.Key.Expressions.function; + +/** + * Tests about applying the Lucene index when used as part of a SyntheticRecord join. + */ +public class LuceneSyntheticPlannerTest extends FDBRecordStoreTestBase { + + protected void openRecordStore(FDBRecordContext context, FDBRecordStoreTestBase.RecordMetaDataHook hook, boolean attemptWholeFilter) { + RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(TestRecordsJoinIndexProto.getDescriptor()); + hook.apply(metaDataBuilder); + recordStore = getStoreBuilder(context, metaDataBuilder.getRecordMetaData()).createOrOpen(); + + PlannableIndexTypes indexTypes = new PlannableIndexTypes( + Sets.newHashSet(IndexTypes.VALUE, IndexTypes.VERSION), + Sets.newHashSet(IndexTypes.RANK, IndexTypes.TIME_WINDOW_LEADERBOARD), + Sets.newHashSet(IndexTypes.TEXT), + Sets.newHashSet(LuceneIndexTypes.LUCENE) + ); + planner = new LucenePlanner(recordStore.getRecordMetaData(), recordStore.getRecordStoreState(), indexTypes, recordStore.getTimer()); + planner.setConfiguration(planner.getConfiguration() + .asBuilder() + .setPlanOtherAttemptWholeFilter(attemptWholeFilter) + .build()); + } + + @Test + void canPlanQueryAgainstSyntheticLuceneType() { + try (FDBRecordContext context = openContext()) { + openRecordStore(context, metaDataBuilder -> { + metaDataBuilder.getRecordType("CustomerWithHeader") + .setPrimaryKey(Key.Expressions.concat(field("___header").nest("z_key"), field("___header").nest("rec_id"))); + metaDataBuilder.getRecordType("OrderWithHeader") + .setPrimaryKey(Key.Expressions.concat(field("___header").nest("z_key"), field("___header").nest("rec_id"))); + + //set up the joined index + final JoinedRecordTypeBuilder joined = metaDataBuilder.addJoinedRecordType("luceneJoinedIdx"); + joined.addConstituent("order", "OrderWithHeader"); + joined.addConstituent("cust", "CustomerWithHeader"); + joined.addJoin("order", field("___header").nest("z_key"), + "cust", field("___header").nest("z_key")); + joined.addJoin("order", field("custRef").nest("string_value"), + "cust", field("___header").nest("rec_id")); + + metaDataBuilder.addIndex(joined, new Index("joinNestedConcat", concat( + field("cust").nest(function(LuceneFunctionNames.LUCENE_STORED, field("name"))), + field("order").nest(concat(function(LuceneFunctionNames.LUCENE_STORED, field("order_no")), + function(LuceneFunctionNames.LUCENE_TEXT, field("order_desc")) + )) + ), LuceneIndexTypes.LUCENE)); + metaDataBuilder.addIndex("OrderWithHeader", "order$custRef", concat(field("___header").nest("z_key"), field("custRef").nest("string_value"))); + }, false); + +// QueryComponent filter = new AndComponent(List.of( +// new NestedField("order",new NestedField("___header",new FieldWithComparison("z_key", new Comparisons.NullComparison(Comparisons.Type.IS_NULL)))), +// new LuceneQueryComponent("order_order_desc: \"twelve pineapple\" and cust_name: \"steve\"",List.of("order","cust")) +// )); + + QueryComponent filter = new LuceneQueryComponent("order_order_desc: \"twelve pineapple\" and cust_name: \"steve\"",List.of("order","cust")); + + + RecordQuery query = RecordQuery.newBuilder() + .setRecordType("luceneJoinedIdx") + .setFilter(filter) + .setRequiredResults(List.of(Key.Expressions.field("order").nest("order_no"))) + .build(); + final RecordQueryPlan plan = planner.plan(query); + Assertions.assertTrue(plan.hasIndexScan("joinNestedConcat"),"Incorrect index scan"); + Assertions.assertTrue(plan instanceof RecordQueryCoveringIndexPlan, "Is not a covering scan!"); + } + + } +}