Skip to content

cherry-pick: manually cherry-pick PR:236 for multi-value support #237

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -29,6 +29,8 @@ public class EntityAttributeMapping {
private static final String NAME_PATH = "name";
private static final Set<AttributeKind> MULTI_VALUED_ATTRIBUTE_KINDS =
Set.of(TYPE_STRING_ARRAY, TYPE_INT64_ARRAY, TYPE_DOUBLE_ARRAY, TYPE_BOOL_ARRAY);
private static final Set<AttributeKind> ARRAY_ATTRIBUTE_KINDS =
Set.of(TYPE_STRING_ARRAY, TYPE_INT64_ARRAY, TYPE_DOUBLE_ARRAY, TYPE_BOOL_ARRAY);

private final CachingAttributeClient attributeClient;
private final Map<String, AttributeMetadataIdentifier> explicitAttributeIdByAttributeMetadata;
Expand Down Expand Up @@ -101,6 +103,15 @@ public Optional<AttributeKind> getAttributeKind(
.blockingGet());
}

public boolean isArray(final RequestContext requestContext, final String columnId) {
final Optional<AttributeKind> attributeKind = getAttributeKind(requestContext, columnId);
return attributeKind.map(this::isArray).orElse(false);
}

public boolean isArray(final AttributeKind attributeKind) {
return ARRAY_ATTRIBUTE_KINDS.contains(attributeKind);
}

