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

Bsb/v0.9.0 #404

Merged
merged 7 commits into from
Apr 8, 2024
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ inherited from the parent poms):
<path>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down Expand Up @@ -453,7 +453,7 @@ repositories {
### Dependency
```groovy
ext {
redisOmVersion = '0.8.9'
redisOmVersion = '0.9.0'
}

dependencies {
Expand Down
4 changes: 2 additions & 2 deletions demos/roms-documents/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -147,7 +147,7 @@
<path>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down
2 changes: 1 addition & 1 deletion demos/roms-hashes/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
2 changes: 1 addition & 1 deletion demos/roms-permits/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion demos/roms-vss/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring-parent</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
<name>redis-om-spring-parent</name>
<packaging>pom</packaging>

Expand Down
14 changes: 9 additions & 5 deletions redis-om-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.9</version>
<version>0.9.0</version>
<packaging>jar</packaging>

<name>redis-om-spring</name>
Expand Down Expand Up @@ -62,13 +62,13 @@
<spring.version>3.2.3</spring.version>
<sdr.version>3.2.3</sdr.version>
<jedis.version>5.0.2</jedis.version>
<cdi>1.0</cdi>
<cdi>2.0-PFD</cdi>
<auto-service.version>1.1.1</auto-service.version>
<testcontainers.redis.version>2.2.0</testcontainers.redis.version>
<testcontainers.redis.junit.version>2.2.0</testcontainers.redis.junit.version>
<guava.version>33.0.0-jre</guava.version>
<ulid.version>5.2.3</ulid.version>
<lombok.version>1.18.30</lombok.version>
<lombok.version>1.18.32</lombok.version>
<commons-lang.version>3.13.0</commons-lang.version>
<javapoet.version>1.13.0</javapoet.version>
<assertj.version>3.25.3</assertj.version>
Expand Down Expand Up @@ -123,6 +123,10 @@
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>maven-central</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>

<distributionManagement>
Expand Down Expand Up @@ -252,7 +256,7 @@
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.3</version>
<version>1.19.7</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -262,7 +266,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.9</version>
<version>0.8.12</version>
<executions>
<execution>
<goals>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ public void createIndexFor(Class<?> cl) {
params.prefix(entityPrefix);
addKeySpaceMapping(entityPrefix, cl);
updateTTLSettings(cl, entityPrefix, isDocument, document, allClassFields);
opsForSearch.createIndex(params, fields);
entityClassToSchema.put(cl, fields);
opsForSearch.createIndex(params, fields);
} catch (Exception e) {
logger.warn(String.format(SKIPPING_INDEX_CREATION, indexName, e.getMessage()));
}
Expand Down Expand Up @@ -766,12 +766,12 @@ private Optional<SchemaField> createIndexedFieldForReferenceIdField( //
}

if (Number.class.isAssignableFrom(idClass)) {
result = Optional.of(NumericField.of(fieldName).sortable());
result = Optional.of(isDocument ? NumericField.of(fieldName) : NumericField.of(fieldName).sortable());
} else {
result = Optional.of(TagField.of(fieldName).separator('|').sortable());
result = Optional.of(isDocument ? TagField.of(fieldName).separator('|') : TagField.of(fieldName).separator('|').sortable());
}
} else {
result = Optional.of(TagField.of(fieldName).separator('|').sortable());
result = Optional.of(isDocument ? TagField.of(fieldName).separator('|') : TagField.of(fieldName).separator('|').sortable());
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class QueryUtils {
',', '.', '<', '>', '{', '}', '[', //
']', '"', '\'', ':', ';', '!', '@', //
'#', '$', '%', '^', '&', '*', '(', //
')', '-', '+', '=', '~', '|' //
')', '-', '+', '=', '~', '|', '/' //
);

public static String escape(String text) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class RediSearchQuery implements RepositoryQuery {
Expand Down Expand Up @@ -84,13 +85,14 @@ public class RediSearchQuery implements RepositoryQuery {

private final RedisModulesOperations<String> modulesOperations;

private boolean isANDQuery = false;
private final boolean isANDQuery;

private final BloomQueryExecutor bloomQueryExecutor;
private final CuckooQueryExecutor cuckooQueryExecutor;
private final AutoCompleteQueryExecutor autoCompleteQueryExecutor;
private final GsonBuilder gsonBuilder;
private Gson gson;
private boolean isNullParamQuery;

@SuppressWarnings("unchecked")
public RediSearchQuery(//
Expand Down Expand Up @@ -120,6 +122,10 @@ public RediSearchQuery(//
@SuppressWarnings("rawtypes")
Class[] params = queryMethod.getParameters().stream().map(Parameter::getType).toArray(Class[]::new);
hasLanguageParameter = Arrays.stream(params).anyMatch(c -> c.isAssignableFrom(SearchLanguage.class));
isANDQuery = QueryClause.hasContainingAllClause(queryMethod.getName());

String methodName = isANDQuery ? QueryClause.getPostProcessMethodName(queryMethod.getName())
: queryMethod.getName();

try {
java.lang.reflect.Method method = repoClass.getMethod(queryMethod.getName(), params);
Expand Down Expand Up @@ -210,31 +216,45 @@ public RediSearchQuery(//
} else if (queryMethod.getName().startsWith(AutoCompleteQueryExecutor.AUTOCOMPLETE_PREFIX)) {
this.type = RediSearchQueryType.AUTOCOMPLETE;
} else {
isANDQuery = QueryClause.hasContainingAllClause(queryMethod.getName());
PartTree pt = new PartTree(methodName, metadata.getDomainType());

String methodName = isANDQuery ? QueryClause.getPostProcessMethodName(queryMethod.getName())
: queryMethod.getName();
List<String> nullParamNames = new ArrayList<>();
List<String> notNullParamNames = new ArrayList<>();

PartTree pt = new PartTree(methodName, metadata.getDomainType());
processPartTree(pt);
pt.getParts().forEach(part -> {
if (part.getType() == Part.Type.IS_NULL) {
nullParamNames.add(part.getProperty().getSegment());
} else if (part.getType() == Part.Type.IS_NOT_NULL) {
notNullParamNames.add(part.getProperty().getSegment());
}
});

this.isNullParamQuery = !nullParamNames.isEmpty() || !notNullParamNames.isEmpty();
this.type = RediSearchQueryType.QUERY;
this.returnFields = new String[] {};
processPartTree(pt, nullParamNames, notNullParamNames);
}
} catch (NoSuchMethodException | SecurityException e) {
logger.debug(String.format("Could not resolved query method %s(%s): %s", queryMethod.getName(),
Arrays.toString(params), e.getMessage()));
}
}

private void processPartTree(PartTree pt) {
private void processPartTree(PartTree pt, List<String> nullParamNames, List<String> notNullParamNames) {
pt.stream().forEach(orPart -> {
List<Pair<String, QueryClause>> orPartParts = new ArrayList<>();
orPart.iterator().forEachRemaining(part -> {
PropertyPath propertyPath = part.getProperty();

List<PropertyPath> path = StreamSupport.stream(propertyPath.spliterator(), false).toList();
orPartParts.addAll(extractQueryFields(domainType, part, path));

String paramName = path.get(path.size() - 1).getSegment();
if (nullParamNames.contains(paramName)) {
orPartParts.add(Pair.of(paramName, QueryClause.IS_NULL));
} else if (notNullParamNames.contains(paramName)) {
orPartParts.add(Pair.of(paramName, QueryClause.IS_NOT_NULL));
} else {
orPartParts.addAll(extractQueryFields(domainType, part, path));
}
});
queryOrParts.add(orPartParts);
});
Expand Down Expand Up @@ -357,7 +377,7 @@ public Object execute(Object[] parameters) {
} else if (maybeCuckooFilter.isPresent()) {
return cuckooQueryExecutor.executeCuckooQuery(parameters, maybeCuckooFilter.get());
} else if (type == RediSearchQueryType.QUERY) {
return executeQuery(parameters);
return !isNullParamQuery ? executeQuery(parameters) : executeNullQuery(parameters);
} else if (type == RediSearchQueryType.AGGREGATION) {
return executeAggregation(parameters);
} else if (type == RediSearchQueryType.TAGVALS) {
Expand All @@ -378,7 +398,8 @@ public QueryMethod getQueryMethod() {

private Object executeQuery(Object[] parameters) {
SearchOperations<String> ops = modulesOperations.opsForSearch(searchIndex);
String preparedQuery = prepareQuery(parameters);
boolean excludeNullParams = !isNullParamQuery;
String preparedQuery = prepareQuery(parameters, excludeNullParams);
Query query = new Query(preparedQuery);
query.returnFields(returnFields);

Expand Down Expand Up @@ -587,7 +608,7 @@ private Object executeFtTagVals() {
return ops.tagVals(this.value);
}

private String prepareQuery(final Object[] parameters) {
private String prepareQuery(final Object[] parameters, boolean excludeNullParams) {
logger.debug(String.format("parameters: %s", Arrays.toString(parameters)));
List<Object> params = new ArrayList<>(Arrays.asList(parameters));
StringBuilder preparedQuery = new StringBuilder();
Expand All @@ -597,6 +618,9 @@ private String prepareQuery(final Object[] parameters) {
preparedQuery.append(queryOrParts.stream().map(qop -> {
String orPart = multipleOrParts ? "(" : "";
orPart = orPart + qop.stream().map(fieldClauses -> {
if (excludeNullParams && (fieldClauses.getSecond() == QueryClause.IS_NULL || fieldClauses.getSecond() == QueryClause.IS_NOT_NULL)) {
return "";
}
String fieldName = fieldClauses.getFirst();
QueryClause queryClause = fieldClauses.getSecond();
int paramsCnt = queryClause.getClauseTemplate().getNumberOfArguments();
Expand Down Expand Up @@ -645,6 +669,10 @@ private String prepareQuery(final Object[] parameters) {
}
}

if (preparedQuery.toString().isBlank()) {
preparedQuery.append("*");
}

logger.debug(String.format("query: %s", preparedQuery));

return preparedQuery.toString();
Expand All @@ -656,4 +684,89 @@ private Gson getGson() {
}
return gson;
}

private Object executeNullQuery(Object[] parameters) {
SearchOperations<String> ops = modulesOperations.opsForSearch(searchIndex);
String baseQuery = prepareQuery(parameters, true);

AggregationBuilder aggregation = new AggregationBuilder(baseQuery);

// Load fields with IS_NULL or IS_NOT_NULL query clauses
String[] fields = Stream.concat(Stream.of("@__key"), queryOrParts.stream().flatMap(List::stream)
.filter(pair -> pair.getSecond() == QueryClause.IS_NULL || pair.getSecond() == QueryClause.IS_NOT_NULL)
.map(pair -> String.format("@%s", pair.getFirst()))).toArray(String[]::new);

aggregation.load(fields);

// Apply exists or !exists filter for null parameters
for (List<Pair<String, QueryClause>> orPartParts : queryOrParts) {
for (Pair<String, QueryClause> pair : orPartParts) {
if (pair.getSecond() == QueryClause.IS_NULL) {
aggregation.filter("!exists(@" + pair.getFirst() + ")");
} else if (pair.getSecond() == QueryClause.IS_NOT_NULL) {
aggregation.filter("exists(@" + pair.getFirst() + ")");
}
}
}

// sort by
Optional<Pageable> maybePageable;

boolean needsLimit = true;
if (queryMethod.isPageQuery()) {
maybePageable = Arrays.stream(parameters).filter(Pageable.class::isInstance).map(Pageable.class::cast)
.findFirst();

if (maybePageable.isPresent()) {
Pageable pageable = maybePageable.get();
if (!pageable.isUnpaged()) {
aggregation.limit(Math.toIntExact(pageable.getOffset()), pageable.getPageSize());
needsLimit = false;

// sort by
pageable.getSort();
for (Order order : pageable.getSort()) {
if (order.isAscending()) {
aggregation.sortByAsc(order.getProperty());
} else {
aggregation.sortByDesc(order.getProperty());
}
}
}
}
}

if ((sortBy != null && !sortBy.isBlank())) {
aggregation.sortByAsc(sortBy);
} else if (!aggregationSortedFields.isEmpty()) {
if (aggregationSortByMax != null) {
aggregation.sortBy(aggregationSortByMax, aggregationSortedFields.toArray(new SortedField[] {}));
} else {
aggregation.sortBy(aggregationSortedFields.toArray(new SortedField[] {}));
}
}

// limit
if (needsLimit) {
if ((limit != null) || (offset != null)) {
aggregation.limit(offset != null ? offset : 0, limit != null ? limit : 0);
} else {
aggregation.limit(0, redisOMProperties.getRepository().getQuery().getLimit());
}
}

// Execute the aggregation query
AggregationResult aggregationResult = ops.aggregate(aggregation);

// extract the keys from the aggregation result
String[] keys = aggregationResult.getResults().stream().map(d -> d.get("__key").toString()).toArray(String[]::new);

var entities = modulesOperations.opsForJSON().mget(domainType, keys);

if (!queryMethod.isCollectionQuery()) {
return entities.isEmpty() ? null : entities.get(0);
} else {
return entities;
}
}
}
Loading
Loading