Search in sources :

Example 51 with FDBQueriedRecord

use of com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord in project fdb-record-layer by FoundationDB.

the class FDBSortQueryIndexSelectionTest method sortOnlyUnique.

private void sortOnlyUnique(RecordMetaDataHook hook) {
    try (FDBRecordContext context = openContext()) {
        openSimpleRecordStore(context, hook);
        for (int i = 0; i < 100; i++) {
            TestRecords1Proto.MySimpleRecord.Builder recBuilder = TestRecords1Proto.MySimpleRecord.newBuilder();
            // Carter-Wegman hash, with large enough prime
            recBuilder.setRecNo((1096 * i + 722) % 1289);
            recBuilder.setNumValueUnique(i);
            recordStore.saveRecord(recBuilder.build());
        }
        commit(context);
    }
    RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setSort(field("num_value_unique")).build();
    // Index(MySimpleRecord$num_value_unique <,>)
    RecordQueryPlan plan = planner.plan(query);
    assertThat(plan, indexScan(allOf(indexName("MySimpleRecord$num_value_unique"), unbounded())));
    assertEquals(-1130465929, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
    assertEquals(-491910604, plan.planHash(PlanHashable.PlanHashKind.FOR_CONTINUATION));
    assertEquals(-491910604, 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(Objects.requireNonNull(rec).getRecord());
                assertEquals(i++, myrec.getNumValueUnique());
            }
        }
        assertEquals(100, i);
        assertDiscardedNone(context);
    }
}
Also used : RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) Message(com.google.protobuf.Message) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) RecordQuery(com.apple.foundationdb.record.query.RecordQuery)

Example 52 with FDBQueriedRecord

use of com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord in project fdb-record-layer by FoundationDB.

the class FDBRepeatedFieldQueryTest method testOnlyRepeatIndex.

/**
 * Verify that an index on a repeated field isn't used for normal scans.
 */
