@@ -71,6 +71,7 @@ Common options:
7171use std:: {
7272 borrow:: Cow ,
7373 env,
74+ fmt:: Write as FmtWrite ,
7475 io:: Write ,
7576 path:: { Path , PathBuf } ,
7677 process:: Command ,
@@ -372,8 +373,10 @@ fn parse_sql(sql: &str) -> SqlInfo {
372373 let upper = sql. to_ascii_uppercase ( ) ;
373374 let tokens: Vec < & str > = upper. split_whitespace ( ) . collect ( ) ;
374375
376+ #[ allow( clippy:: missing_asserts_for_indexing) ]
375377 let has_select_star = tokens. windows ( 2 ) . any ( |w| w[ 0 ] == "SELECT" && w[ 1 ] == "*" ) ;
376378
379+ #[ allow( clippy:: missing_asserts_for_indexing) ]
377380 let has_order_by = tokens. windows ( 2 ) . any ( |w| w[ 0 ] == "ORDER" && w[ 1 ] == "BY" ) ;
378381
379382 let has_limit = tokens. contains ( & "LIMIT" ) ;
@@ -545,12 +548,12 @@ fn extract_join_columns(sql: &str) -> Vec<String> {
545548 {
546549 break ' on_clause;
547550 }
548- if clean. contains ( '.' ) {
549- if let Some ( col) = clean. split ( '.' ) . last ( ) {
550- if !col. is_empty ( ) && ! [ "AND" , "OR" ] . contains ( & upper_clean . as_str ( ) ) {
551- columns . push ( col . to_string ( ) ) ;
552- }
553- }
551+ if clean. contains ( '.' )
552+ && let Some ( col) = clean. split ( '.' ) . next_back ( )
553+ && !col. is_empty ( )
554+ && ! [ "AND" , "OR" ] . contains ( & upper_clean . as_str ( ) )
555+ {
556+ columns . push ( col . to_string ( ) ) ;
554557 }
555558 }
556559 }
@@ -559,21 +562,21 @@ fn extract_join_columns(sql: &str) -> Vec<String> {
559562 // Look for USING clause
560563 for ( i, _) in upper. match_indices ( "USING" ) {
561564 let after = & sql[ i + 5 ..] ;
562- if let Some ( paren_start) = after. find ( '(' ) {
563- if let Some ( paren_end) = after. find ( ')' ) {
564- let cols = & after [ paren_start + 1 ..paren_end ] ;
565- for col in cols . split ( ',' ) {
566- let col = col . trim ( ) ;
567- if ! col. is_empty ( ) {
568- columns . push ( col. to_string ( ) ) ;
569- }
565+ if let Some ( paren_start) = after. find ( '(' )
566+ && let Some ( paren_end) = after. find ( ')' )
567+ {
568+ let cols = & after [ paren_start + 1 ..paren_end ] ;
569+ for col in cols . split ( ',' ) {
570+ let col = col. trim ( ) ;
571+ if ! col. is_empty ( ) {
572+ columns . push ( col . to_string ( ) ) ;
570573 }
571574 }
572575 }
573576 }
574577
575578 // Deduplicate (case-insensitive) to avoid double-counting the same join key
576- columns. sort_unstable_by ( |a, b | a. to_lowercase ( ) . cmp ( & b . to_lowercase ( ) ) ) ;
579+ columns. sort_unstable_by_key ( |a| a. to_lowercase ( ) ) ;
577580 columns. dedup_by ( |a, b| a. eq_ignore_ascii_case ( b) ) ;
578581 columns
579582}
@@ -801,7 +804,7 @@ fn get_polars_plan(
801804 // partial matches (e.g., _t_1 matching inside _t_10).
802805 let mut query = args. arg_sql . clone ( ) ;
803806 let mut alias_pairs: Vec < _ > = table_aliases. iter ( ) . collect ( ) ;
804- alias_pairs. sort_by ( |a , b| b . 1 . len ( ) . cmp ( & a . 1 . len ( ) ) ) ;
807+ alias_pairs. sort_by_key ( | b| std :: cmp:: Reverse ( b . 1 . len ( ) ) ) ;
805808 for ( tname, talias) in alias_pairs {
806809 // Use word-boundary regex to avoid replacing inside other identifiers
807810 // (e.g., alias "data" matching inside "metadata_col")
@@ -845,7 +848,7 @@ fn get_duckdb_plan(args: &Args, table_names: &[String]) -> CliResult<String> {
845848 }
846849 }
847850 // Sort by length descending so longer names are replaced first
848- replacements. sort_by ( |a , b| b . 0 . len ( ) . cmp ( & a . 0 . len ( ) ) ) ;
851+ replacements. sort_by_key ( | b| std :: cmp:: Reverse ( b . 0 . len ( ) ) ) ;
849852 // Deduplicate in case a table name equals an alias
850853 replacements. dedup_by ( |a, b| a. 0 == b. 0 ) ;
851854 for ( from, to) in & replacements {
@@ -1062,22 +1065,21 @@ fn score_filter_selectivity(
10621065 . find ( |f| f. field . to_ascii_uppercase ( ) == col_upper)
10631066 {
10641067 // If the most common value accounts for > 70% of rows, selectivity is low
1065- if let Some ( top) = freq. frequencies . first ( ) {
1066- if top. percentage > 70.0
1067- && top. value != "<ALL_UNIQUE>"
1068- && top. value != "<HIGH_CARDINALITY>"
1069- {
1070- score = score. saturating_sub ( 5 ) ;
1071- details. push ( format ! (
1072- "{}.{} top value '{}' is {:.0}% of rows (low selectivity)" ,
1073- cache. table_name, freq. field, top. value, top. percentage
1074- ) ) ;
1075- suggestions. push ( format ! (
1076- "Column '{}.{}' filter may match {:.0}% of rows — consider a more \
1077- selective filter",
1078- cache. table_name, freq. field, top. percentage
1079- ) ) ;
1080- }
1068+ if let Some ( top) = freq. frequencies . first ( )
1069+ && top. percentage > 70.0
1070+ && top. value != "<ALL_UNIQUE>"
1071+ && top. value != "<HIGH_CARDINALITY>"
1072+ {
1073+ score = score. saturating_sub ( 5 ) ;
1074+ details. push ( format ! (
1075+ "{}.{} top value '{}' is {:.0}% of rows (low selectivity)" ,
1076+ cache. table_name, freq. field, top. value, top. percentage
1077+ ) ) ;
1078+ suggestions. push ( format ! (
1079+ "Column '{}.{}' filter may match {:.0}% of rows — consider a more \
1080+ selective filter",
1081+ cache. table_name, freq. field, top. percentage
1082+ ) ) ;
10811083 }
10821084 }
10831085 }
@@ -1126,41 +1128,41 @@ fn score_data_distribution(
11261128 }
11271129
11281130 // Check skewness
1129- if let Some ( skewness) = stat. skewness {
1130- if skewness. abs ( ) > 2.0 {
1131- score = score . saturating_sub ( 2 ) ;
1132- details . push ( format ! (
1133- "{}.{} highly skewed (skewness={:.2})" ,
1134- cache . table_name , stat . field , skewness
1135- ) ) ;
1136- }
1131+ if let Some ( skewness) = stat. skewness
1132+ && skewness. abs ( ) > 2.0
1133+ {
1134+ score = score . saturating_sub ( 2 ) ;
1135+ details . push ( format ! (
1136+ "{}.{} highly skewed (skewness={:.2})" ,
1137+ cache . table_name , stat . field , skewness
1138+ ) ) ;
11371139 }
11381140
11391141 // Check gini coefficient (from moarstats)
1140- if let Some ( gini) = stat. gini_coefficient {
1141- if gini > 0.5 {
1142- score = score . saturating_sub ( 2 ) ;
1143- details . push ( format ! (
1144- "{}.{} high inequality (gini={:.2})" ,
1145- cache . table_name , stat . field , gini
1146- ) ) ;
1147- suggestions . push ( format ! (
1148- "Column '{}.{}' has high inequality (gini={:.2}) — may cause performance \
1149- issues in joins/aggregations" ,
1150- cache . table_name , stat . field , gini
1151- ) ) ;
1152- }
1142+ if let Some ( gini) = stat. gini_coefficient
1143+ && gini > 0.5
1144+ {
1145+ score = score . saturating_sub ( 2 ) ;
1146+ details . push ( format ! (
1147+ "{}.{} high inequality (gini={:.2})" ,
1148+ cache . table_name , stat . field , gini
1149+ ) ) ;
1150+ suggestions . push ( format ! (
1151+ "Column '{}.{}' has high inequality (gini={:.2}) — may cause performance \
1152+ issues in joins/aggregations" ,
1153+ cache . table_name , stat . field , gini
1154+ ) ) ;
11531155 }
11541156
11551157 // Check entropy (from moarstats)
1156- if let Some ( entropy) = stat. normalized_entropy {
1157- if entropy < 0.3 {
1158- score = score . saturating_sub ( 1 ) ;
1159- details . push ( format ! (
1160- "{}.{} low entropy ({:.2}) — data is highly concentrated" ,
1161- cache . table_name , stat . field , entropy
1162- ) ) ;
1163- }
1158+ if let Some ( entropy) = stat. normalized_entropy
1159+ && entropy < 0.3
1160+ {
1161+ score = score . saturating_sub ( 1 ) ;
1162+ details . push ( format ! (
1163+ "{}.{} low entropy ({:.2}) — data is highly concentrated" ,
1164+ cache . table_name , stat . field , entropy
1165+ ) ) ;
11641166 }
11651167 }
11661168 }
@@ -1239,10 +1241,11 @@ fn score_query_patterns(
12391241fn format_human_report ( report : & ScoreReport ) -> String {
12401242 let mut out = String :: with_capacity ( 2048 ) ;
12411243
1242- out. push_str ( & format ! (
1244+ let _ = write ! (
1245+ out,
12431246 "Score: {}/{} ({})\n \n " ,
12441247 report. score, report. max_score, report. rating
1245- ) ) ;
1248+ ) ;
12461249
12471250 out. push_str ( "=== Query Plan ===\n " ) ;
12481251 out. push_str ( & report. plan ) ;
@@ -1253,19 +1256,20 @@ fn format_human_report(report: &ScoreReport) -> String {
12531256
12541257 out. push_str ( "\n === Scoring Breakdown ===\n " ) ;
12551258 for b in & report. breakdown {
1256- out. push_str ( & format ! (
1257- " {:25} {:>2}/{:<2} - {}\n " ,
1259+ let _ = writeln ! (
1260+ out,
1261+ " {:25} {:>2}/{:<2} - {}" ,
12581262 format!( "{}:" , b. category) ,
12591263 b. score,
12601264 b. max,
12611265 b. detail
1262- ) ) ;
1266+ ) ;
12631267 }
12641268
12651269 if !report. suggestions . is_empty ( ) {
12661270 out. push_str ( "\n === Suggestions ===\n " ) ;
12671271 for ( i, s) in report. suggestions . iter ( ) . enumerate ( ) {
1268- out . push_str ( & format ! ( " {}. {s}\n " , i + 1 ) ) ;
1272+ let _ = writeln ! ( out , " {}. {s}" , i + 1 ) ;
12691273 }
12701274 }
12711275
@@ -1276,22 +1280,25 @@ fn format_human_report(report: &ScoreReport) -> String {
12761280 {
12771281 out. push_str ( "\n === Cache Status ===\n " ) ;
12781282 if !report. cache_status . stats_missing . is_empty ( ) {
1279- out. push_str ( & format ! (
1280- " Stats missing: {}\n " ,
1283+ let _ = writeln ! (
1284+ out,
1285+ " Stats missing: {}" ,
12811286 report. cache_status. stats_missing. join( ", " )
1282- ) ) ;
1287+ ) ;
12831288 }
12841289 if !report. cache_status . frequency_missing . is_empty ( ) {
1285- out. push_str ( & format ! (
1286- " Frequency missing: {}\n " ,
1290+ let _ = writeln ! (
1291+ out,
1292+ " Frequency missing: {}" ,
12871293 report. cache_status. frequency_missing. join( ", " )
1288- ) ) ;
1294+ ) ;
12891295 }
12901296 if !report. cache_status . index_missing . is_empty ( ) {
1291- out. push_str ( & format ! (
1292- " Index missing: {}\n " ,
1297+ let _ = writeln ! (
1298+ out,
1299+ " Index missing: {}" ,
12931300 report. cache_status. index_missing. join( ", " )
1294- ) ) ;
1301+ ) ;
12951302 }
12961303 }
12971304
0 commit comments