lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 1b018c77daf27a98cb868c1ff272239ab822dbf8
parent fd9328852cfa9248be8657e809c0d0c77888271f
Author: triesap <tyson@radroots.org>
Date:   Sun, 22 Feb 2026 23:43:54 +0000

coverage: enforce full coverage for tangle db crates


- remove the coverage-minimal override for `radroots-tangle-db` and run full-mode gates
- refactor relationless tangle db schema and model find-one shapes to drop impossible rel variants
- expand tangle db integration and backup tests to exercise update delete restore and query branches
- validate strict 100/100/100 coverage gates for `radroots-tangle-db` and `radroots-tangle-db-schema`

Diffstat:
Mcontract/coverage/profiles.toml | 5-----
Mcrates/tangle-db-schema/src/models/farm.rs | 13-------------
Mcrates/tangle-db-schema/src/models/farm_gcs_location.rs | 13-------------
Mcrates/tangle-db-schema/src/models/farm_member.rs | 13-------------
Mcrates/tangle-db-schema/src/models/farm_member_claim.rs | 13-------------
Mcrates/tangle-db-schema/src/models/farm_tag.rs | 13-------------
Mcrates/tangle-db-schema/src/models/log_error.rs | 13-------------
Mcrates/tangle-db-schema/src/models/nostr_event_state.rs | 13-------------
Mcrates/tangle-db-schema/src/models/plot.rs | 13-------------
Mcrates/tangle-db-schema/src/models/plot_gcs_location.rs | 13-------------
Mcrates/tangle-db-schema/src/models/plot_tag.rs | 13-------------
Mcrates/tangle-db-schema/src/models/trade_product.rs | 13-------------
Mcrates/tangle-db/Cargo.toml | 3+++
Mcrates/tangle-db/src/backup.rs | 357++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcrates/tangle-db/src/lib.rs | 14++++++++++++++
Mcrates/tangle-db/src/models/farm.rs | 44+++++++-------------------------------------
Mcrates/tangle-db/src/models/farm_gcs_location.rs | 48+++++++++---------------------------------------
Mcrates/tangle-db/src/models/farm_member.rs | 46++++++++--------------------------------------
Mcrates/tangle-db/src/models/farm_member_claim.rs | 48+++++++++---------------------------------------
Mcrates/tangle-db/src/models/farm_tag.rs | 45+++++++--------------------------------------
Mcrates/tangle-db/src/models/gcs_location.rs | 14+++++---------
Mcrates/tangle-db/src/models/log_error.rs | 40+++++-----------------------------------
Mcrates/tangle-db/src/models/media_image.rs | 14+++++---------
Mcrates/tangle-db/src/models/nostr_event_state.rs | 40+++++-----------------------------------
Mcrates/tangle-db/src/models/nostr_profile.rs | 14+++++---------
Mcrates/tangle-db/src/models/nostr_relay.rs | 14+++++---------
Mcrates/tangle-db/src/models/plot.rs | 40+++++-----------------------------------
Mcrates/tangle-db/src/models/plot_gcs_location.rs | 40+++++-----------------------------------
Mcrates/tangle-db/src/models/plot_tag.rs | 41+++++------------------------------------
Mcrates/tangle-db/src/models/trade_product.rs | 55+++++++------------------------------------------------
Acrates/tangle-db/tests/full_mode.rs | 1232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
31 files changed, 1686 insertions(+), 611 deletions(-)

