Skip to content

Commit

Permalink
FT.INFO
Browse files Browse the repository at this point in the history
Signed-off-by: Yury-Fridlyand <yury.fridlyand@improving.com>
  • Loading branch information
Yury-Fridlyand committed Oct 11, 2024
1 parent bbfce44 commit 27886e5
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* Python: Python: Added FT.CREATE command([#2413](https://github.com/valkey-io/valkey-glide/pull/2413))
* Python: Add JSON.ARRLEN command ([#2403](https://github.com/valkey-io/valkey-glide/pull/2403))
* Python: Add JSON.CLEAR command ([#2418](https://github.com/valkey-io/valkey-glide/pull/2418))

* Java: Added `FT.CREATE` ([#2414](https://github.com/valkey-io/valkey-glide/pull/2414))
* Java: Added `FT.INFO` ([#2441](https://github.com/valkey-io/valkey-glide/pull/2441))

#### Breaking Changes

Expand Down
112 changes: 111 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,
FTInfoReturnType,
Lolwut,
ArrayOfStringAndArrays,
ArrayOfArraysOfDoubleOrNull,
Expand Down Expand Up @@ -891,7 +892,115 @@ pub(crate) fn convert_to_expected_type(
format!("(response was {:?})", get_value_type(&value)),
)
.into()),
}
},
ExpectedReturnType::FTInfoReturnType => match value {
/*
Example of the response
1) index_name
2) "957fa3ca-2280-467d-873f-8763a36fbd5a"
3) creation_timestamp
4) (integer) 1728348101740745
5) key_type
6) HASH
7) key_prefixes
8) 1) "blog:post:"
9) fields
10) 1) 1) identifier
2) category
3) field_name
4) category
5) type
6) TAG
7) option
8)
2) 1) identifier
2) vec
3) field_name
4) VEC
5) type
6) VECTOR
7) option
8)
9) vector_params
10) 1) algorithm
2) HNSW
3) data_type
4) FLOAT32
5) dimension
6) (integer) 2
...
Converting response to
1# "index_name" => "957fa3ca-2280-467d-873f-8763a36fbd5a"
2# "creation_timestamp" => 1728348101740745
3# "key_type" => "HASH"
4# "key_prefixes" =>
1) "blog:post:"
5# "fields" =>
1) 1# "identifier" => "category"
2# "field_name" => "category"
3# "type" => "TAG"
4# "option" => ""
2) 1# "identifier" => "vec"
2# "field_name" => "VEC"
3# "type" => "TAVECTORG"
4# "option" => ""
5# "vector_params" =>
1# "algorithm" => "HNSW"
2# "data_type" => "FLOAT32"
3# "dimension" => 2
...
Map keys (odd array elements) are simple strings, not bulk strings.
*/
Value::Array(_) => {
let Value::Map(mut map) = convert_to_expected_type(value, Some(ExpectedReturnType::Map {
key_type: &None,
value_type: &None,
}))? else { unreachable!() };
for pair in map.iter_mut() {
if pair.0 == Value::SimpleString("fields".into()) {
let Value::Array(mut fields) = pair.1.clone() else {
return Err((
ErrorKind::TypeError,
"Response couldn't be converted for FT.INFO",
format!("(`fields` was {:?})", get_value_type(&pair.1.clone())),
)
.into());
};

for field in fields.iter_mut() {
let Value::Map(mut field_params) = convert_to_expected_type(field.clone(), Some(ExpectedReturnType::Map {
key_type: &None,
value_type: &None,
})).unwrap() else { unreachable!() };

for pair in field_params.iter_mut() {
if pair.0 == Value::SimpleString("vector_params".into()) {
*pair = (pair.0.clone(), convert_to_expected_type(pair.1.clone(), Some(ExpectedReturnType::Map {
key_type: &None,
value_type: &None,
}))?);
break;
}
}

*field = Value::Map(field_params);
}

*pair = (pair.0.clone(), Value::Array(fields));
break;
}
}
Ok(Value::Map(map))
},
_ => Err((
ErrorKind::TypeError,
"Response couldn't be converted to Pair",
format!("(response was {:?})", get_value_type(&value)),
)
.into())
},
}
}

Expand Down Expand Up @@ -1256,6 +1365,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
key_type: &None,
value_type: &None,
}),
b"FT.INFO" => Some(ExpectedReturnType::FTInfoReturnType),
_ => None,
}
}
Expand Down
109 changes: 109 additions & 0 deletions java/client/src/main/java/glide/api/commands/servermodules/FT.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import glide.api.models.commands.FT.FTCreateOptions;
import glide.api.models.commands.FT.FTCreateOptions.FieldInfo;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import lombok.NonNull;
Expand Down Expand Up @@ -140,6 +141,114 @@ public static CompletableFuture<String> create(
return executeCommand(client, args, false);
}

/**
* Returns information about a given index.
*
* @param indexName The index name.
* @return Nested maps with info about the index. See example for more details.
* @example
* <pre>{@code
* // example of using the API:
* Map<String, Object> response = client.ftinfo("myIndex").get();
* // the response contains data in the following format:
* Map<String, Object> data = Map.of(
* "index_name", "bcd97d68-4180-4bc5-98fe-5125d0abbcb8",
* "index_status", "AVAILABLE",
* "key_type", "JSON",
* "creation_timestamp", 1728348101728771L,
* "key_prefixes", new String[] { "json:" },
* "num_indexed_vectors", 0L,
* "space_usage", 653471L,
* "num_docs", 0L,
* "vector_space_usage", 653471L,
* "index_degradation_percentage", 0L,
* "fulltext_space_usage", 0L,
* "current_lag", 0L,
* "fields", new Object [] {
* Map.of(
* "identifier", "$.vec",
* "type", "VECTOR",
* "field_name", "VEC",
* "option", ""
* "vector_params", Map.of(
* "data_type", "FLOAT32",
* "initial_capacity", 1000L,
* "current_capacity", 1000L,
* "distance_metric", "L2",
* "dimension", 6L,
* "block_size", 1024L,
* "algorithm", "FLAT"
* )
* ),
* Map.of(
* "identifier", "name",
* "type", "TEXT",
* "field_name", "name",
* "option", ""
* ),
* }
* );
* }</pre>
*/
public static CompletableFuture<Map<String, Object>> info(
@NonNull BaseClient client, @NonNull String indexName) {
return executeCommand(client, new GlideString[] {gs("FT.INFO"), gs(indexName)}, true);
}

/**
* Returns information about a given index.
*
* @param indexName The index name.
* @return Nested maps with info about the index. See example for more details.
* @example
* <pre>{@code
* // example of using the API:
* Map<String, Object> response = client.ftinfo("myIndex").get();
* // the response contains data in the following format:
* Map<String, Object> data = Map.of(
* "index_name", "bcd97d68-4180-4bc5-98fe-5125d0abbcb8",
* "index_status", "AVAILABLE",
* "key_type", "JSON",
* "creation_timestamp", 1728348101728771L,
* "key_prefixes", new String[] { "json:" },
* "num_indexed_vectors", 0L,
* "space_usage", 653471L,
* "num_docs", 0L,
* "vector_space_usage", 653471L,
* "index_degradation_percentage", 0L,
* "fulltext_space_usage", 0L,
* "current_lag", 0L,
* "fields", new Object [] {
* Map.of(
* "identifier", "$.vec",
* "type", "VECTOR",
* "field_name", "VEC",
* "option", ""
* "vector_params", Map.of(
* "data_type", "FLOAT32",
* "initial_capacity", 1000L,
* "current_capacity", 1000L,
* "distance_metric", "L2",
* "dimension", 6L,
* "block_size", 1024L,
* "algorithm", "FLAT"
* )
* ),
* Map.of(
* "identifier", "name",
* "type", "TEXT",
* "field_name", "name",
* "option", ""
* ),
* }
* );
* }</pre>
*/
public static CompletableFuture<Map<String, Object>> info(
@NonNull BaseClient client, @NonNull GlideString indexName) {
return executeCommand(client, new GlideString[] {gs("FT.INFO"), indexName}, true);
}

/**
* A wrapper for custom command API.
*
Expand Down
70 changes: 67 additions & 3 deletions java/integTest/src/test/java/glide/modules/VectorSearchTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static glide.api.BaseClient.OK;
import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_PRIMARIES;
import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleSingleNodeRoute.RANDOM;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -24,8 +25,11 @@
import glide.api.models.commands.FlushMode;
import glide.api.models.commands.InfoOptions.Section;
import glide.api.models.exceptions.RequestException;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -124,12 +128,12 @@ public void ft_create() {
.get());

// create an index with multiple prefixes
var name = UUID.randomUUID().toString();
var index = UUID.randomUUID().toString();
assertEquals(
OK,
FT.create(
client,
name,
index,
new FieldInfo[] {
new FieldInfo("author_id", new TagField()),
new FieldInfo("author_ids", new TagField()),
Expand All @@ -149,7 +153,7 @@ public void ft_create() {
() ->
FT.create(
client,
name,
index,
new FieldInfo[] {
new FieldInfo("title", new TextField()),
new FieldInfo("name", new TextField())
Expand Down Expand Up @@ -182,4 +186,64 @@ public void ft_create() {
assertInstanceOf(RequestException.class, exception.getCause());
assertTrue(exception.getMessage().contains("already exists"));
}

@SneakyThrows
@Test
@SuppressWarnings("unchecked")
public void ft_info() {
var indices =
client
.customCommand(new String[] {"FT._LIST"}, ALL_PRIMARIES)
.get()
.getMultiValue()
.values()
.stream()
.flatMap(s -> Arrays.stream((Object[]) s))
.collect(Collectors.toSet());

// check that we can get a response for all existing indices (no crashes on value conversion or
// so)
for (var idx : indices) {
FT.info(client, (String) idx).get();
}

var index = UUID.randomUUID().toString();
assertEquals(
OK,
FT.create(
client,
index,
new FieldInfo[] {
new FieldInfo(
"$.vec", "VEC", VectorFieldHnsw.builder(DistanceMetric.COSINE, 42).build()),
new FieldInfo("name", new TextField()),
},
FTCreateOptions.builder()
.indexType(IndexType.JSON)
.prefixes(new String[] {"123"})
.build())
.get());

var response = FT.info(client, index).get();
assertEquals(index, response.get("index_name"));
assertEquals("JSON", response.get("key_type"));
assertArrayEquals(new String[] {"123"}, (Object[]) response.get("key_prefixes"));
var fields = (Object[]) response.get("fields");
assertEquals(2, fields.length);
var f1 = (Map<String, Object>) fields[1];
assertEquals("$.vec", f1.get("identifier"));
assertEquals("VECTOR", f1.get("type"));
assertEquals("VEC", f1.get("field_name"));
var f1params = (Map<String, Object>) f1.get("vector_params");
assertEquals("COSINE", f1params.get("distance_metric"));
assertEquals(42L, f1params.get("dimension"));

assertEquals(
Map.of("identifier", "$.name", "type", "TEXT", "field_name", "$.name", "option", ""),
fields[0]);

var exception = assertThrows(ExecutionException.class, () -> FT.info(client, index).get());
assertInstanceOf(RequestException.class, exception.getCause());
assertTrue(exception.getMessage().contains("Index not found"));
}
}
2 changes: 1 addition & 1 deletion submodules/redis-rs

0 comments on commit 27886e5

Please sign in to comment.