Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elide 5 Agg Store: Multiple time grains #1806

Merged
merged 32 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8241b12
Prototype code to add multiple time grains back
Jan 27, 2021
e51f07b
Revised test HJSON files
Jan 27, 2021
5deb0ef
Fixed a bunch of bugs
Jan 27, 2021
4ab2102
Revamped all analytic time types
Jan 28, 2021
557c11e
Month Serde tests pass
Jan 28, 2021
e79cd27
Added time serde tests
Jan 28, 2021
181d939
YearSerde tests pass
Jan 28, 2021
47f3020
QuarterSerde tests pass
Jan 28, 2021
bc1b691
Second, Minute, Hour, and Day serdes now pass
Jan 28, 2021
e70bf7a
Week and ISOWeek serde tests pass
Jan 28, 2021
27f7b3d
Fixed bug in grain validation logic
Jan 28, 2021
0e5c565
Fixed more bugs
Jan 29, 2021
ffeaaca
Fixed most aggregation store tests
Jan 29, 2021
67a8fb7
Fixed last bug in Aggregation store
Jan 29, 2021
7e722a6
Build complete
Jan 29, 2021
579d12d
Added support for conversions to java.sql.Date
Jan 29, 2021
6b761bc
Added one multi-grain time dimension model and test
Feb 2, 2021
80c1468
Added multiple time grain fetch unit test
Feb 2, 2021
94ffaae
Added failing filter by alias tests
Feb 2, 2021
e96431e
All tests pass but one
Feb 2, 2021
7090a0c
Filter by month alias test working
Feb 2, 2021
885d660
Added sort by alias test
Feb 2, 2021
686290d
Added new IT test for parameterized time dimensions
Feb 2, 2021
4e0d10c
Added graphql test DSL unit test
Feb 2, 2021
fa766c0
Added dynamic IT tests with multiple time grains
Feb 2, 2021
0fe92ee
Added tests for ParameterizedModel
Feb 3, 2021
04c146e
Added test for invalid grain
Feb 3, 2021
e983c6e
Added GraphQL entity projection maker unit test
Feb 3, 2021
d640b5b
Added unit tests for entity hydration
Feb 3, 2021
2d02fe4
Inspection rework
Feb 4, 2021
1ee6a1b
More inspection rework
Feb 4, 2021
6fffeec
Merge branch 'master' into MultipleTimeGrains
aklish Feb 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@

import com.yahoo.elide.core.type.Type;

import lombok.Getter;
import lombok.Value;

