Skip to content

Commit

Permalink
FMWK-255 Add qualifier builders (#644)
Browse files Browse the repository at this point in the history
  • Loading branch information
agrgr authored Oct 23, 2023
1 parent 5fe4445 commit f5ba6e4
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 219 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.springframework.data.aerospike.core;

import lombok.experimental.UtilityClass;
import org.springframework.data.aerospike.query.FilterOperation;
import org.springframework.data.aerospike.query.Qualifier;
import org.springframework.util.Assert;

Expand All @@ -11,12 +12,15 @@
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.springframework.data.aerospike.query.Qualifier.and;
import static org.springframework.data.aerospike.query.Qualifier.or;

@UtilityClass
public class TemplateUtils {

public static List<Object> getIdValue(Qualifier qualifier) {
if (qualifier.hasId() && qualifier.getValue1() != null) {
return idObjectToList(qualifier.getValue1().getObject());
if (qualifier.hasId()) {
return idObjectToList(qualifier.getId());
} else {
throw new IllegalArgumentException("Id qualifier must contain value");
}
Expand Down Expand Up @@ -44,8 +48,7 @@ public static Qualifier[] excludeIdQualifier(Qualifier[] qualifiers) {
for (Qualifier qualifier : qualifiers) {
if (qualifier.hasQualifiers()) {
Qualifier[] internalQuals = excludeIdQualifier(qualifier.getQualifiers());
Qualifier.QualifierBuilder qb = Qualifier.builder().setFilterOperation(qualifier.getOperation());
qualifiersWithoutId.add(qb.setQualifiers(internalQuals).build());
qualifiersWithoutId.add(combineMultipleQualifiers(qualifier.getOperation(), internalQuals));
} else if (!qualifier.hasId()) {
qualifiersWithoutId.add(qualifier);
}
Expand All @@ -54,4 +57,14 @@ public static Qualifier[] excludeIdQualifier(Qualifier[] qualifiers) {
}
return null;
}

private static Qualifier combineMultipleQualifiers(FilterOperation operation, Qualifier[] qualifiers) {
if (operation == FilterOperation.OR) {
return or(qualifiers);
} else if (operation == FilterOperation.AND) {
return and(qualifiers);
} else {
throw new UnsupportedOperationException("Only OR / AND operations are supported");
}
}
}
124 changes: 98 additions & 26 deletions src/main/java/org/springframework/data/aerospike/query/Qualifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@

import java.io.Serial;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -44,7 +44,8 @@ public class Qualifier implements Map<String, Object>, Serializable {

protected static final String FIELD = "field";
protected static final String METADATA_FIELD = "metadata_field";
protected static final String ID_VALUE = "id";
protected static final String SINGLE_ID_FIELD = "id";
protected static final String MULTIPLE_IDS_FIELD = "ids";
protected static final String IGNORE_CASE = "ignoreCase";
protected static final String VALUE1 = "value1";
protected static final String VALUE2 = "value2";
Expand All @@ -57,16 +58,24 @@ public class Qualifier implements Map<String, Object>, Serializable {
protected static final String EXCLUDE_FILTER = "excludeFilter";
@Serial
private static final long serialVersionUID = -2689196529952712849L;
protected final Map<String, Object> internalMap;
protected final Map<String, Object> internalMap = new HashMap<>();

protected Qualifier(Qualifier.Builder builder) {
internalMap = new HashMap<>();
if (!builder.getMap().isEmpty()) {
internalMap.putAll(builder.getMap());
}
}

if (!builder.buildMap().isEmpty()) {
internalMap.putAll(builder.buildMap());
protected Qualifier(Qualifier qualifier) {
if (!qualifier.getMap().isEmpty()) {
internalMap.putAll(qualifier.getMap());
}
}

private Map<String, Object> getMap() {
return Collections.unmodifiableMap(this.internalMap);
}

public static QualifierBuilder builder() {
return new QualifierBuilder();
}
Expand Down Expand Up @@ -108,7 +117,15 @@ public boolean hasQualifiers() {
}

public boolean hasId() {
return internalMap.get(FIELD) != null && internalMap.get(FIELD).equals(ID_VALUE);
return internalMap.get(SINGLE_ID_FIELD) != null || internalMap.get(MULTIPLE_IDS_FIELD) != null;
}

public boolean hasSingleId() {
return internalMap.get(SINGLE_ID_FIELD) != null;
}

public Object getId() {
return this.hasSingleId() ? internalMap.get(SINGLE_ID_FIELD) : internalMap.get(MULTIPLE_IDS_FIELD);
}

public Qualifier[] getQualifiers() {
Expand Down Expand Up @@ -217,9 +234,9 @@ public String toString() {
getOperation(), getValue1(), getValue2());
}

public interface Builder {
protected interface Builder {

Map<String, Object> buildMap();
Map<String, Object> getMap();

Qualifier build();
}
Expand All @@ -239,11 +256,6 @@ public QualifierBuilder setField(String field) {
return this;
}

public QualifierBuilder setQualifiers(Qualifier... qualifiers) {
this.map.put(QUALIFIERS, qualifiers);
return this;
}

public QualifierBuilder setValue1(Value value1) {
this.map.put(VALUE1, value1);
return this;
Expand Down Expand Up @@ -336,6 +348,44 @@ protected void validate() {
}
}

private static class IdQualifierBuilder extends BaseQualifierBuilder<IdQualifierBuilder> {

private IdQualifierBuilder() {
}

private IdQualifierBuilder setId(String id) {
this.map.put(SINGLE_ID_FIELD, id);
return this;
}

private IdQualifierBuilder setIds(String... ids) {
this.map.put(MULTIPLE_IDS_FIELD, ids);
return this;
}
}

private static class ConjunctionQualifierBuilder extends BaseQualifierBuilder<ConjunctionQualifierBuilder> {

private ConjunctionQualifierBuilder() {
}

private ConjunctionQualifierBuilder setQualifiers(Qualifier... qualifiers) {
this.map.put(QUALIFIERS, qualifiers);
return this;
}

private Qualifier[] getQualifiers() {
return (Qualifier[]) this.map.get(QUALIFIERS);
}

@Override
protected void validate() {
Assert.notNull(this.getQualifiers(), "Qualifiers must not be null");
Assert.notEmpty(this.getQualifiers(), "Qualifiers must not be empty");
Assert.isTrue(this.getQualifiers().length > 1, "There must be at least 2 qualifiers");
}
}

@SuppressWarnings("unchecked")
protected abstract static class BaseQualifierBuilder<T extends BaseQualifierBuilder<?>> implements Builder {

Expand Down Expand Up @@ -371,8 +421,8 @@ public Qualifier build() {
return new Qualifier(this);
}

public Map<String, Object> buildMap() {
return this.map;
public Map<String, Object> getMap() {
return Collections.unmodifiableMap(this.map);
}

protected void validate() {
Expand All @@ -381,28 +431,50 @@ protected void validate() {
}

/**
* Create qualifier "ID is equal to the given string"
* Create a qualifier for the condition when the primary key is equal to the given string
*
* @param id String value
* @return Single id qualifier
*/
public static Qualifier forId(String id) {
return new Qualifier(new QualifierBuilder()
.setField(ID_VALUE)
.setFilterOperation(FilterOperation.EQ)
.setValue1(Value.get(id)));
return new Qualifier(new IdQualifierBuilder()
.setId(id)
.setFilterOperation(FilterOperation.EQ));
}

/**
* Create qualifier "ID is equal to one of the given strings (logical OR)"
* Create a qualifier for the condition when the primary key is equal to one of the given strings (logical OR)
*
* @param ids String values
* @return Multiple ids qualifier with OR condition
*/
public static Qualifier forIds(String... ids) {
return new Qualifier(new QualifierBuilder()
.setField(ID_VALUE)
.setFilterOperation(FilterOperation.EQ)
.setValue1(Value.get(Arrays.stream(ids).toList())));
return new Qualifier(new IdQualifierBuilder()
.setIds(ids)
.setFilterOperation(FilterOperation.EQ));
}

/**
* Create a parent qualifier that contains the given qualifiers combined using logical OR
*
* @param qualifiers Two or more qualifiers
* @return Parent qualifier
*/
public static Qualifier or(Qualifier... qualifiers) {
return new Qualifier(new ConjunctionQualifierBuilder()
.setFilterOperation(FilterOperation.OR)
.setQualifiers(qualifiers));
}

/**
* Create a parent qualifier that contains the given qualifiers combined using logical AND
*
* @param qualifiers Two or more qualifiers
* @return Parent qualifier
*/
public static Qualifier and(Qualifier... qualifiers) {
return new Qualifier(new ConjunctionQualifierBuilder()
.setFilterOperation(FilterOperation.AND)
.setQualifiers(qualifiers));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

import org.springframework.data.aerospike.query.Qualifier;

import java.util.Objects;

/**
* @author Michael Zhang
* @author Jeff Boone
Expand All @@ -30,6 +28,10 @@ public AerospikeCriteria(Qualifier.Builder builder) {
super(builder);
}

public AerospikeCriteria(Qualifier qualifier) {
super(qualifier);
}

@Override
public Qualifier getCriteriaObject() {
return this;
Expand All @@ -41,6 +43,6 @@ public String getKey() {
}

protected static boolean isSingleIdQuery(AerospikeCriteria criteria) {
return Objects.equals(criteria.getField(), Qualifier.ID_VALUE);
return criteria.hasSingleId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VALUES_NOT_CONTAIN;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VAL_CONTAINING_BY_KEY;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VAL_EQ_BY_KEY;
import static org.springframework.data.aerospike.query.Qualifier.forId;
import static org.springframework.data.aerospike.query.Qualifier.forIds;

/**
* @author Peter Milne
Expand Down Expand Up @@ -121,8 +123,7 @@ private AerospikeCriteria create(Part part, AerospikePersistentProperty property
case NOT_IN -> getCriteria(part, property, v1, null, parameters, FilterOperation.NOT_IN);
case TRUE -> getCriteria(part, property, true, null, parameters, FilterOperation.EQ);
case FALSE -> getCriteria(part, property, false, null, parameters, FilterOperation.EQ);
case EXISTS, IS_NOT_NULL ->
getCriteria(part, property, null, null, parameters, FilterOperation.IS_NOT_NULL);
case EXISTS, IS_NOT_NULL -> getCriteria(part, property, null, null, parameters, IS_NOT_NULL);
case IS_NULL -> getCriteria(part, property, null, null, parameters, IS_NULL);
default -> throw new IllegalArgumentException("Unsupported keyword '" + part.getType() + "'");
};
Expand All @@ -146,7 +147,12 @@ public AerospikeCriteria getCriteria(Part part, AerospikePersistentProperty prop
String dotPath = null;
Object value3 = null;

if (property.isCollectionLike()) {
if (property.isIdProperty()) {
if (value1 instanceof Collection<?>) {
return new AerospikeCriteria(forIds(((Collection<?>) value1).toArray(String[]::new)));
}
return new AerospikeCriteria(forId((String) value1));
} else if (property.isCollectionLike()) {
List<Object> params = new ArrayList<>();
parameters.forEachRemaining(params::add);

Expand All @@ -173,7 +179,7 @@ public AerospikeCriteria getCriteria(Part part, AerospikePersistentProperty prop
case VALUE -> op = MAP_VALUES_CONTAIN;
}
} else {
op = FilterOperation.MAP_VAL_EQ_BY_KEY;
op = MAP_VAL_EQ_BY_KEY;
dotPath = part.getProperty().toDotPath() + "." + Value.get(value1);
setQbValuesForMapByKey(qb, value1, nextParam);
}
Expand Down Expand Up @@ -220,7 +226,7 @@ public AerospikeCriteria getCriteria(Part part, AerospikePersistentProperty prop
}
params = params.stream().limit(params.size() - 1L).collect(Collectors.toList());
} else {
op = FilterOperation.MAP_VAL_EQ_BY_KEY;
op = MAP_VAL_EQ_BY_KEY;
dotPath = part.getProperty().toDotPath() + "." + Value.get(value1);
}

Expand Down Expand Up @@ -301,11 +307,7 @@ private AerospikeCriteria aerospikeCriteriaAndConcatenated(List<Object> params,
null, null, dotPath).build();
}

return new AerospikeCriteria(
Qualifier.builder()
.setQualifiers(qualifiers)
.setFilterOperation(FilterOperation.AND)
);
return new AerospikeCriteria(Qualifier.and(qualifiers));
} else {
qualifiers = new Qualifier[params.size()];
for (int i = 0; i < params.size(); i++) {
Expand All @@ -315,11 +317,7 @@ private AerospikeCriteria aerospikeCriteriaAndConcatenated(List<Object> params,
}
}

return new AerospikeCriteria(
Qualifier.builder()
.setQualifiers(qualifiers)
.setFilterOperation(FilterOperation.AND)
);
return new AerospikeCriteria(Qualifier.and(qualifiers));
}

private Qualifier.QualifierBuilder setQualifierBuilderValues(Qualifier.QualifierBuilder qb, String fieldName,
Expand Down Expand Up @@ -379,18 +377,13 @@ protected AerospikeCriteria and(Part part, AerospikeCriteria base, Iterator<Obje
context.getPersistentPropertyPath(part.getProperty());
AerospikePersistentProperty property = path.getLeafProperty();

return new AerospikeCriteria(Qualifier.builder()
.setFilterOperation(FilterOperation.AND)
.setQualifiers(base, create(part, property, iterator))
);
return new AerospikeCriteria(Qualifier.and(base, create(part, property,
iterator)));
}

@Override
protected AerospikeCriteria or(AerospikeCriteria base, AerospikeCriteria criteria) {
return new AerospikeCriteria(Qualifier.builder()
.setFilterOperation(FilterOperation.OR)
.setQualifiers(base, criteria)
);
return new AerospikeCriteria(Qualifier.or(base, criteria));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ public void countFindsAllItemsByGivenCriteria() {
.build();

long vasya51Count = template.count(
new Query(new AerospikeCriteria(Qualifier.builder()
.setFilterOperation(FilterOperation.AND)
.setQualifiers(qbIs1, qbIs2)
)),
Person.class
new Query(new AerospikeCriteria(Qualifier.and(qbIs1, qbIs2))), Person.class
);

assertThat(vasya51Count).isEqualTo(1);
Expand Down
Loading

0 comments on commit f5ba6e4

Please sign in to comment.