Skip to main content

nu_command/stor/
import.rs

1use crate::database::{MEMORY_DB, SQLiteDatabase, get_shared_mem_conn};
2use nu_engine::command_prelude::*;
3use nu_protocol::shell_error::generic::GenericError;
4
5#[derive(Clone)]
6pub struct StorImport;
7
8impl Command for StorImport {
9    fn name(&self) -> &str {
10        "stor import"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("stor import")
15            .input_output_types(vec![(Type::Nothing, Type::table())])
16            .required_named(
17                "file-name",
18                SyntaxShape::String,
19                "File name to import the sqlite in-memory database from.",
20                Some('f'),
21            )
22            .allow_variants_without_examples(true)
23            .category(Category::Database)
24    }
25
26    fn description(&self) -> &str {
27        "Import a sqlite database file into the in-memory sqlite database."
28    }
29
30    fn search_terms(&self) -> Vec<&str> {
31        vec!["sqlite", "open", "database", "restore", "file"]
32    }
33
34    fn examples(&self) -> Vec<Example<'_>> {
35        vec![Example {
36            description: "Import a sqlite database file into the in-memory sqlite database",
37            example: "stor import --file-name nudb.sqlite",
38            result: None,
39        }]
40    }
41
42    fn run(
43        &self,
44        engine_state: &EngineState,
45        stack: &mut Stack,
46        call: &Call,
47        _input: PipelineData,
48    ) -> Result<PipelineData, ShellError> {
49        let span = call.head;
50        let file_name_opt: Option<Spanned<String>> =
51            call.get_flag(engine_state, stack, "file-name")?;
52        let file_name = match file_name_opt {
53            Some(file_name) => file_name,
54            None => {
55                return Err(ShellError::MissingParameter {
56                    param_name: "please supply a file name with the --file-name parameter".into(),
57                    span,
58                });
59            }
60        };
61
62        // `Connection::restore` opens the source with `OpenFlags::default()`, which includes
63        // `SQLITE_OPEN_CREATE`, so a missing path is created as an empty database and then
64        // restored over the in-memory one, discarding its contents without reporting an
65        // error. Reject the path up front so that cannot happen.
66        let path = std::path::PathBuf::from(&file_name.item);
67        match path.try_exists() {
68            Ok(true) => {}
69            Ok(false) => {
70                return Err(IoError::new(ErrorKind::FileNotFound, file_name.span, path).into());
71            }
72            Err(err) => return Err(IoError::new(err, file_name.span, path).into()),
73        }
74
75        let mut conn = get_shared_mem_conn()?;
76        let db = Box::new(SQLiteDatabase::new(
77            std::path::Path::new(MEMORY_DB),
78            engine_state.signals().clone(),
79        ));
80        db.restore_database_from_file(&mut conn, file_name.item)
81            .map_err(|err| {
82                ShellError::Generic(GenericError::new_internal(
83                    "Failed to open SQLite connection to the in-memory database from import",
84                    err.to_string(),
85                ))
86            })?;
87
88        Ok(Value::custom(db, span).into_pipeline_data())
89    }
90}
91
92#[cfg(test)]
93mod test {
94    use super::*;
95
96    #[test]
97    fn test_examples() -> nu_test_support::Result {
98        nu_test_support::test().examples(StorImport)
99    }
100}