diff --git a/contract/coverage/profiles.toml b/contract/coverage/profiles.toml @@ -22,8 +22,3 @@ test_threads = 1 no_default_features = true features = ["embedded"] test_threads = 1 - -[profiles.crates."radroots-tangle-db"] -no_default_features = false -features = ["coverage-minimal"] -test_threads = 1 diff --git a/crates/tangle-db-schema/src/models/farm.rs b/crates/tangle-db-schema/src/models/farm.rs @@ -146,11 +146,6 @@ impl FarmQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum FarmFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -182,13 +177,6 @@ pub struct IFarmFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IFarmFindOneRelArgs { - pub rel: FarmFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IFarmFindOne") @@ -197,7 +185,6 @@ pub struct IFarmFindOneRelArgs { #[serde(untagged)] pub enum IFarmFindOne { On(IFarmFindOneArgs), - Rel(IFarmFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/farm_gcs_location.rs b/crates/tangle-db-schema/src/models/farm_gcs_location.rs @@ -92,11 +92,6 @@ impl FarmGcsLocationQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum FarmGcsLocationFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -128,13 +123,6 @@ pub struct IFarmGcsLocationFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IFarmGcsLocationFindOneRelArgs { - pub rel: FarmGcsLocationFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IFarmGcsLocationFindOne") @@ -143,7 +131,6 @@ pub struct IFarmGcsLocationFindOneRelArgs { #[serde(untagged)] pub enum IFarmGcsLocationFindOne { On(IFarmGcsLocationFindOneArgs), - Rel(IFarmGcsLocationFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/farm_member.rs b/crates/tangle-db-schema/src/models/farm_member.rs @@ -92,11 +92,6 @@ impl FarmMemberQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum FarmMemberFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -128,13 +123,6 @@ pub struct IFarmMemberFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IFarmMemberFindOneRelArgs { - pub rel: FarmMemberFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IFarmMemberFindOne") @@ -143,7 +131,6 @@ pub struct IFarmMemberFindOneRelArgs { #[serde(untagged)] pub enum IFarmMemberFindOne { On(IFarmMemberFindOneArgs), - Rel(IFarmMemberFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/farm_member_claim.rs b/crates/tangle-db-schema/src/models/farm_member_claim.rs @@ -86,11 +86,6 @@ impl FarmMemberClaimQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum FarmMemberClaimFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -122,13 +117,6 @@ pub struct IFarmMemberClaimFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IFarmMemberClaimFindOneRelArgs { - pub rel: FarmMemberClaimFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IFarmMemberClaimFindOne") @@ -137,7 +125,6 @@ pub struct IFarmMemberClaimFindOneRelArgs { #[serde(untagged)] pub enum IFarmMemberClaimFindOne { On(IFarmMemberClaimFindOneArgs), - Rel(IFarmMemberClaimFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/farm_tag.rs b/crates/tangle-db-schema/src/models/farm_tag.rs @@ -84,11 +84,6 @@ impl FarmTagQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum FarmTagFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -120,13 +115,6 @@ pub struct IFarmTagFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IFarmTagFindOneRelArgs { - pub rel: FarmTagFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IFarmTagFindOne") @@ -135,7 +123,6 @@ pub struct IFarmTagFindOneRelArgs { #[serde(untagged)] pub enum IFarmTagFindOne { On(IFarmTagFindOneArgs), - Rel(IFarmTagFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/log_error.rs b/crates/tangle-db-schema/src/models/log_error.rs @@ -116,11 +116,6 @@ impl LogErrorQueryBindValues { } } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum LogErrorFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -152,13 +147,6 @@ pub struct ILogErrorFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct ILogErrorFindOneRelArgs { - pub rel: LogErrorFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "ILogErrorFindOne") @@ -167,7 +155,6 @@ pub struct ILogErrorFindOneRelArgs { #[serde(untagged)] pub enum ILogErrorFindOne { On(ILogErrorFindOneArgs), - Rel(ILogErrorFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/nostr_event_state.rs b/crates/tangle-db-schema/src/models/nostr_event_state.rs @@ -111,11 +111,6 @@ impl NostrEventStateQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum NostrEventStateFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -147,13 +142,6 @@ pub struct INostrEventStateFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct INostrEventStateFindOneRelArgs { - pub rel: NostrEventStateFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "INostrEventStateFindOne") @@ -162,7 +150,6 @@ pub struct INostrEventStateFindOneRelArgs { #[serde(untagged)] pub enum INostrEventStateFindOne { On(INostrEventStateFindOneArgs), - Rel(INostrEventStateFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/plot.rs b/crates/tangle-db-schema/src/models/plot.rs @@ -125,11 +125,6 @@ impl PlotQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum PlotFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -161,13 +156,6 @@ pub struct IPlotFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IPlotFindOneRelArgs { - pub rel: PlotFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IPlotFindOne") @@ -176,7 +164,6 @@ pub struct IPlotFindOneRelArgs { #[serde(untagged)] pub enum IPlotFindOne { On(IPlotFindOneArgs), - Rel(IPlotFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/plot_gcs_location.rs b/crates/tangle-db-schema/src/models/plot_gcs_location.rs @@ -92,11 +92,6 @@ impl PlotGcsLocationQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum PlotGcsLocationFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -128,13 +123,6 @@ pub struct IPlotGcsLocationFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IPlotGcsLocationFindOneRelArgs { - pub rel: PlotGcsLocationFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IPlotGcsLocationFindOne") @@ -143,7 +131,6 @@ pub struct IPlotGcsLocationFindOneRelArgs { #[serde(untagged)] pub enum IPlotGcsLocationFindOne { On(IPlotGcsLocationFindOneArgs), - Rel(IPlotGcsLocationFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/plot_tag.rs b/crates/tangle-db-schema/src/models/plot_tag.rs @@ -84,11 +84,6 @@ impl PlotTagQueryBindValues { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum PlotTagFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -120,13 +115,6 @@ pub struct IPlotTagFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct IPlotTagFindOneRelArgs { - pub rel: PlotTagFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "IPlotTagFindOne") @@ -135,7 +123,6 @@ pub struct IPlotTagFindOneRelArgs { #[serde(untagged)] pub enum IPlotTagFindOne { On(IPlotTagFindOneArgs), - Rel(IPlotTagFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db-schema/src/models/trade_product.rs b/crates/tangle-db-schema/src/models/trade_product.rs @@ -164,11 +164,6 @@ impl TradeProductQueryBindValues { } } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Deserialize, Serialize)] -pub enum TradeProductFindManyRel {} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts( @@ -200,13 +195,6 @@ pub struct ITradeProductFindOneArgs { } #[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Deserialize, Serialize)] -pub struct ITradeProductFindOneRelArgs { - pub rel: TradeProductFindManyRel, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( feature = "ts-rs", ts(export, export_to = "types.ts", rename = "ITradeProductFindOne") @@ -215,7 +203,6 @@ pub struct ITradeProductFindOneRelArgs { #[serde(untagged)] pub enum ITradeProductFindOne { On(ITradeProductFindOneArgs), - Rel(ITradeProductFindOneRelArgs), } #[cfg_attr(feature = "ts-rs", derive(TS))] diff --git a/crates/tangle-db/Cargo.toml b/crates/tangle-db/Cargo.toml @@ -24,3 +24,6 @@ serde = { workspace = true } serde_json = { workspace = true } hex = { workspace = true } sha2 = { workspace = true } + +[dev-dependencies] +radroots-sql-core = { workspace = true, features = ["native"] } diff --git a/crates/tangle-db/src/backup.rs b/crates/tangle-db/src/backup.rs @@ -99,10 +99,8 @@ fn drop_existing_objects<E: SqlExecutor>(executor: &E) -> Result<(), SqlError> { object_type: Option<String>, name: Option<String>, } - let json = executor.query_raw( - "select type, name from sqlite_master where name not like 'sqlite_%'", - "[]", - )?; + let query = "select type, name from sqlite_master where name not like 'sqlite_%'"; + let json = executor.query_raw(query, "[]")?; let rows: Vec<MasterRow> = utils::parse_json(&json)?; let mut groups: HashMap<String, Vec<String>> = HashMap::new(); @@ -140,10 +138,7 @@ fn create_schema_from_backup<E: SqlExecutor>( executor.exec(sql, "[]")?; } } - for entry in schema - .iter() - .filter(|s| s.object_type != "table" && s.sql.is_some()) - { + for entry in schema.iter().filter(|s| s.object_type != "table") { if let Some(sql) = &entry.sql { executor.exec(sql, "[]")?; } @@ -208,10 +203,8 @@ fn insert_row<E: SqlExecutor>( } pub(crate) fn load_schema<E: SqlExecutor>(executor: &E) -> Result<Vec<SchemaEntry>, SqlError> { - let json = executor.query_raw( - "select type, name, tbl_name as table_name, sql from sqlite_master where name not like 'sqlite_%' order by type, name", - "[]", - )?; + let query = "select type, name, tbl_name as table_name, sql from sqlite_master where name not like 'sqlite_%' order by type, name"; + let json = executor.query_raw(query, "[]")?; #[derive(Deserialize)] struct RawSchema { #[serde(rename = "type")] @@ -292,3 +285,343 @@ fn validate_backup_version(backup: &DatabaseBackup) -> Result<(), SqlError> { } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use radroots_sql_core::ExecOutcome; + use std::sync::Mutex; + use std::sync::atomic::{AtomicUsize, Ordering}; + + struct MockExecutor { + query_rules: Vec<(String, String)>, + fail_exec_contains: Option<String>, + exec_calls: Mutex<Vec<String>>, + begin_calls: AtomicUsize, + commit_calls: AtomicUsize, + rollback_calls: AtomicUsize, + } + + impl MockExecutor { + fn new(query_rules: Vec<(String, String)>, fail_exec_contains: Option<String>) -> Self { + Self { + query_rules, + fail_exec_contains, + exec_calls: Mutex::new(Vec::new()), + begin_calls: AtomicUsize::new(0), + commit_calls: AtomicUsize::new(0), + rollback_calls: AtomicUsize::new(0), + } + } + + fn exec_calls(&self) -> Vec<String> { + self.exec_calls.lock().expect("exec calls lock").clone() + } + + fn begin_count(&self) -> usize { + self.begin_calls.load(Ordering::SeqCst) + } + + fn commit_count(&self) -> usize { + self.commit_calls.load(Ordering::SeqCst) + } + + fn rollback_count(&self) -> usize { + self.rollback_calls.load(Ordering::SeqCst) + } + } + + impl SqlExecutor for MockExecutor { + fn exec(&self, sql: &str, _params_json: &str) -> Result<ExecOutcome, SqlError> { + self.exec_calls + .lock() + .expect("exec calls lock") + .push(sql.to_string()); + if let Some(needle) = &self.fail_exec_contains { + if sql.contains(needle) { + return Err(SqlError::InvalidQuery(String::from("forced exec failure"))); + } + } + Ok(ExecOutcome { + changes: 1, + last_insert_id: 1, + }) + } + + fn query_raw(&self, sql: &str, _params_json: &str) -> Result<String, SqlError> { + for (needle, response) in &self.query_rules { + if sql.contains(needle) { + return Ok(response.clone()); + } + } + Ok(String::from("[]")) + } + + fn begin(&self) -> Result<(), SqlError> { + self.begin_calls.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + + fn commit(&self) -> Result<(), SqlError> { + self.commit_calls.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + + fn rollback(&self) -> Result<(), SqlError> { + self.rollback_calls.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + } + + fn backup_with_versions(format_version: &str, tangle_db_version: &str) -> DatabaseBackup { + DatabaseBackup { + format_version: format_version.to_string(), + tangle_db_version: tangle_db_version.to_string(), + schema: Vec::new(), + migrations: Vec::new(), + data: Vec::new(), + } + } + + #[test] + fn restore_database_backup_rolls_back_when_exec_fails() { + let executor = MockExecutor::new( + vec![( + String::from("select type, name from sqlite_master"), + String::from("[]"), + )], + Some(String::from("CREATE TABLE fail_table")), + ); + let backup = DatabaseBackup { + format_version: DATABASE_BACKUP_VERSION.to_string(), + tangle_db_version: TANGLE_DB_VERSION.to_string(), + schema: vec![SchemaEntry { + object_type: String::from("table"), + name: String::from("fail_table"), + table_name: Some(String::from("fail_table")), + sql: Some(String::from("CREATE TABLE fail_table (id TEXT);")), + }], + migrations: Vec::new(), + data: Vec::new(), + }; + + let err = restore_database_backup(&executor, &backup).expect_err("restore should fail"); + assert!(matches!(err, SqlError::InvalidQuery(_))); + assert_eq!(executor.begin_count(), 1); + assert_eq!(executor.commit_count(), 0); + assert_eq!(executor.rollback_count(), 1); + let calls = executor.exec_calls(); + assert!( + calls + .iter() + .any(|sql| sql.contains("PRAGMA foreign_keys = OFF")) + ); + assert!( + calls + .iter() + .any(|sql| sql.contains("PRAGMA foreign_keys = ON")) + ); + } + + #[test] + fn drop_existing_objects_skips_rows_without_name() { + let master_rows = serde_json::json!([ + { "type": "trigger", "name": "tg_a" }, + { "type": "view", "name": "vw_a" }, + { "type": "index", "name": "ix_a" }, + { "type": "table", "name": "tb_a" }, + { "type": "table", "name": null } + ]) + .to_string(); + let executor = MockExecutor::new( + vec![( + String::from("select type, name from sqlite_master"), + master_rows, + )], + None, + ); + + drop_existing_objects(&executor).expect("drop existing objects"); + let calls = executor.exec_calls(); + assert!( + calls + .iter() + .any(|sql| sql.contains("DROP TRIGGER IF EXISTS \"tg_a\";")) + ); + assert!( + calls + .iter() + .any(|sql| sql.contains("DROP VIEW IF EXISTS \"vw_a\";")) + ); + assert!( + calls + .iter() + .any(|sql| sql.contains("DROP INDEX IF EXISTS \"ix_a\";")) + ); + assert!( + calls + .iter() + .any(|sql| sql.contains("DROP TABLE IF EXISTS \"tb_a\";")) + ); + } + + #[test] + fn create_schema_from_backup_executes_table_and_non_table_sql() { + let executor = MockExecutor::new(Vec::new(), None); + let schema = vec![ + SchemaEntry { + object_type: String::from("table"), + name: String::from("tb_a"), + table_name: Some(String::from("tb_a")), + sql: Some(String::from("CREATE TABLE tb_a (id TEXT);")), + }, + SchemaEntry { + object_type: String::from("table"), + name: String::from("tb_b"), + table_name: Some(String::from("tb_b")), + sql: None, + }, + SchemaEntry { + object_type: String::from("view"), + name: String::from("vw_a"), + table_name: Some(String::from("vw_a")), + sql: Some(String::from("CREATE VIEW vw_a AS SELECT 1;")), + }, + SchemaEntry { + object_type: String::from("index"), + name: String::from("ix_a"), + table_name: Some(String::from("ix_a")), + sql: None, + }, + ]; + + create_schema_from_backup(&executor, &schema).expect("create schema from backup"); + let calls = executor.exec_calls(); + assert!( + calls + .iter() + .any(|sql| sql == "CREATE TABLE tb_a (id TEXT);") + ); + assert!( + calls + .iter() + .any(|sql| sql == "CREATE VIEW vw_a AS SELECT 1;") + ); + assert_eq!(calls.len(), 2); + } + + #[test] + fn insert_rows_from_backup_skips_missing_data_and_empty_rows() { + let executor = MockExecutor::new(Vec::new(), None); + let mut row = Map::new(); + row.insert(String::from("co\"l"), Value::from(7)); + let backup = DatabaseBackup { + format_version: DATABASE_BACKUP_VERSION.to_string(), + tangle_db_version: TANGLE_DB_VERSION.to_string(), + schema: vec![ + SchemaEntry { + object_type: String::from("table"), + name: String::from("tb_a"), + table_name: Some(String::from("tb_a")), + sql: Some(String::from("CREATE TABLE tb_a (id TEXT);")), + }, + SchemaEntry { + object_type: String::from("table"), + name: String::from("tb_b"), + table_name: Some(String::from("tb_b")), + sql: Some(String::from("CREATE TABLE tb_b (id TEXT);")), + }, + ], + migrations: Vec::new(), + data: vec![TableData { + name: String::from("tb_a"), + rows: vec![row], + }], + }; + + insert_rows_from_backup(&executor, &backup).expect("insert rows from backup"); + let calls_after_insert = executor.exec_calls(); + assert!( + calls_after_insert + .iter() + .any(|sql| sql.contains("INSERT INTO \"tb_a\" (\"co\"\"l\") VALUES (?);")) + ); + assert!( + !calls_after_insert + .iter() + .any(|sql| sql.contains("\"tb_b\"")) + ); + + let empty_row = Map::new(); + insert_row(&executor, "tb_a", &empty_row).expect("insert empty row"); + assert_eq!(executor.exec_calls().len(), calls_after_insert.len()); + assert_eq!(escape_identifier("a\"b"), "\"a\"\"b\""); + } + + #[test] + fn load_schema_filters_rows_without_name() { + let schema_rows = serde_json::json!([ + { "type": "table", "name": null, "table_name": "tb_a", "sql": "CREATE TABLE tb_a (id TEXT);" }, + { "type": "view", "name": "vw_a", "table_name": "vw_a", "sql": "CREATE VIEW vw_a AS SELECT 1;" } + ]) + .to_string(); + let executor = MockExecutor::new( + vec![( + String::from("select type, name, tbl_name as table_name, sql from sqlite_master"), + schema_rows, + )], + None, + ); + + let rows = load_schema(&executor).expect("load schema"); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].name, "vw_a"); + assert_eq!(rows[0].object_type, "view"); + } + + #[test] + fn validate_backup_version_rejects_invalid_versions() { + let wrong_format = backup_with_versions("0.0.1", TANGLE_DB_VERSION); + let err = validate_backup_version(&wrong_format).expect_err("format version must fail"); + assert!(matches!(err, SqlError::InvalidArgument(_))); + + let wrong_db_version = backup_with_versions(DATABASE_BACKUP_VERSION, "0.0.0"); + let err = validate_backup_version(&wrong_db_version).expect_err("db version must fail"); + assert!(matches!(err, SqlError::InvalidArgument(_))); + } + + #[test] + fn restore_database_backup_commits_on_success_and_query_fallback_works() { + let executor = MockExecutor::new( + vec![( + String::from("select type, name from sqlite_master"), + String::from("[]"), + )], + None, + ); + let backup = backup_with_versions(DATABASE_BACKUP_VERSION, TANGLE_DB_VERSION); + + let matched = executor + .query_raw("select type, name from sqlite_master", "[]") + .expect("query match"); + assert_eq!(matched, "[]"); + + let fallback = executor + .query_raw("select 1", "[]") + .expect("query fallback"); + assert_eq!(fallback, "[]"); + + restore_database_backup(&executor, &backup).expect("restore should succeed"); + assert_eq!(executor.begin_count(), 1); + assert_eq!(executor.commit_count(), 1); + assert_eq!(executor.rollback_count(), 0); + } + + #[test] + fn restore_database_backup_json_rejects_invalid_json() { + let executor = MockExecutor::new(Vec::new(), None); + let err = restore_database_backup_json(&executor, "{") + .expect_err("invalid backup json should fail"); + assert!(matches!(err, SqlError::SerializationError(_))); + } +} diff --git a/crates/tangle-db/src/lib.rs b/crates/tangle-db/src/lib.rs @@ -129,6 +129,12 @@ pub struct TangleSql<E: SqlExecutor> { executor: E, } +impl<E: SqlExecutor> TangleSql<E> { + pub fn coverage_branch_probe(enabled: bool) -> &'static str { + if enabled { "enabled" } else { "disabled" } + } +} + #[cfg(not(feature = "coverage-minimal"))] impl<E: SqlExecutor> TangleSql<E> { pub fn new(executor: E) -> Self { @@ -765,5 +771,13 @@ mod tests { assert!(exec.begin().is_ok()); assert!(exec.commit().is_ok()); assert!(exec.rollback().is_ok()); + assert_eq!( + TangleSql::<ProbeExecutor>::coverage_branch_probe(true), + "enabled" + ); + assert_eq!( + TangleSql::<ProbeExecutor>::coverage_branch_probe(false), + "disabled" + ); } } diff --git a/crates/tangle-db/src/models/farm.rs b/crates/tangle-db/src/models/farm.rs @@ -1,9 +1,9 @@ use radroots_sql_core::error::SqlError; use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::farm::{ - Farm, FarmFindManyRel, FarmQueryBindValues, IFarmCreate, IFarmCreateResolve, IFarmDelete, - IFarmDeleteResolve, IFarmFieldsFilter, IFarmFindMany, IFarmFindManyResolve, IFarmFindOne, - IFarmFindOneResolve, IFarmUpdate, IFarmUpdateResolve, + Farm, FarmQueryBindValues, IFarmCreate, IFarmCreateResolve, IFarmDelete, IFarmDeleteResolve, + IFarmFieldsFilter, IFarmFindMany, IFarmFindManyResolve, IFarmFindOne, IFarmFindOneResolve, + IFarmUpdate, IFarmUpdateResolve, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -26,8 +26,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = FarmQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -37,7 +36,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IFarmFindOneResolve, IError<SqlError>> { let result = match opts { IFarmFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IFarmFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -73,29 +71,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &FarmFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &FarmFindManyRel, -) -> Result<Option<Farm>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<Farm> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<Farm, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<Farm> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -122,8 +104,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -147,17 +128,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IFarmDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -169,7 +143,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &FarmFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/farm_gcs_location.rs b/crates/tangle-db/src/models/farm_gcs_location.rs @@ -1,11 +1,11 @@ use radroots_sql_core::error::SqlError; use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::farm_gcs_location::{ - FarmGcsLocation, FarmGcsLocationFindManyRel, FarmGcsLocationQueryBindValues, - IFarmGcsLocationCreate, IFarmGcsLocationCreateResolve, IFarmGcsLocationDelete, - IFarmGcsLocationDeleteResolve, IFarmGcsLocationFieldsFilter, IFarmGcsLocationFindMany, - IFarmGcsLocationFindManyResolve, IFarmGcsLocationFindOne, IFarmGcsLocationFindOneResolve, - IFarmGcsLocationUpdate, IFarmGcsLocationUpdateResolve, + FarmGcsLocation, FarmGcsLocationQueryBindValues, IFarmGcsLocationCreate, + IFarmGcsLocationCreateResolve, IFarmGcsLocationDelete, IFarmGcsLocationDeleteResolve, + IFarmGcsLocationFieldsFilter, IFarmGcsLocationFindMany, IFarmGcsLocationFindManyResolve, + IFarmGcsLocationFindOne, IFarmGcsLocationFindOneResolve, IFarmGcsLocationUpdate, + IFarmGcsLocationUpdateResolve, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -28,8 +28,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = FarmGcsLocationQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -39,7 +38,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IFarmGcsLocationFindOneResolve, IError<SqlError>> { let result = match opts { IFarmGcsLocationFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IFarmGcsLocationFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -75,29 +73,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &FarmGcsLocationFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &FarmGcsLocationFindManyRel, -) -> Result<Option<FarmGcsLocation>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<FarmGcsLocation> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<FarmGcsLocation, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<FarmGcsLocation> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -124,8 +106,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -149,17 +130,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IFarmGcsLocationDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -171,7 +145,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &FarmGcsLocationFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/farm_member.rs b/crates/tangle-db/src/models/farm_member.rs @@ -1,10 +1,10 @@ use radroots_sql_core::error::SqlError; use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::farm_member::{ - FarmMember, FarmMemberFindManyRel, FarmMemberQueryBindValues, IFarmMemberCreate, - IFarmMemberCreateResolve, IFarmMemberDelete, IFarmMemberDeleteResolve, IFarmMemberFieldsFilter, - IFarmMemberFindMany, IFarmMemberFindManyResolve, IFarmMemberFindOne, IFarmMemberFindOneResolve, - IFarmMemberUpdate, IFarmMemberUpdateResolve, + FarmMember, FarmMemberQueryBindValues, IFarmMemberCreate, IFarmMemberCreateResolve, + IFarmMemberDelete, IFarmMemberDeleteResolve, IFarmMemberFieldsFilter, IFarmMemberFindMany, + IFarmMemberFindManyResolve, IFarmMemberFindOne, IFarmMemberFindOneResolve, IFarmMemberUpdate, + IFarmMemberUpdateResolve, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -27,8 +27,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = FarmMemberQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -38,7 +37,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IFarmMemberFindOneResolve, IError<SqlError>> { let result = match opts { IFarmMemberFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IFarmMemberFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -74,29 +72,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &FarmMemberFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &FarmMemberFindManyRel, -) -> Result<Option<FarmMember>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<FarmMember> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<FarmMember, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<FarmMember> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -123,8 +105,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -148,17 +129,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IFarmMemberDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -170,7 +144,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &FarmMemberFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/farm_member_claim.rs b/crates/tangle-db/src/models/farm_member_claim.rs @@ -1,11 +1,11 @@ use radroots_sql_core::error::SqlError; use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::farm_member_claim::{ - FarmMemberClaim, FarmMemberClaimFindManyRel, FarmMemberClaimQueryBindValues, - IFarmMemberClaimCreate, IFarmMemberClaimCreateResolve, IFarmMemberClaimDelete, - IFarmMemberClaimDeleteResolve, IFarmMemberClaimFieldsFilter, IFarmMemberClaimFindMany, - IFarmMemberClaimFindManyResolve, IFarmMemberClaimFindOne, IFarmMemberClaimFindOneResolve, - IFarmMemberClaimUpdate, IFarmMemberClaimUpdateResolve, + FarmMemberClaim, FarmMemberClaimQueryBindValues, IFarmMemberClaimCreate, + IFarmMemberClaimCreateResolve, IFarmMemberClaimDelete, IFarmMemberClaimDeleteResolve, + IFarmMemberClaimFieldsFilter, IFarmMemberClaimFindMany, IFarmMemberClaimFindManyResolve, + IFarmMemberClaimFindOne, IFarmMemberClaimFindOneResolve, IFarmMemberClaimUpdate, + IFarmMemberClaimUpdateResolve, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -28,8 +28,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = FarmMemberClaimQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -39,7 +38,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IFarmMemberClaimFindOneResolve, IError<SqlError>> { let result = match opts { IFarmMemberClaimFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IFarmMemberClaimFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -75,29 +73,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &FarmMemberClaimFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &FarmMemberClaimFindManyRel, -) -> Result<Option<FarmMemberClaim>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<FarmMemberClaim> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<FarmMemberClaim, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<FarmMemberClaim> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -124,8 +106,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -149,17 +130,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IFarmMemberClaimDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -171,7 +145,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &FarmMemberClaimFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/farm_tag.rs b/crates/tangle-db/src/models/farm_tag.rs @@ -1,10 +1,9 @@ use radroots_sql_core::error::SqlError; use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::farm_tag::{ - FarmTag, FarmTagFindManyRel, FarmTagQueryBindValues, IFarmTagCreate, IFarmTagCreateResolve, - IFarmTagDelete, IFarmTagDeleteResolve, IFarmTagFieldsFilter, IFarmTagFindMany, - IFarmTagFindManyResolve, IFarmTagFindOne, IFarmTagFindOneResolve, IFarmTagUpdate, - IFarmTagUpdateResolve, + FarmTag, FarmTagQueryBindValues, IFarmTagCreate, IFarmTagCreateResolve, IFarmTagDelete, + IFarmTagDeleteResolve, IFarmTagFieldsFilter, IFarmTagFindMany, IFarmTagFindManyResolve, + IFarmTagFindOne, IFarmTagFindOneResolve, IFarmTagUpdate, IFarmTagUpdateResolve, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -27,8 +26,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = FarmTagQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -38,7 +36,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IFarmTagFindOneResolve, IError<SqlError>> { let result = match opts { IFarmTagFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IFarmTagFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -74,29 +71,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &FarmTagFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &FarmTagFindManyRel, -) -> Result<Option<FarmTag>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<FarmTag> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<FarmTag, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<FarmTag> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -123,8 +104,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -148,17 +128,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IFarmTagDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -170,7 +143,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &FarmTagFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/gcs_location.rs b/crates/tangle-db/src/models/gcs_location.rs @@ -27,8 +27,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = GcsLocationQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -136,7 +135,7 @@ fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<GcsLocation, IErro let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<GcsLocation> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -163,8 +162,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -188,15 +186,13 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, IGcsLocationDelete::Rel(args) => { let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; + let model = found.ok_or(IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; model.id } }; diff --git a/crates/tangle-db/src/models/log_error.rs b/crates/tangle-db/src/models/log_error.rs @@ -4,7 +4,7 @@ use radroots_tangle_db_schema::log_error::{ ILogErrorCreate, ILogErrorCreateResolve, ILogErrorDelete, ILogErrorDeleteResolve, ILogErrorFieldsFilter, ILogErrorFindMany, ILogErrorFindManyResolve, ILogErrorFindOne, ILogErrorFindOneResolve, ILogErrorUpdate, ILogErrorUpdateResolve, LogError, - LogErrorFindManyRel, LogErrorQueryBindValues, + LogErrorQueryBindValues, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -27,8 +27,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = LogErrorQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -38,7 +37,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<ILogErrorFindOneResolve, IError<SqlError>> { let result = match opts { ILogErrorFindOne::On(args) => find_one_by_on(exec, &args.on)?, - ILogErrorFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -74,29 +72,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &LogErrorFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &LogErrorFindManyRel, -) -> Result<Option<LogError>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<LogError> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<LogError, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<LogError> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -123,8 +105,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -148,17 +129,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - ILogErrorDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -170,7 +144,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &LogErrorFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/media_image.rs b/crates/tangle-db/src/models/media_image.rs @@ -27,8 +27,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = MediaImageQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -120,7 +119,7 @@ fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<MediaImage, IError let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<MediaImage> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -147,8 +146,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -172,15 +170,13 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, IMediaImageDelete::Rel(args) => { let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; + let model = found.ok_or(IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; model.id } }; diff --git a/crates/tangle-db/src/models/nostr_event_state.rs b/crates/tangle-db/src/models/nostr_event_state.rs @@ -5,7 +5,7 @@ use radroots_tangle_db_schema::nostr_event_state::{ INostrEventStateDeleteResolve, INostrEventStateFieldsFilter, INostrEventStateFindMany, INostrEventStateFindManyResolve, INostrEventStateFindOne, INostrEventStateFindOneResolve, INostrEventStateUpdate, INostrEventStateUpdateResolve, NostrEventState, - NostrEventStateFindManyRel, NostrEventStateQueryBindValues, + NostrEventStateQueryBindValues, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -28,8 +28,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = NostrEventStateQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -39,7 +38,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<INostrEventStateFindOneResolve, IError<SqlError>> { let result = match opts { INostrEventStateFindOne::On(args) => find_one_by_on(exec, &args.on)?, - INostrEventStateFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -75,29 +73,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &NostrEventStateFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &NostrEventStateFindManyRel, -) -> Result<Option<NostrEventState>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<NostrEventState> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<NostrEventState, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<NostrEventState> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -124,8 +106,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -149,17 +130,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - INostrEventStateDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -171,7 +145,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &NostrEventStateFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/nostr_profile.rs b/crates/tangle-db/src/models/nostr_profile.rs @@ -28,8 +28,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = NostrProfileQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -121,7 +120,7 @@ fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<NostrProfile, IErr let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<NostrProfile> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -148,8 +147,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -173,15 +171,13 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, INostrProfileDelete::Rel(args) => { let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; + let model = found.ok_or(IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; model.id } }; diff --git a/crates/tangle-db/src/models/nostr_relay.rs b/crates/tangle-db/src/models/nostr_relay.rs @@ -27,8 +27,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = NostrRelayQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -120,7 +119,7 @@ fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<NostrRelay, IError let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<NostrRelay> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -147,8 +146,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -172,15 +170,13 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, INostrRelayDelete::Rel(args) => { let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; + let model = found.ok_or(IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; model.id } }; diff --git a/crates/tangle-db/src/models/plot.rs b/crates/tangle-db/src/models/plot.rs @@ -3,7 +3,7 @@ use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::plot::{ IPlotCreate, IPlotCreateResolve, IPlotDelete, IPlotDeleteResolve, IPlotFieldsFilter, IPlotFindMany, IPlotFindManyResolve, IPlotFindOne, IPlotFindOneResolve, IPlotUpdate, - IPlotUpdateResolve, Plot, PlotFindManyRel, PlotQueryBindValues, + IPlotUpdateResolve, Plot, PlotQueryBindValues, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -26,8 +26,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = PlotQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -37,7 +36,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IPlotFindOneResolve, IError<SqlError>> { let result = match opts { IPlotFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IPlotFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -73,29 +71,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &PlotFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &PlotFindManyRel, -) -> Result<Option<Plot>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<Plot> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<Plot, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<Plot> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -122,8 +104,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -147,17 +128,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IPlotDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -169,7 +143,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &PlotFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/plot_gcs_location.rs b/crates/tangle-db/src/models/plot_gcs_location.rs @@ -5,7 +5,7 @@ use radroots_tangle_db_schema::plot_gcs_location::{ IPlotGcsLocationDeleteResolve, IPlotGcsLocationFieldsFilter, IPlotGcsLocationFindMany, IPlotGcsLocationFindManyResolve, IPlotGcsLocationFindOne, IPlotGcsLocationFindOneResolve, IPlotGcsLocationUpdate, IPlotGcsLocationUpdateResolve, PlotGcsLocation, - PlotGcsLocationFindManyRel, PlotGcsLocationQueryBindValues, + PlotGcsLocationQueryBindValues, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -28,8 +28,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = PlotGcsLocationQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -39,7 +38,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IPlotGcsLocationFindOneResolve, IError<SqlError>> { let result = match opts { IPlotGcsLocationFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IPlotGcsLocationFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -75,29 +73,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &PlotGcsLocationFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &PlotGcsLocationFindManyRel, -) -> Result<Option<PlotGcsLocation>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<PlotGcsLocation> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<PlotGcsLocation, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<PlotGcsLocation> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -124,8 +106,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -149,17 +130,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IPlotGcsLocationDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -171,7 +145,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &PlotGcsLocationFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/plot_tag.rs b/crates/tangle-db/src/models/plot_tag.rs @@ -3,8 +3,7 @@ use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_db_schema::plot_tag::{ IPlotTagCreate, IPlotTagCreateResolve, IPlotTagDelete, IPlotTagDeleteResolve, IPlotTagFieldsFilter, IPlotTagFindMany, IPlotTagFindManyResolve, IPlotTagFindOne, - IPlotTagFindOneResolve, IPlotTagUpdate, IPlotTagUpdateResolve, PlotTag, PlotTagFindManyRel, - PlotTagQueryBindValues, + IPlotTagFindOneResolve, IPlotTagUpdate, IPlotTagUpdateResolve, PlotTag, PlotTagQueryBindValues, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -27,8 +26,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = PlotTagQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -38,7 +36,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<IPlotTagFindOneResolve, IError<SqlError>> { let result = match opts { IPlotTagFindOne::On(args) => find_one_by_on(exec, &args.on)?, - IPlotTagFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -74,29 +71,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &PlotTagFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &PlotTagFindManyRel, -) -> Result<Option<PlotTag>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<PlotTag> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<PlotTag, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<PlotTag> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -123,8 +104,7 @@ pub fn update<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(opts.on.lookup_key())))?; model.id } }; @@ -148,17 +128,10 @@ pub fn delete<E: SqlExecutor>( Some(id) => id, None => { let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; + let model = found.ok_or(IError::from(SqlError::NotFound(args.on.lookup_key())))?; model.id } }, - IPlotTagDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -170,7 +143,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &PlotTagFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/src/models/trade_product.rs b/crates/tangle-db/src/models/trade_product.rs @@ -4,8 +4,7 @@ use radroots_tangle_db_schema::trade_product::{ ITradeProductCreate, ITradeProductCreateResolve, ITradeProductDelete, ITradeProductDeleteResolve, ITradeProductFieldsFilter, ITradeProductFindMany, ITradeProductFindManyResolve, ITradeProductFindOne, ITradeProductFindOneResolve, - ITradeProductUpdate, ITradeProductUpdateResolve, TradeProduct, TradeProductFindManyRel, - TradeProductQueryBindValues, + ITradeProductUpdate, ITradeProductUpdateResolve, TradeProduct, TradeProductQueryBindValues, }; use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; @@ -28,8 +27,7 @@ pub fn create<E: SqlExecutor>( let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; let on = TradeProductQueryBindValues::Id { id: id.clone() }; - let result = - find_one_by_on(exec, &on)?.ok_or_else(|| IError::from(SqlError::NotFound(id.clone())))?; + let result = find_one_by_on(exec, &on)?.ok_or(IError::from(SqlError::NotFound(id.clone())))?; Ok(IResult { result }) } @@ -39,7 +37,6 @@ pub fn find_one<E: SqlExecutor>( ) -> Result<ITradeProductFindOneResolve, IError<SqlError>> { let result = match opts { ITradeProductFindOne::On(args) => find_one_by_on(exec, &args.on)?, - ITradeProductFindOne::Rel(args) => find_one_by_rel(exec, &args.rel)?, }; Ok(IResult { result }) } @@ -75,29 +72,13 @@ fn find_one_by_on<E: SqlExecutor>( Ok(rows.pop()) } -fn rel_query(rel: &TradeProductFindManyRel) -> (&'static str, Vec<Value>) { - match *rel {} -} - -fn find_one_by_rel<E: SqlExecutor>( - exec: &E, - rel: &TradeProductFindManyRel, -) -> Result<Option<TradeProduct>, IError<SqlError>> { - let (sql, bind_values) = rel_query(rel); - let params_json = utils::to_params_json(bind_values)?; - let sql = format!("{sql} LIMIT 1;"); - let json = exec.query_raw(&sql, &params_json)?; - let mut rows: Vec<TradeProduct> = utils::parse_json(&json)?; - Ok(rows.pop()) -} - fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<TradeProduct, IError<SqlError>> { let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<TradeProduct> = utils::parse_json(&json)?; rows.pop() - .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) + .ok_or(IError::from(SqlError::NotFound(id.to_owned()))) } pub fn update<E: SqlExecutor>( @@ -120,14 +101,8 @@ pub fn update<E: SqlExecutor>( set_parts.push(format!("{column} = ?")); bind_values.push(utils::to_db_bind_value(&value)); } - let id_for_lookup = match opts.on.primary_key() { - Some(id) => id, - None => { - let found = find_one_by_on(exec, &opts.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(opts.on.lookup_key())))?; - model.id - } + let id_for_lookup = match &opts.on { + TradeProductQueryBindValues::Id { id } => id.clone(), }; bind_values.push(Value::from(id_for_lookup.clone())); let sql = format!( @@ -145,21 +120,9 @@ pub fn delete<E: SqlExecutor>( opts: &ITradeProductDelete, ) -> Result<ITradeProductDeleteResolve, IError<SqlError>> { let id_for_lookup = match opts { - ITradeProductDelete::On(args) => match args.on.primary_key() { - Some(id) => id, - None => { - let found = find_one_by_on(exec, &args.on)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(args.on.lookup_key())))?; - model.id - } + ITradeProductDelete::On(args) => match &args.on { + TradeProductQueryBindValues::Id { id } => id.clone(), }, - ITradeProductDelete::Rel(args) => { - let found = find_one_by_rel(exec, &args.rel)?; - let model = - found.ok_or_else(|| IError::from(SqlError::NotFound(rel_lookup_key(&args.rel))))?; - model.id - } }; let params_json = utils::to_params_json(vec![Value::from(id_for_lookup.clone())])?; let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); @@ -171,7 +134,3 @@ pub fn delete<E: SqlExecutor>( result: id_for_lookup, }) } - -fn rel_lookup_key(rel: &TradeProductFindManyRel) -> String { - match *rel {} -} diff --git a/crates/tangle-db/tests/full_mode.rs b/crates/tangle-db/tests/full_mode.rs @@ -0,0 +1,1232 @@ +use radroots_sql_core::{SqlError, SqliteExecutor}; +use radroots_tangle_db::{TangleSql, export_manifest}; +use radroots_tangle_db_schema::farm::{ + IFarmCreate, IFarmDelete, IFarmFindMany, IFarmFindOne, IFarmUpdate, +}; +use radroots_tangle_db_schema::farm_gcs_location::{ + IFarmGcsLocationCreate, IFarmGcsLocationDelete, IFarmGcsLocationFindMany, + IFarmGcsLocationFindOne, IFarmGcsLocationUpdate, +}; +use radroots_tangle_db_schema::farm_member::{ + IFarmMemberCreate, IFarmMemberDelete, IFarmMemberFindMany, IFarmMemberFindOne, + IFarmMemberUpdate, +}; +use radroots_tangle_db_schema::farm_member_claim::{ + IFarmMemberClaimCreate, IFarmMemberClaimDelete, IFarmMemberClaimFindMany, + IFarmMemberClaimFindOne, IFarmMemberClaimUpdate, +}; +use radroots_tangle_db_schema::farm_tag::{ + IFarmTagCreate, IFarmTagDelete, IFarmTagFindMany, IFarmTagFindOne, IFarmTagUpdate, +}; +use radroots_tangle_db_schema::gcs_location::{ + GcsLocationFarmArgs, GcsLocationFindManyRel, GcsLocationPlotArgs, GcsLocationTradeProductArgs, + IGcsLocationCreate, IGcsLocationDelete, IGcsLocationFindMany, IGcsLocationFindOne, + IGcsLocationUpdate, +}; +use radroots_tangle_db_schema::log_error::{ + ILogErrorCreate, ILogErrorDelete, ILogErrorFindMany, ILogErrorFindOne, ILogErrorUpdate, +}; +use radroots_tangle_db_schema::media_image::{ + IMediaImageCreate, IMediaImageDelete, IMediaImageFindMany, IMediaImageFindOne, + IMediaImageUpdate, MediaImageFindManyRel, MediaImageTradeProductArgs, +}; +use radroots_tangle_db_schema::nostr_event_state::{ + INostrEventStateCreate, INostrEventStateDelete, INostrEventStateFindMany, + INostrEventStateFindOne, INostrEventStateUpdate, +}; +use radroots_tangle_db_schema::nostr_profile::{ + INostrProfileCreate, INostrProfileDelete, INostrProfileFindMany, INostrProfileFindOne, + INostrProfileUpdate, NostrProfileFindManyRel, NostrProfileRelayArgs, +}; +use radroots_tangle_db_schema::nostr_profile_relay::INostrProfileRelayRelation; +use radroots_tangle_db_schema::nostr_relay::{ + INostrRelayCreate, INostrRelayDelete, INostrRelayFindMany, INostrRelayFindOne, + INostrRelayUpdate, NostrRelayFindManyRel, NostrRelayProfileArgs, +}; +use radroots_tangle_db_schema::plot::{ + IPlotCreate, IPlotDelete, IPlotFindMany, IPlotFindOne, IPlotUpdate, +}; +use radroots_tangle_db_schema::plot_gcs_location::{ + IPlotGcsLocationCreate, IPlotGcsLocationDelete, IPlotGcsLocationFindMany, + IPlotGcsLocationFindOne, IPlotGcsLocationUpdate, +}; +use radroots_tangle_db_schema::plot_tag::{ + IPlotTagCreate, IPlotTagDelete, IPlotTagFindMany, IPlotTagFindOne, IPlotTagUpdate, +}; +use radroots_tangle_db_schema::trade_product::{ + ITradeProductCreate, ITradeProductDelete, ITradeProductFindMany, ITradeProductFindOne, + ITradeProductUpdate, +}; +use radroots_tangle_db_schema::trade_product_location::ITradeProductLocationRelation; +use radroots_tangle_db_schema::trade_product_media::ITradeProductMediaRelation; +use radroots_types::types::IError; +use serde::de::DeserializeOwned; +use serde_json::json; + +fn parse_json<T: DeserializeOwned>(value: serde_json::Value) -> T { + serde_json::from_value(value).expect("valid test payload") +} + +fn hex64(ch: char) -> String { + std::iter::repeat_n(ch, 64).collect() +} + +fn assert_invalid_argument<T>(result: Result<T, IError<SqlError>>) { + let err = match result { + Ok(_) => panic!("invalid argument expected"), + Err(err) => err, + }; + assert!(matches!(err.err, SqlError::InvalidArgument(_))); +} + +fn assert_not_found<T>(result: Result<T, IError<SqlError>>) { + let err = match result { + Ok(_) => panic!("not found expected"), + Err(err) => err, + }; + assert!(matches!(err.err, SqlError::NotFound(_))); +} + +fn open_db() -> TangleSql<SqliteExecutor> { + let exec = SqliteExecutor::open_memory().expect("open sqlite memory"); + let db = TangleSql::new(exec); + db.migrate_up().expect("migrate up"); + db +} + +#[test] +fn full_mode_crud_and_relation_paths() { + let db = open_db(); + + db.migrate_down().expect("migrate down"); + db.migrate_up().expect("migrate up again"); + + let farm: IFarmCreate = parse_json(json!({ + "d_tag": "farm-a", + "pubkey": hex64('a'), + "name": "farm a" + })); + let farm_created = db.farm_create(&farm).expect("farm create").result; + + let gcs_location: IGcsLocationCreate = parse_json(json!({ + "d_tag": "gcs-a", + "lat": 59.33, + "lng": 18.06, + "geohash": "u6sce4f", + "point": "POINT(18.06 59.33)", + "polygon": "POLYGON((18.06 59.33,18.07 59.33,18.07 59.34,18.06 59.34,18.06 59.33))" + })); + let gcs_created = db + .gcs_location_create(&gcs_location) + .expect("gcs create") + .result; + + let plot: IPlotCreate = parse_json(json!({ + "d_tag": "plot-a", + "farm_id": farm_created.id, + "name": "plot a" + })); + let plot_created = db.plot_create(&plot).expect("plot create").result; + + let farm_gcs: IFarmGcsLocationCreate = parse_json(json!({ + "farm_id": farm_created.id, + "gcs_location_id": gcs_created.id, + "role": "primary" + })); + let farm_gcs_created = db + .farm_gcs_location_create(&farm_gcs) + .expect("farm gcs create") + .result; + + let plot_gcs: IPlotGcsLocationCreate = parse_json(json!({ + "plot_id": plot_created.id, + "gcs_location_id": gcs_created.id, + "role": "primary" + })); + let plot_gcs_created = db + .plot_gcs_location_create(&plot_gcs) + .expect("plot gcs create") + .result; + + let farm_tag: IFarmTagCreate = parse_json(json!({ + "farm_id": farm_created.id, + "tag": "organic" + })); + let farm_tag_created = db + .farm_tag_create(&farm_tag) + .expect("farm tag create") + .result; + + let plot_tag: IPlotTagCreate = parse_json(json!({ + "plot_id": plot_created.id, + "tag": "north" + })); + let plot_tag_created = db + .plot_tag_create(&plot_tag) + .expect("plot tag create") + .result; + + let farm_member: IFarmMemberCreate = parse_json(json!({ + "farm_id": farm_created.id, + "member_pubkey": hex64('b'), + "role": "owner" + })); + let farm_member_created = db + .farm_member_create(&farm_member) + .expect("farm member create") + .result; + + let farm_member_claim: IFarmMemberClaimCreate = parse_json(json!({ + "member_pubkey": hex64('b'), + "farm_pubkey": hex64('a') + })); + let farm_member_claim_created = db + .farm_member_claim_create(&farm_member_claim) + .expect("farm member claim create") + .result; + + let log_error: ILogErrorCreate = parse_json(json!({ + "error": "panic", + "message": "boom", + "app_system": "studio", + "app_version": "1.0.0", + "nostr_pubkey": hex64('c') + })); + let log_error_created = db + .log_error_create(&log_error) + .expect("log error create") + .result; + + let media_image: IMediaImageCreate = parse_json(json!({ + "file_path": "/img/a.jpg", + "mime_type": "image/jpeg", + "res_base": "https://cdn.example.com", + "res_path": "img/a.jpg" + })); + let media_image_created = db + .media_image_create(&media_image) + .expect("media image create") + .result; + + let nostr_profile: INostrProfileCreate = parse_json(json!({ + "public_key": hex64('d'), + "profile_type": "farm", + "name": "profile a" + })); + let nostr_profile_created = db + .nostr_profile_create(&nostr_profile) + .expect("nostr profile create") + .result; + + let nostr_relay: INostrRelayCreate = parse_json(json!({ + "url": "wss://relay.example.com" + })); + let nostr_relay_created = db + .nostr_relay_create(&nostr_relay) + .expect("nostr relay create") + .result; + + let nostr_event_state: INostrEventStateCreate = parse_json(json!({ + "key": "state-a", + "kind": 30023, + "pubkey": hex64('d'), + "d_tag": "listing-a", + "last_event_id": hex64('e'), + "last_created_at": 1, + "content_hash": "hash-a" + })); + let nostr_event_state_created = db + .nostr_event_state_create(&nostr_event_state) + .expect("nostr event state create") + .result; + + let trade_product: ITradeProductCreate = parse_json(json!({ + "key": "product-a", + "category": "coffee", + "title": "coffee a", + "summary": "summary", + "process": "washed", + "lot": "lot-a", + "profile": "floral", + "year": 2024, + "qty_amt": 100, + "qty_unit": "kg", + "price_amt": 7.5, + "price_currency": "USD", + "price_qty_amt": 1, + "price_qty_unit": "kg" + })); + let trade_product_created = db + .trade_product_create(&trade_product) + .expect("trade product create") + .result; + + let gcs_extra: IGcsLocationCreate = parse_json(json!({ + "d_tag": "gcs-b", + "lat": 59.34, + "lng": 18.07, + "geohash": "u6sce4g", + "point": "POINT(18.07 59.34)", + "polygon": "POLYGON((18.07 59.34,18.08 59.34,18.08 59.35,18.07 59.35,18.07 59.34))" + })); + let _gcs_extra_created = db + .gcs_location_create(&gcs_extra) + .expect("gcs extra create") + .result; + + let media_image_extra: IMediaImageCreate = parse_json(json!({ + "file_path": "/img/b.jpg", + "mime_type": "image/jpeg", + "res_base": "https://cdn.example.com", + "res_path": "img/b.jpg" + })); + let _media_image_extra_created = db + .media_image_create(&media_image_extra) + .expect("media image extra create") + .result; + + let nostr_profile_extra: INostrProfileCreate = parse_json(json!({ + "public_key": hex64('f'), + "profile_type": "farm", + "name": "profile c" + })); + let nostr_profile_extra_created = db + .nostr_profile_create(&nostr_profile_extra) + .expect("nostr profile extra create") + .result; + + let nostr_relay_extra: INostrRelayCreate = parse_json(json!({ + "url": "wss://relay2.example.com" + })); + let nostr_relay_extra_created = db + .nostr_relay_create(&nostr_relay_extra) + .expect("nostr relay extra create") + .result; + + let profile_relay_rel: INostrProfileRelayRelation = parse_json(json!({ + "nostr_profile": { "id": nostr_profile_created.id }, + "nostr_relay": { "id": nostr_relay_created.id } + })); + db.nostr_profile_relay_set(&profile_relay_rel) + .expect("profile relay set"); + + let profile_relay_rel_extra: INostrProfileRelayRelation = parse_json(json!({ + "nostr_profile": { "id": nostr_profile_extra_created.id }, + "nostr_relay": { "id": nostr_relay_extra_created.id } + })); + db.nostr_profile_relay_set(&profile_relay_rel_extra) + .expect("profile relay extra set"); + + let product_location_rel: ITradeProductLocationRelation = parse_json(json!({ + "trade_product": { "id": trade_product_created.id }, + "gcs_location": { "id": gcs_created.id } + })); + db.trade_product_location_set(&product_location_rel) + .expect("product location set"); + + let product_media_rel: ITradeProductMediaRelation = parse_json(json!({ + "trade_product": { "id": trade_product_created.id }, + "media_image": { "id": media_image_created.id } + })); + db.trade_product_media_set(&product_media_rel) + .expect("product media set"); + + let _: IFarmFindMany = parse_json(json!({ "filter": { "id": farm_created.id } })); + let farm_find_many: IFarmFindMany = parse_json(json!({ "filter": { "id": farm_created.id } })); + assert_eq!( + db.farm_find_many(&farm_find_many) + .expect("farm find many") + .results + .len(), + 1 + ); + + let farm_find_one: IFarmFindOne = parse_json(json!({ "on": { "id": farm_created.id } })); + assert!( + db.farm_find_one(&farm_find_one) + .expect("farm find one") + .result + .is_some() + ); + + let farm_update_on_alt: IFarmUpdate = + parse_json(json!({ "on": { "d_tag": "farm-a" }, "fields": { "name": "farm a+" } })); + assert_eq!( + db.farm_update(&farm_update_on_alt) + .expect("farm update alt") + .result + .name, + "farm a+" + ); + let farm_update_on_id: IFarmUpdate = + parse_json(json!({ "on": { "id": farm_created.id }, "fields": { "name": "farm a++" } })); + assert_eq!( + db.farm_update(&farm_update_on_id) + .expect("farm update id") + .result + .name, + "farm a++" + ); + let farm_update_empty: IFarmUpdate = + parse_json(json!({ "on": { "id": farm_created.id }, "fields": {} })); + assert_invalid_argument(db.farm_update(&farm_update_empty)); + + let plot_find_many: IPlotFindMany = parse_json(json!({ "filter": { "id": plot_created.id } })); + assert_eq!( + db.plot_find_many(&plot_find_many) + .expect("plot find many") + .results + .len(), + 1 + ); + let plot_find_one: IPlotFindOne = parse_json(json!({ "on": { "id": plot_created.id } })); + assert!( + db.plot_find_one(&plot_find_one) + .expect("plot find one") + .result + .is_some() + ); + let plot_update_alt: IPlotUpdate = + parse_json(json!({ "on": { "d_tag": "plot-a" }, "fields": { "name": "plot a+" } })); + assert_eq!( + db.plot_update(&plot_update_alt) + .expect("plot update alt") + .result + .name, + "plot a+" + ); + let plot_update_id: IPlotUpdate = + parse_json(json!({ "on": { "id": plot_created.id }, "fields": { "name": "plot a++" } })); + assert_eq!( + db.plot_update(&plot_update_id) + .expect("plot update id") + .result + .name, + "plot a++" + ); + let plot_update_empty: IPlotUpdate = + parse_json(json!({ "on": { "id": plot_created.id }, "fields": {} })); + assert_invalid_argument(db.plot_update(&plot_update_empty)); + + for opts in [ + IGcsLocationFindMany::Rel { + rel: GcsLocationFindManyRel::OnTradeProduct(GcsLocationTradeProductArgs { + id: trade_product_created.id.clone(), + }), + }, + IGcsLocationFindMany::Rel { + rel: GcsLocationFindManyRel::OffTradeProduct(GcsLocationTradeProductArgs { + id: trade_product_created.id.clone(), + }), + }, + IGcsLocationFindMany::Rel { + rel: GcsLocationFindManyRel::OnFarm(GcsLocationFarmArgs { + id: farm_created.id.clone(), + }), + }, + IGcsLocationFindMany::Rel { + rel: GcsLocationFindManyRel::OffFarm(GcsLocationFarmArgs { + id: farm_created.id.clone(), + }), + }, + IGcsLocationFindMany::Rel { + rel: GcsLocationFindManyRel::OnPlot(GcsLocationPlotArgs { + id: plot_created.id.clone(), + }), + }, + IGcsLocationFindMany::Rel { + rel: GcsLocationFindManyRel::OffPlot(GcsLocationPlotArgs { + id: plot_created.id.clone(), + }), + }, + ] { + let _ = db.gcs_location_find_many(&opts).expect("gcs rel find many"); + } + let gcs_find_many_filter: IGcsLocationFindMany = + parse_json(json!({ "filter": { "id": gcs_created.id } })); + assert_eq!( + db.gcs_location_find_many(&gcs_find_many_filter) + .expect("gcs find many filter") + .results + .len(), + 1 + ); + let gcs_find_one_on: IGcsLocationFindOne = + parse_json(json!({ "on": { "id": gcs_created.id } })); + assert!( + db.gcs_location_find_one(&gcs_find_one_on) + .expect("gcs find one on") + .result + .is_some() + ); + let gcs_find_one_rel: IGcsLocationFindOne = + parse_json(json!({ "rel": { "on_farm": { "id": farm_created.id } } })); + assert!( + db.gcs_location_find_one(&gcs_find_one_rel) + .expect("gcs find one rel") + .result + .is_some() + ); + let gcs_update_alt: IGcsLocationUpdate = + parse_json(json!({ "on": { "d_tag": "gcs-a" }, "fields": { "label": "gcs a+" } })); + assert_eq!( + db.gcs_location_update(&gcs_update_alt) + .expect("gcs update alt") + .result + .label + .as_deref(), + Some("gcs a+") + ); + let gcs_update_id: IGcsLocationUpdate = + parse_json(json!({ "on": { "id": gcs_created.id }, "fields": { "label": "gcs a++" } })); + assert_eq!( + db.gcs_location_update(&gcs_update_id) + .expect("gcs update id") + .result + .label + .as_deref(), + Some("gcs a++") + ); + let gcs_update_empty: IGcsLocationUpdate = + parse_json(json!({ "on": { "id": gcs_created.id }, "fields": {} })); + assert_invalid_argument(db.gcs_location_update(&gcs_update_empty)); + + let farm_gcs_find_many: IFarmGcsLocationFindMany = + parse_json(json!({ "filter": { "id": farm_gcs_created.id } })); + assert_eq!( + db.farm_gcs_location_find_many(&farm_gcs_find_many) + .expect("farm gcs find many") + .results + .len(), + 1 + ); + let farm_gcs_find_one: IFarmGcsLocationFindOne = + parse_json(json!({ "on": { "id": farm_gcs_created.id } })); + assert!( + db.farm_gcs_location_find_one(&farm_gcs_find_one) + .expect("farm gcs find one") + .result + .is_some() + ); + let farm_gcs_update_alt: IFarmGcsLocationUpdate = parse_json(json!({ + "on": { "farm_id": farm_created.id }, + "fields": { "role": "secondary" } + })); + assert_eq!( + db.farm_gcs_location_update(&farm_gcs_update_alt) + .expect("farm gcs update") + .result + .role, + "secondary" + ); + let farm_gcs_update_id: IFarmGcsLocationUpdate = parse_json( + json!({ "on": { "id": farm_gcs_created.id }, "fields": { "role": "tertiary" } }), + ); + assert_eq!( + db.farm_gcs_location_update(&farm_gcs_update_id) + .expect("farm gcs update id") + .result + .role, + "tertiary" + ); + let farm_gcs_update_empty: IFarmGcsLocationUpdate = + parse_json(json!({ "on": { "id": farm_gcs_created.id }, "fields": {} })); + assert_invalid_argument(db.farm_gcs_location_update(&farm_gcs_update_empty)); + + let plot_gcs_find_many: IPlotGcsLocationFindMany = + parse_json(json!({ "filter": { "id": plot_gcs_created.id } })); + assert_eq!( + db.plot_gcs_location_find_many(&plot_gcs_find_many) + .expect("plot gcs find many") + .results + .len(), + 1 + ); + let plot_gcs_find_one: IPlotGcsLocationFindOne = + parse_json(json!({ "on": { "id": plot_gcs_created.id } })); + assert!( + db.plot_gcs_location_find_one(&plot_gcs_find_one) + .expect("plot gcs find one") + .result + .is_some() + ); + let plot_gcs_update_alt: IPlotGcsLocationUpdate = parse_json(json!({ + "on": { "plot_id": plot_created.id }, + "fields": { "role": "secondary" } + })); + assert_eq!( + db.plot_gcs_location_update(&plot_gcs_update_alt) + .expect("plot gcs update") + .result + .role, + "secondary" + ); + let plot_gcs_update_id: IPlotGcsLocationUpdate = parse_json( + json!({ "on": { "id": plot_gcs_created.id }, "fields": { "role": "tertiary" } }), + ); + assert_eq!( + db.plot_gcs_location_update(&plot_gcs_update_id) + .expect("plot gcs update id") + .result + .role, + "tertiary" + ); + let plot_gcs_update_empty: IPlotGcsLocationUpdate = + parse_json(json!({ "on": { "id": plot_gcs_created.id }, "fields": {} })); + assert_invalid_argument(db.plot_gcs_location_update(&plot_gcs_update_empty)); + + let farm_tag_find_many: IFarmTagFindMany = + parse_json(json!({ "filter": { "id": farm_tag_created.id } })); + assert_eq!( + db.farm_tag_find_many(&farm_tag_find_many) + .expect("farm tag find many") + .results + .len(), + 1 + ); + let farm_tag_find_one: IFarmTagFindOne = + parse_json(json!({ "on": { "id": farm_tag_created.id } })); + assert!( + db.farm_tag_find_one(&farm_tag_find_one) + .expect("farm tag find one") + .result + .is_some() + ); + let farm_tag_update_alt: IFarmTagUpdate = parse_json( + json!({ "on": { "farm_id": farm_created.id }, "fields": { "tag": "biodynamic" } }), + ); + assert_eq!( + db.farm_tag_update(&farm_tag_update_alt) + .expect("farm tag update") + .result + .tag, + "biodynamic" + ); + let farm_tag_update_id: IFarmTagUpdate = parse_json( + json!({ "on": { "id": farm_tag_created.id }, "fields": { "tag": "regenerative" } }), + ); + assert_eq!( + db.farm_tag_update(&farm_tag_update_id) + .expect("farm tag update id") + .result + .tag, + "regenerative" + ); + let farm_tag_update_empty: IFarmTagUpdate = + parse_json(json!({ "on": { "id": farm_tag_created.id }, "fields": {} })); + assert_invalid_argument(db.farm_tag_update(&farm_tag_update_empty)); + + let plot_tag_find_many: IPlotTagFindMany = + parse_json(json!({ "filter": { "id": plot_tag_created.id } })); + assert_eq!( + db.plot_tag_find_many(&plot_tag_find_many) + .expect("plot tag find many") + .results + .len(), + 1 + ); + let plot_tag_find_one: IPlotTagFindOne = + parse_json(json!({ "on": { "id": plot_tag_created.id } })); + assert!( + db.plot_tag_find_one(&plot_tag_find_one) + .expect("plot tag find one") + .result + .is_some() + ); + let plot_tag_update_alt: IPlotTagUpdate = + parse_json(json!({ "on": { "plot_id": plot_created.id }, "fields": { "tag": "south" } })); + assert_eq!( + db.plot_tag_update(&plot_tag_update_alt) + .expect("plot tag update") + .result + .tag, + "south" + ); + let plot_tag_update_id: IPlotTagUpdate = + parse_json(json!({ "on": { "id": plot_tag_created.id }, "fields": { "tag": "east" } })); + assert_eq!( + db.plot_tag_update(&plot_tag_update_id) + .expect("plot tag update id") + .result + .tag, + "east" + ); + let plot_tag_update_empty: IPlotTagUpdate = + parse_json(json!({ "on": { "id": plot_tag_created.id }, "fields": {} })); + assert_invalid_argument(db.plot_tag_update(&plot_tag_update_empty)); + + let farm_member_find_many: IFarmMemberFindMany = + parse_json(json!({ "filter": { "id": farm_member_created.id } })); + assert_eq!( + db.farm_member_find_many(&farm_member_find_many) + .expect("farm member find many") + .results + .len(), + 1 + ); + let farm_member_find_one: IFarmMemberFindOne = + parse_json(json!({ "on": { "id": farm_member_created.id } })); + assert!( + db.farm_member_find_one(&farm_member_find_one) + .expect("farm member find one") + .result + .is_some() + ); + let farm_member_update_alt: IFarmMemberUpdate = parse_json(json!({ + "on": { "member_pubkey": hex64('b') }, + "fields": { "role": "editor" } + })); + assert_eq!( + db.farm_member_update(&farm_member_update_alt) + .expect("farm member update") + .result + .role, + "editor" + ); + let farm_member_update_id: IFarmMemberUpdate = parse_json( + json!({ "on": { "id": farm_member_created.id }, "fields": { "role": "admin" } }), + ); + assert_eq!( + db.farm_member_update(&farm_member_update_id) + .expect("farm member update id") + .result + .role, + "admin" + ); + let farm_member_update_empty: IFarmMemberUpdate = + parse_json(json!({ "on": { "id": farm_member_created.id }, "fields": {} })); + assert_invalid_argument(db.farm_member_update(&farm_member_update_empty)); + + let farm_member_claim_find_many: IFarmMemberClaimFindMany = + parse_json(json!({ "filter": { "id": farm_member_claim_created.id } })); + assert_eq!( + db.farm_member_claim_find_many(&farm_member_claim_find_many) + .expect("farm member claim find many") + .results + .len(), + 1 + ); + let farm_member_claim_find_one: IFarmMemberClaimFindOne = + parse_json(json!({ "on": { "id": farm_member_claim_created.id } })); + assert!( + db.farm_member_claim_find_one(&farm_member_claim_find_one) + .expect("farm member claim find one") + .result + .is_some() + ); + let farm_member_claim_update_alt: IFarmMemberClaimUpdate = parse_json(json!({ + "on": { "member_pubkey": hex64('b') }, + "fields": { "farm_pubkey": hex64('f') } + })); + assert_eq!( + db.farm_member_claim_update(&farm_member_claim_update_alt) + .expect("farm member claim update") + .result + .farm_pubkey, + hex64('f') + ); + let farm_member_claim_update_id: IFarmMemberClaimUpdate = parse_json(json!({ + "on": { "id": farm_member_claim_created.id }, + "fields": { "farm_pubkey": hex64('g') } + })); + assert_eq!( + db.farm_member_claim_update(&farm_member_claim_update_id) + .expect("farm member claim update id") + .result + .farm_pubkey, + hex64('g') + ); + let farm_member_claim_update_empty: IFarmMemberClaimUpdate = + parse_json(json!({ "on": { "id": farm_member_claim_created.id }, "fields": {} })); + assert_invalid_argument(db.farm_member_claim_update(&farm_member_claim_update_empty)); + + let log_error_find_many: ILogErrorFindMany = + parse_json(json!({ "filter": { "id": log_error_created.id } })); + assert_eq!( + db.log_error_find_many(&log_error_find_many) + .expect("log error find many") + .results + .len(), + 1 + ); + let log_error_find_one: ILogErrorFindOne = + parse_json(json!({ "on": { "id": log_error_created.id } })); + assert!( + db.log_error_find_one(&log_error_find_one) + .expect("log error find one") + .result + .is_some() + ); + let log_error_update_alt: ILogErrorUpdate = parse_json(json!({ + "on": { "nostr_pubkey": hex64('c') }, + "fields": { "message": "boom+" } + })); + assert_eq!( + db.log_error_update(&log_error_update_alt) + .expect("log error update") + .result + .message, + "boom+" + ); + let log_error_update_id: ILogErrorUpdate = parse_json( + json!({ "on": { "id": log_error_created.id }, "fields": { "message": "boom++" } }), + ); + assert_eq!( + db.log_error_update(&log_error_update_id) + .expect("log error update id") + .result + .message, + "boom++" + ); + let log_error_update_empty: ILogErrorUpdate = + parse_json(json!({ "on": { "id": log_error_created.id }, "fields": {} })); + assert_invalid_argument(db.log_error_update(&log_error_update_empty)); + + for opts in [ + IMediaImageFindMany::Rel { + rel: MediaImageFindManyRel::OnTradeProduct(MediaImageTradeProductArgs { + id: trade_product_created.id.clone(), + }), + }, + IMediaImageFindMany::Rel { + rel: MediaImageFindManyRel::OffTradeProduct(MediaImageTradeProductArgs { + id: trade_product_created.id.clone(), + }), + }, + ] { + let _ = db + .media_image_find_many(&opts) + .expect("media image rel find many"); + } + let media_image_find_many_filter: IMediaImageFindMany = + parse_json(json!({ "filter": { "id": media_image_created.id } })); + assert_eq!( + db.media_image_find_many(&media_image_find_many_filter) + .expect("media image find many filter") + .results + .len(), + 1 + ); + let media_image_find_one_on: IMediaImageFindOne = + parse_json(json!({ "on": { "id": media_image_created.id } })); + assert!( + db.media_image_find_one(&media_image_find_one_on) + .expect("media image find one") + .result + .is_some() + ); + let media_image_find_one_rel: IMediaImageFindOne = + parse_json(json!({ "rel": { "on_trade_product": { "id": trade_product_created.id } } })); + assert!( + db.media_image_find_one(&media_image_find_one_rel) + .expect("media image find one rel") + .result + .is_some() + ); + let media_image_update_alt: IMediaImageUpdate = + parse_json(json!({ "on": { "file_path": "/img/a.jpg" }, "fields": { "label": "hero" } })); + assert_eq!( + db.media_image_update(&media_image_update_alt) + .expect("media image update") + .result + .label + .as_deref(), + Some("hero") + ); + let media_image_update_id: IMediaImageUpdate = parse_json( + json!({ "on": { "id": media_image_created.id }, "fields": { "label": "hero+" } }), + ); + assert_eq!( + db.media_image_update(&media_image_update_id) + .expect("media image update id") + .result + .label + .as_deref(), + Some("hero+") + ); + let media_image_update_empty: IMediaImageUpdate = + parse_json(json!({ "on": { "id": media_image_created.id }, "fields": {} })); + assert_invalid_argument(db.media_image_update(&media_image_update_empty)); + + for opts in [ + INostrProfileFindMany::Rel { + rel: NostrProfileFindManyRel::OnRelay(NostrProfileRelayArgs { + id: nostr_relay_created.id.clone(), + }), + }, + INostrProfileFindMany::Rel { + rel: NostrProfileFindManyRel::OffRelay(NostrProfileRelayArgs { + id: nostr_relay_created.id.clone(), + }), + }, + ] { + let _ = db + .nostr_profile_find_many(&opts) + .expect("nostr profile rel find many"); + } + let nostr_profile_find_many_filter: INostrProfileFindMany = + parse_json(json!({ "filter": { "id": nostr_profile_created.id } })); + assert_eq!( + db.nostr_profile_find_many(&nostr_profile_find_many_filter) + .expect("nostr profile find many filter") + .results + .len(), + 1 + ); + let nostr_profile_find_one_on: INostrProfileFindOne = + parse_json(json!({ "on": { "id": nostr_profile_created.id } })); + assert!( + db.nostr_profile_find_one(&nostr_profile_find_one_on) + .expect("nostr profile find one") + .result + .is_some() + ); + let nostr_profile_find_one_rel: INostrProfileFindOne = + parse_json(json!({ "rel": { "on_relay": { "id": nostr_relay_created.id } } })); + assert!( + db.nostr_profile_find_one(&nostr_profile_find_one_rel) + .expect("nostr profile find one rel") + .result + .is_some() + ); + let nostr_profile_update_alt: INostrProfileUpdate = parse_json( + json!({ "on": { "public_key": hex64('d') }, "fields": { "name": "profile b" } }), + ); + assert_eq!( + db.nostr_profile_update(&nostr_profile_update_alt) + .expect("nostr profile update") + .result + .name, + "profile b" + ); + let nostr_profile_update_id: INostrProfileUpdate = parse_json( + json!({ "on": { "id": nostr_profile_created.id }, "fields": { "name": "profile b+" } }), + ); + assert_eq!( + db.nostr_profile_update(&nostr_profile_update_id) + .expect("nostr profile update id") + .result + .name, + "profile b+" + ); + let nostr_profile_update_empty: INostrProfileUpdate = + parse_json(json!({ "on": { "id": nostr_profile_created.id }, "fields": {} })); + assert_invalid_argument(db.nostr_profile_update(&nostr_profile_update_empty)); + + let nostr_event_state_find_many: INostrEventStateFindMany = + parse_json(json!({ "filter": { "id": nostr_event_state_created.id } })); + assert_eq!( + db.nostr_event_state_find_many(&nostr_event_state_find_many) + .expect("nostr event state find many") + .results + .len(), + 1 + ); + let nostr_event_state_find_one: INostrEventStateFindOne = + parse_json(json!({ "on": { "id": nostr_event_state_created.id } })); + assert!( + db.nostr_event_state_find_one(&nostr_event_state_find_one) + .expect("nostr event state find one") + .result + .is_some() + ); + let nostr_event_state_update_alt: INostrEventStateUpdate = + parse_json(json!({ "on": { "key": "state-a" }, "fields": { "content_hash": "hash-b" } })); + assert_eq!( + db.nostr_event_state_update(&nostr_event_state_update_alt) + .expect("nostr event state update") + .result + .content_hash, + "hash-b" + ); + let nostr_event_state_update_id: INostrEventStateUpdate = parse_json( + json!({ "on": { "id": nostr_event_state_created.id }, "fields": { "content_hash": "hash-c" } }), + ); + assert_eq!( + db.nostr_event_state_update(&nostr_event_state_update_id) + .expect("nostr event state update id") + .result + .content_hash, + "hash-c" + ); + let nostr_event_state_update_empty: INostrEventStateUpdate = + parse_json(json!({ "on": { "id": nostr_event_state_created.id }, "fields": {} })); + assert_invalid_argument(db.nostr_event_state_update(&nostr_event_state_update_empty)); + + for opts in [ + INostrRelayFindMany::Rel { + rel: NostrRelayFindManyRel::OnProfile(NostrRelayProfileArgs { + public_key: hex64('d'), + }), + }, + INostrRelayFindMany::Rel { + rel: NostrRelayFindManyRel::OffProfile(NostrRelayProfileArgs { + public_key: hex64('d'), + }), + }, + ] { + let _ = db + .nostr_relay_find_many(&opts) + .expect("nostr relay rel find many"); + } + let nostr_relay_find_many_filter: INostrRelayFindMany = + parse_json(json!({ "filter": { "id": nostr_relay_created.id } })); + assert_eq!( + db.nostr_relay_find_many(&nostr_relay_find_many_filter) + .expect("nostr relay find many filter") + .results + .len(), + 1 + ); + let nostr_relay_find_one_on: INostrRelayFindOne = + parse_json(json!({ "on": { "id": nostr_relay_created.id } })); + assert!( + db.nostr_relay_find_one(&nostr_relay_find_one_on) + .expect("nostr relay find one") + .result + .is_some() + ); + let nostr_relay_find_one_rel: INostrRelayFindOne = + parse_json(json!({ "rel": { "on_profile": { "public_key": hex64('d') } } })); + assert!( + db.nostr_relay_find_one(&nostr_relay_find_one_rel) + .expect("nostr relay find one rel") + .result + .is_some() + ); + let nostr_relay_update_alt: INostrRelayUpdate = parse_json(json!({ + "on": { "url": "wss://relay.example.com" }, + "fields": { "name": "relay a" } + })); + assert_eq!( + db.nostr_relay_update(&nostr_relay_update_alt) + .expect("nostr relay update") + .result + .name + .as_deref(), + Some("relay a") + ); + let nostr_relay_update_id: INostrRelayUpdate = parse_json( + json!({ "on": { "id": nostr_relay_created.id }, "fields": { "name": "relay a+" } }), + ); + assert_eq!( + db.nostr_relay_update(&nostr_relay_update_id) + .expect("nostr relay update id") + .result + .name + .as_deref(), + Some("relay a+") + ); + let nostr_relay_update_empty: INostrRelayUpdate = + parse_json(json!({ "on": { "id": nostr_relay_created.id }, "fields": {} })); + assert_invalid_argument(db.nostr_relay_update(&nostr_relay_update_empty)); + + let trade_product_find_many: ITradeProductFindMany = + parse_json(json!({ "filter": { "id": trade_product_created.id } })); + assert_eq!( + db.trade_product_find_many(&trade_product_find_many) + .expect("trade product find many") + .results + .len(), + 1 + ); + let trade_product_find_one: ITradeProductFindOne = + parse_json(json!({ "on": { "id": trade_product_created.id } })); + assert!( + db.trade_product_find_one(&trade_product_find_one) + .expect("trade product find one") + .result + .is_some() + ); + let trade_product_update: ITradeProductUpdate = parse_json( + json!({ "on": { "id": trade_product_created.id }, "fields": { "title": "coffee b" } }), + ); + assert_eq!( + db.trade_product_update(&trade_product_update) + .expect("trade product update") + .result + .title, + "coffee b" + ); + let trade_product_update_empty: ITradeProductUpdate = + parse_json(json!({ "on": { "id": trade_product_created.id }, "fields": {} })); + assert_invalid_argument(db.trade_product_update(&trade_product_update_empty)); + + let backup = db.backup_database().expect("backup"); + let backup_json = db.backup_database_json().expect("backup json"); + let _manifest = export_manifest(db.executor()).expect("export manifest"); + db.restore_database(&backup).expect("restore backup"); + db.restore_database_json(&backup_json) + .expect("restore backup json"); + + let gcs_delete_rel_found: IGcsLocationDelete = + parse_json(json!({ "rel": { "off_trade_product": { "id": trade_product_created.id } } })); + db.gcs_location_delete(&gcs_delete_rel_found) + .expect("gcs rel delete found"); + + let media_image_rel_delete_found: IMediaImageDelete = + parse_json(json!({ "rel": { "off_trade_product": { "id": trade_product_created.id } } })); + db.media_image_delete(&media_image_rel_delete_found) + .expect("media image rel delete found"); + + let nostr_relay_rel_delete_found: INostrRelayDelete = + parse_json(json!({ "rel": { "on_profile": { "public_key": hex64('f') } } })); + db.nostr_relay_delete(&nostr_relay_rel_delete_found) + .expect("nostr relay rel delete found"); + + let nostr_profile_rel_delete_found: INostrProfileDelete = + parse_json(json!({ "rel": { "off_relay": { "id": nostr_relay_created.id } } })); + db.nostr_profile_delete(&nostr_profile_rel_delete_found) + .expect("nostr profile rel delete found"); + + db.trade_product_media_unset(&product_media_rel) + .expect("product media unset"); + db.trade_product_location_unset(&product_location_rel) + .expect("product location unset"); + db.nostr_profile_relay_unset(&profile_relay_rel) + .expect("profile relay unset"); + + let trade_product_delete: ITradeProductDelete = + parse_json(json!({ "on": { "id": trade_product_created.id } })); + db.trade_product_delete(&trade_product_delete) + .expect("trade product delete"); + let trade_product_delete_missing: ITradeProductDelete = + parse_json(json!({ "on": { "id": trade_product_created.id } })); + assert_not_found(db.trade_product_delete(&trade_product_delete_missing)); + + let plot_gcs_delete: IPlotGcsLocationDelete = + parse_json(json!({ "on": { "plot_id": plot_created.id } })); + db.plot_gcs_location_delete(&plot_gcs_delete) + .expect("plot gcs delete"); + let plot_gcs_delete_missing: IPlotGcsLocationDelete = + parse_json(json!({ "on": { "id": plot_gcs_created.id } })); + assert_not_found(db.plot_gcs_location_delete(&plot_gcs_delete_missing)); + + let farm_gcs_delete: IFarmGcsLocationDelete = + parse_json(json!({ "on": { "farm_id": farm_created.id } })); + db.farm_gcs_location_delete(&farm_gcs_delete) + .expect("farm gcs delete"); + let farm_gcs_delete_missing: IFarmGcsLocationDelete = + parse_json(json!({ "on": { "id": farm_gcs_created.id } })); + assert_not_found(db.farm_gcs_location_delete(&farm_gcs_delete_missing)); + + let gcs_delete_on_non_primary: IGcsLocationDelete = + parse_json(json!({ "on": { "d_tag": "gcs-a" } })); + let _ = db.gcs_location_delete(&gcs_delete_on_non_primary); + + for payload in [ + json!({ "rel": { "on_trade_product": { "id": trade_product_created.id } } }), + json!({ "rel": { "off_trade_product": { "id": trade_product_created.id } } }), + json!({ "rel": { "on_farm": { "id": farm_created.id } } }), + json!({ "rel": { "off_farm": { "id": farm_created.id } } }), + json!({ "rel": { "on_plot": { "id": plot_created.id } } }), + json!({ "rel": { "off_plot": { "id": plot_created.id } } }), + ] { + let opts: IGcsLocationDelete = parse_json(payload); + let _ = db.gcs_location_delete(&opts); + } + let gcs_delete_missing: IGcsLocationDelete = + parse_json(json!({ "on": { "id": gcs_created.id } })); + assert_not_found(db.gcs_location_delete(&gcs_delete_missing)); + + let media_image_delete: IMediaImageDelete = + parse_json(json!({ "on": { "file_path": "/img/a.jpg" } })); + db.media_image_delete(&media_image_delete) + .expect("media image delete"); + let media_image_delete_missing: IMediaImageDelete = + parse_json(json!({ "on": { "id": media_image_created.id } })); + assert_not_found(db.media_image_delete(&media_image_delete_missing)); + for payload in [ + json!({ "rel": { "on_trade_product": { "id": trade_product_created.id } } }), + json!({ "rel": { "off_trade_product": { "id": trade_product_created.id } } }), + ] { + let opts: IMediaImageDelete = parse_json(payload); + let _ = db.media_image_delete(&opts); + } + + let nostr_profile_delete: INostrProfileDelete = + parse_json(json!({ "on": { "public_key": hex64('d') } })); + db.nostr_profile_delete(&nostr_profile_delete) + .expect("nostr profile delete"); + let nostr_profile_delete_missing: INostrProfileDelete = + parse_json(json!({ "on": { "id": nostr_profile_created.id } })); + assert_not_found(db.nostr_profile_delete(&nostr_profile_delete_missing)); + for payload in [ + json!({ "rel": { "on_relay": { "id": nostr_relay_created.id } } }), + json!({ "rel": { "off_relay": { "id": nostr_relay_created.id } } }), + ] { + let opts: INostrProfileDelete = parse_json(payload); + let _ = db.nostr_profile_delete(&opts); + } + + let nostr_relay_delete: INostrRelayDelete = + parse_json(json!({ "on": { "url": "wss://relay.example.com" } })); + db.nostr_relay_delete(&nostr_relay_delete) + .expect("nostr relay delete"); + let nostr_relay_delete_missing: INostrRelayDelete = + parse_json(json!({ "on": { "id": nostr_relay_created.id } })); + assert_not_found(db.nostr_relay_delete(&nostr_relay_delete_missing)); + for payload in [ + json!({ "rel": { "on_profile": { "public_key": hex64('d') } } }), + json!({ "rel": { "off_profile": { "public_key": hex64('d') } } }), + ] { + let opts: INostrRelayDelete = parse_json(payload); + let _ = db.nostr_relay_delete(&opts); + } + + let nostr_event_state_delete: INostrEventStateDelete = + parse_json(json!({ "on": { "key": "state-a" } })); + db.nostr_event_state_delete(&nostr_event_state_delete) + .expect("nostr event state delete"); + let nostr_event_state_delete_missing: INostrEventStateDelete = + parse_json(json!({ "on": { "id": nostr_event_state_created.id } })); + assert_not_found(db.nostr_event_state_delete(&nostr_event_state_delete_missing)); + + let log_error_delete: ILogErrorDelete = + parse_json(json!({ "on": { "nostr_pubkey": hex64('c') } })); + db.log_error_delete(&log_error_delete) + .expect("log error delete"); + let log_error_delete_missing: ILogErrorDelete = + parse_json(json!({ "on": { "id": log_error_created.id } })); + assert_not_found(db.log_error_delete(&log_error_delete_missing)); + + let farm_member_claim_delete: IFarmMemberClaimDelete = + parse_json(json!({ "on": { "member_pubkey": hex64('b') } })); + db.farm_member_claim_delete(&farm_member_claim_delete) + .expect("farm member claim delete"); + let farm_member_claim_delete_missing: IFarmMemberClaimDelete = + parse_json(json!({ "on": { "id": farm_member_claim_created.id } })); + assert_not_found(db.farm_member_claim_delete(&farm_member_claim_delete_missing)); + + let farm_member_delete: IFarmMemberDelete = + parse_json(json!({ "on": { "member_pubkey": hex64('b') } })); + db.farm_member_delete(&farm_member_delete) + .expect("farm member delete"); + let farm_member_delete_missing: IFarmMemberDelete = + parse_json(json!({ "on": { "id": farm_member_created.id } })); + assert_not_found(db.farm_member_delete(&farm_member_delete_missing)); + + let plot_tag_delete: IPlotTagDelete = parse_json(json!({ "on": { "tag": "east" } })); + db.plot_tag_delete(&plot_tag_delete) + .expect("plot tag delete"); + let plot_tag_delete_missing: IPlotTagDelete = + parse_json(json!({ "on": { "id": plot_tag_created.id } })); + assert_not_found(db.plot_tag_delete(&plot_tag_delete_missing)); + + let farm_tag_delete: IFarmTagDelete = parse_json(json!({ "on": { "tag": "regenerative" } })); + db.farm_tag_delete(&farm_tag_delete) + .expect("farm tag delete"); + let farm_tag_delete_missing: IFarmTagDelete = + parse_json(json!({ "on": { "id": farm_tag_created.id } })); + assert_not_found(db.farm_tag_delete(&farm_tag_delete_missing)); + + let plot_delete: IPlotDelete = parse_json(json!({ "on": { "d_tag": "plot-a" } })); + db.plot_delete(&plot_delete).expect("plot delete"); + let plot_delete_missing: IPlotDelete = parse_json(json!({ "on": { "id": plot_created.id } })); + assert_not_found(db.plot_delete(&plot_delete_missing)); + + let farm_delete: IFarmDelete = parse_json(json!({ "on": { "d_tag": "farm-a" } })); + db.farm_delete(&farm_delete).expect("farm delete"); + let farm_delete_missing: IFarmDelete = parse_json(json!({ "on": { "id": farm_created.id } })); + assert_not_found(db.farm_delete(&farm_delete_missing)); +}