Refactor Value cell path functions to fix bugs (#11066)

# Description
Slightly refactors the cell path functions (`insert_data_at_cell_path`,
etc.) for `Value` to fix a few bugs and ensure consistent behavior.
Namely, case (in)sensitivity now applies to lazy records just like it
does for regular `Records`. Also, the insert behavior of `insert` and
`upsert` now match, alongside fixing a few related bugs described below.
Otherwise, a few places were changed to use the `Record` API.

# Tests
Added tests for two bugs:
- `{a: {}} | insert a.b.c 0`: before this PR, doesn't create the
innermost record `c`.
- `{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2`: before
this PR, doesn't add the field `b: 2` to each row.
This commit is contained in:
Ian Manske 2023-11-19 20:46:41 +00:00 committed by GitHub
parent c26fca7419
commit 12effd9b4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 264 additions and 246 deletions

View File

@ -87,3 +87,17 @@ fn lazy_record_test_values() {
); );
assert_eq!(actual.out, "3"); assert_eq!(actual.out, "3");
} }
#[test]
fn deep_cell_path_creates_all_nested_records() {
let actual = nu!(r#"{a: {}} | insert a.b.c 0 | get a.b.c"#);
assert_eq!(actual.out, "0");
}
#[test]
fn inserts_all_rows_in_table_in_record() {
let actual = nu!(
r#"{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2 | get table.col.b | to nuon"#
);
assert_eq!(actual.out, "[2, 2]");
}

View File

@ -90,3 +90,17 @@ fn upsert_support_lazy_record() {
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#); nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#);
assert_eq!(actual.out, "10"); assert_eq!(actual.out, "10");
} }
#[test]
fn deep_cell_path_creates_all_nested_records() {
let actual = nu!(r#"{a: {}} | insert a.b.c 0 | get a.b.c"#);
assert_eq!(actual.out, "0");
}
#[test]
fn upserts_all_rows_in_table_in_record() {
let actual = nu!(
r#"{table: [[col]; [{a: 1}], [{a: 1}]]} | insert table.col.b 2 | get table.col.b | to nuon"#
);
assert_eq!(actual.out, "[2, 2]");
}

View File

@ -894,26 +894,6 @@ impl Value {
} }
} }
/// Check if the content is empty
pub fn is_empty(&self) -> bool {
match self {
Value::String { val, .. } => val.is_empty(),
Value::List { vals, .. } => vals.is_empty(),
Value::Record { val, .. } => val.is_empty(),
Value::Binary { val, .. } => val.is_empty(),
Value::Nothing { .. } => true,
_ => false,
}
}
pub fn is_nothing(&self) -> bool {
matches!(self, Value::Nothing { .. })
}
pub fn is_error(&self) -> bool {
matches!(self, Value::Error { .. })
}
/// Follow a given cell path into the value: for example accessing select elements in a stream or list /// Follow a given cell path into the value: for example accessing select elements in a stream or list
pub fn follow_cell_path( pub fn follow_cell_path(
self, self,
@ -923,8 +903,6 @@ impl Value {
let mut current = self; let mut current = self;
for member in cell_path { for member in cell_path {
// FIXME: this uses a few extra clones for simplicity, but there may be a way
// to traverse the path without them
match member { match member {
PathMember::Int { PathMember::Int {
val: count, val: count,
@ -932,16 +910,22 @@ impl Value {
optional, optional,
} => { } => {
// Treat a numeric path member as `select <val>` // Treat a numeric path member as `select <val>`
match &mut current { match current {
Value::List { vals: val, .. } => { Value::List { mut vals, .. } => {
if let Some(item) = val.get(*count) { if *count < vals.len() {
current = item.clone(); // `vals` is owned and will be dropped right after this,
// so we can `swap_remove` the value at index `count`
// without worrying about preserving order.
current = vals.swap_remove(*count);
} else if *optional { } else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit return Ok(Value::nothing(*origin_span)); // short-circuit
} else if val.is_empty() { } else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span }) return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else { } else {
return Err(ShellError::AccessBeyondEnd { max_idx:val.len()-1,span: *origin_span }); return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *origin_span,
});
} }
} }
Value::Binary { val, .. } => { Value::Binary { val, .. } => {
@ -950,19 +934,22 @@ impl Value {
} else if *optional { } else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit return Ok(Value::nothing(*origin_span)); // short-circuit
} else if val.is_empty() { } else if val.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span }) return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else { } else {
return Err(ShellError::AccessBeyondEnd { max_idx:val.len()-1,span: *origin_span }); return Err(ShellError::AccessBeyondEnd {
max_idx: val.len() - 1,
span: *origin_span,
});
} }
} }
Value::Range { val, .. } => { Value::Range { val, .. } => {
if let Some(item) = val.clone().into_range_iter(None)?.nth(*count) { if let Some(item) = val.into_range_iter(None)?.nth(*count) {
current = item.clone(); current = item;
} else if *optional { } else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit return Ok(Value::nothing(*origin_span)); // short-circuit
} else { } else {
return Err(ShellError::AccessBeyondEndOfStream { return Err(ShellError::AccessBeyondEndOfStream {
span: *origin_span span: *origin_span,
}); });
} }
} }
@ -988,11 +975,14 @@ impl Value {
return Err(ShellError::TypeMismatch { return Err(ShellError::TypeMismatch {
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(), err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
span: *origin_span, span: *origin_span,
}) });
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error),
x => { x => {
return Err(ShellError::IncompatiblePathAccess { type_name:format!("{}",x.get_type()), span: *origin_span }) return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
});
} }
} }
} }
@ -1003,7 +993,7 @@ impl Value {
} => { } => {
let span = current.span(); let span = current.span();
match &mut current { match current {
Value::Record { val, .. } => { Value::Record { val, .. } => {
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected. // Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
if let Some(found) = val.iter().rev().find(|x| { if let Some(found) = val.iter().rev().find(|x| {
@ -1013,7 +1003,7 @@ impl Value {
x.0 == column_name x.0 == column_name
} }
}) { }) {
current = found.1.clone(); current = found.1.clone(); // TODO: avoid clone here
} else if *optional { } else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit return Ok(Value::nothing(*origin_span)); // short-circuit
} else if let Some(suggestion) = } else if let Some(suggestion) =
@ -1022,7 +1012,7 @@ impl Value {
return Err(ShellError::DidYouMean(suggestion, *origin_span)); return Err(ShellError::DidYouMean(suggestion, *origin_span));
} else { } else {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(), col_name: column_name.clone(),
span: *origin_span, span: *origin_span,
src_span: span, src_span: span,
}); });
@ -1031,15 +1021,21 @@ impl Value {
Value::LazyRecord { val, .. } => { Value::LazyRecord { val, .. } => {
let columns = val.column_names(); let columns = val.column_names();
if columns.contains(&column_name.as_str()) { if let Some(col) = columns.iter().rev().find(|&col| {
current = val.get_column_value(column_name)?; if insensitive {
col.eq_ignore_case(column_name)
} else {
col == column_name
}
}) {
current = val.get_column_value(col)?;
} else if *optional { } else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit return Ok(Value::nothing(*origin_span)); // short-circuit
} else if let Some(suggestion) = did_you_mean(&columns, column_name) { } else if let Some(suggestion) = did_you_mean(&columns, column_name) {
return Err(ShellError::DidYouMean(suggestion, *origin_span)); return Err(ShellError::DidYouMean(suggestion, *origin_span));
} else { } else {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(), col_name: column_name.clone(),
span: *origin_span, span: *origin_span,
src_span: span, src_span: span,
}); });
@ -1049,39 +1045,50 @@ impl Value {
// Create a List which contains each matching value for contained // Create a List which contains each matching value for contained
// records in the source list. // records in the source list.
Value::List { vals, .. } => { Value::List { vals, .. } => {
// TODO: this should stream instead of collecting let list = vals
let mut output = vec![]; .into_iter()
for val in vals { .map(|val| {
// only look in records; this avoids unintentionally recursing into deeply nested tables let val_span = val.span();
if matches!(val, Value::Record { .. }) { match val {
if let Ok(result) = val.clone().follow_cell_path( Value::Record { val, .. } => {
&[PathMember::String { if let Some(found) = val.iter().rev().find(|x| {
val: column_name.clone(), if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
}) {
Ok(found.1.clone()) // TODO: avoid clone here
} else if *optional {
Ok(Value::nothing(*origin_span))
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
Err(ShellError::DidYouMean(
suggestion,
*origin_span,
))
} else {
Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: *origin_span,
src_span: val_span,
})
}
}
Value::Nothing { .. } if *optional => {
Ok(Value::nothing(*origin_span))
}
_ => Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: *origin_span, span: *origin_span,
optional: *optional, src_span: val_span,
}], }),
insensitive,
) {
output.push(result);
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(),
span: *origin_span,
src_span: val.span(),
});
} }
} else if *optional && matches!(val, Value::Nothing { .. }) { })
output.push(Value::nothing(*origin_span)); .collect::<Result<_, _>>()?;
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.to_string(),
span: *origin_span,
src_span: val.span(),
});
}
}
current = Value::list(output, span); current = Value::list(list, span);
} }
Value::CustomValue { val, .. } => { Value::CustomValue { val, .. } => {
current = val.follow_path_string(column_name.clone(), *origin_span)?; current = val.follow_path_string(column_name.clone(), *origin_span)?;
@ -1089,12 +1096,12 @@ impl Value {
Value::Nothing { .. } if *optional => { Value::Nothing { .. } if *optional => {
return Ok(Value::nothing(*origin_span)); // short-circuit return Ok(Value::nothing(*origin_span)); // short-circuit
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error),
x => { x => {
return Err(ShellError::IncompatiblePathAccess { return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()), type_name: format!("{}", x.get_type()),
span: *origin_span, span: *origin_span,
}) });
} }
} }
} }
@ -1130,8 +1137,8 @@ impl Value {
cell_path: &[PathMember], cell_path: &[PathMember],
new_val: Value, new_val: Value,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
match cell_path.first() { if let Some((member, path)) = cell_path.split_first() {
Some(path_member) => match path_member { match member {
PathMember::String { PathMember::String {
val: col_name, val: col_name,
span, span,
@ -1141,61 +1148,43 @@ impl Value {
for val in vals.iter_mut() { for val in vals.iter_mut() {
match val { match val {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
let mut found = false; if let Some(val) = record.get_mut(col_name) {
for (col, val) in record.iter_mut() { val.upsert_data_at_cell_path(path, new_val.clone())?;
if col == col_name { } else {
found = true; let new_col = if path.is_empty() {
val.upsert_data_at_cell_path( new_val.clone()
&cell_path[1..],
new_val.clone(),
)?
}
}
if !found {
if cell_path.len() == 1 {
record.push(col_name, new_val);
break;
} else { } else {
let mut new_col = let mut new_col =
Value::record(Record::new(), new_val.span()); Value::record(Record::new(), new_val.span());
new_col.upsert_data_at_cell_path( new_col
&cell_path[1..], .upsert_data_at_cell_path(path, new_val.clone())?;
new_val, new_col
)?; };
vals.push(new_col); record.push(col_name, new_col);
break;
}
} }
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error.clone()),
v => { v => {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
} }
} }
} }
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
let mut found = false; if let Some(val) = record.get_mut(col_name) {
val.upsert_data_at_cell_path(path, new_val)?;
for (col, val) in record.iter_mut() { } else {
if col == col_name { let new_col = if path.is_empty() {
found = true;
val.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
}
}
if !found {
let new_col = if cell_path.len() == 1 {
new_val new_val
} else { } else {
let mut new_col = Value::record(Record::new(), new_val.span()); let mut new_col = Value::record(Record::new(), new_val.span());
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?; new_col.upsert_data_at_cell_path(path, new_val)?;
new_col new_col
}; };
record.push(col_name, new_col); record.push(col_name, new_col);
} }
} }
@ -1203,15 +1192,15 @@ impl Value {
// convert to Record first. // convert to Record first.
let mut record = val.collect()?; let mut record = val.collect()?;
record.upsert_data_at_cell_path(cell_path, new_val)?; record.upsert_data_at_cell_path(cell_path, new_val)?;
*self = record *self = record;
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error.clone()),
v => { v => {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
}, },
PathMember::Int { PathMember::Int {
@ -1219,8 +1208,8 @@ impl Value {
} => match self { } => match self {
Value::List { vals, .. } => { Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) { if let Some(v) = vals.get_mut(*row_num) {
v.upsert_data_at_cell_path(&cell_path[1..], new_val)? v.upsert_data_at_cell_path(path, new_val)?;
} else if vals.len() == *row_num && cell_path.len() == 1 { } else if vals.len() == *row_num && path.is_empty() {
// If the upsert is at 1 + the end of the list, it's OK. // If the upsert is at 1 + the end of the list, it's OK.
// Otherwise, it's prohibited. // Otherwise, it's prohibited.
vals.push(new_val); vals.push(new_val);
@ -1231,18 +1220,17 @@ impl Value {
}); });
} }
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error.clone()),
v => { v => {
return Err(ShellError::NotAList { return Err(ShellError::NotAList {
dst_span: *span, dst_span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
}, },
},
None => {
*self = new_val;
} }
} else {
*self = new_val;
} }
Ok(()) Ok(())
} }
@ -1259,7 +1247,6 @@ impl Value {
match new_val { match new_val {
Value::Error { error, .. } => Err(*error), Value::Error { error, .. } => Err(*error),
new_val => self.update_data_at_cell_path(cell_path, new_val), new_val => self.update_data_at_cell_path(cell_path, new_val),
} }
} }
@ -1270,9 +1257,8 @@ impl Value {
new_val: Value, new_val: Value,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let v_span = self.span(); let v_span = self.span();
if let Some((member, path)) = cell_path.split_first() {
match cell_path.first() { match member {
Some(path_member) => match path_member {
PathMember::String { PathMember::String {
val: col_name, val: col_name,
span, span,
@ -1283,47 +1269,33 @@ impl Value {
let v_span = val.span(); let v_span = val.span();
match val { match val {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
let mut found = false; if let Some(val) = record.get_mut(col_name) {
for (col, val) in record.iter_mut() { val.update_data_at_cell_path(path, new_val.clone())?;
if col == col_name { } else {
found = true;
val.update_data_at_cell_path(
&cell_path[1..],
new_val.clone(),
)?
}
}
if !found {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v_span, src_span: v_span,
}); });
} }
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error.clone()),
v => { v => {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
} }
} }
} }
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
let mut found = false; if let Some(val) = record.get_mut(col_name) {
val.update_data_at_cell_path(path, new_val)?;
for (col, val) in record.iter_mut() { } else {
if col == col_name {
found = true;
val.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
}
}
if !found {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v_span, src_span: v_span,
}); });
@ -1333,15 +1305,15 @@ impl Value {
// convert to Record first. // convert to Record first.
let mut record = val.collect()?; let mut record = val.collect()?;
record.update_data_at_cell_path(cell_path, new_val)?; record.update_data_at_cell_path(cell_path, new_val)?;
*self = record *self = record;
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error.clone()),
v => { v => {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
}, },
PathMember::Int { PathMember::Int {
@ -1349,7 +1321,7 @@ impl Value {
} => match self { } => match self {
Value::List { vals, .. } => { Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) { if let Some(v) = vals.get_mut(*row_num) {
v.update_data_at_cell_path(&cell_path[1..], new_val)? v.update_data_at_cell_path(path, new_val)?;
} else if vals.is_empty() { } else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *span }); return Err(ShellError::AccessEmptyContent { span: *span });
} else { } else {
@ -1359,29 +1331,27 @@ impl Value {
}); });
} }
} }
Value::Error { error, .. } => return Err(*error.to_owned()), Value::Error { error, .. } => return Err(*error.clone()),
v => { v => {
return Err(ShellError::NotAList { return Err(ShellError::NotAList {
dst_span: *span, dst_span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
}, },
},
None => {
*self = new_val;
} }
} else {
*self = new_val;
} }
Ok(()) Ok(())
} }
pub fn remove_data_at_cell_path(&mut self, cell_path: &[PathMember]) -> Result<(), ShellError> { pub fn remove_data_at_cell_path(&mut self, cell_path: &[PathMember]) -> Result<(), ShellError> {
match cell_path.len() { match cell_path {
0 => Ok(()), [] => Ok(()),
1 => { [member] => {
let path_member = cell_path.first().expect("there is a first");
let v_span = self.span(); let v_span = self.span();
match path_member { match member {
PathMember::String { PathMember::String {
val: col_name, val: col_name,
span, span,
@ -1390,12 +1360,11 @@ impl Value {
Value::List { vals, .. } => { Value::List { vals, .. } => {
for val in vals.iter_mut() { for val in vals.iter_mut() {
let v_span = val.span(); let v_span = val.span();
match val { match val {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
if record.remove(col_name).is_none() && !optional { if record.remove(col_name).is_none() && !optional {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v_span, src_span: v_span,
}); });
@ -1403,10 +1372,10 @@ impl Value {
} }
v => { v => {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
} }
} }
@ -1415,7 +1384,7 @@ impl Value {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
if record.remove(col_name).is_none() && !optional { if record.remove(col_name).is_none() && !optional {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v_span, src_span: v_span,
}); });
@ -1430,7 +1399,7 @@ impl Value {
Ok(()) Ok(())
} }
v => Err(ShellError::CantFindColumn { v => Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}), }),
@ -1462,10 +1431,9 @@ impl Value {
}, },
} }
} }
_ => { [member, path @ ..] => {
let path_member = cell_path.first().expect("there is a first");
let v_span = self.span(); let v_span = self.span();
match path_member { match member {
PathMember::String { PathMember::String {
val: col_name, val: col_name,
span, span,
@ -1476,16 +1444,11 @@ impl Value {
let v_span = val.span(); let v_span = val.span();
match val { match val {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
let mut found = false; if let Some(val) = record.get_mut(col_name) {
for (col, val) in record.iter_mut() { val.remove_data_at_cell_path(path)?;
if col == col_name { } else if !optional {
found = true;
val.remove_data_at_cell_path(&cell_path[1..])?
}
}
if !found && !optional {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v_span, src_span: v_span,
}); });
@ -1493,27 +1456,21 @@ impl Value {
} }
v => { v => {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
} }
} }
Ok(()) Ok(())
} }
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
let mut found = false; if let Some(val) = record.get_mut(col_name) {
val.remove_data_at_cell_path(path)?;
for (col, val) in record.iter_mut() { } else if !optional {
if col == col_name {
found = true;
val.remove_data_at_cell_path(&cell_path[1..])?
}
}
if !found && !optional {
return Err(ShellError::CantFindColumn { return Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v_span, src_span: v_span,
}); });
@ -1528,7 +1485,7 @@ impl Value {
Ok(()) Ok(())
} }
v => Err(ShellError::CantFindColumn { v => Err(ShellError::CantFindColumn {
col_name: col_name.to_string(), col_name: col_name.clone(),
span: *span, span: *span,
src_span: v.span(), src_span: v.span(),
}), }),
@ -1540,7 +1497,7 @@ impl Value {
} => match self { } => match self {
Value::List { vals, .. } => { Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) { if let Some(v) = vals.get_mut(*row_num) {
v.remove_data_at_cell_path(&cell_path[1..]) v.remove_data_at_cell_path(path)
} else if *optional { } else if *optional {
Ok(()) Ok(())
} else if vals.is_empty() { } else if vals.is_empty() {
@ -1569,8 +1526,8 @@ impl Value {
head_span: Span, head_span: Span,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let v_span = self.span(); let v_span = self.span();
match cell_path.first() { if let Some((member, path)) = cell_path.split_first() {
Some(path_member) => match path_member { match member {
PathMember::String { PathMember::String {
val: col_name, val: col_name,
span, span,
@ -1581,27 +1538,36 @@ impl Value {
let v_span = val.span(); let v_span = val.span();
match val { match val {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
for (col, val) in record.iter_mut() { if let Some(val) = record.get_mut(col_name) {
if col == col_name { if path.is_empty() {
if cell_path.len() == 1 { return Err(ShellError::ColumnAlreadyExists {
return Err(ShellError::ColumnAlreadyExists { col_name: col_name.clone(),
col_name: col_name.to_string(), span: *span,
span: *span, src_span: v_span,
src_span: v_span, });
}); } else {
} else { val.insert_data_at_cell_path(
return val.insert_data_at_cell_path( path,
&cell_path[1..], new_val.clone(),
new_val, head_span,
head_span, )?;
);
}
} }
} else {
let new_col = if path.is_empty() {
new_val.clone()
} 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
};
record.push(col_name, new_col);
} }
record.push(col_name, new_val.clone());
} }
// SIGH...
Value::Error { error, .. } => return Err(*error.clone()), Value::Error { error, .. } => return Err(*error.clone()),
_ => { _ => {
return Err(ShellError::UnsupportedInput { return Err(ShellError::UnsupportedInput {
@ -1609,37 +1575,42 @@ impl Value {
input: format!("input type: {:?}", val.get_type()), input: format!("input type: {:?}", val.get_type()),
msg_span: head_span, msg_span: head_span,
input_span: *span, input_span: *span,
}) });
} }
} }
} }
} }
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
for (col, val) in record.iter_mut() { if let Some(val) = record.get_mut(col_name) {
if col == col_name { if path.is_empty() {
if cell_path.len() == 1 { return Err(ShellError::ColumnAlreadyExists {
return Err(ShellError::ColumnAlreadyExists { col_name: col_name.clone(),
col_name: col_name.to_string(), span: *span,
span: *span, src_span: v_span,
src_span: v_span, });
}); } else {
} else { val.insert_data_at_cell_path(path, new_val, head_span)?;
return val.insert_data_at_cell_path(
&cell_path[1..],
new_val,
head_span,
);
}
} }
} else {
let new_col = if path.is_empty() {
new_val.clone()
} 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
};
record.push(col_name, new_col);
} }
record.push(col_name, new_val);
} }
Value::LazyRecord { val, .. } => { Value::LazyRecord { val, .. } => {
// convert to Record first. // convert to Record first.
let mut record = val.collect()?; let mut record = val.collect()?;
record.insert_data_at_cell_path(cell_path, new_val, v_span)?; record.insert_data_at_cell_path(cell_path, new_val, v_span)?;
*self = record *self = record;
} }
other => { other => {
return Err(ShellError::UnsupportedInput { return Err(ShellError::UnsupportedInput {
@ -1647,7 +1618,7 @@ impl Value {
input: format!("input type: {:?}", other.get_type()), input: format!("input type: {:?}", other.get_type()),
msg_span: head_span, msg_span: head_span,
input_span: *span, input_span: *span,
}) });
} }
}, },
PathMember::Int { PathMember::Int {
@ -1655,8 +1626,8 @@ impl Value {
} => match self { } => match self {
Value::List { vals, .. } => { Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) { if let Some(v) = vals.get_mut(*row_num) {
v.insert_data_at_cell_path(&cell_path[1..], new_val, head_span)? v.insert_data_at_cell_path(path, new_val, head_span)?;
} else if vals.len() == *row_num && cell_path.len() == 1 { } else if vals.len() == *row_num && path.is_empty() {
// If the insert is at 1 + the end of the list, it's OK. // If the insert is at 1 + the end of the list, it's OK.
// Otherwise, it's prohibited. // Otherwise, it's prohibited.
vals.push(new_val); vals.push(new_val);
@ -1671,17 +1642,36 @@ impl Value {
return Err(ShellError::NotAList { return Err(ShellError::NotAList {
dst_span: *span, dst_span: *span,
src_span: v.span(), src_span: v.span(),
}) });
} }
}, },
},
None => {
*self = new_val;
} }
} else {
*self = new_val;
} }
Ok(()) Ok(())
} }
/// Check if the content is empty
pub fn is_empty(&self) -> bool {
match self {
Value::String { val, .. } => val.is_empty(),
Value::List { vals, .. } => vals.is_empty(),
Value::Record { val, .. } => val.is_empty(),
Value::Binary { val, .. } => val.is_empty(),
Value::Nothing { .. } => true,
_ => false,
}
}
pub fn is_nothing(&self) -> bool {
matches!(self, Value::Nothing { .. })
}
pub fn is_error(&self) -> bool {
matches!(self, Value::Error { .. })
}
pub fn is_true(&self) -> bool { pub fn is_true(&self) -> bool {
matches!(self, Value::Bool { val: true, .. }) matches!(self, Value::Bool { val: true, .. })
} }