Skip to main content

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<usize> = 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 = rows.unwrap_or(1);
101
102    let mut input = input;
103    let input_meta = input.take_metadata();
104
105    // Count is 0: return empty data immediately.
106    //
107    // The main `match` below is not safe for this case-byte streams can still be read from the
108    // pipe, and sqlite lazy queries can still run. For "take nothing" we only produce an empty
109    // value: empty binary (and clear pipeline `content_type` for binary) or an empty list, with
110    // other metadata unchanged.
111    if rows == 0 {
112        return match input {
113            PipelineData::Value(val, _) if matches!(&val, Value::Binary { .. }) => Ok(
114                Value::binary(Vec::new(), val.span()).into_pipeline_data_with_metadata(
115                    input_meta.map(|m| m.with_content_type(None)),
116                ),
117            ),
118            PipelineData::ByteStream(stream, _) => {
119                if stream.type_().is_binary_coercible() {
120                    let span = stream.span();
121                    Ok(
122                        Value::binary(Vec::new(), span).into_pipeline_data_with_metadata(
123                            input_meta.map(|m| m.with_content_type(None)),
124                        ),
125                    )
126                } else {
127                    Ok(Value::list(Vec::new(), head).into_pipeline_data_with_metadata(input_meta))
128                }
129            }
130            _ => Ok(Value::list(Vec::new(), head).into_pipeline_data_with_metadata(input_meta)),
131        };
132    }
133
134    match input {
135        PipelineData::Value(val, _) => {
136            let span = val.span();
137            match val {
138                Value::List { mut vals, .. } => {
139                    if return_single_element {
140                        if let Some(val) = vals.first_mut() {
141                            Ok(std::mem::take(val).into_pipeline_data_with_metadata(input_meta))
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(input_meta))
148                        }
149                    } else {
150                        vals.truncate(rows);
151                        Ok(Value::list(vals, span).into_pipeline_data_with_metadata(input_meta))
152                    }
153                }
154                Value::Binary { mut val, .. } => {
155                    // A slice (or single byte as int) is not the whole file/stream; drop MIME.
156                    let binary_meta = input_meta.map(|m| m.with_content_type(None));
157                    if return_single_element {
158                        if let Some(&val) = val.first() {
159                            Ok(Value::int(val.into(), span)
160                                .into_pipeline_data_with_metadata(binary_meta))
161                        } else if strict_mode {
162                            Err(ShellError::AccessEmptyContent { span: head })
163                        } else {
164                            // There are no values, so return nothing instead of an error so
165                            // that users can pipe this through 'default' if they want to.
166                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(binary_meta))
167                        }
168                    } else {
169                        val.truncate(rows);
170                        Ok(Value::binary(val, span).into_pipeline_data_with_metadata(binary_meta))
171                    }
172                }
173                Value::Range { val, .. } => {
174                    let mut iter = val.into_range_iter(span, Signals::empty());
175                    if return_single_element {
176                        if let Some(v) = iter.next() {
177                            Ok(v.into_pipeline_data_with_metadata(input_meta))
178                        } else if strict_mode {
179                            Err(ShellError::AccessEmptyContent { span: head })
180                        } else {
181                            // There are no values, so return nothing instead of an error so
182                            // that users can pipe this through 'default' if they want to.
183                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(input_meta))
184                        }
185                    } else {
186                        Ok(iter.take(rows).into_pipeline_data_with_metadata(
187                            span,
188                            engine_state.signals().clone(),
189                            input_meta,
190                        ))
191                    }
192                }
193                // Propagate errors by explicitly matching them before the final case.
194                Value::Error { error, .. } => Err(*error),
195                #[cfg(feature = "sqlite")]
196                // Pushdown optimization: handle 'first' on SQLiteQueryBuilder for lazy SQL execution
197                Value::Custom {
198                    val: custom_val,
199                    internal_span,
200                    ..
201                } => {
202                    if let Some(table) = custom_val.as_any().downcast_ref::<SQLiteQueryBuilder>() {
203                        if return_single_element {
204                            // For single element, limit 1
205                            let new_table = table.clone().with_limit(1);
206                            let result = new_table.execute(head)?;
207                            let value = result.into_value(head)?;
208                            if let Value::List { vals, .. } = value {
209                                if let Some(val) = vals.into_iter().next() {
210                                    Ok(val.into_pipeline_data_with_metadata(input_meta))
211                                } else if strict_mode {
212                                    Err(ShellError::AccessEmptyContent { span: head })
213                                } else {
214                                    // There are no values, so return nothing instead of an error so
215                                    // that users can pipe this through 'default' if they want to.
216                                    Ok(Value::nothing(head)
217                                        .into_pipeline_data_with_metadata(input_meta))
218                                }
219                            } else {
220                                Err(ShellError::NushellFailed {
221                                    msg: "Expected list from SQLiteQueryBuilder".into(),
222                                })
223                            }
224                        } else {
225                            // For multiple, limit rows
226                            let new_table = table.clone().with_limit(rows as i64);
227                            new_table
228                                .execute(head)
229                                .map(|data| data.set_metadata(input_meta))
230                        }
231                    } else {
232                        Err(ShellError::OnlySupportsThisInputType {
233                            exp_input_type: "list, binary or range".into(),
234                            wrong_type: custom_val.type_name(),
235                            dst_span: head,
236                            src_span: internal_span,
237                        })
238                    }
239                }
240                other => Err(ShellError::OnlySupportsThisInputType {
241                    exp_input_type: "list, binary or range".into(),
242                    wrong_type: other.get_type().to_string(),
243                    dst_span: head,
244                    src_span: other.span(),
245                }),
246            }
247        }
248        PipelineData::ListStream(stream, _) => {
249            if return_single_element {
250                if let Some(v) = stream.into_iter().next() {
251                    Ok(v.into_pipeline_data_with_metadata(input_meta))
252                } else if strict_mode {
253                    Err(ShellError::AccessEmptyContent { span: head })
254                } else {
255                    // There are no values, so return nothing instead of an error so
256                    // that users can pipe this through 'default' if they want to.
257                    Ok(Value::nothing(head).into_pipeline_data_with_metadata(input_meta))
258                }
259            } else {
260                Ok(PipelineData::list_stream(
261                    stream.modify(|iter| iter.take(rows)),
262                    input_meta,
263                ))
264            }
265        }
266        PipelineData::ByteStream(stream, _) => {
267            if stream.type_().is_binary_coercible() {
268                let span = stream.span();
269                let metadata = input_meta.map(|m| m.with_content_type(None));
270                if let Some(mut reader) = stream.reader() {
271                    if return_single_element {
272                        // Take a single byte
273                        let mut byte = [0u8];
274                        if reader
275                            .read(&mut byte)
276                            .map_err(|err| IoError::new(err, span, None))?
277                            > 0
278                        {
279                            Ok(Value::int(byte[0] as i64, head)
280                                .into_pipeline_data_with_metadata(metadata))
281                        } else {
282                            Err(ShellError::AccessEmptyContent { span: head })
283                        }
284                    } else {
285                        // Just take 'rows' bytes off the stream, mimicking the binary behavior
286                        Ok(PipelineData::byte_stream(
287                            ByteStream::read(
288                                reader.take(rows as u64),
289                                head,
290                                Signals::empty(),
291                                ByteStreamType::Binary,
292                            ),
293                            metadata,
294                        ))
295                    }
296                } else {
297                    Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
298                }
299            } else {
300                Err(ShellError::OnlySupportsThisInputType {
301                    exp_input_type: "list, binary or range".into(),
302                    wrong_type: stream.type_().describe().into(),
303                    dst_span: head,
304                    src_span: stream.span(),
305                })
306            }
307        }
308        PipelineData::Empty => Err(ShellError::OnlySupportsThisInputType {
309            exp_input_type: "list, binary or range".into(),
310            wrong_type: "null".into(),
311            dst_span: call.head,
312            src_span: call.head,
313        }),
314    }
315}
316#[cfg(test)]
317mod test {
318    use super::*;
319    #[test]
320    fn test_examples() -> nu_test_support::Result {
321        nu_test_support::test().examples(First)
322    }
323}