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