@DualPlannerTest
public void testOnlyRepeatIndex() throws Exception {
    RecordMetaDataHook hook = metaData -> {
        metaData.removeIndex("MySimpleRecord$str_value_indexed");
        metaData.removeIndex("MySimpleRecord$num_value_unique");
        metaData.removeIndex("MySimpleRecord$num_value_3_indexed");
        metaData.addIndex("MySimpleRecord", "repeater$fanout", field("repeater", FanType.FanOut));
    };
    try (FDBRecordContext context = openContext()) {
        openSimpleRecordStore(context, hook);
        for (int i = 0; i < 3; i++) {
            TestRecords1Proto.MySimpleRecord.Builder recBuilder = TestRecords1Proto.MySimpleRecord.newBuilder();
            recBuilder.setRecNo(i);
            recordStore.saveRecord(recBuilder.build());
        }
        commit(context);
    }
    RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").build();
    RecordQueryPlan plan = planner.plan(query);
    assertThat(plan, typeFilter(contains("MySimpleRecord"), scan(unbounded())));
    try (FDBRecordContext context = openContext()) {
        openSimpleRecordStore(context, hook);
        assertEquals(LongStream.range(0, 3).mapToObj(Long::valueOf).collect(Collectors.toList()), recordStore.executeQuery(plan).map(FDBQueriedRecord::getRecord).map(message -> message.getField(message.getDescriptorForType().findFieldByNumber(1))).asList().join());
    }
}
Also used : Arrays(java.util.Arrays) PlanMatchers.union(com.apple.foundationdb.record.query.plan.match.PlanMatchers.union) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) PlanMatchers.bounds(com.apple.foundationdb.record.query.plan.match.PlanMatchers.bounds) RecordQueryPlanner(com.apple.foundationdb.record.query.plan.RecordQueryPlanner) TestHelpers.assertDiscardedNone(com.apple.foundationdb.record.TestHelpers.assertDiscardedNone) Tuple(com.apple.foundationdb.tuple.Tuple) TestHelpers(com.apple.foundationdb.record.TestHelpers) Expressions.concat(com.apple.foundationdb.record.metadata.Key.Expressions.concat) Tag(org.junit.jupiter.api.Tag) PlanMatchers.coveringIndexScan(com.apple.foundationdb.record.query.plan.match.PlanMatchers.coveringIndexScan) TestRecords4Proto(com.apple.foundationdb.record.TestRecords4Proto) Query(com.apple.foundationdb.record.query.expressions.Query) TestRecords1Proto(com.apple.foundationdb.record.TestRecords1Proto) Matchers.allOf(org.hamcrest.Matchers.allOf) FanType(com.apple.foundationdb.record.metadata.expressions.KeyExpression.FanType) Collectors(java.util.stream.Collectors) Test(org.junit.jupiter.api.Test) PlanMatchers.hasTupleString(com.apple.foundationdb.record.query.plan.match.PlanMatchers.hasTupleString) List(java.util.List) PlanMatchers.indexName(com.apple.foundationdb.record.query.plan.match.PlanMatchers.indexName) Matchers.contains(org.hamcrest.Matchers.contains) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) Assertions.assertTrue(org.junit.jupiter.api.Assertions.assertTrue) TestRecordsWithHeaderProto(com.apple.foundationdb.record.TestRecordsWithHeaderProto) PlanMatchers.typeFilter(com.apple.foundationdb.record.query.plan.match.PlanMatchers.typeFilter) RecordMetaData(com.apple.foundationdb.record.RecordMetaData) PlanMatchers.fetch(com.apple.foundationdb.record.query.plan.match.PlanMatchers.fetch) PlanMatchers.primaryKeyDistinct(com.apple.foundationdb.record.query.plan.match.PlanMatchers.primaryKeyDistinct) PlanMatchers.indexScan(com.apple.foundationdb.record.query.plan.match.PlanMatchers.indexScan) TestRecords6Proto(com.apple.foundationdb.record.TestRecords6Proto) RecordQuery(com.apple.foundationdb.record.query.RecordQuery) RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) PlanHashable(com.apple.foundationdb.record.PlanHashable) PlanMatchers.filter(com.apple.foundationdb.record.query.plan.match.PlanMatchers.filter) PlanMatchers.scan(com.apple.foundationdb.record.query.plan.match.PlanMatchers.scan) BooleanSource(com.apple.test.BooleanSource) MatcherAssert.assertThat(org.hamcrest.MatcherAssert.assertThat) Assertions.assertEquals(org.junit.jupiter.api.Assertions.assertEquals) Expressions.field(com.apple.foundationdb.record.metadata.Key.Expressions.field) LongStream(java.util.stream.LongStream) RecordMetaDataBuilder(com.apple.foundationdb.record.RecordMetaDataBuilder) Tags(com.apple.test.Tags) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest) TestHelpers.assertDiscardedAtMost(com.apple.foundationdb.record.TestHelpers.assertDiscardedAtMost) PlanMatchers.unbounded(com.apple.foundationdb.record.query.plan.match.PlanMatchers.unbounded) Message(com.google.protobuf.Message) QueryComponent(com.apple.foundationdb.record.query.expressions.QueryComponent) RealAnythingMatcher.anything(com.apple.foundationdb.record.TestHelpers.RealAnythingMatcher.anything) Collections(java.util.Collections) RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) RecordQuery(com.apple.foundationdb.record.query.RecordQuery)

Example 53 with FDBQueriedRecord

use of com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord in project fdb-record-layer by FoundationDB.

the class FDBRestrictedIndexQueryTest method queryAllowedIndexes.

/**
 * Verify that queries do not use prohibited indexes.
 */
