elif_orm/query/
performance_optimized.rs

1//! Performance optimized query building
2//!
3//! This module implements optimized versions of query building operations
4//! to reduce allocations and improve performance for hot paths.
5
6use std::collections::HashMap;
7use std::sync::{RwLock, Arc};
8use once_cell::sync::Lazy;
9use super::builder::QueryBuilder;
10
11/// Cache for common SQL patterns to reduce string allocations
12static QUERY_TEMPLATE_CACHE: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| {
13    RwLock::new(HashMap::new())
14});
15
16/// Cache for parameter placeholders to avoid repeated generation
17static PLACEHOLDER_CACHE: Lazy<RwLock<HashMap<usize, String>>> = Lazy::new(|| {
18    RwLock::new(HashMap::new())
19});
20
21/// Performance-optimized SQL generation with caching
22impl<M> QueryBuilder<M> {
23    /// Generate parameter placeholders with caching for better performance
24    pub fn generate_placeholders_cached(count: usize) -> String {
25        // Check cache first
26        if let Ok(cache) = PLACEHOLDER_CACHE.read() {
27            if let Some(cached) = cache.get(&count) {
28                return cached.clone();
29            }
30        }
31
32        // Generate placeholders
33        let placeholders = (1..=count)
34            .map(|i| format!("${}", i))
35            .collect::<Vec<_>>()
36            .join(", ");
37
38        // Cache for future use
39        if let Ok(mut cache) = PLACEHOLDER_CACHE.write() {
40            cache.insert(count, placeholders.clone());
41        }
42
43        placeholders
44    }
45
46    /// Generate sequential parameter placeholders starting from a specific index
47    /// Used for proper parameter ordering in complex queries
48    pub fn generate_sequential_placeholders(start_index: usize, count: usize) -> String {
49        if count == 0 {
50            return String::new();
51        }
52
53        let placeholders = (start_index..start_index + count)
54            .map(|i| format!("${}", i))
55            .collect::<Vec<_>>()
56            .join(", ");
57
58        placeholders
59    }
60
61    /// Optimized SQL generation with pre-allocated capacity
62    pub fn to_sql_optimized(&self) -> String {
63        // Pre-calculate approximate SQL length to reduce allocations
64        let estimated_length = self.estimate_sql_length();
65        let mut sql = String::with_capacity(estimated_length);
66
67        match self.query_type {
68            super::types::QueryType::Select => {
69                self.build_select_sql_optimized(&mut sql);
70            },
71            _ => {
72                // Fallback to regular implementation for non-SELECT queries
73                return self.to_sql();
74            }
75        }
76
77        sql
78    }
79
80    /// Estimate SQL length to pre-allocate string capacity
81    fn estimate_sql_length(&self) -> usize {
82        let mut length = 100; // Base SQL overhead
83
84        // Estimate SELECT clause length
85        for field in &self.select_fields {
86            length += field.len() + 2; // field + ", "
87        }
88
89        // Estimate FROM clause length
90        for table in &self.from_tables {
91            length += table.len() + 10; // " FROM " + table
92        }
93
94        // Estimate WHERE clause length
95        for condition in &self.where_conditions {
96            length += condition.column.len() + 20; // column + operator + placeholder
97        }
98
99        // Estimate JOIN clause length
100        for join in &self.joins {
101            length += join.table.len() + 30; // JOIN type + table + ON condition
102        }
103
104        length
105    }
106
107    /// Build SELECT SQL with optimized string operations and correct parameter indexing
108    fn build_select_sql_optimized(&self, sql: &mut String) {
109        let mut param_counter = 1usize;
110        // SELECT clause
111        if self.distinct {
112            sql.push_str("SELECT DISTINCT ");
113        } else {
114            sql.push_str("SELECT ");
115        }
116
117        // Fields
118        if self.select_fields.is_empty() {
119            sql.push('*');
120        } else {
121            for (i, field) in self.select_fields.iter().enumerate() {
122                if i > 0 {
123                    sql.push_str(", ");
124                }
125                sql.push_str(field);
126            }
127        }
128
129        // FROM clause
130        if !self.from_tables.is_empty() {
131            sql.push_str(" FROM ");
132            for (i, table) in self.from_tables.iter().enumerate() {
133                if i > 0 {
134                    sql.push_str(", ");
135                }
136                sql.push_str(table);
137            }
138        }
139
140        // JOINs
141        for join in &self.joins {
142            sql.push(' ');
143            match join.join_type {
144                super::types::JoinType::Inner => sql.push_str("INNER JOIN"),
145                super::types::JoinType::Left => sql.push_str("LEFT JOIN"),
146                super::types::JoinType::Right => sql.push_str("RIGHT JOIN"),
147                super::types::JoinType::Full => sql.push_str("FULL JOIN"),
148            }
149            sql.push(' ');
150            sql.push_str(&join.table);
151            sql.push_str(" ON ");
152            
153            // Handle on_conditions
154            for (i, (left_col, right_col)) in join.on_conditions.iter().enumerate() {
155                if i > 0 {
156                    sql.push_str(" AND ");
157                }
158                sql.push_str(left_col);
159                sql.push_str(" = ");
160                sql.push_str(right_col);
161            }
162        }
163
164        // WHERE clause
165        if !self.where_conditions.is_empty() {
166            sql.push_str(" WHERE ");
167            for (i, condition) in self.where_conditions.iter().enumerate() {
168                if i > 0 {
169                    sql.push_str(" AND ");
170                }
171                
172                // Handle special cases
173                if condition.column == "RAW" {
174                    if let Some(ref value) = condition.value {
175                        if let serde_json::Value::String(raw_sql) = value {
176                            sql.push_str(raw_sql);
177                        }
178                    }
179                } else if condition.column == "EXISTS" || condition.column == "NOT EXISTS" {
180                    sql.push_str(&condition.column);
181                    sql.push(' ');
182                    if let Some(ref value) = condition.value {
183                        if let serde_json::Value::String(subquery) = value {
184                            sql.push_str(subquery);
185                        }
186                    }
187                } else {
188                    // Regular conditions
189                    sql.push_str(&condition.column);
190                    
191                    match condition.operator {
192                        super::types::QueryOperator::Equal => sql.push_str(" = "),
193                        super::types::QueryOperator::NotEqual => sql.push_str(" != "),
194                        super::types::QueryOperator::GreaterThan => sql.push_str(" > "),
195                        super::types::QueryOperator::LessThan => sql.push_str(" < "),
196                        super::types::QueryOperator::GreaterThanOrEqual => sql.push_str(" >= "),
197                        super::types::QueryOperator::LessThanOrEqual => sql.push_str(" <= "),
198                        super::types::QueryOperator::Like => sql.push_str(" LIKE "),
199                        super::types::QueryOperator::NotLike => sql.push_str(" NOT LIKE "),
200                        super::types::QueryOperator::In => {
201                            sql.push_str(" IN (");
202                            let placeholder_count = condition.values.len();
203                            if placeholder_count > 0 {
204                                let placeholders = Self::generate_sequential_placeholders(param_counter, placeholder_count);
205                                sql.push_str(&placeholders);
206                                param_counter += placeholder_count;
207                            }
208                            sql.push(')');
209                            continue; // Skip the normal parameter handling
210                        },
211                        super::types::QueryOperator::NotIn => {
212                            sql.push_str(" NOT IN (");
213                            let placeholder_count = condition.values.len();
214                            if placeholder_count > 0 {
215                                let placeholders = Self::generate_sequential_placeholders(param_counter, placeholder_count);
216                                sql.push_str(&placeholders);
217                                param_counter += placeholder_count;
218                            }
219                            sql.push(')');
220                            continue; // Skip the normal parameter handling
221                        },
222                        super::types::QueryOperator::IsNull => {
223                            sql.push_str(" IS NULL");
224                            continue;
225                        },
226                        super::types::QueryOperator::IsNotNull => {
227                            sql.push_str(" IS NOT NULL");
228                            continue;
229                        },
230                        super::types::QueryOperator::Between => {
231                            sql.push_str(&format!(" BETWEEN ${} AND ${}", param_counter, param_counter + 1));
232                            param_counter += 2;
233                            continue;
234                        },
235                        super::types::QueryOperator::Raw => {
236                            // For raw SQL expressions, just add the value directly
237                            if let Some(ref value) = condition.value {
238                                if let serde_json::Value::String(raw_expr) = value {
239                                    sql.push(' ');
240                                    sql.push_str(raw_expr);
241                                }
242                            }
243                            continue;
244                        },
245                    }
246                    
247                    // Add parameter placeholder for regular operators
248                    sql.push_str(&format!("${}", param_counter));
249                    param_counter += 1;
250                }
251            }
252        }
253
254        // GROUP BY
255        if !self.group_by.is_empty() {
256            sql.push_str(" GROUP BY ");
257            for (i, column) in self.group_by.iter().enumerate() {
258                if i > 0 {
259                    sql.push_str(", ");
260                }
261                sql.push_str(column);
262            }
263        }
264
265        // HAVING
266        if !self.having_conditions.is_empty() {
267            sql.push_str(" HAVING ");
268            for (i, condition) in self.having_conditions.iter().enumerate() {
269                if i > 0 {
270                    sql.push_str(" AND ");
271                }
272                sql.push_str(&condition.column);
273                
274                // Handle HAVING operators with proper parameter indexing
275                match condition.operator {
276                    super::types::QueryOperator::Equal => sql.push_str(" = "),
277                    super::types::QueryOperator::GreaterThan => sql.push_str(" > "),
278                    super::types::QueryOperator::LessThan => sql.push_str(" < "),
279                    _ => sql.push_str(" = "), // Default to equals
280                }
281                
282                sql.push_str(&format!("${}", param_counter));
283                param_counter += 1;
284            }
285        }
286
287        // ORDER BY
288        if !self.order_by.is_empty() {
289            sql.push_str(" ORDER BY ");
290            for (i, (column, direction)) in self.order_by.iter().enumerate() {
291                if i > 0 {
292                    sql.push_str(", ");
293                }
294                sql.push_str(column);
295                match direction {
296                    super::types::OrderDirection::Asc => sql.push_str(" ASC"),
297                    super::types::OrderDirection::Desc => sql.push_str(" DESC"),
298                }
299            }
300        }
301
302        // LIMIT
303        if let Some(limit) = self.limit_count {
304            sql.push_str(" LIMIT ");
305            sql.push_str(&limit.to_string());
306        }
307
308        // OFFSET
309        if let Some(offset) = self.offset_value {
310            sql.push_str(" OFFSET ");
311            sql.push_str(&offset.to_string());
312        }
313    }
314}
315
316/// Query builder pool for reusing query builder instances to reduce allocations
317pub struct QueryBuilderPool {
318    pool: Arc<RwLock<Vec<QueryBuilder<()>>>>,
319    max_size: usize,
320}
321
322impl QueryBuilderPool {
323    pub fn new(max_size: usize) -> Self {
324        Self {
325            pool: Arc::new(RwLock::new(Vec::with_capacity(max_size))),
326            max_size,
327        }
328    }
329
330    /// Get a query builder from the pool or create a new one
331    pub fn acquire(&self) -> QueryBuilder<()> {
332        if let Ok(mut pool) = self.pool.write() {
333            if let Some(mut builder) = pool.pop() {
334                // Reset the builder to default state
335                builder.reset();
336                return builder;
337            }
338        }
339        
340        // Create new builder if pool is empty
341        QueryBuilder::new()
342    }
343
344    /// Return a query builder to the pool
345    pub fn release(&self, builder: QueryBuilder<()>) {
346        if let Ok(mut pool) = self.pool.write() {
347            if pool.len() < self.max_size {
348                pool.push(builder);
349            }
350            // If pool is full, just drop the builder
351        }
352    }
353}
354
355impl<M> QueryBuilder<M> {
356    /// Reset query builder to default state for reuse
357    pub fn reset(&mut self) {
358        self.query_type = super::types::QueryType::Select;
359        self.select_fields.clear();
360        self.from_tables.clear();
361        self.insert_table = None;
362        self.update_table = None;
363        self.delete_table = None;
364        self.set_clauses.clear();
365        self.where_conditions.clear();
366        self.joins.clear();
367        self.order_by.clear();
368        self.group_by.clear();
369        self.having_conditions.clear();
370        self.limit_count = None;
371        self.offset_value = None;
372        self.distinct = false;
373    }
374}
375
376/// Global query builder pool instance
377static GLOBAL_QUERY_POOL: Lazy<QueryBuilderPool> = Lazy::new(|| {
378    QueryBuilderPool::new(100) // Pool of up to 100 query builders
379});
380
381/// Get a query builder from the global pool
382pub fn acquire_query_builder() -> QueryBuilder<()> {
383    GLOBAL_QUERY_POOL.acquire()
384}
385
386/// Return a query builder to the global pool
387pub fn release_query_builder(builder: QueryBuilder<()>) {
388    GLOBAL_QUERY_POOL.release(builder);
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_placeholder_caching() {
397        let placeholders1 = QueryBuilder::<()>::generate_placeholders_cached(3);
398        let placeholders2 = QueryBuilder::<()>::generate_placeholders_cached(3);
399        
400        assert_eq!(placeholders1, "$1, $2, $3");
401        assert_eq!(placeholders1, placeholders2);
402    }
403
404    #[test]
405    fn test_query_builder_pool() {
406        let pool = QueryBuilderPool::new(2);
407        
408        let builder1 = pool.acquire();
409        let builder2 = pool.acquire();
410        
411        pool.release(builder1);
412        pool.release(builder2);
413        
414        let builder3 = pool.acquire(); // Should reuse from pool
415        assert!(!builder3.from_tables.is_empty() || builder3.from_tables.is_empty()); // Basic check
416    }
417
418    #[test]
419    fn test_optimized_sql_generation() {
420        let query: QueryBuilder<()> = QueryBuilder::new()
421            .from("users")
422            .select("id, name, email")
423            .where_eq("active", "true");
424        
425        let sql = query.to_sql_optimized();
426        assert!(sql.contains("SELECT"));
427        assert!(sql.contains("FROM users"));
428        assert!(sql.contains("WHERE"));
429    }
430}