feat: stor insert accepts lists (#14175)

Closes #11433 
# Description

This feature implements passing a list into `stor insert` through
pipeline.
```bash
stor create --table-name nudb --columns {bool1: bool, int1: int, float1: float} ;
[[bool1 int1 float1]; [true 5 1.1], [false 8 3.14]] | stor insert --table-name nudb
```
```bash
stor create --table-name files --columns {name: str, type: str, size: int, modified: datetime} ;
ls | stor insert --table-name files
 ```

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
This commit is contained in:
Rodrigo Friães 2024-10-29 12:32:55 +01:00 committed by GitHub
parent 88b0982dac
commit 9ebaa737aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -16,6 +16,7 @@ impl Command for StorInsert {
.input_output_types(vec![ .input_output_types(vec![
(Type::Nothing, Type::table()), (Type::Nothing, Type::table()),
(Type::record(), Type::table()), (Type::record(), Type::table()),
(Type::table(), Type::table()),
]) ])
.required_named( .required_named(
"table-name", "table-name",
@ -43,7 +44,7 @@ impl Command for StorInsert {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs", description: "Insert data in the in-memory sqlite database using a data-record of column-name and column-value pairs",
example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}", example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}",
result: None, result: None,
}, },
@ -52,6 +53,16 @@ impl Command for StorInsert {
example: "{bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17} | stor insert --table-name nudb", example: "{bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17} | stor insert --table-name nudb",
result: None, result: None,
}, },
Example {
description: "Insert data through pipeline input as a table literal",
example: "[[bool1 int1 float1]; [true 5 1.1], [false 8 3.14]] | stor insert --table-name nudb",
result: None,
},
Example {
description: "Insert ls entries",
example: "ls | stor insert --table-name files",
result: None,
},
] ]
} }
@ -71,10 +82,11 @@ impl Command for StorInsert {
Signals::empty(), Signals::empty(),
)); ));
// Check if the record is being passed as input or using the data record parameter let records = handle(span, data_record, input)?;
let columns = handle(span, data_record, input)?;
process(table_name, span, &db, columns)?; for record in records {
process(table_name.clone(), span, &db, record)?;
}
Ok(Value::custom(db, span).into_pipeline_data()) Ok(Value::custom(db, span).into_pipeline_data())
} }
@ -84,51 +96,54 @@ fn handle(
span: Span, span: Span,
data_record: Option<Record>, data_record: Option<Record>,
input: PipelineData, input: PipelineData,
) -> Result<Record, ShellError> { ) -> Result<Vec<Record>, ShellError> {
match input { // Check for conflicting use of both pipeline input and flag
PipelineData::Empty => data_record.ok_or_else(|| ShellError::MissingParameter { if let Some(record) = data_record {
param_name: "requires a record".into(), if !matches!(input, PipelineData::Empty) {
span, return Err(ShellError::GenericError {
}), error: "Pipeline and Flag both being used".into(),
PipelineData::Value(value, ..) => { msg: "Use either pipeline input or '--data-record' parameter".into(),
// Since input is being used, check if the data record parameter is used too span: Some(span),
if data_record.is_some() { help: None,
return Err(ShellError::GenericError { inner: vec![],
error: "Pipeline and Flag both being used".into(), });
msg: "Use either pipeline input or '--data-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
match value {
Value::Record { val, .. } => Ok(val.into_owned()),
val => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
src_span: val.span(),
}),
}
} }
return Ok(vec![record]);
}
// Handle the input types
let values = match input {
PipelineData::Empty => {
return Err(ShellError::MissingParameter {
param_name: "requires a table or a record".into(),
span,
})
}
PipelineData::ListStream(stream, ..) => stream.into_iter().collect::<Vec<_>>(),
PipelineData::Value(Value::List { vals, .. }, ..) => vals,
PipelineData::Value(val, ..) => vec![val],
_ => { _ => {
if data_record.is_some() { return Err(ShellError::OnlySupportsThisInputType {
return Err(ShellError::GenericError { exp_input_type: "list or record".into(),
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--data-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: "".into(), wrong_type: "".into(),
dst_span: span, dst_span: span,
src_span: span, src_span: span,
}) })
} }
} };
values
.into_iter()
.map(|val| match val {
Value::Record { val, .. } => Ok(val.into_owned()),
other => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: other.get_type().to_string(),
dst_span: Span::unknown(),
src_span: other.span(),
}),
})
.collect()
} }
fn process( fn process(