Skip to content

Commit

Permalink
No commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
Yury-Fridlyand committed Oct 1, 2024
1 parent a15d0ec commit 57e0d78
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 5 deletions.
50 changes: 49 additions & 1 deletion glide-core/src/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) enum ExpectedReturnType<'a> {
ArrayOfStrings,
ArrayOfBools,
ArrayOfDoubleOrNull,
FTSearchReturnType,
Lolwut,
ArrayOfStringAndArrays,
ArrayOfArraysOfDoubleOrNull,
Expand Down Expand Up @@ -891,7 +892,53 @@ pub(crate) fn convert_to_expected_type(
format!("(response was {:?})", get_value_type(&value)),
)
.into()),
}
},
ExpectedReturnType::FTSearchReturnType => match value {
/*
Example of the response
1) (integer) 2
2) "json:2"
3) 1) "__VEC_score"
2) "11.1100006104"
3) "$"
4) "{\"vec\":[1.1,1.2,1.3,1.4,1.5,1.6]}"
4) "json:0"
5) 1) "__VEC_score"
2) "91"
3) "$"
4) "{\"vec\":[1,2,3,4,5,6]}"
Converting response to
1) (integer) 2
2) 1# "json:2" =>
1# "__VEC_score" => "11.1100006104"
2# "$" => "{\"vec\":[1.1,1.2,1.3,1.4,1.5,1.6]}"
2# "json:0" =>
1# "__VEC_score" => "91"
2# "$" => "{\"vec\":[1,2,3,4,5,6]}"
Response may contain only 1 element, no conversion in that case.
*/
Value::Array(ref array) if array.len() == 1 => Ok(value),
Value::Array(mut array) => {
Ok(Value::Array(vec![
array[0].clone(),
convert_to_expected_type(Value::Array(array.split_off(1)), Some(ExpectedReturnType::Map {
key_type: &Some(ExpectedReturnType::BulkString),
value_type: &Some(ExpectedReturnType::Map {
key_type: &Some(ExpectedReturnType::BulkString),
value_type: &Some(ExpectedReturnType::BulkString),
}),
}))?
]))
},
_ => Err((
ErrorKind::TypeError,
"Response couldn't be converted to Pair",
format!("(response was {:?})", get_value_type(&value)),
)
.into())
},
}
}

Expand Down Expand Up @@ -1256,6 +1303,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
key_type: &None,
value_type: &None,
}),
b"FT.SEARCH" => Some(ExpectedReturnType::FTSearchReturnType),
_ => None,
}
}
Expand Down
1 change: 1 addition & 0 deletions glide-core/src/protobuf/command_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ enum RequestType {
ScriptShow = 218;

FtCreate = 2000;
FtSearch = 2001;
}

message Command {
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ pub enum RequestType {
ScriptKill = 217,
ScriptShow = 218,
FtCreate = 2000,
FtSearch = 2001,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -459,6 +460,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::ScriptKill => RequestType::ScriptKill,
ProtobufRequestType::ScriptShow => RequestType::ScriptShow,
ProtobufRequestType::FtCreate => RequestType::FtCreate,
ProtobufRequestType::FtSearch => RequestType::FtSearch,
}
}
}
Expand Down Expand Up @@ -688,6 +690,7 @@ impl RequestType {
RequestType::ScriptFlush => Some(get_two_word_command("SCRIPT", "FLUSH")),
RequestType::ScriptKill => Some(get_two_word_command("SCRIPT", "KILL")),
RequestType::FtCreate => Some(cmd("FT.CREATE")),
RequestType::FtSearch => Some(cmd("FT.SEARCH")),
}
}
}
4 changes: 2 additions & 2 deletions java/client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ jar.dependsOn('copyNativeLib')
javadoc.dependsOn('copyNativeLib')
copyNativeLib.dependsOn('buildRustRelease')
compileTestJava.dependsOn('copyNativeLib')
test.dependsOn('buildRust')
testFfi.dependsOn('buildRust')
test.dependsOn('buildRustRelease')
testFfi.dependsOn('buildRustRelease')

