rigsql_rules/ambiguous/
am09.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
10pub struct RuleAM09;
11
12impl Rule for RuleAM09 {
13 fn code(&self) -> &'static str {
14 "AM09"
15 }
16 fn name(&self) -> &'static str {
17 "ambiguous.order_by_limit"
18 }
19 fn description(&self) -> &'static str {
20 "LIMIT without ORDER BY."
21 }
22 fn explanation(&self) -> &'static str {
23 "Using LIMIT without ORDER BY produces non-deterministic results because the \
24 database is free to return rows in any order. Always pair LIMIT with ORDER BY \
25 to get predictable, reproducible result sets."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Ambiguous]
29 }
30 fn is_fixable(&self) -> bool {
31 false
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::SelectStatement])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40
41 let limit_clause = children
42 .iter()
43 .find(|c| c.segment_type() == SegmentType::LimitClause);
44
45 let has_order_by = children
46 .iter()
47 .any(|c| c.segment_type() == SegmentType::OrderByClause);
48
49 if let Some(limit) = limit_clause {
50 if !has_order_by {
51 return vec![LintViolation::with_msg_key(
52 self.code(),
53 "LIMIT without ORDER BY gives non-deterministic results.",
54 limit.span(),
55 "rules.AM09.msg",
56 vec![],
57 )];
58 }
59 }
60
61 vec![]
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::test_utils::lint_sql;
69
70 #[test]
71 fn test_am09_flags_limit_without_order_by() {
72 let violations = lint_sql("SELECT a FROM t LIMIT 10", RuleAM09);
73 assert_eq!(violations.len(), 1);
74 }
75
76 #[test]
77 fn test_am09_accepts_limit_with_order_by() {
78 let violations = lint_sql("SELECT a FROM t ORDER BY a LIMIT 10", RuleAM09);
79 assert_eq!(violations.len(), 0);
80 }
81
82 #[test]
83 fn test_am09_accepts_no_limit() {
84 let violations = lint_sql("SELECT a FROM t", RuleAM09);
85 assert_eq!(violations.len(), 0);
86 }
87}