@DualPlannerTest
void queryAllowedIndexes() {
    RecordMetaDataHook hook = metaData -> {
        metaData.removeIndex("MySimpleRecord$str_value_indexed");
        metaData.addIndex("MySimpleRecord", new Index("limited_str_value_index", field("str_value_indexed"), Index.EMPTY_VALUE, IndexTypes.VALUE, IndexOptions.NOT_ALLOWED_FOR_QUERY_OPTIONS));
    };
    try (FDBRecordContext context = openContext()) {
        openSimpleRecordStore(context, hook);
        recordStore.deleteAllRecords();
        TestRecords1Proto.MySimpleRecord.Builder recBuilder = TestRecords1Proto.MySimpleRecord.newBuilder();
        recBuilder.setRecNo(1);
        recBuilder.setStrValueIndexed("abc");
        recBuilder.setNumValueUnique(123);
        recordStore.saveRecord(recBuilder.build());
        recBuilder.setRecNo(2);
        recBuilder.setStrValueIndexed("xyz");
        recBuilder.setNumValueUnique(987);
        recordStore.saveRecord(recBuilder.build());
        commit(context);
    }
    RecordQuery query1 = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(Query.field("str_value_indexed").equalsValue("abc")).build();
    // Index(limited_str_value_index [[abc],[abc]])
    // Scan(<,>) | [MySimpleRecord] | str_value_indexed EQUALS abc
    RecordQueryPlan plan1 = planner.plan(query1);
    assertThat("should not use prohibited index", plan1, hasNoDescendant(indexScan("limited_str_value_index")));
    assertTrue(plan1.hasFullRecordScan(), "should use full record scan");
    if (planner instanceof RecordQueryPlanner) {
        assertEquals(-223683738, plan1.planHash(PlanHashable.PlanHashKind.LEGACY));
    // TODO: Issue https://github.com/FoundationDB/fdb-record-layer/issues/1074
    // assertEquals(1148834070, plan1.planHash(PlanHashable.PlanHashKind.STRUCTURAL_WITHOUT_LITERALS));
    } else {
        assertEquals(-2136471589, plan1.planHash(PlanHashable.PlanHashKind.LEGACY));
    }
    try (FDBRecordContext context = openContext()) {
        openSimpleRecordStore(context, hook);
        try (RecordCursor<FDBQueriedRecord<Message>> cursor = recordStore.executeQuery(plan1)) {
            FDBQueriedRecord<Message> rec = cursor.getNext().get();
            TestRecords1Proto.MySimpleRecord.Builder myrec = TestRecords1Proto.MySimpleRecord.newBuilder();
            myrec.mergeFrom(Objects.requireNonNull(rec).getRecord());
            assertEquals("abc", myrec.getStrValueIndexed());
            assertFalse(cursor.getNext().hasNext());
        }
        TestHelpers.assertDiscardedExactly(1, context);
        clearStoreCounter(context);
    }
    RecordQuery query2 = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(Query.field("str_value_indexed").equalsValue("abc")).setAllowedIndex("limited_str_value_index").build();
    // Index(limited_str_value_index [[abc],[abc]])
    RecordQueryPlan plan2 = planner.plan(query2);
    assertThat("explicitly use prohibited index", plan2, descendant(indexScan("limited_str_value_index")));
    assertFalse(plan2.hasRecordScan(), "should not use record scan");
    assertEquals(-1573180774, plan2.planHash(PlanHashable.PlanHashKind.LEGACY));
    assertEquals(994464666, plan2.planHash(PlanHashable.PlanHashKind.FOR_CONTINUATION));
    assertEquals(-1531627068, plan2.planHash(PlanHashable.PlanHashKind.STRUCTURAL_WITHOUT_LITERALS));
    try (FDBRecordContext context = openContext()) {
        openSimpleRecordStore(context, hook);
        try (RecordCursor<FDBQueriedRecord<Message>> cursor = recordStore.executeQuery(plan2)) {
            FDBQueriedRecord<Message> rec = cursor.getNext().get();
            TestRecords1Proto.MySimpleRecord.Builder myrec = TestRecords1Proto.MySimpleRecord.newBuilder();
            myrec.mergeFrom(Objects.requireNonNull(rec).getRecord());
            assertEquals("abc", myrec.getStrValueIndexed());
            assertFalse(cursor.getNext().hasNext());
        }
        TestHelpers.assertDiscardedNone(context);
    }
}
Also used : LogMessageKeys(com.apple.foundationdb.record.logging.LogMessageKeys) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) PlanMatchers.bounds(com.apple.foundationdb.record.query.plan.match.PlanMatchers.bounds) RecordQueryPlanner(com.apple.foundationdb.record.query.plan.RecordQueryPlanner) Tuple(com.apple.foundationdb.tuple.Tuple) TestHelpers(com.apple.foundationdb.record.TestHelpers) Assertions.assertFalse(org.junit.jupiter.api.Assertions.assertFalse) RecordCoreException(com.apple.foundationdb.record.RecordCoreException) GroupingKeyExpression(com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression) Tag(org.junit.jupiter.api.Tag) Query(com.apple.foundationdb.record.query.expressions.Query) TestRecords1Proto(com.apple.foundationdb.record.TestRecords1Proto) IndexOptions(com.apple.foundationdb.record.metadata.IndexOptions) Predicate(java.util.function.Predicate) Matchers.allOf(org.hamcrest.Matchers.allOf) IndexQueryabilityFilter(com.apple.foundationdb.record.query.IndexQueryabilityFilter) TupleRange(com.apple.foundationdb.record.TupleRange) Test(org.junit.jupiter.api.Test) Objects(java.util.Objects) PlanMatchers.hasTupleString(com.apple.foundationdb.record.query.plan.match.PlanMatchers.hasTupleString) List(java.util.List) PlanMatchers.indexName(com.apple.foundationdb.record.query.plan.match.PlanMatchers.indexName) RecordStoreState(com.apple.foundationdb.record.RecordStoreState) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) PlanMatchers.hasNoDescendant(com.apple.foundationdb.record.query.plan.match.PlanMatchers.hasNoDescendant) Assertions.assertTrue(org.junit.jupiter.api.Assertions.assertTrue) AggregateFunctionNotSupportedException(com.apple.foundationdb.record.AggregateFunctionNotSupportedException) IndexTypes(com.apple.foundationdb.record.metadata.IndexTypes) Matchers.containsString(org.hamcrest.Matchers.containsString) Assertions.assertThrows(org.junit.jupiter.api.Assertions.assertThrows) FunctionNames(com.apple.foundationdb.record.FunctionNames) IndexAggregateFunction(com.apple.foundationdb.record.metadata.IndexAggregateFunction) PlanMatchers.indexScan(com.apple.foundationdb.record.query.plan.match.PlanMatchers.indexScan) Assertions.assertNull(org.junit.jupiter.api.Assertions.assertNull) CompletableFuture(java.util.concurrent.CompletableFuture) RecordQuery(com.apple.foundationdb.record.query.RecordQuery) RangeSet(com.apple.foundationdb.async.RangeSet) RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) Function(java.util.function.Function) PlanHashable(com.apple.foundationdb.record.PlanHashable) Key(com.apple.foundationdb.record.metadata.Key) MatcherAssert.assertThat(org.hamcrest.MatcherAssert.assertThat) Assertions.assertEquals(org.junit.jupiter.api.Assertions.assertEquals) Nonnull(javax.annotation.Nonnull) Expressions.field(com.apple.foundationdb.record.metadata.Key.Expressions.field) EmptyKeyExpression(com.apple.foundationdb.record.metadata.expressions.EmptyKeyExpression) IsolationLevel(com.apple.foundationdb.record.IsolationLevel) Tags(com.apple.test.Tags) Index(com.apple.foundationdb.record.metadata.Index) Executable(org.junit.jupiter.api.function.Executable) Message(com.google.protobuf.Message) RecordCursor(com.apple.foundationdb.record.RecordCursor) PlanMatchers.descendant(com.apple.foundationdb.record.query.plan.match.PlanMatchers.descendant) Collections(java.util.Collections) RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) Message(com.google.protobuf.Message) Index(com.apple.foundationdb.record.metadata.Index) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) RecordQueryPlanner(com.apple.foundationdb.record.query.plan.RecordQueryPlanner) RecordQuery(com.apple.foundationdb.record.query.RecordQuery)

