Skip to content
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

FMWK-255 Add qualifier builders #644

Merged
merged 4 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

@roimenashe roimenashe Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here I think, not sure about the "for" prefix maybe IdEquals for single id comparison and IdIn() for multiple ids comparison? lets talk about it tomorrow

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
Loading