Skip to content

Commit

Permalink
♻️ Refactor query and aggregation operator interpolation classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ujibang committed Oct 27, 2023
1 parent c0c55b2 commit 28928ef
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,46 +29,50 @@
import org.bson.BsonValue;
import org.restheart.exchange.InvalidMetadataException;
import org.restheart.exchange.QueryVariableNotBoundException;
import static org.restheart.mongodb.utils.VarOperatorsInterpolator.OPERATOR;
import static org.restheart.mongodb.utils.VarsInterpolator.VAR_OPERATOR;
import org.restheart.utils.BsonUtils;
import org.restheart.utils.LambdaUtils;


/**
* Utility class for interpolating aggregation stages with a specified format, e.g., <code>{ <operator>: "name"}</code>,
* Utility class for interpolating aggregation stages with a specified format, e.g., <code>{ [operator]: "name"}</code>,
* and replacing placeholders with provided values. It also supports conditional stages using
* <code>{ "$ifvar": <var>}</code>, which are removed if the variable is missing.
* <code>{ "$ifvar": [var] }</code>, which are removed if the variable is missing.
*/

public class AggregationInterpolator {
public class StagesInterpolator {
public enum STAGE_OPERATOR { $ifvar, $ifarg };

/**
* @param varOperator the var operator, $var for queries and aggregations, $arg for GraphQL mappings
* @param stageOperator the stagee operator, $ifvar for aggregations, $ifarg for GraphQL mappings
* @param stages the aggregation pipeline stages
* @param values RequestContext.getAggregationVars()
* @return the stages, with unescaped operators and bound variables
* @throws org.restheart.exchange.InvalidMetadataException
* @throws org.restheart.exchange.QueryVariableNotBoundException
*/
public static List<BsonDocument> interpolate(OPERATOR operator, BsonArray stages, BsonDocument values) throws InvalidMetadataException, QueryVariableNotBoundException {
public static List<BsonDocument> interpolate(VAR_OPERATOR varOperator, STAGE_OPERATOR stageOperator, BsonArray stages, BsonDocument values) throws InvalidMetadataException, QueryVariableNotBoundException {
var stagesWithUnescapedOperators = BsonUtils.unescapeKeys(stages).asArray();

// check optional stages
stagesWithUnescapedOperators.stream()
.map(s -> s.asDocument())
.filter(stage -> optional(stage)).forEach(optionalStage -> {
.filter(stage -> optional(stageOperator, stage)).forEach(optionalStage -> {
try {
checkIfVar(optionalStage);
checkIfVar(stageOperator, optionalStage);
} catch(InvalidMetadataException ime) {
LambdaUtils.throwsSneakyException(ime);
}
});

var stagesWithoutUnboudOptionalStages = stagesWithUnescapedOperators.stream()
.map(s -> s.asDocument())
.map(stage -> _stage(stage, values))
.map(stage -> _stage(stageOperator, stage, values))
.filter(stage -> stage != null)
.collect(Collectors.toCollection(BsonArray::new));

var resolvedStages = VarOperatorsInterpolator.interpolate(operator, stagesWithoutUnboudOptionalStages, values).asArray();
var resolvedStages = VarsInterpolator.interpolate(varOperator, stagesWithoutUnboudOptionalStages, values).asArray();

var ret = new ArrayList<BsonDocument>();

Expand Down Expand Up @@ -102,26 +106,26 @@ public static void shouldNotContainOperators(BsonValue values) throws SecurityEx
} else if (values.isArray()) {
values.asArray().getValues().stream()
.filter(el -> (el.isDocument() || el.isArray()))
.forEachOrdered(AggregationInterpolator::shouldNotContainOperators);
.forEachOrdered(StagesInterpolator::shouldNotContainOperators);
}
}
/**
* @param stage
* @return true if stage is optional
* @throws InvalidMetadataException
*/
private static boolean optional(BsonDocument stage) {
return stage.containsKey("$ifvar");
private static boolean optional(STAGE_OPERATOR stageOperator, BsonDocument stage) {
return stage.containsKey(stageOperator.name());
}

/**
* @param stage
* @return true if optional stage is valid
* @throws InvalidMetadataException
*/
private static void checkIfVar(BsonDocument stage) throws InvalidMetadataException {
if (stage.containsKey("$ifvar")) {
var ifvar = stage.get("$ifvar");
private static void checkIfVar(STAGE_OPERATOR stageOperator, BsonDocument stage) throws InvalidMetadataException {
if (stage.containsKey(stageOperator.name())) {
var ifvar = stage.get(stageOperator.name());

if (!(ifvar.isArray() && (ifvar.asArray().size() == 2 || ifvar.asArray().size() == 3) &&
(ifvar.asArray().get(0).isString() ||
Expand All @@ -133,8 +137,8 @@ private static void checkIfVar(BsonDocument stage) throws InvalidMetadataExcepti
}
}

private static boolean stageApplies(BsonDocument stage, BsonDocument avars) {
var vars = stage.get("$ifvar").asArray().get(0);
private static boolean stageApplies(STAGE_OPERATOR stageOperator, BsonDocument stage, BsonDocument avars) {
var vars = stage.get(stageOperator.name()).asArray().get(0);

if (vars.isString()) {
return BsonUtils.get(avars, vars.asString().getValue()).isPresent();
Expand All @@ -143,20 +147,20 @@ private static boolean stageApplies(BsonDocument stage, BsonDocument avars) {
}
}

private static BsonDocument elseStage(BsonDocument stage) {
return stage.get("$ifvar").asArray().size() > 2
? stage.get("$ifvar").asArray().get(2).asDocument()
private static BsonDocument elseStage(STAGE_OPERATOR stageOperator, BsonDocument stage) {
return stage.get(stageOperator.name()).asArray().size() > 2
? stage.get(stageOperator.name()).asArray().get(2).asDocument()
: null;
}


private static BsonDocument _stage(BsonDocument stage, BsonDocument avars) {
if (!optional(stage)) {
private static BsonDocument _stage(STAGE_OPERATOR stageOperator, BsonDocument stage, BsonDocument avars) {
if (!optional(stageOperator, stage)) {
return stage;
} else if (stageApplies(stage, avars)){
return stage.get("$ifvar").asArray().get(1).asDocument();
} else if (stageApplies(stageOperator, stage, avars)){
return stage.get(stageOperator.name()).asArray().get(1).asDocument();
} else {
return elseStage(stage); // null, if no else stage specified
return elseStage(stageOperator, stage); // null, if no else stage specified
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
* such as <code>{ [operator]: "name"}</code>, and replacing placeholders with provided values.
* The class facilitates the dynamic substitution of placeholders in BSON documents and arrays.
*/
public class VarOperatorsInterpolator {
public enum OPERATOR { $var, $arg };
public class VarsInterpolator {
public enum VAR_OPERATOR { $var, $arg };
/**
* @param operator
* @param bson the BsonDocument or BsonArray containing variables
Expand All @@ -47,7 +47,7 @@ public enum OPERATOR { $var, $arg };
* @throws org.restheart.exchange.InvalidMetadataException
* @throws org.restheart.exchange.QueryVariableNotBoundException
*/
public static BsonValue interpolate(OPERATOR operator, BsonValue bson, BsonDocument values) throws InvalidMetadataException, QueryVariableNotBoundException {
public static BsonValue interpolate(VAR_OPERATOR operator, BsonValue bson, BsonDocument values) throws InvalidMetadataException, QueryVariableNotBoundException {
if (bson == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@
import org.restheart.graphql.datafetchers.GQLBatchAggregationDataFetcher;
import org.restheart.graphql.datafetchers.GraphQLDataFetcher;
import org.restheart.graphql.dataloaders.AggregationBatchLoader;
import org.restheart.mongodb.utils.AggregationInterpolator;
import org.restheart.mongodb.utils.VarOperatorsInterpolator.OPERATOR;
import org.restheart.mongodb.utils.StagesInterpolator;
import org.restheart.mongodb.utils.StagesInterpolator.STAGE_OPERATOR;
import org.restheart.mongodb.utils.VarsInterpolator.VAR_OPERATOR;
import org.restheart.utils.BsonUtils;

import graphql.schema.DataFetchingEnvironment;
Expand Down Expand Up @@ -96,7 +97,7 @@ public List<BsonDocument> interpolateArgs(DataFetchingEnvironment env) throws Qu
}

try {
var argInterpolated = AggregationInterpolator.interpolate(OPERATOR.$arg, stages, values);
var argInterpolated = StagesInterpolator.interpolate(VAR_OPERATOR.$arg, STAGE_OPERATOR.$ifarg, stages, values);
var argAndFkInterpolated = new ArrayList<BsonDocument>();

for (var s: argInterpolated) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
import org.restheart.graphql.datafetchers.GQLQueryDataFetcher;
import org.restheart.graphql.datafetchers.GraphQLDataFetcher;
import org.restheart.graphql.dataloaders.QueryBatchLoader;
import org.restheart.mongodb.utils.VarOperatorsInterpolator;
import org.restheart.mongodb.utils.VarOperatorsInterpolator.OPERATOR;
import org.restheart.mongodb.utils.VarsInterpolator;
import org.restheart.mongodb.utils.VarsInterpolator.VAR_OPERATOR;
import org.restheart.utils.BsonUtils;

import graphql.schema.DataFetchingEnvironment;
Expand Down Expand Up @@ -141,7 +141,7 @@ public BsonDocument interpolateArgs(DataFetchingEnvironment env) throws IllegalA
}

try {
var argInterpolated = VarOperatorsInterpolator.interpolate(OPERATOR.$arg, bsonDoc, values);
var argInterpolated = VarsInterpolator.interpolate(VAR_OPERATOR.$arg, bsonDoc, values);
var argAndFkIntepolated = argInterpolated;

if (argInterpolated.isDocument()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import org.restheart.exchange.MongoResponse;
import org.restheart.exchange.UnsupportedDocumentIdException;
import org.restheart.mongodb.MongoServiceConfiguration;
import org.restheart.mongodb.utils.AggregationInterpolator;
import org.restheart.mongodb.utils.StagesInterpolator;
import org.restheart.mongodb.utils.MongoURLUtils;
import org.restheart.utils.HttpStatus;
import org.restheart.utils.BsonUtils;
Expand Down Expand Up @@ -309,7 +309,7 @@ public static void inject(HttpServerExchange exchange) {

// throws SecurityException if aVars contains operators
if (MongoServiceConfiguration.get().getAggregationCheckOperators()) {
AggregationInterpolator.shouldNotContainOperators(qvars);
StagesInterpolator.shouldNotContainOperators(qvars);
}

request.setAggregationVars(qvars);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@
import org.restheart.security.WithProperties;
import org.restheart.utils.BsonUtils;
import org.restheart.utils.HttpStatus;
import org.restheart.mongodb.utils.AggregationInterpolator;
import static org.restheart.mongodb.utils.VarOperatorsInterpolator.OPERATOR;
import org.restheart.mongodb.utils.StagesInterpolator;
import static org.restheart.mongodb.utils.StagesInterpolator.STAGE_OPERATOR;
import static org.restheart.mongodb.utils.VarsInterpolator.VAR_OPERATOR;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -161,7 +162,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
try {
var clientSession = request.getClientSession();

var stages = AggregationInterpolator.interpolate(OPERATOR.$var, pipeline.getStages(), avars);
var stages = StagesInterpolator.interpolate(VAR_OPERATOR.$var, STAGE_OPERATOR.$ifvar, pipeline.getStages(), avars);

if (clientSession == null) {
agrOutput = dbs.collection(request.rsOps(), request.getDBName(), request.getCollectionName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
package org.restheart.mongodb.handlers.aggregation;

import java.util.regex.Matcher;

import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.restheart.exchange.InvalidMetadataException;
import org.restheart.exchange.QueryVariableNotBoundException;
import org.restheart.mongodb.utils.VarOperatorsInterpolator;
import static org.restheart.mongodb.utils.VarOperatorsInterpolator.OPERATOR;
import org.restheart.mongodb.utils.VarsInterpolator;
import static org.restheart.mongodb.utils.VarsInterpolator.VAR_OPERATOR;
import org.restheart.utils.BsonUtils;

/**
Expand Down Expand Up @@ -145,7 +146,7 @@ public BsonValue getQuery() {
*/
public BsonDocument getResolvedQuery(BsonDocument aVars)
throws InvalidMetadataException, QueryVariableNotBoundException {
return VarOperatorsInterpolator.interpolate(OPERATOR.$var, BsonUtils.unescapeKeys(query), aVars).asDocument();
return VarsInterpolator.interpolate(VAR_OPERATOR.$var, BsonUtils.unescapeKeys(query), aVars).asDocument();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package org.restheart.mongodb.handlers.changestreams;

import com.mongodb.client.model.changestream.FullDocument;

import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
Expand All @@ -37,8 +38,9 @@
import org.restheart.exchange.QueryVariableNotBoundException;
import org.restheart.handlers.PipelinedHandler;
import org.restheart.mongodb.RHMongoClients;
import org.restheart.mongodb.utils.AggregationInterpolator;
import org.restheart.mongodb.utils.VarOperatorsInterpolator.OPERATOR;
import org.restheart.mongodb.utils.StagesInterpolator;
import org.restheart.mongodb.utils.StagesInterpolator.STAGE_OPERATOR;
import org.restheart.mongodb.utils.VarsInterpolator.VAR_OPERATOR;
import org.restheart.utils.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -138,7 +140,7 @@ private List<BsonDocument> getResolvedStagesAsList(MongoRequest request) throws

ChangeStreamOperation pipeline = _query.get();

List<BsonDocument> resolvedStages = AggregationInterpolator.interpolate(OPERATOR.$var, pipeline.getStages(), request.getAggregationVars());
List<BsonDocument> resolvedStages = StagesInterpolator.interpolate(VAR_OPERATOR.$var, STAGE_OPERATOR.$ifvar, pipeline.getStages(), request.getAggregationVars());
return resolvedStages;
}

Expand Down
Loading

0 comments on commit 28928ef

Please sign in to comment.