Skip to main content

shannon_nu_command/filters/
first.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{Signals, shell_error::io::IoError};
3use std::io::Read;
4
5#[cfg(feature = "sqlite")]
6use crate::database::SQLiteQueryBuilder;
7
8#[derive(Clone)]
9pub struct First;
10
11impl Command for First {
12    fn name(&self) -> &str {
13        "first"
14    }
15
16    fn signature(&self) -> Signature {
17        Signature::build("first")
18            .input_output_types(vec![
19                (
20                    // TODO: This is too permissive; if we could express this
21                    // using a type parameter it would be List<T> -> T.
22                    Type::List(Box::new(Type::Any)),
23                    Type::Any,
24                ),
25                (Type::Binary, Type::Binary),
26                (Type::Range, Type::Any),
27            ])
28            .optional(
29                "rows",
30                SyntaxShape::Int,
31                "Starting from the front, the number of rows to return.",
32            )
33            .switch("strict", "Throw an error if input is empty.", Some('s'))
34            .allow_variants_without_examples(true)
35            .category(Category::Filters)
36    }
37
38    fn search_terms(&self) -> Vec<&str> {
39        vec!["head"]
40    }
41
42    fn description(&self) -> &str {
43        "Return only the first several rows of the input. Counterpart of `last`. Opposite of `skip`."
44    }
45
46    fn run(
47        &self,
48        engine_state: &EngineState,
49        stack: &mut Stack,
50        call: &Call,
51        input: PipelineData,
52    ) -> Result<PipelineData, ShellError> {
53        first_helper(engine_state, stack, call, input)
54    }
55
56    fn examples(&self) -> Vec<Example<'_>> {
57        vec![
58            Example {
59                description: "Return the first item of a list/table.",
60                example: "[1 2 3] | first",
61                result: Some(Value::test_int(1)),
62            },
63            Example {
64                description: "Return the first 2 items of a list/table.",
65                example: "[1 2 3] | first 2",
66                result: Some(Value::list(
67                    vec![Value::test_int(1), Value::test_int(2)],
68                    Span::test_data(),
69                )),
70            },
71            Example {
72                description: "Return the first 2 bytes of a binary value.",
73                example: "0x[01 23 45] | first 2",
74                result: Some(Value::binary(vec![0x01, 0x23], Span::test_data())),
75            },
76            Example {
77                description: "Return the first item of a range.",
78                example: "1..3 | first",
79                result: Some(Value::test_int(1)),
80            },
81        ]
82    }
83}
84
85fn first_helper(
86    engine_state: &EngineState,
87    stack: &mut Stack,
88    call: &Call,
89    input: PipelineData,
90) -> Result<PipelineData, ShellError> {
91    let head = call.head;
92    let rows: Option<Spanned<i64>> = call.opt(engine_state, stack, 0)?;
93    let strict_mode = call.has_flag(engine_state, stack, "strict")?;
94
95    // FIXME: for backwards compatibility reasons, if `rows` is not specified we
96    // return a single element and otherwise we return a single list. We should probably
97    // remove `rows` so that `first` always returns a single element; getting a list of
98    // the first N elements is covered by `take`
99    let return_single_element = rows.is_none();
100    let rows = if let Some(rows) = rows {
101        if rows.item < 0 {
102            return Err(ShellError::NeedsPositiveValue { span: rows.span });
103        } else {
104            rows.item as usize
105        }
106    } else {
107        1
108    };
109
110    // first 5 bytes of an image/png are not image/png themselves
111    let metadata = input.metadata().map(|m| m.with_content_type(None));
112
113    // early exit for `first 0`
114    if rows == 0 {
115        return Ok(Value::list(Vec::new(), head).into_pipeline_data_with_metadata(metadata));
116    }
117
118    match input {
119        PipelineData::Value(val, _) => {
120            let span = val.span();
121            match val {
122                Value::List { mut vals, .. } => {
123                    if return_single_element {
124                        if let Some(val) = vals.first_mut() {
125                            Ok(std::mem::take(val).into_pipeline_data())
126                        } else if strict_mode {
127                            Err(ShellError::AccessEmptyContent { span: head })
128                        } else {
129                            // There are no values, so return nothing instead of an error so
130                            // that users can pipe this through 'default' if they want to.
131                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
132                        }
133                    } else {
134                        vals.truncate(rows);
135                        Ok(Value::list(vals, span).into_pipeline_data_with_metadata(metadata))
136                    }
137                }
138                Value::Binary { mut val, .. } => {
139                    if return_single_element {
140                        if let Some(&val) = val.first() {
141                            Ok(Value::int(val.into(), span).into_pipeline_data())
142                        } else if strict_mode {
143                            Err(ShellError::AccessEmptyContent { span: head })
144                        } else {
145                            // There are no values, so return nothing instead of an error so
146                            // that users can pipe this through 'default' if they want to.
147                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
148                        }
149                    } else {
150                        val.truncate(rows);
151                        Ok(Value::binary(val, span).into_pipeline_data_with_metadata(metadata))
152                    }
153                }
154                Value::Range { val, .. } => {
155                    let mut iter = val.into_range_iter(span, Signals::empty());
156                    if return_single_element {
157                        if let Some(v) = iter.next() {
158                            Ok(v.into_pipeline_data())
159                        } else if strict_mode {
160                            Err(ShellError::AccessEmptyContent { span: head })
161                        } else {
162                            // There are no values, so return nothing instead of an error so
163                            // that users can pipe this through 'default' if they want to.
164                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
165                        }
166                    } else {
167                        Ok(iter.take(rows).into_pipeline_data_with_metadata(
168                            span,
169                            engine_state.signals().clone(),
170                            metadata,
171                        ))
172                    }
173                }
174                // Propagate errors by explicitly matching them before the final case.
175                Value::Error { error, .. } => Err(*error),
176                #[cfg(feature = "sqlite")]
177                // Pushdown optimization: handle 'first' on SQLiteQueryBuilder for lazy SQL execution
178                Value::Custom {
179                    val: custom_val,
180                    internal_span,
181                    ..
182                } => {
183                    if let Some(table) = custom_val.as_any().downcast_ref::<SQLiteQueryBuilder>() {
184                        if return_single_element {
185                            // For single element, limit 1
186                            let new_table = table.clone().with_limit(1);
187                            let result = new_table.execute(head)?;
188                            let value = result.into_value(head)?;
189                            if let Value::List { vals, .. } = value {
190                                if let Some(val) = vals.into_iter().next() {
191                                    Ok(val.into_pipeline_data())
192                                } else if strict_mode {
193                                    Err(ShellError::AccessEmptyContent { span: head })
194                                } else {
195                                    // There are no values, so return nothing instead of an error so
196                                    // that users can pipe this through 'default' if they want to.
197                                    Ok(Value::nothing(head)
198                                        .into_pipeline_data_with_metadata(metadata))
199                                }
200                            } else {
201                                Err(ShellError::NushellFailed {
202                                    msg: "Expected list from SQLiteQueryBuilder".into(),
203                                })
204                            }
205                        } else {
206                            // For multiple, limit rows
207                            let new_table = table.clone().with_limit(rows as i64);
208                            new_table.execute(head)
209                        }
210                    } else {
211                        Err(ShellError::OnlySupportsThisInputType {
212                            exp_input_type: "list, binary or range".into(),
213                            wrong_type: custom_val.type_name(),
214                            dst_span: head,
215                            src_span: internal_span,
216                        })
217                    }
218                }
219                other => Err(ShellError::OnlySupportsThisInputType {
220                    exp_input_type: "list, binary or range".into(),
221                    wrong_type: other.get_type().to_string(),
222                    dst_span: head,
223                    src_span: other.span(),
224                }),
225            }
226        }
227        PipelineData::ListStream(stream, metadata) => {
228            if return_single_element {
229                if let Some(v) = stream.into_iter().next() {
230                    Ok(v.into_pipeline_data())
231                } else if strict_mode {
232                    Err(ShellError::AccessEmptyContent { span: head })
233                } else {
234                    // There are no values, so return nothing instead of an error so
235                    // that users can pipe this through 'default' if they want to.
236                    Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
237                }
238            } else {
239                Ok(PipelineData::list_stream(
240                    stream.modify(|iter| iter.take(rows)),
241                    metadata,
242                ))
243            }
244        }
245        PipelineData::ByteStream(stream, metadata) => {
246            if stream.type_().is_binary_coercible() {
247                let span = stream.span();
248                let metadata = metadata.map(|m| m.with_content_type(None));
249                if let Some(mut reader) = stream.reader() {
250                    if return_single_element {
251                        // Take a single byte
252                        let mut byte = [0u8];
253                        if reader
254                            .read(&mut byte)
255                            .map_err(|err| IoError::new(err, span, None))?
256                            > 0
257                        {
258                            Ok(Value::int(byte[0] as i64, head).into_pipeline_data())
259                        } else {
260                            Err(ShellError::AccessEmptyContent { span: head })
261                        }
262                    } else {
263                        // Just take 'rows' bytes off the stream, mimicking the binary behavior
264                        Ok(PipelineData::byte_stream(
265                            ByteStream::read(
266                                reader.take(rows as u64),
267                                head,
268                                Signals::empty(),
269                                ByteStreamType::Binary,
270                            ),
271                            metadata,
272                        ))
273                    }
274                } else {
275                    Ok(PipelineData::empty())
276                }
277            } else {
278                Err(ShellError::OnlySupportsThisInputType {
279                    exp_input_type: "list, binary or range".into(),
280                    wrong_type: stream.type_().describe().into(),
281                    dst_span: head,
282                    src_span: stream.span(),
283                })
284            }
285        }
286        PipelineData::Empty => Err(ShellError::OnlySupportsThisInputType {
287            exp_input_type: "list, binary or range".into(),
288            wrong_type: "null".into(),
289            dst_span: call.head,
290            src_span: call.head,
291        }),
292    }
293}
294#[cfg(test)]
295mod test {
296    use super::*;
297    #[test]
298    fn test_examples() -> nu_test_support::Result {
299        nu_test_support::test().examples(First)
300    }
301}