lace_sans_io/logic/
limits.rs1use std::collections::HashSet;
4
5use super::types::{BodyAtom, Rule, Tuple, is_canonical_unsigned_decimal, validate_tuple_values};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct DatalogLimits {
13 pub max_base_facts: usize,
14 pub max_runtime_facts: usize,
15 pub max_derived_facts_per_relation: usize,
16 pub max_rules: usize,
17 pub max_stratum_iterations: usize,
18 pub max_tuple_arity: usize,
19}
20
21impl Default for DatalogLimits {
22 fn default() -> Self {
23 Self {
24 max_base_facts: 1 << 20,
25 max_runtime_facts: 1 << 20,
26 max_derived_facts_per_relation: 1 << 18,
27 max_rules: 256,
28 max_stratum_iterations: 1000,
29 max_tuple_arity: 8,
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum PreparationError {
37 Policy(String),
38 LimitExceeded(String),
39}
40impl PreparationError {
41 pub fn is_limit_exceeded(&self) -> bool {
42 matches!(self, Self::LimitExceeded(_))
43 }
44}
45impl std::fmt::Display for PreparationError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::Policy(message) | Self::LimitExceeded(message) => message.fmt(f),
49 }
50 }
51}
52impl std::error::Error for PreparationError {}
53impl From<String> for PreparationError {
54 fn from(message: String) -> Self {
55 Self::Policy(message)
56 }
57}
58impl From<&str> for PreparationError {
59 fn from(message: &str) -> Self {
60 Self::Policy(message.into())
61 }
62}
63impl From<PreparationError> for String {
64 fn from(error: PreparationError) -> Self {
65 error.to_string()
66 }
67}
68
69impl DatalogLimits {
70 pub(crate) fn for_generated_rules() -> Self {
74 Self {
75 max_rules: usize::MAX,
76 ..Self::default()
77 }
78 }
79
80 pub(crate) fn validate_arity(&self, arity: usize) -> Result<(), PreparationError> {
81 if arity > self.max_tuple_arity {
82 return Err(PreparationError::LimitExceeded(format!(
83 "predicate arity exceeds {}",
84 self.max_tuple_arity
85 )));
86 }
87 Ok(())
88 }
89
90 pub(crate) fn validate_tuple(&self, tuple: &Tuple) -> Result<(), PreparationError> {
91 validate_tuple_values(tuple)?;
92 self.validate_arity(tuple.0.len() - 1)
93 }
94
95 pub(crate) fn validate_rules<'a>(
96 &self,
97 rules: impl IntoIterator<Item = &'a Rule>,
98 ) -> Result<(), PreparationError> {
99 let mut distinct = HashSet::new();
100 for rule in rules {
101 if self.max_rules != usize::MAX {
102 distinct.insert(rule);
103 }
104 self.check_preparation_rule(rule, distinct.len())?;
105 }
106 Ok(())
107 }
108
109 pub fn check_preparation_rule(
112 &self,
113 rule: &Rule,
114 distinct_count: usize,
115 ) -> Result<(), PreparationError> {
116 if distinct_count > self.max_rules {
117 return Err(PreparationError::LimitExceeded(format!(
118 "rule count exceeds {}",
119 self.max_rules
120 )));
121 }
122 self.validate_arity(rule.head_terms.len())?;
123 for atom in &rule.body {
124 if let Some((_, terms, _)) = atom.relation() {
125 self.validate_arity(terms.len())?;
126 }
127 if let BodyAtom::TopK { limit, .. } = atom {
128 if !is_canonical_unsigned_decimal(limit) {
129 return Err("TopK limit must be canonical unsigned decimal text".into());
130 }
131 let bound: usize = limit.parse().map_err(|_| {
132 PreparationError::LimitExceeded(
133 "TopK limit exceeds the configured derived-fact bound".into(),
134 )
135 })?;
136 if bound > self.max_derived_facts_per_relation {
137 return Err(PreparationError::LimitExceeded(
138 "TopK limit exceeds the configured derived-fact bound".into(),
139 ));
140 }
141 }
142 }
143 Ok(())
144 }
145}
146
147pub(crate) struct RuleCollector {
150 limits: DatalogLimits,
151 seen: HashSet<String>,
152 rules: Vec<Rule>,
153}
154
155impl RuleCollector {
156 pub(crate) fn new(limits: DatalogLimits) -> Self {
157 Self {
158 limits,
159 seen: HashSet::new(),
160 rules: Vec::new(),
161 }
162 }
163
164 pub(crate) fn push(&mut self, rule: Rule) -> Result<(), PreparationError> {
165 let key = crate::lacegram::render_rule_line(&rule);
166 let fresh = !self.seen.contains(&key);
167 self.limits
168 .check_preparation_rule(&rule, self.rules.len() + usize::from(fresh))?;
169 if fresh {
170 self.seen.insert(key);
171 self.rules.push(rule);
172 }
173 Ok(())
174 }
175
176 pub(crate) fn into_rules(self) -> Vec<Rule> {
177 self.rules
178 }
179}