diff --git a/CHANGELOG.md b/CHANGELOG.md index 464beda5b7..2d46dcee0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Java: Added `FT.DROPINDEX` ([#2440](https://github.com/valkey-io/valkey-glide/pull/2440)) * Java: Added `FT.SEARCH` ([#2439](https://github.com/valkey-io/valkey-glide/pull/2439)) * Java: Added `FT.AGGREGATE` ([#2466](https://github.com/valkey-io/valkey-glide/pull/2466)) +* Java: Added `FT.PROFILE` ([#2473](https://github.com/valkey-io/valkey-glide/pull/2473)) * Java: Added `JSON.SET` and `JSON.GET` ([#2462](https://github.com/valkey-io/valkey-glide/pull/2462)) * Java: Added `JSON.ARRINSERT` and `JSON.ARRLEN` ([#2476](https://github.com/valkey-io/valkey-glide/pull/2476)) * Java: Added `FT.ALIASADD`, `FT.ALIASDEL`, `FT.ALIASUPDATE` ([#2442](https://github.com/valkey-io/valkey-glide/pull/2442)) diff --git a/glide-core/src/client/value_conversion.rs b/glide-core/src/client/value_conversion.rs index d16d4ef939..f4706e762a 100644 --- a/glide-core/src/client/value_conversion.rs +++ b/glide-core/src/client/value_conversion.rs @@ -24,6 +24,7 @@ pub(crate) enum ExpectedReturnType<'a> { ArrayOfDoubleOrNull, FTAggregateReturnType, FTSearchReturnType, + FTProfileReturnType(&'a Option>), FTInfoReturnType, Lolwut, ArrayOfStringAndArrays, @@ -939,7 +940,7 @@ pub(crate) fn convert_to_expected_type( let Value::Array(fields) = aggregation else { return Err(( ErrorKind::TypeError, - "Response couldn't be converted for FT.AGGREGATION", + "Response couldn't be converted for FT.AGGREGATE", format!("(`fields` was {:?})", get_value_type(&aggregation)), ) .into()); @@ -954,7 +955,7 @@ pub(crate) fn convert_to_expected_type( } _ => Err(( ErrorKind::TypeError, - "Response couldn't be converted to FT.AGGREGATION", + "Response couldn't be converted for FT.AGGREGATE", format!("(response was {:?})", get_value_type(&value)), ) .into()), @@ -1106,6 +1107,44 @@ pub(crate) fn convert_to_expected_type( ) .into()) }, + ExpectedReturnType::FTProfileReturnType(type_of_query) => match value { + /* + Example of the response + 1) + 2) 1) 1) "parse.time" + 2) 119 + 2) 1) "all.count" + 2) 4 + 3) 1) "sync.time" + 2) 0 + ... + + Converting response to + 1) + 2) 1# "parse.time" => 119 + 2# "all.count" => 4 + 3# "sync.time" => 0 + ... + + Converting first array element as it is needed for the inner query and second element to a map. + */ + Value::Array(mut array) if array.len() == 2 => { + let res = vec![ + convert_to_expected_type(array.remove(0), *type_of_query)?, + convert_to_expected_type(array.remove(0), Some(ExpectedReturnType::Map { + key_type: &None, + value_type: &None, + }))?]; + + Ok(Value::Array(res)) + }, + _ => Err(( + ErrorKind::TypeError, + "Response couldn't be converted for FT.PROFILE", + format!("(response was {:?})", get_value_type(&value)), + ) + .into()) + } } } @@ -1472,6 +1511,14 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option { }), b"FT.AGGREGATE" => Some(ExpectedReturnType::FTAggregateReturnType), b"FT.SEARCH" => Some(ExpectedReturnType::FTSearchReturnType), + // TODO replace with tuple + b"FT.PROFILE" => Some(ExpectedReturnType::FTProfileReturnType( + if cmd.arg_idx(2).is_some_and(|a| a == b"SEARCH") { + &Some(ExpectedReturnType::FTSearchReturnType) + } else { + &Some(ExpectedReturnType::FTAggregateReturnType) + }, + )), b"FT.INFO" => Some(ExpectedReturnType::FTInfoReturnType), _ => None, } diff --git a/java/client/src/main/java/glide/api/commands/servermodules/FT.java b/java/client/src/main/java/glide/api/commands/servermodules/FT.java index 38b0ec7096..aa48da11d1 100644 --- a/java/client/src/main/java/glide/api/commands/servermodules/FT.java +++ b/java/client/src/main/java/glide/api/commands/servermodules/FT.java @@ -13,6 +13,7 @@ import glide.api.models.commands.FT.FTAggregateOptions; import glide.api.models.commands.FT.FTCreateOptions; import glide.api.models.commands.FT.FTCreateOptions.FieldInfo; +import glide.api.models.commands.FT.FTProfileOptions; import glide.api.models.commands.FT.FTSearchOptions; import java.util.Arrays; import java.util.Map; @@ -474,6 +475,54 @@ public static CompletableFuture[]> aggregate( .thenApply(res -> castArray(res, Map.class)); } + /** + * Runs a search or aggregation query and collects performance profiling information. + * + * @param client The client to execute the command. + * @param indexName The index name. + * @param options Querying and profiling parameters - see {@link FTProfileOptions}. + * @return A two-element array. The first element contains results of query being profiled, the + * second element stores profiling information. + * @example + *
{@code
+     * var options = FTSearchOptions.builder().params(Map.of(
+     *         gs("query_vec"),
+     *         gs(new byte[] { (byte) 0, (byte) 0, (byte) 0, (byte) 0 })))
+     *     .build();
+     * var result = FT.profile(client, "myIndex", new FTProfileOptions("*=>[KNN 2 @VEC $query_vec]", options)).get();
+     * // result[0] contains `FT.SEARCH` response with the given options and query
+     * // result[1] contains profiling data as a `Map`
+     * }
+ */ + public static CompletableFuture profile( + @NonNull BaseClient client, @NonNull String indexName, @NonNull FTProfileOptions options) { + return profile(client, gs(indexName), options); + } + + /** + * Runs a search or aggregation query and collects performance profiling information. + * + * @param client The client to execute the command. + * @param indexName The index name. + * @param options Querying and profiling parameters - see {@link FTProfileOptions}. + * @return A two-element array. The first element contains results of query being profiled, the + * second element stores profiling information. + * @example + *
{@code
+     * var commandLine = new String[] { "*", "LOAD", "1", "__key", "GROUPBY", "1", "@condition", "REDUCE", "COUNT", "0", "AS", "bicylces" };
+     * var result = FT.profile(client, gs("myIndex"), new FTProfileOptions(QueryType.AGGREGATE, commandLine)).get();
+     * // result[0] contains `FT.AGGREGATE` response with the given command line
+     * // result[1] contains profiling data as a `Map`
+     * }
+ */ + public static CompletableFuture profile( + @NonNull BaseClient client, + @NonNull GlideString indexName, + @NonNull FTProfileOptions options) { + var args = concatenateArrays(new GlideString[] {gs("FT.PROFILE"), indexName}, options.toArgs()); + return executeCommand(client, args, false); + } + /** * Returns information about a given index. * @@ -693,7 +742,6 @@ public static CompletableFuture aliasupdate( public static CompletableFuture aliasupdate( @NonNull BaseClient client, @NonNull GlideString aliasName, @NonNull GlideString indexName) { var args = new GlideString[] {gs("FT.ALIASUPDATE"), aliasName, indexName}; - return executeCommand(client, args, false); } diff --git a/java/client/src/main/java/glide/api/models/commands/FT/FTAggregateOptions.java b/java/client/src/main/java/glide/api/models/commands/FT/FTAggregateOptions.java index 73ffdbf412..700695b7ae 100644 --- a/java/client/src/main/java/glide/api/models/commands/FT/FTAggregateOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/FT/FTAggregateOptions.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.stream.Stream; import lombok.Builder; +import lombok.NonNull; /** * Additional arguments for {@link FT#aggregate(BaseClient, String, String, FTAggregateOptions)} @@ -79,19 +80,19 @@ public FTAggregateOptionsBuilder loadAll() { return this; } - public FTAggregateOptionsBuilder loadFields(String[] fields) { + public FTAggregateOptionsBuilder loadFields(@NonNull String[] fields) { loadFields = toGlideStringArray(fields); loadAll = false; return this; } - public FTAggregateOptionsBuilder loadFields(GlideString[] fields) { + public FTAggregateOptionsBuilder loadFields(@NonNull GlideString[] fields) { loadFields = fields; loadAll = false; return this; } - public FTAggregateOptionsBuilder addExpression(FTAggregateExpression expression) { + public FTAggregateOptionsBuilder addExpression(@NonNull FTAggregateExpression expression) { if (expressions == null) expressions = new ArrayList<>(); expressions.add(expression); return this; @@ -138,11 +139,11 @@ GlideString[] toArgs() { public static class Filter extends FTAggregateExpression { private final GlideString expression; - public Filter(GlideString expression) { + public Filter(@NonNull GlideString expression) { this.expression = expression; } - public Filter(String expression) { + public Filter(@NonNull String expression) { this.expression = gs(expression); } @@ -160,22 +161,22 @@ public static class GroupBy extends FTAggregateExpression { private final GlideString[] properties; private final Reducer[] reducers; - public GroupBy(GlideString[] properties, Reducer[] reducers) { + public GroupBy(@NonNull GlideString[] properties, @NonNull Reducer[] reducers) { this.properties = properties; this.reducers = reducers; } - public GroupBy(String[] properties, Reducer[] reducers) { + public GroupBy(@NonNull String[] properties, @NonNull Reducer[] reducers) { this.properties = toGlideStringArray(properties); this.reducers = reducers; } - public GroupBy(GlideString[] properties) { + public GroupBy(@NonNull GlideString[] properties) { this.properties = properties; this.reducers = new Reducer[0]; } - public GroupBy(String[] properties) { + public GroupBy(@NonNull String[] properties) { this.properties = toGlideStringArray(properties); this.reducers = new Reducer[0]; } @@ -199,25 +200,25 @@ public static class Reducer { private final GlideString[] args; private final String alias; - public Reducer(String function, GlideString[] args, String alias) { + public Reducer(@NonNull String function, @NonNull GlideString[] args, @NonNull String alias) { this.function = function; this.args = args; this.alias = alias; } - public Reducer(String function, GlideString[] args) { + public Reducer(@NonNull String function, @NonNull GlideString[] args) { this.function = function; this.args = args; this.alias = null; } - public Reducer(String function, String[] args, String alias) { + public Reducer(@NonNull String function, @NonNull String[] args, @NonNull String alias) { this.function = function; this.args = toGlideStringArray(args); this.alias = alias; } - public Reducer(String function, String[] args) { + public Reducer(@NonNull String function, @NonNull String[] args) { this.function = function; this.args = toGlideStringArray(args); this.alias = null; @@ -240,12 +241,12 @@ public static class SortBy extends FTAggregateExpression { private final SortProperty[] properties; private final Integer max; - public SortBy(SortProperty[] properties) { + public SortBy(@NonNull SortProperty[] properties) { this.properties = properties; this.max = null; } - public SortBy(SortProperty[] properties, int max) { + public SortBy(@NonNull SortProperty[] properties, int max) { this.properties = properties; this.max = max; } @@ -273,12 +274,12 @@ public static class SortProperty { private final GlideString property; private final SortOrder order; - public SortProperty(GlideString property, SortOrder order) { + public SortProperty(@NonNull GlideString property, @NonNull SortOrder order) { this.property = property; this.order = order; } - public SortProperty(String property, SortOrder order) { + public SortProperty(@NonNull String property, @NonNull SortOrder order) { this.property = gs(property); this.order = order; } @@ -297,12 +298,12 @@ public static class Apply extends FTAggregateExpression { private final GlideString expression; private final GlideString alias; - public Apply(GlideString expression, GlideString alias) { + public Apply(@NonNull GlideString expression, @NonNull GlideString alias) { this.expression = expression; this.alias = alias; } - public Apply(String expression, String alias) { + public Apply(@NonNull String expression, @NonNull String alias) { this.expression = gs(expression); this.alias = gs(alias); } diff --git a/java/client/src/main/java/glide/api/models/commands/FT/FTCreateOptions.java b/java/client/src/main/java/glide/api/models/commands/FT/FTCreateOptions.java index 1cdb6c77d0..c407894cf8 100644 --- a/java/client/src/main/java/glide/api/models/commands/FT/FTCreateOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/FT/FTCreateOptions.java @@ -54,7 +54,7 @@ public GlideString[] toArgs() { } public static class FTCreateOptionsBuilder { - public FTCreateOptionsBuilder prefixes(String[] prefixes) { + public FTCreateOptionsBuilder prefixes(@NonNull String[] prefixes) { this.prefixes = Stream.of(prefixes).map(GlideString::gs).toArray(GlideString[]::new); return this; } diff --git a/java/client/src/main/java/glide/api/models/commands/FT/FTProfileOptions.java b/java/client/src/main/java/glide/api/models/commands/FT/FTProfileOptions.java new file mode 100644 index 0000000000..840a501da7 --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/FT/FTProfileOptions.java @@ -0,0 +1,175 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands.FT; + +import static glide.api.models.GlideString.gs; +import static glide.utils.ArrayTransformUtils.concatenateArrays; + +import glide.api.commands.servermodules.FT; +import glide.api.models.GlideString; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import lombok.NonNull; + +/** Mandatory parameters for {@link FT#profile} command. */ +public class FTProfileOptions { + private final QueryType queryType; + private final boolean limited; + private final GlideString[] query; + + /** Query type being profiled. */ + public enum QueryType { + SEARCH, + AGGREGATE + } + + /** + * Profile a query given as an array of module command line arguments. + * + * @param queryType The query type. + * @param commandLine Command arguments (not including index name). + */ + public FTProfileOptions(@NonNull QueryType queryType, @NonNull GlideString[] commandLine) { + this(queryType, commandLine, false); + } + + /** + * Profile a query given as an array of module command line arguments. + * + * @param queryType The query type. + * @param commandLine Command arguments (not including index name). + */ + public FTProfileOptions(@NonNull QueryType queryType, @NonNull String[] commandLine) { + this(queryType, commandLine, false); + } + + /** + * Profile a query given as an array of module command line arguments. + * + * @param queryType The query type. + * @param commandLine Command arguments (not including index name). + * @param limited Either provide a full verbose output or some brief version (limited). + */ + public FTProfileOptions( + @NonNull QueryType queryType, @NonNull GlideString[] commandLine, boolean limited) { + this.queryType = queryType; + this.query = commandLine; + this.limited = limited; + } + + /** + * Profile a query given as an array of module command line arguments. + * + * @param queryType The query type. + * @param commandLine Command arguments (not including index name). + * @param limited Either provide a full verbose output or some brief version (limited). + */ + public FTProfileOptions( + @NonNull QueryType queryType, @NonNull String[] commandLine, boolean limited) { + this( + queryType, + Stream.of(commandLine).map(GlideString::gs).toArray(GlideString[]::new), + limited); + } + + /** + * Profile an aggregation query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#aggregate} options. + */ + public FTProfileOptions(@NonNull String query, @NonNull FTAggregateOptions options) { + this(gs(query), options); + } + + /** + * Profile an aggregation query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#aggregate} options. + */ + public FTProfileOptions(@NonNull GlideString query, @NonNull FTAggregateOptions options) { + this(QueryType.AGGREGATE, concatenateArrays(new GlideString[] {query}, options.toArgs())); + } + + /** + * Profile a search query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#search} options. + */ + public FTProfileOptions(@NonNull String query, @NonNull FTSearchOptions options) { + this(gs(query), options); + } + + /** + * Profile a search query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#search} options. + */ + public FTProfileOptions(@NonNull GlideString query, @NonNull FTSearchOptions options) { + this(QueryType.SEARCH, concatenateArrays(new GlideString[] {query}, options.toArgs())); + } + + /** + * Profile an aggregation query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#aggregate} options. + * @param limited Either provide a full verbose output or some brief version (limited). + */ + public FTProfileOptions( + @NonNull String query, @NonNull FTAggregateOptions options, boolean limited) { + this(gs(query), options, limited); + } + + /** + * Profile a search query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#search} options. + * @param limited Either provide a full verbose output or some brief version (limited). + */ + public FTProfileOptions( + @NonNull GlideString query, @NonNull FTAggregateOptions options, boolean limited) { + this( + QueryType.AGGREGATE, + concatenateArrays(new GlideString[] {query}, options.toArgs()), + limited); + } + + /** + * Profile an aggregation query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#aggregate} options. + * @param limited Either provide a full verbose output or some brief version (limited). + */ + public FTProfileOptions( + @NonNull String query, @NonNull FTSearchOptions options, boolean limited) { + this(gs(query), options, limited); + } + + /** + * Profile a search query with given parameters. + * + * @param query The query itself. + * @param options {@link FT#search} options. + * @param limited Either provide a full verbose output or some brief version (limited). + */ + public FTProfileOptions( + @NonNull GlideString query, @NonNull FTSearchOptions options, boolean limited) { + this(QueryType.SEARCH, concatenateArrays(new GlideString[] {query}, options.toArgs()), limited); + } + + /** Convert to module API. */ + public GlideString[] toArgs() { + var args = new ArrayList(); + args.add(gs(queryType.toString())); + if (limited) args.add(gs("LIMITED")); + args.add(gs("QUERY")); + args.addAll(List.of(query)); + return args.toArray(GlideString[]::new); + } +} diff --git a/java/client/src/main/java/glide/api/models/commands/FT/FTSearchOptions.java b/java/client/src/main/java/glide/api/models/commands/FT/FTSearchOptions.java index 990eab2cb3..74407c64c0 100644 --- a/java/client/src/main/java/glide/api/models/commands/FT/FTSearchOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/FT/FTSearchOptions.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import lombok.Builder; +import lombok.NonNull; import org.apache.commons.lang3.tuple.Pair; /** Mandatory parameters for {@link FT#search}. */ @@ -84,25 +85,26 @@ void count(boolean count) {} void identifiers(Map identifiers) {} /** Add a field to be returned. */ - public FTSearchOptionsBuilder addReturnField(String field) { + public FTSearchOptionsBuilder addReturnField(@NonNull String field) { this.identifiers$value.put(gs(field), null); return this; } /** Add a field with an alias to be returned. */ - public FTSearchOptionsBuilder addReturnField(String field, String alias) { + public FTSearchOptionsBuilder addReturnField(@NonNull String field, @NonNull String alias) { this.identifiers$value.put(gs(field), gs(alias)); return this; } /** Add a field to be returned. */ - public FTSearchOptionsBuilder addReturnField(GlideString field) { + public FTSearchOptionsBuilder addReturnField(@NonNull GlideString field) { this.identifiers$value.put(field, null); return this; } /** Add a field with an alias to be returned. */ - public FTSearchOptionsBuilder addReturnField(GlideString field, GlideString alias) { + public FTSearchOptionsBuilder addReturnField( + @NonNull GlideString field, @NonNull GlideString alias) { this.identifiers$value.put(field, alias); return this; } diff --git a/java/integTest/src/test/java/glide/modules/VectorSearchTests.java b/java/integTest/src/test/java/glide/modules/VectorSearchTests.java index bb39afe19c..de76985f8e 100644 --- a/java/integTest/src/test/java/glide/modules/VectorSearchTests.java +++ b/java/integTest/src/test/java/glide/modules/VectorSearchTests.java @@ -33,6 +33,7 @@ import glide.api.models.commands.FT.FTCreateOptions.TextField; import glide.api.models.commands.FT.FTCreateOptions.VectorFieldFlat; import glide.api.models.commands.FT.FTCreateOptions.VectorFieldHnsw; +import glide.api.models.commands.FT.FTProfileOptions; import glide.api.models.commands.FT.FTSearchOptions; import glide.api.models.commands.FlushMode; import glide.api.models.commands.InfoOptions.Section; @@ -66,6 +67,7 @@ public static void init() { @AfterAll @SneakyThrows public static void teardown() { + client.flushall(FlushMode.SYNC, ALL_PRIMARIES).get(); client.close(); } @@ -255,22 +257,22 @@ public void ft_search() { }))) .get()); Thread.sleep(DATA_PROCESSING_TIMEOUT); // let server digest the data and update index - var ftsearch = - FT.search( - client, - index, - "*=>[KNN 2 @VEC $query_vec]", - FTSearchOptions.builder() - .params( - Map.of( - gs("query_vec"), - gs( - new byte[] { - (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, - (byte) 0, (byte) 0 - }))) - .build()) - .get(); + + // FT.SEARCH hash_idx1 "*=>[KNN 2 @VEC $query_vec]" PARAMS 2 query_vec + // "\x00\x00\x00\x00\x00\x00\x00\x00" DIALECT 2 + var options = + FTSearchOptions.builder() + .params( + Map.of( + gs("query_vec"), + gs( + new byte[] { + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0 + }))) + .build(); + var query = "*=>[KNN 2 @VEC $query_vec]"; + var ftsearch = FT.search(client, index, query, options).get(); assertArrayEquals( new Object[] { @@ -299,6 +301,9 @@ public void ft_search() { // TODO more tests with json index + var ftprofile = FT.profile(client, index, new FTProfileOptions(query, options)).get(); + assertArrayEquals(ftsearch, (Object[]) ftprofile[0]); + // querying non-existing index var exception = assertThrows( @@ -531,19 +536,15 @@ public void ft_aggregate() { Thread.sleep(DATA_PROCESSING_TIMEOUT); // let server digest the data and update index // FT.AGGREGATE idx:bicycle "*" LOAD 1 "__key" GROUPBY 1 "@condition" REDUCE COUNT 0 AS bicylces - var aggreg = - FT.aggregate( - client, - indexBicycles, - "*", - FTAggregateOptions.builder() - .loadFields(new String[] {"__key"}) - .addExpression( - new GroupBy( - new String[] {"@condition"}, - new Reducer[] {new Reducer("COUNT", new String[0], "bicycles")})) - .build()) - .get(); + var options = + FTAggregateOptions.builder() + .loadFields(new String[] {"__key"}) + .addExpression( + new GroupBy( + new String[] {"@condition"}, + new Reducer[] {new Reducer("COUNT", new String[0], "bicycles")})) + .build(); + var aggreg = FT.aggregate(client, indexBicycles, "*", options).get(); // elements (maps in array) could be reordered, comparing as sets assertDeepEquals( Set.of( @@ -658,30 +659,26 @@ public void ft_aggregate() { // FT.AGGREGATE idx:movie * LOAD * APPLY ceil(@rating) as r_rating GROUPBY 1 @genre REDUCE // COUNT 0 AS nb_of_movies REDUCE SUM 1 votes AS nb_of_votes REDUCE AVG 1 r_rating AS avg_rating // SORTBY 4 @avg_rating DESC @nb_of_votes DESC - aggreg = - FT.aggregate( - client, - indexMovies, - "*", - FTAggregateOptions.builder() - .loadAll() - .addExpression(new Apply("ceil(@rating)", "r_rating")) - .addExpression( - new GroupBy( - new String[] {"@genre"}, - new Reducer[] { - new Reducer("COUNT", new String[0], "nb_of_movies"), - new Reducer("SUM", new String[] {"votes"}, "nb_of_votes"), - new Reducer("AVG", new String[] {"r_rating"}, "avg_rating") - })) - .addExpression( - new SortBy( - new SortProperty[] { - new SortProperty("@avg_rating", SortOrder.DESC), - new SortProperty("@nb_of_votes", SortOrder.DESC) - })) - .build()) - .get(); + options = + FTAggregateOptions.builder() + .loadAll() + .addExpression(new Apply("ceil(@rating)", "r_rating")) + .addExpression( + new GroupBy( + new String[] {"@genre"}, + new Reducer[] { + new Reducer("COUNT", new String[0], "nb_of_movies"), + new Reducer("SUM", new String[] {"votes"}, "nb_of_votes"), + new Reducer("AVG", new String[] {"r_rating"}, "avg_rating") + })) + .addExpression( + new SortBy( + new SortProperty[] { + new SortProperty("@avg_rating", SortOrder.DESC), + new SortProperty("@nb_of_votes", SortOrder.DESC) + })) + .build(); + aggreg = FT.aggregate(client, indexMovies, "*", options).get(); // elements (maps in array) could be reordered, comparing as sets assertDeepEquals( Set.of( @@ -713,6 +710,9 @@ public void ft_aggregate() { gs("avg_rating"), 9.)), Set.of(aggreg)); + + var ftprofile = FT.profile(client, indexMovies, new FTProfileOptions("*", options)).get(); + assertDeepEquals(aggreg, ftprofile[0]); } @SuppressWarnings("unchecked")