public boolean isMultiValued(RequestContext requestContext, String attributeId) {
return requestContext.call(
() ->
Expand Down
2 changes: 1 addition & 1 deletion entity-service-factory/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ dependencies {
implementation(project(":entity-service-attribute-translator"))
implementation(project(":entity-service-impl"))
implementation(project(":entity-service-change-event-generator"))
implementation("org.hypertrace.core.documentstore:document-store:0.7.23")
implementation("org.hypertrace.core.documentstore:document-store:0.7.27")
}
2 changes: 1 addition & 1 deletion entity-service-impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies {
annotationProcessor("org.projectlombok:lombok:1.18.22")
compileOnly("org.projectlombok:lombok:1.18.18")

implementation("org.hypertrace.core.documentstore:document-store:0.7.23")
implementation("org.hypertrace.core.documentstore:document-store:0.7.27")
implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.11.2")
implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.11.2")
implementation("org.hypertrace.core.attribute.service:caching-attribute-service-client:0.14.15")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,7 @@ public SubDocumentValue convertToSubDocumentValue(final Value value) throws Conv
final ValueType type = value.getValueType();

if (isArray(type)) {
final List<Value> values = ARRAY_TO_PRIMITIVE_CONVERTER_MAP.get().get(type).apply(value);
final List<Document> documents = new ArrayList<>();

for (final Value singleValue : values) {
documents.add(convertToDocument(singleValue));
}

final List<Document> documents = getDocumentListFromArrayValue(value);
return SubDocumentValue.of(documents);
}

Expand All @@ -182,6 +176,18 @@ public SubDocumentValue convertToSubDocumentValue(final Value value) throws Conv
throw new ConversionException(String.format("Unsupported value type: %s", type));
}

public List<Document> getDocumentListFromArrayValue(final Value value)
throws ConversionException {
final List<Value> values =
ARRAY_TO_PRIMITIVE_CONVERTER_MAP.get().get(value.getValueType()).apply(value);
final List<Document> documents = new ArrayList<>();

for (final Value singleValue : values) {
documents.add(convertToDocument(singleValue));
}
return documents;
}

public ConstantExpression convertToConstantExpression(final Value value, final int index)
throws ConversionException {
switch (value.getValueType()) {
Expand Down Expand Up @@ -344,7 +350,7 @@ private static Map<String, ValueType> getStringValueToPrimitiveTypeMap() {
return unmodifiableMap(map);
}

private JSONDocument convertToDocument(final Value value) throws ConversionException {
public JSONDocument convertToDocument(final Value value) throws ConversionException {
try {
return new JSONDocument(PRINTER.print(LiteralConstant.newBuilder().setValue(value)));
} catch (final IOException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.hypertrace.entity.query.service.converter.filter;

import static java.util.Map.entry;
import static org.hypertrace.core.documentstore.expression.operators.LogicalOperator.AND;
import static org.hypertrace.core.documentstore.expression.operators.LogicalOperator.OR;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.CONTAINS;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NOT_CONTAINS;
import static org.hypertrace.entity.query.service.converter.identifier.IdentifierConverter.getSubDocPathById;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.experimental.Accessors;
import org.hypertrace.core.documentstore.Document;
import org.hypertrace.core.documentstore.expression.impl.ConstantExpression;
import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression;
import org.hypertrace.core.documentstore.expression.impl.LogicalExpression;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
import org.hypertrace.core.documentstore.expression.operators.LogicalOperator;
import org.hypertrace.core.documentstore.expression.operators.RelationalOperator;
import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression;
import org.hypertrace.core.grpcutils.context.RequestContext;
import org.hypertrace.entity.attribute.translator.EntityAttributeMapping;
import org.hypertrace.entity.query.service.converter.ConversionException;
import org.hypertrace.entity.query.service.converter.ValueHelper;
import org.hypertrace.entity.query.service.converter.identifier.ArrayPathSuffixAddingIdentifierConverter;
import org.hypertrace.entity.query.service.converter.identifier.IdentifierConversionMetadata;
import org.hypertrace.entity.query.service.v1.ColumnIdentifier;
import org.hypertrace.entity.query.service.v1.LiteralConstant;
import org.hypertrace.entity.query.service.v1.Operator;
import org.hypertrace.entity.query.service.v1.Value;
import org.hypertrace.entity.query.service.v1.ValueType;

@Singleton
@AllArgsConstructor(onConstructor_ = {@Inject})
public class ContainsFilteringExpressionConverter extends FilteringExpressionConverterBase {
private static final Map<Operator, OperatorPair> OPERATOR_MAP =
Map.ofEntries(
entry(Operator.IN, OperatorPair.of(CONTAINS, OR)),
entry(Operator.NOT_IN, OperatorPair.of(NOT_CONTAINS, AND)));

private final EntityAttributeMapping entityAttributeMapping;
private final ArrayPathSuffixAddingIdentifierConverter arrayPathSuffixAddingIdentifierConverter;
private final ValueHelper valueHelper;

@Override
public FilterTypeExpression convert(
final ColumnIdentifier columnIdentifier,
final Operator operator,
final LiteralConstant constant,
final RequestContext requestContext)
throws ConversionException {
final String id = columnIdentifier.getColumnName();
final String subDocPath = getSubDocPathById(entityAttributeMapping, id, requestContext);
final Value value = constant.getValue();
final ValueType valueType = value.getValueType();

final List<Document> list = valueHelper.getDocumentListFromArrayValue(value);

if (list.isEmpty()) {
throw new ConversionException("Conversion of empty-list is unsupported");
}

final OperatorPair operatorPair = OPERATOR_MAP.get(operator);
if (operatorPair == null) {
throw new ConversionException(
String.format("Operator %s is not supported on array column %s", operator, id));
}

final IdentifierConversionMetadata metadata =
IdentifierConversionMetadata.builder()
.subDocPath(subDocPath)
.operator(operator)
.valueType(valueType)
.build();
final String suffixedSubDocPath =
arrayPathSuffixAddingIdentifierConverter.convert(metadata, requestContext);
final IdentifierExpression lhs = IdentifierExpression.of(suffixedSubDocPath);

final List<RelationalExpression> expressions = new ArrayList<>();

for (final Document document : list) {
final ConstantExpression rhs = ConstantExpression.of(document);
final RelationalExpression expression =
RelationalExpression.of(lhs, operatorPair.relationalOperator(), rhs);

expressions.add(expression);
}

if (expressions.size() == 1) {
return expressions.get(0);
}

return LogicalExpression.builder()
.operator(operatorPair.logicalOperator())
.operands(expressions)
.build();
}

@lombok.Value
@Accessors(fluent = true)
@AllArgsConstructor(staticName = "of")
private static class OperatorPair {
RelationalOperator relationalOperator;
LogicalOperator logicalOperator;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package org.hypertrace.entity.query.service.converter.filter;

import org.hypertrace.core.grpcutils.context.RequestContext;
import org.hypertrace.entity.query.service.converter.ConversionException;
import org.hypertrace.entity.query.service.v1.ColumnIdentifier;
import org.hypertrace.entity.query.service.v1.Operator;
import org.hypertrace.entity.query.service.v1.Value;

public interface FilteringExpressionConverterFactory {
FilteringExpressionConverter getConverter(final Value value) throws ConversionException;
FilteringExpressionConverter getConverter(
final ColumnIdentifier identifier,
final Operator operator,
final Value value,
final RequestContext context)
throws ConversionException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,51 @@

import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Set;
import lombok.AllArgsConstructor;
import org.hypertrace.core.grpcutils.context.RequestContext;
import org.hypertrace.entity.attribute.translator.EntityAttributeMapping;
import org.hypertrace.entity.query.service.converter.ConversionException;
import org.hypertrace.entity.query.service.converter.ValueHelper;
import org.hypertrace.entity.query.service.v1.ColumnIdentifier;
import org.hypertrace.entity.query.service.v1.Operator;
import org.hypertrace.entity.query.service.v1.Value;
import org.hypertrace.entity.query.service.v1.ValueType;

@Singleton
@AllArgsConstructor(onConstructor_ = {@Inject})
public class FilteringExpressionConverterFactoryImpl
implements FilteringExpressionConverterFactory {
private static final Set<Operator> CONTAINMENT_OPERATORS = Set.of(Operator.IN, Operator.NOT_IN);
private NullFilteringExpressionConverter nullFilteringExpressionConverter;
private PrimitiveFilteringExpressionConverter primitiveFilteringExpressionConverter;
private ArrayFilteringExpressionConverter arrayFilteringExpressionConverter;
private MapFilteringExpressionConverter mapFilteringExpressionConverter;
private ValueHelper valueHelper;
private ContainsFilteringExpressionConverter containsFilteringExpressionConverter;
private EntityAttributeMapping entityAttributeMapping;

@Override
public FilteringExpressionConverter getConverter(final Value value) throws ConversionException {
public FilteringExpressionConverter getConverter(
final ColumnIdentifier identifier,
final Operator operator,
final Value value,
final RequestContext context)
throws ConversionException {
ValueType valueType = value.getValueType();

// should always be first
if (valueHelper.isNull(value)) {
return nullFilteringExpressionConverter;
}

if (CONTAINMENT_OPERATORS.contains(operator)) {
if (entityAttributeMapping.isArray(context, identifier.getColumnName())) {
return containsFilteringExpressionConverter;
}
return primitiveFilteringExpressionConverter;
}

if (valueHelper.isPrimitive(valueType)) {
return primitiveFilteringExpressionConverter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public FilterTypeExpression convert(final Filter filter, final RequestContext re
expressionAccessor.access(rhs, rhs.getValueCase(), Set.of(LITERAL));

final FilteringExpressionConverter filteringExpressionConverter =
filteringExpressionConverterFactory.getConverter(literal.getValue());
filteringExpressionConverterFactory.getConverter(
identifier, operator, literal.getValue(), requestContext);
return filteringExpressionConverter.convert(identifier, operator, literal, requestContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ void setup() throws ConversionException {
doReturn(constantExpression)
.when(constantExpressionConverter)
.convert(literalConstant, requestContext);
when(filteringExpressionConverterFactory.getConverter(any(Value.class)))
when(filteringExpressionConverterFactory.getConverter(
any(ColumnIdentifier.class),
any(Operator.class),
any(Value.class),
any(RequestContext.class)))
.thenReturn(filteringExpressionConverter);
}

Expand Down
2 changes: 1 addition & 1 deletion entity-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies {
implementation(project(":entity-service-factory"))

implementation("org.hypertrace.core.serviceframework:platform-grpc-service-framework:0.1.37")
implementation("org.hypertrace.core.documentstore:document-store:0.7.23")
implementation("org.hypertrace.core.documentstore:document-store:0.7.27")

runtimeOnly("io.grpc:grpc-netty:1.45.1")

Expand Down
Loading