mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
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:
parent
c26fca7419
commit
12effd9b4e
3 changed files with 264 additions and 246 deletions
|
@ -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]");
|
||||||
|
}
|
||||||
|
|
|
@ -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]");
|
||||||
|
}
|
||||||
|
|
|
@ -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, .. })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue