From 3a17533be83afb15c8a12527a4a41a24d2d97a9b Mon Sep 17 00:00:00 2001 From: Handy-caT <37216852+Handy-caT@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:27:55 +0300 Subject: [PATCH] fix + bump --- Cargo.toml | 4 +- codegen/Cargo.toml | 2 +- .../src/worktable/generator/queries/type.rs | 8 + tests/persistence/sync/mod.rs | 1 + tests/persistence/sync/option.rs | 462 ++++++++++++ tests/worktable/option.rs | 697 ++++++++++++++++++ 6 files changed, 1171 insertions(+), 3 deletions(-) create mode 100644 tests/persistence/sync/option.rs diff --git a/Cargo.toml b/Cargo.toml index 739079fd..d10b458b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["codegen", "examples", "performance_measurement", "performance_measur [package] name = "worktable" -version = "0.8.17" +version = "0.8.18" edition = "2024" authors = ["Handy-caT"] license = "MIT" @@ -16,7 +16,7 @@ perf_measurements = ["dep:performance_measurement", "dep:performance_measurement # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -worktable_codegen = { path = "codegen", version = "=0.8.14" } +worktable_codegen = { path = "codegen", version = "=0.8.18" } eyre = "0.6.12" derive_more = { version = "2.0.1", features = ["from", "error", "display", "into"] } diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 643768b8..f154a804 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "worktable_codegen" -version = "0.8.14" +version = "0.8.18" edition = "2024" license = "MIT" description = "WorkTable codegeneration crate" diff --git a/codegen/src/worktable/generator/queries/type.rs b/codegen/src/worktable/generator/queries/type.rs index 3985f0c6..a0c93e3f 100644 --- a/codegen/src/worktable/generator/queries/type.rs +++ b/codegen/src/worktable/generator/queries/type.rs @@ -15,6 +15,14 @@ pub fn map_to_uppercase(str: &str) -> String { .expect("OrderedFloat def contains inner type") .replace(">", ""); format!("Ordered{}", inner_type.to_uppercase().trim()) + } else if str.contains("Option") { + let mut split = str.split("<"); + let _ = split.next(); + let inner_type = split + .next() + .expect("Option def contains inner type") + .replace(">", ""); + format!("Option{}", inner_type.to_uppercase().trim()) } else { str.to_uppercase() } diff --git a/tests/persistence/sync/mod.rs b/tests/persistence/sync/mod.rs index ce3e97bd..e932943c 100644 --- a/tests/persistence/sync/mod.rs +++ b/tests/persistence/sync/mod.rs @@ -5,6 +5,7 @@ use worktable::prelude::*; use worktable::worktable; mod many_strings; +mod option; mod string_primary_index; mod string_re_read; mod string_secondary_index; diff --git a/tests/persistence/sync/option.rs b/tests/persistence/sync/option.rs new file mode 100644 index 00000000..efc9e170 --- /dev/null +++ b/tests/persistence/sync/option.rs @@ -0,0 +1,462 @@ +use worktable::prelude::*; +use worktable_codegen::worktable; + +use crate::remove_dir_if_exists; + +worktable! ( + name: TestOptionSync, + persist: true, + columns: { + id: u64 primary_key autoincrement, + test: u64 optional, + another: u64, + exchange: i32, + }, + indexes: { + another_idx: another unique, + exchnage_idx: exchange, + }, + queries: { + update: { + TestById(test) by id, + TestByAnother(test) by another, + TestByExchange(test) by exchange, + } + } +); + +#[test] +fn test_option_insert_none_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/insert_none", + "tests/data/option_sync/insert_none", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/insert_none".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: None, + another: 1, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, None); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_insert_some_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/insert_some", + "tests/data/option_sync/insert_some", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/insert_some".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: Some(42), + another: 1, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, Some(42)); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_update_full_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/update_full", + "tests/data/option_sync/update_full", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/update_full".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: None, + another: 1, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + + table + .update(TestOptionSyncRow { + id: row.id, + test: Some(100), + another: 1, + exchange: 1, + }) + .await + .unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, Some(100)); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_update_by_id_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/update_by_id", + "tests/data/option_sync/update_by_id", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/update_by_id".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: None, + another: 1, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + + table + .update_test_by_id(TestByIdQuery { test: Some(42) }, row.id) + .await + .unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, Some(42)); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_update_none_to_some_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/none_to_some", + "tests/data/option_sync/none_to_some", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/none_to_some".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: None, + another: 1, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + + table + .update_test_by_id(TestByIdQuery { test: Some(55) }, row.id) + .await + .unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, Some(55)); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_update_some_to_none_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/some_to_none", + "tests/data/option_sync/some_to_none", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/some_to_none".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: Some(100), + another: 1, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + + table + .update_test_by_id(TestByIdQuery { test: None }, row.id) + .await + .unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, None); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_update_by_another_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/update_by_another", + "tests/data/option_sync/update_by_another", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/update_by_another".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: None, + another: 123, + exchange: 1, + }; + table.insert(row.clone()).unwrap(); + + table + .update_test_by_another(TestByAnotherQuery { test: Some(77) }, 123) + .await + .unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, Some(77)); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_update_by_exchange_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/update_by_exchange", + "tests/data/option_sync/update_by_exchange", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/update_by_exchange".to_string()).await; + + let pk = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + let row = TestOptionSyncRow { + id: table.get_next_pk().0, + test: None, + another: 1, + exchange: 456, + }; + table.insert(row.clone()).unwrap(); + + table + .update_test_by_exchange(TestByExchangeQuery { test: Some(88) }, 456) + .await + .unwrap(); + table.wait_for_ops().await; + row.id + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + let selected = table.select(pk).unwrap(); + assert_eq!(selected.test, Some(88)); + assert_eq!(table.0.pk_gen.get_state(), pk + 1); + } + }); +} + +#[test] +fn test_option_multiple_rows_sync() { + let config = PersistenceConfig::new( + "tests/data/option_sync/multiple_rows", + "tests/data/option_sync/multiple_rows", + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_io() + .enable_time() + .build() + .unwrap(); + + runtime.block_on(async { + remove_dir_if_exists("tests/data/option_sync/multiple_rows".to_string()).await; + + let (pk1, pk2) = { + let table = TestOptionSyncWorkTable::load_from_file(config.clone()) + .await + .unwrap(); + + let row1 = TestOptionSyncRow { + id: table.get_next_pk().0, + test: Some(10), + another: 1, + exchange: 1, + }; + let pk1 = table.insert(row1).unwrap(); + + let row2 = TestOptionSyncRow { + id: table.get_next_pk().0, + test: Some(20), + another: 2, + exchange: 2, + }; + let pk2 = table.insert(row2).unwrap(); + + table + .update_test_by_id(TestByIdQuery { test: Some(30) }, pk1.clone()) + .await + .unwrap(); + + table.wait_for_ops().await; + (pk1, pk2) + }; + + { + let table = TestOptionSyncWorkTable::load_from_file(config) + .await + .unwrap(); + assert_eq!(table.select(pk1).unwrap().test, Some(30)); + assert_eq!(table.select(pk2).unwrap().test, Some(20)); + } + }); +} diff --git a/tests/worktable/option.rs b/tests/worktable/option.rs index 378240b3..bdd8607e 100644 --- a/tests/worktable/option.rs +++ b/tests/worktable/option.rs @@ -1,3 +1,5 @@ +use uuid::Uuid; + use worktable::prelude::*; use worktable::worktable; @@ -78,3 +80,698 @@ async fn update_by_exchange() { let selected_row = table.select(pk).unwrap(); assert_eq!(selected_row.test, Some(1)); } + +#[tokio::test] +async fn update_none_to_some() { + let table = TestWorkTable::default(); + let row = TestRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + assert_eq!(table.select(pk.clone()).unwrap().test, None); + + table + .update_test_by_id(TestByIdQuery { test: Some(42) }, pk.clone()) + .await + .unwrap(); + + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row.test, Some(42)); +} + +#[tokio::test] +async fn update_some_to_none() { + let table = TestWorkTable::default(); + let row = TestRow { + id: table.get_next_pk().into(), + test: Some(100), + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + assert_eq!(table.select(pk.clone()).unwrap().test, Some(100)); + + table + .update_test_by_id(TestByIdQuery { test: None }, pk.clone()) + .await + .unwrap(); + + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row.test, None); +} + +#[tokio::test] +async fn update_multiple_values() { + let table = TestWorkTable::default(); + + let row1 = TestRow { + id: table.get_next_pk().into(), + test: Some(10), + another: 1, + exchange: 1, + }; + let pk1 = table.insert(row1).unwrap(); + + let row2 = TestRow { + id: table.get_next_pk().into(), + test: Some(20), + another: 2, + exchange: 2, + }; + let pk2 = table.insert(row2).unwrap(); + + table + .update_test_by_id(TestByIdQuery { test: Some(30) }, pk1.clone()) + .await + .unwrap(); + + assert_eq!(table.select(pk1).unwrap().test, Some(30)); + assert_eq!(table.select(pk2).unwrap().test, Some(20)); +} + +worktable! ( + name: TestCustom, + columns: { + id: u64 primary_key autoincrement, + test: Uuid optional, + another: u64, + exchange: i32, + }, + indexes: { + another_idx: another unique, + exchnage_idx: exchange, + }, + queries: { + update: { + CustomTestById(test) by id, + CustomTestByAnother(test) by another, + CustomTestByExchange(test) by exchange, + } + } +); + +#[tokio::test] +async fn custom_update() { + let table = TestCustomWorkTable::default(); + let row = TestCustomRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + let test_uuid = Uuid::new_v4(); + let new_row = TestCustomRow { + id: pk.clone().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + table.update(new_row.clone()).await.unwrap(); + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row, new_row); +} + +#[tokio::test] +async fn custom_update_by_another() { + let table = TestCustomWorkTable::default(); + let row = TestCustomRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + let test_uuid = Uuid::new_v4(); + table + .update_custom_test_by_another( + CustomTestByAnotherQuery { + test: Some(test_uuid), + }, + 1, + ) + .await + .unwrap(); + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row.test, Some(test_uuid)); +} + +#[tokio::test] +async fn custom_update_by_exchange() { + let table = TestCustomWorkTable::default(); + let row = TestCustomRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + let test_uuid = Uuid::new_v4(); + table + .update_custom_test_by_exchange( + CustomTestByExchangeQuery { + test: Some(test_uuid), + }, + 1, + ) + .await + .unwrap(); + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row.test, Some(test_uuid)); +} + +#[tokio::test] +async fn custom_update_none_to_some() { + let table = TestCustomWorkTable::default(); + let row = TestCustomRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + assert_eq!(table.select(pk.clone()).unwrap().test, None); + + let test_uuid = Uuid::new_v4(); + table + .update_custom_test_by_id( + CustomTestByIdQuery { + test: Some(test_uuid), + }, + pk.clone(), + ) + .await + .unwrap(); + + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row.test, Some(test_uuid)); +} + +#[tokio::test] +async fn custom_update_some_to_none() { + let table = TestCustomWorkTable::default(); + let test_uuid = Uuid::new_v4(); + let row = TestCustomRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + assert_eq!(table.select(pk.clone()).unwrap().test, Some(test_uuid)); + + table + .update_custom_test_by_id(CustomTestByIdQuery { test: None }, pk.clone()) + .await + .unwrap(); + + let selected_row = table.select(pk).unwrap(); + assert_eq!(selected_row.test, None); +} + +#[tokio::test] +async fn custom_update_multiple_uuids() { + let table = TestCustomWorkTable::default(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + let row1 = TestCustomRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 1, + exchange: 1, + }; + let pk1 = table.insert(row1).unwrap(); + + let row2 = TestCustomRow { + id: table.get_next_pk().into(), + test: Some(uuid2), + another: 2, + exchange: 2, + }; + let pk2 = table.insert(row2).unwrap(); + + let uuid3 = Uuid::new_v4(); + table + .update_custom_test_by_id(CustomTestByIdQuery { test: Some(uuid3) }, pk1.clone()) + .await + .unwrap(); + + assert_eq!(table.select(pk1).unwrap().test, Some(uuid3)); + assert_eq!(table.select(pk2).unwrap().test, Some(uuid2)); +} + +worktable! ( + name: TestIndex, + columns: { + id: u64 primary_key autoincrement, + test: Uuid optional, + another: u64, + exchange: i32, + }, + indexes: { + another_idx: another unique, + test_idx: test, + exchnage_idx: exchange, + }, + queries: { + update: { + IndexTestById(test) by id, + IndexTestByAnother(test) by another, + IndexTestByExchange(test) by exchange, + } + } +); + +#[tokio::test] +async fn indexed_insert_and_select_by_uuid_some() { + let table = TestIndexWorkTable::default(); + let test_uuid = Uuid::new_v4(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + let pk = table.insert(row.clone()).unwrap(); + + // Select by the indexed UUID field with Some value + let result = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].id, pk.0); +} + +#[tokio::test] +async fn indexed_select_by_uuid_none() { + let table = TestIndexWorkTable::default(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row).unwrap(); + + // Select by None in the indexed field + let result = table.select_by_test(None).execute().unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].id, pk.0); +} + +#[tokio::test] +async fn indexed_multiple_rows_same_uuid() { + let table = TestIndexWorkTable::default(); + let test_uuid = Uuid::new_v4(); + + let row1 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + let pk1 = table.insert(row1).unwrap(); + + let row2 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 2, + exchange: 2, + }; + let pk2 = table.insert(row2).unwrap(); + + let row3 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 3, + exchange: 3, + }; + let pk3 = table.insert(row3).unwrap(); + + // Should find all three rows with the same UUID + let result = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result.len(), 3); + assert!(result.iter().any(|r| r.id == pk1.0)); + assert!(result.iter().any(|r| r.id == pk2.0)); + assert!(result.iter().any(|r| r.id == pk3.0)); +} + +#[tokio::test] +async fn indexed_multiple_rows_none() { + let table = TestIndexWorkTable::default(); + + let row1 = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk1 = table.insert(row1).unwrap(); + + let row2 = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 2, + exchange: 2, + }; + let pk2 = table.insert(row2).unwrap(); + + let row3 = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 3, + exchange: 3, + }; + let pk3 = table.insert(row3).unwrap(); + + // Should find all three rows with None + let result = table.select_by_test(None).execute().unwrap(); + assert_eq!(result.len(), 3); + assert!(result.iter().any(|r| r.id == pk1.0)); + assert!(result.iter().any(|r| r.id == pk2.0)); + assert!(result.iter().any(|r| r.id == pk3.0)); +} + +#[tokio::test] +async fn indexed_update_indexed_field() { + let table = TestIndexWorkTable::default(); + let uuid1 = Uuid::new_v4(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 1, + exchange: 1, + }; + let pk = table.insert(row).unwrap(); + + // Verify initial UUID is indexed + let result = table.select_by_test(Some(uuid1)).execute().unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].id, pk.0); + + // Update to a new UUID + let uuid2 = Uuid::new_v4(); + table + .update_index_test_by_id(IndexTestByIdQuery { test: Some(uuid2) }, pk.clone()) + .await + .unwrap(); + + // Old UUID should not be found + let result_old = table.select_by_test(Some(uuid1)).execute().unwrap(); + assert_eq!(result_old.len(), 0); + + // New UUID should be found + let result_new = table.select_by_test(Some(uuid2)).execute().unwrap(); + assert_eq!(result_new.len(), 1); + assert_eq!(result_new[0].id, pk.0); +} + +#[tokio::test] +async fn indexed_update_from_some_to_none() { + let table = TestIndexWorkTable::default(); + let test_uuid = Uuid::new_v4(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + let pk = table.insert(row).unwrap(); + + // Verify initial UUID is indexed + let result = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result.len(), 1); + + // Update to None + table + .update_index_test_by_id(IndexTestByIdQuery { test: None }, pk.clone()) + .await + .unwrap(); + + // Old UUID should not be found + let result_old = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result_old.len(), 0); + + // Should be findable by None + let result_none = table.select_by_test(None).execute().unwrap(); + assert_eq!(result_none.len(), 1); + assert_eq!(result_none[0].id, pk.0); +} + +#[tokio::test] +async fn indexed_update_from_none_to_some() { + let table = TestIndexWorkTable::default(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row).unwrap(); + + // Verify None is indexed + let result = table.select_by_test(None).execute().unwrap(); + assert_eq!(result.len(), 1); + + // Update to Some UUID + let test_uuid = Uuid::new_v4(); + table + .update_index_test_by_id( + IndexTestByIdQuery { + test: Some(test_uuid), + }, + pk.clone(), + ) + .await + .unwrap(); + + // None count should decrease + let result_none = table.select_by_test(None).execute().unwrap(); + assert_eq!(result_none.len(), 0); + + // New UUID should be findable + let result_uuid = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result_uuid.len(), 1); + assert_eq!(result_uuid[0].id, pk.0); +} + +#[tokio::test] +async fn indexed_update_via_another_index() { + let table = TestIndexWorkTable::default(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 999, + exchange: 1, + }; + table.insert(row).unwrap(); + + // Update via the unique 'another' index + table + .update_index_test_by_another(IndexTestByAnotherQuery { test: Some(uuid2) }, 999) + .await + .unwrap(); + + // Verify both indexes are updated correctly + let result_uuid = table.select_by_test(Some(uuid2)).execute().unwrap(); + assert_eq!(result_uuid.len(), 1); + + let result_another = table.select_by_another(999).unwrap(); + assert_eq!(result_another.test, Some(uuid2)); +} + +#[tokio::test] +async fn indexed_update_via_non_unique_index() { + let table = TestIndexWorkTable::default(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + let row1 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 1, + exchange: 100, + }; + let pk1 = table.insert(row1).unwrap(); + + let row2 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 2, + exchange: 100, + }; + let pk2 = table.insert(row2).unwrap(); + + // Update both rows via the non-unique 'exchange' index + table + .update_index_test_by_exchange(IndexTestByExchangeQuery { test: Some(uuid2) }, 100) + .await + .unwrap(); + + // Both rows should be updated + assert_eq!(table.select(pk1).unwrap().test, Some(uuid2)); + assert_eq!(table.select(pk2).unwrap().test, Some(uuid2)); + + // Old UUID should not be found + let result_old = table.select_by_test(Some(uuid1)).execute().unwrap(); + assert_eq!(result_old.len(), 0); + + // New UUID should find both rows + let result_new = table.select_by_test(Some(uuid2)).execute().unwrap(); + assert_eq!(result_new.len(), 2); +} + +#[tokio::test] +async fn indexed_mixed_none_and_some() { + let table = TestIndexWorkTable::default(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + // Insert rows with None and Some UUIDs + let row1 = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + table.insert(row1).unwrap(); + + let row2 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 2, + exchange: 2, + }; + table.insert(row2).unwrap(); + + let row3 = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 3, + exchange: 3, + }; + table.insert(row3).unwrap(); + + let row4 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid2), + another: 4, + exchange: 4, + }; + table.insert(row4).unwrap(); + + // Verify counts + let result_none = table.select_by_test(None).execute().unwrap(); + assert_eq!(result_none.len(), 2); + + let result_uuid1 = table.select_by_test(Some(uuid1)).execute().unwrap(); + assert_eq!(result_uuid1.len(), 1); + + let result_uuid2 = table.select_by_test(Some(uuid2)).execute().unwrap(); + assert_eq!(result_uuid2.len(), 1); +} + +#[tokio::test] +async fn indexed_delete_row_with_uuid() { + let table = TestIndexWorkTable::default(); + let test_uuid = Uuid::new_v4(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + let pk = table.insert(row).unwrap(); + + // Verify UUID is indexed + let result = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result.len(), 1); + + // Delete the row + table.delete(pk).await.unwrap(); + + // UUID should not be found after deletion + let result = table.select_by_test(Some(test_uuid)).execute().unwrap(); + assert_eq!(result.len(), 0); +} + +#[tokio::test] +async fn indexed_delete_row_with_none() { + let table = TestIndexWorkTable::default(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: None, + another: 1, + exchange: 1, + }; + let pk = table.insert(row).unwrap(); + + // Verify None is indexed + let result = table.select_by_test(None).execute().unwrap(); + assert_eq!(result.len(), 1); + + // Delete the row + table.delete(pk).await.unwrap(); + + // None count should decrease + let result = table.select_by_test(None).execute().unwrap(); + assert_eq!(result.len(), 0); +} + +#[tokio::test] +async fn indexed_no_match_for_uuid() { + let table = TestIndexWorkTable::default(); + let test_uuid = Uuid::new_v4(); + + let row = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(test_uuid), + another: 1, + exchange: 1, + }; + table.insert(row).unwrap(); + + // Search for a different UUID + let other_uuid = Uuid::new_v4(); + let result = table.select_by_test(Some(other_uuid)).execute().unwrap(); + assert_eq!(result.len(), 0); +} + +#[tokio::test] +async fn indexed_no_match_for_none_when_all_have_values() { + let table = TestIndexWorkTable::default(); + let uuid1 = Uuid::new_v4(); + let uuid2 = Uuid::new_v4(); + + let row1 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid1), + another: 1, + exchange: 1, + }; + table.insert(row1).unwrap(); + + let row2 = TestIndexRow { + id: table.get_next_pk().into(), + test: Some(uuid2), + another: 2, + exchange: 2, + }; + table.insert(row2).unwrap(); + + // Search for None when all rows have Some values + let result = table.select_by_test(None).execute().unwrap(); + assert_eq!(result.len(), 0); +}