/**
* Argument Type wraps an argument to the type of value it accepts.
*/
@Value
public class ArgumentType {
@Getter
private String name;
@Getter
private Type<?> type;
private Object defaultValue;

public ArgumentType(String name, Type<?> type) {
this(name, type, null);
}

public ArgumentType(String name, Type<?> type, Object defaultValue) {
this.name = name;
this.type = type;
this.defaultValue = defaultValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
package com.yahoo.elide.core.exceptions;

import com.yahoo.elide.core.request.Argument;
import com.yahoo.elide.core.request.Attribute;

/**
Expand All @@ -15,4 +16,9 @@ public InvalidParameterizedAttributeException(Attribute attribute) {
super(HttpStatus.SC_BAD_REQUEST, "No attribute found with matching parameters for attribute: "
+ attribute.toString());
}

public InvalidParameterizedAttributeException(String attributeName, Argument argument) {
super(HttpStatus.SC_BAD_REQUEST, String.format("Invalid argument : %s for attribute: %s",
argument, attributeName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class FilterPredicate implements FilterExpression, Function<RequestScope,
@Getter @NonNull private List<Object> values;
@Getter @NonNull private String field;
@Getter @NonNull private String fieldPath;
@Getter @NonNull private Type fieldType;

public static boolean toManyInPath(EntityDictionary dictionary, Path path) {
return path.getPathElements().stream()
Expand Down Expand Up @@ -85,6 +86,9 @@ public FilterPredicate(Path path, Operator op, List<Object> values) {
this.fieldPath = path.getPathElements().stream()
.map(PathElement::getFieldName)
.collect(Collectors.joining(PERIOD));
this.fieldType = path.lastElement()
.map(PathElement::getType)
.orElse(null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@

package com.yahoo.elide.core.type;

import com.yahoo.elide.annotation.Exclude;
import com.yahoo.elide.core.exceptions.InvalidParameterizedAttributeException;
import com.yahoo.elide.core.request.Argument;
import com.yahoo.elide.core.request.Attribute;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
* Base class that contains one or more parameterized attributes.
*/
public abstract class ParameterizedModel {
private Map<Attribute, ParameterizedAttribute> parameterizedAttributes;

@Exclude
protected Map<Attribute, ParameterizedAttribute> parameterizedAttributes;

public ParameterizedModel() {
this(new HashMap<>());
Expand All @@ -45,9 +50,37 @@ public <T> T invoke(Set<Argument> arguments) {
* @return The attribute value.
*/
public <T> T invoke(Attribute attribute) {
if (! parameterizedAttributes.containsKey(attribute)) {
Optional<Attribute> match = parameterizedAttributes.keySet().stream()

//Only filter by alias required. (Filtering by type may not work with inheritance).
.filter((modelAttribute) -> attribute.getAlias().equals(modelAttribute.getAlias()))
.findFirst();

if (! match.isPresent()) {
throw new InvalidParameterizedAttributeException(attribute);
}
return parameterizedAttributes.get(attribute).invoke(attribute.getArguments());

return parameterizedAttributes.get(match.get()).invoke(attribute.getArguments());
}

/**
* Fetch the attribute value by name.
* @param alias The field name to fetch.
* @param defaultValue Returned if the field name is not found
* @param <T> The return type of the attribute.
* @return The attribute value or the provided default value.
*/
public <T> T fetch(String alias, T defaultValue) {
Optional<Attribute> match = parameterizedAttributes.keySet().stream()

//Only filter by alias required. (Filtering by type may not work with inheritance).
.filter((modelAttribute) -> alias.equals(modelAttribute.getAlias()))
.findFirst();

if (! match.isPresent()) {
return defaultValue;
}

return parameterizedAttributes.get(match.get()).invoke(new HashSet<>());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2020, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/

package com.yahoo.elide.core.type;

import static com.yahoo.elide.core.type.ClassType.STRING_TYPE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.spy;
import com.yahoo.elide.core.exceptions.InvalidParameterizedAttributeException;
import com.yahoo.elide.core.request.Attribute;
import org.junit.jupiter.api.Test;

public class ParameterizedModelTest {

@Test
public void testInvoke() {
ParameterizedModel testModel = spy(ParameterizedModel.class);
Attribute testAttribute = Attribute.builder().type(STRING_TYPE).name("foo").build();
String testValue = "bar";

testModel.addAttributeValue(testAttribute, testValue);

assertEquals(testValue, testModel.invoke(testAttribute));
}

@Test
public void testInvokeException() {
ParameterizedModel testModel = spy(ParameterizedModel.class);
Attribute testAttribute = Attribute.builder().type(STRING_TYPE).name("foo").build();

Exception exception = assertThrows(InvalidParameterizedAttributeException.class,
() -> testModel.invoke(testAttribute));

assertEquals("No attribute found with matching parameters for attribute: Attribute(name=foo)",
exception.getMessage());
}

@Test
public void testFetch() {
ParameterizedModel testModel = spy(ParameterizedModel.class);
Attribute testAttribute = Attribute.builder().type(STRING_TYPE).name("foo").build();
String testValue = "bar";

testModel.addAttributeValue(testAttribute, testValue);

assertEquals(testValue, testModel.fetch(testAttribute.getAlias(), "blah"));
}

@Test
public void testFetchDefault() {
ParameterizedModel testModel = spy(ParameterizedModel.class);
Attribute testAttribute = Attribute.builder().type(STRING_TYPE).name("foo").build();
String testValue = "blah";

assertEquals(testValue, testModel.fetch(testAttribute.getAlias(), testValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@

import com.yahoo.elide.core.datastore.DataStore;
import com.yahoo.elide.core.datastore.DataStoreTransaction;
import com.yahoo.elide.core.dictionary.ArgumentType;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.type.ClassType;
import com.yahoo.elide.core.type.Type;
import com.yahoo.elide.core.utils.ClassScanner;
import com.yahoo.elide.datastores.aggregation.annotation.Join;
import com.yahoo.elide.datastores.aggregation.cache.Cache;
import com.yahoo.elide.datastores.aggregation.core.QueryLogger;
import com.yahoo.elide.datastores.aggregation.metadata.models.Table;
import com.yahoo.elide.datastores.aggregation.metadata.models.TimeDimension;
import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromSubquery;
import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromTable;
import lombok.Builder;
Expand Down Expand Up @@ -57,6 +61,16 @@ public void populateEntityDictionary(EntityDictionary dictionary) {
ClassScanner.getAnnotatedClasses(AGGREGATION_STORE_CLASSES).forEach(
cls -> dictionary.bindEntity(cls, Collections.singleton(Join.class))
);

/* Add 'grain' argument to each TimeDimensionColumn */
for (Table table : queryEngine.getMetaDataStore().getMetaData(new ClassType<>(Table.class))) {
for (TimeDimension timeDim : table.getTimeDimensions()) {
dictionary.addArgumentToAttribute(
dictionary.getEntityClass(table.getName(), table.getVersion()),
timeDim.getName(),
new ArgumentType("grain", ClassType.STRING_TYPE, timeDim.getDefaultGrain().getGrain()));
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
*
* @return time grain.
*/
TimeGrainDefinition grain() default @TimeGrainDefinition(grain = TimeGrain.DAY, expression = "{{}}");
TimeGrainDefinition[] grains() default { @TimeGrainDefinition(grain = TimeGrain.DAY, expression = "{{}}") };

/**
* The timezone in {@link String} of the column.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ private void addColumn(Column column) {
addMetaData(column, version);

if (column instanceof TimeDimension) {
addTimeDimensionGrain(((TimeDimension) column).getSupportedGrain(), version);
TimeDimension timeDimension = (TimeDimension) column;
for (TimeDimensionGrain grain : timeDimension.getSupportedGrains()) {
addTimeDimensionGrain(grain, version);
}
} else if (column instanceof Metric) {
addMetricFunction(((Metric) column).getMetricFunction(), version);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import com.yahoo.elide.annotation.Include;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.datastores.aggregation.annotation.Temporal;
import com.yahoo.elide.datastores.aggregation.metadata.enums.TimeGrain;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.Value;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.TimeZone;
import java.util.stream.Collectors;
import javax.persistence.ManyToMany;

/**
Expand All @@ -25,7 +29,7 @@
public class TimeDimension extends Column {
@ManyToMany
@ToString.Exclude
TimeDimensionGrain supportedGrain;
LinkedHashSet<TimeDimensionGrain> supportedGrains;

TimeZone timezone;

Expand All @@ -38,6 +42,16 @@ public TimeDimension(Table table, String fieldName, EntityDictionary dictionary)
Temporal.class,
fieldName);

this.supportedGrain = new TimeDimensionGrain(getId(), temporal.grain());
if (temporal.grains().length == 0) {
this.supportedGrains = new LinkedHashSet<>(Arrays.asList(new TimeDimensionGrain(getId(), TimeGrain.DAY)));
} else {
this.supportedGrains = Arrays.stream(temporal.grains())
.map(grain -> new TimeDimensionGrain(getId(), grain))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}

public TimeDimensionGrain getDefaultGrain() {
return supportedGrains.iterator().next();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,20 @@ public class TimeDimensionGrain {
private final String format;

public TimeDimensionGrain(String fieldName, TimeGrainDefinition definition) {
this.id = fieldName + "." + definition.grain().name().toLowerCase(Locale.ENGLISH);
this.id = getId(fieldName, definition.grain());
this.grain = definition.grain();
this.expression = definition.expression();
this.format = definition.grain().getFormat();
}

public TimeDimensionGrain(String fieldName, TimeGrain grain) {
this.id = getId(fieldName, grain);
this.grain = grain;
this.expression = "{{}}";
this.format = grain.getFormat();
}

private static String getId(String fieldName, TimeGrain grain) {
return fieldName + "." + grain.name().toLowerCase(Locale.ENGLISH);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ default String getAlias(String columnName) {

/**
* Retrieves a column by name.
* @param name The name of the column.
* @param name The alias of the column.
* @return The column.
*/
default ColumnProjection getColumnProjection(String name) {
return getColumnProjections().stream()
.filter(dim -> dim.getName().equals(name))
.filter(dim -> dim.getAlias().equals(name))
.findFirst()
.orElse(null);
}
Expand All @@ -79,7 +79,7 @@ default ColumnProjection getColumnProjection(String name) {
*/
default ColumnProjection getDimensionProjection(String name) {
return getDimensionProjections().stream()
.filter(dim -> dim.getName().equals(name))
.filter(dim -> dim.getAlias().equals(name))
.findFirst()
.orElse(null);
}
Expand All @@ -97,7 +97,7 @@ default ColumnProjection getDimensionProjection(String name) {
*/
default MetricProjection getMetricProjection(String name) {
return getMetricProjections().stream()
.filter(metric -> metric.getName().equals(name))
.filter(metric -> metric.getAlias().equals(name))
.findFirst()
.orElse(null);
}
Expand All @@ -115,7 +115,7 @@ default MetricProjection getMetricProjection(String name) {
*/
default TimeDimensionProjection getTimeDimensionProjection(String name) {
return getTimeDimensionProjections().stream()
.filter(dim -> dim.getName().equals(name))
.filter(dim -> dim.getAlias().equals(name))
.findFirst()
.orElse(null);
}
Expand Down
Loading