diff --git a/codegen/src/worktable/generator/queries/delete.rs b/codegen/src/worktable/generator/queries/delete.rs index c1fcf051..78f91a29 100644 --- a/codegen/src/worktable/generator/queries/delete.rs +++ b/codegen/src/worktable/generator/queries/delete.rs @@ -22,21 +22,62 @@ impl Generator { quote! {} }; let full_row_delete = self.gen_full_row_delete(); + let full_row_delete_without_lock = self.gen_full_row_delete_without_lock(); Ok(quote! { impl #table_ident { #full_row_delete + #full_row_delete_without_lock #custom_deletes } }) } fn gen_full_row_delete(&mut self) -> TokenStream { + let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); + let pk_ident = name_generator.get_primary_key_type_ident(); + let lock_ident = name_generator.get_lock_type_ident(); + let delete_logic = self.gen_delete_logic(); + + quote! { + pub async fn delete(&self, pk: #pk_ident) -> core::result::Result<(), WorkTableError> { + if let Some(lock) = self.0.lock_map.get(&pk) { + lock.lock_await().await; // Waiting for all locks released + } + + let lock_id = self.0.lock_map.next_id(); + let lock = std::sync::Arc::new(#lock_ident::with_lock(lock_id.into())); //Creates new LockType with None + self.0.lock_map.insert(pk.clone(), lock.clone()); // adds LockType to LockMap + + #delete_logic + + lock.unlock(); // Releases locks + self.0.lock_map.remove(&pk); // Removes locks + + core::result::Result::Ok(()) + } + } + } + + fn gen_full_row_delete_without_lock(&mut self) -> TokenStream { + let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); + let pk_ident = name_generator.get_primary_key_type_ident(); + let delete_logic = self.gen_delete_logic(); + + quote! { + pub async fn delete_without_lock(&self, pk: #pk_ident) -> core::result::Result<(), WorkTableError> { + #delete_logic + core::result::Result::Ok(()) + } + } + } + + fn gen_delete_logic(&self) -> TokenStream { let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); let pk_ident = name_generator.get_primary_key_type_ident(); let secondary_events_ident = name_generator.get_space_secondary_index_events_ident(); - let delete_logic = if self.is_persist { + let process = if self.is_persist { quote! { let secondary_keys_events = self.0.indexes.delete_row_cdc(row, link)?; let (_, primary_key_events) = TableIndexCdc::remove_cdc(&self.0.pk_map, pk.clone(), link); @@ -60,34 +101,14 @@ impl Generator { } }; - let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); - let lock_ident = name_generator.get_lock_type_ident(); - quote! { - pub async fn delete(&self, pk: #pk_ident) -> core::result::Result<(), WorkTableError> { - - if let Some(lock) = self.0.lock_map.get(&pk) { - lock.lock_await().await; // Waiting for all locks released - } - - let lock_id = self.0.lock_map.next_id(); - let lock = std::sync::Arc::new(#lock_ident::with_lock(lock_id.into())); //Creates new LockType with None - self.0.lock_map.insert(pk.clone(), lock.clone()); // adds LockType to LockMap - - let link = self.0 + let link = self.0 .pk_map .get(&pk) .map(|v| v.get().value) .ok_or(WorkTableError::NotFound)?; - - let row = self.select(pk.clone()).unwrap(); - #delete_logic - - lock.unlock(); // Releases locks - self.0.lock_map.remove(&pk); // Removes locks - - core::result::Result::Ok(()) - } + let row = self.select(pk.clone()).unwrap(); + #process } } diff --git a/codegen/src/worktable/generator/queries/mod.rs b/codegen/src/worktable/generator/queries/mod.rs index b97b9927..d4385753 100644 --- a/codegen/src/worktable/generator/queries/mod.rs +++ b/codegen/src/worktable/generator/queries/mod.rs @@ -2,4 +2,5 @@ mod delete; mod locks; mod select; pub mod r#type; +mod unsized_; mod update; diff --git a/codegen/src/worktable/generator/queries/unsized_.rs b/codegen/src/worktable/generator/queries/unsized_.rs new file mode 100644 index 00000000..f125f1cf --- /dev/null +++ b/codegen/src/worktable/generator/queries/unsized_.rs @@ -0,0 +1,100 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; + +use crate::name_generator::WorktableNameGenerator; +use crate::worktable::generator::Generator; + +impl Generator { + pub fn gen_unsized_impls(&self) -> TokenStream { + if self.columns.is_sized { + quote! {} + } else { + let unsized_field_len_fns = self.gen_get_unsized_field_len_wt_fn(); + let unsized_field_len_query_fns = self.gen_get_unsized_field_len_query_fn(); + quote! { + #unsized_field_len_fns + #unsized_field_len_query_fns + } + } + } + + fn gen_get_unsized_field_len_wt_fn(&self) -> TokenStream { + let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); + let table_ident = name_generator.get_work_table_ident(); + + let unsized_fields: Vec<_> = self + .columns + .columns_map + .iter() + .filter_map(|(k, v)| { + if v.to_string() == "String" { + Some(k) + } else { + None + } + }) + .map(|f| { + let fn_ident = Ident::new(format!("get_{}_size", f).as_str(), Span::call_site()); + quote! { + fn #fn_ident(&self, link: Link) -> core::result::Result { + self.0.data + .with_ref(link, |row_ref| row_ref.inner.#f.len()) + .map_err(WorkTableError::PagesError) + } + } + }) + .collect(); + + quote! { + impl #table_ident { + #(#unsized_fields)* + } + } + } + + fn gen_get_unsized_field_len_query_fn(&self) -> TokenStream { + if let Some(q) = &self.queries { + let query_impls: Vec<_> = q + .updates + .iter() + .filter(|(_, op)| { + op.columns + .iter() + .any(|c| self.columns.columns_map.get(c).unwrap().to_string() == "String") + }) + .map(|(i, op)| { + let archived_ident = + Ident::new(format!("Archived{}Query", i).as_str(), Span::call_site()); + let unsized_fields: Vec<_> = op + .columns + .iter() + .filter(|c| { + self.columns.columns_map.get(c).unwrap().to_string() == "String" + }) + .map(|c| { + let fn_ident = + Ident::new(format!("get_{}_size", c).as_str(), Span::call_site()); + quote! { + pub fn #fn_ident(&self) -> usize { + self.#c.len() + } + } + }) + .collect(); + + quote! { + impl #archived_ident { + #(#unsized_fields)* + } + } + }) + .collect(); + + quote! { + #(#query_impls)* + } + } else { + quote! {} + } + } +} diff --git a/codegen/src/worktable/generator/queries/update.rs b/codegen/src/worktable/generator/queries/update.rs index 21e2d36d..ccd6eeeb 100644 --- a/codegen/src/worktable/generator/queries/update.rs +++ b/codegen/src/worktable/generator/queries/update.rs @@ -57,6 +57,21 @@ impl Generator { let diff_process = self.gen_process_diffs_on_index(idents.as_slice(), Some(&idents)); let persist_call = self.gen_persist_call(); let persist_op = self.gen_persist_op(); + let size_check = if self.columns.is_sized { + quote! {} + } else { + quote! { + if bytes.len() > link.length as usize { + self.delete_without_lock(pk.clone()).await?; + self.insert(row)?; + + lock.unlock(); // Releases locks + self.0.lock_map.remove(&pk); // Removes locks + + return core::result::Result::Ok(()); + } + } + }; quote! { pub async fn update(&self, row: #row_ident) -> core::result::Result<(), WorkTableError> { @@ -70,12 +85,14 @@ impl Generator { self.0.lock_map.insert(pk.clone(), lock.clone()); // adds LockType to LockMap let mut bytes = rkyv::to_bytes::(&row).map_err(|_| WorkTableError::SerializeError)?; - let mut archived_row = unsafe { rkyv::access_unchecked_mut::<<#row_ident as rkyv::Archive>::Archived>(&mut bytes[..]).unseal_unchecked() }; let link = self.0 .pk_map .get(&pk) .map(|v| v.get().value) .ok_or(WorkTableError::NotFound)?; + #size_check + + let mut archived_row = unsafe { rkyv::access_unchecked_mut::<<#row_ident as rkyv::Archive>::Archived>(&mut bytes[..]).unseal_unchecked() }; #diff_process #persist_op @@ -119,6 +136,22 @@ impl Generator { Some(columns) } }; + let unsized_columns = if self.columns.is_sized { + None + } else { + let fields = op + .columns + .iter() + .filter(|c| { + self.columns.columns_map.get(c).unwrap().to_string() == "String" + }) + .collect::>(); + if fields.is_empty() { + None + } else { + Some(fields) + } + }; let idents = &op.columns; if let Some(index) = index { @@ -131,6 +164,7 @@ impl Generator { index_name, idents, indexes_columns.as_ref(), + unsized_columns, ) } else { self.gen_non_unique_update( @@ -139,11 +173,18 @@ impl Generator { index_name, idents, indexes_columns.as_ref(), + unsized_columns, ) } } else if self.columns.primary_keys.len() == 1 { if *self.columns.primary_keys.first().unwrap() == op.by { - self.gen_pk_update(snake_case_name, name, idents, indexes_columns.as_ref()) + self.gen_pk_update( + snake_case_name, + name, + idents, + indexes_columns.as_ref(), + unsized_columns, + ) } else { todo!() } @@ -173,6 +214,49 @@ impl Generator { } } + fn gen_size_check(&self, unsized_fields: Option>, idents: &[Ident]) -> TokenStream { + if let Some(f) = unsized_fields { + let fields_check: Vec<_> = f + .iter() + .map(|f| { + let fn_ident = + Ident::new(format!("get_{}_size", f).as_str(), Span::call_site()); + quote! { + if !need_to_reinsert { + need_to_reinsert = archived_row.#fn_ident() > self.#fn_ident(link)? + } + } + }) + .collect(); + let row_updates = idents + .iter() + .map(|i| { + quote! { + row_old.#i = row.#i; + } + }) + .collect::>(); + + quote! { + let mut need_to_reinsert = false; + #(#fields_check)* + if need_to_reinsert { + let mut row_old = self.select(pk.clone()).unwrap(); + #(#row_updates)* + self.delete_without_lock(pk.clone()).await?; + self.insert(row_old)?; + + lock.unlock(); // Releases locks + self.0.lock_map.remove(&pk); // Removes locks + + return core::result::Result::Ok(()); + } + } + } else { + quote! {} + } + } + fn gen_persist_op(&self) -> TokenStream { let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); let secondary_events_ident = name_generator.get_space_secondary_index_events_ident(); @@ -272,6 +356,7 @@ impl Generator { name: &Ident, idents: &[Ident], idx_idents: Option<&Vec>, + unsized_fields: Option>, ) -> TokenStream { let pk_ident = &self.pk.as_ref().unwrap().ident; let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); @@ -305,6 +390,7 @@ impl Generator { }) .collect::>(); + let size_check = self.gen_size_check(unsized_fields, idents); let diff_process = self.gen_process_diffs_on_index(idents, idx_idents); let persist_call = self.gen_persist_call(); let persist_op = self.gen_persist_op(); @@ -328,6 +414,7 @@ impl Generator { .map(|v| v.get().value) .ok_or(WorkTableError::NotFound)?; + #size_check #diff_process #persist_op @@ -352,6 +439,7 @@ impl Generator { index: &Ident, idents: &[Ident], idx_idents: Option<&Vec>, + unsized_fields: Option>, ) -> TokenStream { let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); let lock_type_ident = name_generator.get_lock_type_ident(); @@ -386,20 +474,63 @@ impl Generator { }) .collect::>(); + let size_check = if let Some(f) = unsized_fields { + let fields_check: Vec<_> = f + .iter() + .map(|f| { + let fn_ident = + Ident::new(format!("get_{}_size", f).as_str(), Span::call_site()); + quote! { + if !need_to_reinsert { + need_to_reinsert = archived_row.#fn_ident() > self.#fn_ident(link)? + } + } + }) + .collect(); + let row_updates = idents + .iter() + .map(|i| { + quote! { + row_old.#i = row.#i.clone(); + } + }) + .collect::>(); + + quote! { + let mut need_to_reinsert = false; + #(#fields_check)* + if need_to_reinsert { + let mut row_old = self.select(pk.clone()).unwrap(); + #(#row_updates)* + self.delete_without_lock(pk.clone()).await?; + self.insert(row_old)?; + + let lock = self.0.lock_map.get(&pk).expect("was inserted before and not deleted"); + lock.unlock(); // Releases locks + self.0.lock_map.remove(&pk); // Removes locks + } else { + links_to_unlock.push(link) + } + } + } else { + quote! {} + }; let diff_process = self.gen_process_diffs_on_index(idents, idx_idents); let persist_call = self.gen_persist_call(); let persist_op = self.gen_persist_op(); quote! { pub async fn #method_ident(&self, row: #query_ident, by: #by_ident) -> core::result::Result<(), WorkTableError> { - for (_, link) in self.0.indexes.#index.get(&by) { + let links: Vec<_> = self.0.indexes.#index.get(&by).map(|(_, l)| *l).collect(); + + for link in links.iter() { let pk = self.0.data.select(*link)?.get_primary_key(); if let Some(lock) = self.0.lock_map.get(&pk) { lock.#lock_await_ident().await; } } - for (_, link) in self.0.indexes.#index.get(&by) { + for link in links.iter() { let pk = self.0.data.select(*link)?.get_primary_key(); let lock_id = self.0.lock_map.next_id(); let mut lock = #lock_type_ident::new(lock_id.into()); @@ -407,8 +538,8 @@ impl Generator { self.0.lock_map.insert(pk.clone(), std::sync::Arc::new(lock.clone())); } - for (_, link) in self.0.indexes.#index.get(&by) { - let link = *link; + let mut links_to_unlock = vec![]; + for link in links.into_iter() { let pk = self.0.data.select(link)?.get_primary_key().clone(); let mut bytes = rkyv::to_bytes::(&row) .map_err(|_| WorkTableError::SerializeError)?; @@ -418,6 +549,7 @@ impl Generator { .unseal_unchecked() }; + #size_check #diff_process #persist_op @@ -429,7 +561,7 @@ impl Generator { #persist_call } - for (_, link) in self.0.indexes.#index.get(&by) { + for link in links_to_unlock.iter() { let pk = self.0.data.select(*link)?.get_primary_key(); if let Some(lock) = self.0.lock_map.get(&pk) { lock.#unlock_ident(); @@ -448,6 +580,7 @@ impl Generator { index: &Ident, idents: &[Ident], idx_idents: Option<&Vec>, + unsized_fields: Option>, ) -> TokenStream { let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); let lock_type_ident = name_generator.get_lock_type_ident(); @@ -477,10 +610,11 @@ impl Generator { .iter() .map(|i| { quote! { - std::mem::swap(&mut archived.inner.#i, &mut archived_new_row.#i); + std::mem::swap(&mut archived.inner.#i, &mut archived_row.#i); } }) .collect::>(); + let size_check = self.gen_size_check(unsized_fields, idents); let diff_process = self.gen_process_diffs_on_index(idents, idx_idents); let persist_call = self.gen_persist_call(); let persist_op = self.gen_persist_op(); @@ -490,7 +624,7 @@ impl Generator { let mut bytes = rkyv::to_bytes::(&row) .map_err(|_| WorkTableError::SerializeError)?; - let mut archived_new_row = unsafe { + let mut archived_row = unsafe { rkyv::access_unchecked_mut::<<#query_ident as rkyv::Archive>::Archived>(&mut bytes[..]) .unseal_unchecked() }; @@ -509,6 +643,7 @@ impl Generator { lock.#lock_ident(); self.0.lock_map.insert(pk.clone(), std::sync::Arc::new(lock.clone())); + #size_check #diff_process #persist_op diff --git a/codegen/src/worktable/mod.rs b/codegen/src/worktable/mod.rs index 3ba431e9..983e45cc 100644 --- a/codegen/src/worktable/mod.rs +++ b/codegen/src/worktable/mod.rs @@ -59,6 +59,7 @@ pub fn expand(input: TokenStream) -> syn::Result { let select_impls = generator.gen_query_select_impl()?; let update_impls = generator.gen_query_update_impl()?; let delete_impls = generator.gen_query_delete_impl()?; + let unsized_impl = generator.gen_unsized_impls(); Ok(quote! { #pk_def @@ -73,5 +74,6 @@ pub fn expand(input: TokenStream) -> syn::Result { #select_impls #update_impls #delete_impls + #unsized_impl }) } diff --git a/codegen/src/worktable/model/column.rs b/codegen/src/worktable/model/column.rs index 388cf6bf..8d2ef85e 100644 --- a/codegen/src/worktable/model/column.rs +++ b/codegen/src/worktable/model/column.rs @@ -10,8 +10,13 @@ fn is_float(ident: &Ident) -> bool { matches!(ident.to_string().as_str(), "f64" | "f32") } +fn is_sized(ident: &Ident) -> bool { + !matches!(ident.to_string().as_str(), "String") +} + #[derive(Debug, Clone)] pub struct Columns { + pub is_sized: bool, pub columns_map: HashMap, pub indexes: HashMap, pub primary_keys: Vec, @@ -30,11 +35,15 @@ pub struct Row { impl Columns { pub fn try_from_rows(rows: Vec, input: &TokenStream) -> syn::Result { let mut columns_map = HashMap::new(); + let mut sized = true; let mut pk = vec![]; let mut gen_type = None; for row in rows { let type_ = &row.type_; + if sized { + sized = is_sized(type_) + } let type_ = if is_float(type_) { quote! { ordered_float::OrderedFloat<#type_> } } else { @@ -64,6 +73,7 @@ impl Columns { } Ok(Self { + is_sized: sized, columns_map, indexes: Default::default(), primary_keys: pk, diff --git a/tests/worktable/base.rs b/tests/worktable/base.rs index e4d765a0..3c14c791 100644 --- a/tests/worktable/base.rs +++ b/tests/worktable/base.rs @@ -1,5 +1,6 @@ use std::sync::Arc; use std::time::Duration; + use worktable::prelude::*; use worktable::worktable; @@ -191,6 +192,31 @@ async fn update() { assert!(table.select(2.into()).is_none()) } +#[tokio::test] +async fn update_string() { + let table = TestWorkTable::default(); + let row = TestRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + }; + let pk = table.insert(row.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + let updated = TestRow { + id: pk.clone().into(), + test: 2, + another: 3, + exchange: "much bigger test to make size of new row bigger than previous one".to_string(), + }; + table.update(updated.clone()).await.unwrap(); + let selected_row = table.select(pk).unwrap(); + + assert_eq!(selected_row, updated); + assert_eq!(table.0.data.get_empty_links().first().unwrap(), &first_link); + assert!(table.select(2.into()).is_none()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn update_parallel() { let table = Arc::new(TestWorkTable::default()); diff --git a/tests/worktable/index/update_full.rs b/tests/worktable/index/update_full.rs index c3df65b8..1b3a130d 100644 --- a/tests/worktable/index/update_full.rs +++ b/tests/worktable/index/update_full.rs @@ -118,3 +118,52 @@ async fn update_by_full_row_non_unique_indexes() { .expect("rows"); assert_eq!(updated.first(), None); } + +#[tokio::test] +async fn update_by_full_row_unique_with_string_update() { + let test_table = Test3UniqueWorkTable::default(); + + let attr1_old = "TEST".to_string(); + let attr2_old = 1000; + let attr3_old = 65000; + + let row = Test3UniqueRow { + val: 1, + attr1: attr1_old.clone(), + attr2: attr2_old, + attr3: attr3_old, + id: 0, + }; + + let attr1_new = "TEST 1337 TO BE BIGGER THAN PREVIOUS".to_string(); + let attr2_new = 1337; + let attr3_new = 1337; + + let pk = test_table.insert(row.clone()).unwrap(); + test_table + .update(Test3UniqueRow { + attr1: attr1_new.clone(), + id: pk.clone().into(), + val: row.val, + attr2: attr2_new, + attr3: attr3_new, + }) + .await + .unwrap(); + + // Checks idx updated + let updated = test_table.select_by_attr1(attr1_new.clone()); + assert_eq!(updated.unwrap().attr1, attr1_new); + let updated = test_table.select_by_attr2(attr2_new); + assert_eq!(updated.unwrap().attr2, attr2_new); + let updated = test_table.select_by_attr3(attr3_new); + assert_eq!(updated.unwrap().attr3, attr3_new); + + // Check old idx removed + let updated = test_table.select_by_attr1(attr1_old.clone()); + assert_eq!(updated, None); + let updated = test_table.select_by_attr2(attr2_old); + assert_eq!(updated, None); + let updated = test_table.select_by_attr3(attr3_old); + assert_eq!(updated, None); +} diff --git a/tests/worktable/mod.rs b/tests/worktable/mod.rs index 448bd4cc..ce0b1438 100644 --- a/tests/worktable/mod.rs +++ b/tests/worktable/mod.rs @@ -7,5 +7,6 @@ mod float; mod index; mod option; mod tuple_primary_key; +mod unsized_; mod uuid; mod with_enum; diff --git a/tests/worktable/unsized_.rs b/tests/worktable/unsized_.rs new file mode 100644 index 00000000..971a6971 --- /dev/null +++ b/tests/worktable/unsized_.rs @@ -0,0 +1,366 @@ +use worktable::prelude::*; +use worktable::worktable; + +worktable! ( + name: Test, + columns: { + id: u64 primary_key autoincrement, + test: i64, + another: u64, + exchange: String, + }, + indexes: { + test_idx: test unique, + exchnage_idx: exchange, + another_idx: another, + } + queries: { + update: { + ExchangeByTest(exchange) by test, + ExchangeById(exchange) by id, + ExchangeByAbother(exchange) by another, + } + } +); + +#[tokio::test] +async fn test_update_string_by_unique() { + let table = TestWorkTable::default(); + let row = TestRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + }; + let pk = table.insert(row.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = ExchangeByTestQuery { + exchange: "bigger test to test string update".to_string(), + }; + table.update_exchange_by_test(row, 1).await.unwrap(); + + let row = table.select_by_test(1).unwrap(); + + assert_eq!( + row, + TestRow { + id: 0, + test: 1, + another: 1, + exchange: "bigger test to test string update".to_string(), + } + ); + assert_eq!(table.0.data.get_empty_links().first().unwrap(), &first_link) +} + +#[tokio::test] +async fn test_update_string_by_pk() { + let table = TestWorkTable::default(); + let row = TestRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + }; + let pk = table.insert(row.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = ExchangeByIdQuery { + exchange: "bigger test to test string update".to_string(), + }; + table.update_exchange_by_id(row, pk).await.unwrap(); + + let row = table.select_by_test(1).unwrap(); + + assert_eq!( + row, + TestRow { + id: 0, + test: 1, + another: 1, + exchange: "bigger test to test string update".to_string(), + } + ); + assert_eq!(table.0.data.get_empty_links().first().unwrap(), &first_link) +} + +#[tokio::test] +async fn test_update_string_by_non_unique() { + let table = TestWorkTable::default(); + let row1 = TestRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + }; + let pk = table.insert(row1.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + let row2 = TestRow { + id: table.get_next_pk().into(), + test: 2, + another: 1, + exchange: "test".to_string(), + }; + let pk = table.insert(row2.clone()).unwrap(); + let second_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = ExchangeByAbotherQuery { + exchange: "bigger test to test string update".to_string(), + }; + table.update_exchange_by_abother(row, 1).await.unwrap(); + + let all = table.select_all().execute().unwrap(); + + assert_eq!(all.len(), 2); + assert_eq!( + &all[0], + &TestRow { + id: 0, + test: 1, + another: 1, + exchange: "bigger test to test string update".to_string(), + } + ); + assert_eq!( + &all[1], + &TestRow { + id: 1, + test: 2, + another: 1, + exchange: "bigger test to test string update".to_string(), + } + ); + let empty_links = table.0.data.get_empty_links(); + assert_eq!(empty_links.len(), 2); + assert!(empty_links.contains(&first_link)); + assert!(empty_links.contains(&second_link)) +} + +worktable! ( + name: TestMoreStrings, + columns: { + id: u64 primary_key autoincrement, + test: i64, + another: u64, + exchange: String, + some_string: String, + other_srting: String, + }, + indexes: { + test_idx: test unique, + exchnage_idx: exchange, + another_idx: another, + } + queries: { + update: { + ExchangeAndSomeByTest(exchange, some_string) by test, + ExchangeAndSomeById(exchange, some_string) by id, + ExchangeAndSomeByAnother(exchange, some_string) by another, + SomeOtherByExchange(some_string, other_srting) by exchange, + } + } +); + +#[tokio::test] +async fn test_update_many_strings_by_unique() { + let table = TestMoreStringsWorkTable::default(); + let row = TestMoreStringsRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + some_string: "some".to_string(), + other_srting: "other".to_string(), + }; + let pk = table.insert(row.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = ExchangeAndSomeByTestQuery { + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + }; + table + .update_exchange_and_some_by_test(row, 1) + .await + .unwrap(); + + let row = table.select_by_test(1).unwrap(); + + assert_eq!( + row, + TestMoreStringsRow { + id: 0, + test: 1, + another: 1, + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + other_srting: "other".to_string(), + } + ); + assert_eq!(table.0.data.get_empty_links().first().unwrap(), &first_link) +} + +#[tokio::test] +async fn test_update_many_strings_by_pk() { + let table = TestMoreStringsWorkTable::default(); + let row = TestMoreStringsRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + some_string: "some".to_string(), + other_srting: "other".to_string(), + }; + let pk = table.insert(row.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = ExchangeAndSomeByIdQuery { + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + }; + table.update_exchange_and_some_by_id(row, pk).await.unwrap(); + + let row = table.select_by_test(1).unwrap(); + + assert_eq!( + row, + TestMoreStringsRow { + id: 0, + test: 1, + another: 1, + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + other_srting: "other".to_string(), + } + ); + assert_eq!(table.0.data.get_empty_links().first().unwrap(), &first_link) +} + +#[tokio::test] +async fn test_update_many_strings_by_non_unique() { + let table = TestMoreStringsWorkTable::default(); + let row1 = TestMoreStringsRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + some_string: "some".to_string(), + other_srting: "other".to_string(), + }; + let pk = table.insert(row1.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + let row2 = TestMoreStringsRow { + id: table.get_next_pk().into(), + test: 2, + another: 1, + exchange: "test another".to_string(), + some_string: "some".to_string(), + other_srting: "other".to_string(), + }; + let pk = table.insert(row2.clone()).unwrap(); + let second_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = ExchangeAndSomeByAnotherQuery { + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + }; + table + .update_exchange_and_some_by_another(row, 1) + .await + .unwrap(); + + let all = table.select_all().execute().unwrap(); + + assert_eq!(all.len(), 2); + assert_eq!( + &all[0], + &TestMoreStringsRow { + id: 0, + test: 1, + another: 1, + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + other_srting: "other".to_string(), + } + ); + assert_eq!( + &all[1], + &TestMoreStringsRow { + id: 1, + test: 2, + another: 1, + exchange: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + other_srting: "other".to_string(), + } + ); + let empty_links = table.0.data.get_empty_links(); + assert_eq!(empty_links.len(), 2); + assert!(empty_links.contains(&first_link)); + assert!(empty_links.contains(&second_link)) +} + +#[tokio::test] +async fn test_update_many_strings_by_string() { + let table = TestMoreStringsWorkTable::default(); + let row1 = TestMoreStringsRow { + id: table.get_next_pk().into(), + test: 1, + another: 1, + exchange: "test".to_string(), + some_string: "something".to_string(), + other_srting: "other er".to_string(), + }; + let pk = table.insert(row1.clone()).unwrap(); + let first_link = table.0.pk_map.get(&pk).unwrap().get().value; + let row2 = TestMoreStringsRow { + id: table.get_next_pk().into(), + test: 2, + another: 1, + exchange: "test".to_string(), + some_string: "some ome".to_string(), + other_srting: "other".to_string(), + }; + let pk = table.insert(row2.clone()).unwrap(); + let second_link = table.0.pk_map.get(&pk).unwrap().get().value; + + let row = SomeOtherByExchangeQuery { + other_srting: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + }; + table + .update_some_other_by_exchange(row, "test".to_string()) + .await + .unwrap(); + + let all = table.select_all().execute().unwrap(); + + assert_eq!(all.len(), 2); + assert_eq!( + &all[0], + &TestMoreStringsRow { + id: 0, + test: 1, + another: 1, + other_srting: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + exchange: "test".to_string(), + } + ); + assert_eq!( + &all[1], + &TestMoreStringsRow { + id: 1, + test: 2, + another: 1, + other_srting: "bigger test to test string update".to_string(), + some_string: "some bigger some to test".to_string(), + exchange: "test".to_string(), + } + ); + let empty_links = table.0.data.get_empty_links(); + assert_eq!(empty_links.len(), 2); + assert!(empty_links.contains(&first_link)); + assert!(empty_links.contains(&second_link)) +}