diff --git a/Cargo.toml b/Cargo.toml index 485f3c91..e665a16a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["codegen", "examples", "performance_measurement", "performance_measur [package] name = "worktable" -version = "0.7.2" +version = "0.8.0" edition = "2024" authors = ["Handy-caT"] license = "MIT" @@ -27,9 +27,9 @@ lockfree = { version = "0.5.1" } fastrand = "2.3.0" futures = "0.3.30" uuid = { version = "1.10.0", features = ["v4", "v7"] } -data_bucket = "0.2.10" +data_bucket = "0.3.0" # data_bucket = { git = "https://github.com/pathscale/DataBucket", branch = "page_cdc_correction", version = "0.2.7" } -# data_bucket = { path = "../DataBucket", version = "0.2.9" } +# data_bucket = { path = "../DataBucket", version = "0.2.10" } performance_measurement_codegen = { path = "performance_measurement/codegen", version = "0.1.0", optional = true } performance_measurement = { path = "performance_measurement", version = "0.1.0", optional = true } # indexset = { version = "0.12.3", features = ["concurrent", "cdc", "multimap"] } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ea69dc85..26698b74 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -3,4 +3,14 @@ name = "wt-examples" version = "0.1.0" edition = "2021" -[dependencies] \ No newline at end of file +[dependencies] +worktable = { path = "../", version = "0.8.0" } +tokio = { version = "1.39.2", features = ["full"] } +rkyv = { version = "0.8.9", features = ["uuid-1"] } +eyre = "0.6.12" +serde = { version = "1.0.215", features = ["derive"] } +futures = "0.3.30" +uuid = { version = "1.8.0", features = ["v4", "serde"] } +derive_more = { version = "1.0.0", features = ["full"] } +atomic_float = "1.1.0" +rand = "0.9.1" diff --git a/examples/src/main.rs b/examples/src/main.rs index 15514a6a..f328e4d9 100644 --- a/examples/src/main.rs +++ b/examples/src/main.rs @@ -1,107 +1 @@ -// use futures::executor::block_on; -// use worktable::prelude::*; -// use worktable::worktable; -// -// #[tokio::main] -// async fn main() { -// // describe WorkTable -// worktable!( -// name: My, -// persist: true, -// columns: { -// id: u64 primary_key autoincrement, -// val: i64, -// test: i32, -// attr: String, -// attr2: i32, -// attr_float: f64, -// attr_string: String, -// -// }, -// indexes: { -// idx1: attr, -// idx2: attr2 unique, -// idx3: attr_string, -// }, -// queries: { -// update: { -// ValById(val) by id, -// AllAttrById(attr, attr2) by id, -// UpdateOptionalById(test) by id, -// }, -// delete: { -// ByAttr() by attr, -// ById() by id, -// } -// } -// ); -// -// // Init Worktable -// let config = PersistenceConfig::new("data", "data"); -// let my_table = MyWorkTable::new(config).await.unwrap(); -// -// // WT rows (has prefix My because of table name) -// let row = MyRow { -// val: 777, -// attr: "Attribute0".to_string(), -// attr2: 345, -// test: 1, -// id: 0, -// attr_float: 100.0, -// attr_string: "String_attr0".to_string(), -// }; -// -// for i in 2..1000000_i64 { -// let row = MyRow { -// val: 777, -// attr: format!("Attribute{}", i), -// attr2: 345 + i as i32, -// test: i as i32, -// id: i as u64, -// attr_float: 100.0 + i as f64, -// attr_string: format!("String_attr{}", i), -// }; -// -// my_table.insert(row).unwrap(); -// } -// -// // insert -// let pk: MyPrimaryKey = my_table.insert(row).expect("primary key"); -// -// // Select ALL records from WT -// let _select_all = my_table.select_all().execute(); -// //println!("Select All {:?}", select_all); -// -// // Select All records with attribute TEST -// let _select_all = my_table.select_all().execute(); -// //println!("Select All {:?}", select_all); -// -// // Select by Idx -// //let _select_by_attr = my_table -// // .select_by_attr("Attribute1".to_string()) -// // .execute() -// //r .unwrap(); -// -// //for row in select_by_attr { -// // println!("Select by idx, row {:?}", row); -// //} -// -// // Update Value query -// let update = my_table.update_val_by_id(ValByIdQuery { val: 1337 }, pk.clone()); -// let _ = block_on(update); -// -// let _select_all = my_table.select_all().execute(); -// //println!("Select after update val {:?}", select_all); -// -// let delete = my_table.delete(pk); -// let _ = block_on(delete); -// -// let _select_all = my_table.select_all().execute(); -// //println!("Select after delete {:?}", select_all); -// -// let info = my_table.system_info(); -// -// println!("{info}"); -// } - fn main() {} diff --git a/src/index/unsized_node.rs b/src/index/unsized_node.rs index 40f7033a..6b62e8d5 100644 --- a/src/index/unsized_node.rs +++ b/src/index/unsized_node.rs @@ -16,7 +16,7 @@ where { inner: Vec, length_capacity: usize, - length_without_deleted: usize, + removed_length: usize, length: usize, } @@ -45,9 +45,23 @@ where inner, length, length_capacity, - length_without_deleted: length, + removed_length: 0, } } + + pub fn rebuild(&mut self) { + self.length = self + .inner + .last() + .expect("should not rebuild on empty node") + .aligned_size(); + self.length += UNSIZED_HEADER_LENGTH as usize; + for value in self.inner.iter() { + self.length += value.aligned_size(); + self.length += UnsizedIndexPageUtility::::slots_value_size(); + } + self.removed_length = 0; + } } impl NodeLike for UnsizedNode @@ -59,7 +73,7 @@ where inner: Vec::new(), length_capacity: capacity, length: UNSIZED_HEADER_LENGTH as usize, - length_without_deleted: UNSIZED_HEADER_LENGTH as usize, + removed_length: 0, } } @@ -68,10 +82,10 @@ where } fn halve(&mut self) -> Self { - let middle_length = (self.length_without_deleted + let middle_length = (self.length + - self.removed_length - (self.max().unwrap().aligned_size() + UNSIZED_HEADER_LENGTH as usize)) / 2; - let current_node_id_size = self.max().unwrap().aligned_size(); let mut middle_variance = f64::INFINITY; let mut ind = false; let mut i = 1; @@ -79,7 +93,9 @@ where let mut middle_idx = 0; let mut iter = self.inner.iter(); while !ind { - let val = iter.next().expect("we should stop before node's end"); + let Some(val) = iter.next() else { + break; + }; current_length += val.aligned_size(); current_length += UnsizedIndexPageUtility::::slots_value_size(); let current_middle_variance = @@ -96,19 +112,8 @@ where } let new_inner = self.inner.split_off(middle_idx); - let node_id_len = new_inner.last().unwrap().aligned_size(); - let split = Self { - inner: new_inner, - length_capacity: self.length_capacity, - length: self.length_without_deleted - (current_node_id_size + current_length) - + node_id_len, - length_without_deleted: self.length_without_deleted - - (current_node_id_size + current_length) - + node_id_len, - }; - self.length = - current_length + self.max().unwrap().aligned_size() + UNSIZED_HEADER_LENGTH as usize; - self.length_without_deleted = self.length; + let split = Self::from_inner(new_inner, self.length_capacity); + self.rebuild(); split } @@ -134,13 +139,10 @@ where // Node id is stored separately too, so we need to count node_id twice self.length -= node_id_len; self.length += value_size; - self.length_without_deleted -= node_id_len; - self.length_without_deleted += value_size; } self.length += value_size; self.length += UnsizedIndexPageUtility::::slots_value_size(); - self.length_without_deleted += value_size; - self.length_without_deleted += UnsizedIndexPageUtility::::slots_value_size(); + (true, idx) } (false, idx) => (false, idx), @@ -172,16 +174,14 @@ where where T: Borrow, { - let node_id_len = self.max().map(|v| v.aligned_size()).unwrap_or(0); // TODO: Refactor this when empty links logic will be added to the page if let Some((val, i)) = NodeLike::delete(&mut self.inner, value) { - let new_node_id_len = self.max().map(|v| v.aligned_size()).unwrap_or(0); - if new_node_id_len != node_id_len { - self.length_without_deleted -= node_id_len; - self.length_without_deleted += new_node_id_len; + self.removed_length += + val.aligned_size() + UnsizedIndexPageUtility::::slots_value_size(); + + if self.removed_length > self.length_capacity / 2 { + self.rebuild() } - self.length_without_deleted -= val.aligned_size(); - self.length_without_deleted -= UnsizedIndexPageUtility::::slots_value_size(); Some((val, i)) } else { None @@ -246,10 +246,10 @@ mod test { assert_eq!(node.length, node.length_capacity); let split = node.halve(); assert_eq!(node.length, 152); - assert_eq!(node.length_without_deleted, 152); + assert_eq!(node.removed_length, 0); assert_eq!(node.inner.len(), 2); assert_eq!(split.length, 136); - assert_eq!(split.length_without_deleted, 136); + assert_eq!(split.removed_length, 0); assert_eq!(split.inner.len(), 1); } @@ -258,10 +258,10 @@ mod test { let mut node = UnsizedNode::::with_capacity(200); node.insert(String::from_utf8(vec![b'1'; 16]).unwrap()); assert_eq!(node.length, 120); - assert_eq!(node.length_without_deleted, 120); + assert_eq!(node.removed_length, 0); node.delete(&String::from_utf8(vec![b'1'; 16]).unwrap()); assert_eq!(node.length, 120); - assert_eq!(node.length_without_deleted, 64); + assert_eq!(node.removed_length, 32); } #[test] @@ -270,10 +270,10 @@ mod test { node.insert(String::from_utf8(vec![b'1'; 16]).unwrap()); node.insert(String::from_utf8(vec![b'2'; 24]).unwrap()); assert_eq!(node.length, 168); - assert_eq!(node.length_without_deleted, 168); + assert_eq!(node.removed_length, 0); node.delete(&String::from_utf8(vec![b'2'; 24]).unwrap()); assert_eq!(node.length, 168); - assert_eq!(node.length_without_deleted, 120); + assert_eq!(node.removed_length, 40); } #[test] diff --git a/tests/data/expected/space_index_unsized/indexset/process_create_node.wt.idx b/tests/data/expected/space_index_unsized/indexset/process_create_node.wt.idx index b95bb2ab..4b55de09 100644 Binary files a/tests/data/expected/space_index_unsized/indexset/process_create_node.wt.idx and b/tests/data/expected/space_index_unsized/indexset/process_create_node.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/indexset/process_insert_at.wt.idx b/tests/data/expected/space_index_unsized/indexset/process_insert_at.wt.idx index b550d959..54b6055b 100644 Binary files a/tests/data/expected/space_index_unsized/indexset/process_insert_at.wt.idx and b/tests/data/expected/space_index_unsized/indexset/process_insert_at.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx b/tests/data/expected/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx index 026114ef..2d7d9ee4 100644 Binary files a/tests/data/expected/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx and b/tests/data/expected/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_create_node.wt.idx b/tests/data/expected/space_index_unsized/process_create_node.wt.idx index 4161a40f..4601d2a9 100644 Binary files a/tests/data/expected/space_index_unsized/process_create_node.wt.idx and b/tests/data/expected/space_index_unsized/process_create_node.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_create_node_after_remove.wt.idx b/tests/data/expected/space_index_unsized/process_create_node_after_remove.wt.idx index 4f4f18de..92928f9c 100644 Binary files a/tests/data/expected/space_index_unsized/process_create_node_after_remove.wt.idx and b/tests/data/expected/space_index_unsized/process_create_node_after_remove.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_create_second_node.wt.idx b/tests/data/expected/space_index_unsized/process_create_second_node.wt.idx index c0fde358..06bdd5d7 100644 Binary files a/tests/data/expected/space_index_unsized/process_create_second_node.wt.idx and b/tests/data/expected/space_index_unsized/process_create_second_node.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_insert_at.wt.idx b/tests/data/expected/space_index_unsized/process_insert_at.wt.idx index e7b918c9..e6942d81 100644 Binary files a/tests/data/expected/space_index_unsized/process_insert_at.wt.idx and b/tests/data/expected/space_index_unsized/process_insert_at.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_insert_at_big_amount.wt.idx b/tests/data/expected/space_index_unsized/process_insert_at_big_amount.wt.idx index b4ad84ef..48cd3566 100644 Binary files a/tests/data/expected/space_index_unsized/process_insert_at_big_amount.wt.idx and b/tests/data/expected/space_index_unsized/process_insert_at_big_amount.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_insert_at_removed_place.wt.idx b/tests/data/expected/space_index_unsized/process_insert_at_removed_place.wt.idx index ff84db94..2edd226e 100644 Binary files a/tests/data/expected/space_index_unsized/process_insert_at_removed_place.wt.idx and b/tests/data/expected/space_index_unsized/process_insert_at_removed_place.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_insert_at_with_node_id_update.wt.idx b/tests/data/expected/space_index_unsized/process_insert_at_with_node_id_update.wt.idx index 193a6083..809410fb 100644 Binary files a/tests/data/expected/space_index_unsized/process_insert_at_with_node_id_update.wt.idx and b/tests/data/expected/space_index_unsized/process_insert_at_with_node_id_update.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_remove_at.wt.idx b/tests/data/expected/space_index_unsized/process_remove_at.wt.idx index 3f29badb..ca1fd08f 100644 Binary files a/tests/data/expected/space_index_unsized/process_remove_at.wt.idx and b/tests/data/expected/space_index_unsized/process_remove_at.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_remove_at_node_id.wt.idx b/tests/data/expected/space_index_unsized/process_remove_at_node_id.wt.idx index 531577f6..bd772e9a 100644 Binary files a/tests/data/expected/space_index_unsized/process_remove_at_node_id.wt.idx and b/tests/data/expected/space_index_unsized/process_remove_at_node_id.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_remove_node.wt.idx b/tests/data/expected/space_index_unsized/process_remove_node.wt.idx index 259fbd0b..c2cbcadf 100644 Binary files a/tests/data/expected/space_index_unsized/process_remove_node.wt.idx and b/tests/data/expected/space_index_unsized/process_remove_node.wt.idx differ diff --git a/tests/data/expected/space_index_unsized/process_split_node.wt.idx b/tests/data/expected/space_index_unsized/process_split_node.wt.idx index dfab1421..caf696f8 100644 Binary files a/tests/data/expected/space_index_unsized/process_split_node.wt.idx and b/tests/data/expected/space_index_unsized/process_split_node.wt.idx differ diff --git a/tests/data/space_index_unsized/indexset/process_create_node.wt.idx b/tests/data/space_index_unsized/indexset/process_create_node.wt.idx index b95bb2ab..4b55de09 100644 Binary files a/tests/data/space_index_unsized/indexset/process_create_node.wt.idx and b/tests/data/space_index_unsized/indexset/process_create_node.wt.idx differ diff --git a/tests/data/space_index_unsized/indexset/process_insert_at.wt.idx b/tests/data/space_index_unsized/indexset/process_insert_at.wt.idx index b550d959..54b6055b 100644 Binary files a/tests/data/space_index_unsized/indexset/process_insert_at.wt.idx and b/tests/data/space_index_unsized/indexset/process_insert_at.wt.idx differ diff --git a/tests/data/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx b/tests/data/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx index 026114ef..2d7d9ee4 100644 Binary files a/tests/data/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx and b/tests/data/space_index_unsized/indexset/process_insert_at_big_amount.wt.idx differ diff --git a/tests/data/space_index_unsized/process_create_node.wt.idx b/tests/data/space_index_unsized/process_create_node.wt.idx index 4161a40f..4601d2a9 100644 Binary files a/tests/data/space_index_unsized/process_create_node.wt.idx and b/tests/data/space_index_unsized/process_create_node.wt.idx differ diff --git a/tests/data/space_index_unsized/process_create_node_after_remove.wt.idx b/tests/data/space_index_unsized/process_create_node_after_remove.wt.idx index 4f4f18de..92928f9c 100644 Binary files a/tests/data/space_index_unsized/process_create_node_after_remove.wt.idx and b/tests/data/space_index_unsized/process_create_node_after_remove.wt.idx differ diff --git a/tests/data/space_index_unsized/process_create_second_node.wt.idx b/tests/data/space_index_unsized/process_create_second_node.wt.idx index c0fde358..06bdd5d7 100644 Binary files a/tests/data/space_index_unsized/process_create_second_node.wt.idx and b/tests/data/space_index_unsized/process_create_second_node.wt.idx differ diff --git a/tests/data/space_index_unsized/process_insert_at.wt.idx b/tests/data/space_index_unsized/process_insert_at.wt.idx index e7b918c9..e6942d81 100644 Binary files a/tests/data/space_index_unsized/process_insert_at.wt.idx and b/tests/data/space_index_unsized/process_insert_at.wt.idx differ diff --git a/tests/data/space_index_unsized/process_insert_at_big_amount.wt.idx b/tests/data/space_index_unsized/process_insert_at_big_amount.wt.idx index b4ad84ef..48cd3566 100644 Binary files a/tests/data/space_index_unsized/process_insert_at_big_amount.wt.idx and b/tests/data/space_index_unsized/process_insert_at_big_amount.wt.idx differ diff --git a/tests/data/space_index_unsized/process_insert_at_removed_place.wt.idx b/tests/data/space_index_unsized/process_insert_at_removed_place.wt.idx index ff84db94..2edd226e 100644 Binary files a/tests/data/space_index_unsized/process_insert_at_removed_place.wt.idx and b/tests/data/space_index_unsized/process_insert_at_removed_place.wt.idx differ diff --git a/tests/data/space_index_unsized/process_insert_at_with_node_id_update.wt.idx b/tests/data/space_index_unsized/process_insert_at_with_node_id_update.wt.idx index 193a6083..809410fb 100644 Binary files a/tests/data/space_index_unsized/process_insert_at_with_node_id_update.wt.idx and b/tests/data/space_index_unsized/process_insert_at_with_node_id_update.wt.idx differ diff --git a/tests/data/space_index_unsized/process_remove_at.wt.idx b/tests/data/space_index_unsized/process_remove_at.wt.idx index 3f29badb..ca1fd08f 100644 Binary files a/tests/data/space_index_unsized/process_remove_at.wt.idx and b/tests/data/space_index_unsized/process_remove_at.wt.idx differ diff --git a/tests/data/space_index_unsized/process_remove_at_node_id.wt.idx b/tests/data/space_index_unsized/process_remove_at_node_id.wt.idx index 531577f6..bd772e9a 100644 Binary files a/tests/data/space_index_unsized/process_remove_at_node_id.wt.idx and b/tests/data/space_index_unsized/process_remove_at_node_id.wt.idx differ diff --git a/tests/data/space_index_unsized/process_remove_node.wt.idx b/tests/data/space_index_unsized/process_remove_node.wt.idx index 259fbd0b..c2cbcadf 100644 Binary files a/tests/data/space_index_unsized/process_remove_node.wt.idx and b/tests/data/space_index_unsized/process_remove_node.wt.idx differ diff --git a/tests/data/space_index_unsized/process_split_node.wt.idx b/tests/data/space_index_unsized/process_split_node.wt.idx index dfab1421..caf696f8 100644 Binary files a/tests/data/space_index_unsized/process_split_node.wt.idx and b/tests/data/space_index_unsized/process_split_node.wt.idx differ diff --git a/tests/worktable/index/insert.rs b/tests/worktable/index/insert.rs index 833c05de..93e67a2c 100644 --- a/tests/worktable/index/insert.rs +++ b/tests/worktable/index/insert.rs @@ -236,6 +236,53 @@ fn insert_when_unique_violated() { h.join().unwrap(); } +#[test] +fn insert_after_unique_violated() { + let table = Arc::new(TestWorkTable::default()); + + let row = TestRow { + id: table.get_next_pk().into(), + val: 13, + attr1: "Attribute".to_string(), + attr2: i16::MIN, + attr3: 123456789, + attr4: "Attribute4".to_string(), + }; + let _ = table.insert(row.clone()).unwrap(); + + let row_new_attr_2 = 128; + let row_new_attr_4 = row.attr4.clone(); + + for _ in 0..5_000 { + let row = TestRow { + id: table.get_next_pk().into(), + val: 13, + attr1: "Attribute".to_string(), + attr2: row_new_attr_2, + attr3: 123456789, + attr4: row_new_attr_4.clone(), + }; + assert!(table.insert(row).is_err()); + } + + for i in 2..5_000 { + let attr2 = if i % 2 == 0 { + i as i16 / 2 + } else { + -i as i16 / 2 + }; + let row = TestRow { + id: table.get_next_pk().into(), + val: 13, + attr1: "Attribute".to_string(), + attr2, + attr3: 123456789, + attr4: format!("{i}"), + }; + assert!(table.insert(row).is_ok()); + } +} + #[test] fn insert_when_pk_violated() { let table = Arc::new(TestWorkTable::default()); @@ -275,3 +322,62 @@ fn insert_when_pk_violated() { h.join().unwrap(); } + +worktable!( + name: TestStrings, + columns: { + id: u64 primary_key autoincrement, + attr1: String, + attr2: String, + attr3: String, + }, + indexes: { + attr1_idx: attr1, + attr2_idx: attr2 unique, + attr4_idx: attr3 unique, + } +); + +#[test] +fn insert_after_unique_violated_strings() { + let table = Arc::new(TestStringsWorkTable::default()); + + let row = TestStringsRow { + id: table.get_next_pk().into(), + attr1: "Attribute_1".to_string(), + attr2: "Attribute_2".to_string(), + attr3: "Attribute_3".to_string(), + }; + let _ = table.insert(row.clone()).unwrap(); + + let row_new_attr_3 = row.attr3.clone(); + for _ in 0..5_000 { + let row = TestStringsRow { + id: table.get_next_pk().into(), + attr1: "Attribute_1_NEW".to_string(), + attr2: "Attribute_2_NEW".to_string(), + attr3: row_new_attr_3.clone(), + }; + assert!(table.insert(row).is_err()); + } + let row_new_attr_2 = row.attr2.clone(); + for i in 0..5_000 { + let row = TestStringsRow { + id: table.get_next_pk().into(), + attr1: "Attribute_1_NEW".to_string(), + attr2: row_new_attr_2.clone(), + attr3: format!("Attribute_3_{i}"), + }; + assert!(table.insert(row).is_err()); + } + + for i in 0..5_000 { + let row = TestStringsRow { + id: table.get_next_pk().into(), + attr1: format!("Attribute_1_{i}"), + attr2: format!("Attribute_2_{i}"), + attr3: format!("Attribute_3_{i}"), + }; + assert!(table.insert(row).is_ok()); + } +} diff --git a/tests/worktable/index/update_full.rs b/tests/worktable/index/update_full.rs index b4411115..fa57ca17 100644 --- a/tests/worktable/index/update_full.rs +++ b/tests/worktable/index/update_full.rs @@ -306,7 +306,7 @@ async fn update_by_full_row_with_reinsert_and_primary_key_violation() { }; test_table.insert(row2.clone()).unwrap(); let mut update = row1.clone(); - update.id = row2.id.clone(); + update.id = row2.id; update.attr1 = "TEST_______________________1".to_string(); assert!(test_table.update(update).await.is_err()); @@ -389,7 +389,7 @@ async fn update_by_full_row_with_secondary_unique_violation() { }; test_table.insert(row2.clone()).unwrap(); let mut update = row1.clone(); - update.attr2 = row2.attr2.clone(); + update.attr2 = row2.attr2; assert!(test_table.update(update).await.is_err()); assert_eq!(test_table.select(row1.id).unwrap(), row1);