Coverage Report

Created: 2026-10-01 05:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}