1use sqrust_core::{Diagnostic, FileContext, Rule};
2use sqlparser::ast::{
3 Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, Query, Select, SelectItem,
4 SetExpr, Statement, TableFactor, WindowType,
5};
6
7pub struct WindowFunctionWithoutPartition;
8
9impl Rule for WindowFunctionWithoutPartition {
10 fn name(&self) -> &'static str {
11 "Ambiguous/WindowFunctionWithoutPartition"
12 }
13
14 fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
15 if !ctx.parse_errors.is_empty() {
16 return Vec::new();
17 }
18
19 let mut diags = Vec::new();
20 for stmt in &ctx.statements {
21 if let Statement::Query(query) = stmt {
22 check_query(query, ctx, &mut diags);
23 }
24 }
25 diags
26 }
27}
28
29fn check_query(query: &Query, ctx: &FileContext, diags: &mut Vec<Diagnostic>) {
32 if let Some(with) = &query.with {
34 for cte in &with.cte_tables {
35 check_query(&cte.query, ctx, diags);
36 }
37 }
38
39 check_set_expr(&query.body, ctx, diags);
40
41 if let Some(order_by) = &query.order_by {
43 for ob in &order_by.exprs {
44 check_expr(&ob.expr, ctx, diags);
45 }
46 }
47}
48
49fn check_set_expr(expr: &SetExpr, ctx: &FileContext, diags: &mut Vec<Diagnostic>) {
50 match expr {
51 SetExpr::Select(sel) => check_select(sel, ctx, diags),
52 SetExpr::Query(inner) => check_query(inner, ctx, diags),
53 SetExpr::SetOperation { left, right, .. } => {
54 check_set_expr(left, ctx, diags);
55 check_set_expr(right, ctx, diags);
56 }
57 _ => {}
58 }
59}
60
61fn check_select(sel: &Select, ctx: &FileContext, diags: &mut Vec<Diagnostic>) {
62 for item in &sel.projection {
63 if let SelectItem::UnnamedExpr(e) | SelectItem::ExprWithAlias { expr: e, .. } = item {
64 check_expr(e, ctx, diags);
65 }
66 }
67
68 if let Some(selection) = &sel.selection {
69 check_expr(selection, ctx, diags);
70 }
71
72 if let Some(having) = &sel.having {
73 check_expr(having, ctx, diags);
74 }
75
76 for twj in &sel.from {
77 check_table_factor(&twj.relation, ctx, diags);
78 for join in &twj.joins {
79 check_table_factor(&join.relation, ctx, diags);
80 }
81 }
82}
83
84fn check_table_factor(tf: &TableFactor, ctx: &FileContext, diags: &mut Vec<Diagnostic>) {
85 if let TableFactor::Derived { subquery, .. } = tf {
86 check_query(subquery, ctx, diags);
87 }
88}
89
90fn check_expr(expr: &Expr, ctx: &FileContext, diags: &mut Vec<Diagnostic>) {
93 match expr {
94 Expr::Function(func) => check_function(func, ctx, diags),
95 Expr::BinaryOp { left, right, .. } => {
96 check_expr(left, ctx, diags);
97 check_expr(right, ctx, diags);
98 }
99 Expr::UnaryOp { expr, .. } => check_expr(expr, ctx, diags),
100 Expr::Nested(e) => check_expr(e, ctx, diags),
101 Expr::IsNull(e) => check_expr(e, ctx, diags),
102 Expr::IsNotNull(e) => check_expr(e, ctx, diags),
103 Expr::Case {
104 operand,
105 conditions,
106 results,
107 else_result,
108 } => {
109 if let Some(op) = operand {
110 check_expr(op, ctx, diags);
111 }
112 for c in conditions {
113 check_expr(c, ctx, diags);
114 }
115 for r in results {
116 check_expr(r, ctx, diags);
117 }
118 if let Some(el) = else_result {
119 check_expr(el, ctx, diags);
120 }
121 }
122 Expr::Subquery(q) => check_query(q, ctx, diags),
123 Expr::InSubquery { subquery, .. } => check_query(subquery, ctx, diags),
124 Expr::Exists { subquery, .. } => check_query(subquery, ctx, diags),
125 _ => {}
126 }
127}
128
129fn check_function(func: &Function, ctx: &FileContext, diags: &mut Vec<Diagnostic>) {
132 if let Some(WindowType::WindowSpec(spec)) = &func.over {
133 if spec.partition_by.is_empty() && !spec.order_by.is_empty() {
134 let (line, col) = find_func_name_pos(&ctx.source, &func.name.to_string());
135 diags.push(Diagnostic {
136 rule: "Ambiguous/WindowFunctionWithoutPartition",
137 message:
138 "Window function has ORDER BY but no PARTITION BY; all rows are treated as one partition"
139 .to_string(),
140 line,
141 col,
142 });
143 }
144 }
145
146 if let FunctionArguments::List(list) = &func.args {
148 for arg in &list.args {
149 let fae = match arg {
150 FunctionArg::Named { arg, .. }
151 | FunctionArg::ExprNamed { arg, .. }
152 | FunctionArg::Unnamed(arg) => arg,
153 };
154 if let FunctionArgExpr::Expr(e) = fae {
155 check_expr(e, ctx, diags);
156 }
157 }
158 }
159}
160
161fn find_func_name_pos(source: &str, name: &str) -> (usize, usize) {
166 let bare = name.rsplit('.').next().unwrap_or(name);
168 let upper_name = bare.to_uppercase();
169 let kw_len = upper_name.len();
170 let upper_src = source.to_uppercase();
171 let bytes = upper_src.as_bytes();
172 let len = bytes.len();
173
174 let mut pos = 0;
175 while pos + kw_len <= len {
176 if let Some(rel) = upper_src[pos..].find(upper_name.as_str()) {
177 let abs = pos + rel;
178
179 let before_ok = abs == 0 || {
180 let b = bytes[abs - 1];
181 !b.is_ascii_alphanumeric() && b != b'_'
182 };
183 let after = abs + kw_len;
184 let after_ok = after >= len || {
185 let b = bytes[after];
186 !b.is_ascii_alphanumeric() && b != b'_'
187 };
188
189 if before_ok && after_ok {
190 return line_col(source, abs);
191 }
192
193 pos = abs + 1;
194 } else {
195 break;
196 }
197 }
198
199 (1, 1)
200}
201
202fn line_col(source: &str, offset: usize) -> (usize, usize) {
204 let before = &source[..offset];
205 let line = before.chars().filter(|&c| c == '\n').count() + 1;
206 let col = before.rfind('\n').map(|p| offset - p - 1).unwrap_or(offset) + 1;
207 (line, col)
208}