/build/source/nativelink-store/src/redis_utils/ft_info.rs
Line | Count | Source |
1 | | // Copyright 2026 The NativeLink Authors. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use redis::aio::ConnectionLike; |
16 | | use redis::{ErrorKind, RedisError, Value}; |
17 | | |
18 | | /// Whether `RediSearch` is still backfilling `index`: `FT.INFO`'s |
19 | | /// `indexing` field, `1` while the background scan that follows `FT.CREATE` |
20 | | /// is running and `0` once every existing key is in the index. An aggregate |
21 | | /// issued before that reads a partial index, which for the scheduler's queue |
22 | | /// is a queue with actions missing from it. |
23 | 1 | pub(crate) async fn ft_info_indexing<C>( |
24 | 1 | mut connection_manager: C, |
25 | 1 | index: &str, |
26 | 1 | ) -> Result<bool, RedisError> |
27 | 1 | where |
28 | 1 | C: ConnectionLike + Send, |
29 | 1 | { |
30 | 1 | let value = redis::cmd("FT.INFO") |
31 | 1 | .arg(index) |
32 | 1 | .query_async::<Value>(&mut connection_manager) |
33 | 1 | .await?0 ; |
34 | 1 | parse_indexing(&value) |
35 | 1 | } |
36 | | |
37 | | /// `FT.INFO` answers a flat key-value array on RESP2 and a map on RESP3. |
38 | 1 | fn parse_indexing(value: &Value) -> Result<bool, RedisError> { |
39 | 2 | let field1 = |key: &Value, value: &Value| -> Option<bool> { |
40 | 2 | let name = match key { |
41 | 2 | Value::BulkString(bytes) => String::from_utf8_lossy(bytes).into_owned(), |
42 | 0 | Value::SimpleString(text) => text.clone(), |
43 | 0 | _ => return None, |
44 | | }; |
45 | 2 | if name != "indexing" { |
46 | 1 | return None; |
47 | 1 | } |
48 | 1 | match value { |
49 | 1 | Value::Int(n) => Some(*n != 0), |
50 | 0 | Value::BulkString(bytes) => Some(bytes.as_slice() != b"0"), |
51 | 0 | Value::SimpleString(text) => Some(text != "0"), |
52 | 0 | _ => None, |
53 | | } |
54 | 2 | }; |
55 | 1 | match value { |
56 | 1 | Value::Array(items) => { |
57 | 2 | for pair in items.chunks(2)1 { |
58 | 2 | if let [key, value] = pair |
59 | 2 | && let Some(indexing1 ) = field(key, value) |
60 | | { |
61 | 1 | return Ok(indexing); |
62 | 1 | } |
63 | | } |
64 | | } |
65 | 0 | Value::Map(entries) => { |
66 | 0 | for (key, value) in entries { |
67 | 0 | if let Some(indexing) = field(key, value) { |
68 | 0 | return Ok(indexing); |
69 | 0 | } |
70 | | } |
71 | | } |
72 | 0 | _ => {} |
73 | | } |
74 | 0 | Err(RedisError::from(( |
75 | 0 | ErrorKind::Parse, |
76 | 0 | "FT.INFO reply carries no indexing field", |
77 | 0 | ))) |
78 | 1 | } |