Fix $in value for insert closure (#12209)

# Description
Fixes #12193 where the `$in` value may be null for closures provided to
`insert`.

# User-Facing Changes
The `$in` value will now always be the same as the closure parameter for
`insert`.
This commit is contained in:
Ian Manske 2024-03-14 21:43:03 +00:00 committed by GitHub
parent 9cf2e873b5
commit c950269575
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 44 additions and 32 deletions

View File

@ -43,6 +43,11 @@ impl Command for Insert {
"Insert a new column, using an expression or closure to create each row's values."
}
fn extra_usage(&self) -> &str {
"When inserting a column, the closure will be run for each row, and the current row will be passed as the first argument.
When inserting into a specific index, the closure will instead get the current value at the index or null if inserting at the end of a list/table."
}
fn search_terms(&self) -> Vec<&str> {
vec!["add"]
}
@ -63,7 +68,7 @@ impl Command for Insert {
description: "Insert a new entry into a single record",
example: "{'name': 'nu', 'stars': 5} | insert alias 'Nushell'",
result: Some(Value::test_record(record! {
"name" => Value::test_string("nu"),
"name" => Value::test_string("nu"),
"stars" => Value::test_int(5),
"alias" => Value::test_string("Nushell"),
})),
@ -73,8 +78,8 @@ impl Command for Insert {
example: "[[project, lang]; ['Nushell', 'Rust']] | insert type 'shell'",
result: Some(Value::test_list(vec![Value::test_record(record! {
"project" => Value::test_string("Nushell"),
"lang" => Value::test_string("Rust"),
"type" => Value::test_string("shell"),
"lang" => Value::test_string("Rust"),
"type" => Value::test_string("shell"),
})])),
},
Example {
@ -322,26 +327,22 @@ fn insert_value_by_closure(
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let input_at_path = value.clone().follow_cell_path(cell_path, false);
let input = if first_path_member_int {
value
.clone()
.follow_cell_path(cell_path, false)
.unwrap_or(Value::nothing(span))
} else {
value.clone()
};
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(
*var_id,
if first_path_member_int {
input_at_path.clone().unwrap_or(Value::nothing(span))
} else {
value.clone()
},
)
if let Some(var_id) = var.var_id {
stack.add_var(var_id, input.clone());
}
}
let input_at_path = input_at_path
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);
let output = eval_block_fn(engine_state, stack, block, input_at_path)?;
let output = eval_block_fn(engine_state, stack, block, input.into_pipeline_data())?;
value.insert_data_at_cell_path(cell_path, output.into_value(span), span)
}

View File

@ -43,6 +43,13 @@ impl Command for Update {
"Update an existing column to have a new value."
}
fn extra_usage(&self) -> &str {
"When updating a column, the closure will be run for each row, and the current row will be passed as the first argument. \
Referencing `$in` inside the closure will provide the value at the column for the current row.
When updating a specific index, the closure will instead be run once. The first argument to the closure and the `$in` value will both be the current value at the index."
}
fn run(
&self,
engine_state: &EngineState,
@ -74,7 +81,7 @@ impl Command for Update {
)),
},
Example {
description: "You can also use a simple command to update 'authors' to a single string",
description: "Implicitly use the `$in` value in a closure to update 'authors'",
example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors { str join ',' }",
result: Some(Value::test_list(
vec![Value::test_record(record! {

View File

@ -43,6 +43,14 @@ impl Command for Upsert {
"Update an existing column to have a new value, or insert a new column."
}
fn extra_usage(&self) -> &str {
"When updating or inserting a column, the closure will be run for each row, and the current row will be passed as the first argument. \
Referencing `$in` inside the closure will provide the value at the column for the current row or null if the column does not exist.
When updating a specific index, the closure will instead be run once. The first argument to the closure and the `$in` value will both be the current value at the index. \
If the command is inserting at the end of a list or table, then both of these values will be null."
}
fn search_terms(&self) -> Vec<&str> {
vec!["add"]
}

View File

@ -147,14 +147,14 @@ fn record_replacement_closure() {
let actual = nu!("{ a: text } | insert b {|r| $r.a | str upcase } | to nuon");
assert_eq!(actual.out, "{a: text, b: TEXT}");
let actual = nu!("{ a: text } | insert b { default TEXT } | to nuon");
let actual = nu!("{ a: text } | insert b { $in.a | str upcase } | to nuon");
assert_eq!(actual.out, "{a: text, b: TEXT}");
let actual = nu!("{ a: { b: 1 } } | insert a.c {|r| $r.a.b } | to nuon");
assert_eq!(actual.out, "{a: {b: 1, c: 1}}");
let actual = nu!("{ a: { b: 1 } } | insert a.c { default 0 } | to nuon");
assert_eq!(actual.out, "{a: {b: 1, c: 0}}");
let actual = nu!("{ a: { b: 1 } } | insert a.c { $in.a.b } | to nuon");
assert_eq!(actual.out, "{a: {b: 1, c: 1}}");
}
#[test]
@ -162,14 +162,14 @@ fn table_replacement_closure() {
let actual = nu!("[[a]; [text]] | insert b {|r| $r.a | str upcase } | to nuon");
assert_eq!(actual.out, "[[a, b]; [text, TEXT]]");
let actual = nu!("[[a]; [text]] | insert b { default TEXT } | to nuon");
let actual = nu!("[[a]; [text]] | insert b { $in.a | str upcase } | to nuon");
assert_eq!(actual.out, "[[a, b]; [text, TEXT]]");
let actual = nu!("[[b]; [1]] | wrap a | insert a.c {|r| $r.a.b } | to nuon");
assert_eq!(actual.out, "[[a]; [{b: 1, c: 1}]]");
let actual = nu!("[[b]; [1]] | wrap a | insert a.c { default 0 } | to nuon");
assert_eq!(actual.out, "[[a]; [{b: 1, c: 0}]]");
let actual = nu!("[[b]; [1]] | wrap a | insert a.c { $in.a.b } | to nuon");
assert_eq!(actual.out, "[[a]; [{b: 1, c: 1}]]");
}
#[test]
@ -191,6 +191,6 @@ fn list_stream_replacement_closure() {
let actual = nu!("[[a]; [text]] | every 1 | insert b {|r| $r.a | str upcase } | to nuon");
assert_eq!(actual.out, "[[a, b]; [text, TEXT]]");
let actual = nu!("[[a]; [text]] | every 1 | insert b { default TEXT } | to nuon");
let actual = nu!("[[a]; [text]] | every 1 | insert b { $in.a | str upcase } | to nuon");
assert_eq!(actual.out, "[[a, b]; [text, TEXT]]");
}

View File

@ -1766,14 +1766,10 @@ impl Value {
}
} else {
let new_col = if path.is_empty() {
new_val.clone()
new_val
} else {
let mut new_col = Value::record(Record::new(), new_val.span());
new_col.insert_data_at_cell_path(
path,
new_val.clone(),
head_span,
)?;
new_col.insert_data_at_cell_path(path, new_val, head_span)?;
new_col
};
record.push(col_name, new_col);