sqruff_lib/rules/ambiguous/
am09.rs1use hashbrown::HashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3
4use crate::core::config::Value;
5use crate::core::rules::context::RuleContext;
6use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
7use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
8
9#[derive(Debug, Clone)]
10pub struct RuleAM09;
11
12impl Rule for RuleAM09 {
13 fn load_from_config(&self, _config: &HashMap<String, Value>) -> Result<ErasedRule, String> {
14 Ok(RuleAM09.erased())
15 }
16
17 fn name(&self) -> &'static str {
18 "ambiguous.order_by_limit"
19 }
20
21 fn description(&self) -> &'static str {
22 "LIMIT/OFFSET without ORDER BY."
23 }
24
25 fn long_description(&self) -> &'static str {
26 r#"
27**Anti-pattern**
28
29Using `LIMIT` or `OFFSET` without `ORDER BY` leads to non-deterministic results, as the database may return different rows on successive executions.
30
31```sql
32SELECT *
33FROM foo
34LIMIT 10;
35```
36
37**Best practice**
38
39Always use `ORDER BY` when using `LIMIT` or `OFFSET`.
40
41```sql
42SELECT *
43FROM foo
44ORDER BY id
45LIMIT 10;
46```
47"#
48 }
49
50 fn groups(&self) -> &'static [RuleGroups] {
51 &[RuleGroups::All, RuleGroups::Ambiguous]
52 }
53
54 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
55 assert!(context.segment.is_type(SyntaxKind::SelectStatement));
56
57 let children = context.segment.segments();
58
59 let limit_or_offset = children
61 .iter()
62 .find(|s| s.is_type(SyntaxKind::LimitClause) || s.is_type(SyntaxKind::OffsetClause));
63
64 let Some(anchor) = limit_or_offset else {
65 return Vec::new();
66 };
67
68 let has_order_by = children
70 .iter()
71 .any(|s| s.is_type(SyntaxKind::OrderbyClause));
72
73 if has_order_by {
74 return Vec::new();
75 }
76
77 vec![LintResult::new(
78 Some(anchor.clone()),
79 Vec::new(),
80 None,
81 Some("LIMIT/OFFSET without ORDER BY may lead to non-deterministic results.".into()),
82 )]
83 }
84
85 fn crawl_behaviour(&self) -> Crawler {
86 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectStatement]) }).into()
87 }
88}