diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java index 7ad951b3..2a9adeb0 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java @@ -688,6 +688,39 @@ public void testQueryQ1AggregationFilterWithStringInFilterAlongWithNonAliasField "mongo/test_string_in_filter_aggr_alias_distinct_count_response.json"); } + @ParameterizedTest + @MethodSource("databaseContextBoth") + void testQueryQ1DistinctCountAggregationWithOnlyFilter(String dataStoreName) throws IOException { + Datastore datastore = datastoreMap.get(dataStoreName); + Collection collection = datastore.getCollection(COLLECTION_NAME); + org.hypertrace.core.documentstore.query.Query query = + org.hypertrace.core.documentstore.query.Query.builder() + .addSelection( + AggregateExpression.of(DISTINCT_COUNT, IdentifierExpression.of("quantity")), + "qty_count") + .addSelection(IdentifierExpression.of("item")) + .addSelection(IdentifierExpression.of("price")) + .setFilter( + LogicalExpression.builder() + .operator(AND) + .operand( + RelationalExpression.of( + IdentifierExpression.of("price"), LTE, ConstantExpression.of(10))) + .operand( + RelationalExpression.of( + IdentifierExpression.of("item"), + IN, + ConstantExpression.ofStrings( + List.of("Mirror", "Comb", "Shampoo", "Bottle")))) + .build()) + .build(); + + try (CloseableIterator resultDocs = collection.aggregate(query)) { + Utils.assertDocsAndSizeEqualWithoutOrder( + dataStoreName, resultDocs, 1, "mongo/test_aggr_only_with_fliter_response.json"); + } + } + @ParameterizedTest @MethodSource("databaseContextBoth") public void testQueryV1ForSimpleWhereClause(String dataStoreName) throws IOException { diff --git a/document-store/src/integrationTest/resources/mongo/test_aggr_only_with_fliter_response.json b/document-store/src/integrationTest/resources/mongo/test_aggr_only_with_fliter_response.json new file mode 100644 index 00000000..4a9a6ffb --- /dev/null +++ b/document-store/src/integrationTest/resources/mongo/test_aggr_only_with_fliter_response.json @@ -0,0 +1,5 @@ +[ + { + "qty_count":3 + } +] \ No newline at end of file diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java index 29970a25..c02027b8 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java @@ -45,6 +45,7 @@ import org.hypertrace.core.documentstore.UpdateResult; import org.hypertrace.core.documentstore.commons.DocStoreConstants; import org.hypertrace.core.documentstore.postgres.internal.BulkUpdateSubDocsInternalResult; +import org.hypertrace.core.documentstore.postgres.query.v1.transformer.PostgresQueryTransformer; import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -782,7 +783,7 @@ private CloseableIterator executeQueryV1( final org.hypertrace.core.documentstore.query.Query query) { org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser queryParser = new org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser( - collectionName, query); + collectionName, transformAndLog(query)); String sqlQuery = queryParser.parse(); try { PreparedStatement preparedStatement = @@ -801,6 +802,14 @@ private CloseableIterator executeQueryV1( } } + private org.hypertrace.core.documentstore.query.Query transformAndLog( + org.hypertrace.core.documentstore.query.Query query) { + LOGGER.debug("Original query before transformation: {}", query); + query = PostgresQueryTransformer.transform(query); + LOGGER.debug("Query after transformation: {}", query); + return query; + } + private boolean isValidType(Object v) { Set> validClassez = new HashSet<>() { diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParser.java index e4e8769e..adc3070d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParser.java @@ -7,6 +7,7 @@ import lombok.Setter; import org.hypertrace.core.documentstore.postgres.Params; import org.hypertrace.core.documentstore.postgres.Params.Builder; +import org.hypertrace.core.documentstore.postgres.PostgresCollection; import org.hypertrace.core.documentstore.postgres.query.v1.transformer.FieldToPgColumnTransformer; import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresAggregationFilterTypeExpressionVisitor; import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresFilterTypeExpressionVisitor; @@ -17,8 +18,12 @@ import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresUnnestFilterTypeExpressionVisitor; import org.hypertrace.core.documentstore.query.Pagination; import org.hypertrace.core.documentstore.query.Query; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PostgresQueryParser { + private static final Logger LOGGER = LoggerFactory.getLogger(PostgresCollection.class); + @Getter private final String collection; @Getter private final Query query; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresQueryTransformer.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresQueryTransformer.java new file mode 100644 index 00000000..0491bd2b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresQueryTransformer.java @@ -0,0 +1,25 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.transformer; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.hypertrace.core.documentstore.query.Query; +import org.hypertrace.core.documentstore.query.transform.QueryTransformer; + +public class PostgresQueryTransformer { + + // Transform the query in the listed below order + private static final List TRANSFORMERS = + new ImmutableList.Builder() + .add(new PostgresSelectionQueryTransformer()) + .build(); + + public static Query transform(final Query query) { + Query transformedQuery = query; + + for (QueryTransformer transformer : TRANSFORMERS) { + transformedQuery = transformer.transform(transformedQuery); + } + + return transformedQuery; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java new file mode 100644 index 00000000..89a0589b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java @@ -0,0 +1,70 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.transformer; + +import java.util.List; +import java.util.stream.Collectors; +import org.hypertrace.core.documentstore.expression.impl.AggregateExpression; +import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; +import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; +import org.hypertrace.core.documentstore.query.Query; +import org.hypertrace.core.documentstore.query.SelectionSpec; +import org.hypertrace.core.documentstore.query.transform.QueryTransformer; +import org.hypertrace.core.documentstore.query.transform.TransformedQueryBuilder; + +/* + * Postgres doesn't support the selection of attributes and aggregation w/o group by expression. + * e.g + * SELECT COUNT(DISTINCT document->>'quantity' ) AS QTY, document->'price' AS price + * FROM testCollection + * WHERE (CAST (document->>'price' AS NUMERIC) <= 10) + * + * So, if group by clause is missing, and selection contains any aggregation expression, + * this transformer removes all the non-aggregated expressions. So, the above query will be transformed + * to: + * + * SELECT COUNT(DISTINCT document->>'quantity' ) AS QTY + * FROM testCollection + * WHERE (CAST (document->>'price' AS NUMERIC) <= 10) + * + * This is the similar behavior supported in our other document store implementation (e.g Mongo) + * */ +public class PostgresSelectionQueryTransformer + implements QueryTransformer, SelectTypeExpressionVisitor { + + @Override + public Query transform(Query query) { + // no-op if group by clause exits + if (!query.getAggregations().isEmpty()) return query; + + // check for all selections, remove non-aggregated selections. + List finalSelectionSpecs = + query.getSelections().stream() + .filter(selectionSpec -> selectionSpec.getExpression().accept(this)) + .collect(Collectors.toUnmodifiableList()); + + return finalSelectionSpecs.size() > 0 + ? new TransformedQueryBuilder(query).setSelections(finalSelectionSpecs).build() + : query; + } + + @Override + public Boolean visit(AggregateExpression expression) { + return true; + } + + @Override + public Boolean visit(ConstantExpression expression) { + return false; + } + + @Override + public Boolean visit(FunctionExpression expression) { + return false; + } + + @Override + public Boolean visit(IdentifierExpression expression) { + return false; + } +} diff --git a/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParserTest.java b/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParserTest.java index d02da9a4..0f0a70ee 100644 --- a/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParserTest.java +++ b/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParserTest.java @@ -30,6 +30,7 @@ import org.hypertrace.core.documentstore.expression.impl.UnnestExpression; import org.hypertrace.core.documentstore.expression.operators.FunctionOperator; import org.hypertrace.core.documentstore.postgres.Params; +import org.hypertrace.core.documentstore.postgres.query.v1.transformer.PostgresQueryTransformer; import org.hypertrace.core.documentstore.query.Filter; import org.hypertrace.core.documentstore.query.Pagination; import org.hypertrace.core.documentstore.query.Query; @@ -51,7 +52,8 @@ void testParseSimpleFilter() { RelationalExpression.of( IdentifierExpression.of("quantity"), NEQ, ConstantExpression.of(10))) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT * FROM testCollection " @@ -79,7 +81,8 @@ void testFilterWithNestedFiled() { ConstantExpression.of("Kolkata"))) .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT * FROM testCollection " @@ -107,7 +110,8 @@ void testFilterWithLogicalExpressionAnd() { IdentifierExpression.of("quantity"), LTE, ConstantExpression.of(10))) .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT * FROM testCollection WHERE (CAST (document->>'quantity' AS NUMERIC) >= ?) " @@ -134,7 +138,8 @@ void testFilterWithLogicalExpressionOr() { IdentifierExpression.of("quantity"), LTE, ConstantExpression.of(10))) .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT * FROM testCollection WHERE (CAST (document->>'quantity' AS NUMERIC) >= ?) " @@ -173,7 +178,8 @@ void testFilterWithLogicalExpressionAndOr() { .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT * FROM testCollection WHERE (CAST (document->>'price' AS NUMERIC) >= ?) " @@ -194,7 +200,8 @@ void testBasicSelectionExpression() { .addSelection(IdentifierExpression.of("item")) .addSelection(IdentifierExpression.of("price")) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT document->'item' AS item, document->'price' AS price FROM testCollection", sql); @@ -216,7 +223,8 @@ void testFunctionalSelectionExpression() { .build(), "total") .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT document->'item' AS item, " @@ -243,7 +251,8 @@ void testFunctionalSelectionExpressionWithNestedField() { .build(), "total") .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT document->'item' AS item, " @@ -272,7 +281,8 @@ void testFunctionalSelectionExpressionWithNestedFieldWithAlias() { .build(), "total") .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT document->'item' AS item, " @@ -310,7 +320,8 @@ void testAggregationExpression() { .addAggregation(IdentifierExpression.of("item")) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( "SELECT document->'item' AS item, " @@ -348,7 +359,8 @@ void testAggregationExpressionDistinctCount() { .addAggregation(IdentifierExpression.of("item")) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -384,7 +396,8 @@ void testAggregateWithMultipleGroupingLevels() { .addSort(IdentifierExpression.of("item"), DESC) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -414,7 +427,8 @@ void testAggregationFilter() { IdentifierExpression.of("qty_count"), LTE, ConstantExpression.of(10))) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -446,7 +460,8 @@ void testAggregationFilterAndWhereFilter() { IdentifierExpression.of("qty_count"), LTE, ConstantExpression.of(10))) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -486,7 +501,8 @@ void testAggregationFilterAlongWithNonAliasFields() { .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -514,7 +530,8 @@ void testSimpleOrderByClause() { .addSort(IdentifierExpression.of("item"), DESC) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -543,7 +560,8 @@ void testOrderByClauseWithAlias() { .addSort(IdentifierExpression.of("item"), DESC) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -595,7 +613,8 @@ void testFindWithSortingAndPagination() { .setPagination(pagination) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -631,7 +650,8 @@ void testUnnestWithoutPreserveNullAndEmptyArrays() { .addFromClause(UnnestExpression.of(IdentifierExpression.of("sales.medium"), false)) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -662,7 +682,8 @@ void testUnnestWithPreserveNullAndEmptyArrays() { .addFromClause(UnnestExpression.of(IdentifierExpression.of("sales.medium"), true)) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -704,7 +725,8 @@ void testUnnestWithoutPreserveNullAndEmptyArraysWithFilters() { .addFromClause(UnnestExpression.of(IdentifierExpression.of("sales.medium"), false)) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -748,7 +770,8 @@ void testQueryQ1AggregationFilterWithStringAlongWithNonAliasFields() { .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -790,7 +813,8 @@ void testQueryQ1AggregationFilterWithStringInFilterAlongWithNonAliasFields() { .build()) .build(); - PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION, query); + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); String sql = postgresQueryParser.parse(); Assertions.assertEquals( @@ -808,4 +832,47 @@ void testQueryQ1AggregationFilterWithStringInFilterAlongWithNonAliasFields() { Assertions.assertEquals("\"Shampoo\"", params.getObjectParams().get(4)); Assertions.assertEquals("\"Bottle\"", params.getObjectParams().get(5)); } + + @Test + void testQueryQ1DistinctCountAggregationWithOnlyFilter() { + org.hypertrace.core.documentstore.query.Query query = + org.hypertrace.core.documentstore.query.Query.builder() + .addSelection( + AggregateExpression.of(DISTINCT_COUNT, IdentifierExpression.of("quantity")), + "qty_count") + .addSelection(IdentifierExpression.of("item")) + .addSelection(IdentifierExpression.of("price")) + .setFilter( + LogicalExpression.builder() + .operator(AND) + .operand( + RelationalExpression.of( + IdentifierExpression.of("price"), LTE, ConstantExpression.of(10))) + .operand( + RelationalExpression.of( + IdentifierExpression.of("item"), + IN, + ConstantExpression.ofStrings( + List.of("Mirror", "Comb", "Shampoo", "Bottle")))) + .build()) + .build(); + + PostgresQueryParser postgresQueryParser = + new PostgresQueryParser(TEST_COLLECTION, PostgresQueryTransformer.transform(query)); + String sql = postgresQueryParser.parse(); + + Assertions.assertEquals( + "SELECT COUNT(DISTINCT document->>'quantity' ) AS \"qty_count\" " + + "FROM testCollection " + + "WHERE (CAST (document->>'price' AS NUMERIC) <= ?) AND (document->>'item' IN (?, ?, ?, ?))", + sql); + + Params params = postgresQueryParser.getParamsBuilder().build(); + Assertions.assertEquals(5, params.getObjectParams().size()); + Assertions.assertEquals(10, params.getObjectParams().get(1)); + Assertions.assertEquals("Mirror", params.getObjectParams().get(2)); + Assertions.assertEquals("Comb", params.getObjectParams().get(3)); + Assertions.assertEquals("Shampoo", params.getObjectParams().get(4)); + Assertions.assertEquals("Bottle", params.getObjectParams().get(5)); + } }