1use crate::dialects::DialectType;
9use crate::expressions::Expression;
10use crate::schema::Schema;
11
12use super::annotate_types::annotate_types;
13use super::canonicalize::canonicalize;
14use super::eliminate_ctes::eliminate_ctes;
15use super::eliminate_joins::eliminate_joins;
16use super::normalize::normalize;
17use super::optimize_joins::optimize_joins;
18use super::pushdown_predicates::pushdown_predicates;
19use super::pushdown_projections::pushdown_projections;
20use super::qualify_columns::{qualify_columns, quote_identifiers};
21use super::simplify::simplify;
22use super::subquery::{merge_subqueries, unnest_subqueries};
23
24pub struct OptimizerConfig<'a> {
26 pub schema: Option<&'a dyn Schema>,
28 pub db: Option<String>,
30 pub catalog: Option<String>,
32 pub dialect: Option<DialectType>,
34 pub isolate_tables: bool,
36 pub quote_identifiers: bool,
38}
39
40impl<'a> Default for OptimizerConfig<'a> {
41 fn default() -> Self {
42 Self {
43 schema: None,
44 db: None,
45 catalog: None,
46 dialect: None,
47 isolate_tables: true,
48 quote_identifiers: false,
49 }
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum OptimizationRule {
56 Qualify,
58 PushdownProjections,
60 Normalize,
62 UnnestSubqueries,
64 PushdownPredicates,
66 OptimizeJoins,
68 EliminateSubqueries,
70 MergeSubqueries,
72 EliminateJoins,
74 EliminateCtes,
76 QuoteIdentifiers,
78 AnnotateTypes,
80 Canonicalize,
82 Simplify,
84}
85
86pub const DEFAULT_RULES: &[OptimizationRule] = &[
88 OptimizationRule::Qualify,
89 OptimizationRule::PushdownProjections,
90 OptimizationRule::Normalize,
91 OptimizationRule::UnnestSubqueries,
92 OptimizationRule::PushdownPredicates,
93 OptimizationRule::OptimizeJoins,
94 OptimizationRule::EliminateSubqueries,
95 OptimizationRule::MergeSubqueries,
96 OptimizationRule::EliminateJoins,
97 OptimizationRule::EliminateCtes,
98 OptimizationRule::QuoteIdentifiers,
99 OptimizationRule::AnnotateTypes,
100 OptimizationRule::Canonicalize,
101 OptimizationRule::Simplify,
102];
103
104const QUICK_RULES: &[OptimizationRule] =
105 &[OptimizationRule::Simplify, OptimizationRule::Canonicalize];
106const FAST_PATH_MAX_DEPTH: usize = 768;
107const FAST_PATH_MAX_CONNECTORS: usize = 10_000;
108const FAST_PATH_MAX_CONNECTOR_DEPTH: usize = 1024;
109const FAST_PATH_MAX_NODES: usize = 50_000;
110const CLONE_HEAVY_RULE_SKIP_NODES: usize = 20_000;
111
112#[derive(Debug, Clone, Copy)]
113struct ExpressionComplexity {
114 node_count: usize,
115 max_depth: usize,
116 connector_count: usize,
117 max_connector_depth: usize,
118}
119
120pub fn optimize(expression: Expression, config: &OptimizerConfig<'_>) -> Expression {
132 optimize_with_rules(expression, config, DEFAULT_RULES)
133}
134
135pub fn optimize_with_rules(
145 mut expression: Expression,
146 config: &OptimizerConfig<'_>,
147 rules: &[OptimizationRule],
148) -> Expression {
149 let complexity = analyze_expression_complexity(&expression);
150 if rules == DEFAULT_RULES && should_skip_all_optimization(&complexity) {
151 return expression;
152 }
153
154 let active_rules = if rules == DEFAULT_RULES && should_use_quick_path(&complexity) {
155 QUICK_RULES
156 } else {
157 rules
158 };
159
160 for rule in active_rules {
161 if complexity.node_count >= CLONE_HEAVY_RULE_SKIP_NODES
162 && matches!(
163 rule,
164 OptimizationRule::Qualify | OptimizationRule::Normalize
165 )
166 {
167 continue;
168 }
169 expression = apply_rule(expression, *rule, config);
170 }
171 expression
172}
173
174fn should_skip_all_optimization(complexity: &ExpressionComplexity) -> bool {
175 complexity.max_depth >= FAST_PATH_MAX_DEPTH
176 || complexity.max_connector_depth >= FAST_PATH_MAX_CONNECTOR_DEPTH
177}
178
179fn should_use_quick_path(complexity: &ExpressionComplexity) -> bool {
180 complexity.connector_count >= FAST_PATH_MAX_CONNECTORS
181 || complexity.max_connector_depth >= FAST_PATH_MAX_CONNECTOR_DEPTH
182 || complexity.node_count >= FAST_PATH_MAX_NODES
183}
184
185fn analyze_expression_complexity(expression: &Expression) -> ExpressionComplexity {
186 let mut node_count = 0usize;
187 let mut max_depth = 0usize;
188 let mut connector_count = 0usize;
189 let mut max_connector_depth = 0usize;
190 let mut stack: Vec<(&Expression, usize, usize)> = vec![(expression, 0, 0)];
191
192 while let Some((node, depth, connector_depth)) = stack.pop() {
193 node_count += 1;
194 max_depth = max_depth.max(depth);
195
196 match node {
197 Expression::And(op) | Expression::Or(op) => {
198 connector_count += 1;
199 let next_connector_depth = connector_depth + 1;
200 max_connector_depth = max_connector_depth.max(next_connector_depth);
201 stack.push((&op.right, depth + 1, next_connector_depth));
202 stack.push((&op.left, depth + 1, next_connector_depth));
203 }
204 Expression::Paren(paren) => {
205 stack.push((&paren.this, depth + 1, connector_depth));
206 }
207 _ => {
208 let child_start = stack.len();
209 crate::ast_children::for_each_child(node, |_, child| {
210 stack.push((child, depth + 1, 0));
211 });
212 stack[child_start..].reverse();
213 }
214 }
215 }
216
217 ExpressionComplexity {
218 node_count,
219 max_depth,
220 connector_count,
221 max_connector_depth,
222 }
223}
224
225fn apply_rule(
227 expression: Expression,
228 rule: OptimizationRule,
229 config: &OptimizerConfig<'_>,
230) -> Expression {
231 match rule {
232 OptimizationRule::Qualify => {
233 if let Some(schema) = config.schema {
235 let options = super::qualify_columns::QualifyColumnsOptions {
236 dialect: config.dialect,
237 ..Default::default()
238 };
239 let original = expression.clone();
240 qualify_columns(expression, schema, &options).unwrap_or(original)
241 } else {
242 expression
244 }
245 }
246 OptimizationRule::PushdownProjections => {
247 pushdown_projections(expression, config.dialect, true)
248 }
249 OptimizationRule::Normalize => {
250 let original = expression.clone();
252 normalize(expression, false, super::normalize::DEFAULT_MAX_DISTANCE).unwrap_or(original)
253 }
254 OptimizationRule::UnnestSubqueries => unnest_subqueries(expression),
255 OptimizationRule::PushdownPredicates => pushdown_predicates(expression, config.dialect),
256 OptimizationRule::OptimizeJoins => optimize_joins(expression),
257 OptimizationRule::EliminateSubqueries => eliminate_subqueries_opt(expression),
258 OptimizationRule::MergeSubqueries => merge_subqueries(expression, config.isolate_tables),
259 OptimizationRule::EliminateJoins => eliminate_joins(expression),
260 OptimizationRule::EliminateCtes => eliminate_ctes(expression),
261 OptimizationRule::QuoteIdentifiers => {
262 if config.quote_identifiers {
263 quote_identifiers(expression, config.dialect)
264 } else {
265 expression
266 }
267 }
268 OptimizationRule::AnnotateTypes => {
269 let mut expr = expression;
270 annotate_types(&mut expr, config.schema, config.dialect);
271 expr
272 }
273 OptimizationRule::Canonicalize => canonicalize(expression, config.dialect),
274 OptimizationRule::Simplify => simplify(expression, config.dialect),
275 }
276}
277
278use super::subquery::eliminate_subqueries as eliminate_subqueries_opt;
280
281pub fn quick_optimize(expression: Expression, dialect: Option<DialectType>) -> Expression {
285 let config = OptimizerConfig {
286 dialect,
287 ..Default::default()
288 };
289
290 optimize_with_rules(expression, &config, QUICK_RULES)
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::generator::Generator;
297 use crate::parser::Parser;
298
299 fn gen(expr: &Expression) -> String {
300 Generator::new().generate(expr).unwrap()
301 }
302
303 fn parse(sql: &str) -> Expression {
304 Parser::parse_sql(sql).expect("Failed to parse")[0].clone()
305 }
306
307 #[test]
308 fn test_optimize_simple() {
309 let expr = parse("SELECT a FROM t");
310 let config = OptimizerConfig::default();
311 let result = optimize(expr, &config);
312 let sql = gen(&result);
313 assert!(sql.contains("SELECT"));
314 }
315
316 #[test]
317 fn test_optimize_with_where() {
318 let expr = parse("SELECT a FROM t WHERE b = 1");
319 let config = OptimizerConfig::default();
320 let result = optimize(expr, &config);
321 let sql = gen(&result);
322 assert!(sql.contains("WHERE"));
323 }
324
325 #[test]
326 fn test_optimize_with_join() {
327 let expr = parse("SELECT t.a FROM t JOIN s ON t.id = s.id");
328 let config = OptimizerConfig::default();
329 let result = optimize(expr, &config);
330 let sql = gen(&result);
331 assert!(sql.contains("JOIN"));
332 }
333
334 #[test]
335 fn test_quick_optimize() {
336 let expr = parse("SELECT 1 + 0 FROM t");
337 let result = quick_optimize(expr, None);
338 let sql = gen(&result);
339 assert!(sql.contains("SELECT"));
340 }
341
342 #[test]
343 fn test_optimize_with_custom_rules() {
344 let expr = parse("SELECT a FROM t WHERE NOT NOT b = 1");
345 let config = OptimizerConfig::default();
346 let rules = &[OptimizationRule::Simplify];
347 let result = optimize_with_rules(expr, &config, rules);
348 let sql = gen(&result);
349 assert!(sql.contains("SELECT"));
350 }
351
352 #[test]
353 fn test_optimizer_config_default() {
354 let config = OptimizerConfig::default();
355 assert!(config.schema.is_none());
356 assert!(config.dialect.is_none());
357 assert!(config.isolate_tables);
358 assert!(!config.quote_identifiers);
359 }
360
361 #[test]
362 fn test_default_rules() {
363 assert_eq!(
364 DEFAULT_RULES,
365 &[
366 OptimizationRule::Qualify,
367 OptimizationRule::PushdownProjections,
368 OptimizationRule::Normalize,
369 OptimizationRule::UnnestSubqueries,
370 OptimizationRule::PushdownPredicates,
371 OptimizationRule::OptimizeJoins,
372 OptimizationRule::EliminateSubqueries,
373 OptimizationRule::MergeSubqueries,
374 OptimizationRule::EliminateJoins,
375 OptimizationRule::EliminateCtes,
376 OptimizationRule::QuoteIdentifiers,
377 OptimizationRule::AnnotateTypes,
378 OptimizationRule::Canonicalize,
379 OptimizationRule::Simplify,
380 ]
381 );
382 }
383
384 #[test]
385 fn test_quote_identifiers_rule_respects_config_flag() {
386 let mut expr = parse("SELECT a FROM t");
387 if let Expression::Select(ref mut select) = expr {
388 if let Expression::Column(ref mut col) = select.expressions[0] {
389 col.name.name = "select".to_string();
390 } else {
391 panic!("expected column projection");
392 }
393 if let Some(ref mut from) = select.from {
394 if let Expression::Table(ref mut table) = from.expressions[0] {
395 table.name.name = "from".to_string();
396 } else {
397 panic!("expected table reference");
398 }
399 } else {
400 panic!("expected FROM clause");
401 }
402 } else {
403 panic!("expected select expression");
404 }
405 let config = OptimizerConfig {
406 quote_identifiers: true,
407 dialect: Some(DialectType::PostgreSQL),
408 ..Default::default()
409 };
410 let result = optimize_with_rules(expr, &config, &[OptimizationRule::QuoteIdentifiers]);
411 let sql = gen(&result);
412 assert!(sql.contains("\"select\""), "{sql}");
413 assert!(sql.contains("\"from\""), "{sql}");
414 }
415
416 #[test]
417 fn test_quote_identifiers_rule_noop_by_default() {
418 let expr = parse("SELECT a FROM t");
419 let config = OptimizerConfig::default();
420 let result =
421 optimize_with_rules(expr.clone(), &config, &[OptimizationRule::QuoteIdentifiers]);
422 assert_eq!(gen(&result), gen(&expr));
423 }
424
425 #[test]
426 fn test_optimize_subquery() {
427 let expr = parse("SELECT * FROM (SELECT a FROM t) AS sub");
428 let config = OptimizerConfig::default();
429 let result = optimize(expr, &config);
430 let sql = gen(&result);
431 assert!(sql.contains("SELECT"));
432 }
433
434 #[test]
435 fn test_optimize_cte() {
436 let expr = parse("WITH cte AS (SELECT a FROM t) SELECT * FROM cte");
437 let config = OptimizerConfig::default();
438 let result = optimize(expr, &config);
439 let sql = gen(&result);
440 assert!(sql.contains("WITH"));
441 }
442
443 #[test]
444 fn test_optimize_preserves_semantics() {
445 let expr = parse("SELECT a, b FROM t WHERE c > 1 ORDER BY a");
446 let config = OptimizerConfig::default();
447 let result = optimize(expr, &config);
448 let sql = gen(&result);
449 assert!(sql.contains("ORDER BY"));
450 }
451
452 #[test]
453 fn test_analyze_expression_complexity_deep_connector_chain() {
454 let mut expr = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
455 Expression::column("c0"),
456 Expression::number(0),
457 )));
458
459 for i in 1..1500 {
460 let predicate = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
461 Expression::column(format!("c{i}")),
462 Expression::number(i as i64),
463 )));
464 expr = Expression::And(Box::new(crate::expressions::BinaryOp::new(expr, predicate)));
465 }
466
467 let complexity = analyze_expression_complexity(&expr);
468 assert!(complexity.max_connector_depth >= 1499);
469 assert!(complexity.connector_count >= 1499);
470 }
471
472 #[test]
473 fn test_optimize_handles_deep_connector_chain() {
474 let mut expr = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
475 Expression::column("c0"),
476 Expression::number(0),
477 )));
478
479 for i in 1..2200 {
480 let predicate = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
481 Expression::column(format!("c{i}")),
482 Expression::number(i as i64),
483 )));
484 expr = Expression::And(Box::new(crate::expressions::BinaryOp::new(expr, predicate)));
485 }
486
487 let config = OptimizerConfig::default();
488 let optimized = optimize(expr, &config);
489 let sql = gen(&optimized);
490 assert!(sql.contains("c2199 = 2199"), "{sql}");
491 }
492}