diff --git a/codegen/src/common/model/primary_key.rs b/codegen/src/common/model/primary_key.rs index 6664eea1..bbcb6441 100644 --- a/codegen/src/common/model/primary_key.rs +++ b/codegen/src/common/model/primary_key.rs @@ -1,10 +1,10 @@ +use indexmap::IndexMap; use proc_macro2::{Ident, TokenStream}; -use std::collections::HashMap; #[derive(Debug, Clone)] pub struct PrimaryKey { pub ident: Ident, - pub values: HashMap, + pub values: IndexMap, } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/codegen/src/generators/in_memory/primary_key.rs b/codegen/src/generators/in_memory/primary_key.rs index 31de463a..72ac82d5 100644 --- a/codegen/src/generators/in_memory/primary_key.rs +++ b/codegen/src/generators/in_memory/primary_key.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use indexmap::IndexMap; use crate::common::model::{GeneratorType, PrimaryKey}; use crate::common::name_generator::{WorktableNameGenerator, is_unsized_vec}; @@ -27,7 +27,7 @@ impl InMemoryGenerator { .clone(), ) }) - .collect::>(); + .collect::>(); let def = self.gen_primary_key_type()?; let impl_ = self.gen_table_primary_key_impl()?; diff --git a/codegen/src/generators/in_memory/queries/delete.rs b/codegen/src/generators/in_memory/queries/delete.rs index fad4375f..1a27a156 100644 --- a/codegen/src/generators/in_memory/queries/delete.rs +++ b/codegen/src/generators/in_memory/queries/delete.rs @@ -118,7 +118,10 @@ impl InMemoryGenerator { return Err(e); } }; - let row = self.0.select(pk.clone()).unwrap(); + let row = self.0 + .data + .select_non_ghosted(link) + .map_err(WorkTableError::PagesError)?; #process } } else { @@ -129,7 +132,10 @@ impl InMemoryGenerator { .get_value(&pk) .map(Into::into) .ok_or(WorkTableError::NotFound)?; - let row = self.0.select(pk.clone()).unwrap(); + let row = self.0 + .data + .select_non_ghosted(link) + .map_err(WorkTableError::PagesError)?; #process } } diff --git a/codegen/src/generators/persist/primary_key.rs b/codegen/src/generators/persist/primary_key.rs index 68622fcf..5929f0bf 100644 --- a/codegen/src/generators/persist/primary_key.rs +++ b/codegen/src/generators/persist/primary_key.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use indexmap::IndexMap; use crate::common::model::{GeneratorType, PrimaryKey}; use crate::common::name_generator::{WorktableNameGenerator, is_unsized_vec}; @@ -26,7 +26,7 @@ impl PersistGenerator { .clone(), ) }) - .collect::>(); + .collect::>(); let def = self.gen_primary_key_type()?; let impl_ = self.gen_table_primary_key_impl()?; diff --git a/codegen/src/generators/persist/queries/delete.rs b/codegen/src/generators/persist/queries/delete.rs index 0c3e2290..9a4f56bf 100644 --- a/codegen/src/generators/persist/queries/delete.rs +++ b/codegen/src/generators/persist/queries/delete.rs @@ -111,7 +111,10 @@ impl PersistGenerator { return Err(e); } }; - let row = self.0.select(pk.clone()).unwrap(); + let row = self.0 + .data + .select_non_ghosted(link) + .map_err(WorkTableError::PagesError)?; #process } } else { @@ -122,7 +125,10 @@ impl PersistGenerator { .get_value(&pk) .map(Into::into) .ok_or(WorkTableError::NotFound)?; - let row = self.0.select(pk.clone()).unwrap(); + let row = self.0 + .data + .select_non_ghosted(link) + .map_err(WorkTableError::PagesError)?; #process } } diff --git a/codegen/src/generators/read_only/primary_key.rs b/codegen/src/generators/read_only/primary_key.rs index 99a89aa6..2fe733ca 100644 --- a/codegen/src/generators/read_only/primary_key.rs +++ b/codegen/src/generators/read_only/primary_key.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use indexmap::IndexMap; use crate::common::model::{GeneratorType, PrimaryKey}; use crate::common::name_generator::{WorktableNameGenerator, is_unsized_vec}; @@ -26,7 +26,7 @@ impl ReadOnlyGenerator { .clone(), ) }) - .collect::>(); + .collect::>(); let def = self.gen_primary_key_type()?; let impl_ = self.gen_table_primary_key_impl()?; diff --git a/codegen/src/worktable/mod.rs b/codegen/src/worktable/mod.rs index 3725345f..0097313f 100644 --- a/codegen/src/worktable/mod.rs +++ b/codegen/src/worktable/mod.rs @@ -137,6 +137,40 @@ mod tests { use super::expand; + fn assert_composite_primary_key_field_order(output: proc_macro2::TokenStream) { + let output = output.to_string(); + let get_primary_key = output + .split("fn get_primary_key") + .nth(1) + .expect("generated TableRow implementation"); + let tenant = get_primary_key + .find("self . tenant_id . clone") + .expect("first primary-key field"); + let record = get_primary_key + .find("self . record_id . clone") + .expect("second primary-key field"); + + assert!(tenant < record, "composite primary-key declaration order changed"); + } + + #[test] + fn composite_primary_key_codegen_preserves_declaration_order() { + for persist in [true, false] { + let output = expand(quote! { + name: CompositePrimaryKeyOrder, + persist: #persist, + columns: { + tenant_id: u64 primary_key, + record_id: u64 primary_key, + value: i64, + }, + }) + .unwrap(); + + assert_composite_primary_key_field_order(output); + } + } + #[test] fn absent_using_keeps_worktables_index_default() { let output = expand(quote! { diff --git a/tests/persistence/tuple_primary_key.rs b/tests/persistence/tuple_primary_key.rs index 03e4b1bc..e7b07b85 100644 --- a/tests/persistence/tuple_primary_key.rs +++ b/tests/persistence/tuple_primary_key.rs @@ -53,6 +53,7 @@ async fn composite_primary_key_survives_mutations_and_reload() { for row in &rows { assert_eq!(table.select((row.tenant_id, row.record_id)), Some(row.clone())); } + table.close().await.unwrap(); } { @@ -84,5 +85,6 @@ async fn composite_primary_key_survives_mutations_and_reload() { assert!(table.select((7, 41)).is_none()); assert_eq!(table.select((7, 42)).unwrap().value, 99); assert_eq!(table.select((8, 1)), Some(rows[2].clone())); + table.close().await.unwrap(); } } diff --git a/tests/worktable/upsert.rs b/tests/worktable/upsert.rs index 7ba61a3b..6cad0670 100644 --- a/tests/worktable/upsert.rs +++ b/tests/worktable/upsert.rs @@ -27,7 +27,10 @@ async fn upsert_completes_under_same_key_churn() { /// Intense variant for the same-key upsert/delete linearization protocol. #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn upsert_completes_under_extreme_same_key_churn() { - churn_run(5_000, 2_000).await; + // Keep this materially heavier than the normal case without making the + // assertion depend on runner speed: 2,000 churn flips perform both an + // upsert and a delete, alongside 4,000 competing upserts. + churn_run(2_000, 1_000).await; } /// A synchronous insert does not participate in the generated async row lock.