@@ -8,64 +8,51 @@ use postgresql_cst_parser::{
88 tree_sitter:: { Node , Range } ,
99} ;
1010
11- /// Detects functions use in JOIN or WHERE conditions.
11+ /// Detects functions use on columns in JOIN or WHERE conditions.
1212/// source: https://future-architect.github.io/coding-standards/documents/forSQL/SQL%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84%EF%BC%88PostgreSQL%EF%BC%89.html#:~:text=1-,%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AB%E9%96%A2%E6%95%B0,-%E3%82%92%E9%80%9A%E3%81%97%E3%81%9F
13- pub struct NoFunctionInJoinOrWhere ;
13+ pub struct NoFunctionOnColumnInJoinOrWhere ;
1414
15- impl Rule for NoFunctionInJoinOrWhere {
15+ impl Rule for NoFunctionOnColumnInJoinOrWhere {
1616 fn name ( & self ) -> & ' static str {
17- "no-function-in-join-or-where"
17+ "no-function-on-column- in-join-or-where"
1818 }
1919
2020 fn default_severity ( & self ) -> Severity {
2121 Severity :: Warning
2222 }
2323
2424 fn target_kinds ( & self ) -> & ' static [ SyntaxKind ] {
25- & [ SyntaxKind :: join_qual , SyntaxKind :: where_clause ]
25+ & [ SyntaxKind :: func_expr ]
2626 }
2727
2828 fn run_on_node < ' tree > ( & self , node : & Node < ' tree > , ctx : & mut LintContext , severity : Severity ) {
29- assert ! ( matches! (
30- node. kind ( ) ,
31- SyntaxKind :: join_qual | SyntaxKind :: where_clause
32- ) ) ;
33-
34- let Some ( top_expr ) = find_top_expr ( node ) else {
29+ assert_eq ! ( node . kind ( ) , SyntaxKind :: func_expr ) ;
30+ let func_expr = node;
31+
32+ // 親を参照し、 join_qual か where_clause があるかをチェックする
33+ // その途中で select_no_parens があれば探索を停止する
34+ if ! is_in_detection_range ( func_expr ) {
3535 return ;
36- } ;
37-
38- let ranges = detect_column_function_calls ( & top_expr) ;
39-
40- for range in ranges {
41- let diagnostic = Diagnostic :: new (
42- self . name ( ) ,
43- severity,
44- "Functions in JOIN or WHERE conditions can prevent index usage; rewrite without wrapping the column." ,
45- & range,
46- ) ;
47- ctx. report ( diagnostic) ;
4836 }
49- }
50- }
51-
52- /// Finds the top `a_expr` in a JOIN or WHERE clause.
53- fn find_top_expr < ' a > ( join_qual_or_where_clause : & ' a Node < ' a > ) -> Option < Node < ' a > > {
54- // join_qual:
55- // - ON a_expr
56- // - USING '(' name_list ')' opt_alias_clause_for_joiln_using
57- //
58- // where_clause:
59- // - a_expr
60-
61- let last_child = join_qual_or_where_clause
62- . last_child ( )
63- . expect ( "join_qual or where_clause must have a last child." ) ;
64-
65- if last_child. kind ( ) == SyntaxKind :: a_expr {
66- Some ( last_child)
67- } else {
68- None
37+
38+ // func_expr の最初の子供は func_application, json_aggregate_func, または func_expr_common_subexpr のいずれかである
39+ let function_body = func_expr. first_child ( ) . expect ( "func_expr should have one of func_application, json_aggregate_func, or func_expr_common_subexpr as its first child." ) ;
40+
41+ // 引数にカラムがあるか判定
42+
43+ // 警告範囲には filter や over を含めない
44+ // func_expr の最初の子供の範囲とする
45+
46+
47+ unimplemented ! ( )
48+
49+ let diagnostic = Diagnostic :: new (
50+ self . name ( ) ,
51+ severity,
52+ "Functions in JOIN or WHERE conditions can prevent index usage; rewrite without wrapping the column." ,
53+ & range,
54+ ) ;
55+ ctx. report ( diagnostic) ;
6956 }
7057}
7158
@@ -79,6 +66,13 @@ fn find_top_expr<'a>(join_qual_or_where_clause: &'a Node<'a>) -> Option<Node<'a>
7966//
8067// - func_application
8168// - カラムが出現しうる箇所は子供のうち func_arg_list か func_arg_expr を見れば良さそう
69+ // - func_name '(' ')'
70+ // - func_name '(' func_arg_list opt_sort_clause ')'
71+ // - func_name '(' VARIADIC func_arg_expr opt_sort_clause ')'
72+ // - func_name '(' func_arg_list ',' VARIADIC func_arg_expr opt_sort_clause ')'
73+ // - func_name '(' ALL func_arg_list opt_sort_clause ')'
74+ // - func_name '(' DISTINCT func_arg_list opt_sort_clause ')'
75+ // - func_name '(' '*' ')'
8276//
8377// - json_aggregate_func
8478// - JSON_ARRAY_AGG '(' json_value_expr_list json_array_constructor_null_clause_opt json_returning_clause_opt
@@ -91,6 +85,34 @@ fn find_top_expr<'a>(join_qual_or_where_clause: &'a Node<'a>) -> Option<Node<'a>
9185// - a_expr ':' json_value_expr
9286//
9387// - func_expr_common_subexpr
88+ // - nothing
89+ // - a_expr
90+ // - extract_list (EXTRACT '(' extract_list ')')
91+ // - overlay_list (OVERLAY '(' overlay_list ')')
92+ // - func_arg_list_opt (func_name '(' func_arg_list_opt ')')
93+ // - func_arg_list
94+ // - position_list (POSITION '(' position_list ')')
95+ // - substr_list (SUBSTRING '(' substr_list ')')
96+ // - trim_list (TRIM '(' trim_list ')')
97+ // - expr_list
98+ // - xmlexists_argument (XMLEXISTS '(' c_expr xmlexists_argument ')')
99+ // - c_expr
100+ // - xml_attribute_list (XMLFOREST '(' xml_attribute_list ')')
101+
102+
103+
104+ // - JSON_OBJECT '(' json_name_and_value_list json_object_constructor_null_clause_opt json_key_uniqueness_constraint_opt json_returning_clause_opt ')'
105+ // - JSON_OBJECT '(' json_returning_clause_opt ')'
106+ // - JSON_ARRAY '(' json_value_expr_list json_array_constructor_null_clause_opt json_returning_clause_opt ')'
107+ // - JSON_ARRAY '(' select_no_parens json_format_clause_opt json_returning_clause_opt ')'
108+ // - JSON_ARRAY '(' json_returning_clause_opt ')'
109+ // - JSON '(' json_value_expr json_key_uniqueness_constraint_opt ')'
110+ // - JSON_SERIALIZE '(' json_value_expr json_returning_clause_opt ')'
111+ // - JSON_QUERY '(' json_value_expr ',' a_expr json_passing_clause_opt json_returning_clause_opt json_wrapper_behavior json_quotes_clause_opt json_behavior_clause_opt ')'
112+ // - JSON_EXISTS '(' json_value_expr ',' a_expr json_passing_clause_opt json_on_error_clause_opt ')'
113+ // - JSON_VALUE '(' json_value_expr ',' a_expr json_passing_clause_opt json_returning_clause_opt json_behavior_clause_opt ')'
114+
115+
94116//
95117// a_expr の子孫で func_expr が現れるまでの最短ルート
96118// a_expr
@@ -103,57 +125,25 @@ const FUNCTION_KINDS: &[SyntaxKind] = &[
103125 SyntaxKind :: json_aggregate_func,
104126] ;
105127
106- // SELECT サブクエリ以下は JOIN / WHERE の外側なので探索対象外。
107- const SUBQUERY_KINDS : & [ SyntaxKind ] =
108- & [ SyntaxKind :: select_with_parens, SyntaxKind :: select_no_parens] ;
109-
110- fn detect_column_function_calls ( a_expr : & Node < ' _ > ) -> Vec < Range > {
111- let mut ranges = Vec :: new ( ) ;
112- let mut stack = vec ! [ a_expr. clone( ) ] ;
113-
114- while let Some ( current) = stack. pop ( ) {
115- // 対象となる関数ノードを見つけたら、直下に列参照があるかを判定する。
116- // ネストしている場合は「列に最も近い(内側の)関数だけ」を診断対象にする。
117- if FUNCTION_KINDS . contains ( & current. kind ( ) )
118- && contains_columnref_excluding_nested_functions ( & current)
119- {
120- ranges. push ( current. range ( ) ) ;
121- }
122- push_child_nodes ( & mut stack, & current) ;
123- }
124-
125- ranges
126- }
128+ fn is_in_detection_range ( func_expr : & Node ) -> bool {
129+ // 親を辿り、 join_qual か where_clause があるかを検証する
130+ // その途中で select_no_parens があれば探索を停止する
127131
128- fn contains_columnref_excluding_nested_functions ( node : & Node < ' _ > ) -> bool {
129- let mut stack = Vec :: new ( ) ;
130- push_child_nodes ( & mut stack, node) ;
131-
132- while let Some ( current) = stack. pop ( ) {
133- if current. kind ( ) == SyntaxKind :: columnref {
134- return true ;
135- }
136- // 内側に別の関数が現れたら、そちらで改めて判定するためここでは追わない。
137- if FUNCTION_KINDS . contains ( & current. kind ( ) ) {
138- continue ;
132+ let mut node = func_expr. parent ( ) ;
133+ while let Some ( current) = node {
134+ match current. kind ( ) {
135+ SyntaxKind :: join_qual | SyntaxKind :: where_clause => return true ,
136+ SyntaxKind :: select_no_parens => return false ,
137+ _ => ( ) ,
139138 }
140- push_child_nodes ( & mut stack , & current) ;
139+ node = current. parent ( ) ;
141140 }
142-
143141 false
144142}
145143
146- fn push_child_nodes < ' tree > ( stack : & mut Vec < Node < ' tree > > , node : & Node < ' tree > ) {
147- if SUBQUERY_KINDS . contains ( & node. kind ( ) ) {
148- return ;
149- }
150-
151- for child in node. children ( ) {
152- if child. child_count ( ) == 0 {
153- continue ;
154- }
155- stack. push ( child) ;
156- }
144+ // 引数にカラムを含むかどうか
145+ fn has_column_argument ( func_expr : & Node ) -> bool {
146+ unimplemented ! ( )
157147}
158148
159149#[ cfg( test) ]
@@ -162,7 +152,7 @@ mod tests {
162152 use crate :: { linter:: tests:: run_with_rules, Diagnostic , SqlSpan } ;
163153
164154 fn run ( sql : & str ) -> Vec < Diagnostic > {
165- run_with_rules ( sql, vec ! [ Box :: new( NoFunctionInJoinOrWhere ) ] )
155+ run_with_rules ( sql, vec ! [ Box :: new( NoFunctionOnColumnInJoinOrWhere ) ] )
166156 }
167157
168158 mod where_clause {
@@ -192,7 +182,7 @@ mod tests {
192182
193183 assert ! ( diagnostics
194184 . iter( )
195- . any( |diag| diag. rule_id == "no-function-in-join-or-where" ) , ) ;
185+ . any( |diag| diag. rule_id == "no-function-on-column- in-join-or-where" ) , ) ;
196186
197187 assert_eq ! ( diagnostics. len( ) , 1 ) ;
198188
@@ -248,7 +238,7 @@ mod tests {
248238
249239 assert ! ( diagnostics
250240 . iter( )
251- . all( |diag| diag. rule_id == "no-function-in-join-or-where" ) , ) ;
241+ . all( |diag| diag. rule_id == "no-function-on-column- in-join-or-where" ) , ) ;
252242
253243 assert_eq ! (
254244 diagnostics. len( ) ,
@@ -291,7 +281,7 @@ mod tests {
291281
292282 assert ! ( diagnostics
293283 . iter( )
294- . any( |diag| diag. rule_id == "no-function-in-join-or-where" ) , ) ;
284+ . any( |diag| diag. rule_id == "no-function-on-column- in-join-or-where" ) , ) ;
295285
296286 assert_eq ! ( diagnostics. len( ) , 1 ) ;
297287
@@ -306,7 +296,7 @@ mod tests {
306296
307297 assert ! ( diagnostics
308298 . iter( )
309- . all( |diag| diag. rule_id == "no-function-in-join-or-where" ) , ) ;
299+ . all( |diag| diag. rule_id == "no-function-on-column- in-join-or-where" ) , ) ;
310300
311301 assert_eq ! ( diagnostics. len( ) , 2 , ) ;
312302
0 commit comments