Skip to content

Filters support #278

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add filter and Search support
Filter and search support
  • Loading branch information
galaxyse committed Jun 22, 2023
commit 2e26a382a3474261936b1947d65265647b869bba
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -39,5 +45,8 @@ public interface ArangoParameterAccessor extends ParameterAccessor {
Map<String, Object> getBindVars();

Map<String, Object> getSpelVars();

Filterable getFilter(String placeholder);

Searchable getSearch();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -16,13 +17,15 @@
* 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;

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;
Expand All @@ -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;
Expand All @@ -46,11 +52,14 @@
* @author Mark McCormick
* @author Mark Vollmary
* @author Christian Lechner
* @author Siva Prasad Erigineni
*/
public class ArangoParameters extends Parameters<ArangoParameters, ArangoParameters.ArangoParameter> {

private final int queryOptionsIndex;
private final int bindVarsIndex;
private final Map<String, Integer> filterIndexes;
private final int searchIndex;

public ArangoParameters(final Method method) {
super(method);
Expand All @@ -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<ArangoParameter> parameters, final int queryOptionsIndex,
final int bindVarsIndex) {
super(parameters);
this.queryOptionsIndex = queryOptionsIndex;
this.bindVarsIndex = bindVarsIndex;
this.filterIndexes = filterIndexes;
this.searchIndex = searchIndex;
}

@Override
Expand All @@ -77,7 +91,7 @@ protected ArangoParameter createParameter(final MethodParameter parameter) {

@Override
protected ArangoParameters createFrom(final List<ArangoParameter> parameters) {
return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex);
return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex, this.filterIndexes, this.searchIndex);
}

public boolean hasQueryOptions() {
Expand All @@ -95,6 +109,9 @@ public boolean hasBindVars() {
public int getBindVarsIndex() {
return this.bindVarsIndex;
}
public boolean hasFilterParameter() { return this.filterIndexes.size() > 0; }

public Map<String, Integer> getFilterIndexes() { return this.filterIndexes; }

private int getIndexOfSpecialParameter(final Predicate<ArangoParameter> condition) {
for (int index = 0; index < getNumberOfParameters(); ++index) {
Expand All @@ -106,6 +123,29 @@ private int getIndexOfSpecialParameter(final Predicate<ArangoParameter> 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<String, Integer> mapFilterIndexes() {
Map<String, Integer> 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<ArangoParameter> condition, final String message) {
boolean found = false;
for (int index = 0; index < getNumberOfParameters(); ++index) {
Expand Down Expand Up @@ -145,6 +185,7 @@ public ArangoParameter(final MethodParameter parameter) {
this.parameter = parameter;
assertCorrectBindParamPattern();
assertCorrectBindVarsType();
assertFilterType();
}

@Override
Expand All @@ -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<String> getName() {
Expand All @@ -176,7 +222,11 @@ public Optional<String> 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());
}
}
Expand Down Expand Up @@ -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);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -67,5 +70,16 @@ public Map<String, Object> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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));
}
}

}
Loading