forked from extern/nushell
Copy-on-write for record values (#12305)
# Description This adds a `SharedCow` type as a transparent copy-on-write pointer that clones to unique on mutate. As an initial test, the `Record` within `Value::Record` is shared. There are some pretty big wins for performance. I'll post benchmark results in a comment. The biggest winner is nested access, as that would have cloned the records for each cell path follow before and it doesn't have to anymore. The reusability of the `SharedCow` type is nice and I think it could be used to clean up the previous work I did with `Arc` in `EngineState`. It's meant to be a mostly transparent clone-on-write that just clones on `.to_mut()` or `.into_owned()` if there are actually multiple references, but avoids cloning if the reference is unique. # User-Facing Changes - `Value::Record` field is a different type (plugin authors) # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting - [ ] use for `EngineState` - [ ] use for `Value::List`
This commit is contained in:
@ -201,13 +201,13 @@ impl Value {
|
||||
// the `2`.
|
||||
|
||||
if let Value::Record { val, .. } = self {
|
||||
val.retain_mut(|key, value| {
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
let span = value.span();
|
||||
match key {
|
||||
// Grouped options
|
||||
"ls" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"use_ls_colors" => {
|
||||
@ -236,7 +236,7 @@ impl Value {
|
||||
}
|
||||
"rm" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"always_trash" => {
|
||||
@ -263,7 +263,7 @@ impl Value {
|
||||
"history" => {
|
||||
let history = &mut config.history;
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"isolation" => {
|
||||
@ -305,7 +305,7 @@ impl Value {
|
||||
}
|
||||
"completions" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"quick" => {
|
||||
@ -326,7 +326,7 @@ impl Value {
|
||||
}
|
||||
"external" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key3, value|
|
||||
val.to_mut().retain_mut(|key3, value|
|
||||
{
|
||||
let span = value.span();
|
||||
match key3 {
|
||||
@ -393,7 +393,7 @@ impl Value {
|
||||
}
|
||||
"cursor_shape" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
let config_point = match key2 {
|
||||
"vi_insert" => &mut config.cursor_shape_vi_insert,
|
||||
@ -426,7 +426,7 @@ impl Value {
|
||||
}
|
||||
"table" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"mode" => {
|
||||
@ -451,7 +451,7 @@ impl Value {
|
||||
}
|
||||
Value::Record { val, .. } => {
|
||||
let mut invalid = false;
|
||||
val.retain(|key3, value| {
|
||||
val.to_mut().retain(|key3, value| {
|
||||
match key3 {
|
||||
"left" => {
|
||||
match value.as_int() {
|
||||
@ -546,7 +546,7 @@ impl Value {
|
||||
}
|
||||
"filesize" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value| {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"metric" => {
|
||||
@ -719,7 +719,7 @@ impl Value {
|
||||
},
|
||||
"datetime_format" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.retain_mut(|key2, value|
|
||||
val.to_mut().retain_mut(|key2, value|
|
||||
{
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
|
@ -39,7 +39,7 @@ impl PluginGcConfigs {
|
||||
self.plugins = HashMap::new();
|
||||
}
|
||||
|
||||
val.retain_mut(|key, value| {
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
let span = value.span();
|
||||
match key {
|
||||
"default" => {
|
||||
@ -88,7 +88,7 @@ fn process_plugins(
|
||||
// Remove any plugin configs that aren't in the value
|
||||
plugins.retain(|key, _| val.contains(key));
|
||||
|
||||
val.retain_mut(|key, value| {
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
if matches!(value, Value::Record { .. }) {
|
||||
plugins.entry(key.to_owned()).or_default().process(
|
||||
&join_path(path, &[key]),
|
||||
@ -150,7 +150,7 @@ impl PluginGcConfig {
|
||||
self.stop_after = PluginGcConfig::default().stop_after;
|
||||
}
|
||||
|
||||
val.retain_mut(|key, value| {
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
let span = value.span();
|
||||
match key {
|
||||
"enabled" => process_bool_config(value, errors, &mut self.enabled),
|
||||
|
@ -75,7 +75,7 @@ pub trait Eval {
|
||||
RecordItem::Spread(_, inner) => {
|
||||
match Self::eval::<D>(state, mut_state, inner)? {
|
||||
Value::Record { val: inner_val, .. } => {
|
||||
for (col_name, val) in *inner_val {
|
||||
for (col_name, val) in inner_val.into_owned() {
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
|
@ -538,7 +538,7 @@ impl FromValue for Vec<Value> {
|
||||
impl FromValue for Record {
|
||||
fn from_value(v: Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Record { val, .. } => Ok(*val),
|
||||
Value::Record { val, .. } => Ok(val.into_owned()),
|
||||
v => Err(ShellError::CantConvert {
|
||||
to_type: "Record".into(),
|
||||
from_type: v.get_type().to_string(),
|
||||
|
@ -29,7 +29,7 @@ use fancy_regex::Regex;
|
||||
use nu_utils::{
|
||||
contains_emoji,
|
||||
locale::{get_system_locale_string, LOCALE_OVERRIDE_ENV_VAR},
|
||||
IgnoreCaseExt,
|
||||
IgnoreCaseExt, SharedCow,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@ -110,7 +110,7 @@ pub enum Value {
|
||||
internal_span: Span,
|
||||
},
|
||||
Record {
|
||||
val: Box<Record>,
|
||||
val: SharedCow<Record>,
|
||||
// note: spans are being refactored out of Value
|
||||
// please use .span() instead of matching this span value
|
||||
#[serde(rename = "span")]
|
||||
@ -538,7 +538,7 @@ impl Value {
|
||||
/// Unwraps the inner [`Record`] value or returns an error if this `Value` is not a record
|
||||
pub fn into_record(self) -> Result<Record, ShellError> {
|
||||
if let Value::Record { val, .. } = self {
|
||||
Ok(*val)
|
||||
Ok(val.into_owned())
|
||||
} else {
|
||||
self.cant_convert_to("record")
|
||||
}
|
||||
@ -1159,7 +1159,7 @@ impl Value {
|
||||
match current {
|
||||
Value::Record { mut val, .. } => {
|
||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
||||
if let Some(found) = val.iter_mut().rev().find(|x| {
|
||||
if let Some(found) = val.to_mut().iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
@ -1220,13 +1220,15 @@ impl Value {
|
||||
let val_span = val.span();
|
||||
match val {
|
||||
Value::Record { mut val, .. } => {
|
||||
if let Some(found) = val.iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
if let Some(found) =
|
||||
val.to_mut().iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
})
|
||||
{
|
||||
Ok(std::mem::take(found.1))
|
||||
} else if *optional {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
@ -1332,7 +1334,7 @@ impl Value {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
val.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||
} else {
|
||||
let new_col = if path.is_empty() {
|
||||
@ -1344,7 +1346,7 @@ impl Value {
|
||||
.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||
new_col
|
||||
};
|
||||
record.push(col_name, new_col);
|
||||
record.to_mut().push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error.clone()),
|
||||
@ -1359,7 +1361,7 @@ impl Value {
|
||||
}
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
val.upsert_data_at_cell_path(path, new_val)?;
|
||||
} else {
|
||||
let new_col = if path.is_empty() {
|
||||
@ -1369,7 +1371,7 @@ impl Value {
|
||||
new_col.upsert_data_at_cell_path(path, new_val)?;
|
||||
new_col
|
||||
};
|
||||
record.push(col_name, new_col);
|
||||
record.to_mut().push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
@ -1456,7 +1458,7 @@ impl Value {
|
||||
let v_span = val.span();
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
val.update_data_at_cell_path(path, new_val.clone())?;
|
||||
} else {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
@ -1478,7 +1480,7 @@ impl Value {
|
||||
}
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
val.update_data_at_cell_path(path, new_val)?;
|
||||
} else {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
@ -1548,7 +1550,7 @@ impl Value {
|
||||
let v_span = val.span();
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if record.remove(col_name).is_none() && !optional {
|
||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: col_name.clone(),
|
||||
span: *span,
|
||||
@ -1568,7 +1570,7 @@ impl Value {
|
||||
Ok(())
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if record.remove(col_name).is_none() && !optional {
|
||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: col_name.clone(),
|
||||
span: *span,
|
||||
@ -1628,7 +1630,7 @@ impl Value {
|
||||
let v_span = val.span();
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
val.remove_data_at_cell_path(path)?;
|
||||
} else if !optional {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
@ -1650,7 +1652,7 @@ impl Value {
|
||||
Ok(())
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
val.remove_data_at_cell_path(path)?;
|
||||
} else if !optional {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
@ -1720,7 +1722,7 @@ impl Value {
|
||||
let v_span = val.span();
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
if path.is_empty() {
|
||||
return Err(ShellError::ColumnAlreadyExists {
|
||||
col_name: col_name.clone(),
|
||||
@ -1747,7 +1749,7 @@ impl Value {
|
||||
)?;
|
||||
new_col
|
||||
};
|
||||
record.push(col_name, new_col);
|
||||
record.to_mut().push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error.clone()),
|
||||
@ -1763,7 +1765,7 @@ impl Value {
|
||||
}
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
if path.is_empty() {
|
||||
return Err(ShellError::ColumnAlreadyExists {
|
||||
col_name: col_name.clone(),
|
||||
@ -1781,7 +1783,7 @@ impl Value {
|
||||
new_col.insert_data_at_cell_path(path, new_val, head_span)?;
|
||||
new_col
|
||||
};
|
||||
record.push(col_name, new_col);
|
||||
record.to_mut().push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
@ -1854,6 +1856,7 @@ impl Value {
|
||||
// Check for contained values
|
||||
match self {
|
||||
Value::Record { ref mut val, .. } => val
|
||||
.to_mut()
|
||||
.iter_mut()
|
||||
.try_for_each(|(_, rec_value)| rec_value.recurse_mut(f)),
|
||||
Value::List { ref mut vals, .. } => vals
|
||||
@ -1989,7 +1992,7 @@ impl Value {
|
||||
|
||||
pub fn record(val: Record, span: Span) -> Value {
|
||||
Value::Record {
|
||||
val: Box::new(val),
|
||||
val: SharedCow::new(val),
|
||||
internal_span: span,
|
||||
}
|
||||
}
|
||||
@ -2432,8 +2435,8 @@ impl PartialOrd for Value {
|
||||
// reorder cols and vals to make more logically compare.
|
||||
// more general, if two record have same col and values,
|
||||
// the order of cols shouldn't affect the equal property.
|
||||
let mut lhs = lhs.clone();
|
||||
let mut rhs = rhs.clone();
|
||||
let mut lhs = lhs.clone().into_owned();
|
||||
let mut rhs = rhs.clone().into_owned();
|
||||
lhs.sort_cols();
|
||||
rhs.sort_cols();
|
||||
|
||||
|
@ -151,7 +151,7 @@ impl Record {
|
||||
///
|
||||
/// fn remove_foo_recursively(val: &mut Value) {
|
||||
/// if let Value::Record {val, ..} = val {
|
||||
/// val.retain_mut(keep_non_foo);
|
||||
/// val.to_mut().retain_mut(keep_non_foo);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
Reference in New Issue
Block a user