Skip to content

FMWK-47 Support Query annotation #871

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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 0 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ Data Aerospike 5.0.0: Important Updates]
== Spring Data Aerospike Compatibility

.Compatibility Table
[%collapsible]
====
[width="100%",cols="<24%,<14%,<18%,<26%,<18%",options="header",]
|===
|Spring Data Aerospike |Spring Boot |Aerospike Client |Aerospike Reactor Client |Aerospike Server
Expand Down Expand Up @@ -97,7 +95,6 @@ Data Aerospike 5.0.0: Important Updates]

|1.2.1.RELEASE |1.5.x |4.1.x | |
|===
====

== Quick Start

Expand Down
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<maven.gpg.plugin>1.6</maven.gpg.plugin>
<aerospike-client-jdk8>9.0.5</aerospike-client-jdk8>
<aerospike-reactor-client>9.0.5</aerospike-reactor-client>
<aerospike-expression-dsl.version>0.1.0</aerospike-expression-dsl.version>
<reactor-test>3.7.4</reactor-test>
<embedded-aerospike>3.1.11</embedded-aerospike>
<jodatime>2.13.1</jodatime>
Expand Down Expand Up @@ -195,6 +196,11 @@
<artifactId>aerospike-reactor-client</artifactId>
<version>${aerospike-reactor-client}</version>
</dependency>
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-expression-dsl</artifactId>
<version>${aerospike-expression-dsl.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
Expand Down Expand Up @@ -243,6 +249,10 @@
<groupId>com.aerospike</groupId>
<artifactId>aerospike-reactor-client</artifactId>
</dependency>
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-expression-dsl</artifactId>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
Expand Down
1 change: 1 addition & 0 deletions src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ include::reference/query-methods-pojo.adoc[]
include::reference/query-methods-id.adoc[]
include::reference/query-methods-combined.adoc[]
include::reference/query-methods-modification.adoc[]
include::reference/query-annotation.adoc[]
include::reference/aerospike-object-mapping.adoc[]
include::reference/aerospike-custom-converters.adoc[]
include::reference/template.adoc[]
Expand Down
75 changes: 75 additions & 0 deletions src/main/asciidoc/reference/query-annotation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[[aerospike.query-annotation]]
= Query Annotation

For details on standard Spring Data Aerospike repository queries see <<aerospike.query-methods-preface, Query Methods>>.

Another way of defining queries is leveraging `@Query` annotation for particular method names in the repository.

This annotation allows to define custom query expressions directly within methods. Instead of relying on Spring Data's
automatic query derivation, you can use `@Query` to specify the exact String-based expression that should be executed. This provides greater flexibility for complex or specific data retrieval needs.

NOTE: DSL expression provided via `@Query` annotation replaces the original query method that it is added to.

== Using Placeholders

Element `?0` in the following example is a placeholder that gets substituted with the actual query parameter, number 0 in it is referring to the parameter's index.

[source,java]
----
public interface PersonRepository extends AerospikeRepository<Person, Long> {

@Query(expression = "$.lastName == ?0")
List<Person> findByLastName(String lastName);
}
----

NOTE: Currently placeholders in `@Query` annotation can substitute only String, number and boolean parameters.

== Using Static Expression

DSL expression can be provided via `@Query` without placeholders, thus discarding query parameters. Here is an example:

[source,java]
----
public interface PersonRepository extends AerospikeRepository<Person, Long> {

@Query(expression = "$.lastName == 'Simpson''")
List<Person> findByLastName(String lastName);
}
----

NOTE: Such a query will always run with the same static expression " `lastName` bin has value 'Simpson' ".

== Examples

Below is an example of an interface with several `@Query`-annotated methods.

[source,java]
----
public interface PersonRepository extends AerospikeRepository<Person, Long> {

@Query(expression = "$.lastName == ?0")
List<Person> findByLastName(String lastName);

@Query(expression = "$.age > ?0")
List<P> findByAgeGreaterThan(int age);

@Query(expression = "$.isActive.get(type: BOOL) == ?0")
List<P> findByIsActive(boolean isActive);
}
----

== Custom Queries

Just like with regular and metadata queries, you can use <<find-using-query, custom queries>> with DSL expressions when such flexibility is required or when there is no need to annotate repository methods.

[source,java]
----
// creating an expression "firsName is equal to John" using DSL expression
Qualifier firstNameEqJohn = Qualifier.dslStringBuilder()
.setDSLString("$.firstName == 'John'")
.build();
result = repository.findUsingQuery(new Query(firstNameEqJohn))
assertThat(result).containsOnly(john);
----

2 changes: 1 addition & 1 deletion src/main/asciidoc/reference/query-methods-collection.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#list[informa
----
findByStringListBetween(Collection<String> lowerLimit, Collection<String> upperLimit)
----
|...where x.stringList between ? and ?
|...where x.stringList between ? (inclusive) and ? (exclusive)
|only scan
|Find records where `stringList` bin value is in the range between the given arguments.
See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#list[information about ordering].
Expand Down
2 changes: 1 addition & 1 deletion src/main/asciidoc/reference/query-methods-map.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#map[informat
----
findByStringMapBetween(Map<String, String> lowerLimit, Map<String, String> upperLimit)
----
|...where x.stringMap between ? and ?
|...where x.stringMap between ? (inclusive) and ? (exclusive)
|only scan
|Find records where `stringMap` bin value is in the range between the given arguments.
See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#map[information about ordering].
Expand Down
17 changes: 13 additions & 4 deletions src/main/asciidoc/reference/query-methods-modification.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ post-processing step. This limiting can affect performance, depending on the num
results and limiting parameters.

[[find-using-query]]
== Find Using Query
== Custom Queries

User can perform a custom `Query` for finding matching entities in the Aerospike database.
A `Query` can be created using a `Qualifier` which represents an expression.
You can perform a custom `Query` for finding matching entities in the Aerospike database via `findUsingQuery()` method.

A `Query` object can be created using a `Qualifier` which represents an expression.
It may contain other qualifiers and combine them using either `AND` or `OR`.

`Qualifier` can be created for regular bins, metadata and ids (primary keys).
`Qualifier` can be created for regular bins, metadata and ids (primary keys). It can also be created for a DSL expression.

Below is an example of different variations:

[source,java]
Expand Down Expand Up @@ -73,4 +75,11 @@ Below is an example of different variations:
// expressions are combined using AND
result = repository.findUsingQuery(new Query(Qualifier.and(firstNameEqJohn, keyEqJohnsId, sinceUpdateTimeLt50Seconds)));
assertThat(result).containsOnly(john);

// creating an expression "firsName is equal to John" using DSL expression
Qualifier firstNameEqJohn = Qualifier.dslStringBuilder()
.setDSLString("$.firstName == 'John'")
.build();
result = repository.findUsingQuery(new Query(firstNameEqJohn))
assertThat(result).containsOnly(john);
----
2 changes: 1 addition & 1 deletion src/main/asciidoc/reference/query-methods-pojo.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#map[informat
----
findByAddressBetween(Address lowerLimit, Address upperLimit)
----
|...where x.address between ? and ?
|...where x.address between ? (inclusive) and ? (exclusive)
|only scan
|Find records where `address` bin value (POJOs are stored in AerospikeDB as maps) is in the range between
the given arguments. See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#map[information about ordering].
Expand Down
5 changes: 3 additions & 2 deletions src/main/asciidoc/reference/query-methods-preface.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[[aerospike.query-methods-preface]]
= Query Methods

Spring Data Aerospike supports defining queries by method name in the Repository interface so that the implementation
Spring Data Aerospike supports defining queries by method names in the Repository interface so that the implementation
is generated.
The format of method names is fairly flexible, comprising a verb and criteria.

Expand All @@ -19,7 +19,8 @@ Repository queries in Spring Data Aerospike can be divided into 3 groups:

* Queries that only use <<aerospike.scan-operation, scan operation>>

* Queries that use both secondary index and scan (when looking for a nested integer or string property).
* Queries that use both secondary index and scan (e.g., for indexed queries combined with AND
or when looking for a nested integer or string property).

If no corresponding secondary index is found, each query does a fallback to using scan only.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ findByAgeBetween(int lowerLimit, int upperLimit)

findByFirstNameBetween(String lowerLimit, String upperLimit)
----
|...where x.age between ? and ?
|...where x.age between ? (inclusive) and ? (exclusive)

...where x.firstName between ? and ?
...where x.firstName between ? (inclusive) and ? (exclusive)
|only for integers
|Strings are compared by order of each byte, assuming they have UTF-8 encoding.
See https://docs.aerospike.com/server/guide/data-types/cdt-ordering#string[information about ordering].
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.springframework.data.aerospike.annotation;

import org.springframework.data.annotation.QueryAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Query annotation to define expressions for repository methods. Allows using either a fixed DSL String or
* a String with placeholders like {@code ?0}, {@code ?1} etc.
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Documented
@QueryAnnotation
@Beta
public @interface Query {

/**
* Use expression DSL to define the query taking precedence over method name
*
* @return empty {@link String} by default.
*/
String expression();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.data.aerospike.config;

import com.aerospike.client.IAerospikeClient;
import com.aerospike.dsl.DSLParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
Expand All @@ -31,6 +32,7 @@
import org.springframework.data.aerospike.query.StatementBuilder;
import org.springframework.data.aerospike.query.cache.IndexInfoParser;
import org.springframework.data.aerospike.query.cache.IndexRefresher;
import org.springframework.data.aerospike.query.cache.IndexesCacheHolder;
import org.springframework.data.aerospike.query.cache.IndexesCacheUpdater;
import org.springframework.data.aerospike.query.cache.InternalIndexOperations;
import org.springframework.data.aerospike.server.version.ServerVersionSupport;
Expand All @@ -45,12 +47,13 @@ public AerospikeTemplate aerospikeTemplate(IAerospikeClient aerospikeClient,
AerospikeMappingContext aerospikeMappingContext,
AerospikeExceptionTranslator aerospikeExceptionTranslator,
QueryEngine queryEngine, IndexRefresher indexRefresher,
IndexesCacheHolder indexCacheHolder,
ServerVersionSupport serverVersionSupport,
AerospikeSettings settings)
AerospikeSettings settings, DSLParser dslParser)
{
return new AerospikeTemplate(aerospikeClient, settings.getDataSettings().getNamespace(),
mappingAerospikeConverter, aerospikeMappingContext, aerospikeExceptionTranslator, queryEngine,
indexRefresher, serverVersionSupport);
indexRefresher, indexCacheHolder, serverVersionSupport, dslParser);
}

@Bean(name = "aerospikeQueryEngine")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.aerospike.client.policy.ClientPolicy;
import com.aerospike.client.reactor.AerospikeReactorClient;
import com.aerospike.client.reactor.IAerospikeReactorClient;
import com.aerospike.dsl.DSLParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
Expand All @@ -33,6 +34,7 @@
import org.springframework.data.aerospike.query.ReactorQueryEngine;
import org.springframework.data.aerospike.query.StatementBuilder;
import org.springframework.data.aerospike.query.cache.IndexInfoParser;
import org.springframework.data.aerospike.query.cache.IndexesCacheHolder;
import org.springframework.data.aerospike.query.cache.IndexesCacheUpdater;
import org.springframework.data.aerospike.query.cache.InternalIndexOperations;
import org.springframework.data.aerospike.query.cache.ReactorIndexRefresher;
Expand All @@ -54,12 +56,13 @@ public ReactiveAerospikeTemplate reactiveAerospikeTemplate(MappingAerospikeConve
IAerospikeReactorClient aerospikeReactorClient,
ReactorQueryEngine reactorQueryEngine,
ReactorIndexRefresher reactorIndexRefresher,
IndexesCacheHolder indexCacheHolder,
ServerVersionSupport serverVersionSupport,
AerospikeSettings settings)
AerospikeSettings settings, DSLParser dslParser)
{
return new ReactiveAerospikeTemplate(aerospikeReactorClient, settings.getDataSettings().getNamespace(),
mappingAerospikeConverter, aerospikeMappingContext, aerospikeExceptionTranslator,
reactorQueryEngine, reactorIndexRefresher, serverVersionSupport);
reactorQueryEngine, reactorIndexRefresher, indexCacheHolder, serverVersionSupport, dslParser);
}

