use of com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan in project fdb-record-layer by FoundationDB.
the class ImplementInJoinRule method onMatch.
@SuppressWarnings("java:S135")
@Override
public void onMatch(@Nonnull PlannerRuleCall call) {
final var context = call.getContext();
final var bindings = call.getBindings();
final var requestedOrderingsOptional = call.getInterestingProperty(OrderingAttribute.ORDERING);
if (requestedOrderingsOptional.isEmpty()) {
return;
}
final var requestedOrderings = requestedOrderingsOptional.get();
final var selectExpression = bindings.get(root);
if (!selectExpression.getPredicates().isEmpty()) {
return;
}
final var explodeQuantifiers = bindings.get(explodeQuantifiersMatcher);
if (explodeQuantifiers.isEmpty()) {
return;
}
final var explodeAliasToQuantifierMap = Quantifiers.aliasToQuantifierMap(explodeQuantifiers);
final var explodeAliases = explodeAliasToQuantifierMap.keySet();
final var innerQuantifierOptional = findInnerQuantifier(selectExpression, explodeQuantifiers, explodeAliases);
if (innerQuantifierOptional.isEmpty()) {
return;
}
final var innerQuantifier = innerQuantifierOptional.get();
final List<? extends Value> resultValues = selectExpression.getResultValues();
if (resultValues.stream().anyMatch(resultValue -> !(resultValue instanceof QuantifiedColumnValue) || !((QuantifiedColumnValue) resultValue).getAlias().equals(innerQuantifier.getAlias()))) {
return;
}
final var explodeExpressions = bindings.getAll(explodeExpressionMatcher);
final var quantifierToExplodeBiMap = computeQuantifierToExplodeMap(explodeQuantifiers, explodeExpressions.stream().collect(LinkedIdentitySet.toLinkedIdentitySet()));
final Map<Ordering, ImmutableList<RecordQueryPlan>> groupedByOrdering = innerQuantifier.getRangesOver().getMembers().stream().flatMap(relationalExpression -> relationalExpression.narrowMaybe(RecordQueryPlan.class).stream()).flatMap(plan -> {
final Optional<Ordering> orderingForLegOptional = OrderingProperty.evaluate(plan, context);
return orderingForLegOptional.stream().map(ordering -> Pair.of(ordering, plan));
}).collect(Collectors.groupingBy(Pair::getLeft, Collectors.mapping(Pair::getRight, ImmutableList.toImmutableList())));
for (final Map.Entry<Ordering, ImmutableList<RecordQueryPlan>> providedOrderingEntry : groupedByOrdering.entrySet()) {
final var providedOrdering = providedOrderingEntry.getKey();
for (final RequestedOrdering requestedOrdering : requestedOrderings) {
final ImmutableList<InSource> sources = getInSourcesForRequestedOrdering(explodeAliasToQuantifierMap, explodeAliases, quantifierToExplodeBiMap, providedOrdering, requestedOrdering);
if (sources.isEmpty()) {
continue;
}
final var reverseSources = Lists.reverse(sources);
GroupExpressionRef<RecordQueryPlan> newInnerPlanReference = GroupExpressionRef.from(providedOrderingEntry.getValue());
for (final InSource inSource : reverseSources) {
final var inJoinPlan = inSource.toInJoinPlan(Quantifier.physical(newInnerPlanReference));
newInnerPlanReference = GroupExpressionRef.of(inJoinPlan);
}
call.yield(newInnerPlanReference);
}
}
}
use of com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan in project fdb-record-layer by FoundationDB.
the class FDBFilterCoalescingQueryTest method simpleRangeCoalesce.
/**
* Validate that a query for all values within a given range on an indexed field will be coalesced
* into a single query on that field.
*/
@DualPlannerTest
public void simpleRangeCoalesce() throws Exception {
complexQuerySetup(NO_HOOK);
RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(Query.and(Query.field("num_value_3_indexed").greaterThanOrEquals(0), Query.field("num_value_3_indexed").lessThanOrEquals(1))).build();
// Index(MySimpleRecord$num_value_3_indexed [[0],[1]])
RecordQueryPlan plan = planner.plan(query);
assertThat(plan, indexScan(allOf(indexName("MySimpleRecord$num_value_3_indexed"), bounds(hasTupleString("[[0],[1]]")))));
assertEquals(1869980849, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
assertEquals(876021686, plan.planHash(PlanHashable.PlanHashKind.FOR_CONTINUATION));
assertEquals(-2083489995, plan.planHash(PlanHashable.PlanHashKind.STRUCTURAL_WITHOUT_LITERALS));
try (FDBRecordContext context = openContext()) {
openSimpleRecordStore(context);
int i = 0;
try (RecordCursorIterator<FDBQueriedRecord<Message>> cursor = recordStore.executeQuery(plan).asIterator()) {
while (cursor.hasNext()) {
FDBQueriedRecord<Message> rec = cursor.next();
TestRecords1Proto.MySimpleRecord.Builder myrec = TestRecords1Proto.MySimpleRecord.newBuilder();
myrec.mergeFrom(rec.getRecord());
assertThat(myrec.getNumValue3Indexed(), allOf(greaterThanOrEqualTo(0), lessThanOrEqualTo(1)));
assertThat(myrec.getRecNo() % 5, allOf(greaterThanOrEqualTo(0L), lessThanOrEqualTo(1L)));
i++;
}
}
assertEquals(40, i);
assertDiscardedNone(context);
}
}
use of com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan in project fdb-record-layer by FoundationDB.
the class FDBFilterCoalescingQueryTest method duplicateFilters.
/**
* Verify that the planner removes duplicate filters.
* TODO We currently don't. Update this test when it gets implemented.
* TODO: Some query plans include redundant filtering operations even when the index is a complete specification (https://github.com/FoundationDB/fdb-record-layer/issues/2)
*/
@Test
public void duplicateFilters() throws Exception {
RecordMetaDataHook hook = metaData -> {
metaData.addIndex("MySimpleRecord", new Index("multi_index", "str_value_indexed", "num_value_3_indexed"));
};
complexQuerySetup(hook);
RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(Query.and(Query.field("str_value_indexed").equalsValue("even"), Query.field("num_value_3_indexed").equalsValue(3), Query.field("num_value_3_indexed").equalsValue(3))).build();
// Fetch(Covering(Index(multi_index [[even, 3],[even, 3]]) -> [num_value_3_indexed: KEY[1], rec_no: KEY[2], str_value_indexed: KEY[0]]) | num_value_3_indexed EQUALS 3)
RecordQueryPlan plan = planner.plan(query);
assertThat(plan, descendant(coveringIndexScan(indexScan(allOf(indexName("multi_index"), bounds(hasTupleString("[[even, 3],[even, 3]]")))))));
assertEquals(-766201402, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
assertEquals(-1632715349, plan.planHash(PlanHashable.PlanHashKind.FOR_CONTINUATION));
assertEquals(-1418679945, plan.planHash(PlanHashable.PlanHashKind.STRUCTURAL_WITHOUT_LITERALS));
try (FDBRecordContext context = openContext()) {
openSimpleRecordStore(context, hook);
int i = 0;
try (RecordCursorIterator<FDBQueriedRecord<Message>> cursor = recordStore.executeQuery(plan).asIterator()) {
while (cursor.hasNext()) {
FDBQueriedRecord<Message> rec = cursor.next();
TestRecords1Proto.MySimpleRecord.Builder myrec = TestRecords1Proto.MySimpleRecord.newBuilder();
myrec.mergeFrom(rec.getRecord());
assertEquals("even", myrec.getStrValueIndexed());
assertEquals(3, myrec.getNumValue3Indexed());
assertEquals(3, myrec.getRecNo() % 5);
i++;
}
}
assertEquals(10, i);
assertDiscardedNone(context);
}
}
use of com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan in project fdb-record-layer by FoundationDB.
the class FDBFilterCoalescingQueryTest method versionRangeCoalesce.
/**
* Validate that a query for all values within a given range of versions will be coalesced into a single scan.
* This is similar to the {@link #simpleRangeCoalesce()} test above, but it is for version key expressions in
* particular.
*/
@Test
public void versionRangeCoalesce() throws Exception {
Index versionIndex = new Index("MySimpleRecord$version", VersionKeyExpression.VERSION, IndexTypes.VERSION);
RecordMetaDataHook hook = metaData -> {
metaData.setStoreRecordVersions(true);
metaData.addIndex("MySimpleRecord", versionIndex);
};
complexQuerySetup(hook);
try (FDBRecordContext context = openContext()) {
openSimpleRecordStore(context, hook);
long readVersion = context.getReadVersion();
FDBRecordVersion lowBoundary = FDBRecordVersion.firstInDBVersion(0);
FDBRecordVersion highBoundary = FDBRecordVersion.lastInDBVersion(readVersion);
RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(Query.and(Query.version().greaterThan(lowBoundary), Query.version().lessThan(highBoundary))).build();
RecordQueryPlan plan = planner.plan(query);
assertThat(plan, indexScan(allOf(indexName(versionIndex.getName()), bounds(hasTupleString("([" + lowBoundary.toVersionstamp() + "],[" + highBoundary.toVersionstamp() + "])")))));
try (RecordCursorIterator<FDBQueriedRecord<Message>> cursor = recordStore.executeQuery(plan).asIterator()) {
int i = 0;
while (cursor.hasNext()) {
FDBQueriedRecord<Message> rec = cursor.next();
FDBRecordVersion version = rec.getVersion();
assertNotNull(version);
assertThat(version, allOf(lessThan(highBoundary), greaterThan(lowBoundary)));
i++;
}
assertEquals(100, i);
assertDiscardedNone(context);
}
}
}
use of com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan in project fdb-record-layer by FoundationDB.
the class FDBInQueryTest method testNotInQueryParameterBad.
/**
* Verify that NOT IN is planned correctly, and fails if no binding is provided.
*/
@Test
void testNotInQueryParameterBad() throws Exception {
complexQuerySetup(NO_HOOK);
final QueryComponent filter = Query.not(Query.field("num_value_3_indexed").in("valueThrees"));
RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(filter).build();
// Scan(<,>) | [MySimpleRecord] | Not(num_value_3_indexed IN $valueThrees)
RecordQueryPlan plan = planner.plan(query);
assertMatchesExactly(plan, filterPlan(descendantPlans(scanPlan().where(scanComparisons(unbounded())))).where(queryComponents(exactly(equalsObject(filter)))));
assertEquals(1667070490, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
assertEquals(1804602975, plan.planHash(PlanHashable.PlanHashKind.FOR_CONTINUATION));
assertEquals(1804602975, plan.planHash(PlanHashable.PlanHashKind.STRUCTURAL_WITHOUT_LITERALS));
assertEquals(100, querySimpleRecordStore(NO_HOOK, plan, () -> EvaluationContext.forBinding("valueThrees", Collections.emptyList()), myrec -> {
}, TestHelpers::assertDiscardedNone));
assertEquals(0, querySimpleRecordStore(NO_HOOK, plan, () -> EvaluationContext.forBinding("valueThrees", null), /* no binding for valueThrees */
myrec -> fail("There should be no results")));
}
Aggregations