Skip to content

Commit

Permalink
Resolution using Table Context (#2004)
Browse files Browse the repository at this point in the history
* Resolution using Table Context

* Handling JPA annotated columns

* Minor Change

* Add comments

* Do not stor joins in TableContext

* Move parseArguments functions

* Pass column arguments for non-sql helper references also

* Change method signature

* Do not store column definition in table context

* Remove context from Query

* Review Comments

* Clean code

* Review Comments

* Remove extra code

* Remove extra code

* Add comments

* CheckStyle Fix

Co-authored-by: Rishi Agarwal <[email protected]>
  • Loading branch information
rishi-aga and Rishi Agarwal authored Apr 22, 2021
1 parent 7a901ac commit 89e96b0
Show file tree
Hide file tree
Showing 22 changed files with 676 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package com.yahoo.elide.core.filter.dialect;

import static com.yahoo.elide.core.dictionary.EntityDictionary.REGULAR_ID_NAME;
import static com.yahoo.elide.core.request.Argument.ARGUMENTS_PATTERN;
import static com.yahoo.elide.core.request.Argument.getArgumentsFromString;
import static com.yahoo.elide.core.type.ClassType.COLLECTION_TYPE;
import static com.yahoo.elide.core.type.ClassType.NUMBER_TYPE;
import static com.yahoo.elide.core.type.ClassType.STRING_TYPE;
Expand Down Expand Up @@ -36,7 +38,6 @@
import com.yahoo.elide.jsonapi.parser.JsonApiParser;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import cz.jirutka.rsql.parser.RSQLParser;
import cz.jirutka.rsql.parser.RSQLParserException;
Expand All @@ -49,8 +50,6 @@
import cz.jirutka.rsql.parser.ast.RSQLVisitor;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -70,12 +69,9 @@ public class RSQLFilterDialect implements FilterDialect, SubqueryFilterDialect,
private static final String SINGLE_PARAMETER_ONLY = "There can only be a single filter query parameter";
private static final String INVALID_QUERY_PARAMETER = "Invalid query parameter: ";
private static final Pattern TYPED_FILTER_PATTERN = Pattern.compile("filter\\[([^\\]]+)\\]");
// square brackets having non-empty argument name and encoded agument value separated by ':'
// eg: [grain:month] , [foo:bar][blah:Encoded+Value]
private static final Pattern FILTER_ARGUMENTS_PATTERN = Pattern.compile("\\[(\\w+):([^\\]]+)\\]");
// field name followed by zero or more filter arguments
// eg: name, orderDate[grain:month] , title[foo:bar][blah:Encoded+Value]
private static final String FILTER_SELECTOR_REGEX = "(\\w+)(" + FILTER_ARGUMENTS_PATTERN + ")*$";
private static final String FILTER_SELECTOR_REGEX = "(\\w+)(" + ARGUMENTS_PATTERN + ")*$";
private static final ComparisonOperator INI = new ComparisonOperator("=ini=", true);
private static final ComparisonOperator NOT_INI = new ComparisonOperator("=outi=", true);
private static final ComparisonOperator ISNULL_OP = new ComparisonOperator("=isnull=", false);
Expand Down Expand Up @@ -336,18 +332,21 @@ private Path buildPath(Type rootEntityType, String selector) {
associationName = dictionary.getIdFieldName(entityType);
}

Set<Argument> arguments = new HashSet<>();
Set<Argument> arguments;
int argsIndex = associationName.indexOf('[');
if (argsIndex > 0) {
try {
parseArguments(associationName.substring(argsIndex), arguments);
arguments = getArgumentsFromString(associationName.substring(argsIndex));
} catch (UnsupportedEncodingException | IllegalArgumentException e) {
throw new RSQLParseException(
String.format("Filter expression is not in expected format at: %s. %s",
associationName, e.getMessage()));
}
associationName = associationName.substring(0, argsIndex);
} else {
arguments = new HashSet<>();
}

addDefaultArguments(arguments, dictionary.getAttributeArguments(entityType, associationName));
String typeName = dictionary.getJsonAliasFor(entityType);
Type fieldType = dictionary.getParameterizedType(entityType, associationName);
Expand All @@ -364,20 +363,6 @@ private Path buildPath(Type rootEntityType, String selector) {
return new Path(path);
}

private void parseArguments(String argsString, Set<Argument> arguments) throws UnsupportedEncodingException {
if (StringUtils.isEmpty(argsString)) {
return;
}

Matcher matcher = FILTER_ARGUMENTS_PATTERN.matcher(argsString);
while (matcher.find()) {
arguments.add(Argument.builder()
.name(matcher.group(1))
.value(URLDecoder.decode(matcher.group(2), StandardCharsets.UTF_8.name()))
.build());
}
}

private void addDefaultArguments(Set<Argument> clientArguments, Set<ArgumentType> availableArgTypes) {

Set<String> clientArgNames = clientArguments.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,34 @@

package com.yahoo.elide.core.request;

import static org.apache.commons.lang3.StringUtils.isEmpty;

import lombok.Builder;
import lombok.NonNull;
import lombok.Value;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* Represents an argument passed to an attribute.
*/
@Value
@Builder
public class Argument {

// square brackets having non-empty argument name and encoded agument value separated by ':'
// eg: [grain:month] , [foo:bar][blah:Encoded+Value]
public static final Pattern ARGUMENTS_PATTERN = Pattern.compile("\\[(\\w+):([^\\]]+)\\]");

@NonNull
String name;

Expand All @@ -29,4 +46,50 @@ public class Argument {
public Class<?> getType() {
return value.getClass();
}

/**
* Parses input string and returns a set of {@link Argument}.
*
* @param argsString String to parse for arguments.
* @return A Set of {@link Argument}.
* @throws UnsupportedEncodingException
*/
public static Set<Argument> getArgumentsFromString(String argsString) throws UnsupportedEncodingException {
Set<Argument> arguments = new HashSet<Argument>();

if (!isEmpty(argsString)) {

Matcher matcher = ARGUMENTS_PATTERN.matcher(argsString);
while (matcher.find()) {
arguments.add(Argument.builder()
.name(matcher.group(1))
.value(URLDecoder.decode(matcher.group(2), StandardCharsets.UTF_8.name()))
.build());
}
}

return arguments;
}

/**
* Converts Set of {@link Argument} into Map.
* @param arguments Set of {@link Argument}.
* @return a Map of {@link Argument}.
*/
public static Map<String, Argument> getArgumentMapFromArgumentSet(Set<Argument> arguments) {
return arguments.stream()
.collect(Collectors.toMap(Argument::getName, Function.identity()));
}

/**
* Parses input string and returns a Map of {@link Argument}.
*
* @param argsString String to parse for arguments.
* @return a Map of {@link Argument}.
* @throws UnsupportedEncodingException
*/
public static Map<String, Argument> getArgumentMapFromString(String argsString)
throws UnsupportedEncodingException {
return getArgumentMapFromArgumentSet(getArgumentsFromString(argsString));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.yahoo.elide.core.type.ClassType;
import com.yahoo.elide.core.type.Dynamic;
import com.yahoo.elide.core.type.Type;

import com.google.common.collect.Sets;

import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ Query buildQuery(EntityProjection entityProjection, RequestScope scope) {
queryEngine,
table,
entityProjection,
scope.getDictionary(),
scope.getUser(),
scope,
bypassCache);

Query query = translator.getQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
*/
package com.yahoo.elide.datastores.aggregation;

import static com.yahoo.elide.core.request.Argument.getArgumentMapFromArgumentSet;

import com.yahoo.elide.core.RequestScope;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.exceptions.InvalidOperationException;
import com.yahoo.elide.core.filter.expression.FilterExpression;
import com.yahoo.elide.core.filter.expression.PredicateExtractionVisitor;
import com.yahoo.elide.core.request.Argument;
import com.yahoo.elide.core.request.Attribute;
import com.yahoo.elide.core.request.EntityProjection;
import com.yahoo.elide.core.request.Relationship;
import com.yahoo.elide.core.security.User;
import com.yahoo.elide.datastores.aggregation.filter.visitor.FilterConstraints;
import com.yahoo.elide.datastores.aggregation.filter.visitor.SplitFilterExpressionVisitor;
import com.yahoo.elide.datastores.aggregation.metadata.models.Dimension;
Expand All @@ -26,16 +27,12 @@
import com.yahoo.elide.datastores.aggregation.query.TimeDimensionProjection;
import com.google.common.collect.Sets;

import lombok.NonNull;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
Expand All @@ -52,19 +49,18 @@ public class EntityProjectionTranslator {
private FilterExpression whereFilter;
private FilterExpression havingFilter;
private EntityDictionary dictionary;
private User user;
private Boolean bypassCache;

private RequestScope scope;

public EntityProjectionTranslator(QueryEngine engine, Table table,
EntityProjection entityProjection, EntityDictionary dictionary,
User user, Boolean bypassCache) {
EntityProjection entityProjection, RequestScope scope,
Boolean bypassCache) {
this.engine = engine;
this.queriedTable = table;
this.entityProjection = entityProjection;
this.dictionary = dictionary;
this.user = user;
this.dictionary = scope.getDictionary();
this.bypassCache = bypassCache;
this.scope = scope;
dimensionProjections = resolveNonTimeDimensions();
timeDimensions = resolveTimeDimensions();
metrics = resolveMetrics();
Expand All @@ -86,8 +82,9 @@ public Query getQuery() {
.havingFilter(havingFilter)
.sorting(entityProjection.getSorting())
.pagination(ImmutablePagination.from(entityProjection.getPagination()))
.context(prepareQueryContext())
.arguments(getArgumentMapFromArgumentSet(entityProjection.getArguments()))
.bypassingCache(bypassCache)
.scope(scope)
.build();

QueryValidator validator = new QueryValidator(query, getAllFields(), dictionary);
Expand Down Expand Up @@ -155,8 +152,7 @@ private Set<TimeDimensionProjection> resolveTimeDimensions() {
return engine.constructTimeDimensionProjection(
timeDim,
timeDimAttr.getAlias(),
timeDimAttr.getArguments().stream()
.collect(Collectors.toMap(Argument::getName, Function.identity())));
getArgumentMapFromArgumentSet(timeDimAttr.getArguments()));
})
.collect(Collectors.toCollection(LinkedHashSet::new));
}
Expand All @@ -174,8 +170,7 @@ private Set<ColumnProjection> resolveNonTimeDimensions() {
: engine.constructDimensionProjection(
dimension,
dimAttr.getAlias(),
dimAttr.getArguments().stream()
.collect(Collectors.toMap(Argument::getName, Function.identity())));
getArgumentMapFromArgumentSet(dimAttr.getArguments()));
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Expand Down Expand Up @@ -205,8 +200,7 @@ private List<MetricProjection> resolveMetrics() {
.map(attribute -> engine.constructMetricProjection(
queriedTable.getMetric(attribute.getName()),
attribute.getAlias(),
attribute.getArguments().stream()
.collect(Collectors.toMap(Argument::getName, Function.identity()))))
getArgumentMapFromArgumentSet(attribute.getArguments())))
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -237,45 +231,4 @@ private Set<String> getAllFields() {
allFields.addAll(getRelationships());
return allFields;
}

private Map<String, Object> prepareQueryContext() {
Map<String, Object> context = new HashMap<>();
populateUserContext(context);
populateRequestContext(context);
return context;
}

private void populateUserContext(Map<String, Object> context) {
Map<String, Object> userMap = new HashMap<>();
context.put("$$user", userMap);
userMap.put("identity", user.getName());
}

private void populateRequestContext(Map<String, Object> context) {

Map<String, Object> requestMap = new HashMap<>();
context.put("$$request", requestMap);
Map<String, Object> tableMap = new HashMap<>();
requestMap.put("table", tableMap);
Map<String, Object> columnsMap = new HashMap<>();
requestMap.put("columns", columnsMap);

// Populate $$request.table context
tableMap.put("name", queriedTable.getName());
tableMap.put("args", entityProjection.getArguments().stream()
.collect(Collectors.toMap(Argument::getName, Argument::getValue)));

// Populate $$request.columns context
entityProjection.getAttributes().forEach(attr -> {
@NonNull
String columnName = attr.getName();
Map<String, Object> columnMap = new HashMap<>();
columnsMap.put(columnName, columnMap);

// Populate $$request.columns.column context
columnMap.put("name", attr.getName());
columnMap.put("args", attr.getArguments().stream()
.collect(Collectors.toMap(Argument::getName, Argument::getValue)));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ public static List<String> resolveFormulaReferences(String formula) {
List<String> references = new ArrayList<>();

while (matcher.find()) {
references.add(matcher.group(1));
String value = matcher.group(1);
if (!value.startsWith("$$") && !value.startsWith("sql ")) {
references.add(value);
}
}

return references;
Expand Down
Loading

0 comments on commit 89e96b0

Please sign in to comment.