diff --git a/elide-core/src/main/java/com/yahoo/elide/core/EntityDictionary.java b/elide-core/src/main/java/com/yahoo/elide/core/EntityDictionary.java index 7d9b956967..1e1785c94d 100644 --- a/elide-core/src/main/java/com/yahoo/elide/core/EntityDictionary.java +++ b/elide-core/src/main/java/com/yahoo/elide/core/EntityDictionary.java @@ -29,6 +29,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.antlr.v4.runtime.tree.ParseTree; import org.apache.commons.lang3.StringUtils; @@ -1422,6 +1423,16 @@ public void addArgumentsToAttributes(Class cls, String attributeName, Set cls, String attributeName, ArgumentType argument) { + this.addArgumentsToAttributes(cls, attributeName, Sets.newHashSet(argument)); + } + /** * Returns the Collection of all attributes of an argument. * @param cls The entity diff --git a/elide-core/src/main/java/com/yahoo/elide/core/PersistentResource.java b/elide-core/src/main/java/com/yahoo/elide/core/PersistentResource.java index 9deca8a35b..457651b473 100644 --- a/elide-core/src/main/java/com/yahoo/elide/core/PersistentResource.java +++ b/elide-core/src/main/java/com/yahoo/elide/core/PersistentResource.java @@ -1104,10 +1104,20 @@ public RelationshipType getRelationshipType(String relation) { * @param attr Attribute name * @return Object value for attribute */ + @Deprecated public Object getAttribute(String attr) { return this.getValueChecked(attr); } + /** + * Get the value for a particular attribute (i.e. non-relational field) + * @param attr the Attribute + * @return Object value for attribute + */ + public Object getAttribute(Attribute attr) { + return this.getValueChecked(attr); + } + /** * Wrapped Entity bean. * @@ -1363,6 +1373,7 @@ protected void nullValue(String fieldName, PersistentResource oldValue) { * @param fieldName the field name * @return value value */ + @Deprecated protected Object getValueChecked(String fieldName) { requestScope.publishLifecycleEvent(this, CRUDEvent.CRUDAction.READ); requestScope.publishLifecycleEvent(this, fieldName, CRUDEvent.CRUDAction.READ, Optional.empty()); @@ -1370,6 +1381,18 @@ protected Object getValueChecked(String fieldName) { return getValue(getObject(), fieldName, requestScope); } + /** + * Gets a value from an entity and checks read permissions. + * @param attribute the attribute to fetch. + * @return value value + */ + protected Object getValueChecked(Attribute attribute) { + requestScope.publishLifecycleEvent(this, CRUDEvent.CRUDAction.READ); + requestScope.publishLifecycleEvent(this, attribute.getName(), CRUDEvent.CRUDAction.READ, Optional.empty()); + checkFieldAwareDeferPermissions(ReadPermission.class, attribute.getName(), (Object) null, (Object) null); + return transaction.getAttribute(getObject(), attribute, requestScope); + } + /** * Retrieve an object without checking read permissions (i.e. value is used internally and not sent to others) * diff --git a/elide-core/src/main/java/com/yahoo/elide/security/FilterExpressionCheck.java b/elide-core/src/main/java/com/yahoo/elide/security/FilterExpressionCheck.java index c64486f797..36dde42a10 100644 --- a/elide-core/src/main/java/com/yahoo/elide/security/FilterExpressionCheck.java +++ b/elide-core/src/main/java/com/yahoo/elide/security/FilterExpressionCheck.java @@ -43,7 +43,6 @@ public final boolean ok(User user) { throw new UnsupportedOperationException(); } - /** * The filter expression is evaluated in memory if it cannot be pushed to the data store by elide for any reason. * diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStore.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStore.java index 99a18561ed..18f15155d6 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStore.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStore.java @@ -5,9 +5,12 @@ */ package com.yahoo.elide.datastores.aggregation; +import com.yahoo.elide.core.ArgumentType; import com.yahoo.elide.core.DataStore; import com.yahoo.elide.core.DataStoreTransaction; import com.yahoo.elide.core.EntityDictionary; +import com.yahoo.elide.datastores.aggregation.schema.Schema; +import com.yahoo.elide.datastores.aggregation.schema.dimension.TimeDimensionColumn; /** * DataStore that supports Aggregation. Uses {@link QueryEngine} to return results. @@ -28,6 +31,14 @@ public AggregationDataStore(QueryEngineFactory queryEngineFactory) { @Override public void populateEntityDictionary(EntityDictionary dictionary) { queryEngine = queryEngineFactory.buildQueryEngine(dictionary); + + /* Add 'grain' argument to each TimeDimensionColumn */ + for (Schema schema: queryEngine.getSchemas()) { + for (TimeDimensionColumn timeDim : schema.getTimeDimensions()) { + dictionary.addArgumentToAttribute(schema.getEntityClass(), timeDim.getName(), + new ArgumentType("grain", String.class)); + } + } } @Override diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreHelper.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreHelper.java index 19d03d76df..d9bab6f9f8 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreHelper.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreHelper.java @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -101,7 +102,6 @@ private void splitFilters() { } } - //TODO - Add tests in the next PR. /** * Gets time dimensions based on relationships and attributes from {@link EntityProjection}. * @@ -129,7 +129,7 @@ private Set resolveTimeDimensions() { String.format("Requested default grain, no grain defined on %s", attribute.getName()))); } else { - String requestedGrainName = timeGrainArgument.getValue().toString(); + String requestedGrainName = timeGrainArgument.getValue().toString().toUpperCase(Locale.ENGLISH); TimeGrain requestedGrain; try { diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/QueryEngine.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/QueryEngine.java index dfabd93838..308c5280cc 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/QueryEngine.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/QueryEngine.java @@ -10,6 +10,8 @@ import com.yahoo.elide.datastores.aggregation.query.Query; import com.yahoo.elide.datastores.aggregation.schema.Schema; +import java.util.List; + /** * A {@link QueryEngine} is an abstraction that an AggregationDataStore leverages to run analytic queries (OLAP style) * against an underlying persistence layer. @@ -68,5 +70,16 @@ public interface QueryEngine { * @param entityClass The class to map to a schema. * @return The schema that represents the provided entity. */ - Schema getSchema(Class entityClass); + default Schema getSchema(Class entityClass) { + return getSchemas().stream() + .filter(schema -> schema.getEntityClass().equals(entityClass)) + .findFirst() + .orElse(null); + } + + /** + * Returns all schemas managed by the engine. + * @return The schemas of the managed entities. + */ + List getSchemas(); } diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngine.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngine.java index feb071514d..aeffba11d1 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngine.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngine.java @@ -23,6 +23,7 @@ import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.JoinTo; import com.yahoo.elide.datastores.aggregation.queryengines.sql.schema.SQLDimensionColumn; import com.yahoo.elide.datastores.aggregation.queryengines.sql.schema.SQLSchema; +import com.yahoo.elide.datastores.aggregation.queryengines.sql.schema.SQLTimeDimensionColumn; import com.yahoo.elide.datastores.aggregation.schema.Schema; import com.yahoo.elide.datastores.aggregation.schema.dimension.DimensionColumn; import com.yahoo.elide.datastores.aggregation.schema.metric.Aggregation; @@ -82,6 +83,11 @@ public Schema getSchema(Class entityClass) { return schemas.get(entityClass); } + @Override + public List getSchemas() { + return schemas.values().stream().collect(Collectors.toList()); + } + @Override public Iterable executeQuery(Query query) { EntityManager entityManager = null; @@ -460,8 +466,7 @@ private String extractProjection(Query query) { * @return The SQL GROUP BY clause */ private String extractGroupBy(Query query) { - return "GROUP BY " + extractDimensionProjections(query).stream() - .collect(Collectors.joining(",")); + return "GROUP BY " + extractDimensionProjections(query).stream().collect(Collectors.joining(",")); } /** @@ -470,11 +475,21 @@ private String extractGroupBy(Query query) { * @return */ private List extractDimensionProjections(Query query) { - return query.getDimensions().stream() + List dimensionStrings = query.getGroupDimensions().stream() .map(requestedDim -> query.getSchema().getDimension(requestedDim.getName())) .map((SQLDimensionColumn.class::cast)) .map(SQLDimensionColumn::getColumnReference) .collect(Collectors.toList()); + + dimensionStrings.addAll(query.getTimeDimensions().stream() + .map(requestedDim -> { + SQLTimeDimensionColumn timeDim = (SQLTimeDimensionColumn) + query.getSchema().getTimeDimension(requestedDim.getName()); + + return timeDim.getColumnReference(requestedDim.getTimeGrain().grain()); + }).collect(Collectors.toList())); + + return dimensionStrings; } /** diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLSchema.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLSchema.java index 1d3acbfc05..b1aa5270ad 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLSchema.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLSchema.java @@ -20,7 +20,6 @@ import com.yahoo.elide.datastores.aggregation.schema.metric.Metric; import org.hibernate.annotations.Subselect; - import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLTimeDimensionColumn.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLTimeDimensionColumn.java index 92c53ba80f..375cf0874c 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLTimeDimensionColumn.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/schema/SQLTimeDimensionColumn.java @@ -9,6 +9,7 @@ import com.yahoo.elide.core.Path; import com.yahoo.elide.datastores.aggregation.annotation.TimeGrainDefinition; import com.yahoo.elide.datastores.aggregation.schema.dimension.TimeDimensionColumn; +import com.yahoo.elide.datastores.aggregation.time.TimeGrain; import java.util.Set; import java.util.TimeZone; @@ -20,9 +21,10 @@ public class SQLTimeDimensionColumn extends SQLDimensionColumn implements TimeDimensionColumn { /** * Constructor. - * @param dimension a wrapped dimension. + * + * @param dimension a wrapped dimension. * @param columnAlias The column alias in SQL to refer to this dimension. - * @param tableAlias The table alias in SQL where this dimension lives. + * @param tableAlias The table alias in SQL where this dimension lives. */ public SQLTimeDimensionColumn(TimeDimensionColumn dimension, String columnAlias, String tableAlias) { super(dimension, columnAlias, tableAlias); @@ -30,11 +32,12 @@ public SQLTimeDimensionColumn(TimeDimensionColumn dimension, String columnAlias, /** * Constructor. - * @param dimension a wrapped dimension. + * + * @param dimension a wrapped dimension. * @param columnAlias The column alias in SQL to refer to this dimension. - * @param tableAlias The table alias in SQL where this dimension lives. - * @param joinPath A '.' separated path through the entity relationship graph that describes - * how to join the time dimension into the current AnalyticView. + * @param tableAlias The table alias in SQL where this dimension lives. + * @param joinPath A '.' separated path through the entity relationship graph that describes + * how to join the time dimension into the current AnalyticView. */ public SQLTimeDimensionColumn(TimeDimensionColumn dimension, String columnAlias, String tableAlias, Path joinPath) { super(dimension, columnAlias, tableAlias, joinPath); @@ -50,4 +53,20 @@ public TimeZone getTimeZone() { public Set getSupportedGrains() { return ((TimeDimensionColumn) wrapped).getSupportedGrains(); } + + /** + * Returns a String that identifies this dimension in a SQL query. + * @param requestedGrain The requested time grain. + * + * @return Something like "table_alias.column_name" + */ + public String getColumnReference(TimeGrain requestedGrain) { + TimeGrainDefinition definition = getSupportedGrains().stream() + .filter(grainDef -> grainDef.grain().equals(requestedGrain)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Requested time grain not supported.")); + + //TODO - We will likely migrate to a templating language when we support parameterized metrics. + return String.format(definition.expression(), getColumnReference()); + } } diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/schema/Schema.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/schema/Schema.java index 4965221582..d1641bffa0 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/schema/Schema.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/schema/Schema.java @@ -124,6 +124,17 @@ public TimeDimensionColumn getTimeDimension(String dimensionName) { return null; } + /** + * Returns the complete list of time dimensions. + * @return the complete list of time dimensions. + */ + public List getTimeDimensions() { + return dimensions.values().stream() + .filter(dim -> dim instanceof TimeDimensionColumn) + .map(TimeDimensionColumn.class::cast) + .collect(Collectors.toList()); + } + /** * Finds the {@link Metric} by name. * diff --git a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreIntegrationTest.java b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreIntegrationTest.java index ef938fe2ae..3afefb931a 100644 --- a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreIntegrationTest.java +++ b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/AggregationDataStoreIntegrationTest.java @@ -477,6 +477,36 @@ public void aggregationComputedMetricTest() throws Exception { runQueryWithExpectedResult(graphQLRequest, expected); } + @Test + public void timeGrainAggregationTest() throws Exception { + String graphQLRequest = document( + selection( + field( + "playerStats", + selections( + field("highScore"), + field("recordedDate", arguments( + argument("grain", "\"month\"") + )) + ) + ) + ) + ).toQuery(); + + String expected = document( + selections( + field( + "playerStats", + selections( + field("highScore", 2412), + field("recordedDate", "2019-07-01T00:00Z") + ) + ) + )).toResponse(); + + runQueryWithExpectedResult(graphQLRequest, expected); + } + private void create(String query, Map variables) throws IOException { runQuery(toJsonQuery(query, variables)); } diff --git a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/PlayerStats.java b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/PlayerStats.java index 189a0111a5..27b9624033 100644 --- a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/PlayerStats.java +++ b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/PlayerStats.java @@ -41,6 +41,9 @@ @FromTable(name = "playerStats") public class PlayerStats { + public static final String DAY_FORMAT = "PARSEDATETIME(FORMATDATETIME(%s, 'yyyy-MM-dd'), 'yyyy-MM-dd')"; + public static final String MONTH_FORMAT = "PARSEDATETIME(FORMATDATETIME(%s, 'yyyy-MM-01'), 'yyyy-MM-dd')"; + /** * PK. */ @@ -161,7 +164,10 @@ public void setPlayer(final Player player) { * {@link EntityDimensionTest#testCardinalityScan()}. * @return the date of the player session. */ - @Temporal(grains = { @TimeGrainDefinition(grain = TimeGrain.DAY, expression = "") }, timeZone = "UTC") + @Temporal(grains = { + @TimeGrainDefinition(grain = TimeGrain.DAY, expression = DAY_FORMAT), + @TimeGrainDefinition(grain = TimeGrain.MONTH, expression = MONTH_FORMAT) + }, timeZone = "UTC") public Date getRecordedDate() { return recordedDate; } diff --git a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/SubCountry.java b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/SubCountry.java index a009fd3911..f6c14c611e 100644 --- a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/SubCountry.java +++ b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/example/SubCountry.java @@ -18,7 +18,7 @@ import javax.persistence.Id; /** - * A root level entity for testing AggregationDataStore with @Subselect annotation + * A root level entity for testing AggregationDataStore with @Subselect annotation. */ @Data @Entity diff --git a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngineTest.java b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngineTest.java index b8dad3dbc6..ea42244337 100644 --- a/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngineTest.java +++ b/elide-datastore/elide-datastore-aggregation/src/test/java/com/yahoo/elide/datastores/aggregation/queryengines/sql/SQLQueryEngineTest.java @@ -10,6 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import com.yahoo.elide.core.EntityDictionary; +import com.yahoo.elide.core.Path; +import com.yahoo.elide.core.filter.FilterPredicate; +import com.yahoo.elide.core.filter.Operator; import com.yahoo.elide.core.filter.dialect.RSQLFilterDialect; import com.yahoo.elide.core.pagination.Pagination; import com.yahoo.elide.core.sort.Sorting; @@ -27,6 +30,7 @@ import com.yahoo.elide.datastores.aggregation.schema.metric.Sum; import com.yahoo.elide.datastores.aggregation.time.TimeGrain; +import com.google.common.collect.Lists; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -647,6 +651,62 @@ public void testJoinToSort() throws Exception { assertEquals(stats3, results.get(2)); } + /** + * Test month grain query. + */ + @Test + public void testTotalScoreByMonth() { + QueryEngine engine = new SQLQueryEngine(emf, dictionary); + + Query query = Query.builder() + .schema(playerStatsSchema) + .metric(playerStatsSchema.getMetric("highScore"), Sum.class) + .timeDimension(toTimeDimension(playerStatsSchema, TimeGrain.MONTH, "recordedDate")) + .build(); + + List results = StreamSupport.stream(engine.executeQuery(query).spliterator(), false) + .collect(Collectors.toList()); + + PlayerStats stats0 = new PlayerStats(); + stats0.setId("0"); + stats0.setHighScore(4646); + stats0.setRecordedDate(Timestamp.valueOf("2019-07-01 00:00:00")); + + assertEquals(1, results.size()); + assertEquals(stats0, results.get(0)); + } + + /** + * Test filter by time dimension. + */ + @Test + public void testFilterByTemporalDimension() throws Exception { + QueryEngine engine = new SQLQueryEngine(emf, dictionary); + + FilterPredicate predicate = new FilterPredicate( + new Path(PlayerStats.class, dictionary, "recordedDate"), + Operator.IN, + Lists.newArrayList(Timestamp.valueOf("2019-07-11 00:00:00"))); + + Query query = Query.builder() + .schema(playerStatsSchema) + .metric(playerStatsSchema.getMetric("highScore"), Sum.class) + .timeDimension(toTimeDimension(playerStatsSchema, TimeGrain.DAY, "recordedDate")) + .whereFilter(predicate) + .build(); + + List results = StreamSupport.stream(engine.executeQuery(query).spliterator(), false) + .collect(Collectors.toList()); + + PlayerStats stats0 = new PlayerStats(); + stats0.setId("0"); + stats0.setHighScore(2412); + stats0.setRecordedDate(Timestamp.valueOf("2019-07-11 00:00:00")); + + assertEquals(1, results.size()); + assertEquals(stats0, results.get(0)); + } + //TODO - Add Invalid Request Tests /** diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/containers/NodeContainer.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/containers/NodeContainer.java index 50a1bd7f14..f246851e54 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/containers/NodeContainer.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/containers/NodeContainer.java @@ -10,6 +10,7 @@ import com.yahoo.elide.graphql.DeferredId; import com.yahoo.elide.graphql.Environment; import com.yahoo.elide.graphql.PersistentResourceFetcher; +import com.yahoo.elide.request.Attribute; import com.yahoo.elide.request.Relationship; import lombok.AllArgsConstructor; @@ -36,7 +37,9 @@ public Object processFetch(Environment context, PersistentResourceFetcher fetche String idFieldName = dictionary.getIdFieldName(parentClass); if (dictionary.isAttribute(parentClass, fieldName)) { /* fetch attribute properties */ - Object attribute = context.parentResource.getAttribute(fieldName); + Attribute requested = context.requestScope.getProjectionInfo() + .getAttributeMap().getOrDefault(context.field.getSourceLocation(), null); + Object attribute = context.parentResource.getAttribute(requested); if (attribute instanceof Map) { return ((Map) attribute).entrySet().stream() .map(MapEntryContainer::new) diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLEntityProjectionMaker.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLEntityProjectionMaker.java index e5a1fc9f82..e3bc2ea05c 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLEntityProjectionMaker.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLEntityProjectionMaker.java @@ -69,6 +69,7 @@ public class GraphQLEntityProjectionMaker { private final Map relationshipMap = new HashMap<>(); private final Map rootProjections = new HashMap<>(); + private final Map attributeMap = new HashMap<>(); /** * Constructor. @@ -134,7 +135,7 @@ public GraphQLProjectionInfo make(String query) { } }); - return new GraphQLProjectionInfo(rootProjections, relationshipMap); + return new GraphQLProjectionInfo(rootProjections, relationshipMap, attributeMap); } /** @@ -351,6 +352,7 @@ private void addAttributeField(Field attributeField, EntityProjectionBuilder pro .build(); projectionBuilder.attribute(attribute); + attributeMap.put(attributeField.getSourceLocation(), attribute); } else { throw new InvalidEntityBodyException(String.format( "Unknown attribute field {%s.%s}.", diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLProjectionInfo.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLProjectionInfo.java index af99e2671e..b067687068 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLProjectionInfo.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/parser/GraphQLProjectionInfo.java @@ -6,6 +6,7 @@ package com.yahoo.elide.graphql.parser; +import com.yahoo.elide.request.Attribute; import com.yahoo.elide.request.EntityProjection; import com.yahoo.elide.request.Relationship; import graphql.language.SourceLocation; @@ -23,4 +24,6 @@ public class GraphQLProjectionInfo { @Getter private final Map projections; @Getter private final Map relationshipMap; + + @Getter private final Map attributeMap; } diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/StandardTestBinder.java b/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/StandardTestBinder.java index 993f3a2f95..b1747e4366 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/StandardTestBinder.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/StandardTestBinder.java @@ -24,6 +24,7 @@ import org.glassfish.hk2.utilities.binding.AbstractBinder; import java.util.Arrays; +import java.util.Calendar; /** * Typical-use test binder for integration test resource configs. @@ -59,6 +60,7 @@ public Elide provide() { .withJoinFilterDialect(multipleFilterStrategy) .withSubqueryFilterDialect(multipleFilterStrategy) .withEntityDictionary(dictionary) + .withISO8601Dates("yyyy-MM-dd'T'HH:mm'Z'", Calendar.getInstance().getTimeZone()) .build()); }