diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 3e8e5cb614ec5..ab2bd12fdf080 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -230,7 +230,7 @@ fn select( Value::List { vals: input_vals, .. } => { - Ok(input_vals + let results = input_vals .into_iter() .map(move |input_val| { if !columns.is_empty() { @@ -250,11 +250,34 @@ fn select( input_val.clone() } }) - .into_pipeline_data_with_metadata( - call_span, - engine_state.signals().clone(), - metadata, - )) + .collect::>(); + + let missing_col_errors = results + .iter() + .filter_map(|row| { + if let Value::Error { error, .. } = row { + if let ShellError::CantFindColumn { col_name, .. } = &**error { + return Some(col_name.as_str()); + } + } + return None; + }) + .collect::>(); + + if missing_col_errors.len() < results.len() { + if let Some(first) = missing_col_errors.first() { + return Err(ShellError::RowLacksValue { + col_name: first.to_string(), + span: Some(call_span), + }); + } + } + + Ok(results.into_pipeline_data_with_metadata( + call_span, + engine_state.signals().clone(), + metadata, + )) } _ => { if !columns.is_empty() { diff --git a/crates/nu-command/tests/commands/get.rs b/crates/nu-command/tests/commands/get.rs index c5c1c02e956ae..bfe76ccf686f5 100644 --- a/crates/nu-command/tests/commands/get.rs +++ b/crates/nu-command/tests/commands/get.rs @@ -180,6 +180,12 @@ fn errors_fetching_by_accessing_empty_list() { assert!(actual.err.contains("Row number too large (empty content)"),); } +#[test] +fn errors_column_missing_in_some_rows() { + let actual = nu!("[{a: 1, b: 2} {a: 3, b: 5} {a: 3}] | get b "); + assert!(actual.err.contains("Not all rows contain a value for 'b'")); +} + #[test] fn quoted_column_access() { let actual = nu!(r#"'[{"foo bar": {"baz": 4}}]' | from json | get "foo bar".baz.0 "#); diff --git a/crates/nu-command/tests/commands/select.rs b/crates/nu-command/tests/commands/select.rs index 534943767f301..2302de5dce296 100644 --- a/crates/nu-command/tests/commands/select.rs +++ b/crates/nu-command/tests/commands/select.rs @@ -192,7 +192,7 @@ fn select_failed1() { let actual = nu!("[{a: 1, b: 2} {a: 3, b: 5} {a: 3}] | select b "); assert!(actual.out.is_empty()); - assert!(actual.err.contains("cannot find column")); + assert!(actual.err.contains("Not all rows contain a value for 'b'")); } #[test] diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index 208f2178f6340..7fd9880e7b837 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -595,6 +595,19 @@ pub enum ShellError { src_span: Span, }, + /// Not all rows contain data in the requested column. + /// + /// ## Resolution + /// + /// Use the Optional Operator, --ignore-errors with get or select, or fill in the missing values. + #[error("Not all rows contain a value for '{col_name}'")] + #[diagnostic(code(nu::shell::row_lacks_value))] + RowLacksValue { + col_name: String, + #[label = "try using '{col_name}?'"] + span: Option, + }, + /// Attempted to insert a column into a table, but a column with that name already exists. /// /// ## Resolution diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 5ffe376269882..202caccc268db 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1252,9 +1252,22 @@ impl Value { }), } }) - .collect::>()?; + .collect::>>(); - current = Value::list(list, span); + let missing_col_errors = list + .iter() + .filter(|row| matches!(row, Err(ShellError::CantFindColumn { .. }))) + .count(); + + if missing_col_errors != 0 && missing_col_errors < list.len() { + return Err(ShellError::RowLacksValue { + col_name: column_name.clone(), + span: Some(*origin_span), + }); + } + + current = + Value::list(list.into_iter().collect::>()?, span); } Value::Custom { ref val, .. } => { current = match val.follow_path_string( diff --git a/tests/repl/test_cell_path.rs b/tests/repl/test_cell_path.rs index c89c50259bcf5..2f0a314654fc7 100644 --- a/tests/repl/test_cell_path.rs +++ b/tests/repl/test_cell_path.rs @@ -105,8 +105,14 @@ fn list_single_field_failure() -> TestResult { // Test the scenario where the requested column is not present in all rows #[test] fn jagged_list_access_fails() -> TestResult { - fail_test("[{foo: 'bar'}, {}].foo", "cannot find column")?; - fail_test("[{}, {foo: 'bar'}].foo", "cannot find column") + fail_test( + "[{foo: 'bar'}, {}].foo", + "cannot find value for 'foo' in some rows", + )?; + fail_test( + "[{}, {foo: 'bar'}].foo", + "cannot find value for 'foo' in some rows", + ) } #[test] diff --git a/tests/repl/test_table_operations.rs b/tests/repl/test_table_operations.rs index 5d61e0d902d5a..ffe80ecbb53bc 100644 --- a/tests/repl/test_table_operations.rs +++ b/tests/repl/test_table_operations.rs @@ -180,7 +180,7 @@ fn update_cell_path_1() -> TestResult { fn missing_column_errors() -> TestResult { fail_test( r#"[ { name: ABC, size: 20 }, { name: HIJ } ].size.1 == null"#, - "cannot find column", + "cannot find value for 'size' in some rows", ) }