Example 54 with FDBQueriedRecord

use of com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord in project fdb-record-layer by FoundationDB.

the class FDBSortQueryIndexSelectionTest method testComplexQuery5r.

/**
 * Verify that a sort can be implemented by traversing an index in the reverse order.
 */
@DualPlannerTest
public void testComplexQuery5r() throws Exception {
    RecordMetaDataHook hook = complexQuerySetupHook();
    complexQuerySetup(hook);
    RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setSort(field("num_value_unique"), true).build();
    // Index(MySimpleRecord$num_value_unique <,> REVERSE)
    RecordQueryPlan plan = planner.plan(query);
    assertThat(plan, indexScan(allOf(indexName("MySimpleRecord$num_value_unique"), unbounded())));
    assertTrue(plan.isReverse(), "plan should have reversal");
    assertEquals(-1130465928, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
    assertEquals(-491910790, plan.planHash(PlanHashable.PlanHashKind.FOR_CONTINUATION));
    assertEquals(-491910790, 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(Objects.requireNonNull(rec).getRecord());
                assertEquals(1000 - i, myrec.getNumValueUnique());
                i++;
            }
        }
        assertEquals(100, i);
        assertDiscardedNone(context);
    }
}
Also used : RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) Message(com.google.protobuf.Message) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) RecordQuery(com.apple.foundationdb.record.query.RecordQuery)

