Skip to content

Commit de2157f

Browse files
authored
Improve ranges ordering queries logic (#159)
1 parent 4a06fd5 commit de2157f

13 files changed

Lines changed: 184 additions & 45 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["codegen", "examples", "performance_measurement", "performance_measur
33

44
[package]
55
name = "worktable"
6-
version = "0.9.0-beta0.2.1"
6+
version = "0.9.0-beta0.2.2"
77
edition = "2024"
88
authors = ["Handy-caT"]
99
license = "MIT"
@@ -46,7 +46,7 @@ tracing = "0.1"
4646
url = { version = "2", optional = true }
4747
uuid = { version = "1.10.0", features = ["v4", "v7"] }
4848
walkdir = { version = "2", optional = true }
49-
worktable_codegen = { path = "codegen", version = "=0.9.0-beta0.2.1" }
49+
worktable_codegen = { path = "codegen", version = "=0.9.0-beta0.2.2" }
5050

5151
[dev-dependencies]
5252
chrono = "0.4.43"

codegen/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "worktable_codegen"
3-
version = "0.9.0-beta0.2.1"
3+
version = "0.9.0-beta0.2.2"
44
edition = "2024"
55
license = "MIT"
66
description = "WorkTable codegeneration crate"

codegen/src/generators/in_memory/table/impls.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use proc_macro2::TokenStream;
1+
use convert_case::{Case, Casing};
2+
use proc_macro2::{Ident, Span, TokenStream};
23
use quote::quote;
34

45
use crate::common::model::GeneratorType;
@@ -83,6 +84,18 @@ impl InMemoryGenerator {
8384
let column_range_type = name_generator.get_column_range_type_ident();
8485
let row_fields_ident = name_generator.get_row_fields_enum_ident();
8586

87+
let pk_sorted_by = if self.columns.primary_keys.len() == 1 {
88+
let pk_field = &self.columns.primary_keys[0];
89+
let pk_pascal = Ident::new(&pk_field.to_string().to_case(Case::Pascal), Span::mixed_site());
90+
quote! {
91+
SelectQueryBuilder::new_sorted(rows, #row_fields_ident::#pk_pascal)
92+
}
93+
} else {
94+
quote! {
95+
SelectQueryBuilder::new(rows)
96+
}
97+
};
98+
8699
quote! {
87100
pub fn select_by_pk_range<R, Pk>(&self, range: R) -> SelectQueryBuilder<#row_type,
88101
impl DoubleEndedIterator<Item = #row_type> + '_,
@@ -101,7 +114,7 @@ impl InMemoryGenerator {
101114
.range(converted_range)
102115
.filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok());
103116

104-
SelectQueryBuilder::new(rows)
117+
#pk_sorted_by
105118
}
106119
}
107120
}

codegen/src/generators/in_memory/table/index_fns.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
22

3+
use convert_case::{Case, Casing};
34
use proc_macro2::{Ident, Span, TokenStream};
45
use quote::quote;
56

@@ -130,6 +131,7 @@ impl InMemoryGenerator {
130131
let type_ = columns_map.get(i).ok_or(syn::Error::new(i.span(), "Row not found"))?;
131132
let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site());
132133
let field_ident = &idx.name;
134+
let column_pascal = Ident::new(&i.to_string().to_case(Case::Pascal), Span::mixed_site());
133135

134136
let (range_bounds, range_arg) = if is_float(type_.to_string().as_str()) {
135137
(
@@ -157,7 +159,7 @@ impl InMemoryGenerator {
157159
.range(#range_arg)
158160
.filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok());
159161

160-
SelectQueryBuilder::new(rows)
162+
SelectQueryBuilder::new_sorted(rows, #row_fields_ident::#column_pascal)
161163
}
162164
})
163165
}

codegen/src/generators/in_memory/table/select_executor.rs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ impl InMemoryGenerator {
155155
}
156156
};
157157

158+
let fallback_sort = quote! {
159+
let mut items: Vec<#row_type> = iter.collect();
160+
items.sort_by(|a, b| {
161+
for (order, col) in &self.params.order {
162+
match col {
163+
#(#order_matches)*
164+
_ => continue,
165+
}
166+
}
167+
std::cmp::Ordering::Equal
168+
});
169+
iter = Box::new(items.into_iter());
170+
};
171+
158172
quote! {
159173
impl<I> SelectQueryExecutor<#row_type, I, #column_range_type, #row_fields_ident>
160174
for SelectQueryBuilder<#row_type, I, #column_range_type, #row_fields_ident>
@@ -181,19 +195,30 @@ impl InMemoryGenerator {
181195
#range
182196

183197
if !self.params.order.is_empty() {
184-
let mut items: Vec<#row_type> = iter.collect();
185-
186-
items.sort_by(|a, b| {
187-
for (order, col) in &self.params.order {
188-
match col {
189-
#(#order_matches)*
190-
_ => continue,
198+
// Optimization: single order on pre-sorted column with no additional range filters
199+
let can_optimize = self.params.sorted_by.is_some()
200+
&& self.params.range.is_empty()
201+
&& self.params.order.len() == 1;
202+
203+
if can_optimize {
204+
let (order, col) = &self.params.order[0];
205+
let sorted_col = self.params.sorted_by.as_ref().unwrap();
206+
207+
if col == sorted_col {
208+
match order {
209+
Order::Desc => {
210+
iter = Box::new(iter.rev());
211+
}
212+
Order::Asc => {
213+
// Already sorted correctly, no action needed
214+
}
191215
}
216+
} else {
217+
#fallback_sort
192218
}
193-
std::cmp::Ordering::Equal
194-
});
195-
196-
iter = Box::new(items.into_iter());
219+
} else {
220+
#fallback_sort
221+
}
197222
}
198223

199224
let iter_result: Box<dyn Iterator<Item = #row_type>> = if let Some(offset) = self.params.offset {

codegen/src/generators/persist/table/impls.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use proc_macro2::TokenStream;
1+
use convert_case::{Case, Casing};
2+
use proc_macro2::{Ident, Span, TokenStream};
23
use quote::quote;
34

45
use crate::common::model::GeneratorType;
@@ -167,6 +168,18 @@ impl PersistGenerator {
167168
let column_range_type = name_generator.get_column_range_type_ident();
168169
let row_fields_ident = name_generator.get_row_fields_enum_ident();
169170

171+
let pk_sorted_by = if self.columns.primary_keys.len() == 1 {
172+
let pk_field = &self.columns.primary_keys[0];
173+
let pk_pascal = Ident::new(&pk_field.to_string().to_case(Case::Pascal), Span::mixed_site());
174+
quote! {
175+
SelectQueryBuilder::new_sorted(rows, #row_fields_ident::#pk_pascal)
176+
}
177+
} else {
178+
quote! {
179+
SelectQueryBuilder::new(rows)
180+
}
181+
};
182+
170183
quote! {
171184
pub fn select_by_pk_range<R, Pk>(&self, range: R) -> SelectQueryBuilder<#row_type,
172185
impl DoubleEndedIterator<Item = #row_type> + '_,
@@ -185,7 +198,7 @@ impl PersistGenerator {
185198
.range(converted_range)
186199
.filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok());
187200

188-
SelectQueryBuilder::new(rows)
201+
#pk_sorted_by
189202
}
190203
}
191204
}

codegen/src/generators/persist/table/index_fns.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
22

3+
use convert_case::{Case, Casing};
34
use proc_macro2::{Ident, Span, TokenStream};
45
use quote::quote;
56

@@ -130,6 +131,7 @@ impl PersistGenerator {
130131
let type_ = columns_map.get(i).ok_or(syn::Error::new(i.span(), "Row not found"))?;
131132
let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site());
132133
let field_ident = &idx.name;
134+
let column_pascal = Ident::new(&i.to_string().to_case(Case::Pascal), Span::mixed_site());
133135

134136
let (range_bounds, range_arg) = if is_float(type_.to_string().as_str()) {
135137
(
@@ -157,7 +159,7 @@ impl PersistGenerator {
157159
.range(#range_arg)
158160
.filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok());
159161

160-
SelectQueryBuilder::new(rows)
162+
SelectQueryBuilder::new_sorted(rows, #row_fields_ident::#column_pascal)
161163
}
162164
})
163165
}

codegen/src/generators/persist/table/select_executor.rs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ impl PersistGenerator {
155155
}
156156
};
157157

158+
let fallback_sort = quote! {
159+
let mut items: Vec<#row_type> = iter.collect();
160+
items.sort_by(|a, b| {
161+
for (order, col) in &self.params.order {
162+
match col {
163+
#(#order_matches)*
164+
_ => continue,
165+
}
166+
}
167+
std::cmp::Ordering::Equal
168+
});
169+
iter = Box::new(items.into_iter());
170+
};
171+
158172
quote! {
159173
impl<I> SelectQueryExecutor<#row_type, I, #column_range_type, #row_fields_ident>
160174
for SelectQueryBuilder<#row_type, I, #column_range_type, #row_fields_ident>
@@ -181,19 +195,30 @@ impl PersistGenerator {
181195
#range
182196

183197
if !self.params.order.is_empty() {
184-
let mut items: Vec<#row_type> = iter.collect();
185-
186-
items.sort_by(|a, b| {
187-
for (order, col) in &self.params.order {
188-
match col {
189-
#(#order_matches)*
190-
_ => continue,
198+
// Optimization: single order on pre-sorted column with no additional range filters
199+
let can_optimize = self.params.sorted_by.is_some()
200+
&& self.params.range.is_empty()
201+
&& self.params.order.len() == 1;
202+
203+
if can_optimize {
204+
let (order, col) = &self.params.order[0];
205+
let sorted_col = self.params.sorted_by.as_ref().unwrap();
206+
207+
if col == sorted_col {
208+
match order {
209+
Order::Desc => {
210+
iter = Box::new(iter.rev());
211+
}
212+
Order::Asc => {
213+
// Already sorted correctly, no action needed
214+
}
191215
}
216+
} else {
217+
#fallback_sort
192218
}
193-
std::cmp::Ordering::Equal
194-
});
195-
196-
iter = Box::new(items.into_iter());
219+
} else {
220+
#fallback_sort
221+
}
197222
}
198223

199224
let iter_result: Box<dyn Iterator<Item = #row_type>> = if let Some(offset) = self.params.offset {

codegen/src/generators/read_only/table/impls.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use proc_macro2::TokenStream;
1+
use convert_case::{Case, Casing};
2+
use proc_macro2::{Ident, Span, TokenStream};
23
use quote::quote;
34

45
use crate::common::name_generator::{WorktableNameGenerator, is_unsized_vec};
@@ -163,6 +164,18 @@ impl ReadOnlyGenerator {
163164
let column_range_type = name_generator.get_column_range_type_ident();
164165
let row_fields_ident = name_generator.get_row_fields_enum_ident();
165166

167+
let pk_sorted_by = if self.columns.primary_keys.len() == 1 {
168+
let pk_field = &self.columns.primary_keys[0];
169+
let pk_pascal = Ident::new(&pk_field.to_string().to_case(Case::Pascal), Span::mixed_site());
170+
quote! {
171+
SelectQueryBuilder::new_sorted(rows, #row_fields_ident::#pk_pascal)
172+
}
173+
} else {
174+
quote! {
175+
SelectQueryBuilder::new(rows)
176+
}
177+
};
178+
166179
quote! {
167180
pub fn select_by_pk_range<R, Pk>(&self, range: R) -> SelectQueryBuilder<#row_type,
168181
impl DoubleEndedIterator<Item = #row_type> + '_,
@@ -181,7 +194,7 @@ impl ReadOnlyGenerator {
181194
.range(converted_range)
182195
.filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok());
183196

184-
SelectQueryBuilder::new(rows)
197+
#pk_sorted_by
185198
}
186199
}
187200
}

codegen/src/generators/read_only/table/index_fns.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
22

3+
use convert_case::{Case, Casing};
34
use proc_macro2::{Ident, Span, TokenStream};
45
use quote::quote;
56

@@ -130,6 +131,7 @@ impl ReadOnlyGenerator {
130131
let type_ = columns_map.get(i).ok_or(syn::Error::new(i.span(), "Row not found"))?;
131132
let fn_name = Ident::new(format!("select_by_{i}_range").as_str(), Span::mixed_site());
132133
let field_ident = &idx.name;
134+
let column_pascal = Ident::new(&i.to_string().to_case(Case::Pascal), Span::mixed_site());
133135

134136
let (range_bounds, range_arg) = if is_float(type_.to_string().as_str()) {
135137
(
@@ -157,7 +159,7 @@ impl ReadOnlyGenerator {
157159
.range(#range_arg)
158160
.filter_map(|(_, link)| self.0.data.select_non_ghosted(link.0).ok());
159161

160-
SelectQueryBuilder::new(rows)
162+
SelectQueryBuilder::new_sorted(rows, #row_fields_ident::#column_pascal)
161163
}
162164
})
163165
}

0 commit comments

Comments
 (0)