Skip to main content

nu_command/filters/
first.rs

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