diff --git a/src/main/java/com/arangodb/springframework/annotation/Filter.java b/src/main/java/com/arangodb/springframework/annotation/Filter.java new file mode 100644 index 000000000..52ddda4ab --- /dev/null +++ b/src/main/java/com/arangodb/springframework/annotation/Filter.java @@ -0,0 +1,32 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ + +package com.arangodb.springframework.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.PARAMETER }) +public @interface Filter { + String value() default "#filter"; +} diff --git a/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java b/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java index 68f531b14..185714d65 100644 --- a/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java +++ b/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java @@ -22,6 +22,11 @@ import com.arangodb.springframework.core.mapping.ArangoMappingContext; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; +import com.arangodb.springframework.repository.query.filter.AqlFilterBuilder; +import com.arangodb.springframework.repository.query.filter.Filterable; +import com.arangodb.springframework.repository.query.search.AqlSearchBuilder; +import com.arangodb.springframework.repository.query.search.Searchable; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; @@ -34,6 +39,7 @@ /** * * @author Christian Lechner + * @author Siva Prasad Erigineni */ public final class AqlUtils { @@ -138,7 +144,10 @@ public static Sort toPersistentSort( .collect(Collectors.toList()); return Sort.by(orders); } - + public static String escapeProperty(final String str) + { + return escapeSortProperty(str); + } private static String escapeSortProperty(final String str) { // dots are not allowed at start/end if (str.charAt(0) == '.' || str.charAt(str.length() - 1) == '.') { @@ -240,5 +249,11 @@ public static String buildCollectionName(final String collection) { public static String buildFieldName(final String field) { return field.contains("-") ? "`" + field + "`" : field; } + public static String buildFilterClause(Filterable filter) { + return AqlFilterBuilder.of(filter).toFilterStatement(); + } + public static String buildSearchClause(Searchable search) { + return AqlSearchBuilder.of(search).toSearchStatement(); + } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java index bc6d2f5ea..1e11ed757 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -2,7 +2,7 @@ * DISCLAIMER * * Copyright 2018 ArangoDB GmbH, Cologne, Germany - * + * Copyright 2023 Hewlett Packard Enterprise Development LP. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,6 +16,7 @@ * limitations under the License. * * Copyright holder is ArangoDB GmbH, Cologne, Germany + * Copyright holder is Hewlett Packard Enterprise Development LP. */ package com.arangodb.springframework.repository.query; @@ -177,6 +178,10 @@ protected AqlQueryOptions mergeQueryOptions(final AqlQueryOptions oldStatic, fin if (mergedOptions.getAllowDirtyRead() == null) { mergedOptions.allowDirtyRead(oldStatic.getAllowDirtyRead()); } + if(mergedOptions.getForceOneShardAttributeValue() != null) { + mergedOptions.forceOneShardAttributeValue(oldStatic.getForceOneShardAttributeValue()); + } + return mergedOptions; } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java index 95bd4fb83..a4474cc03 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java @@ -2,6 +2,7 @@ * DISCLAIMER * * Copyright 2018 ArangoDB GmbH, Cologne, Germany + * Copyright 2023 Hewlett Packard Enterprise Development LP. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +17,7 @@ * limitations under the License. * * Copyright holder is ArangoDB GmbH, Cologne, Germany + * Copyright holder is Hewlett Packard Enterprise Development LP. */ package com.arangodb.springframework.repository.query; @@ -26,6 +28,10 @@ import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.repository.query.filter.Filterable; + +import com.arangodb.springframework.repository.query.search.Searchable; + /** * * @author Christian Lechner @@ -39,5 +45,8 @@ public interface ArangoParameterAccessor extends ParameterAccessor { Map getBindVars(); Map getSpelVars(); + + Filterable getFilter(String placeholder); + Searchable getSearch(); } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java index 1420b05ac..44c6edfd4 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java @@ -2,6 +2,7 @@ * DISCLAIMER * * Copyright 2017 ArangoDB GmbH, Cologne, Germany + * Copyright 2023 Hewlett Packard Enterprise Development LP. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +17,7 @@ * limitations under the License. * * Copyright holder is ArangoDB GmbH, Cologne, Germany + * Copyright holder is Hewlett Packard Enterprise Development LP. */ package com.arangodb.springframework.repository.query; @@ -23,6 +25,7 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -36,6 +39,9 @@ import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.util.Assert; +import com.arangodb.springframework.annotation.Filter; +import com.arangodb.springframework.repository.query.filter.Filterable; +import com.arangodb.springframework.repository.query.search.Searchable; import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.annotation.BindVars; @@ -46,11 +52,14 @@ * @author Mark McCormick * @author Mark Vollmary * @author Christian Lechner + * @author Siva Prasad Erigineni */ public class ArangoParameters extends Parameters { private final int queryOptionsIndex; private final int bindVarsIndex; + private final Map filterIndexes; + private final int searchIndex; public ArangoParameters(final Method method) { super(method); @@ -61,13 +70,18 @@ public ArangoParameters(final Method method) { assertNonDuplicateParamNames(method); this.queryOptionsIndex = getIndexOfSpecialParameter(ArangoParameter::isQueryOptions); this.bindVarsIndex = getIndexOfSpecialParameter(ArangoParameter::isBindVars); + this.filterIndexes = mapFilterIndexes(); + this.searchIndex = getIndexOfSpecialParameter(ArangoParameter::isSearch); + } private ArangoParameters(final List parameters, final int queryOptionsIndex, - final int bindVarsIndex) { + final int bindVarsIndex, final Map filterIndexes, final int searchIndex) { super(parameters); this.queryOptionsIndex = queryOptionsIndex; this.bindVarsIndex = bindVarsIndex; + this.filterIndexes = filterIndexes; + this.searchIndex = searchIndex; } @Override @@ -77,7 +91,7 @@ protected ArangoParameter createParameter(final MethodParameter parameter) { @Override protected ArangoParameters createFrom(final List parameters) { - return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex); + return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex, this.filterIndexes, this.searchIndex); } public boolean hasQueryOptions() { @@ -95,6 +109,9 @@ public boolean hasBindVars() { public int getBindVarsIndex() { return this.bindVarsIndex; } + public boolean hasFilterParameter() { return this.filterIndexes.size() > 0; } + + public Map getFilterIndexes() { return this.filterIndexes; } private int getIndexOfSpecialParameter(final Predicate condition) { for (int index = 0; index < getNumberOfParameters(); ++index) { @@ -106,6 +123,29 @@ private int getIndexOfSpecialParameter(final Predicate conditio return -1; } + public boolean hasSearchParameter() { return this.searchIndex != -1; } + + public int getSearchIndex() { + return this.searchIndex; + } + + /** + * Returns the index of all Filter parameters indexed by their corresponding placeholders. + */ + private Map mapFilterIndexes() { + Map filters = new HashMap<>(); + + for (int index = 0; index < getNumberOfParameters(); ++index) { + final ArangoParameter param = getParameter(index); + if (param.isFilter()) { + filters.put(param.getPlaceholder(), index); + } + } + + return filters; + } + + private void assertSingleSpecialParameter(final Predicate condition, final String message) { boolean found = false; for (int index = 0; index < getNumberOfParameters(); ++index) { @@ -145,6 +185,7 @@ public ArangoParameter(final MethodParameter parameter) { this.parameter = parameter; assertCorrectBindParamPattern(); assertCorrectBindVarsType(); + assertFilterType(); } @Override @@ -163,6 +204,11 @@ public boolean isBindVars() { public boolean isSpelParam() { return parameter.hasParameterAnnotation(SpelParam.class); } + public boolean isFilter() { return parameter.hasParameterAnnotation(Filter.class); } + + public boolean isSearch() { + return Searchable.class.isAssignableFrom(parameter.getParameterType()); + } @Override public Optional getName() { @@ -176,7 +222,11 @@ public Optional getName() { public String getPlaceholder() { if (isNamedParameter()) { return String.format(NAMED_PARAMETER_TEMPLATE, getName().get()); - } else { + } + else if (isFilter()) { + return this.parameter.getParameterAnnotation(Filter.class).value(); + } + else { return String.format(POSITION_PARAMETER_TEMPLATE, getIndex()); } } @@ -212,6 +262,14 @@ private void assertCorrectBindVarsType() { Assert.isTrue(Object.class.equals(valueType), errorMsg); } } + private void assertFilterType() { + final String errorMsg = "@Filter parameter must be of type Filterable! Offending parameter: parameter " + + parameter.getParameterIndex() + " on method" + parameter.getMethod(); + + if (isFilter()) { + Assert.isTrue(Filterable.class.equals(parameter.getParameterType()), errorMsg); + } + } } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java index d67422dc7..ac8af0a7d 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java @@ -2,6 +2,7 @@ * DISCLAIMER * * Copyright 2017 ArangoDB GmbH, Cologne, Germany + * Copyright 2023 Hewlett Packard Enterprise Development LP. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +17,7 @@ * limitations under the License. * * Copyright holder is ArangoDB GmbH, Cologne, Germany + * Copyright holder is Hewlett Packard Enterprise Development LP. */ package com.arangodb.springframework.repository.query; @@ -24,7 +26,8 @@ import java.util.stream.Collectors; import org.springframework.data.repository.query.ParametersParameterAccessor; - +import com.arangodb.springframework.repository.query.filter.Filterable; +import com.arangodb.springframework.repository.query.search.Searchable; import com.arangodb.model.AqlQueryOptions; /** @@ -67,5 +70,16 @@ public Map getSpelVars() { .filter(ArangoParameters.ArangoParameter::isSpelParam) .collect(Collectors.toMap(it -> it.getName().get(), it -> getValue(it.getIndex()))); } + @Override + public Filterable getFilter(String placeholder) { + final Integer filterIndex = parameters.getFilterIndexes().get(placeholder); + return filterIndex == null ? null : getValue(filterIndex); + } + + @Override + public Searchable getSearch() { + final int searchIndex = parameters.getSearchIndex(); + return searchIndex == -1 ? null : getValue(searchIndex); + } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java index 3cc068901..714e7ee0e 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java @@ -46,6 +46,7 @@ * @author Mark Vollmary * @author Christian Lechner * @author Michele Rastelli + * @author Siva Prasad Erigineni */ public class StringBasedArangoQuery extends AbstractArangoQuery { private static final SpelExpressionParser PARSER = new SpelExpressionParser(); @@ -59,9 +60,14 @@ public class StringBasedArangoQuery extends AbstractArangoQuery { private static final String COLLECTION_PLACEHOLDER = "#collection"; private static final Pattern COLLECTION_PLACEHOLDER_PATTERN = Pattern .compile(Pattern.quote(COLLECTION_PLACEHOLDER)); + private static final Pattern FILTER_PLACEHOLDER_PATTERN = Pattern + .compile("#filter[a-zA-Z0-9-]*"); private static final Pattern BIND_PARAM_PATTERN = Pattern.compile("@(@?[A-Za-z0-9][A-Za-z0-9_]*)"); + private static final String SEARCH_PLACEHOLDER = "#search"; + private static final Pattern SEARCH_PLACEHOLDER_PATTERN = Pattern + .compile(Pattern.quote(SEARCH_PLACEHOLDER)); private final String query; private final String collectionName; private final Expression queryExpression; @@ -84,7 +90,9 @@ public StringBasedArangoQuery(final String query, final ArangoQueryMethod method assertSinglePageablePlaceholder(); assertSingleSortPlaceholder(); - + assertFiltersPlaceholders(); + assertSingleSearchPlaceholder(); + this.queryBindParams = getBindParamsInQuery(); queryExpression = PARSER.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION); } @@ -132,6 +140,20 @@ private String prepareQuery(final ArangoParameterAccessor accessor) { final String sortClause = AqlUtils.buildSortClause(accessor.getSort()); preparedQuery = SORT_PLACEHOLDER_PATTERN.matcher(preparedQuery).replaceFirst(sortClause); } + + if (accessor.getParameters().hasFilterParameter()) { + Matcher matcher = FILTER_PLACEHOLDER_PATTERN.matcher(preparedQuery); + while(matcher.find()) { + final String placeholder = matcher.group().replace("#", ""); + final String filterClause = AqlUtils.buildFilterClause(accessor.getFilter(placeholder)); + preparedQuery = FILTER_PLACEHOLDER_PATTERN.matcher(preparedQuery).replaceFirst(filterClause); + } + } + + if (accessor.getParameters().hasSearchParameter()) { + final String searchClause = AqlUtils.buildSearchClause(accessor.getSearch()); + preparedQuery = SEARCH_PLACEHOLDER_PATTERN.matcher(preparedQuery).replaceFirst(searchClause); + } return preparedQuery; } @@ -225,5 +247,31 @@ private void assertSingleSortPlaceholder() { SORT_PLACEHOLDER, method)); } } + private void assertFiltersPlaceholders() { + if (method.getParameters().hasFilterParameter()) { + int placeholderOccurrences = 0; + int parameterOccurrences = method.getParameters().getFilterIndexes().size(); + + Matcher matcher = FILTER_PLACEHOLDER_PATTERN.matcher(query); + while(matcher.find()) { + placeholderOccurrences++; + } + + Assert.isTrue(placeholderOccurrences == parameterOccurrences, String.format("The number of filter " + + " placeholders (%d) does not match with the number of @Filter parameters (%d)! Offending method: %s", placeholderOccurrences, parameterOccurrences, method)); + } + } + + private void assertSingleSearchPlaceholder() { + if (method.getParameters().hasSearchParameter()) { + final int firstOccurrence = query.indexOf(SEARCH_PLACEHOLDER); + final int secondOccurrence = query.indexOf(SEARCH_PLACEHOLDER, firstOccurrence + SEARCH_PLACEHOLDER.length()); + + Assert.isTrue(firstOccurrence > -1 && secondOccurrence < 0, + String.format( + "Native query with @Search param must contain exactly one search placeholder (%s)! Offending method: %s", + SEARCH_PLACEHOLDER, method)); + } + } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/AbstractExpression.java b/src/main/java/com/arangodb/springframework/repository/query/filter/AbstractExpression.java new file mode 100644 index 000000000..9516cc1ee --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/AbstractExpression.java @@ -0,0 +1,62 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +import java.util.List; +import java.util.stream.Collectors; + +public abstract class AbstractExpression implements Filterable { + @Override + public Filterable and(Filterable other) { + return new CombinedExpression(Operator.AND, this, other); + } + + @Override + public Filterable or(Filterable other) { + return new CombinedExpression(Operator.OR, this, other); + } + + @Override + public Filterable group() { + return new GroupExpression(this); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(""); + + if (this instanceof CompareExpression) { + sb.append(((CompareExpression) this).toString()); + } else if (this instanceof GroupExpression) { + final GroupExpression filter = (GroupExpression) this; + final List expressions = filter.expressions.stream().map(expr -> expr.toString()).collect(Collectors.toList()); + final String mergedExpr = (filter.hasSingleCondition()) ? expressions.get(0): String.join(filter.operator.surroundedWithSpaces(), expressions); + + sb.append(String.format("(%s)", mergedExpr)); + } else if (this instanceof CombinedExpression) { + final CombinedExpression filter = (CombinedExpression) this; + final List expressions = filter.expressions.stream().map(expr -> expr.toString()).collect(Collectors.toList()); + + sb.append(String.join(filter.operator.surroundedWithSpaces(), expressions)); + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/AqlFilterBuilder.java b/src/main/java/com/arangodb/springframework/repository/query/filter/AqlFilterBuilder.java new file mode 100644 index 000000000..8374bd4ef --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/AqlFilterBuilder.java @@ -0,0 +1,64 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +import java.util.List; +import java.util.stream.Collectors; + +public class AqlFilterBuilder { + final Filterable filter; + + private AqlFilterBuilder(final Filterable filter) { + this.filter = filter; + } + + public static AqlFilterBuilder of(final Filterable filter) { + return new AqlFilterBuilder(filter); + } + + public String toFilterStatement() { + if (filter == null) { + return ""; + } + + return String.format("FILTER %s", filter); + } + + String parseFilter() { + StringBuilder sb = new StringBuilder(""); + + if (this.filter instanceof CompareExpression) { + sb.append(((CompareExpression) this.filter).toString()); + } else if (this.filter instanceof GroupExpression) { + final GroupExpression filter = (GroupExpression) this.filter; + final List conditions = filter.expressions.stream().map(f -> f.toString()).collect(Collectors.toList()); + final String expression = (filter.hasSingleCondition()) ? conditions.get(0): String.join(filter.operator.surroundedWithSpaces(), conditions); + + sb.append(String.format("(%s)", expression)); + } else if (this.filter instanceof CombinedExpression) { + final CombinedExpression filter = (CombinedExpression) this.filter; + final List conditions = filter.expressions.stream().map(f -> f.toString()).collect(Collectors.toList()); + + sb.append(String.join(filter.operator.surroundedWithSpaces(), conditions)); + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/CombinedExpression.java b/src/main/java/com/arangodb/springframework/repository/query/filter/CombinedExpression.java new file mode 100644 index 000000000..b4329ca93 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/CombinedExpression.java @@ -0,0 +1,42 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +import java.util.ArrayList; +import java.util.List; + +public class CombinedExpression extends AbstractExpression { + + final Operator operator; + final List expressions; + + public CombinedExpression(Operator operator, Filterable left, Filterable right) { + this.operator = operator; + this.expressions = new ArrayList() { + { + add(left); + } + }; + + if (right != null) { + expressions.add(right); + } + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/Comparator.java b/src/main/java/com/arangodb/springframework/repository/query/filter/Comparator.java new file mode 100644 index 000000000..44b086915 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/Comparator.java @@ -0,0 +1,36 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +public enum Comparator { + EQ("=="), + ; + + final private String text; + + Comparator(final String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/CompareExpression.java b/src/main/java/com/arangodb/springframework/repository/query/filter/CompareExpression.java new file mode 100644 index 000000000..91a6e0bae --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/CompareExpression.java @@ -0,0 +1,39 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +public class CompareExpression extends AbstractExpression { + final Comparator comparator; + final Field left; + final String right; + + public CompareExpression(Comparator comparator, Field left, String right) { + this.comparator = comparator; + this.left = left; + this.right = right; + } + + @Override + public String toString() { + String escapedRight = String.format("\"%s\"", right); + return String.format("%s %s %s", left, comparator, escapedRight); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/Field.java b/src/main/java/com/arangodb/springframework/repository/query/filter/Field.java new file mode 100644 index 000000000..b41531f9a --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/Field.java @@ -0,0 +1,46 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +import com.arangodb.springframework.core.util.AqlUtils; + +public class Field { + final String collection; + final String name; + + public Field(String collection, String name) { + this.collection = collection; + this.name = name; + } + + public static Field of(String collection, String name) { + return new Field(collection, name); + } + + public Filterable eq(String right) { + return new CompareExpression(Comparator.EQ, this, right); + } + + @Override + public String toString() { + return AqlUtils.escapeProperty(String.format("%s.%s", collection, name)); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/Filterable.java b/src/main/java/com/arangodb/springframework/repository/query/filter/Filterable.java new file mode 100644 index 000000000..0abb4518f --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/Filterable.java @@ -0,0 +1,30 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +import java.io.Serializable; + +public interface Filterable extends Serializable { + Filterable and(Filterable other); + + Filterable or(Filterable other); + + Filterable group(); +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/GroupExpression.java b/src/main/java/com/arangodb/springframework/repository/query/filter/GroupExpression.java new file mode 100644 index 000000000..c0f72c83f --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/GroupExpression.java @@ -0,0 +1,34 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +public class GroupExpression extends CombinedExpression { + public GroupExpression(Operator operator, Filterable left, Filterable right) { + super(operator, left, right); + } + + public GroupExpression(Filterable filter) { + super(null, filter, null); + } + + public boolean hasSingleCondition() { + return this.operator == null; + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/filter/Operator.java b/src/main/java/com/arangodb/springframework/repository/query/filter/Operator.java new file mode 100644 index 000000000..29e5a6d2d --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/filter/Operator.java @@ -0,0 +1,41 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.filter; + +public enum Operator { + AND("AND"), + OR("OR"), + ; + + final private String text; + + Operator(final String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } + + public String surroundedWithSpaces() { + return String.format(" %s ", this); + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/search/AqlSearchBuilder.java b/src/main/java/com/arangodb/springframework/repository/query/search/AqlSearchBuilder.java new file mode 100644 index 000000000..51fc543c1 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/search/AqlSearchBuilder.java @@ -0,0 +1,36 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.search; + +public class AqlSearchBuilder { + final private Searchable search; + + private AqlSearchBuilder(final Searchable search) { + this.search = search; + } + + public static AqlSearchBuilder of(final Searchable expression) { + return new AqlSearchBuilder(expression); + } + + public String toSearchStatement() { + return this.search.toString(); + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/search/SearchExpression.java b/src/main/java/com/arangodb/springframework/repository/query/search/SearchExpression.java new file mode 100644 index 000000000..8bcacb275 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/search/SearchExpression.java @@ -0,0 +1,38 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.search; + +public class SearchExpression implements Searchable { + + final private String expression; + + private SearchExpression(final String expression) { + this.expression = expression; + } + + public static SearchExpression of(final String expression) { + return new SearchExpression((expression)); + } + + @Override + public String toString() { + return this.expression; + } +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/search/Searchable.java b/src/main/java/com/arangodb/springframework/repository/query/search/Searchable.java new file mode 100644 index 000000000..955bd2f74 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/search/Searchable.java @@ -0,0 +1,23 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ +package com.arangodb.springframework.repository.query.search; + +public interface Searchable { +} diff --git a/src/test/java/com/arangodb/springframework/repository/query/filter/FilterTest.java b/src/test/java/com/arangodb/springframework/repository/query/filter/FilterTest.java new file mode 100644 index 000000000..0b2c9eae9 --- /dev/null +++ b/src/test/java/com/arangodb/springframework/repository/query/filter/FilterTest.java @@ -0,0 +1,94 @@ +/* + * DISCLAIMER + * + * Copyright 2023 Hewlett Packard Enterprise Development LP. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is Hewlett Packard Enterprise Development LP. + */ + +package com.arangodb.springframework.repository.query.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.arangodb.springframework.repository.query.filter.*; +import org.junit.Test; + +public class FilterTest { + final static String expected = "FILTER (`c`.`a` == \"A\" AND `c`.`b` == \"B\") OR (`c`.`c` == \"C\" AND `c`.`d` == \"D\")"; + + @Test + public void createFilterTest() { + final Filterable f1 = new CompareExpression(Comparator.EQ, Field.of("c", "a"), "A"); + final Filterable f2 = new CompareExpression(Comparator.EQ, Field.of("c", "b"), "B"); + final Filterable f3 = new CompareExpression(Comparator.EQ, Field.of("c", "c"), "C"); + final Filterable f4 = new CompareExpression(Comparator.EQ, Field.of("c", "d"), "D"); + + final Filterable f5 = new GroupExpression(Operator.AND, f1, f2); + final Filterable f6 = new GroupExpression(Operator.AND, f3, f4); + final Filterable f7 = new CombinedExpression(Operator.OR, f5, f6); + + final AqlFilterBuilder fb = AqlFilterBuilder.of(f7); + + assertEquals("Filter expressions don't match", expected, fb.toFilterStatement()); + } + + @Test + public void createFilterOneLineTest() { + final Filterable f = Field.of("c", "a").eq("A").and(Field.of("c", "b").eq("B")).group() + .or(Field.of("c", "c").eq("C").and(Field.of("c", "d").eq("D")).group()); + + final AqlFilterBuilder fb = AqlFilterBuilder.of(f); + + assertEquals("Filter expressions don't match", expected, fb.toFilterStatement()); + } + + @Test + public void createFilterMultipleScenariosTest() { + TestCase[] tests = new TestCase[] { + TestCase.of("FILTER `c`.`a` == \"A\"", Field.of("c", "a").eq("A")), + TestCase.of("FILTER `c`.`a` == \"A\" AND `c`.`b` == \"B\"", + Field.of("c", "a").eq("A").and(Field.of("c", "b").eq("B"))), + TestCase.of("FILTER (`c`.`a` == \"A\")", Field.of("c", "a").eq("A").group()), + TestCase.of("FILTER (`c`.`a` == \"A\") AND (`c`.`b` == \"B\")", + Field.of("c", "a").eq("A").group().and(Field.of("c", "b").eq("B").group())) + }; + + + AqlFilterBuilder fb; + + for(TestCase test: tests) { + fb = AqlFilterBuilder.of(test.filter); + assertEquals("Filter expressions don't match", test.expected, fb.toFilterStatement()); + } + + } + + static class TestCase { + final String expected; + final Filterable filter; + + private TestCase(String expected, Filterable filter) { + this.expected = expected; + this.filter = filter; + } + + public static TestCase of(String expected, Filterable filter) { + return new TestCase(expected, filter); + } + } + + +}