Skip to main content

nu_command/filters/
length.rs

1use std::io::Read;
2
3#[cfg(feature = "sqlite")]
4use crate::database::SQLiteQueryBuilder;
5use nu_engine::command_prelude::*;
6
7#[derive(Clone)]
8pub struct Length;
9
10impl Command for Length {
11    fn name(&self) -> &str {
12        "length"
13    }
14
15    fn description(&self) -> &str {
16        "Count the number of items in an input list, rows in a table, or bytes in binary data."
17    }
18
19    fn signature(&self) -> nu_protocol::Signature {
20        Signature::build("length")
21            .input_output_types(vec![
22                (Type::List(Box::new(Type::Any)), Type::Int),
23                (Type::Binary, Type::Int),
24                (Type::Nothing, Type::Int),
25                #[cfg(feature = "sqlite")]
26                (Type::Custom("SQLiteQueryBuilder".into()), Type::Int),
27            ])
28            .allow_variants_without_examples(true)
29            .category(Category::Filters)
30    }
31
32    fn search_terms(&self) -> Vec<&str> {
33        vec!["count", "size", "wc"]
34    }
35
36    fn run(
37        &self,
38        _engine_state: &EngineState,
39        _stack: &mut Stack,
40        call: &Call,
41        input: PipelineData,
42    ) -> Result<PipelineData, ShellError> {
43        length_row(call, input)
44    }
45
46    fn examples(&self) -> Vec<Example<'_>> {
47        vec![
48            Example {
49                description: "Count the number of items in a list",
50                example: "[1 2 3 4 5] | length",
51                result: Some(Value::test_int(5)),
52            },
53            Example {
54                description: "Count the number of rows in a table",
55                example: "[{a:1 b:2}, {a:2 b:3}] | length",
56                result: Some(Value::test_int(2)),
57            },
58            Example {
59                description: "Count the number of bytes in binary data",
60                example: "0x[01 02] | length",
61                result: Some(Value::test_int(2)),
62            },
63            Example {
64                description: "Count the length a null value",
65                example: "null | length",
66                result: Some(Value::test_int(0)),
67            },
68        ]
69    }
70}
71
72fn length_row(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
73    let span = input.span().unwrap_or(call.head);
74
75    #[cfg(feature = "sqlite")]
76    // Pushdown optimization: handle 'length' on SQLiteQueryBuilder using COUNT(*)
77    if let PipelineData::Value(Value::Custom { val, .. }, ..) = &input
78        && let Some(table) = val.as_any().downcast_ref::<SQLiteQueryBuilder>()
79    {
80        let count = table.count(call.head)?;
81        return Ok(Value::int(count, call.head).into_pipeline_data());
82    }
83
84    match input {
85        PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, ..) => {
86            Ok(Value::int(0, call.head).into_pipeline_data())
87        }
88        PipelineData::Value(Value::Binary { val, .. }, ..) => {
89            Ok(Value::int(val.len() as i64, call.head).into_pipeline_data())
90        }
91        #[cfg(feature = "sqlite")]
92        PipelineData::Value(Value::Custom { val, .. }, ..)
93            if val.as_any().downcast_ref::<SQLiteQueryBuilder>().is_some() =>
94        {
95            let table = val
96                .as_any()
97                .downcast_ref::<SQLiteQueryBuilder>()
98                .expect("already checked");
99            let count = table.count(call.head)?;
100            Ok(Value::int(count, call.head).into_pipeline_data())
101        }
102        #[cfg(feature = "sqlite")]
103        PipelineData::Value(
104            Value::Custom {
105                val, internal_span, ..
106            },
107            ..,
108        ) => Err(ShellError::OnlySupportsThisInputType {
109            exp_input_type: "list, table, binary, and nothing".into(),
110            wrong_type: val.type_name(),
111            dst_span: call.head,
112            src_span: internal_span,
113        }),
114        PipelineData::Value(Value::List { vals, .. }, ..) => {
115            Ok(Value::int(vals.len() as i64, call.head).into_pipeline_data())
116        }
117        PipelineData::ListStream(stream, ..) => {
118            Ok(Value::int(stream.into_iter().count() as i64, call.head).into_pipeline_data())
119        }
120        PipelineData::ByteStream(stream, ..) if stream.type_().is_binary_coercible() => {
121            Ok(Value::int(
122                match stream.reader() {
123                    Some(r) => r.bytes().count() as i64,
124                    None => 0,
125                },
126                call.head,
127            )
128            .into_pipeline_data())
129        }
130        _ => Err(ShellError::OnlySupportsThisInputType {
131            exp_input_type: "list, table, binary, and nothing".into(),
132            wrong_type: input.get_type().to_string(),
133            dst_span: call.head,
134            src_span: span,
135        }),
136    }
137}
138
139#[cfg(test)]
140mod test {
141    use super::*;
142
143    #[test]
144    fn test_examples() {
145        use crate::test_examples;
146
147        test_examples(Length {})
148    }
149}