@@ -3,9 +3,10 @@ use crate::constants::{
33 SOURCE_REFERENCE_KIND , VIEW_MATERIALIZATION ,
44} ;
55use crate :: models:: { Declaration , EvaluateRequest , Fault , Model , RuleMetadata , RulesConfig } ;
6- use crate :: rules:: _helpers:: authored_literals:: numeric_literal_tokens;
76use crate :: rules:: _helpers:: domain_layout:: folder_layer_details;
8- use crate :: rules:: _helpers:: { contract_name_types, explicit_output_types, typed_contract_columns} ;
7+ use crate :: rules:: _helpers:: {
8+ contract_name_types, explicit_output_types, numeric_decisions, typed_contract_columns,
9+ } ;
910use crate :: rules:: models:: {
1011 FaultCollector , ModelEvaluationRequest , ProjectEvaluationRequest , ResolvedThresholdOverride ,
1112} ;
@@ -35,6 +36,9 @@ struct Position {
3536#[ derive( Default ) ]
3637struct ComparisonFact {
3738 position : Option < Position > ,
39+ sql : String ,
40+ equality : bool ,
41+ output_name : Option < String > ,
3842 columns : BTreeSet < String > ,
3943 string_literals : Vec < String > ,
4044 numeric_literals : Vec < String > ,
@@ -64,7 +68,7 @@ struct ParsedModel<'a> {
6468 query : Query ,
6569 model : & ' a Model ,
6670 classification : ModelClassification ,
67- authored_numeric_literals : BTreeSet < String > ,
71+ authored_compact_sql : String ,
6872}
6973
7074struct ModelClassification {
@@ -137,14 +141,11 @@ fn evaluate_model_inner(request: ModelEvaluationRequest<'_>) -> Result<Vec<Fault
137141 } ;
138142 let query = * query;
139143 let classification = classify_model ( & query, model, & request. dialect ) ?;
140- let authored_dialect = rules_dialect ( & request. dialect ) ;
141- let authored_numeric_literals =
142- numeric_literal_tokens ( & model. authored_sql , authored_dialect. as_ref ( ) ) ?;
143144 let parsed = ParsedModel {
144145 query,
145146 model,
146147 classification,
147- authored_numeric_literals ,
148+ authored_compact_sql : numeric_decisions :: compact_sql ( & model . authored_sql ) ,
148149 } ;
149150 if let Some ( rule) = metadata ( "SQBRMODEL101" ) {
150151 import_ctes ( & parsed, rule, & faults) ;
@@ -1361,11 +1362,22 @@ fn evaluate_literal_rules(
13611362 }
13621363 if let Some ( rule) = selected. get ( "SQBRDECLARATION102" ) {
13631364 let magic = comparison. numeric_literals . iter ( ) . any ( |literal| {
1364- !matches ! ( literal. as_str( ) , "-1" | "0" | "1" )
1365- && parsed. authored_numeric_literals . contains ( literal)
1365+ numeric_decisions:: is_authored_decision ( numeric_decisions:: AuthoredDecision {
1366+ authored_compact_sql : & parsed. authored_compact_sql ,
1367+ comparison_sql : & comparison. sql ,
1368+ equality : comparison. equality ,
1369+ output_name : comparison. output_name . as_deref ( ) ,
1370+ literal,
1371+ } )
13661372 } ) ;
13671373 if magic {
1368- faults. push ( fault ( parsed. model , rule, comparison. position . as_ref ( ) ) ) ;
1374+ faults. push ( custom_fault ! (
1375+ parsed. model,
1376+ rule,
1377+ comparison. position. as_ref( ) ,
1378+ format!( "non-canonical numeric comparison: {}" , comparison. sql) ,
1379+ None ,
1380+ ) ) ;
13691381 }
13701382 }
13711383 }
@@ -1633,11 +1645,24 @@ fn select_facts(query: &Query) -> Vec<SelectFacts> {
16331645 }
16341646 for item in & select. projection {
16351647 match item {
1636- SelectItem :: UnnamedExpr ( expression)
1637- | SelectItem :: ExprWithAlias {
1638- expr : expression, ..
1648+ SelectItem :: UnnamedExpr ( expression) => {
1649+ case_comparison_facts (
1650+ expression,
1651+ None ,
1652+ & source_context,
1653+ & mut result. comparisons ,
1654+ ) ;
1655+ }
1656+ SelectItem :: ExprWithAlias {
1657+ expr : expression,
1658+ alias,
16391659 } => {
1640- case_comparison_facts ( expression, & source_context, & mut result. comparisons ) ;
1660+ case_comparison_facts (
1661+ expression,
1662+ Some ( & alias. value ) ,
1663+ & source_context,
1664+ & mut result. comparisons ,
1665+ ) ;
16411666 }
16421667 SelectItem :: QualifiedWildcard ( _, _) | SelectItem :: Wildcard ( _) => { }
16431668 }
@@ -1738,7 +1763,7 @@ fn join_constraint(operator: &JoinOperator) -> Option<&JoinConstraint> {
17381763}
17391764
17401765fn numeric_value ( expression : & Expr ) -> Option < String > {
1741- match expression {
1766+ match unwrap_nested ( expression) {
17421767 Expr :: Value ( value) => match & value. value {
17431768 Value :: Number ( value, _) => Some ( value. to_string ( ) ) ,
17441769 _ => None ,
@@ -1784,6 +1809,7 @@ fn comparison_facts(root: &Expr, source_context: &SourceContext, output: &mut Ve
17841809
17851810fn case_comparison_facts (
17861811 root : & Expr ,
1812+ output_name : Option < & str > ,
17871813 source_context : & SourceContext ,
17881814 output : & mut Vec < ComparisonFact > ,
17891815) {
@@ -1815,7 +1841,11 @@ fn case_comparison_facts(
18151841 source_context,
18161842 output,
18171843 } ;
1844+ let start = visitor. output . len ( ) ;
18181845 let _ = root. visit ( & mut visitor) ;
1846+ for fact in & mut visitor. output [ start..] {
1847+ fact. output_name = output_name. map ( str:: to_owned) ;
1848+ }
18191849}
18201850
18211851fn comparison_fact ( root : & Expr , source_context : & SourceContext ) -> ComparisonFact {
@@ -1835,9 +1865,6 @@ fn comparison_fact(root: &Expr, source_context: &SourceContext) -> ComparisonFac
18351865 }
18361866 }
18371867 Expr :: Value ( value) => match & value. value {
1838- Value :: Number ( number, _) => {
1839- self . fact . numeric_literals . push ( number. to_string ( ) ) ;
1840- }
18411868 Value :: SingleQuotedString ( _)
18421869 | Value :: DoubleQuotedString ( _)
18431870 | Value :: TripleSingleQuotedString ( _)
@@ -1849,11 +1876,6 @@ fn comparison_fact(root: &Expr, source_context: &SourceContext) -> ComparisonFac
18491876 }
18501877 _ => { }
18511878 } ,
1852- Expr :: UnaryOp { op, expr } if op. to_string ( ) == NEGATION_OPERATOR => {
1853- if let Some ( number) = numeric_value ( expr) {
1854- self . fact . numeric_literals . push ( format ! ( "-{number}" ) ) ;
1855- }
1856- }
18571879 _ => { }
18581880 }
18591881 ControlFlow :: Continue ( ( ) )
@@ -1862,13 +1884,24 @@ fn comparison_fact(root: &Expr, source_context: &SourceContext) -> ComparisonFac
18621884 let mut visitor = Values {
18631885 fact : ComparisonFact {
18641886 position : Some ( position ( root) ) ,
1887+ sql : root. to_string ( ) ,
1888+ equality : matches ! (
1889+ root,
1890+ Expr :: BinaryOp {
1891+ op: BinaryOperator :: Eq ,
1892+ ..
1893+ }
1894+ ) ,
18651895 source_context : source_context. clone ( ) ,
18661896 ..ComparisonFact :: default ( )
18671897 } ,
18681898 } ;
18691899 let _ = root. visit ( & mut visitor) ;
18701900 if let Expr :: BinaryOp { left, right, .. } = root {
18711901 for operand in [ left. as_ref ( ) , right. as_ref ( ) ] {
1902+ if let Some ( number) = numeric_value ( operand) {
1903+ visitor. fact . numeric_literals . push ( number) ;
1904+ }
18721905 if direct_column ( operand) . is_none ( ) {
18731906 visitor. fact . modified_columns . extend ( column_facts ( operand) ) ;
18741907 }
0 commit comments