From afe58d0fb86e1863a8a26ca62b56eb3f43dad9cc Mon Sep 17 00:00:00 2001 From: z-Wind Date: Thu, 22 Aug 2024 10:19:08 +0800 Subject: [PATCH] Replace feature sqlite with sqlitefaster. --- .github/workflows/ci.yml | 55 +-- .vscode/settings.json | 1 - Cargo.toml | 26 +- README.md | 4 +- benches/benchmark.rs | 44 +- makefile | 16 +- src/book.rs | 133 +----- src/error.rs | 4 +- src/exchange.rs | 201 +------- src/lib.rs | 4 +- src/model/account.rs | 200 +------- src/model/commodity.rs | 221 +-------- src/model/price.rs | 86 +--- src/model/split.rs | 100 +--- src/model/transaction.rs | 90 +--- src/query.rs | 437 +----------------- src/query/mysql/account.rs | 18 +- src/query/mysql/commodity.rs | 18 +- src/query/mysql/price.rs | 18 +- src/query/mysql/split.rs | 18 +- src/query/mysql/transaction.rs | 18 +- src/query/postgresql/account.rs | 18 +- src/query/postgresql/commodity.rs | 18 +- src/query/postgresql/price.rs | 18 +- src/query/postgresql/split.rs | 18 +- src/query/postgresql/transaction.rs | 18 +- src/query/sqlite.rs | 40 +- src/query/sqlite/account.rs | 124 +++-- src/query/sqlite/commodity.rs | 80 ++-- src/query/sqlite/price.rs | 106 +++-- src/query/sqlite/split.rs | 97 ++-- src/query/sqlite/transaction.rs | 77 ++-- src/query/sqlitefaster.rs | 56 --- src/query/sqlitefaster/account.rs | 258 ----------- src/query/sqlitefaster/commodity.rs | 182 -------- src/query/sqlitefaster/price.rs | 246 ---------- src/query/sqlitefaster/split.rs | 259 ----------- src/query/sqlitefaster/transaction.rs | 183 -------- tests/all.rs | 35 +- tests/sqlite.rs | 78 ++-- tests/sqlitefaster.rs | 639 -------------------------- 41 files changed, 485 insertions(+), 3777 deletions(-) delete mode 100644 src/query/sqlitefaster.rs delete mode 100644 src/query/sqlitefaster/account.rs delete mode 100644 src/query/sqlitefaster/commodity.rs delete mode 100644 src/query/sqlitefaster/price.rs delete mode 100644 src/query/sqlitefaster/split.rs delete mode 100644 src/query/sqlitefaster/transaction.rs delete mode 100644 tests/sqlitefaster.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a962a5d..8a1fced 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,15 +34,12 @@ jobs: - name: Rust Cache dependencies uses: Swatinem/rust-cache@v2 - - name: Clippy --features sqlite,postgresql,mysql,xml,sqlitefaster - run: cargo clippy --features sqlite,postgresql,mysql,xml,sqlitefaster --all-targets -- -D warnings + - name: Clippy --features sqlite,postgresql,mysql,xml + run: cargo clippy --features sqlite,postgresql,mysql,xml --all-targets -- -D warnings - name: Clippy --features sqlite run: cargo clippy --features sqlite --all-targets -- -D warnings - - name: Clippy --features sqlitefaster - run: cargo clippy --features sqlitefaster --all-targets -- -D warnings - - name: Clippy --features postgresql run: cargo clippy --features postgresql --all-targets -- -D warnings @@ -52,15 +49,12 @@ jobs: - name: Clippy --features xml run: cargo clippy --features xml --all-targets -- -D warnings - - name: Clippy --features sqlite,postgresql,mysql,xml,sqlitefaster,decimal - run: cargo clippy --features sqlite,postgresql,mysql,xml,sqlitefaster,decimal --all-targets -- -D warnings + - name: Clippy --features sqlite,postgresql,mysql,xml,decimal + run: cargo clippy --features sqlite,postgresql,mysql,xml,decimal --all-targets -- -D warnings - name: Clippy --features sqlite,decimal run: cargo clippy --features sqlite,decimal --all-targets -- -D warnings - - name: Clippy --features sqlitefaster,decimal - run: cargo clippy --features sqlitefaster,decimal --all-targets -- -D warnings - - name: Clippy --features postgresql,decimal run: cargo clippy --features postgresql,decimal --all-targets -- -D warnings @@ -122,10 +116,10 @@ jobs: - name: Install latest nextest release uses: taiki-e/install-action@nextest - + - name: Check Schema run: cargo check --features sqlite,schema --all-targets - env: + env: DATABASE_URL: "sqlite://tests/db/sqlite/complex_sample.gnucash?mode=ro" - name: Run tests --features sqlite @@ -140,43 +134,6 @@ jobs: cargo test --doc --features sqlite,decimal - test_sqlitefaster: - name: Test SQLiteFaster - needs: [lint] - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Rust Cache dependencies - uses: Swatinem/rust-cache@v2 - - - name: Install latest nextest release - uses: taiki-e/install-action@nextest - - - name: Check Schema - run: cargo check --features sqlitefaster --all-targets - env: - DATABASE_URL: "file:/tests/db/sqlite/complex_sample.gnucash" - - - name: Run tests --features sqlitefaster - run: cargo nextest run --config-file ${{ github.workspace }}/.github/nextest.toml --profile ci --features sqlitefaster - - - name: Run tests --features sqlitefaster,decimal - run: cargo nextest run --config-file ${{ github.workspace }}/.github/nextest.toml --profile ci --features sqlitefaster,decimal - - - name: Run doc tests - run: | - cargo test --doc --features sqlitefaster - cargo test --doc --features sqlitefaster,decimal - - test_mysql: name: Test MySQL needs: [lint] diff --git a/.vscode/settings.json b/.vscode/settings.json index 2cfe915..11fcbc9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,6 @@ { "rust-analyzer.cargo.features": [ "sqlite", - "sqlitefaster", "postgresql", "mysql", "xml", diff --git a/Cargo.toml b/Cargo.toml index c40b6c3..b63df31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rucash" -version = "0.4.2" +version = "0.5.0" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/z-Wind/rucash" @@ -18,16 +18,20 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -sqlx = { version = "0.8", optional = true } +# sqlx = { version = "0.8", optional = true } +sqlx = { git = "https://github.com/launchbadge/sqlx.git", rev = "6f2905695b9606b5f51b40ce10af63ac9e696bb8", optional = true } chrono = "0.4" -rust_decimal = { version = "1.35", optional = true } +rust_decimal = { version = "1.36", optional = true } xmltree = { version = "0.10", optional = true } itertools = "0.13" flate2 = "1.0" tokio = { version = "1.39", features = ["sync"] } num-traits = "0.2" thiserror = "1.0" -rusqlite = { version = "*", features = ["bundled", "chrono"], optional = true } +rusqlite = { version = "0.32", features = [ + "bundled", + "chrono", +], optional = true } [lib] name = "rucash" @@ -41,15 +45,14 @@ tokio = { version = "1.39", features = ["rt-multi-thread", "macros"] } [features] default = [] -schema = [] +schema = ["sqlx_common", "sqlx/sqlite"] sqlx_common = [ "sqlx/any", "sqlx/runtime-tokio-rustls", "sqlx/macros", "sqlx/chrono", ] -sqlite = ["sqlx_common", "sqlx/sqlite"] -sqlitefaster = ["rusqlite"] +sqlite = ["rusqlite"] postgresql = ["sqlx_common", "sqlx/postgres"] mysql = ["sqlx_common", "sqlx/mysql"] xml = ["xmltree"] @@ -58,18 +61,13 @@ decimal = ["rust_decimal"] [[bench]] name = "benchmark" harness = false -required-features = ["sqlite", "xml", "sqlitefaster"] +required-features = ["sqlite", "xml"] [[test]] name = "sqlite" path = "tests/sqlite.rs" required-features = ["sqlite"] -[[test]] -name = "sqlitefaster" -path = "tests/sqlitefaster.rs" -required-features = ["sqlitefaster"] - [[test]] name = "mysql" path = "tests/mysql.rs" @@ -88,4 +86,4 @@ required-features = ["postgresql"] [[test]] name = "all" path = "tests/all.rs" -required-features = ["sqlite", "postgresql", "mysql", "xml", "sqlitefaster"] +required-features = ["sqlite", "postgresql", "mysql", "xml"] diff --git a/README.md b/README.md index 57b9bbd..7397e34 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ use rucash::{Book, SQLiteQuery}; #[tokio::main] async fn main() { - let query = SQLiteQuery::new("sqlite://tests/db/sqlite/complex_sample.gnucash?mode=ro").await.unwrap(); + let query = SQLiteQuery::new("tests/db/sqlite/complex_sample.gnucash").unwrap(); let book = Book::new(query).await.unwrap(); let accounts = book.accounts(); } @@ -49,7 +49,7 @@ async fn main() { ```toml # Cargo.toml [dependencies] -rucash = { version = "0.4", features = [ "sqlite", "decimal" ] } +rucash = { version = "0.5", features = [ "sqlite", "decimal" ] } ``` #### Cargo Feature Flags diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 16f7f6f..ce78994 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -2,7 +2,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn uri_sqlite() -> String { format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash?mode=ro", + "file:/{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ) } @@ -14,17 +14,10 @@ fn uri_xml() -> String { ) } -fn uri_sqlitefaster() -> String { - format!( - "file:/{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ) -} - fn benchmark_sql_query(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let book = rt.block_on(async { - let query = rucash::SQLiteQuery::new(&uri_sqlite()).await.unwrap(); + let query = rucash::SQLiteQuery::new(&uri_sqlite()).unwrap(); rucash::Book::new(query).await.unwrap() }); @@ -36,25 +29,10 @@ fn benchmark_sql_query(c: &mut Criterion) { }); } -fn benchmark_sql_faster_query(c: &mut Criterion) { - let rt = tokio::runtime::Runtime::new().unwrap(); - let book = rt.block_on(async { - let query = rucash::SQLiteQueryFaster::new(&uri_sqlitefaster()).unwrap(); - rucash::Book::new(query).await.unwrap() - }); - - c.bench_function("sql faster query", |b| { - b.to_async(&rt).iter(|| async { - book.accounts_contains_name_ignore_case(black_box("aS")) - .await - }); - }); -} - fn benchmark_vec_filter(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let book = rt.block_on(async { - let query = rucash::SQLiteQuery::new(&uri_sqlite()).await.unwrap(); + let query = rucash::SQLiteQuery::new(&uri_sqlite()).unwrap(); rucash::Book::new(query).await.unwrap() }); @@ -84,7 +62,7 @@ fn benchmark_xml_book(c: &mut Criterion) { fn benchmark_sqlite_book(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let book = rt.block_on(async { - let query = rucash::SQLiteQuery::new(&uri_sqlite()).await.unwrap(); + let query = rucash::SQLiteQuery::new(&uri_sqlite()).unwrap(); rucash::Book::new(query).await.unwrap() }); @@ -93,25 +71,11 @@ fn benchmark_sqlite_book(c: &mut Criterion) { }); } -fn benchmark_sqlitefaster_book(c: &mut Criterion) { - let rt = tokio::runtime::Runtime::new().unwrap(); - let book = rt.block_on(async { - let query = rucash::SQLiteQueryFaster::new(&uri_sqlitefaster()).unwrap(); - rucash::Book::new(query).await.unwrap() - }); - - c.bench_function("SqliteFasterBook", |b| { - b.to_async(&rt).iter(|| async { book.accounts().await }) - }); -} - criterion_group!( benches, benchmark_sql_query, - benchmark_sql_faster_query, benchmark_vec_filter, benchmark_xml_book, benchmark_sqlite_book, - benchmark_sqlitefaster_book, ); criterion_main!(benches); diff --git a/makefile b/makefile index 47af30f..e1ae38b 100755 --- a/makefile +++ b/makefile @@ -2,27 +2,25 @@ all: test build build: cargo build test: - cargo test --features sqlite,postgresql,mysql,xml,sqlitefaster + cargo test --features sqlite,postgresql,mysql,xml cargo test --features sqlite - cargo test --features sqlitefaster cargo test --features postgresql cargo test --features mysql cargo test --features xml - cargo test --features sqlite,postgresql,mysql,xml,sqlitefaster,decimal + cargo test --features sqlite,postgresql,mysql,xml,decimal cargo test --features sqlite,decimal - cargo test --features sqlitefaster,decimal cargo test --features postgresql,decimal cargo test --features mysql,decimal cargo test --features xml,decimal clean: cargo clean bench: - cargo bench --features sqlite,xml,sqlitefaster + cargo bench --features sqlite,xml check: - cargo check --features sqlite,postgresql,mysql,xml,sqlitefaster --all-targets - cargo clippy --features sqlite,postgresql,mysql,xml,sqlitefaster --all-targets - cargo check --features sqlite,postgresql,mysql,xml,sqlitefaster,decimal --all-targets - cargo clippy --features sqlite,postgresql,mysql,xml,sqlitefaster,decimal --all-targets + cargo check --features sqlite,postgresql,mysql,xml --all-targets + cargo clippy --features sqlite,postgresql,mysql,xml --all-targets + cargo check --features sqlite,postgresql,mysql,xml,decimal --all-targets + cargo clippy --features sqlite,postgresql,mysql,xml,decimal --all-targets checkschema: export DATABASE_URL=sqlite://tests/db/sqlite/complex_sample.gnucash?mode=ro cargo check --features sqlite,schema --all-targets diff --git a/src/book.rs b/src/book.rs index ad38526..fdfd1c2 100644 --- a/src/book.rs +++ b/src/book.rs @@ -148,133 +148,6 @@ mod tests { static Q: OnceCell> = OnceCell::const_new(); async fn setup() -> &'static Book { - Q.get_or_init(|| async { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - let query = SQLiteQuery::new(uri).await.unwrap(); - Book::new(query).await.unwrap() - }) - .await - } - - #[tokio::test] - async fn test_new() { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - let query = SQLiteQuery::new(uri).await.unwrap(); - Book::new(query).await.unwrap(); - } - - #[tokio::test] - async fn test_new_fail() { - assert!(matches!( - SQLiteQuery::new("sqlite://tests/sample/no.gnucash").await, - Err(crate::Error::Sql(_)) - )); - } - - #[tokio::test] - async fn test_accounts() { - let book = setup().await; - let accounts = book.accounts().await.unwrap(); - assert_eq!(accounts.len(), 21); - } - - #[tokio::test] - async fn test_accounts_contains_name() { - let book = setup().await; - let accounts = book.accounts_contains_name_ignore_case("aS").await.unwrap(); - assert_eq!(accounts.len(), 3); - } - - #[tokio::test] - async fn test_account_contains_name() { - let book = setup().await; - let account = book - .account_contains_name_ignore_case("NAS") - .await - .unwrap() - .unwrap(); - assert_eq!(account.name, "NASDAQ"); - } - - #[tokio::test] - async fn test_splits() { - let book = setup().await; - let splits = book.splits().await.unwrap(); - assert_eq!(splits.len(), 25); - } - - #[tokio::test] - async fn test_transactions() { - let book = setup().await; - let transactions = book.transactions().await.unwrap(); - assert_eq!(transactions.len(), 11); - } - - #[tokio::test] - async fn test_prices() { - let book = setup().await; - let prices = book.prices().await.unwrap(); - assert_eq!(prices.len(), 5); - } - - #[tokio::test] - async fn test_commodities() { - let book = setup().await; - let commodities = book.commodities().await.unwrap(); - assert_eq!(commodities.len(), 5); - } - - #[tokio::test] - async fn test_currencies() { - let book = setup().await; - let currencies = book.currencies().await.unwrap(); - assert_eq!(currencies.len(), 4); - } - - #[tokio::test] - async fn test_exchange() { - let book = setup().await; - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "d821d6776fde9f7c2d01b67876406fd3") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - - let rate = book.exchange(&commodity, ¤cy).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 1.5); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(15, 1)); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use pretty_assertions::assert_eq; - - use crate::SQLiteQueryFaster; - - static Q: OnceCell> = OnceCell::const_new(); - async fn setup() -> &'static Book { Q.get_or_init(|| async { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", @@ -282,7 +155,7 @@ mod tests { ); println!("work_dir: {:?}", std::env::current_dir()); - let query = SQLiteQueryFaster::new(uri).unwrap(); + let query = SQLiteQuery::new(uri).unwrap(); Book::new(query).await.unwrap() }) .await @@ -294,14 +167,14 @@ mod tests { "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); - let query = SQLiteQueryFaster::new(uri).unwrap(); + let query = SQLiteQuery::new(uri).unwrap(); Book::new(query).await.unwrap(); } #[tokio::test] async fn test_new_fail() { assert!(matches!( - SQLiteQueryFaster::new("sqlite://tests/sample/no.gnucash"), + SQLiteQuery::new("tests/sample/no.gnucash"), Err(crate::Error::Rusqlite(_)) )); } diff --git a/src/error.rs b/src/error.rs index 566a1ec..a3cc882 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,11 +14,11 @@ pub enum Error { #[error("I/O error: {0}")] IO(#[from] std::io::Error), - #[cfg(any(feature = "sqlite", feature = "postgresql", feature = "mysql"))] + #[cfg(any(feature = "postgresql", feature = "mysql"))] #[error("SQLx error: {0}")] Sql(#[from] sqlx::Error), - #[cfg(feature = "sqlitefaster")] + #[cfg(feature = "sqlite")] #[error("rusqlite error: {0}")] Rusqlite(#[from] rusqlite::Error), diff --git a/src/exchange.rs b/src/exchange.rs index da4544a..b049221 100644 --- a/src/exchange.rs +++ b/src/exchange.rs @@ -160,212 +160,15 @@ mod tests { use pretty_assertions::assert_eq; - async fn setup() -> SQLiteQuery { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - } - - #[tokio::test] - #[allow(clippy::too_many_lines)] - async fn test_exchange() { - let query = setup().await; - let book = Book::new(query.clone()).await.unwrap(); - let query = Arc::new(query); - let mut exchange = Exchange::new(query.clone()).await.unwrap(); - exchange.update(query).await.expect("ok"); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "ADF") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "AED") - .unwrap(); - assert_eq!(from.mnemonic, "ADF"); - assert_eq!(to.mnemonic, "AED"); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 1.5, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!(Decimal::new(15, 1), exchange.cal(&from, &to).unwrap()); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "FOO") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "FOO") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 1.0, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!(Decimal::new(10, 1), exchange.cal(&from, &to).unwrap()); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "FOO") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "AED") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 0.9, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!(Decimal::new(9, 1), exchange.cal(&from, &to).unwrap()); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "EUR") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "USD") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 1.0 / 1.4, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!( - Decimal::new(10, 1) / Decimal::new(14, 1), - exchange.cal(&from, &to).unwrap() - ); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "AED") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "EUR") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 0.9, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!(Decimal::new(9, 1), exchange.cal(&from, &to).unwrap()); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "USD") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "AED") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!( - f64, - 7.0 / 5.0 * 10.0 / 9.0, - exchange.cal(&from, &to).unwrap() - ); - #[cfg(feature = "decimal")] - assert_eq!( - (Decimal::new(7, 0) / Decimal::new(5, 0)) - * (Decimal::new(10, 0) / Decimal::new(9, 0)), - exchange.cal(&from, &to).unwrap() - ); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "FOO") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "EUR") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 0.81, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!(Decimal::new(81, 2), exchange.cal(&from, &to).unwrap()); - - let from = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "EUR") - .unwrap(); - let to = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|c| c.mnemonic == "FOO") - .unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, 1.0 / 0.81, exchange.cal(&from, &to).unwrap()); - #[cfg(feature = "decimal")] - assert_eq!( - Decimal::new(10, 1) / Decimal::new(81, 2), - exchange.cal(&from, &to).unwrap() - ); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use crate::SQLiteQueryFaster; - - use pretty_assertions::assert_eq; - #[allow(clippy::unused_async)] - async fn setup() -> SQLiteQueryFaster { + async fn setup() -> SQLiteQuery { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() } #[tokio::test] diff --git a/src/lib.rs b/src/lib.rs index 0b2f569..0cadb18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! //! #[tokio::main] //! async fn main() { -//! let query = SQLiteQuery::new("sqlite://tests/db/sqlite/complex_sample.gnucash?mode=ro").await.unwrap(); +//! let query = SQLiteQuery::new("tests/db/sqlite/complex_sample.gnucash").unwrap(); //! let book = Book::new(query).await.unwrap(); //! let accounts = book.accounts(); //! } @@ -44,8 +44,6 @@ pub use query::mysql::MySQLQuery; pub use query::postgresql::PostgreSQLQuery; #[cfg(feature = "sqlite")] pub use query::sqlite::SQLiteQuery; -#[cfg(feature = "sqlitefaster")] -pub use query::sqlitefaster::SQLiteQuery as SQLiteQueryFaster; #[cfg(feature = "xml")] pub use query::xml::XMLQuery; pub use query::Query; diff --git a/src/model/account.rs b/src/model/account.rs index 7fe01ee..6fe502a 100644 --- a/src/model/account.rs +++ b/src/model/account.rs @@ -169,211 +169,15 @@ mod tests { use crate::query::sqlite::account::Account as AccountBase; use crate::SQLiteQuery; - async fn setup() -> SQLiteQuery { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - } - - #[tokio::test] - async fn test_from_with_query() { - let query = Arc::new(setup().await); - let item = AccountBase { - guid: "guid".to_string(), - name: "name".to_string(), - account_type: "account_type".to_string(), - commodity_guid: Some("commodity_guid".to_string()), - commodity_scu: 100, - non_std_scu: 0, - parent_guid: Some("parent_guid".to_string()), - code: Some("code".to_string()), - description: Some("description".to_string()), - hidden: Some(0), - placeholder: Some(1), - }; - - let result = Account::from_with_query(&item, query); - - assert_eq!(result.guid, "guid"); - assert_eq!(result.name, "name"); - assert_eq!(result.r#type, "account_type"); - assert_eq!(result.commodity_guid, "commodity_guid"); - assert_eq!(result.commodity_scu, 100); - assert_eq!(result.non_std_scu, false); - assert_eq!(result.parent_guid, "parent_guid"); - assert_eq!(result.code, "code"); - assert_eq!(result.description, "description"); - assert_eq!(result.hidden, false); - assert_eq!(result.placeholder, true); - } - - #[tokio::test] - async fn test_splits() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Cash") - .await - .unwrap() - .unwrap(); - let splits = account.splits().await.unwrap(); - assert_eq!(splits.len(), 3); - } - - #[tokio::test] - async fn test_parent() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Cash") - .await - .unwrap() - .unwrap(); - let parent = account.parent().await.unwrap().unwrap(); - assert_eq!(parent.name, "Current"); - } - - #[tokio::test] - async fn test_no_parent() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Root Account") - .await - .unwrap() - .unwrap(); - let parent = account.parent().await.unwrap(); - dbg!(&parent); - assert!(parent.is_none()); - } - - #[tokio::test] - async fn test_children() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Current") - .await - .unwrap() - .unwrap(); - let children = account.children().await.unwrap(); - assert_eq!(children.len(), 3); - } - - #[tokio::test] - async fn test_children2() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Asset") - .await - .unwrap() - .unwrap(); - let children = account.children().await.unwrap(); - - assert_eq!(children.len(), 3); - assert!( - children - .iter() - .map(|x| &x.name) - .any(|name| name == "Current"), - "children does not contains Current" - ); - assert!( - children.iter().map(|x| &x.name).any(|name| name == "Fixed"), - "children does not contains Fixed" - ); - assert!( - children - .iter() - .map(|x| &x.name) - .any(|name| name == "Broker"), - "children does not contains Broker" - ); - } - - #[tokio::test] - async fn test_commodity() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Cash") - .await - .unwrap() - .unwrap(); - let commodity = account.commodity().await.unwrap(); - assert_eq!(commodity.mnemonic, "EUR"); - } - - #[tokio::test] - async fn test_balance_into_currency() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Asset") - .await - .unwrap() - .unwrap(); - let commodity = account.commodity().await.unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!( - f64, - account - .balance_into_currency(&commodity, &book) - .await - .unwrap(), - 24695.3 - ); - #[cfg(feature = "decimal")] - assert_eq!( - account - .balance_into_currency(&commodity, &book) - .await - .unwrap(), - Decimal::new(246_953, 1) - ); - } - - #[tokio::test] - async fn test_balance() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Current") - .await - .unwrap() - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, account.balance(&book).await.unwrap(), 4590.0); - #[cfg(feature = "decimal")] - assert_eq!(account.balance(&book).await.unwrap(), Decimal::new(4590, 0)); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use pretty_assertions::assert_eq; - - use crate::query::sqlitefaster::account::Account as AccountBase; - use crate::SQLiteQueryFaster; - #[allow(clippy::unused_async)] - async fn setup() -> SQLiteQueryFaster { + async fn setup() -> SQLiteQuery { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() } #[tokio::test] diff --git a/src/model/commodity.rs b/src/model/commodity.rs index fc5cc82..ddf2b2a 100644 --- a/src/model/commodity.rs +++ b/src/model/commodity.rs @@ -111,232 +111,15 @@ mod tests { use crate::query::sqlite::commodity::Commodity as CommodityBase; use crate::SQLiteQuery; - async fn setup() -> SQLiteQuery { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - } - - #[tokio::test] - async fn test_from_with_query() { - let query = Arc::new(setup().await); - let item = CommodityBase { - guid: "guid".to_string(), - namespace: "namespace".to_string(), - mnemonic: "mnemonic".to_string(), - fullname: Some("fullname".to_string()), - cusip: Some("cusip".to_string()), - fraction: 100, - quote_flag: 1, - quote_source: Some("quote_source".to_string()), - quote_tz: Some("quote_tz".to_string()), - }; - - let result = Commodity::from_with_query(&item, query); - - assert_eq!(result.guid, "guid"); - assert_eq!(result.namespace, "namespace"); - assert_eq!(result.mnemonic, "mnemonic"); - assert_eq!(result.fullname, "fullname"); - assert_eq!(result.cusip, "cusip"); - assert_eq!(result.fraction, 100); - assert_eq!(result.quote_flag, true); - assert_eq!(result.quote_source, "quote_source"); - assert_eq!(result.quote_tz, "quote_tz"); - } - - #[tokio::test] - async fn test_accounts() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let accounts = commodity.accounts().await.unwrap(); - assert_eq!(accounts.len(), 14); - } - - #[tokio::test] - async fn test_transactions() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let transactions = commodity.transactions().await.unwrap(); - assert_eq!(transactions.len(), 11); - } - - #[tokio::test] - async fn test_as_commodity_prices() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let prices = commodity.as_commodity_prices().await.unwrap(); - assert_eq!(prices.len(), 1); - } - - #[tokio::test] - async fn test_as_currency_prices() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let prices = commodity.as_currency_prices().await.unwrap(); - assert_eq!(prices.len(), 2); - } - - #[tokio::test] - async fn test_as_commodity_or_currency_prices() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let prices = commodity.as_commodity_or_currency_prices().await.unwrap(); - assert_eq!(prices.len(), 3); - } - - #[tokio::test] - async fn test_rate_direct() { - // ADF => AED - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "d821d6776fde9f7c2d01b67876406fd3") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - - let rate = commodity.sell(¤cy, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 1.5); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(15, 1)); - - let rate = currency.buy(&commodity, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 1.5); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(15, 1)); - - // AED => EUR - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - - let rate = commodity.sell(¤cy, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 9.0 / 10.0); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(9, 0) / Decimal::new(10, 0)); - - let rate = currency.buy(&commodity, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 9.0 / 10.0); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(9, 0) / Decimal::new(10, 0)); - } - - #[tokio::test] - async fn test_rate_indirect() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - // USD => AED - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "1e5d65e2726a5d4595741cb204992991") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - - let rate = commodity.sell(¤cy, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 7.0 / 5.0 * 10.0 / 9.0); - #[cfg(feature = "decimal")] - assert_eq!( - rate, - (Decimal::new(7, 0) / Decimal::new(5, 0)) - * (Decimal::new(10, 0) / Decimal::new(9, 0)), - ); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use pretty_assertions::assert_eq; - - use crate::query::sqlitefaster::commodity::Commodity as CommodityBase; - use crate::SQLiteQueryFaster; - #[allow(clippy::unused_async)] - async fn setup() -> SQLiteQueryFaster { + async fn setup() -> SQLiteQuery { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() } #[tokio::test] diff --git a/src/model/price.rs b/src/model/price.rs index 35537b1..043af82 100644 --- a/src/model/price.rs +++ b/src/model/price.rs @@ -110,97 +110,15 @@ mod tests { use crate::query::sqlite::price::Price as PriceBase; use crate::SQLiteQuery; - async fn setup() -> SQLiteQuery { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - } - - #[tokio::test] - async fn test_from_with_query() { - let query = Arc::new(setup().await); - let item = PriceBase { - guid: "guid".to_string(), - commodity_guid: "commodity_guid".to_string(), - currency_guid: "currency_guid".to_string(), - date: NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S") - .unwrap(), - source: Some("source".to_string()), - r#type: Some("type".to_string()), - value_num: 1000, - value_denom: 10, - }; - - let result = Price::from_with_query(&item, query); - - assert_eq!(result.guid, "guid"); - assert_eq!(result.commodity_guid, "commodity_guid"); - assert_eq!(result.currency_guid, "currency_guid"); - assert_eq!( - result.datetime, - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!(result.source, "source"); - assert_eq!(result.r#type, "type"); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.value, 100.0); - #[cfg(feature = "decimal")] - assert_eq!(result.value, Decimal::new(100, 0)); - } - - #[tokio::test] - async fn commodity() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let price = book - .prices() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "0d6684f44fb018e882de76094ed9c433") - .unwrap(); - let commodity = price.commodity().await.unwrap(); - assert_eq!(commodity.fullname, "Andorran Franc"); - } - - #[tokio::test] - async fn currency() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let price = book - .prices() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "0d6684f44fb018e882de76094ed9c433") - .unwrap(); - let currency = price.currency().await.unwrap(); - assert_eq!(currency.fullname, "UAE Dirham"); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use pretty_assertions::assert_eq; - - use crate::query::sqlitefaster::price::Price as PriceBase; - use crate::SQLiteQueryFaster; - #[allow(clippy::unused_async)] - async fn setup() -> SQLiteQueryFaster { + async fn setup() -> SQLiteQuery { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() } #[tokio::test] diff --git a/src/model/split.rs b/src/model/split.rs index 9aac73a..9b121c3 100644 --- a/src/model/split.rs +++ b/src/model/split.rs @@ -114,111 +114,15 @@ mod tests { use crate::query::sqlite::split::Split as SplitBase; use crate::SQLiteQuery; - async fn setup() -> SQLiteQuery { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - } - - #[tokio::test] - async fn test_from_with_query() { - let query = Arc::new(setup().await); - let item = SplitBase { - guid: "guid".to_string(), - tx_guid: "tx_guid".to_string(), - account_guid: "account_guid".to_string(), - memo: "memo".to_string(), - action: "action".to_string(), - reconcile_state: "n".to_string(), - reconcile_date: NaiveDateTime::parse_from_str( - "2014-12-24 10:59:00", - "%Y-%m-%d %H:%M:%S", - ) - .ok(), - lot_guid: Some("lot_guid".to_string()), - - value_num: 1000, - value_denom: 10, - quantity_num: 1100, - quantity_denom: 10, - }; - - let result = Split::from_with_query(&item, query); - - assert_eq!(result.guid, "guid"); - assert_eq!(result.tx_guid, "tx_guid"); - assert_eq!(result.account_guid, "account_guid"); - assert_eq!(result.memo, "memo"); - assert_eq!(result.action, "action"); - assert_eq!(result.reconcile_state, false); - assert_eq!( - result.reconcile_datetime, - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").ok() - ); - assert_eq!(result.lot_guid, "lot_guid"); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.value, 100.0); - #[cfg(feature = "decimal")] - assert_eq!(result.value, Decimal::new(100, 0)); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.quantity, 110.0); - #[cfg(feature = "decimal")] - assert_eq!(result.quantity, Decimal::new(110, 0)); - } - - #[tokio::test] - async fn transaction() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let split = book - .splits() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "de832fe97e37811a7fff7e28b3a43425") - .unwrap(); - let transaction = split.transaction().await.unwrap(); - assert_eq!(transaction.description, "income 1"); - } - - #[tokio::test] - async fn account() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let split = book - .splits() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "de832fe97e37811a7fff7e28b3a43425") - .unwrap(); - let account = split.account().await.unwrap(); - assert_eq!(account.name, "Cash"); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use pretty_assertions::assert_eq; - - use crate::query::sqlitefaster::split::Split as SplitBase; - use crate::SQLiteQueryFaster; - #[allow(clippy::unused_async)] - async fn setup() -> SQLiteQueryFaster { + async fn setup() -> SQLiteQuery { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() } #[tokio::test] diff --git a/src/model/transaction.rs b/src/model/transaction.rs index 58406e1..5059c35 100644 --- a/src/model/transaction.rs +++ b/src/model/transaction.rs @@ -86,101 +86,15 @@ mod tests { use crate::query::sqlite::transaction::Transaction as TransactionBase; use crate::SQLiteQuery; - async fn setup() -> SQLiteQuery { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - } - - #[tokio::test] - async fn test_from_with_query() { - let query = Arc::new(setup().await); - let item = TransactionBase { - guid: "guid".to_string(), - currency_guid: "currency_guid".to_string(), - num: "currency_guid".to_string(), - post_date: NaiveDateTime::parse_from_str( - "2014-12-24 10:59:00", - "%Y-%m-%d %H:%M:%S", - ) - .ok(), - enter_date: NaiveDateTime::parse_from_str( - "2014-12-24 10:59:00", - "%Y-%m-%d %H:%M:%S", - ) - .ok(), - description: Some("source".to_string()), - }; - - let result = Transaction::from_with_query(&item, query); - - assert_eq!(result.guid, "guid"); - assert_eq!(result.currency_guid, "currency_guid"); - assert_eq!(result.num, "currency_guid"); - assert_eq!( - result.post_datetime, - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!( - result.enter_datetime, - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!(result.description, "source"); - } - - #[tokio::test] - async fn currency() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let transaction = book - .transactions() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "6c8876003c4a6026e38e3afb67d6f2b1") - .unwrap(); - let currency = transaction.currency().await.unwrap(); - assert_eq!(currency.fullname, "Euro"); - } - - #[tokio::test] - async fn splits() { - let query = setup().await; - let book = Book::new(query).await.unwrap(); - let transaction = book - .transactions() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "6c8876003c4a6026e38e3afb67d6f2b1") - .unwrap(); - let splits = transaction.splits().await.unwrap(); - assert_eq!(splits.len(), 2); - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use pretty_assertions::assert_eq; - - use crate::query::sqlitefaster::transaction::Transaction as TransactionBase; - use crate::SQLiteQueryFaster; - #[allow(clippy::unused_async)] - async fn setup() -> SQLiteQueryFaster { + async fn setup() -> SQLiteQuery { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() } #[tokio::test] diff --git a/src/query.rs b/src/query.rs index 1e18922..9c53b09 100644 --- a/src/query.rs +++ b/src/query.rs @@ -4,8 +4,6 @@ pub(crate) mod mysql; pub(crate) mod postgresql; #[cfg(feature = "sqlite")] pub(crate) mod sqlite; -#[cfg(feature = "sqlitefaster")] -pub(crate) mod sqlitefaster; #[cfg(feature = "xml")] pub(crate) mod xml; @@ -210,439 +208,6 @@ mod tests { static Q: OnceCell = OnceCell::const_new(); async fn setup() -> &'static SQLiteQuery { - Q.get_or_init(|| async { - let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() - }) - .await - } - - mod query { - use super::*; - - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn test_accounts() { - let query = setup().await; - let result = query.accounts().await.unwrap(); - assert_eq!(result.len(), 21); - } - - #[tokio::test] - async fn test_accounts_contains_name() { - let query = setup().await; - let result = query - .accounts_contains_name_ignore_case("aS") - .await - .unwrap(); - assert_eq!(result.len(), 3); - } - - #[tokio::test] - async fn test_splits() { - let query = setup().await; - let result = query.splits().await.unwrap(); - assert_eq!(result.len(), 25); - } - - #[tokio::test] - async fn test_transactions() { - let query = setup().await; - let result = query.transactions().await.unwrap(); - assert_eq!(result.len(), 11); - } - - #[tokio::test] - async fn test_prices() { - let query = setup().await; - let result = query.prices().await.unwrap(); - assert_eq!(result.len(), 5); - } - - #[tokio::test] - async fn test_commodities() { - let query = setup().await; - let result = query.commodities().await.unwrap(); - assert_eq!(result.len(), 5); - } - - #[tokio::test] - async fn test_currencies() { - let query = setup().await; - let result = query.currencies().await.unwrap(); - assert_eq!(result.len(), 4); - } - } - mod account_q { - use super::*; - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = AccountQ::all(query).await.unwrap(); - assert_eq!(result.len(), 21); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = AccountQ::guid(query, "fcd795021c976ba75621ec39e75f6214") - .await - .unwrap(); - assert_eq!(result[0].guid, "fcd795021c976ba75621ec39e75f6214"); - } - - #[tokio::test] - async fn test_commodity_guid() { - let query = setup().await; - let result = AccountQ::commodity_guid(query, "346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - assert!( - result - .iter() - .map(|x| &x.guid) - .any(|guid| guid == "fcd795021c976ba75621ec39e75f6214"), - "result does not contains fcd795021c976ba75621ec39e75f6214" - ); - } - - #[tokio::test] - async fn test_parent_guid() { - let query = setup().await; - let result = AccountQ::parent_guid(query, "fcd795021c976ba75621ec39e75f6214") - .await - .unwrap(); - assert_eq!(result[0].guid, "3bc319753945b6dba3e1928abed49e35"); - } - - #[tokio::test] - async fn test_name() { - let query = setup().await; - let result = AccountQ::name(query, "Asset").await.unwrap(); - assert_eq!(result[0].guid, "fcd795021c976ba75621ec39e75f6214"); - } - - #[tokio::test] - async fn test_contains_name_ignore_case() { - let query = setup().await; - let result = AccountQ::contains_name_ignore_case(query, "aS") - .await - .unwrap(); - assert_eq!(result.len(), 3); - } - } - mod commodity_q { - use super::*; - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = CommodityQ::all(query).await.unwrap(); - assert_eq!(result.len(), 5); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = CommodityQ::guid(query, "346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - assert_eq!(result[0].fullname.as_ref().unwrap(), "Euro"); - } - - #[tokio::test] - async fn test_namespace() { - let query = setup().await; - let result = CommodityQ::namespace(query, "CURRENCY").await.unwrap(); - assert_eq!(result.len(), 4); - } - } - mod price_q { - use super::*; - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = PriceQ::all(query).await.unwrap(); - assert_eq!(result.len(), 5); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = PriceQ::guid(query, "0d6684f44fb018e882de76094ed9c433") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn commodity_guid() { - let query = setup().await; - let result = PriceQ::commodity_guid(query, "d821d6776fde9f7c2d01b67876406fd3") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn currency_guid() { - let query = setup().await; - let result = PriceQ::currency_guid(query, "5f586908098232e67edb1371408bfaa8") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn commodity_or_currency_guid() { - let query = setup().await; - let result = - PriceQ::commodity_or_currency_guid(query, "5f586908098232e67edb1371408bfaa8") - .await - .unwrap(); - assert_eq!(result.len(), 4); - } - } - mod split_q { - use super::*; - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = SplitQ::all(query).await.unwrap(); - assert_eq!(result.len(), 25); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = SplitQ::guid(query, "de832fe97e37811a7fff7e28b3a43425") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 150.0); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(150, 0)); - } - - #[tokio::test] - async fn test_account_guid() { - let query = setup().await; - let result = SplitQ::account_guid(query, "93fc043c3062aaa1297b30e543d2cd0d") - .await - .unwrap(); - assert_eq!(result.len(), 3); - } - - #[tokio::test] - async fn test_tx_guid() { - let query = setup().await; - let result = SplitQ::tx_guid(query, "6c8876003c4a6026e38e3afb67d6f2b1") - .await - .unwrap(); - assert_eq!(result.len(), 2); - } - } - mod transaction_q { - use super::*; - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = TransactionQ::all(query).await.unwrap(); - assert_eq!(result.len(), 11); - } - - #[tokio::test] - async fn test_by_guid() { - let query = setup().await; - let result = TransactionQ::guid(query, "6c8876003c4a6026e38e3afb67d6f2b1") - .await - .unwrap(); - - assert_eq!( - result[0].post_date.unwrap(), - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S") - .unwrap() - ); - - assert_eq!( - result[0].enter_date.unwrap(), - NaiveDateTime::parse_from_str("2014-12-25 10:08:15", "%Y-%m-%d %H:%M:%S") - .unwrap() - ); - } - - #[tokio::test] - async fn test_currency_guid() { - let query = setup().await; - let result = TransactionQ::currency_guid(query, "346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - - assert_eq!(result.len(), 11); - } - } - mod account_t { - use super::*; - - #[tokio::test] - async fn test_trait_fn() { - let query = setup().await; - let result = AccountQ::guid(query, "fcd795021c976ba75621ec39e75f6214") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "fcd795021c976ba75621ec39e75f6214"); - assert_eq!(result.name(), "Asset"); - assert_eq!(result.account_type(), "ASSET"); - assert_eq!(result.commodity_guid(), "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(result.commodity_scu(), 100); - assert!(!result.non_std_scu()); - assert_eq!(result.parent_guid(), "00622dda21937b29e494179de5013f82"); - assert_eq!(result.code(), ""); - assert_eq!(result.description(), ""); - assert!(!result.hidden()); - assert!(result.placeholder()); - } - } - mod commodity_t { - use super::*; - - #[tokio::test] - async fn test_trait_fn() { - let query = setup().await; - let result = CommodityQ::guid(query, "346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(result.namespace(), "CURRENCY"); - assert_eq!(result.mnemonic(), "EUR"); - assert_eq!(result.fullname(), "Euro"); - assert_eq!(result.cusip(), "978"); - assert_eq!(result.fraction(), 100); - assert!(result.quote_flag()); - assert_eq!(result.quote_source(), "currency"); - assert_eq!(result.quote_tz(), ""); - } - } - mod price_t { - use super::*; - - #[tokio::test] - async fn test_trait_fn() { - let query = setup().await; - let result = PriceQ::guid(query, "0d6684f44fb018e882de76094ed9c433") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "0d6684f44fb018e882de76094ed9c433"); - assert_eq!(result.commodity_guid(), "d821d6776fde9f7c2d01b67876406fd3"); - assert_eq!(result.currency_guid(), "5f586908098232e67edb1371408bfaa8"); - assert_eq!( - result.datetime(), - NaiveDateTime::parse_from_str("2018-02-20 23:00:00", "%Y-%m-%d %H:%M:%S") - .unwrap() - ); - assert_eq!(result.source(), "user:price-editor"); - assert_eq!(result.r#type(), "unknown"); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result.value(), Decimal::new(15, 1)); - } - } - mod split_t { - use super::*; - - #[tokio::test] - async fn test_trait_fn() { - let query = setup().await; - let result = SplitQ::guid(query, "de832fe97e37811a7fff7e28b3a43425") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "de832fe97e37811a7fff7e28b3a43425"); - assert_eq!(result.tx_guid(), "6c8876003c4a6026e38e3afb67d6f2b1"); - assert_eq!(result.account_guid(), "93fc043c3062aaa1297b30e543d2cd0d"); - assert_eq!(result.memo(), ""); - assert_eq!(result.action(), ""); - assert!(!result.reconcile_state()); - assert_eq!(result.reconcile_datetime(), None); - assert_eq!(result.lot_guid(), ""); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.value(), 150.0); - #[cfg(feature = "decimal")] - assert_eq!(result.value(), Decimal::new(150, 0)); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.quantity(), 150.0); - #[cfg(feature = "decimal")] - assert_eq!(result.quantity(), Decimal::new(150, 0)); - } - } - mod transaction_t { - use super::*; - - #[tokio::test] - async fn test_trait_fn() { - let query = setup().await; - let result = TransactionQ::guid(query, "6c8876003c4a6026e38e3afb67d6f2b1") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "6c8876003c4a6026e38e3afb67d6f2b1"); - assert_eq!(result.currency_guid(), "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(result.num(), ""); - assert_eq!( - result.post_datetime(), - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S") - .unwrap() - ); - assert_eq!( - result.enter_datetime(), - NaiveDateTime::parse_from_str("2014-12-25 10:08:15", "%Y-%m-%d %H:%M:%S") - .unwrap() - ); - assert_eq!(result.description(), "income 1"); - } - } - } - - #[cfg(feature = "sqlitefaster")] - mod sqlitefaster { - use super::*; - - use crate::SQLiteQueryFaster; - - static Q: OnceCell = OnceCell::const_new(); - async fn setup() -> &'static SQLiteQueryFaster { Q.get_or_init(|| async { let uri: &str = &format!( "{}/tests/db/sqlite/complex_sample.gnucash", @@ -650,7 +215,7 @@ mod tests { ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQueryFaster::new(uri).unwrap() + SQLiteQuery::new(uri).unwrap() }) .await } diff --git a/src/query/mysql/account.rs b/src/query/mysql/account.rs index 18d4a57..53ba848 100644 --- a/src/query/mysql/account.rs +++ b/src/query/mysql/account.rs @@ -133,13 +133,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> MySQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_account_schemas(&self) { - let _ = sqlx::query_as!( - Account, - r" + // test schemas on compile time + #[allow(dead_code)] + fn test_account_schemas() { + let _ = sqlx::query_as!( + Account, + r" SELECT guid, name, @@ -154,10 +153,7 @@ mod tests { placeholder FROM accounts " - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/mysql/commodity.rs b/src/query/mysql/commodity.rs index 544d909..8d56567 100644 --- a/src/query/mysql/commodity.rs +++ b/src/query/mysql/commodity.rs @@ -97,13 +97,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> MySQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_commodity_schemas(&self) { - let _ = sqlx::query_as!( - Commodity, - r" + // test schemas on compile time + #[allow(dead_code)] + fn test_commodity_schemas() { + let _ = sqlx::query_as!( + Commodity, + r" SELECT guid, namespace, @@ -116,10 +115,7 @@ mod tests { quote_tz FROM commodities ", - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/mysql/price.rs b/src/query/mysql/price.rs index 9405b98..88ce3f6 100644 --- a/src/query/mysql/price.rs +++ b/src/query/mysql/price.rs @@ -120,13 +120,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> MySQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_price_schemas(&self) { - let _ = sqlx::query_as!( - Price, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_price_schemas() { + let _ = sqlx::query_as!( + Price, + r#" SELECT guid, commodity_guid, @@ -138,10 +137,7 @@ mod tests { value_denom FROM prices "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/mysql/split.rs b/src/query/mysql/split.rs index 03e71a7..d90cba6 100644 --- a/src/query/mysql/split.rs +++ b/src/query/mysql/split.rs @@ -145,13 +145,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> MySQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_split_schemas(&self) { - let _ = sqlx::query_as!( - Split, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_split_schemas() { + let _ = sqlx::query_as!( + Split, + r#" SELECT guid, tx_guid, @@ -167,10 +166,7 @@ mod tests { lot_guid FROM splits "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/mysql/transaction.rs b/src/query/mysql/transaction.rs index b351295..f420448 100644 --- a/src/query/mysql/transaction.rs +++ b/src/query/mysql/transaction.rs @@ -85,13 +85,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> MySQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_transaction_schemas(&self) { - let _ = sqlx::query_as!( - Transaction, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_transaction_schemas() { + let _ = sqlx::query_as!( + Transaction, + r#" SELECT guid, currency_guid, @@ -101,10 +100,7 @@ mod tests { description FROM transactions "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/postgresql/account.rs b/src/query/postgresql/account.rs index da4911f..ec065a9 100644 --- a/src/query/postgresql/account.rs +++ b/src/query/postgresql/account.rs @@ -133,13 +133,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> PostgreSQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_account_schemas(&self) { - let _ = sqlx::query_as!( - Account, - r" + // test schemas on compile time + #[allow(dead_code)] + fn test_account_schemas() { + let _ = sqlx::query_as!( + Account, + r" SELECT guid, name, @@ -154,10 +153,7 @@ mod tests { placeholder FROM accounts " - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/postgresql/commodity.rs b/src/query/postgresql/commodity.rs index cc12751..f84c7dc 100644 --- a/src/query/postgresql/commodity.rs +++ b/src/query/postgresql/commodity.rs @@ -97,13 +97,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> PostgreSQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_commodity_schemas(&self) { - let _ = sqlx::query_as!( - Commodity, - r" + // test schemas on compile time + #[allow(dead_code)] + fn test_commodity_schemas() { + let _ = sqlx::query_as!( + Commodity, + r" SELECT guid, namespace, @@ -116,10 +115,7 @@ mod tests { quote_tz FROM commodities ", - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/postgresql/price.rs b/src/query/postgresql/price.rs index a680254..cfa49d0 100644 --- a/src/query/postgresql/price.rs +++ b/src/query/postgresql/price.rs @@ -120,13 +120,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> PostgreSQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_price_schemas(&self) { - let _ = sqlx::query_as!( - Price, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_price_schemas() { + let _ = sqlx::query_as!( + Price, + r#" SELECT guid, commodity_guid, @@ -138,10 +137,7 @@ mod tests { value_denom FROM prices "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/postgresql/split.rs b/src/query/postgresql/split.rs index 8835883..9039c74 100644 --- a/src/query/postgresql/split.rs +++ b/src/query/postgresql/split.rs @@ -145,13 +145,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> PostgreSQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_split_schemas(&self) { - let _ = sqlx::query_as!( - Split, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_split_schemas() { + let _ = sqlx::query_as!( + Split, + r#" SELECT guid, tx_guid, @@ -167,10 +166,7 @@ mod tests { lot_guid FROM splits "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/postgresql/transaction.rs b/src/query/postgresql/transaction.rs index 64ae597..216ea0a 100644 --- a/src/query/postgresql/transaction.rs +++ b/src/query/postgresql/transaction.rs @@ -85,13 +85,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> PostgreSQLQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_transaction_schemas(&self) { - let _ = sqlx::query_as!( - Transaction, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_transaction_schemas() { + let _ = sqlx::query_as!( + Transaction, + r#" SELECT guid, currency_guid, @@ -101,10 +100,7 @@ mod tests { description FROM transactions "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); diff --git a/src/query/sqlite.rs b/src/query/sqlite.rs index 7b662a7..7f9ba13 100644 --- a/src/query/sqlite.rs +++ b/src/query/sqlite.rs @@ -4,14 +4,15 @@ pub(crate) mod price; pub(crate) mod split; pub(crate) mod transaction; +use rusqlite::{Connection, OpenFlags}; +use std::sync::{Arc, Mutex}; + use super::Query; use crate::error::Error; -const MAX_CONNECTIONS: u32 = 10; - #[derive(Debug, Clone)] pub struct SQLiteQuery { - pool: sqlx::SqlitePool, + conn: Arc>, } impl SQLiteQuery { @@ -20,18 +21,19 @@ impl SQLiteQuery { /// /// | URI | Description | /// | -- | -- | - /// `sqlite::memory:` | Open an in-memory database. | - /// `sqlite:data.db` | Open the file `data.db` in the current directory. | - /// `sqlite://data.db` | Open the file `data.db` in the current directory. | - /// `sqlite:///data.db` | Open the file `data.db` from the root (`/`) directory. | - /// `sqlite://data.db?mode=ro` | Open the file `data.db` for read-only access. | - pub async fn new(uri: &str) -> Result { - let pool = sqlx::sqlite::SqlitePoolOptions::new() - .max_connections(MAX_CONNECTIONS) - .connect(uri) - .await?; - - Ok(Self { pool }) + /// `file::memory:` | Open an in-memory database. | + /// `path-to-db/data.db` | Open the file `data.db` | + /// `file:/path-to-db/data.db` | Open the file `data.db` | + pub fn new(uri: &str) -> Result { + let conn = Connection::open_with_flags( + uri, + OpenFlags::SQLITE_OPEN_READ_ONLY + | OpenFlags::SQLITE_OPEN_URI + | OpenFlags::SQLITE_OPEN_NO_MUTEX, + )?; + let conn = Arc::new(Mutex::new(conn)); + + Ok(Self { conn }) } } @@ -41,14 +43,14 @@ impl Query for SQLiteQuery {} mod tests { use super::*; - #[tokio::test] - async fn test_new() { + #[test] + fn test_new() { let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap(); + SQLiteQuery::new(uri).unwrap(); } } diff --git a/src/query/sqlite/account.rs b/src/query/sqlite/account.rs index 8a8acf4..cbde242 100644 --- a/src/query/sqlite/account.rs +++ b/src/query/sqlite/account.rs @@ -1,12 +1,14 @@ // ref: https://piecash.readthedocs.io/en/master/object_model.html // ref: https://wiki.gnucash.org/wiki/SQL +use rusqlite::Row; + +use super::SQLiteQuery; use crate::error::Error; -use crate::query::sqlite::SQLiteQuery; use crate::query::{AccountQ, AccountT}; #[allow(clippy::struct_field_names)] -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, sqlx::FromRow)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] pub struct Account { pub(crate) guid: String, pub(crate) name: String, @@ -21,6 +23,26 @@ pub struct Account { pub(crate) placeholder: Option, } +impl<'a> TryFrom<&'a Row<'a>> for Account { + type Error = rusqlite::Error; + + fn try_from(row: &'a Row<'a>) -> Result { + Ok(Self { + guid: row.get(0)?, + name: row.get(1)?, + account_type: row.get(2)?, + commodity_guid: row.get(3)?, + commodity_scu: row.get(4)?, + non_std_scu: row.get(5)?, + parent_guid: row.get(6)?, + code: row.get(7)?, + description: row.get(8)?, + hidden: row.get(9)?, + placeholder: row.get(10)?, + }) + } +} + impl AccountT for Account { fn guid(&self) -> String { self.guid.clone() @@ -58,8 +80,8 @@ impl AccountT for Account { } const SEL: &str = r" -SELECT -guid, +SELECT +guid, name, account_type, commodity_guid, @@ -77,51 +99,65 @@ impl AccountQ for SQLiteQuery { type A = Account; async fn all(&self) -> Result, Error> { - sqlx::query_as(SEL) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(SEL)?; + let result = stmt + .query([])? + .mapped(|row| Account::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Account::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn commodity_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE commodity_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE commodity_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Account::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn parent_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE parent_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE parent_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Account::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn name(&self, name: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE name = ?")) - .bind(name) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE name = ?"))?; + let result = stmt + .query([name])? + .mapped(|row| Account::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn contains_name_ignore_case(&self, name: &str) -> Result, Error> { let name = format!("%{name}%"); - sqlx::query_as(&format!("{SEL}\nWHERE name LIKE ?")) - .bind(name) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE name LIKE ?"))?; + let result = stmt + .query([name])? + .mapped(|row| Account::try_from(row)) + .collect::, _>>()?; + Ok(result) } } @@ -133,13 +169,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> SQLiteQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_account_schemas(&self) { - let _ = sqlx::query_as!( - Account, - r" + // test schemas on compile time + #[allow(dead_code)] + fn test_account_schemas() { + let _ = sqlx::query_as!( + Account, + r" SELECT guid, name, @@ -154,22 +189,19 @@ mod tests { placeholder FROM accounts " - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); async fn setup() -> &'static SQLiteQuery { Q.get_or_init(|| async { let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() + SQLiteQuery::new(uri).unwrap() }) .await } diff --git a/src/query/sqlite/commodity.rs b/src/query/sqlite/commodity.rs index 313c997..0b067bf 100644 --- a/src/query/sqlite/commodity.rs +++ b/src/query/sqlite/commodity.rs @@ -1,11 +1,12 @@ // ref: https://piecash.readthedocs.io/en/master/object_model.html // ref: https://wiki.gnucash.org/wiki/SQL +use rusqlite::Row; +use super::SQLiteQuery; use crate::error::Error; -use crate::query::sqlite::SQLiteQuery; use crate::query::{CommodityQ, CommodityT}; -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, sqlx::FromRow)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] pub struct Commodity { pub(crate) guid: String, pub(crate) namespace: String, @@ -18,6 +19,24 @@ pub struct Commodity { pub(crate) quote_tz: Option, } +impl<'a> TryFrom<&'a Row<'a>> for Commodity { + type Error = rusqlite::Error; + + fn try_from(row: &'a Row<'a>) -> Result { + Ok(Self { + guid: row.get(0)?, + namespace: row.get(1)?, + mnemonic: row.get(2)?, + fullname: row.get(3)?, + cusip: row.get(4)?, + fraction: row.get(5)?, + quote_flag: row.get(6)?, + quote_source: row.get(7)?, + quote_tz: row.get(8)?, + }) + } +} + impl CommodityT for Commodity { fn guid(&self) -> String { self.guid.clone() @@ -66,26 +85,33 @@ impl CommodityQ for SQLiteQuery { type C = Commodity; async fn all(&self) -> Result, Error> { - sqlx::query_as(SEL) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(SEL)?; + let result = stmt + .query([])? + .mapped(|row| Commodity::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Commodity::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn namespace(&self, namespace: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE namespace = ?")) - .bind(namespace) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE namespace = ?"))?; + let result = stmt + .query([namespace])? + .mapped(|row| Commodity::try_from(row)) + .collect::, _>>()?; + Ok(result) } } @@ -97,13 +123,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> SQLiteQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_commodity_schemas(&self) { - let _ = sqlx::query_as!( - Commodity, - r" + // test schemas on compile time + #[allow(dead_code)] + fn test_commodity_schemas() { + let _ = sqlx::query_as!( + Commodity, + r" SELECT guid, namespace, @@ -116,22 +141,19 @@ mod tests { quote_tz FROM commodities ", - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); async fn setup() -> &'static SQLiteQuery { Q.get_or_init(|| async { let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() + SQLiteQuery::new(uri).unwrap() }) .await } diff --git a/src/query/sqlite/price.rs b/src/query/sqlite/price.rs index d0800f3..fb7d451 100644 --- a/src/query/sqlite/price.rs +++ b/src/query/sqlite/price.rs @@ -2,14 +2,15 @@ // ref: https://wiki.gnucash.org/wiki/SQL use chrono::NaiveDateTime; +use rusqlite::Row; #[cfg(feature = "decimal")] use rust_decimal::Decimal; +use super::SQLiteQuery; use crate::error::Error; -use crate::query::sqlite::SQLiteQuery; use crate::query::{PriceQ, PriceT}; -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, sqlx::FromRow)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] pub struct Price { pub guid: String, pub commodity_guid: String, @@ -21,6 +22,23 @@ pub struct Price { pub value_denom: i64, } +impl<'a> TryFrom<&'a Row<'a>> for Price { + type Error = rusqlite::Error; + + fn try_from(row: &'a Row<'a>) -> Result { + Ok(Self { + guid: row.get(0)?, + commodity_guid: row.get(1)?, + currency_guid: row.get(2)?, + date: row.get(3)?, + source: row.get(4)?, + r#type: row.get(5)?, + value_num: row.get(6)?, + value_denom: row.get(7)?, + }) + } +} + impl PriceT for Price { fn guid(&self) -> String { self.guid.clone() @@ -72,41 +90,51 @@ impl PriceQ for SQLiteQuery { type P = Price; async fn all(&self) -> Result, Error> { - sqlx::query_as(SEL) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(SEL)?; + let result = stmt + .query([])? + .mapped(|row| Price::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Price::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn commodity_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE commodity_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE commodity_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Price::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn currency_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE currency_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE currency_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Price::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn commodity_or_currency_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!( + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!( "{SEL}\nWHERE commodity_guid = ? OR currency_guid = ?" - )) - .bind(guid) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + ))?; + let result = stmt + .query([guid, guid])? + .mapped(|row| Price::try_from(row)) + .collect::, _>>()?; + Ok(result) } } @@ -120,13 +148,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> SQLiteQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_price_schemas(&self) { - let _ = sqlx::query_as!( - Price, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_price_schemas() { + let _ = sqlx::query_as!( + Price, + r#" SELECT guid, commodity_guid, @@ -138,22 +165,19 @@ mod tests { value_denom FROM prices "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); async fn setup() -> &'static SQLiteQuery { Q.get_or_init(|| async { let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() + SQLiteQuery::new(uri).unwrap() }) .await } diff --git a/src/query/sqlite/split.rs b/src/query/sqlite/split.rs index d0ba946..d8fe01a 100644 --- a/src/query/sqlite/split.rs +++ b/src/query/sqlite/split.rs @@ -2,14 +2,15 @@ // ref: https://wiki.gnucash.org/wiki/SQL use chrono::NaiveDateTime; +use rusqlite::Row; #[cfg(feature = "decimal")] use rust_decimal::Decimal; +use super::SQLiteQuery; use crate::error::Error; -use crate::query::sqlite::SQLiteQuery; use crate::query::{SplitQ, SplitT}; -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, sqlx::FromRow)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] pub struct Split { pub guid: String, pub tx_guid: String, @@ -25,6 +26,27 @@ pub struct Split { pub lot_guid: Option, } +impl<'a> TryFrom<&'a Row<'a>> for Split { + type Error = rusqlite::Error; + + fn try_from(row: &'a Row<'a>) -> Result { + Ok(Self { + guid: row.get(0)?, + tx_guid: row.get(1)?, + account_guid: row.get(2)?, + memo: row.get(3)?, + action: row.get(4)?, + reconcile_state: row.get(5)?, + reconcile_date: row.get(6)?, + value_num: row.get(7)?, + value_denom: row.get(8)?, + quantity_num: row.get(9)?, + quantity_denom: row.get(10)?, + lot_guid: row.get(11)?, + }) + } +} + impl SplitT for Split { fn guid(&self) -> String { self.guid.clone() @@ -83,7 +105,7 @@ impl SplitT for Split { } const SEL: &str = r" -SELECT +SELECT guid, tx_guid, account_guid, @@ -103,34 +125,43 @@ impl SplitQ for SQLiteQuery { type S = Split; async fn all(&self) -> Result, Error> { - sqlx::query_as(SEL) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(SEL)?; + let result = stmt + .query([])? + .mapped(|row| Split::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Split::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn account_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE account_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE account_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Split::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn tx_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE tx_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE tx_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Split::try_from(row)) + .collect::, _>>()?; + Ok(result) } } @@ -145,13 +176,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> SQLiteQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_split_schemas(&self) { - let _ = sqlx::query_as!( - Split, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_split_schemas() { + let _ = sqlx::query_as!( + Split, + r#" SELECT guid, tx_guid, @@ -167,22 +197,19 @@ mod tests { lot_guid FROM splits "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); async fn setup() -> &'static SQLiteQuery { Q.get_or_init(|| async { let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() + SQLiteQuery::new(uri).unwrap() }) .await } diff --git a/src/query/sqlite/transaction.rs b/src/query/sqlite/transaction.rs index 6090b7f..a61cbe4 100644 --- a/src/query/sqlite/transaction.rs +++ b/src/query/sqlite/transaction.rs @@ -2,12 +2,13 @@ // ref: https://wiki.gnucash.org/wiki/SQL use chrono::NaiveDateTime; +use rusqlite::Row; +use super::SQLiteQuery; use crate::error::Error; -use crate::query::sqlite::SQLiteQuery; use crate::query::{TransactionQ, TransactionT}; -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, sqlx::FromRow)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] pub struct Transaction { pub guid: String, pub currency_guid: String, @@ -17,6 +18,21 @@ pub struct Transaction { pub description: Option, } +impl<'a> TryFrom<&'a Row<'a>> for Transaction { + type Error = rusqlite::Error; + + fn try_from(row: &'a Row<'a>) -> Result { + Ok(Self { + guid: row.get(0)?, + currency_guid: row.get(1)?, + num: row.get(2)?, + post_date: row.get(3)?, + enter_date: row.get(4)?, + description: row.get(5)?, + }) + } +} + impl TransactionT for Transaction { fn guid(&self) -> String { self.guid.clone() @@ -54,26 +70,33 @@ impl TransactionQ for SQLiteQuery { type T = Transaction; async fn all(&self) -> Result, Error> { - sqlx::query_as(SEL) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(SEL)?; + let result = stmt + .query([])? + .mapped(|row| Transaction::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Transaction::try_from(row)) + .collect::, _>>()?; + Ok(result) } async fn currency_guid(&self, guid: &str) -> Result, Error> { - sqlx::query_as(&format!("{SEL}\nWHERE currency_guid = ?")) - .bind(guid) - .fetch_all(&self.pool) - .await - .map_err(std::convert::Into::into) + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare(&format!("{SEL}\nWHERE currency_guid = ?"))?; + let result = stmt + .query([guid])? + .mapped(|row| Transaction::try_from(row)) + .collect::, _>>()?; + Ok(result) } } @@ -85,13 +108,12 @@ mod tests { use tokio::sync::OnceCell; #[cfg(feature = "schema")] - impl<'q> SQLiteQuery { - // test schemas on compile time - #[allow(dead_code)] - async fn test_transaction_schemas(&self) { - let _ = sqlx::query_as!( - Transaction, - r#" + // test schemas on compile time + #[allow(dead_code)] + fn test_transaction_schemas() { + let _ = sqlx::query_as!( + Transaction, + r#" SELECT guid, currency_guid, @@ -101,22 +123,19 @@ mod tests { description FROM transactions "#, - ) - .fetch_all(&self.pool) - .await; - } + ); } static Q: OnceCell = OnceCell::const_new(); async fn setup() -> &'static SQLiteQuery { Q.get_or_init(|| async { let uri: &str = &format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ); println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(&format!("{uri}?mode=ro")).await.unwrap() + SQLiteQuery::new(uri).unwrap() }) .await } diff --git a/src/query/sqlitefaster.rs b/src/query/sqlitefaster.rs deleted file mode 100644 index 7f9ba13..0000000 --- a/src/query/sqlitefaster.rs +++ /dev/null @@ -1,56 +0,0 @@ -pub(crate) mod account; -pub(crate) mod commodity; -pub(crate) mod price; -pub(crate) mod split; -pub(crate) mod transaction; - -use rusqlite::{Connection, OpenFlags}; -use std::sync::{Arc, Mutex}; - -use super::Query; -use crate::error::Error; - -#[derive(Debug, Clone)] -pub struct SQLiteQuery { - conn: Arc>, -} - -impl SQLiteQuery { - /// Options and flags which can be used to configure a `SQLite` connection. - /// Described by [`SQLite`](https://www.sqlite.org/uri.html). - /// - /// | URI | Description | - /// | -- | -- | - /// `file::memory:` | Open an in-memory database. | - /// `path-to-db/data.db` | Open the file `data.db` | - /// `file:/path-to-db/data.db` | Open the file `data.db` | - pub fn new(uri: &str) -> Result { - let conn = Connection::open_with_flags( - uri, - OpenFlags::SQLITE_OPEN_READ_ONLY - | OpenFlags::SQLITE_OPEN_URI - | OpenFlags::SQLITE_OPEN_NO_MUTEX, - )?; - let conn = Arc::new(Mutex::new(conn)); - - Ok(Self { conn }) - } -} - -impl Query for SQLiteQuery {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new() { - let uri: &str = &format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(uri).unwrap(); - } -} diff --git a/src/query/sqlitefaster/account.rs b/src/query/sqlitefaster/account.rs deleted file mode 100644 index 7059cd4..0000000 --- a/src/query/sqlitefaster/account.rs +++ /dev/null @@ -1,258 +0,0 @@ -// ref: https://piecash.readthedocs.io/en/master/object_model.html -// ref: https://wiki.gnucash.org/wiki/SQL - -use rusqlite::Row; - -use super::SQLiteQuery; -use crate::error::Error; -use crate::query::{AccountQ, AccountT}; - -#[allow(clippy::struct_field_names)] -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] -pub struct Account { - pub(crate) guid: String, - pub(crate) name: String, - pub(crate) account_type: String, - pub(crate) commodity_guid: Option, - pub(crate) commodity_scu: i64, - pub(crate) non_std_scu: i64, - pub(crate) parent_guid: Option, - pub(crate) code: Option, - pub(crate) description: Option, - pub(crate) hidden: Option, - pub(crate) placeholder: Option, -} - -impl<'a> TryFrom<&'a Row<'a>> for Account { - type Error = rusqlite::Error; - - fn try_from(row: &'a Row<'a>) -> Result { - Ok(Self { - guid: row.get(0)?, - name: row.get(1)?, - account_type: row.get(2)?, - commodity_guid: row.get(3)?, - commodity_scu: row.get(4)?, - non_std_scu: row.get(5)?, - parent_guid: row.get(6)?, - code: row.get(7)?, - description: row.get(8)?, - hidden: row.get(9)?, - placeholder: row.get(10)?, - }) - } -} - -impl AccountT for Account { - fn guid(&self) -> String { - self.guid.clone() - } - fn name(&self) -> String { - self.name.clone() - } - fn account_type(&self) -> String { - self.account_type.clone() - } - fn commodity_guid(&self) -> String { - self.commodity_guid.clone().unwrap_or_default() - } - fn commodity_scu(&self) -> i64 { - self.commodity_scu - } - fn non_std_scu(&self) -> bool { - self.non_std_scu != 0 - } - fn parent_guid(&self) -> String { - self.parent_guid.clone().unwrap_or_default() - } - fn code(&self) -> String { - self.code.clone().unwrap_or_default() - } - fn description(&self) -> String { - self.description.clone().unwrap_or_default() - } - fn hidden(&self) -> bool { - self.hidden.is_some_and(|x| x != 0) - } - fn placeholder(&self) -> bool { - self.placeholder.is_some_and(|x| x != 0) - } -} - -const SEL: &str = r" -SELECT -guid, -name, -account_type, -commodity_guid, -commodity_scu, -non_std_scu, -parent_guid, -code, -description, -hidden, -placeholder -FROM accounts -"; - -impl AccountQ for SQLiteQuery { - type A = Account; - - async fn all(&self) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(SEL)?; - let result = stmt - .query([])? - .mapped(|row| Account::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Account::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn commodity_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE commodity_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Account::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn parent_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE parent_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Account::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn name(&self, name: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE name = ?"))?; - let result = stmt - .query([name])? - .mapped(|row| Account::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn contains_name_ignore_case(&self, name: &str) -> Result, Error> { - let name = format!("%{name}%"); - - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE name LIKE ?"))?; - let result = stmt - .query([name])? - .mapped(|row| Account::try_from(row)) - .collect::, _>>()?; - Ok(result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use pretty_assertions::assert_eq; - use tokio::sync::OnceCell; - - static Q: OnceCell = OnceCell::const_new(); - async fn setup() -> &'static SQLiteQuery { - Q.get_or_init(|| async { - let uri: &str = &format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(uri).unwrap() - }) - .await - } - - #[tokio::test] - async fn test_account() { - let query = setup().await; - let result = query - .guid("fcd795021c976ba75621ec39e75f6214") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "fcd795021c976ba75621ec39e75f6214"); - assert_eq!(result.name(), "Asset"); - assert_eq!(result.account_type(), "ASSET"); - assert_eq!(result.commodity_guid(), "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(result.commodity_scu(), 100); - assert_eq!(result.non_std_scu(), false); - assert_eq!(result.parent_guid(), "00622dda21937b29e494179de5013f82"); - assert_eq!(result.code(), ""); - assert_eq!(result.description(), ""); - assert_eq!(result.hidden(), false); - assert_eq!(result.placeholder(), true); - } - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = query.all().await.unwrap(); - assert_eq!(result.len(), 21); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = query - .guid("fcd795021c976ba75621ec39e75f6214") - .await - .unwrap(); - - assert_eq!(result[0].name, "Asset"); - } - - #[tokio::test] - async fn test_commodity_guid() { - let query = setup().await; - let result = query - .commodity_guid("346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - assert_eq!(result.len(), 14); - } - - #[tokio::test] - async fn test_parent_guid() { - let query = setup().await; - let result = query - .parent_guid("fcd795021c976ba75621ec39e75f6214") - .await - .unwrap(); - assert_eq!(result.len(), 3); - } - - #[tokio::test] - async fn test_name() { - let query = setup().await; - let result = query.name("Asset").await.unwrap(); - assert_eq!(result[0].guid, "fcd795021c976ba75621ec39e75f6214"); - } - - #[tokio::test] - async fn test_contains_name_ignore_case() { - let query = setup().await; - let result = query.contains_name_ignore_case("AS").await.unwrap(); - assert_eq!(result.len(), 3); - } -} diff --git a/src/query/sqlitefaster/commodity.rs b/src/query/sqlitefaster/commodity.rs deleted file mode 100644 index e591c25..0000000 --- a/src/query/sqlitefaster/commodity.rs +++ /dev/null @@ -1,182 +0,0 @@ -// ref: https://piecash.readthedocs.io/en/master/object_model.html -// ref: https://wiki.gnucash.org/wiki/SQL -use rusqlite::Row; - -use super::SQLiteQuery; -use crate::error::Error; -use crate::query::{CommodityQ, CommodityT}; - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] -pub struct Commodity { - pub(crate) guid: String, - pub(crate) namespace: String, - pub(crate) mnemonic: String, - pub(crate) fullname: Option, - pub(crate) cusip: Option, - pub(crate) fraction: i64, - pub(crate) quote_flag: i64, - pub(crate) quote_source: Option, - pub(crate) quote_tz: Option, -} - -impl<'a> TryFrom<&'a Row<'a>> for Commodity { - type Error = rusqlite::Error; - - fn try_from(row: &'a Row<'a>) -> Result { - Ok(Self { - guid: row.get(0)?, - namespace: row.get(1)?, - mnemonic: row.get(2)?, - fullname: row.get(3)?, - cusip: row.get(4)?, - fraction: row.get(5)?, - quote_flag: row.get(6)?, - quote_source: row.get(7)?, - quote_tz: row.get(8)?, - }) - } -} - -impl CommodityT for Commodity { - fn guid(&self) -> String { - self.guid.clone() - } - fn namespace(&self) -> String { - self.namespace.clone() - } - fn mnemonic(&self) -> String { - self.mnemonic.clone() - } - fn fullname(&self) -> String { - self.fullname.clone().unwrap_or_default() - } - fn cusip(&self) -> String { - self.cusip.clone().unwrap_or_default() - } - fn fraction(&self) -> i64 { - self.fraction - } - fn quote_flag(&self) -> bool { - self.quote_flag != 0 - } - fn quote_source(&self) -> String { - self.quote_source.clone().unwrap_or_default() - } - fn quote_tz(&self) -> String { - self.quote_tz.clone().unwrap_or_default() - } -} - -const SEL: &str = r" -SELECT -guid, -namespace, -mnemonic, -fullname, -cusip, -fraction, -quote_flag, -quote_source, -quote_tz -FROM commodities -"; - -impl CommodityQ for SQLiteQuery { - type C = Commodity; - - async fn all(&self) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(SEL)?; - let result = stmt - .query([])? - .mapped(|row| Commodity::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Commodity::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn namespace(&self, namespace: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE namespace = ?"))?; - let result = stmt - .query([namespace])? - .mapped(|row| Commodity::try_from(row)) - .collect::, _>>()?; - Ok(result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use pretty_assertions::assert_eq; - use tokio::sync::OnceCell; - - static Q: OnceCell = OnceCell::const_new(); - async fn setup() -> &'static SQLiteQuery { - Q.get_or_init(|| async { - let uri: &str = &format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(uri).unwrap() - }) - .await - } - - #[tokio::test] - async fn test_commodity() { - let query = setup().await; - let result = query - .guid("346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(result.namespace(), "CURRENCY"); - assert_eq!(result.mnemonic(), "EUR"); - assert_eq!(result.fullname(), "Euro"); - assert_eq!(result.cusip(), "978"); - assert_eq!(result.fraction(), 100); - assert_eq!(result.quote_flag(), true); - assert_eq!(result.quote_source(), "currency"); - assert_eq!(result.quote_tz(), ""); - } - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = query.all().await.unwrap(); - assert_eq!(result.len(), 5); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = query - .guid("346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - assert_eq!(result[0].fullname.as_ref().unwrap(), "Euro"); - } - - #[tokio::test] - async fn test_namespace() { - let query = setup().await; - let result = query.namespace("CURRENCY").await.unwrap(); - assert_eq!(result.len(), 4); - } -} diff --git a/src/query/sqlitefaster/price.rs b/src/query/sqlitefaster/price.rs deleted file mode 100644 index 29a9e04..0000000 --- a/src/query/sqlitefaster/price.rs +++ /dev/null @@ -1,246 +0,0 @@ -// ref: https://piecash.readthedocs.io/en/master/object_model.html -// ref: https://wiki.gnucash.org/wiki/SQL - -use chrono::NaiveDateTime; -use rusqlite::Row; -#[cfg(feature = "decimal")] -use rust_decimal::Decimal; - -use super::SQLiteQuery; -use crate::error::Error; -use crate::query::{PriceQ, PriceT}; - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] -pub struct Price { - pub guid: String, - pub commodity_guid: String, - pub currency_guid: String, - pub date: NaiveDateTime, - pub source: Option, - pub r#type: Option, - pub value_num: i64, - pub value_denom: i64, -} - -impl<'a> TryFrom<&'a Row<'a>> for Price { - type Error = rusqlite::Error; - - fn try_from(row: &'a Row<'a>) -> Result { - Ok(Self { - guid: row.get(0)?, - commodity_guid: row.get(1)?, - currency_guid: row.get(2)?, - date: row.get(3)?, - source: row.get(4)?, - r#type: row.get(5)?, - value_num: row.get(6)?, - value_denom: row.get(7)?, - }) - } -} - -impl PriceT for Price { - fn guid(&self) -> String { - self.guid.clone() - } - fn commodity_guid(&self) -> String { - self.commodity_guid.clone() - } - fn currency_guid(&self) -> String { - self.currency_guid.clone() - } - fn datetime(&self) -> NaiveDateTime { - self.date - } - fn source(&self) -> String { - self.source.clone().unwrap_or_default() - } - fn r#type(&self) -> String { - self.r#type.clone().unwrap_or_default() - } - - #[cfg(not(feature = "decimal"))] - #[must_use] - #[allow(clippy::cast_precision_loss)] - fn value(&self) -> f64 { - self.value_num as f64 / self.value_denom as f64 - } - - #[cfg(feature = "decimal")] - #[must_use] - fn value(&self) -> Decimal { - Decimal::new(self.value_num, 0) / Decimal::new(self.value_denom, 0) - } -} - -const SEL: &str = r" -SELECT -guid, -commodity_guid, -currency_guid, -date, -source, -type, -value_num, -value_denom -FROM prices -"; - -impl PriceQ for SQLiteQuery { - type P = Price; - - async fn all(&self) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(SEL)?; - let result = stmt - .query([])? - .mapped(|row| Price::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - async fn guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Price::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - async fn commodity_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE commodity_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Price::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - async fn currency_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE currency_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Price::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - async fn commodity_or_currency_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!( - "{SEL}\nWHERE commodity_guid = ? OR currency_guid = ?" - ))?; - let result = stmt - .query([guid, guid])? - .mapped(|row| Price::try_from(row)) - .collect::, _>>()?; - Ok(result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(not(feature = "decimal"))] - use float_cmp::assert_approx_eq; - use pretty_assertions::assert_eq; - use tokio::sync::OnceCell; - - static Q: OnceCell = OnceCell::const_new(); - async fn setup() -> &'static SQLiteQuery { - Q.get_or_init(|| async { - let uri: &str = &format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(uri).unwrap() - }) - .await - } - - #[tokio::test] - async fn test_price() { - let query = setup().await; - let result = query - .guid("0d6684f44fb018e882de76094ed9c433") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "0d6684f44fb018e882de76094ed9c433"); - assert_eq!(result.commodity_guid(), "d821d6776fde9f7c2d01b67876406fd3"); - assert_eq!(result.currency_guid(), "5f586908098232e67edb1371408bfaa8"); - assert_eq!( - result.datetime(), - NaiveDateTime::parse_from_str("2018-02-20 23:00:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!(result.source(), "user:price-editor"); - assert_eq!(result.r#type(), "unknown"); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result.value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = query.all().await.unwrap(); - assert_eq!(result.len(), 5); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = query - .guid("0d6684f44fb018e882de76094ed9c433") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn commodity_guid() { - let query = setup().await; - let result = query - .commodity_guid("d821d6776fde9f7c2d01b67876406fd3") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn currency_guid() { - let query = setup().await; - let result = query - .currency_guid("5f586908098232e67edb1371408bfaa8") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 1.5); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(15, 1)); - } - - #[tokio::test] - async fn commodity_or_currency_guid() { - let query = setup().await; - let result = query - .commodity_or_currency_guid("5f586908098232e67edb1371408bfaa8") - .await - .unwrap(); - assert_eq!(result.len(), 4); - } -} diff --git a/src/query/sqlitefaster/split.rs b/src/query/sqlitefaster/split.rs deleted file mode 100644 index 533edee..0000000 --- a/src/query/sqlitefaster/split.rs +++ /dev/null @@ -1,259 +0,0 @@ -// ref: https://piecash.readthedocs.io/en/master/object_model.html -// ref: https://wiki.gnucash.org/wiki/SQL - -use chrono::NaiveDateTime; -use rusqlite::Row; -#[cfg(feature = "decimal")] -use rust_decimal::Decimal; - -use super::SQLiteQuery; -use crate::error::Error; -use crate::query::{SplitQ, SplitT}; - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] -pub struct Split { - pub guid: String, - pub tx_guid: String, - pub account_guid: String, - pub memo: String, - pub action: String, - pub reconcile_state: String, - pub reconcile_date: Option, - pub value_num: i64, - pub value_denom: i64, - pub quantity_num: i64, - pub quantity_denom: i64, - pub lot_guid: Option, -} - -impl<'a> TryFrom<&'a Row<'a>> for Split { - type Error = rusqlite::Error; - - fn try_from(row: &'a Row<'a>) -> Result { - Ok(Self { - guid: row.get(0)?, - tx_guid: row.get(1)?, - account_guid: row.get(2)?, - memo: row.get(3)?, - action: row.get(4)?, - reconcile_state: row.get(5)?, - reconcile_date: row.get(6)?, - value_num: row.get(7)?, - value_denom: row.get(8)?, - quantity_num: row.get(9)?, - quantity_denom: row.get(10)?, - lot_guid: row.get(11)?, - }) - } -} - -impl SplitT for Split { - fn guid(&self) -> String { - self.guid.clone() - } - fn tx_guid(&self) -> String { - self.tx_guid.clone() - } - fn account_guid(&self) -> String { - self.account_guid.clone() - } - fn memo(&self) -> String { - self.memo.clone() - } - fn action(&self) -> String { - self.action.clone() - } - fn reconcile_state(&self) -> bool { - self.reconcile_state == "y" || self.reconcile_state == "Y" - } - fn reconcile_datetime(&self) -> Option { - let datetime = self.reconcile_date?; - if datetime == NaiveDateTime::UNIX_EPOCH { - return None; - } - Some(datetime) - } - fn lot_guid(&self) -> String { - self.lot_guid.clone().unwrap_or_default() - } - - #[cfg(not(feature = "decimal"))] - #[must_use] - #[allow(clippy::cast_precision_loss)] - fn value(&self) -> f64 { - self.value_num as f64 / self.value_denom as f64 - } - - #[cfg(feature = "decimal")] - #[must_use] - fn value(&self) -> Decimal { - Decimal::new(self.value_num, 0) / Decimal::new(self.value_denom, 0) - } - - #[cfg(not(feature = "decimal"))] - #[must_use] - #[allow(clippy::cast_precision_loss)] - fn quantity(&self) -> f64 { - self.quantity_num as f64 / self.quantity_denom as f64 - } - - #[cfg(feature = "decimal")] - #[must_use] - fn quantity(&self) -> Decimal { - Decimal::new(self.quantity_num, 0) / Decimal::new(self.quantity_denom, 0) - } -} - -const SEL: &str = r" -SELECT -guid, -tx_guid, -account_guid, -memo, -action, -reconcile_state, -reconcile_date, -value_num, -value_denom, -quantity_num, -quantity_denom, -lot_guid -FROM splits -"; - -impl SplitQ for SQLiteQuery { - type S = Split; - - async fn all(&self) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(SEL)?; - let result = stmt - .query([])? - .mapped(|row| Split::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Split::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn account_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE account_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Split::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn tx_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE tx_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Split::try_from(row)) - .collect::, _>>()?; - Ok(result) - } -} - -#[cfg(test)] - -mod tests { - use super::*; - - #[cfg(not(feature = "decimal"))] - use float_cmp::assert_approx_eq; - use pretty_assertions::assert_eq; - use tokio::sync::OnceCell; - - static Q: OnceCell = OnceCell::const_new(); - async fn setup() -> &'static SQLiteQuery { - Q.get_or_init(|| async { - let uri: &str = &format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(uri).unwrap() - }) - .await - } - - #[tokio::test] - async fn test_split() { - let query = setup().await; - let result = query - .guid("de832fe97e37811a7fff7e28b3a43425") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "de832fe97e37811a7fff7e28b3a43425"); - assert_eq!(result.tx_guid(), "6c8876003c4a6026e38e3afb67d6f2b1"); - assert_eq!(result.account_guid(), "93fc043c3062aaa1297b30e543d2cd0d"); - assert_eq!(result.memo(), ""); - assert_eq!(result.action(), ""); - assert_eq!(result.reconcile_state(), false); - assert_eq!(result.reconcile_datetime(), None); - assert_eq!(result.lot_guid(), ""); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.value(), 150.0); - #[cfg(feature = "decimal")] - assert_eq!(result.value(), Decimal::new(150, 0)); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result.quantity(), 150.0); - #[cfg(feature = "decimal")] - assert_eq!(result.quantity(), Decimal::new(150, 0)); - } - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = query.all().await.unwrap(); - assert_eq!(result.len(), 25); - } - - #[tokio::test] - async fn test_guid() { - let query = setup().await; - let result = query - .guid("de832fe97e37811a7fff7e28b3a43425") - .await - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, result[0].value(), 150.0); - #[cfg(feature = "decimal")] - assert_eq!(result[0].value(), Decimal::new(150, 0)); - } - - #[tokio::test] - async fn test_account_guid() { - let query = setup().await; - let result = query - .account_guid("93fc043c3062aaa1297b30e543d2cd0d") - .await - .unwrap(); - assert_eq!(result.len(), 3); - } - - #[tokio::test] - async fn test_tx_guid() { - let query = setup().await; - let result = query - .tx_guid("6c8876003c4a6026e38e3afb67d6f2b1") - .await - .unwrap(); - assert_eq!(result.len(), 2); - } -} diff --git a/src/query/sqlitefaster/transaction.rs b/src/query/sqlitefaster/transaction.rs deleted file mode 100644 index 34c2f45..0000000 --- a/src/query/sqlitefaster/transaction.rs +++ /dev/null @@ -1,183 +0,0 @@ -// ref: https://piecash.readthedocs.io/en/master/object_model.html -// ref: https://wiki.gnucash.org/wiki/SQL - -use chrono::NaiveDateTime; -use rusqlite::Row; - -use super::SQLiteQuery; -use crate::error::Error; -use crate::query::{TransactionQ, TransactionT}; - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] -pub struct Transaction { - pub guid: String, - pub currency_guid: String, - pub num: String, - pub post_date: Option, - pub enter_date: Option, - pub description: Option, -} - -impl<'a> TryFrom<&'a Row<'a>> for Transaction { - type Error = rusqlite::Error; - - fn try_from(row: &'a Row<'a>) -> Result { - Ok(Self { - guid: row.get(0)?, - currency_guid: row.get(1)?, - num: row.get(2)?, - post_date: row.get(3)?, - enter_date: row.get(4)?, - description: row.get(5)?, - }) - } -} - -impl TransactionT for Transaction { - fn guid(&self) -> String { - self.guid.clone() - } - fn currency_guid(&self) -> String { - self.currency_guid.clone() - } - fn num(&self) -> String { - self.num.clone() - } - fn post_datetime(&self) -> NaiveDateTime { - self.post_date.expect("transaction post_date should exist") - } - fn enter_datetime(&self) -> NaiveDateTime { - self.enter_date - .expect("transaction enter_date should exist") - } - fn description(&self) -> String { - self.description.clone().unwrap_or_default() - } -} - -const SEL: &str = r" -SELECT -guid, -currency_guid, -num, -post_date, -enter_date, -description -FROM transactions -"; - -impl TransactionQ for SQLiteQuery { - type T = Transaction; - - async fn all(&self) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(SEL)?; - let result = stmt - .query([])? - .mapped(|row| Transaction::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Transaction::try_from(row)) - .collect::, _>>()?; - Ok(result) - } - - async fn currency_guid(&self, guid: &str) -> Result, Error> { - let conn = self.conn.lock().unwrap(); - let mut stmt = conn.prepare(&format!("{SEL}\nWHERE currency_guid = ?"))?; - let result = stmt - .query([guid])? - .mapped(|row| Transaction::try_from(row)) - .collect::, _>>()?; - Ok(result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use pretty_assertions::assert_eq; - use tokio::sync::OnceCell; - - static Q: OnceCell = OnceCell::const_new(); - async fn setup() -> &'static SQLiteQuery { - Q.get_or_init(|| async { - let uri: &str = &format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ); - - println!("work_dir: {:?}", std::env::current_dir()); - SQLiteQuery::new(uri).unwrap() - }) - .await - } - - #[tokio::test] - async fn test_transaction() { - let query = setup().await; - let result = query - .guid("6c8876003c4a6026e38e3afb67d6f2b1") - .await - .unwrap(); - - let result = &result[0]; - assert_eq!(result.guid(), "6c8876003c4a6026e38e3afb67d6f2b1"); - assert_eq!(result.currency_guid(), "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(result.num(), ""); - assert_eq!( - result.post_datetime(), - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!( - result.enter_datetime(), - NaiveDateTime::parse_from_str("2014-12-25 10:08:15", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!(result.description(), "income 1"); - } - - #[tokio::test] - async fn test_all() { - let query = setup().await; - let result = query.all().await.unwrap(); - assert_eq!(result.len(), 11); - } - - #[tokio::test] - async fn test_by_guid() { - let query = setup().await; - let result = query - .guid("6c8876003c4a6026e38e3afb67d6f2b1") - .await - .unwrap(); - - assert_eq!( - result[0].post_date.unwrap(), - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - - assert_eq!( - result[0].enter_date.unwrap(), - NaiveDateTime::parse_from_str("2014-12-25 10:08:15", "%Y-%m-%d %H:%M:%S").unwrap() - ); - } - - #[tokio::test] - async fn test_currency_guid() { - let query = setup().await; - let result = query - .currency_guid("346629655191dcf59a7e2c2a85b70f69") - .await - .unwrap(); - - assert_eq!(result.len(), 11); - } -} diff --git a/tests/all.rs b/tests/all.rs index 4e9db03..f833688 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1,9 +1,8 @@ -use rucash::{Book, MySQLQuery, PostgreSQLQuery, SQLiteQuery, SQLiteQueryFaster, XMLQuery}; +use rucash::{Book, MySQLQuery, PostgreSQLQuery, SQLiteQuery, XMLQuery}; mod mysql; mod postgresql; mod sqlite; -mod sqlitefaster; mod xml; mod consistency { @@ -11,12 +10,7 @@ mod consistency { use pretty_assertions::assert_eq; async fn setup_sqlite() -> Book { - let query = SQLiteQuery::new(&sqlite::uri()).await.unwrap(); - Book::new(query).await.unwrap() - } - - async fn setup_sqlitefaster() -> Book { - let query = SQLiteQueryFaster::new(&sqlitefaster::uri()).unwrap(); + let query = SQLiteQuery::new(&sqlite::uri()).unwrap(); Book::new(query).await.unwrap() } @@ -67,9 +61,6 @@ mod consistency { let mut v_sqlite = setup_sqlite().await.accounts().await.unwrap(); v_sqlite.sort_by_key(|x| x.guid.clone()); - let mut v_sqlitefaster = setup_sqlitefaster().await.accounts().await.unwrap(); - v_sqlitefaster.sort_by_key(|x| x.guid.clone()); - let mut v_postgresql = setup_postgresql().await.accounts().await.unwrap(); v_postgresql.sort_by_key(|x| x.guid.clone()); @@ -79,8 +70,6 @@ mod consistency { let mut v_xml = setup_xml().await.accounts().await.unwrap(); v_xml.sort_by_key(|x| x.guid.clone()); - println!("vec_match(&v_sqlite, &v_sqlitefaster)"); - assert!(vec_match(&v_sqlite, &v_sqlitefaster, cmp)); println!("vec_match(&v_sqlite, &v_postgresql)"); assert!(vec_match(&v_sqlite, &v_postgresql, cmp)); println!("vec_match(&v_sqlite, &v_mysql)"); @@ -119,9 +108,6 @@ mod consistency { let mut v_sqlite = setup_sqlite().await.splits().await.unwrap(); v_sqlite.sort_by_key(|x| x.guid.clone()); - let mut v_sqlitefaster = setup_sqlitefaster().await.splits().await.unwrap(); - v_sqlitefaster.sort_by_key(|x| x.guid.clone()); - let mut v_postgresql = setup_postgresql().await.splits().await.unwrap(); v_postgresql.sort_by_key(|x| x.guid.clone()); @@ -131,8 +117,6 @@ mod consistency { let mut v_xml = setup_xml().await.splits().await.unwrap(); v_xml.sort_by_key(|x| x.guid.clone()); - println!("vec_match(&v_sqlite, &v_sqlitefaster)"); - assert!(vec_match(&v_sqlite, &v_sqlitefaster, cmp)); println!("vec_match(&v_sqlite, &v_postgresql)"); assert!(vec_match(&v_sqlite, &v_postgresql, cmp)); println!("vec_match(&v_sqlite, &v_mysql)"); @@ -161,9 +145,6 @@ mod consistency { let mut v_sqlite = setup_sqlite().await.transactions().await.unwrap(); v_sqlite.sort_by_key(|x| x.guid.clone()); - let mut v_sqlitefaster = setup_sqlitefaster().await.transactions().await.unwrap(); - v_sqlitefaster.sort_by_key(|x| x.guid.clone()); - let mut v_postgresql = setup_postgresql().await.transactions().await.unwrap(); v_postgresql.sort_by_key(|x| x.guid.clone()); @@ -173,8 +154,6 @@ mod consistency { let mut v_xml = setup_xml().await.transactions().await.unwrap(); v_xml.sort_by_key(|x| x.guid.clone()); - println!("vec_match(&v_sqlite, &v_sqlitefaster)"); - assert!(vec_match(&v_sqlite, &v_sqlitefaster, cmp)); println!("vec_match(&v_sqlite, &v_postgresql)"); assert!(vec_match(&v_sqlite, &v_postgresql, cmp)); println!("vec_match(&v_sqlite, &v_mysql)"); @@ -205,9 +184,6 @@ mod consistency { let mut v_sqlite = setup_sqlite().await.prices().await.unwrap(); v_sqlite.sort_by_key(|x| x.guid.clone()); - let mut v_sqlitefaster = setup_sqlitefaster().await.prices().await.unwrap(); - v_sqlitefaster.sort_by_key(|x| x.guid.clone()); - let mut v_postgresql = setup_postgresql().await.prices().await.unwrap(); v_postgresql.sort_by_key(|x| x.guid.clone()); @@ -217,8 +193,6 @@ mod consistency { let mut v_xml = setup_xml().await.prices().await.unwrap(); v_xml.sort_by_key(|x| x.guid.clone()); - println!("vec_match(&v_sqlite, &v_sqlitefaster)"); - assert!(vec_match(&v_sqlite, &v_sqlitefaster, cmp)); println!("vec_match(&v_sqlite, &v_postgresql)"); assert!(vec_match(&v_sqlite, &v_postgresql, cmp)); println!("vec_match(&v_sqlite, &v_mysql)"); @@ -254,9 +228,6 @@ mod consistency { let mut v_sqlite = setup_sqlite().await.commodities().await.unwrap(); v_sqlite.sort_by_key(|x| x.mnemonic.clone()); - let mut v_sqlitefaster = setup_sqlitefaster().await.commodities().await.unwrap(); - v_sqlitefaster.sort_by_key(|x| x.guid.clone()); - let mut v_postgresql = setup_postgresql().await.commodities().await.unwrap(); v_postgresql.sort_by_key(|x| x.mnemonic.clone()); @@ -266,8 +237,6 @@ mod consistency { let mut v_xml = setup_xml().await.commodities().await.unwrap(); v_xml.sort_by_key(|x| x.mnemonic.clone()); - println!("vec_match(&v_sqlite, &v_sqlitefaster)"); - assert!(vec_match(&v_sqlite, &v_sqlitefaster, cmp)); println!("vec_match(&v_sqlite, &v_postgresql)"); assert!(vec_match(&v_sqlite, &v_postgresql, cmp)); println!("vec_match(&v_sqlite, &v_mysql)"); diff --git a/tests/sqlite.rs b/tests/sqlite.rs index 96bade2..8012e50 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -8,7 +8,7 @@ use rucash::{Book, SQLiteQuery}; pub fn uri() -> String { format!( - "sqlite://{}/tests/db/sqlite/complex_sample.gnucash", + "{}/tests/db/sqlite/complex_sample.gnucash", env!("CARGO_MANIFEST_DIR") ) } @@ -19,22 +19,20 @@ mod book { #[tokio::test] async fn new() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); Book::new(query).await.unwrap(); } #[tokio::test] #[should_panic] async fn new_fail() { - let query = SQLiteQuery::new("sqlite://tests/sample/no.gnucash") - .await - .unwrap(); + let query = SQLiteQuery::new("tests/sample/no.gnucash").unwrap(); Book::new(query).await.unwrap(); } #[tokio::test] async fn accounts() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let accounts = book.accounts().await.unwrap(); assert_eq!(accounts.len(), 21); @@ -42,7 +40,7 @@ mod book { #[tokio::test] async fn accounts_filter() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let accounts = book .accounts() @@ -55,7 +53,7 @@ mod book { #[tokio::test] async fn accounts_contains_name_ignore_case() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let accounts = book.accounts_contains_name_ignore_case("aS").await.unwrap(); assert_eq!(accounts.len(), 3); @@ -63,7 +61,7 @@ mod book { #[tokio::test] async fn account_contains_name_ignore_case() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .account_contains_name_ignore_case("NAS") @@ -75,7 +73,7 @@ mod book { #[tokio::test] async fn splits() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let splits = book.splits().await.unwrap(); assert_eq!(splits.len(), 25); @@ -83,7 +81,7 @@ mod book { #[tokio::test] async fn transactions() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let transactions = book.transactions().await.unwrap(); assert_eq!(transactions.len(), 11); @@ -91,7 +89,7 @@ mod book { #[tokio::test] async fn prices() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let prices = book.prices().await.unwrap(); assert_eq!(prices.len(), 5); @@ -99,7 +97,7 @@ mod book { #[tokio::test] async fn commodities() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodities = book.commodities().await.unwrap(); assert_eq!(commodities.len(), 5); @@ -107,7 +105,7 @@ mod book { #[tokio::test] async fn currencies() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let currencies = book.currencies().await.unwrap(); assert_eq!(currencies.len(), 4); @@ -118,7 +116,7 @@ mod account { use pretty_assertions::assert_eq; #[tokio::test] async fn property() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .accounts() @@ -143,7 +141,7 @@ mod account { #[tokio::test] async fn balance() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .accounts() @@ -160,7 +158,7 @@ mod account { } #[tokio::test] async fn balance_diff_currency() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .accounts() @@ -180,7 +178,7 @@ mod account { } #[tokio::test] async fn splits() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .account_contains_name_ignore_case("Cash") @@ -193,7 +191,7 @@ mod account { #[tokio::test] async fn parent() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .account_contains_name_ignore_case("Cash") @@ -206,7 +204,7 @@ mod account { #[tokio::test] async fn no_parent() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .account_contains_name_ignore_case("Root Account") @@ -220,7 +218,7 @@ mod account { #[tokio::test] async fn children() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .account_contains_name_ignore_case("Current") @@ -233,7 +231,7 @@ mod account { #[tokio::test] async fn commodity() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let account = book .account_contains_name_ignore_case("Cash") @@ -251,7 +249,7 @@ mod split { #[tokio::test] async fn property() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let split = book .splits() @@ -284,7 +282,7 @@ mod split { #[tokio::test] async fn transaction() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let split = book .splits() @@ -299,7 +297,7 @@ mod split { #[tokio::test] async fn account() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let split = book .splits() @@ -320,7 +318,7 @@ mod transaction { #[tokio::test] async fn property() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let transaction = book .transactions() @@ -349,7 +347,7 @@ mod transaction { #[tokio::test] async fn currency() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let transaction = book .transactions() @@ -364,7 +362,7 @@ mod transaction { #[tokio::test] async fn splits() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let transaction = book .transactions() @@ -386,7 +384,7 @@ mod price { #[tokio::test] async fn property() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let price = book .prices() @@ -414,7 +412,7 @@ mod price { #[tokio::test] async fn commodity() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let price = book .prices() @@ -429,7 +427,7 @@ mod price { #[tokio::test] async fn currency() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let price = book .prices() @@ -450,7 +448,7 @@ mod commodity { #[tokio::test] async fn property() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -473,7 +471,7 @@ mod commodity { #[tokio::test] async fn accounts() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -488,7 +486,7 @@ mod commodity { #[tokio::test] async fn transactions() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -503,7 +501,7 @@ mod commodity { #[tokio::test] async fn as_commodity_prices() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -518,7 +516,7 @@ mod commodity { #[tokio::test] async fn as_currency_prices() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -533,7 +531,7 @@ mod commodity { #[tokio::test] async fn as_commodity_or_currency_prices() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -549,7 +547,7 @@ mod commodity { #[tokio::test] async fn rate_direct() { // ADF => AED - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -579,7 +577,7 @@ mod commodity { assert_eq!(rate, Decimal::new(15, 1)); // AED => EUR - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); let commodity = book .commodities() @@ -611,7 +609,7 @@ mod commodity { #[tokio::test] async fn rate_indirect() { - let query = SQLiteQuery::new(&uri()).await.unwrap(); + let query = SQLiteQuery::new(&uri()).unwrap(); let book = Book::new(query).await.unwrap(); // USD => AED let commodity = book diff --git a/tests/sqlitefaster.rs b/tests/sqlitefaster.rs deleted file mode 100644 index 464aed9..0000000 --- a/tests/sqlitefaster.rs +++ /dev/null @@ -1,639 +0,0 @@ -use chrono::NaiveDateTime; -#[cfg(not(feature = "decimal"))] -use float_cmp::assert_approx_eq; -#[cfg(feature = "decimal")] -use rust_decimal::Decimal; - -use rucash::{Book, SQLiteQueryFaster}; - -pub fn uri() -> String { - format!( - "{}/tests/db/sqlite/complex_sample.gnucash", - env!("CARGO_MANIFEST_DIR") - ) -} - -mod book { - use super::*; - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn new() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - Book::new(query).await.unwrap(); - } - - #[tokio::test] - #[should_panic] - async fn new_fail() { - let query = SQLiteQueryFaster::new("sqlite://tests/sample/no.gnucash").unwrap(); - Book::new(query).await.unwrap(); - } - - #[tokio::test] - async fn accounts() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let accounts = book.accounts().await.unwrap(); - assert_eq!(accounts.len(), 21); - } - - #[tokio::test] - async fn accounts_filter() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let accounts = book - .accounts() - .await - .unwrap() - .into_iter() - .filter(|x| x.name.to_lowercase().contains(&"aS".to_lowercase())); - assert_eq!(accounts.count(), 3); - } - - #[tokio::test] - async fn accounts_contains_name_ignore_case() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let accounts = book.accounts_contains_name_ignore_case("aS").await.unwrap(); - assert_eq!(accounts.len(), 3); - } - - #[tokio::test] - async fn account_contains_name_ignore_case() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("NAS") - .await - .unwrap() - .unwrap(); - assert_eq!(account.name, "NASDAQ"); - } - - #[tokio::test] - async fn splits() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let splits = book.splits().await.unwrap(); - assert_eq!(splits.len(), 25); - } - - #[tokio::test] - async fn transactions() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let transactions = book.transactions().await.unwrap(); - assert_eq!(transactions.len(), 11); - } - - #[tokio::test] - async fn prices() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let prices = book.prices().await.unwrap(); - assert_eq!(prices.len(), 5); - } - - #[tokio::test] - async fn commodities() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodities = book.commodities().await.unwrap(); - assert_eq!(commodities.len(), 5); - } - - #[tokio::test] - async fn currencies() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let currencies = book.currencies().await.unwrap(); - assert_eq!(currencies.len(), 4); - } -} -mod account { - use super::*; - use pretty_assertions::assert_eq; - #[tokio::test] - async fn property() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .accounts() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "fcd795021c976ba75621ec39e75f6214") - .unwrap(); - - assert_eq!(account.guid, "fcd795021c976ba75621ec39e75f6214"); - assert_eq!(account.name, "Asset"); - assert_eq!(account.r#type, "ASSET"); - assert_eq!(account.commodity_guid, "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(account.commodity_scu, 100); - assert_eq!(account.non_std_scu, false); - assert_eq!(account.parent_guid, "00622dda21937b29e494179de5013f82"); - assert_eq!(account.code, ""); - assert_eq!(account.description, ""); - assert_eq!(account.hidden, false); - assert_eq!(account.placeholder, true); - } - - #[tokio::test] - async fn balance() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .accounts() - .await - .unwrap() - .into_iter() - .find(|x| x.name == "Current") - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, account.balance(&book).await.unwrap(), 4590.0); - #[cfg(feature = "decimal")] - assert_eq!(account.balance(&book).await.unwrap(), Decimal::new(4590, 0)); - } - #[tokio::test] - async fn balance_diff_currency() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .accounts() - .await - .unwrap() - .into_iter() - .find(|x| x.name == "Asset") - .unwrap(); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, account.balance(&book).await.unwrap(), 24695.3); - #[cfg(feature = "decimal")] - assert_eq!( - account.balance(&book).await.unwrap(), - Decimal::new(246953, 1) - ); - } - #[tokio::test] - async fn splits() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Cash") - .await - .unwrap() - .unwrap(); - let splits = account.splits().await.unwrap(); - assert_eq!(splits.len(), 3); - } - - #[tokio::test] - async fn parent() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Cash") - .await - .unwrap() - .unwrap(); - let parent = account.parent().await.unwrap().unwrap(); - assert_eq!(parent.name, "Current"); - } - - #[tokio::test] - async fn no_parent() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Root Account") - .await - .unwrap() - .unwrap(); - let parent = account.parent().await.unwrap(); - dbg!(&parent); - assert!(parent.is_none()); - } - - #[tokio::test] - async fn children() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Current") - .await - .unwrap() - .unwrap(); - let children = account.children().await.unwrap(); - assert_eq!(children.len(), 3); - } - - #[tokio::test] - async fn commodity() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let account = book - .account_contains_name_ignore_case("Cash") - .await - .unwrap() - .unwrap(); - let commodity = account.commodity().await.unwrap(); - assert_eq!(commodity.mnemonic, "EUR"); - } -} - -mod split { - use super::*; - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn property() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let split = book - .splits() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "de832fe97e37811a7fff7e28b3a43425") - .unwrap(); - - assert_eq!(split.guid, "de832fe97e37811a7fff7e28b3a43425"); - assert_eq!(split.tx_guid, "6c8876003c4a6026e38e3afb67d6f2b1"); - assert_eq!(split.account_guid, "93fc043c3062aaa1297b30e543d2cd0d"); - assert_eq!(split.memo, ""); - assert_eq!(split.action, ""); - assert_eq!(split.reconcile_state, false); - assert_eq!(split.reconcile_datetime, None); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, split.value, 150.0); - #[cfg(feature = "decimal")] - assert_eq!(split.value, Decimal::new(150, 0)); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, split.quantity, 150.0); - #[cfg(feature = "decimal")] - assert_eq!(split.quantity, Decimal::new(150, 0)); - - assert_eq!(split.lot_guid, ""); - } - - #[tokio::test] - async fn transaction() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let split = book - .splits() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "de832fe97e37811a7fff7e28b3a43425") - .unwrap(); - let transaction = split.transaction().await.unwrap(); - assert_eq!(transaction.description, "income 1"); - } - - #[tokio::test] - async fn account() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let split = book - .splits() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "de832fe97e37811a7fff7e28b3a43425") - .unwrap(); - let account = split.account().await.unwrap(); - assert_eq!(account.name, "Cash"); - } -} - -mod transaction { - use super::*; - - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn property() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let transaction = book - .transactions() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "6c8876003c4a6026e38e3afb67d6f2b1") - .unwrap(); - - assert_eq!(transaction.guid, "6c8876003c4a6026e38e3afb67d6f2b1"); - assert_eq!( - transaction.currency_guid, - "346629655191dcf59a7e2c2a85b70f69" - ); - assert_eq!(transaction.num, ""); - assert_eq!( - transaction.post_datetime, - NaiveDateTime::parse_from_str("2014-12-24 10:59:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!( - transaction.enter_datetime, - NaiveDateTime::parse_from_str("2014-12-25 10:08:15", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!(transaction.description, "income 1"); - } - - #[tokio::test] - async fn currency() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let transaction = book - .transactions() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "6c8876003c4a6026e38e3afb67d6f2b1") - .unwrap(); - let currency = transaction.currency().await.unwrap(); - assert_eq!(currency.fullname, "Euro"); - } - - #[tokio::test] - async fn splits() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let transaction = book - .transactions() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "6c8876003c4a6026e38e3afb67d6f2b1") - .unwrap(); - let splits = transaction.splits().await.unwrap(); - assert_eq!(splits.len(), 2); - } -} - -mod price { - use super::*; - - use chrono::NaiveDateTime; - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn property() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let price = book - .prices() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "0d6684f44fb018e882de76094ed9c433") - .unwrap(); - - assert_eq!(price.guid, "0d6684f44fb018e882de76094ed9c433"); - assert_eq!(price.commodity_guid, "d821d6776fde9f7c2d01b67876406fd3"); - assert_eq!(price.currency_guid, "5f586908098232e67edb1371408bfaa8"); - assert_eq!( - price.datetime, - NaiveDateTime::parse_from_str("2018-02-20 23:00:00", "%Y-%m-%d %H:%M:%S").unwrap() - ); - assert_eq!(price.source, "user:price-editor"); - assert_eq!(price.r#type, "unknown"); - - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, price.value, 1.5); - #[cfg(feature = "decimal")] - assert_eq!(price.value, Decimal::new(15, 1)); - } - - #[tokio::test] - async fn commodity() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let price = book - .prices() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "0d6684f44fb018e882de76094ed9c433") - .unwrap(); - let commodity = price.commodity().await.unwrap(); - assert_eq!(commodity.fullname, "Andorran Franc"); - } - - #[tokio::test] - async fn currency() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let price = book - .prices() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "0d6684f44fb018e882de76094ed9c433") - .unwrap(); - let currency = price.currency().await.unwrap(); - assert_eq!(currency.fullname, "UAE Dirham"); - } -} - -mod commodity { - use super::*; - - use pretty_assertions::assert_eq; - - #[tokio::test] - async fn property() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - - assert_eq!(commodity.guid, "346629655191dcf59a7e2c2a85b70f69"); - assert_eq!(commodity.namespace, "CURRENCY"); - assert_eq!(commodity.mnemonic, "EUR"); - assert_eq!(commodity.fullname, "Euro"); - assert_eq!(commodity.cusip, "978"); - assert_eq!(commodity.fraction, 100); - assert_eq!(commodity.quote_flag, true); - assert_eq!(commodity.quote_source, "currency"); - assert_eq!(commodity.quote_tz, ""); - } - - #[tokio::test] - async fn accounts() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let accounts = commodity.accounts().await.unwrap(); - assert_eq!(accounts.len(), 14); - } - - #[tokio::test] - async fn transactions() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let transactions = commodity.transactions().await.unwrap(); - assert_eq!(transactions.len(), 11); - } - - #[tokio::test] - async fn as_commodity_prices() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let prices = commodity.as_commodity_prices().await.unwrap(); - assert_eq!(prices.len(), 1); - } - - #[tokio::test] - async fn as_currency_prices() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let prices = commodity.as_currency_prices().await.unwrap(); - assert_eq!(prices.len(), 2); - } - - #[tokio::test] - async fn as_commodity_or_currency_prices() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - let prices = commodity.as_commodity_or_currency_prices().await.unwrap(); - assert_eq!(prices.len(), 3); - } - - #[tokio::test] - async fn rate_direct() { - // ADF => AED - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "d821d6776fde9f7c2d01b67876406fd3") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - - let rate = commodity.sell(¤cy, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 1.5); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(15, 1)); - - let rate = currency.buy(&commodity, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 1.5); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(15, 1)); - - // AED => EUR - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "346629655191dcf59a7e2c2a85b70f69") - .unwrap(); - - let rate = commodity.sell(¤cy, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 9.0 / 10.0); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(9, 0) / Decimal::new(10, 0)); - - let rate = currency.buy(&commodity, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 9.0 / 10.0); - #[cfg(feature = "decimal")] - assert_eq!(rate, Decimal::new(9, 0) / Decimal::new(10, 0)); - } - - #[tokio::test] - async fn rate_indirect() { - let query = SQLiteQueryFaster::new(&uri()).unwrap(); - let book = Book::new(query).await.unwrap(); - // USD => AED - let commodity = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "1e5d65e2726a5d4595741cb204992991") - .unwrap(); - let currency = book - .commodities() - .await - .unwrap() - .into_iter() - .find(|x| x.guid == "5f586908098232e67edb1371408bfaa8") - .unwrap(); - - let rate = commodity.sell(¤cy, &book).await.unwrap(); - #[cfg(not(feature = "decimal"))] - assert_approx_eq!(f64, rate, 7.0 / 5.0 * 10.0 / 9.0); - #[cfg(feature = "decimal")] - assert_eq!( - rate, - (Decimal::new(7, 0) / Decimal::new(5, 0)) * (Decimal::new(10, 0) / Decimal::new(9, 0)), - ); - } -}