test {
exclude "glide/ffi/FfiTest.class"
Expand Down
13 changes: 13 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static command_request.CommandRequestOuterClass.RequestType.FCall;
import static command_request.CommandRequestOuterClass.RequestType.FCallReadOnly;
import static command_request.CommandRequestOuterClass.RequestType.FtCreate;
import static command_request.CommandRequestOuterClass.RequestType.FtSearch;
import static command_request.CommandRequestOuterClass.RequestType.GeoAdd;
import static command_request.CommandRequestOuterClass.RequestType.GeoDist;
import static command_request.CommandRequestOuterClass.RequestType.GeoHash;
Expand Down Expand Up @@ -268,6 +269,7 @@
import glide.api.models.commands.stream.StreamTrimOptions;
import glide.api.models.commands.vss.FTCreateOptions.FieldInfo;
import glide.api.models.commands.vss.FTCreateOptions.IndexType;
import glide.api.models.commands.vss.FTSearchOptions;
import glide.api.models.configuration.BaseClientConfiguration;
import glide.api.models.configuration.BaseSubscriptionConfiguration;
import glide.api.models.exceptions.ConfigurationError;
Expand Down Expand Up @@ -5167,4 +5169,15 @@ public CompletableFuture<String> ftcreate(
return commandManager.submitNewCommand(
FtCreate, args.toArray(String[]::new), this::handleStringResponse);
}

@Override
public CompletableFuture<Object[]> ftsearch(
String indexName, String query, FTSearchOptions options) {
var args =
concatenateArrays(
new GlideString[] {gs(indexName), gs(query)},
options.toArgs(),
new GlideString[] {gs("DIALECT"), gs("2")});
return commandManager.submitNewCommand(FtSearch, args, this::handleArrayResponseBinary);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import glide.api.models.commands.vss.FTCreateOptions.FieldInfo;
import glide.api.models.commands.vss.FTCreateOptions.IndexType;
import glide.api.models.commands.vss.FTSearchOptions;
import glide.api.models.commands.vss.FTSearchOptions.FTSearchOptionsBuilder;
import java.util.concurrent.CompletableFuture;

public interface VectorSearchBaseCommands {
Expand Down Expand Up @@ -30,4 +32,34 @@ public interface VectorSearchBaseCommands {
*/
CompletableFuture<String> ftcreate(
String indexName, IndexType indexType, String[] prefixes, FieldInfo[] fields);

/**
* Uses the provided query expression to locate keys within an index. Once located, the count
* and/or content of indexed fields within those keys can be returned.
*
* @see TODO
* @param indexName The index name to search into.
* @param query The text query to search.
* @param options The search options - see {@link FTSearchOptions}.
* @return A two element array, where first element is count of documents in result set, and the
* second element, which has format <code>
* {@literal Map<GlideString, Map<GlideString, GlideString>>}</code> - a mapping between
* document names and map of their attributes.<br>
* If {@link FTSearchOptionsBuilder#count()} or {@link FTSearchOptionsBuilder#limit(int, int)}
* with values <code>0, 0</code> is set, the command returns array with only one element - the
* count of the documents.
* @example
* <pre>{@code
* byte[] vector = new byte[24];
* Arrays.fill(vector, (byte) 0);
* var result = client.ftsearch("json_idx1", "*=>[KNN 2 @VEC $query_vec]",
* FTSearchOptions.builder().params(Map.of("query_vec", gs(vector))).build())
* .get();
* assertArrayEquals(result, new Object[] { 2L, Map.of(
* gs("json:2"), Map.of(gs("__VEC_score"), gs("11.1100006104"), gs("$"), gs("{\"vec\":[1.1,1.2,1.3,1.4,1.5,1.6]}")),
* gs("json:0"), Map.of(gs("__VEC_score"), gs("91"), gs("$"), gs("{\"vec\":[1,2,3,4,5,6]}")))
* });
* }</pre>
*/
CompletableFuture<Object[]> ftsearch(String indexName, String query, FTSearchOptions options);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models.commands.vss;

import static glide.api.models.GlideString.gs;

import glide.api.commands.VectorSearchBaseCommands;
import glide.api.models.GlideString;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import lombok.Builder;
import org.apache.commons.lang3.tuple.Pair;

/** Mandatory parameters for {@link VectorSearchBaseCommands#ftsearch}. */
@Builder
public class FTSearchOptions {
/**
* Which fields of a key to be returned.<br>
* Map keys are field names and their values are aliases. Aliases are optional, use <code>null
* </code> to omit.
*/
@Builder.Default private final Map<String, String> identifiers = new HashMap<>();

/** Query timeout in milliseconds. */
private final Integer timeout;

private final Pair<Integer, Integer> limit;

@Builder.Default private final boolean count = false;

/**
* Query parameters, which could be referenced in the query by <code>$</code> sign, followed by
* the parameter name.
*/
@Builder.Default private final Map<String, GlideString> params = new HashMap<>();

// TODO maxstale?
// dialect is no-op

/** Convert to module API. */
public GlideString[] toArgs() {
var args = new ArrayList<GlideString>();
if (!identifiers.isEmpty()) {
args.add(gs("RETURN"));
int tokenCount = 0;
for (var pair : identifiers.entrySet()) {
tokenCount++;
args.add(gs(pair.getKey()));
if (pair.getValue() != null) {
tokenCount += 2;
args.add(gs("AS"));
args.add(gs(pair.getValue()));
}
}
args.add(1, gs(Integer.toString(tokenCount)));
}
if (timeout != null) {
args.add(gs("TIMEOUT"));
args.add(gs(timeout.toString()));
}
if (!params.isEmpty()) {
args.add(gs("PARAMS"));
args.add(gs(Integer.toString(params.size() * 2)));
params.forEach(
(name, value) -> {
args.add(gs(name));
args.add(value);
});
}
if (limit != null) {
args.add(gs("LIMIT"));
args.add(gs(Integer.toString(limit.getLeft())));
args.add(gs(Integer.toString(limit.getRight())));
}
if (count) {
args.add(gs("COUNT"));
}
return args.toArray(GlideString[]::new);
}

public static class FTSearchOptionsBuilder {

// private - hiding this API from user
void limit(Pair<Integer, Integer> limit) {}

void count(boolean count) {}

/**
* Configure query pagination. By default only first 10 documents are returned.
*
* @param offset Zero-based offset.
* @param count Number of elements to return.
*/
public FTSearchOptionsBuilder limit(int offset, int count) {
this.limit = Pair.of(offset, count);
return this;
}

/**
* Once set, the query will return only number of documents in the result set without actually
* returning them.
*/
public FTSearchOptionsBuilder count() {
this.count$value = true;
this.count$set = true;
return this;
}
}
}
4 changes: 2 additions & 2 deletions java/integTest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ test {

tasks.register('modulesTest', Test) {
doFirst {
systemProperty 'test.server.standalone.ports', 6379
systemProperty 'test.server.cluster.ports', 7000
clusterPorts = [7000]
standalonePorts = [6379]
}

filter {
Expand Down
Loading

0 comments on commit 57e0d78

Please sign in to comment.