From 9b184231816725790f20c2035aad4fcbf1b20c0f Mon Sep 17 00:00:00 2001 From: Handy-caT <37216852+Handy-caT@users.noreply.github.com> Date: Tue, 5 May 2026 16:51:49 +0300 Subject: [PATCH 1/3] add range selects logic --- .../src/generators/in_memory/table/impls.rs | 26 ++ .../generators/in_memory/table/index_fns.rs | 56 ++- codegen/src/generators/persist/table/impls.rs | 26 ++ .../src/generators/persist/table/index_fns.rs | 56 ++- .../src/generators/read_only/table/impls.rs | 26 ++ .../generators/read_only/table/index_fns.rs | 56 ++- tests/worktable/index/mod.rs | 1 + tests/worktable/index/range.rs | 356 ++++++++++++++++++ 8 files changed, 591 insertions(+), 12 deletions(-) create mode 100644 tests/worktable/index/range.rs diff --git a/codegen/src/generators/in_memory/table/impls.rs b/codegen/src/generators/in_memory/table/impls.rs index 526220a..7243e68 100644 --- a/codegen/src/generators/in_memory/table/impls.rs +++ b/codegen/src/generators/in_memory/table/impls.rs @@ -13,6 +13,7 @@ impl InMemoryGenerator { let persisted_impl = self.gen_table_new_fn(); let name_fn = self.gen_table_name_fn(); let select_fn = self.gen_table_select_fn(); + let select_range_fn = self.gen_table_select_range_fn(); let insert_fn = self.gen_table_insert_fn(); let reinsert_fn = self.gen_table_reinsert_fn(); let upsert_fn = self.gen_table_upsert_fn(); @@ -28,6 +29,7 @@ impl InMemoryGenerator { impl #ident { #name_fn #select_fn + #select_range_fn #insert_fn #reinsert_fn #upsert_fn @@ -74,6 +76,30 @@ impl InMemoryGenerator { } } + fn gen_table_select_range_fn(&self) -> TokenStream { + let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); + let row_type = name_generator.get_row_type_ident(); + let primary_key_type = name_generator.get_primary_key_type_ident(); + let column_range_type = name_generator.get_column_range_type_ident(); + let row_fields_ident = name_generator.get_row_fields_enum_ident(); + + quote! { + pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, + impl DoubleEndedIterator + '_, + #column_range_type, + #row_fields_ident> + where + R: std::ops::RangeBounds<#primary_key_type> + { + let rows = self.0.primary_index.pk_map + .range(range) + .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); + + SelectQueryBuilder::new(rows) + } + } + } + fn gen_table_insert_fn(&self) -> TokenStream { let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); let row_type = name_generator.get_row_type_ident(); diff --git a/codegen/src/generators/in_memory/table/index_fns.rs b/codegen/src/generators/in_memory/table/index_fns.rs index 673a539..e72e07f 100644 --- a/codegen/src/generators/in_memory/table/index_fns.rs +++ b/codegen/src/generators/in_memory/table/index_fns.rs @@ -20,8 +20,8 @@ impl InMemoryGenerator { .indexes .iter() .map(|(i, idx)| { - if idx.is_unique { - Self::gen_unique_index_fn(i, idx, &self.columns.columns_map, row_ident.clone()) + let point_fn = if idx.is_unique { + Self::gen_unique_index_fn(i, idx, &self.columns.columns_map, row_ident.clone())? } else { Self::gen_non_unique_index_fn( i, @@ -30,8 +30,19 @@ impl InMemoryGenerator { row_ident.clone(), &column_range_type, &row_fields_ident, - ) - } + )? + }; + + let range_fn = Self::gen_range_index_fn( + i, + idx, + &self.columns.columns_map, + row_ident.clone(), + &column_range_type, + &row_fields_ident, + )?; + + Ok(quote! { #point_fn #range_fn }) }) .collect::, syn::Error>>()?; @@ -111,4 +122,41 @@ impl InMemoryGenerator { } }) } + + fn gen_range_index_fn( + i: &Ident, + idx: &Index, + columns_map: &HashMap, + row_ident: Ident, + column_range_type: &Ident, + row_fields_ident: &Ident, + ) -> syn::Result { + let type_ = columns_map + .get(i) + .ok_or(syn::Error::new(i.span(), "Row not found"))?; + let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site()); + let field_ident = &idx.name; + + let range_bounds = if is_float(type_.to_string().as_str()) { + quote! { std::ops::RangeBounds> } + } else { + quote! { std::ops::RangeBounds<#type_> } + }; + + Ok(quote! { + pub fn #fn_name(&self, range: R) -> SelectQueryBuilder<#row_ident, + impl DoubleEndedIterator + '_, + #column_range_type, + #row_fields_ident> + where + R: #range_bounds + { + let rows = self.0.indexes.#field_ident + .range(range) + .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); + + SelectQueryBuilder::new(rows) + } + }) + } } diff --git a/codegen/src/generators/persist/table/impls.rs b/codegen/src/generators/persist/table/impls.rs index 5f6c845..ab95537 100644 --- a/codegen/src/generators/persist/table/impls.rs +++ b/codegen/src/generators/persist/table/impls.rs @@ -14,6 +14,7 @@ impl PersistGenerator { let name_fn = self.gen_table_name_fn(); let version_fn = self.gen_table_version_fn(); let select_fn = self.gen_table_select_fn(); + let select_range_fn = self.gen_table_select_range_fn(); let insert_fn = self.gen_table_insert_fn(); let reinsert_fn = self.gen_table_reinsert_fn(); let upsert_fn = self.gen_table_upsert_fn(); @@ -30,6 +31,7 @@ impl PersistGenerator { #name_fn #version_fn #select_fn + #select_range_fn #insert_fn #reinsert_fn #upsert_fn @@ -158,6 +160,30 @@ impl PersistGenerator { } } + fn gen_table_select_range_fn(&self) -> TokenStream { + let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); + let row_type = name_generator.get_row_type_ident(); + let primary_key_type = name_generator.get_primary_key_type_ident(); + let column_range_type = name_generator.get_column_range_type_ident(); + let row_fields_ident = name_generator.get_row_fields_enum_ident(); + + quote! { + pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, + impl DoubleEndedIterator + '_, + #column_range_type, + #row_fields_ident> + where + R: std::ops::RangeBounds<#primary_key_type> + { + let rows = self.0.primary_index.pk_map + .range(range) + .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); + + SelectQueryBuilder::new(rows) + } + } + } + fn gen_table_insert_fn(&self) -> TokenStream { let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); let row_type = name_generator.get_row_type_ident(); diff --git a/codegen/src/generators/persist/table/index_fns.rs b/codegen/src/generators/persist/table/index_fns.rs index 1ca1eca..453b827 100644 --- a/codegen/src/generators/persist/table/index_fns.rs +++ b/codegen/src/generators/persist/table/index_fns.rs @@ -20,8 +20,8 @@ impl PersistGenerator { .indexes .iter() .map(|(i, idx)| { - if idx.is_unique { - Self::gen_unique_index_fn(i, idx, &self.columns.columns_map, row_ident.clone()) + let point_fn = if idx.is_unique { + Self::gen_unique_index_fn(i, idx, &self.columns.columns_map, row_ident.clone())? } else { Self::gen_non_unique_index_fn( i, @@ -30,8 +30,19 @@ impl PersistGenerator { row_ident.clone(), &column_range_type, &row_fields_ident, - ) - } + )? + }; + + let range_fn = Self::gen_range_index_fn( + i, + idx, + &self.columns.columns_map, + row_ident.clone(), + &column_range_type, + &row_fields_ident, + )?; + + Ok(quote! { #point_fn #range_fn }) }) .collect::, syn::Error>>()?; @@ -111,4 +122,41 @@ impl PersistGenerator { } }) } + + fn gen_range_index_fn( + i: &Ident, + idx: &Index, + columns_map: &HashMap, + row_ident: Ident, + column_range_type: &Ident, + row_fields_ident: &Ident, + ) -> syn::Result { + let type_ = columns_map + .get(i) + .ok_or(syn::Error::new(i.span(), "Row not found"))?; + let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site()); + let field_ident = &idx.name; + + let range_bounds = if is_float(type_.to_string().as_str()) { + quote! { std::ops::RangeBounds> } + } else { + quote! { std::ops::RangeBounds<#type_> } + }; + + Ok(quote! { + pub fn #fn_name(&self, range: R) -> SelectQueryBuilder<#row_ident, + impl DoubleEndedIterator + '_, + #column_range_type, + #row_fields_ident> + where + R: #range_bounds + { + let rows = self.0.indexes.#field_ident + .range(range) + .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); + + SelectQueryBuilder::new(rows) + } + }) + } } \ No newline at end of file diff --git a/codegen/src/generators/read_only/table/impls.rs b/codegen/src/generators/read_only/table/impls.rs index 060b17b..7e406e0 100644 --- a/codegen/src/generators/read_only/table/impls.rs +++ b/codegen/src/generators/read_only/table/impls.rs @@ -13,6 +13,7 @@ impl ReadOnlyGenerator { let name_fn = self.gen_table_name_fn(); let version_fn = self.gen_table_version_fn(); let select_fn = self.gen_table_select_fn(); + let select_range_fn = self.gen_table_select_range_fn(); let insert_fn = self.gen_table_insert_fn(); let reinsert_fn = self.gen_table_reinsert_fn(); let upsert_fn = self.gen_table_upsert_fn(); @@ -29,6 +30,7 @@ impl ReadOnlyGenerator { #name_fn #version_fn #select_fn + #select_range_fn #insert_fn #reinsert_fn #upsert_fn @@ -154,6 +156,30 @@ impl ReadOnlyGenerator { } } + fn gen_table_select_range_fn(&self) -> TokenStream { + let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string()); + let row_type = name_generator.get_row_type_ident(); + let primary_key_type = name_generator.get_primary_key_type_ident(); + let column_range_type = name_generator.get_column_range_type_ident(); + let row_fields_ident = name_generator.get_row_fields_enum_ident(); + + quote! { + pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, + impl DoubleEndedIterator + '_, + #column_range_type, + #row_fields_ident> + where + R: std::ops::RangeBounds<#primary_key_type> + { + let rows = self.0.primary_index.pk_map + .range(range) + .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); + + SelectQueryBuilder::new(rows) + } + } + } + fn gen_table_insert_fn(&self) -> TokenStream { quote! {} } diff --git a/codegen/src/generators/read_only/table/index_fns.rs b/codegen/src/generators/read_only/table/index_fns.rs index d5a9e9f..a01c90f 100644 --- a/codegen/src/generators/read_only/table/index_fns.rs +++ b/codegen/src/generators/read_only/table/index_fns.rs @@ -20,8 +20,8 @@ impl ReadOnlyGenerator { .indexes .iter() .map(|(i, idx)| { - if idx.is_unique { - Self::gen_unique_index_fn(i, idx, &self.columns.columns_map, row_ident.clone()) + let point_fn = if idx.is_unique { + Self::gen_unique_index_fn(i, idx, &self.columns.columns_map, row_ident.clone())? } else { Self::gen_non_unique_index_fn( i, @@ -30,8 +30,19 @@ impl ReadOnlyGenerator { row_ident.clone(), &column_range_type, &row_fields_ident, - ) - } + )? + }; + + let range_fn = Self::gen_range_index_fn( + i, + idx, + &self.columns.columns_map, + row_ident.clone(), + &column_range_type, + &row_fields_ident, + )?; + + Ok(quote! { #point_fn #range_fn }) }) .collect::, syn::Error>>()?; @@ -111,4 +122,41 @@ impl ReadOnlyGenerator { } }) } + + fn gen_range_index_fn( + i: &Ident, + idx: &Index, + columns_map: &HashMap, + row_ident: Ident, + column_range_type: &Ident, + row_fields_ident: &Ident, + ) -> syn::Result { + let type_ = columns_map + .get(i) + .ok_or(syn::Error::new(i.span(), "Row not found"))?; + let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site()); + let field_ident = &idx.name; + + let range_bounds = if is_float(type_.to_string().as_str()) { + quote! { std::ops::RangeBounds> } + } else { + quote! { std::ops::RangeBounds<#type_> } + }; + + Ok(quote! { + pub fn #fn_name(&self, range: R) -> SelectQueryBuilder<#row_ident, + impl DoubleEndedIterator + '_, + #column_range_type, + #row_fields_ident> + where + R: #range_bounds + { + let rows = self.0.indexes.#field_ident + .range(range) + .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); + + SelectQueryBuilder::new(rows) + } + }) + } } \ No newline at end of file diff --git a/tests/worktable/index/mod.rs b/tests/worktable/index/mod.rs index e949c1e..845d34a 100644 --- a/tests/worktable/index/mod.rs +++ b/tests/worktable/index/mod.rs @@ -1,5 +1,6 @@ mod insert; mod order; +mod range; mod update_by_pk; mod update_full; mod update_query; diff --git a/tests/worktable/index/range.rs b/tests/worktable/index/range.rs new file mode 100644 index 0000000..85fed8c --- /dev/null +++ b/tests/worktable/index/range.rs @@ -0,0 +1,356 @@ +use worktable::prelude::*; +use worktable::worktable; + +worktable!( + name: RangeTest, + columns: { + id: u64 primary_key autoincrement, + value: i64, + name: String, + }, + indexes: { + val_idx: value, + } +); + +worktable!( + name: UniqueRangeTest, + columns: { + id: u64 primary_key autoincrement, + num: u64, + }, + indexes: { + num_idx: num unique, + } +); + +worktable!( + name: PkRangeTest, + columns: { + id: u64 primary_key autoincrement, + data: String, + } +); + +#[test] +fn test_range_select_basic() { + let table = RangeTestWorkTable::default(); + + for v in 0..6 { + table + .insert(RangeTestRow { + id: table.get_next_pk().into(), + value: v * 10, + name: format!("name_{}", v * 10), + }) + .unwrap(); + } + + let results = table.select_by_value_range(10..30).execute().unwrap(); + assert_eq!(results.len(), 2); +} + +#[test] +fn test_range_select_inclusive() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 10000u64; + for n in base..=base + 10 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table + .select_by_num_range(base..=base + 5) + .execute() + .unwrap(); + assert_eq!(results.len(), 6); +} + +#[test] +fn test_range_select_open_from() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 20000u64; + for n in base..=base + 10 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table.select_by_num_range(base + 5..).execute().unwrap(); + assert_eq!(results.len(), 6); +} + +#[test] +fn test_range_select_open_to() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 30000u64; + for n in base..=base + 10 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table.select_by_num_range(..base + 5).execute().unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_range_select_with_limit() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 40000u64; + for n in base..=base + 20 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table + .select_by_num_range(base..) + .limit(5) + .execute() + .unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_range_select_with_offset() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 50000u64; + for n in base..=base + 20 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table + .select_by_num_range(base..=base + 10) + .offset(3) + .execute() + .unwrap(); + assert_eq!(results.len(), 8); +} + +#[test] +fn test_range_select_empty_result() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 60000u64; + for n in base..=base + 10 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table.select_by_num_range(100000..200000).execute().unwrap(); + assert_eq!(results.len(), 0); +} + +#[test] +fn test_range_select_full_range() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 70000u64; + for n in base..=base + 10 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table.select_by_num_range(..).execute().unwrap(); + assert_eq!(results.len(), 11); +} + +#[test] +fn test_range_select_non_unique_multiple_per_key() { + let table = RangeTestWorkTable::default(); + + for (v, suffix) in [(10, "a"), (10, "b"), (10, "c"), (20, "d"), (20, "e")] { + table + .insert(RangeTestRow { + id: table.get_next_pk().into(), + value: v, + name: format!("item_{}", suffix), + }) + .unwrap(); + } + + let results = table.select_by_value_range(10..=20).execute().unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_range_select_with_order() { + let table = UniqueRangeTestWorkTable::default(); + + let base = 80000u64; + for n in base..=base + 10 { + table + .insert(UniqueRangeTestRow { + id: table.get_next_pk().into(), + num: n, + }) + .unwrap(); + } + + let results = table + .select_by_num_range(base..=base + 5) + .order_on(UniqueRangeTestRowFields::Num, Order::Desc) + .execute() + .unwrap(); + + assert_eq!(results.len(), 6); + assert_eq!(results.first().unwrap().num, base + 5); + assert_eq!(results.last().unwrap().num, base); +} + +#[test] +fn test_pk_range_select_basic() { + let table = PkRangeTestWorkTable::default(); + + for i in 0..20 { + table + .insert(PkRangeTestRow { + id: table.get_next_pk().into(), + data: format!("data_{}", i), + }) + .unwrap(); + } + + let results = table + .select_by_pk_range(PkRangeTestPrimaryKey(5)..PkRangeTestPrimaryKey(10)) + .execute() + .unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_pk_range_select_inclusive() { + let table = PkRangeTestWorkTable::default(); + + for i in 0..20 { + table + .insert(PkRangeTestRow { + id: table.get_next_pk().into(), + data: format!("data_{}", i), + }) + .unwrap(); + } + + let results = table + .select_by_pk_range(PkRangeTestPrimaryKey(5)..=PkRangeTestPrimaryKey(10)) + .execute() + .unwrap(); + assert_eq!(results.len(), 6); +} + +#[test] +fn test_pk_range_select_open_from() { + let table = PkRangeTestWorkTable::default(); + + for i in 0..20 { + table + .insert(PkRangeTestRow { + id: table.get_next_pk().into(), + data: format!("data_{}", i), + }) + .unwrap(); + } + + let results = table + .select_by_pk_range(PkRangeTestPrimaryKey(15)..) + .execute() + .unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_pk_range_select_open_to() { + let table = PkRangeTestWorkTable::default(); + + for i in 0..20 { + table + .insert(PkRangeTestRow { + id: table.get_next_pk().into(), + data: format!("data_{}", i), + }) + .unwrap(); + } + + let results = table + .select_by_pk_range(..PkRangeTestPrimaryKey(5)) + .execute() + .unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_pk_range_select_with_limit() { + let table = PkRangeTestWorkTable::default(); + + for i in 0..50 { + table + .insert(PkRangeTestRow { + id: table.get_next_pk().into(), + data: format!("data_{}", i), + }) + .unwrap(); + } + + let results = table + .select_by_pk_range(PkRangeTestPrimaryKey(10)..) + .limit(5) + .execute() + .unwrap(); + assert_eq!(results.len(), 5); +} + +#[test] +fn test_pk_range_select_with_order() { + let table = PkRangeTestWorkTable::default(); + + for i in 0..20 { + table + .insert(PkRangeTestRow { + id: table.get_next_pk().into(), + data: format!("data_{}", i), + }) + .unwrap(); + } + + let results = table + .select_by_pk_range(PkRangeTestPrimaryKey(5)..=PkRangeTestPrimaryKey(10)) + .order_on(PkRangeTestRowFields::Id, Order::Desc) + .execute() + .unwrap(); + + assert_eq!(results.len(), 6); + assert_eq!(results.first().unwrap().id, 10); + assert_eq!(results.last().unwrap().id, 5); +} From cf18b725d62b47c068042d53b9b1607750aa60a5 Mon Sep 17 00:00:00 2001 From: Handy-caT <37216852+Handy-caT@users.noreply.github.com> Date: Tue, 5 May 2026 17:32:01 +0300 Subject: [PATCH 2/3] rework floats to not expose ordered float --- .../generators/in_memory/table/index_fns.rs | 19 +++++-- .../src/generators/persist/table/index_fns.rs | 19 +++++-- .../generators/read_only/table/index_fns.rs | 19 +++++-- tests/worktable/float.rs | 52 +++++++++++++++++++ 4 files changed, 97 insertions(+), 12 deletions(-) diff --git a/codegen/src/generators/in_memory/table/index_fns.rs b/codegen/src/generators/in_memory/table/index_fns.rs index e72e07f..47f6a2a 100644 --- a/codegen/src/generators/in_memory/table/index_fns.rs +++ b/codegen/src/generators/in_memory/table/index_fns.rs @@ -137,10 +137,21 @@ impl InMemoryGenerator { let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site()); let field_ident = &idx.name; - let range_bounds = if is_float(type_.to_string().as_str()) { - quote! { std::ops::RangeBounds> } + let (range_bounds, range_arg) = if is_float(type_.to_string().as_str()) { + ( + quote! { std::ops::RangeBounds<#type_> }, + quote! { + ( + range.start_bound().map(|v| OrderedFloat(*v)), + range.end_bound().map(|v| OrderedFloat(*v)), + ) + }, + ) } else { - quote! { std::ops::RangeBounds<#type_> } + ( + quote! { std::ops::RangeBounds<#type_> }, + quote! { range }, + ) }; Ok(quote! { @@ -152,7 +163,7 @@ impl InMemoryGenerator { R: #range_bounds { let rows = self.0.indexes.#field_ident - .range(range) + .range(#range_arg) .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); SelectQueryBuilder::new(rows) diff --git a/codegen/src/generators/persist/table/index_fns.rs b/codegen/src/generators/persist/table/index_fns.rs index 453b827..c450837 100644 --- a/codegen/src/generators/persist/table/index_fns.rs +++ b/codegen/src/generators/persist/table/index_fns.rs @@ -137,10 +137,21 @@ impl PersistGenerator { let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site()); let field_ident = &idx.name; - let range_bounds = if is_float(type_.to_string().as_str()) { - quote! { std::ops::RangeBounds> } + let (range_bounds, range_arg) = if is_float(type_.to_string().as_str()) { + ( + quote! { std::ops::RangeBounds<#type_> }, + quote! { + ( + range.start_bound().map(|v| OrderedFloat(*v)), + range.end_bound().map(|v| OrderedFloat(*v)), + ) + }, + ) } else { - quote! { std::ops::RangeBounds<#type_> } + ( + quote! { std::ops::RangeBounds<#type_> }, + quote! { range }, + ) }; Ok(quote! { @@ -152,7 +163,7 @@ impl PersistGenerator { R: #range_bounds { let rows = self.0.indexes.#field_ident - .range(range) + .range(#range_arg) .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); SelectQueryBuilder::new(rows) diff --git a/codegen/src/generators/read_only/table/index_fns.rs b/codegen/src/generators/read_only/table/index_fns.rs index a01c90f..50391d3 100644 --- a/codegen/src/generators/read_only/table/index_fns.rs +++ b/codegen/src/generators/read_only/table/index_fns.rs @@ -137,10 +137,21 @@ impl ReadOnlyGenerator { let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site()); let field_ident = &idx.name; - let range_bounds = if is_float(type_.to_string().as_str()) { - quote! { std::ops::RangeBounds> } + let (range_bounds, range_arg) = if is_float(type_.to_string().as_str()) { + ( + quote! { std::ops::RangeBounds<#type_> }, + quote! { + ( + range.start_bound().map(|v| OrderedFloat(*v)), + range.end_bound().map(|v| OrderedFloat(*v)), + ) + }, + ) } else { - quote! { std::ops::RangeBounds<#type_> } + ( + quote! { std::ops::RangeBounds<#type_> }, + quote! { range }, + ) }; Ok(quote! { @@ -152,7 +163,7 @@ impl ReadOnlyGenerator { R: #range_bounds { let rows = self.0.indexes.#field_ident - .range(range) + .range(#range_arg) .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); SelectQueryBuilder::new(rows) diff --git a/tests/worktable/float.rs b/tests/worktable/float.rs index 8dfcfb2..685d089 100644 --- a/tests/worktable/float.rs +++ b/tests/worktable/float.rs @@ -89,3 +89,55 @@ fn select_by_another_test() { assert_eq!(where_200.len(), 1); assert!(where_200.contains(&row3)); } + +#[test] +fn select_by_another_range_test() { + let table = TestFloatWorkTable::default(); + + let rows: Vec = (0..10) + .map(|i| TestFloatRow { + id: table.get_next_pk().into(), + test: i, + another: (i * 10) as f64, + exchange: format!("ex_{}", i), + }) + .collect(); + + for row in &rows { + table.insert(row.clone()).unwrap(); + } + + let results = table.select_by_another_range(20.0..50.0).execute().unwrap(); + assert_eq!(results.len(), 3); + + let results = table + .select_by_another_range(20.0..=50.0) + .execute() + .unwrap(); + assert_eq!(results.len(), 4); + + let results = table.select_by_another_range(70.0..).execute().unwrap(); + assert_eq!(results.len(), 3); + + let results = table.select_by_another_range(..30.0).execute().unwrap(); + assert_eq!(results.len(), 3); + + let results = table.select_by_another_range(..).execute().unwrap(); + assert_eq!(results.len(), 10); + + let results = table + .select_by_another_range(0.0..) + .limit(3) + .execute() + .unwrap(); + assert_eq!(results.len(), 3); + + let results = table + .select_by_another_range(20.0..=50.0) + .order_on(TestFloatRowFields::Another, Order::Desc) + .execute() + .unwrap(); + assert_eq!(results.len(), 4); + assert_eq!(results.first().unwrap().another, 50.0); + assert_eq!(results.last().unwrap().another, 20.0); +} From c2920a67b2eeb5fc2dd997d574dabc26cec98027 Mon Sep 17 00:00:00 2001 From: Handy-caT <37216852+Handy-caT@users.noreply.github.com> Date: Tue, 5 May 2026 19:03:09 +0300 Subject: [PATCH 3/3] fix pk ergonomics --- codegen/src/generators/in_memory/table/impls.rs | 12 +++++++++--- codegen/src/generators/persist/table/impls.rs | 12 +++++++++--- codegen/src/generators/read_only/table/impls.rs | 12 +++++++++--- tests/worktable/index/range.rs | 12 ++++++------ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/codegen/src/generators/in_memory/table/impls.rs b/codegen/src/generators/in_memory/table/impls.rs index 7243e68..c3dd6a6 100644 --- a/codegen/src/generators/in_memory/table/impls.rs +++ b/codegen/src/generators/in_memory/table/impls.rs @@ -84,15 +84,21 @@ impl InMemoryGenerator { let row_fields_ident = name_generator.get_row_fields_enum_ident(); quote! { - pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, + pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, impl DoubleEndedIterator + '_, #column_range_type, #row_fields_ident> where - R: std::ops::RangeBounds<#primary_key_type> + #primary_key_type: From, + R: std::ops::RangeBounds, + Pk: Clone, { + let converted_range = ( + range.start_bound().map(|v| #primary_key_type::from(v.clone())), + range.end_bound().map(|v| #primary_key_type::from(v.clone())), + ); let rows = self.0.primary_index.pk_map - .range(range) + .range(converted_range) .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); SelectQueryBuilder::new(rows) diff --git a/codegen/src/generators/persist/table/impls.rs b/codegen/src/generators/persist/table/impls.rs index ab95537..7051691 100644 --- a/codegen/src/generators/persist/table/impls.rs +++ b/codegen/src/generators/persist/table/impls.rs @@ -168,15 +168,21 @@ impl PersistGenerator { let row_fields_ident = name_generator.get_row_fields_enum_ident(); quote! { - pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, + pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, impl DoubleEndedIterator + '_, #column_range_type, #row_fields_ident> where - R: std::ops::RangeBounds<#primary_key_type> + #primary_key_type: From, + R: std::ops::RangeBounds, + Pk: Clone, { + let converted_range = ( + range.start_bound().map(|v| #primary_key_type::from(v.clone())), + range.end_bound().map(|v| #primary_key_type::from(v.clone())), + ); let rows = self.0.primary_index.pk_map - .range(range) + .range(converted_range) .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); SelectQueryBuilder::new(rows) diff --git a/codegen/src/generators/read_only/table/impls.rs b/codegen/src/generators/read_only/table/impls.rs index 7e406e0..3531044 100644 --- a/codegen/src/generators/read_only/table/impls.rs +++ b/codegen/src/generators/read_only/table/impls.rs @@ -164,15 +164,21 @@ impl ReadOnlyGenerator { let row_fields_ident = name_generator.get_row_fields_enum_ident(); quote! { - pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, + pub fn select_by_pk_range(&self, range: R) -> SelectQueryBuilder<#row_type, impl DoubleEndedIterator + '_, #column_range_type, #row_fields_ident> where - R: std::ops::RangeBounds<#primary_key_type> + #primary_key_type: From, + R: std::ops::RangeBounds, + Pk: Clone, { + let converted_range = ( + range.start_bound().map(|v| #primary_key_type::from(v.clone())), + range.end_bound().map(|v| #primary_key_type::from(v.clone())), + ); let rows = self.0.primary_index.pk_map - .range(range) + .range(converted_range) .filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok()); SelectQueryBuilder::new(rows) diff --git a/tests/worktable/index/range.rs b/tests/worktable/index/range.rs index 85fed8c..f988585 100644 --- a/tests/worktable/index/range.rs +++ b/tests/worktable/index/range.rs @@ -244,7 +244,7 @@ fn test_pk_range_select_basic() { } let results = table - .select_by_pk_range(PkRangeTestPrimaryKey(5)..PkRangeTestPrimaryKey(10)) + .select_by_pk_range(5..10) .execute() .unwrap(); assert_eq!(results.len(), 5); @@ -264,7 +264,7 @@ fn test_pk_range_select_inclusive() { } let results = table - .select_by_pk_range(PkRangeTestPrimaryKey(5)..=PkRangeTestPrimaryKey(10)) + .select_by_pk_range(5..=10) .execute() .unwrap(); assert_eq!(results.len(), 6); @@ -284,7 +284,7 @@ fn test_pk_range_select_open_from() { } let results = table - .select_by_pk_range(PkRangeTestPrimaryKey(15)..) + .select_by_pk_range(15..) .execute() .unwrap(); assert_eq!(results.len(), 5); @@ -304,7 +304,7 @@ fn test_pk_range_select_open_to() { } let results = table - .select_by_pk_range(..PkRangeTestPrimaryKey(5)) + .select_by_pk_range(..5) .execute() .unwrap(); assert_eq!(results.len(), 5); @@ -324,7 +324,7 @@ fn test_pk_range_select_with_limit() { } let results = table - .select_by_pk_range(PkRangeTestPrimaryKey(10)..) + .select_by_pk_range(10..) .limit(5) .execute() .unwrap(); @@ -345,7 +345,7 @@ fn test_pk_range_select_with_order() { } let results = table - .select_by_pk_range(PkRangeTestPrimaryKey(5)..=PkRangeTestPrimaryKey(10)) + .select_by_pk_range(5..=10) .order_on(PkRangeTestRowFields::Id, Order::Desc) .execute() .unwrap();