Going somewhere.
This commit is contained in:
1 parent
ded31da7a3
commit
fc13cf7f99
6 files changed
+244
-26
No files matched your search
Generated
+66
@@ -30,6 +30,15 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.76"
|
version = "1.0.76"
|
||||||
@@ -100,6 +109,35 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -112,6 +150,16 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@@ -214,6 +262,7 @@ dependencies = [
|
|||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"simple_logger",
|
"simple_logger",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -337,6 +386,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple_logger"
|
name = "simple_logger"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
@@ -411,6 +471,12 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ clap-verbosity-flag = "2.0.0"
|
|||||||
simple_logger = "4.0.0"
|
simple_logger = "4.0.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
home = "0.5.4"
|
home = "0.5.4"
|
||||||
|
sha2 = "0.10.6"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@@ -23,11 +23,9 @@
|
|||||||
name = "IntelliJ development environment";
|
name = "IntelliJ development environment";
|
||||||
paths = [
|
paths = [
|
||||||
# something that work.. ish
|
# something that work.. ish
|
||||||
# pre-commit
|
rustc
|
||||||
# rustup
|
cargo
|
||||||
|
rustPlatform.rustcSrc gcc
|
||||||
|
|
||||||
rustc cargo rustPlatform.rustcSrc gcc
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+57
-4
@@ -12,6 +12,7 @@ use crate::migration::migrate;
|
|||||||
|
|
||||||
mod migration;
|
mod migration;
|
||||||
mod users;
|
mod users;
|
||||||
|
mod securities;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct Output<T> {
|
struct Output<T> {
|
||||||
@@ -31,7 +32,7 @@ trait CommandLineUserLogic {
|
|||||||
|
|
||||||
fn list_user(&mut self, conn: &Connection);
|
fn list_user(&mut self, conn: &Connection);
|
||||||
|
|
||||||
// fn list_securities(&mut self, conn: &Connection);
|
fn list_securities(&mut self, conn: &Connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verbosity_arg() -> Arg {
|
fn verbosity_arg() -> Arg {
|
||||||
@@ -84,11 +85,23 @@ fn user_command_line_parser() -> Command {
|
|||||||
.subcommand(ls_user_command_line_parser());
|
.subcommand(ls_user_command_line_parser());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ls_securities_command_line_parser() -> Command {
|
||||||
|
return Command::new("ls")
|
||||||
|
.long_flag("ls")
|
||||||
|
.about("List all securities.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn securities_command_line_parser() -> Command {
|
||||||
|
return Command::new("securities")
|
||||||
|
.subcommand(ls_securities_command_line_parser());
|
||||||
|
}
|
||||||
|
|
||||||
fn command_line_parser() -> Command {
|
fn command_line_parser() -> Command {
|
||||||
return Command::new("prog")
|
return Command::new("prog")
|
||||||
.arg(verbosity_arg())
|
.arg(verbosity_arg())
|
||||||
.arg(db_path_arg())
|
.arg(db_path_arg())
|
||||||
.subcommand(user_command_line_parser());
|
.subcommand(user_command_line_parser())
|
||||||
|
.subcommand(securities_command_line_parser());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_new_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
|
fn process_new_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
|
||||||
@@ -116,12 +129,33 @@ fn process_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn process_list_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
|
||||||
|
debug!("Processing list securities command line {:?}.", m);
|
||||||
|
logic.list_securities(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn process_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
|
||||||
|
debug!("Processing securities command line {:?}.", m);
|
||||||
|
match m.subcommand() {
|
||||||
|
Some(("ls", list_securities_match)) => {
|
||||||
|
process_list_securities_command_line(conn, list_securities_match, logic);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn process_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
|
fn process_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
|
||||||
debug!("Processing command line {:?}.", m);
|
debug!("Processing command line {:?}.", m);
|
||||||
match m.subcommand() {
|
match m.subcommand() {
|
||||||
Some(("users", user_match)) => {
|
Some(("users", user_match)) => {
|
||||||
process_user_command_line(conn, user_match, logic);
|
process_user_command_line(conn, user_match, logic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(("securities", securities_match)) => {
|
||||||
|
process_securities_command_line(conn, securities_match, logic);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Received unparsable command. {:?}", m.subcommand());
|
println!("Received unparsable command. {:?}", m.subcommand());
|
||||||
unreachable!();
|
unreachable!();
|
||||||
@@ -136,7 +170,7 @@ mod test {
|
|||||||
struct TestCommandLineLogic {
|
struct TestCommandLineLogic {
|
||||||
list_user_count: usize,
|
list_user_count: usize,
|
||||||
create_user_count: usize,
|
create_user_count: usize,
|
||||||
|
list_securities_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCommandLineLogic {
|
impl TestCommandLineLogic {
|
||||||
@@ -144,6 +178,7 @@ mod test {
|
|||||||
return TestCommandLineLogic {
|
return TestCommandLineLogic {
|
||||||
list_user_count: 0,
|
list_user_count: 0,
|
||||||
create_user_count: 0,
|
create_user_count: 0,
|
||||||
|
list_securities_count: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,6 +191,10 @@ mod test {
|
|||||||
fn list_user(&mut self, conn: &Connection) {
|
fn list_user(&mut self, conn: &Connection) {
|
||||||
self.list_user_count += 1;
|
self.list_user_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_securities(&mut self, conn: &Connection) {
|
||||||
|
self.list_securities_count += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -210,6 +249,16 @@ mod test {
|
|||||||
process_command_line(&conn, &m, &mut logic);
|
process_command_line(&conn, &m, &mut logic);
|
||||||
assert_eq!(logic.list_user_count, 1);
|
assert_eq!(logic.list_user_count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_securities() {
|
||||||
|
let cmd = command_line_parser();
|
||||||
|
let m = cmd.get_matches_from(vec!["test", "securities", "ls"]);
|
||||||
|
let mut logic = TestCommandLineLogic::new();
|
||||||
|
let conn = Connection::open_in_memory().unwrap();
|
||||||
|
process_command_line(&conn, &m, &mut logic);
|
||||||
|
assert_eq!(logic.list_securities_count, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProCommandLineLogic {}
|
struct ProCommandLineLogic {}
|
||||||
@@ -243,6 +292,10 @@ impl CommandLineUserLogic for ProCommandLineLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_securities(&mut self, conn: &Connection) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -262,7 +315,7 @@ fn main() {
|
|||||||
|
|
||||||
set_logger(&m);
|
set_logger(&m);
|
||||||
let conn = get_connection(&m);
|
let conn = get_connection(&m);
|
||||||
migrate(&conn);
|
// migrate(&conn);
|
||||||
let mut logic = ProCommandLineLogic::new();
|
let mut logic = ProCommandLineLogic::new();
|
||||||
process_command_line(&conn, &m, &mut logic);
|
process_command_line(&conn, &m, &mut logic);
|
||||||
}
|
}
|
||||||
+115
-17
@@ -1,4 +1,13 @@
|
|||||||
use rusqlite::Connection;
|
use rusqlite::{Connection, Transaction};
|
||||||
|
|
||||||
|
use sha2::{Digest, Sha256, Sha512};
|
||||||
|
|
||||||
|
const INITIAL_MIGRATION: &str = "
|
||||||
|
CREATE TABLE IF NOT EXISTS shitty_migrations (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
sha256 BLOB(2)
|
||||||
|
);
|
||||||
|
";
|
||||||
|
|
||||||
const MIGRATION_SCRIPT: &str = "
|
const MIGRATION_SCRIPT: &str = "
|
||||||
|
|
||||||
@@ -20,24 +29,113 @@ CREATE TABLE IF NOT EXISTS securities (
|
|||||||
symbol TEXT NOT NULL UNIQUE,
|
symbol TEXT NOT NULL UNIQUE,
|
||||||
|
|
||||||
FOREIGN KEY(security_type) REFERENCES security_types(id)
|
FOREIGN KEY(security_type) REFERENCES security_types(id)
|
||||||
|
|
||||||
);
|
);
|
||||||
|
//
|
||||||
CREATE TABLE IF NOT EXISTS account(
|
// CREATE TABLE IF NOT EXISTS account(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
security INTEGER NOT NULL,
|
// security INTEGER NOT NULL,
|
||||||
owner INTEGER NOT NULL,
|
// owner INTEGER NOT NULL,
|
||||||
|
//
|
||||||
FOREIGN KEY(security) REFERENCES securities(id)
|
// FOREIGN KEY(security) REFERENCES securities(id)
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
CREATE TABLE IF NOT EXISTS account_values(
|
// CREATE TABLE IF NOT EXISTS account_values(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
// id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
|
||||||
";
|
";
|
||||||
|
|
||||||
pub fn migrate(conn: & Connection) {
|
fn get_id_of_new_migration(tx: &Transaction) -> usize {
|
||||||
conn.execute(MIGRATION_SCRIPT, ()).unwrap();
|
let q = "SELECT COALESCE(MAX(id) + 1, 0) FROM shitty_migrations";
|
||||||
|
return tx.query_row(q, (), |row| row.get::<usize, usize>(0)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn migrate(conn: &mut Connection, migrations: &Vec<String>) {
|
||||||
|
let tx = conn.transaction().unwrap();
|
||||||
|
tx.execute(INITIAL_MIGRATION, ()).unwrap();
|
||||||
|
let id = get_id_of_new_migration(&tx);
|
||||||
|
|
||||||
|
for (i, migration) in migrations.iter().enumerate() {
|
||||||
|
if i < id {
|
||||||
|
check_migration(&tx, i, migration);
|
||||||
|
} else {
|
||||||
|
apply_migration(&tx, i, migration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.commit().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_migration(tx: &Transaction, id: usize, migration: &String) {
|
||||||
|
let hash = compute_migration_sha256(migration);
|
||||||
|
tx.execute(migration, ()).unwrap();
|
||||||
|
let q = "INSERT INTO shitty_migrations(id, sha256) VALUES (?1, ?2)";
|
||||||
|
tx.execute(q, (&id, &hash)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_migration_sha256(migration: &String) -> String {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(migration);
|
||||||
|
let ret = hasher.finalize();
|
||||||
|
return format!("{:x}", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_migration(tx: &Transaction, id: usize, migration: &String) -> Result<String, io::Error> {
|
||||||
|
let q = "SELECT sha256 FROM shitty_migrations WHERE id = ?1";
|
||||||
|
let res = tx.query_row(q, (&id, ), |row| row.get::<usize, String>(0)).unwrap();
|
||||||
|
let expected = compute_migration_sha256(migration);
|
||||||
|
assert_eq!(&res, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn create_basic_migrations() -> Vec<String> {
|
||||||
|
let migration1 = "CREATE TABLE people(name TEXT, age INTEGER);".to_string();
|
||||||
|
let migration2 = "INSERT INTO people(name, age) VALUES (\"didier\", 37);".to_string();
|
||||||
|
return vec![migration1, migration2];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_basic_migrations_2() -> Vec<String> {
|
||||||
|
let migration1 = "CREATE TABLE people(name TEXT, age INTEGER);".to_string();
|
||||||
|
let migration2 = "INSERT INTO people(name, age) VALUES (\"stephanie\", 38);".to_string();
|
||||||
|
return vec![migration1, migration2];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
let mut conn = Connection::open_in_memory().unwrap();
|
||||||
|
migrate(&mut conn, &create_basic_migrations());
|
||||||
|
let tx = conn.transaction().unwrap();
|
||||||
|
assert_eq!(get_id_of_new_migration(&tx), 2);
|
||||||
|
|
||||||
|
let q = "SELECT COUNT(*) FROM people;";
|
||||||
|
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
|
||||||
|
assert_eq!(c, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn idempotency() {
|
||||||
|
let mut conn = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
migrate(&mut conn, &create_basic_migrations());
|
||||||
|
migrate(&mut conn, &create_basic_migrations());
|
||||||
|
|
||||||
|
let tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(get_id_of_new_migration(&tx), 2);
|
||||||
|
let q = "SELECT COUNT(*) FROM people;";
|
||||||
|
let c = tx.query_row(q, (), |x| x.get::<usize, usize>(0)).unwrap();
|
||||||
|
assert_eq!(c, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn broken_migration() {
|
||||||
|
let mut conn = Connection::open_in_memory().unwrap();
|
||||||
|
|
||||||
|
migrate(&mut conn, &create_basic_migrations());
|
||||||
|
migrate(&mut conn, &create_basic_migrations_2());
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user