1use crate::report::violations::ViolationTier;
2use crate::rules::Rule;
3use crate::rules::conflict::ConflictRule;
4use crate::rules::constraints::BlockingConstraintRule;
5use crate::rules::destructive::{
6 CascadingDropRule, CreateTableAsSelectRule, DropDatabaseRule, DropSchemaCascadeRule,
7 GeneralCascadeRule, ReversibilityRule, SizeAwareAddColumnRule, TypeChangeRewriteRule,
8};
9use crate::rules::drift::DriftDetectionRule;
10use crate::rules::expressions::VolatileDefaultRule;
11use crate::rules::functions::{BrokenComputeRule, FunctionVolatilityRule};
12use crate::rules::idempotency::IdempotencyRule;
13use crate::rules::indexes::ConcurrentIndexRule;
14use crate::rules::opaque::OpaqueDynamicSqlRule;
15use crate::rules::partitions::{PartitionLockRule, PartitionStrategyMismatchRule};
16use crate::rules::policies::RestrictivePolicyRule;
17use crate::rules::security::OverbroadGrantRule;
18use crate::rules::transactions::{
19 AlterTypeAddValueRule, ConcurrentInsideTransactionRule, VacuumFullRule,
20};
21use crate::rules::triggers::DisableTriggerRule;
22use crate::rules::views::MaterializedViewRefreshRule;
23
24pub struct RuleDescriptor {
30 pub id: &'static str,
31 pub title: &'static str,
32 pub summary: &'static str,
33 pub impact: &'static str,
34 factory: fn() -> Box<dyn Rule>,
35}
36
37impl RuleDescriptor {
38 pub fn build(&self) -> Box<dyn Rule> {
39 (self.factory)()
40 }
41
42 pub fn default_tier(&self) -> ViolationTier {
43 self.build().default_tier()
44 }
45
46 pub fn recipe(&self) -> &'static str {
47 self.build().recipe()
48 }
49}
50
51macro_rules! descriptor {
52 ($id:literal, $title:literal, $summary:literal, $impact:literal, $rule:expr) => {
53 RuleDescriptor {
54 id: $id,
55 title: $title,
56 summary: $summary,
57 impact: $impact,
58 factory: || Box::new($rule),
59 }
60 };
61}
62
63pub static PRIMARY_RULES: &[RuleDescriptor] = &[
66 descriptor!(
67 "irreversible-migration",
68 "Irreversible migration",
69 "Flags destructive operations that cannot be reversed.",
70 "data loss",
71 ReversibilityRule
72 ),
73 descriptor!(
74 "drop-database",
75 "Drop database",
76 "Flags database deletion.",
77 "data loss",
78 DropDatabaseRule
79 ),
80 descriptor!(
81 "drop-schema-cascade",
82 "Drop schema with cascade",
83 "Flags schema-wide cascading deletion.",
84 "data loss",
85 DropSchemaCascadeRule
86 ),
87 descriptor!(
88 "destructive-general-cascade",
89 "Destructive cascade",
90 "Flags cascading non-table drops.",
91 "data loss",
92 GeneralCascadeRule
93 ),
94 descriptor!(
95 "destructive-cascade",
96 "Drop table with cascade",
97 "Flags table drops that remove dependencies.",
98 "data loss",
99 CascadingDropRule
100 ),
101 descriptor!(
102 "create-table-as-select",
103 "Create table as select",
104 "Flags potentially expensive CTAS operations.",
105 "rewrite",
106 CreateTableAsSelectRule
107 ),
108 descriptor!(
109 "size-aware-add-column",
110 "Add column on a large table",
111 "Flags column additions that can rewrite large tables.",
112 "rewrite",
113 SizeAwareAddColumnRule
114 ),
115 descriptor!(
116 "type-change-rewrite",
117 "Type change rewrite",
118 "Flags column type changes that rewrite data.",
119 "rewrite",
120 TypeChangeRewriteRule
121 ),
122 descriptor!(
123 "blocking-constraint",
124 "Blocking constraint",
125 "Flags constraint changes that lock or scan tables.",
126 "locking",
127 BlockingConstraintRule
128 ),
129 descriptor!(
130 "require-concurrent-index",
131 "Require concurrent index",
132 "Flags index changes that should use CONCURRENTLY.",
133 "locking",
134 ConcurrentIndexRule
135 ),
136 descriptor!(
137 "blocking-mat-view-refresh",
138 "Blocking materialized-view refresh",
139 "Flags refreshes that block readers.",
140 "locking",
141 MaterializedViewRefreshRule
142 ),
143 descriptor!(
144 "blocking-partition-mutation",
145 "Blocking partition mutation",
146 "Flags partition attach and detach locks.",
147 "locking",
148 PartitionLockRule
149 ),
150 descriptor!(
151 "partition-strategy-mismatch",
152 "Partition strategy mismatch",
153 "Flags incompatible partition attachment.",
154 "correctness",
155 PartitionStrategyMismatchRule
156 ),
157 descriptor!(
158 "restrictive-policy",
159 "Restrictive policy",
160 "Flags policies that narrow row visibility.",
161 "access control",
162 RestrictivePolicyRule
163 ),
164 descriptor!(
165 "disable-trigger",
166 "Disable trigger",
167 "Flags disabled triggers.",
168 "correctness",
169 DisableTriggerRule
170 ),
171 descriptor!(
172 "broken-compute",
173 "Broken compute dependency",
174 "Flags function changes that break triggers.",
175 "correctness",
176 BrokenComputeRule
177 ),
178 descriptor!(
179 "function-volatility-change",
180 "Function volatility change",
181 "Flags changed function volatility.",
182 "query planning",
183 FunctionVolatilityRule
184 ),
185 descriptor!(
186 "missing-idempotency",
187 "Missing idempotency",
188 "Flags migrations unsafe to rerun.",
189 "operability",
190 IdempotencyRule
191 ),
192 descriptor!(
193 "concurrent-in-transaction",
194 "Concurrent index in transaction",
195 "Flags CONCURRENTLY inside a transaction.",
196 "correctness",
197 ConcurrentInsideTransactionRule
198 ),
199 descriptor!(
200 "alter-type-add-value-txn",
201 "Enum value in transaction",
202 "Flags ALTER TYPE ADD VALUE in a transaction.",
203 "correctness",
204 AlterTypeAddValueRule
205 ),
206 descriptor!(
207 "vacuum-full",
208 "Vacuum full",
209 "Flags VACUUM FULL in migrations.",
210 "locking",
211 VacuumFullRule
212 ),
213 descriptor!(
214 "opaque-dynamic-sql",
215 "Opaque dynamic SQL",
216 "Flags SQL whose schema effects cannot be modeled.",
217 "confidence",
218 OpaqueDynamicSqlRule
219 ),
220 descriptor!(
221 "volatile-default",
222 "Volatile default",
223 "Flags volatile default expressions.",
224 "correctness",
225 VolatileDefaultRule
226 ),
227 descriptor!(
228 "overbroad-grant",
229 "Overbroad grant",
230 "Flags broad public privileges.",
231 "access control",
232 OverbroadGrantRule
233 ),
234 descriptor!(
235 "schema-drift",
236 "Schema drift",
237 "Flags references missing from the baseline.",
238 "correctness",
239 DriftDetectionRule
240 ),
241 descriptor!(
242 "chain-conflict",
243 "Migration chain conflict",
244 "Flags statements that conflict with prior migration state.",
245 "correctness",
246 ConflictRule
247 ),
248];
249
250pub fn primary_rule_ids() -> impl Iterator<Item = &'static str> {
251 PRIMARY_RULES.iter().map(|rule| rule.id)
252}
253
254pub fn find_primary_rule(id: &str) -> Option<&'static RuleDescriptor> {
255 PRIMARY_RULES.iter().find(|rule| rule.id == id)
256}
257pub fn build_primary_rules() -> Vec<Box<dyn Rule>> {
258 PRIMARY_RULES.iter().map(RuleDescriptor::build).collect()
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use std::collections::HashSet;
265
266 #[test]
267 fn descriptors_have_unique_ids_matching_the_rules_they_construct() {
268 let ids: HashSet<_> = PRIMARY_RULES
269 .iter()
270 .map(|descriptor| descriptor.id)
271 .collect();
272 assert_eq!(ids.len(), PRIMARY_RULES.len());
273 for descriptor in PRIMARY_RULES {
274 let rule = descriptor.build();
275 assert_eq!(rule.id(), descriptor.id);
276 assert_eq!(descriptor.default_tier(), rule.default_tier());
277 assert_eq!(descriptor.recipe(), rule.recipe());
278 }
279 }
280}