diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2225848cb4..2193598686 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,14 @@ updates: ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] + groups: + # Only update polars as a whole as there are many subcrates that need to + # be updated at once. We explicitly depend on some of them, so batch their + # updates to not take up dependabot PR slots with dysfunctional PRs + polars: + patterns: + - "polars" + - "polars-*" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 7b7f3fbc6a..1142b2dbaf 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.6 - name: Check spelling - uses: crate-ci/typos@v1.22.0 + uses: crate-ci/typos@v1.22.1 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..25731e87b2 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,26 @@ +cff-version: 1.2.0 +title: 'Nushell' +message: >- + If you use this software and wish to cite it, + you can use the metadata from this file. +type: software +authors: + - name: "The Nushell Project Team" +identifiers: + - type: url + value: 'https://github.com/nushell/nushell' + description: Repository +repository-code: 'https://github.com/nushell/nushell' +url: 'https://www.nushell.sh/' +abstract: >- + The goal of the Nushell project is to take the Unix + philosophy of shells, where pipes connect simple commands + together, and bring it to the modern style of development. + Thus, rather than being either a shell, or a programming + language, Nushell connects both by bringing a rich + programming language and a full-featured shell together + into one package. +keywords: + - nushell + - shell +license: MIT diff --git a/Cargo.lock b/Cargo.lock index 23e18657db..c95f0d1435 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,17 +478,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ada7f35ca622a86a4d6c27be2633fc6c243ecc834859628fcce0681d8e76e1c8" -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor 2.5.1", -] - [[package]] name = "brotli" version = "5.0.0" @@ -497,17 +486,7 @@ checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor 4.0.0", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "brotli-decompressor", ] [[package]] @@ -871,7 +850,7 @@ checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm", "strum", - "strum_macros 0.26.2", + "strum_macros", "unicode-width", ] @@ -1295,6 +1274,9 @@ name = "either" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +dependencies = [ + "serde", +] [[package]] name = "eml-parser" @@ -1794,6 +1776,7 @@ dependencies = [ "ahash 0.8.11", "allocator-api2", "rayon", + "serde", ] [[package]] @@ -2935,7 +2918,7 @@ dependencies = [ "alphanumeric-sort", "base64 0.22.1", "bracoxide", - "brotli 5.0.0", + "brotli", "byteorder", "bytesize", "calamine", @@ -3222,7 +3205,7 @@ dependencies = [ name = "nu-protocol" version = "0.94.3" dependencies = [ - "brotli 5.0.0", + "brotli", "byte-unit", "chrono", "chrono-humanize", @@ -3243,7 +3226,7 @@ dependencies = [ "serde", "serde_json", "strum", - "strum_macros 0.26.2", + "strum_macros", "tempfile", "thiserror", "typetag", @@ -3404,7 +3387,7 @@ dependencies = [ "polars-plan", "polars-utils", "serde", - "sqlparser 0.45.0", + "sqlparser 0.47.0", "tempfile", "typetag", "uuid", @@ -3731,9 +3714,9 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.1.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4014,9 +3997,9 @@ dependencies = [ [[package]] name = "polars" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea21b858b16b9c0e17a12db2800d11aa5b4bd182be6b3022eb537bbfc1f2db5" +checksum = "e148396dca5496566880fa19374f3f789a29db94e3eb458afac1497b4bac5442" dependencies = [ "getrandom", "polars-arrow", @@ -4034,9 +4017,9 @@ dependencies = [ [[package]] name = "polars-arrow" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "725b09f2b5ef31279b66e27bbab63c58d49d8f6696b66b1f46c7eaab95e80f75" +checksum = "1cb5e11cd0752ae022fa6ca3afa50a14b0301b7ce53c0135828fbb0f4fa8303e" dependencies = [ "ahash 0.8.11", "atoi", @@ -4082,9 +4065,9 @@ dependencies = [ [[package]] name = "polars-compute" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a796945b14b14fbb79b91ef0406e6fddca2be636e889f81ea5d6ee7d36efb4fe" +checksum = "89fc4578f826234cdecb782952aa9c479dc49373f81694a7b439c70b6f609ba0" dependencies = [ "bytemuck", "either", @@ -4098,9 +4081,9 @@ dependencies = [ [[package]] name = "polars-core" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465f70d3e96b6d0b1a43c358ba451286b8c8bd56696feff020d65702aa33e35c" +checksum = "e490c6bace1366a558feea33d1846f749a8ca90bd72a6748752bc65bb4710b2a" dependencies = [ "ahash 0.8.11", "bitflags 2.5.0", @@ -4132,9 +4115,9 @@ dependencies = [ [[package]] name = "polars-error" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5224d5d05e6b8a6f78b75951ae1b5f82c8ab1979e11ffaf5fd41941e3d5b0757" +checksum = "08888f58e61599b00f5ea0c2ccdc796b54b9859559cc0d4582733509451fa01a" dependencies = [ "avro-schema", "polars-arrow-format", @@ -4144,10 +4127,30 @@ dependencies = [ ] [[package]] -name = "polars-io" -version = "0.39.2" +name = "polars-expr" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c8589e418cbe4a48228d64b2a8a40284a82ec3c98817c0c2bcc0267701338b" +checksum = "4173591920fe56ad55af025f92eb0d08421ca85705c326a640c43856094e3484" +dependencies = [ + "ahash 0.8.11", + "bitflags 2.5.0", + "once_cell", + "polars-arrow", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", + "smartstring", +] + +[[package]] +name = "polars-io" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5842896aea46d975b425d63f156f412aed3cfde4c257b64fb1f43ceea288074e" dependencies = [ "ahash 0.8.11", "async-trait", @@ -4186,9 +4189,9 @@ dependencies = [ [[package]] name = "polars-json" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81224492a649a12b668480c0cf219d703f432509765d2717e72fe32ad16fc701" +checksum = "160cbad0145b93ac6a88639aadfa6f7d7c769d05a8674f9b7e895b398cae9901" dependencies = [ "ahash 0.8.11", "chrono", @@ -4207,9 +4210,9 @@ dependencies = [ [[package]] name = "polars-lazy" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2632b1af668e2058d5f8f916d8fbde3cac63d03ae29a705f598e41dcfeb7f" +checksum = "e805ea2ebbc6b7749b0afb31b7fc5d32b42b57ba29b984549d43d3a16114c4a5" dependencies = [ "ahash 0.8.11", "bitflags 2.5.0", @@ -4217,6 +4220,7 @@ dependencies = [ "once_cell", "polars-arrow", "polars-core", + "polars-expr", "polars-io", "polars-json", "polars-ops", @@ -4231,13 +4235,13 @@ dependencies = [ [[package]] name = "polars-ops" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdbdb4d9a92109bc2e0ce8e17af5ae8ab643bb5b7ee9d1d74f0aeffd1fbc95f" +checksum = "7b0aed7e169c81b98457641cf82b251f52239a668916c2e683abd1f38df00d58" dependencies = [ "ahash 0.8.11", "argminmax", - "base64 0.21.7", + "base64 0.22.1", "bytemuck", "chrono", "chrono-tz 0.8.6", @@ -4267,14 +4271,14 @@ dependencies = [ [[package]] name = "polars-parquet" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b421d2196f786fdfe162db614c8485f8308fe41575d4de634a39bbe460d1eb6a" +checksum = "c70670a9e51cac66d0e77fd20b5cc957dbcf9f2660d410633862bb72f846d5b8" dependencies = [ "ahash 0.8.11", "async-stream", - "base64 0.21.7", - "brotli 3.5.0", + "base64 0.22.1", + "brotli", "ethnum", "flate2", "futures", @@ -4293,9 +4297,9 @@ dependencies = [ [[package]] name = "polars-pipe" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48700f1d5bd56a15451e581f465c09541492750360f18637b196f995470a015c" +checksum = "0a40ae1b3c74ee07e2d1f7cbf56c5d6e15969e45d9b6f0903bd2acaf783ba436" dependencies = [ "crossbeam-channel", "crossbeam-queue", @@ -4305,6 +4309,7 @@ dependencies = [ "polars-arrow", "polars-compute", "polars-core", + "polars-expr", "polars-io", "polars-ops", "polars-plan", @@ -4318,13 +4323,14 @@ dependencies = [ [[package]] name = "polars-plan" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb8e2302e20c44defd5be8cad9c96e75face63c3a5f609aced8c4ec3b3ac97d" +checksum = "8daa3541ae7e9af311a4389bc2b21f83349c34c723cc67fa524cdefdaa172d90" dependencies = [ "ahash 0.8.11", "bytemuck", "chrono-tz 0.8.6", + "either", "hashbrown 0.14.5", "once_cell", "percent-encoding", @@ -4341,15 +4347,15 @@ dependencies = [ "regex", "serde", "smartstring", - "strum_macros 0.25.3", + "strum_macros", "version_check", ] [[package]] name = "polars-row" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a515bdc68c2ae3702e3de70d89601f3b71ca8137e282a226dddb53ee4bacfa2e" +checksum = "deb285f2f3a65b00dd06bef16bb9f712dbb5478f941dab5cf74f9f016d382e40" dependencies = [ "bytemuck", "polars-arrow", @@ -4359,11 +4365,12 @@ dependencies = [ [[package]] name = "polars-sql" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4bb7cc1c04c3023d1953b2f1dec50515e8fd8169a5a2bf4967b3b082232db7" +checksum = "a724f699d194cb02c25124d3832f7d4d77f387f1a89ee42f6b9e88ec561d4ad9" dependencies = [ "hex", + "once_cell", "polars-arrow", "polars-core", "polars-error", @@ -4377,11 +4384,12 @@ dependencies = [ [[package]] name = "polars-time" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc18e3ad92eec55db89d88f16c22d436559ba7030cf76f86f6ed7a754b673f1" +checksum = "87ebec238d8b6200d9f0c3ce411c8441e950bd5a7df7806b8172d06c1d5a4b97" dependencies = [ "atoi", + "bytemuck", "chrono", "chrono-tz 0.8.6", "now", @@ -4398,9 +4406,9 @@ dependencies = [ [[package]] name = "polars-utils" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c760b6c698cfe2fbbbd93d6cfb408db14ececfe1d92445dae2229ce1b5b21ae8" +checksum = "34e1a907c63abf71e5f21467e2e4ff748896c28196746f631c6c25512ec6102c" dependencies = [ "ahash 0.8.11", "bytemuck", @@ -4834,7 +4842,7 @@ dependencies = [ "serde_json", "strip-ansi-escapes", "strum", - "strum_macros 0.26.2", + "strum_macros", "thiserror", "unicode-segmentation", "unicode-width", @@ -5562,9 +5570,9 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.45.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7bbffee862a796d67959a89859d6b1046bb5016d63e23835ad0da182777bbe0" +checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25" dependencies = [ "log", ] @@ -5678,20 +5686,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "strum_macros 0.26.2", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.60", + "strum_macros", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2d99b7b42e..7e5f436bbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,7 +118,7 @@ num-traits = "0.2" omnipath = "0.1" once_cell = "1.18" open = "5.1" -os_pipe = { version = "1.1", features = ["io_safety"] } +os_pipe = { version = "1.2", features = ["io_safety"] } pathdiff = "0.2" percent-encoding = "2" pretty_assertions = "1.4" diff --git a/crates/nu-command/src/stor/insert.rs b/crates/nu-command/src/stor/insert.rs index e0c0ad4d28..4b9677941b 100644 --- a/crates/nu-command/src/stor/insert.rs +++ b/crates/nu-command/src/stor/insert.rs @@ -12,14 +12,17 @@ impl Command for StorInsert { fn signature(&self) -> Signature { Signature::build("stor insert") - .input_output_types(vec![(Type::Nothing, Type::table())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::record(), Type::table()), + ]) .required_named( "table-name", SyntaxShape::String, "name of the table you want to insert into", Some('t'), ) - .required_named( + .named( "data-record", SyntaxShape::Record(vec![]), "a record of column names and column values to insert into the specified table", @@ -39,10 +42,16 @@ impl Command for StorInsert { fn examples(&self) -> Vec { vec![Example { - description: "Insert data 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}", - result: None, - }] + description: "Insert data 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}", + result: None, + }, + Example { + description: "Insert data through pipeline input as a record of column-name and column-value pairs", + example: "{bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17} | stor insert --table-name nudb", + result: None, + }, + ] } fn run( @@ -50,25 +59,79 @@ impl Command for StorInsert { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; - let columns: Option = call.get_flag(engine_state, stack, "data-record")?; + let data_record: Option = call.get_flag(engine_state, stack, "data-record")?; // let config = engine_state.get_config(); let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + // Check if the record is being passed as input or using the data record parameter + let columns = handle(span, data_record, input)?; + process(table_name, span, &db, columns)?; Ok(Value::custom(db, span).into_pipeline_data()) } } +fn handle( + span: Span, + data_record: Option, + input: PipelineData, +) -> Result { + match input { + PipelineData::Empty => data_record.ok_or_else(|| ShellError::MissingParameter { + param_name: "requires a record".into(), + span, + }), + PipelineData::Value(value, ..) => { + // Since input is being used, check if the data record parameter is used too + if data_record.is_some() { + return Err(ShellError::GenericError { + 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(), + }), + } + } + _ => { + if data_record.is_some() { + return Err(ShellError::GenericError { + 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(), + dst_span: span, + src_span: span, + }) + } + } +} + fn process( table_name: Option, span: Span, db: &SQLiteDatabase, - columns: Option, + record: Record, ) -> Result<(), ShellError> { if table_name.is_none() { return Err(ShellError::MissingParameter { @@ -77,54 +140,45 @@ fn process( }); } let new_table_name = table_name.unwrap_or("table".into()); + if let Ok(conn) = db.open_connection() { - match columns { - Some(record) => { - let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name); - let cols = record.columns(); - cols.for_each(|col| { - create_stmt.push_str(&format!("{}, ", col)); - }); - if create_stmt.ends_with(", ") { - create_stmt.pop(); - create_stmt.pop(); - } + let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name); + let cols = record.columns(); + cols.for_each(|col| { + create_stmt.push_str(&format!("{}, ", col)); + }); + if create_stmt.ends_with(", ") { + create_stmt.pop(); + create_stmt.pop(); + } - // Values are set as placeholders. - create_stmt.push_str(") VALUES ( "); - for (index, _) in record.columns().enumerate() { - create_stmt.push_str(&format!("?{}, ", index + 1)); - } + // Values are set as placeholders. + create_stmt.push_str(") VALUES ( "); + for (index, _) in record.columns().enumerate() { + create_stmt.push_str(&format!("?{}, ", index + 1)); + } - if create_stmt.ends_with(", ") { - create_stmt.pop(); - create_stmt.pop(); - } + if create_stmt.ends_with(", ") { + create_stmt.pop(); + create_stmt.pop(); + } - create_stmt.push(')'); + create_stmt.push(')'); - // dbg!(&create_stmt); + // dbg!(&create_stmt); - // Get the params from the passed values - let params = values_to_sql(record.values().cloned())?; + // Get the params from the passed values + let params = values_to_sql(record.values().cloned())?; - conn.execute(&create_stmt, params_from_iter(params)) - .map_err(|err| ShellError::GenericError { - error: "Failed to open SQLite connection in memory from insert".into(), - msg: err.to_string(), - span: Some(Span::test_data()), - help: None, - inner: vec![], - })?; - } - None => { - return Err(ShellError::MissingParameter { - param_name: "requires at least one column".into(), - span, - }); - } - }; - } + conn.execute(&create_stmt, params_from_iter(params)) + .map_err(|err| ShellError::GenericError { + error: "Failed to open SQLite connection in memory from insert".into(), + msg: err.to_string(), + span: Some(Span::test_data()), + help: None, + inner: vec![], + })?; + }; // dbg!(db.clone()); Ok(()) } @@ -176,7 +230,7 @@ mod test { ), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_ok()); } @@ -201,7 +255,7 @@ mod test { Value::test_string("String With Spaces".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_ok()); } @@ -226,7 +280,7 @@ mod test { Value::test_string("ThisIsALongString".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); // SQLite uses dynamic typing, making any length acceptable for a varchar column assert!(result.is_ok()); } @@ -251,7 +305,7 @@ mod test { Value::test_string("ThisIsTheWrongType".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); // SQLite uses dynamic typing, making any type acceptable for a column assert!(result.is_ok()); } @@ -276,7 +330,7 @@ mod test { Value::test_string("ThisIsALongString".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_err()); } @@ -293,7 +347,7 @@ mod test { Value::test_string("ThisIsALongString".to_string()), ); - let result = process(table_name, span, &db, Some(columns)); + let result = process(table_name, span, &db, columns); assert!(result.is_err()); } diff --git a/crates/nu-command/src/stor/update.rs b/crates/nu-command/src/stor/update.rs index d50614d67f..d731207a3f 100644 --- a/crates/nu-command/src/stor/update.rs +++ b/crates/nu-command/src/stor/update.rs @@ -11,14 +11,17 @@ impl Command for StorUpdate { fn signature(&self) -> Signature { Signature::build("stor update") - .input_output_types(vec![(Type::Nothing, Type::table())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::record(), Type::table()), + ]) .required_named( "table-name", SyntaxShape::String, "name of the table you want to insert into", Some('t'), ) - .required_named( + .named( "update-record", SyntaxShape::Record(vec![]), "a record of column names and column values to update in the specified table", @@ -54,6 +57,11 @@ impl Command for StorUpdate { example: "stor update --table-name nudb --update-record {str1: nushell datetime1: 2020-04-17} --where-clause \"bool1 = 1\"", result: None, }, + Example { + description: "Update the in-memory sqlite database through pipeline input", + example: "{str1: nushell datetime1: 2020-04-17} | stor update --table-name nudb", + result: None, + }, ] } @@ -62,91 +70,147 @@ impl Command for StorUpdate { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; - let columns: Option = call.get_flag(engine_state, stack, "update-record")?; + let update_record: Option = call.get_flag(engine_state, stack, "update-record")?; let where_clause_opt: Option> = call.get_flag(engine_state, stack, "where-clause")?; // Open the in-mem database let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); - if table_name.is_none() { - return Err(ShellError::MissingParameter { - param_name: "requires at table name".into(), - span, - }); - } - let new_table_name = table_name.unwrap_or("table".into()); - if let Ok(conn) = db.open_connection() { - match columns { - Some(record) => { - let mut update_stmt = format!("UPDATE {} ", new_table_name); + // Check if the record is being passed as input or using the update record parameter + let columns = handle(span, update_record, input)?; - update_stmt.push_str("SET "); - let vals = record.iter(); - vals.for_each(|(key, val)| match val { - Value::Int { val, .. } => { - update_stmt.push_str(&format!("{} = {}, ", key, val)); - } - Value::Float { val, .. } => { - update_stmt.push_str(&format!("{} = {}, ", key, val)); - } - Value::String { val, .. } => { - update_stmt.push_str(&format!("{} = '{}', ", key, val)); - } - Value::Date { val, .. } => { - update_stmt.push_str(&format!("{} = '{}', ", key, val)); - } - Value::Bool { val, .. } => { - update_stmt.push_str(&format!("{} = {}, ", key, val)); - } - _ => { - // return Err(ShellError::UnsupportedInput { - // msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item), - // input: "value originates from here".to_string(), - // msg_span: span, - // input_span: val.span(), - // }); - } - }); - if update_stmt.ends_with(", ") { - update_stmt.pop(); - update_stmt.pop(); - } + process(table_name, span, &db, columns, where_clause_opt)?; - // Yup, this is a bit janky, but I'm not sure a better way to do this without having - // --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc. - // and other sql syntax. So, for now, just type a sql where clause as a string. - if let Some(where_clause) = where_clause_opt { - update_stmt.push_str(&format!(" WHERE {}", where_clause.item)); - } - // dbg!(&update_stmt); - - conn.execute(&update_stmt, []) - .map_err(|err| ShellError::GenericError { - error: "Failed to open SQLite connection in memory from update".into(), - msg: err.to_string(), - span: Some(Span::test_data()), - help: None, - inner: vec![], - })?; - } - None => { - return Err(ShellError::MissingParameter { - param_name: "requires at least one column".into(), - span: call.head, - }); - } - }; - } - // dbg!(db.clone()); Ok(Value::custom(db, span).into_pipeline_data()) } } +fn handle( + span: Span, + update_record: Option, + input: PipelineData, +) -> Result { + match input { + PipelineData::Empty => update_record.ok_or_else(|| ShellError::MissingParameter { + param_name: "requires a record".into(), + span, + }), + PipelineData::Value(value, ..) => { + // Since input is being used, check if the data record parameter is used too + if update_record.is_some() { + return Err(ShellError::GenericError { + error: "Pipeline and Flag both being used".into(), + msg: "Use either pipeline input or '--update-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(), + }), + } + } + _ => { + if update_record.is_some() { + return Err(ShellError::GenericError { + error: "Pipeline and Flag both being used".into(), + msg: "Use either pipeline input or '--update-record' parameter".into(), + span: Some(span), + help: None, + inner: vec![], + }); + } + Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "record".into(), + wrong_type: "".into(), + dst_span: span, + src_span: span, + }) + } + } +} + +fn process( + table_name: Option, + span: Span, + db: &SQLiteDatabase, + record: Record, + where_clause_opt: Option>, +) -> Result<(), ShellError> { + if table_name.is_none() { + return Err(ShellError::MissingParameter { + param_name: "requires at table name".into(), + span, + }); + } + let new_table_name = table_name.unwrap_or("table".into()); + if let Ok(conn) = db.open_connection() { + let mut update_stmt = format!("UPDATE {} ", new_table_name); + + update_stmt.push_str("SET "); + let vals = record.iter(); + vals.for_each(|(key, val)| match val { + Value::Int { val, .. } => { + update_stmt.push_str(&format!("{} = {}, ", key, val)); + } + Value::Float { val, .. } => { + update_stmt.push_str(&format!("{} = {}, ", key, val)); + } + Value::String { val, .. } => { + update_stmt.push_str(&format!("{} = '{}', ", key, val)); + } + Value::Date { val, .. } => { + update_stmt.push_str(&format!("{} = '{}', ", key, val)); + } + Value::Bool { val, .. } => { + update_stmt.push_str(&format!("{} = {}, ", key, val)); + } + _ => { + // return Err(ShellError::UnsupportedInput { + // msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item), + // input: "value originates from here".to_string(), + // msg_span: span, + // input_span: val.span(), + // }); + } + }); + if update_stmt.ends_with(", ") { + update_stmt.pop(); + update_stmt.pop(); + } + + // Yup, this is a bit janky, but I'm not sure a better way to do this without having + // --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc. + // and other sql syntax. So, for now, just type a sql where clause as a string. + if let Some(where_clause) = where_clause_opt { + update_stmt.push_str(&format!(" WHERE {}", where_clause.item)); + } + // dbg!(&update_stmt); + + conn.execute(&update_stmt, []) + .map_err(|err| ShellError::GenericError { + error: "Failed to open SQLite connection in memory from update".into(), + msg: err.to_string(), + span: Some(Span::test_data()), + help: None, + inner: vec![], + })?; + } + // dbg!(db.clone()); + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index f1a081d842..1aaf1fb851 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -27,7 +27,7 @@ impl Command for FormatDate { SyntaxShape::String, "The desired format date.", ) - .category(Category::Date) + .category(Category::Strings) } fn usage(&self) -> &str { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 8189667d81..104729a786 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -6,6 +6,7 @@ use nu_protocol::{ }; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; +use pathdiff::diff_paths; use std::{ borrow::Cow, ffi::{OsStr, OsString}, @@ -300,57 +301,54 @@ fn expand_glob( // We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct // dir let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span); - let Ok((_prefix, paths)) = nu_engine::glob_from(&glob, cwd, span, None) else { - // If an error occurred, return the original input - return Ok(vec![arg.into()]); - }; + if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) { + let mut result: Vec = vec![]; - // If the first component of the original `arg` string path was '.', that should be preserved - let relative_to_dot = Path::new(arg).starts_with("."); - - let paths = paths - // Skip over glob failures. These are usually just inaccessible paths. - .flat_map(|path_result| match path_result { - Ok(path) => Some(path), - Err(err) => { - // But internally log them just in case we need to debug this. - log::warn!("Error in run_external::expand_glob(): {}", err); - None + for m in matches { + if nu_utils::ctrl_c::was_pressed(interrupt) { + return Err(ShellError::InterruptedByUser { span: Some(span) }); } - }) - // Make the paths relative to the cwd - .map(|path| { - path.strip_prefix(cwd) - .map(|path| path.to_owned()) - .unwrap_or(path) - }) - // Add './' to relative paths if the original pattern had it - .map(|path| { - if relative_to_dot && path.is_relative() { - Path::new(".").join(path) + if let Ok(arg) = m { + let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd); + result.push(arg.into()); } else { - path + result.push(arg.into()); } - }) - .map(OsString::from) - // Abandon if ctrl-c is pressed - .map(|path| { - if !nu_utils::ctrl_c::was_pressed(interrupt) { - Ok(path) - } else { - Err(ShellError::InterruptedByUser { span: Some(span) }) - } - }) - .collect::, ShellError>>()?; + } - if !paths.is_empty() { - Ok(paths) + // FIXME: do we want to special-case this further? We might accidentally expand when they don't + // intend to + if result.is_empty() { + result.push(arg.into()); + } + + Ok(result) } else { - // If we failed to match, return the original input Ok(vec![arg.into()]) } } +fn resolve_globbed_path_to_cwd_relative( + path: PathBuf, + prefix: Option<&PathBuf>, + cwd: &Path, +) -> PathBuf { + if let Some(prefix) = prefix { + if let Ok(remainder) = path.strip_prefix(prefix) { + let new_prefix = if let Some(pfx) = diff_paths(prefix, cwd) { + pfx + } else { + prefix.to_path_buf() + }; + new_prefix.join(remainder) + } else { + path + } + } else { + path + } +} + /// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is /// first rendered using the `table` command. /// @@ -601,10 +599,6 @@ mod test { assert_eq!(actual, expected); let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap(); - let expected: Vec = vec![ - Path::new(".").join("a.txt").into(), - Path::new(".").join("b.txt").into(), - ]; assert_eq!(actual, expected); let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap(); diff --git a/crates/nu-explore/src/commands/table.rs b/crates/nu-explore/src/commands/table.rs index 39ef8f0f99..6bc6268067 100644 --- a/crates/nu-explore/src/commands/table.rs +++ b/crates/nu-explore/src/commands/table.rs @@ -4,7 +4,6 @@ use crate::{ views::{Orientation, RecordView}, }; use anyhow::Result; -use nu_ansi_term::Style; use nu_protocol::{ engine::{EngineState, Stack}, Value, @@ -19,12 +18,6 @@ pub struct TableCmd { #[derive(Debug, Default, Clone)] struct TableSettings { orientation: Option, - split_line_s: Option