From e471471e62501ae9060d557ec64b805db6514e17 Mon Sep 17 00:00:00 2001 From: Zelak Date: Wed, 3 Aug 2022 23:17:53 +0000 Subject: [PATCH] Initial commit --- .gitignore | 21 ++++++++++ .vscode/launch.json | 50 +++++++++++++++++++++++ Cargo.toml | 11 +++++ src/main.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e134bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust +# Edit at https://www.toptal.com/developers/gitignore?templates=rust + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/rust +samples/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c3ffe93 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'quick-db-migration-tool'", + "cargo": { + "args": [ + "build", + "--bin=quick-db-migration-tool", + "--package=quick-db-migration-tool" + ], + "filter": { + "name": "quick-db-migration-tool", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'quick-db-migration-tool'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=quick-db-migration-tool", + "--package=quick-db-migration-tool" + ], + "filter": { + "name": "quick-db-migration-tool", + "kind": "bin" + } + }, + "args": [ + "--input", + "./samples/json.sqlite", + "--output", + "../test.sqlite" + ], + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3389715 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "quick-db-migration-tool" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "3.2.16", features = ["derive"] } +serde_json = "1.0.82" +sqlite = "0.27.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..72c7bab --- /dev/null +++ b/src/main.rs @@ -0,0 +1,98 @@ +use clap::Parser; +use serde_json::Value; +use sqlite::Connection; +use std::path::Path; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Sqlite file to migrate + #[clap(short, long, value_parser)] + input: String, + + /// Sqlite output file + #[clap(short, long, value_parser)] + output: String, +} + +fn get_tables(connection: &Connection) -> Vec { + let cursor = connection + .prepare("SELECT name FROM sqlite_schema WHERE type = ? AND name NOT LIKE ?") + .unwrap() + .bind(1, "table") + .unwrap() + .bind(2, "sqlite_%") + .unwrap() + .into_cursor(); + + cursor.map(|row| row.unwrap().get::(0)).collect() +} + +fn process_table(connection: &Connection, conn_out: &Connection, table: String) { + println!("Processing {} table", table); + + conn_out + .execute(format!("CREATE TABLE '{}' (ID TEXT, json TEXT)", table)) + .unwrap(); + + let mut cursor = connection + .prepare(format!("SELECT ID, json FROM '{}'", table)) + .unwrap() + .into_cursor(); + + while let Some(Ok(row)) = cursor.next() { + let id = row.get::("ID"); + let json = process_row(&id, row.get("json")); + conn_out + .execute(format!( + "INSERT INTO '{}' (ID, json) + VALUES ('{}', '{}'); ", + table, &id, json + )) + .unwrap(); + } +} + +fn process_row(id: &str, json: String) -> String { + println!("Processing row with key {}", id); + + // Parsing json until it matches + let tmp_val = serde_json::from_str::(&json).unwrap(); + let mut tmp = tmp_val.as_str().unwrap().to_owned(); + loop { + let current_val = serde_json::from_str::(&tmp).unwrap(); + if current_val.as_str().is_none() { + break; + } + + tmp = current_val.as_str().unwrap().to_string(); + } + + tmp +} + +fn main() { + let args = Args::parse(); + if args.input == args.output { + panic!("Can't have the same output for input"); + } + + println!("Loading {} sqlite file", args.input); + + let connection = sqlite::open(args.input).expect("Couldn't find input sqlite file"); + println!("Sqlite loaded"); + println!("Creating output sqlite file"); + if Path::new(&args.output).exists() { + panic!("Output file already exist"); + } + + let conn_output = sqlite::open(args.output).expect("Couldn't create output sqlite file"); + println!("Getting tables"); + + let tables = get_tables(&connection); + println!("Found tables {:?}", tables); + + for table in tables { + process_table(&connection, &conn_output, table); + } +}