-
Notifications
You must be signed in to change notification settings - Fork 5
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
ENG-48446: Implementation for new Filter condition and filter conversion #228
Changes from all commits
79a409f
10dd780
259560c
eeccbe1
6d141c7
04ad66c
1730bc8
718756f
593d819
acafffd
cdbd390
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
import static org.hypertrace.config.service.store.ConfigDocument.RESOURCE_NAMESPACE_FIELD_NAME; | ||
import static org.hypertrace.config.service.store.ConfigDocument.TENANT_ID_FIELD_NAME; | ||
import static org.hypertrace.config.service.store.ConfigDocument.VERSION_FIELD_NAME; | ||
import static org.hypertrace.core.documentstore.Filter.Op.OR; | ||
|
||
import com.google.common.collect.Maps; | ||
import com.google.protobuf.Value; | ||
|
@@ -29,6 +30,7 @@ | |
import org.hypertrace.config.service.ConfigServiceUtils; | ||
import org.hypertrace.config.service.v1.ContextSpecificConfig; | ||
import org.hypertrace.config.service.v1.UpsertAllConfigsResponse.UpsertedConfig; | ||
import org.hypertrace.config.service.v1.UpsertConfigRequest; | ||
import org.hypertrace.core.documentstore.CloseableIterator; | ||
import org.hypertrace.core.documentstore.Collection; | ||
import org.hypertrace.core.documentstore.Datastore; | ||
|
@@ -37,6 +39,7 @@ | |
import org.hypertrace.core.documentstore.Key; | ||
import org.hypertrace.core.documentstore.OrderBy; | ||
import org.hypertrace.core.documentstore.Query; | ||
import org.hypertrace.core.documentstore.UpdateResult; | ||
|
||
/** Document store which stores and serves the configurations. */ | ||
@Slf4j | ||
|
@@ -46,34 +49,54 @@ public class DocumentConfigStore implements ConfigStore { | |
|
||
private final Datastore datastore; | ||
private final Collection collection; | ||
private final FilterBuilder filterBuilder; | ||
|
||
public DocumentConfigStore(Clock clock, Datastore datastore) { | ||
this.clock = clock; | ||
this.datastore = datastore; | ||
this.collection = this.datastore.getCollection(CONFIGURATIONS_COLLECTION); | ||
this.filterBuilder = new FilterBuilder(); | ||
} | ||
|
||
@Override | ||
public UpsertedConfig writeConfig( | ||
ConfigResourceContext configResourceContext, | ||
String lastUpdatedUserId, | ||
Value latestConfig, | ||
UpsertConfigRequest request, | ||
String lastUpdatedUserEmail) | ||
throws IOException { | ||
Optional<ConfigDocument> previousConfigDoc = getLatestVersionConfigDoc(configResourceContext); | ||
Optional<ContextSpecificConfig> optionalPreviousConfig = | ||
previousConfigDoc.flatMap(this::convertToContextSpecificConfig); | ||
|
||
// reject create config with condition | ||
if (optionalPreviousConfig.isEmpty() && request.hasUpsertCondition()) { | ||
throw Status.FAILED_PRECONDITION | ||
.withDescription("No upsert condition required for creating config") | ||
.asRuntimeException(); | ||
} | ||
|
||
Key latestDocKey = new ConfigDocumentKey(configResourceContext); | ||
ConfigDocument latestConfigDocument = | ||
buildConfigDocument( | ||
configResourceContext, | ||
latestConfig, | ||
request.getConfig(), | ||
lastUpdatedUserId, | ||
previousConfigDoc, | ||
lastUpdatedUserEmail); | ||
|
||
collection.upsert(latestDocKey, latestConfigDocument); | ||
if (request.hasUpsertCondition()) { | ||
Filter filter = this.filterBuilder.buildDocStoreFilter(request.getUpsertCondition()); | ||
UpdateResult updateResult = collection.update(latestDocKey, latestConfigDocument, filter); | ||
if (updateResult.getUpdatedCount() <= 0) { | ||
throw Status.FAILED_PRECONDITION | ||
.withDescription("Update failed because upsert condition did not match given record") | ||
.asRuntimeException(); | ||
} | ||
} else { | ||
collection.createOrReplace(latestDocKey, latestConfigDocument); | ||
aaron-steinfeld marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return optionalPreviousConfig | ||
.map(previousConfig -> this.buildUpsertResult(latestConfigDocument, previousConfig)) | ||
.orElseGet(() -> this.buildUpsertResult(latestConfigDocument)); | ||
|
@@ -262,7 +285,7 @@ private Filter buildConfigResourceContextsFilter( | |
.map(this::getConfigResourceFieldContextFilter) | ||
.collect(Collectors.toUnmodifiableList()); | ||
Filter configResourceFieldContextFilter = new Filter(); | ||
configResourceFieldContextFilter.setOp(Filter.Op.OR); | ||
configResourceFieldContextFilter.setOp(OR); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this PR or another - it'd be good to move this filter building into the filter builder class now that there's a dedicated class for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok making those refactor in a different PR |
||
configResourceFieldContextFilter.setChildFilters(childFilters.toArray(Filter[]::new)); | ||
Filter tenantIdFilter = | ||
Filter.eq( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package org.hypertrace.config.service.store; | ||
|
||
import static org.hypertrace.config.service.store.ConfigDocument.CONFIG_FIELD_NAME; | ||
|
||
import io.grpc.Status; | ||
import org.hypertrace.config.service.v1.LogicalFilter; | ||
import org.hypertrace.config.service.v1.RelationalFilter; | ||
import org.hypertrace.core.documentstore.Filter; | ||
|
||
class FilterBuilder { | ||
|
||
public Filter buildDocStoreFilter(org.hypertrace.config.service.v1.Filter filter) { | ||
switch (filter.getTypeCase()) { | ||
case LOGICAL_FILTER: | ||
return evaluateCompositeExpression(filter.getLogicalFilter()); | ||
case RELATIONAL_FILTER: | ||
return evaluateLeafExpression(filter.getRelationalFilter()); | ||
case TYPE_NOT_SET: | ||
default: | ||
throw Status.INVALID_ARGUMENT.withDescription("Filter type unset").asRuntimeException(); | ||
} | ||
} | ||
|
||
private Filter evaluateCompositeExpression(LogicalFilter logicalFilter) { | ||
switch (logicalFilter.getOperator()) { | ||
case LOGICAL_OPERATOR_OR: | ||
{ | ||
Filter[] childFilters = | ||
logicalFilter.getOperandsList().stream() | ||
.map(this::buildDocStoreFilter) | ||
.toArray(Filter[]::new); | ||
Filter filter = new Filter(); | ||
filter.setOp(Filter.Op.OR); | ||
filter.setChildFilters(childFilters); | ||
return filter; | ||
} | ||
case LOGICAL_OPERATOR_AND: | ||
{ | ||
Filter[] childFilters = | ||
logicalFilter.getOperandsList().stream() | ||
.map(this::buildDocStoreFilter) | ||
.toArray(Filter[]::new); | ||
Filter filter = new Filter(); | ||
filter.setOp(Filter.Op.AND); | ||
filter.setChildFilters(childFilters); | ||
return filter; | ||
} | ||
case LOGICAL_OPERATOR_UNSPECIFIED: | ||
default: | ||
throw Status.INVALID_ARGUMENT | ||
.withDescription("Unknown logical operator while building filter") | ||
.asRuntimeException(); | ||
} | ||
} | ||
|
||
private Filter evaluateLeafExpression(RelationalFilter relationalFilter) { | ||
switch (relationalFilter.getOperator()) { | ||
case RELATIONAL_OPERATOR_EQ: | ||
return new Filter( | ||
Filter.Op.EQ, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_NEQ: | ||
return new Filter( | ||
Filter.Op.NEQ, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_IN: | ||
return new Filter( | ||
Filter.Op.IN, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_NOT_IN: | ||
return new Filter( | ||
Filter.Op.NOT_IN, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_LT: | ||
return new Filter( | ||
Filter.Op.LT, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_GT: | ||
return new Filter( | ||
Filter.Op.GT, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_LTE: | ||
return new Filter( | ||
Filter.Op.LTE, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case RELATIONAL_OPERATOR_GTE: | ||
return new Filter( | ||
Filter.Op.GTE, | ||
buildConfigFieldPath(relationalFilter.getConfigJsonPath()), | ||
relationalFilter.getValue()); | ||
case UNRECOGNIZED: | ||
default: | ||
throw Status.INVALID_ARGUMENT | ||
.withDescription("Unknown relational operator while building filter") | ||
.asRuntimeException(); | ||
} | ||
} | ||
|
||
private String buildConfigFieldPath(String configJsonPath) { | ||
return String.format("%s.%s", CONFIG_FIELD_NAME, configJsonPath); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably treat this the same as an update operation where the condition doesn't hold. At least the same status, even if a specific message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So given the latest change, that'd be a failed precondition