Example 55 with FDBQueriedRecord

use of com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord in project fdb-record-layer by FoundationDB.

the class FDBSortQueryIndexSelectionTest method testComplexQuery8x.

/**
 * Verify that a query with a filter on one field and a sort on another uses the index of the sort preferentially,
 * and falls back to filtering to implement the filter if an appropriate multi-field index is not available.
 */
@DualPlannerTest
public void testComplexQuery8x() throws Exception {
    RecordMetaDataHook hook = complexQuerySetupHook();
    complexQuerySetup(hook);
    final QueryComponent filter = Query.field("str_value_indexed").equalsValue("even");
    RecordQuery query = RecordQuery.newBuilder().setRecordType("MySimpleRecord").setFilter(filter).setSort(field("num_value_3_indexed")).build();
    RecordQueryPlan plan = planner.plan(query);
    assertThat(plan, filter(filter, indexScan(allOf(indexName("MySimpleRecord$num_value_3_indexed"), unbounded()))));
    if (planner instanceof RecordQueryPlanner) {
        assertEquals(-1429997503, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
    // TODO: Issue https://github.com/FoundationDB/fdb-record-layer/issues/1074
    // assertEquals(-1729416480, plan.planHash(PlanHashable.PlanHashKind.STRUCTURAL_WITHOUT_LITERALS));
    } else {
        assertEquals(952181942, plan.planHash(PlanHashable.PlanHashKind.LEGACY));
    }
    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(Objects.requireNonNull(rec).getRecord());
                assertEquals("even", myrec.getStrValueIndexed());
                i++;
            }
        }
        assertEquals(50, i);
        assertDiscardedAtMost(50, context);
    }
}
Also used : RecordQueryPlan(com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan) QueryComponent(com.apple.foundationdb.record.query.expressions.QueryComponent) Message(com.google.protobuf.Message) FDBQueriedRecord(com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord) FDBRecordContext(com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext) RecordQueryPlanner(com.apple.foundationdb.record.query.plan.RecordQueryPlanner) RecordQuery(com.apple.foundationdb.record.query.RecordQuery)

Aggregations

FDBQueriedRecord (com.apple.foundationdb.record.provider.foundationdb.FDBQueriedRecord)137 FDBRecordContext (com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext)125 RecordQueryPlan (com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan)117 RecordQuery (com.apple.foundationdb.record.query.RecordQuery)116 Message (com.google.protobuf.Message)108 Test (org.junit.jupiter.api.Test)65 Tags (com.apple.test.Tags)52 Assertions.assertEquals (org.junit.jupiter.api.Assertions.assertEquals)52 Tag (org.junit.jupiter.api.Tag)52 RecordQueryPlanner (com.apple.foundationdb.record.query.plan.RecordQueryPlanner)50 Query (com.apple.foundationdb.record.query.expressions.Query)47 List (java.util.List)47 MatcherAssert.assertThat (org.hamcrest.MatcherAssert.assertThat)46 ExecuteProperties (com.apple.foundationdb.record.ExecuteProperties)45 Index (com.apple.foundationdb.record.metadata.Index)45 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)45 ArrayList (java.util.ArrayList)43 Collections (java.util.Collections)42 PlanHashable (com.apple.foundationdb.record.PlanHashable)41 Tuple (com.apple.foundationdb.tuple.Tuple)41