@Bean(name = "reactiveAerospikeQueryEngine")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import com.aerospike.client.async.EventPolicy;
import com.aerospike.client.async.NettyEventLoops;
import com.aerospike.client.policy.ClientPolicy;
import com.aerospike.dsl.DSLParser;
import com.aerospike.dsl.DSLParserImpl;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
Expand Down Expand Up @@ -327,4 +329,9 @@ protected AerospikeSettings aerospikeSettings(AerospikeDataSettings dataSettings

return new AerospikeSettings(connectionSettings, dataSettings);
}

@Bean
public DSLParser dslParser() {
return new DSLParserImpl();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ public interface AerospikeOperations {
* @param document The document to be inserted. Must not be {@literal null}.
* @throws OptimisticLockingFailureException if the document has a version attribute with a different value from
* that found on server.
* @throws DataAccessException If operation failed (see
* {@link DefaultAerospikeExceptionTranslator} for details).
* @throws DataAccessException If operation failed (see {@link DefaultAerospikeExceptionTranslator}
* for details).
*/
<T> void insert(T document);

Expand All @@ -200,8 +200,8 @@ public interface AerospikeOperations {
* @param setName Set name to override the set associated with the document.
* @throws OptimisticLockingFailureException if the document has a version attribute with a different value from
* that found on server.
* @throws DataAccessException If operation failed (see
* {@link DefaultAerospikeExceptionTranslator} for details).
* @throws DataAccessException If operation failed (see {@link DefaultAerospikeExceptionTranslator}
* for details).
*/
<T> void insert(T document, String setName);

Expand Down
Loading