qualia_core_db/sparql_library/
sparql_shacl.rs1use crate::sparql_ast::*;
6use crate::sparql_executor::*;
7use crate::sparql_planner::*;
8use crate::NQuin;
9
10#[repr(C)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ShaclConstraint {
14 NodeKind {
16 node_kind: u8, },
18 Class { class_iri: u64 },
20 MinCount { min_count: u64 },
22 MaxCount { max_count: u64 },
24 Datatype { datatype_iri: u64 },
26 Pattern {
28 pattern_regex: u64, },
30 Sparql { query: SparqlQuery },
32 Property {
34 predicate: u64,
35 constraints: [ConstraintId; 16],
36 constraint_count: u8,
37 },
38}
39
40pub type ConstraintId = u16;
41
42#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct ShaclShape {
46 pub shape_iri: u64,
47 pub target_class: Option<u64>,
48 pub target_node: Option<u64>,
49 pub constraints: [ConstraintId; 32],
50 pub constraint_count: u8,
51}
52
53#[repr(C)]
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct ValidationResult {
57 pub conforms: bool,
58 pub focus_node: u64,
59 pub violation_count: u16,
60}
61
62pub struct ShaclValidator<'a> {
64 pub quins: &'a [NQuin],
65 pub shapes: [ShaclShape; 64],
66 pub shape_count: u8,
67 pub constraints: [ShaclConstraint; 256],
68 pub constraint_count: u16,
69}
70
71impl<'a> ShaclValidator<'a> {
72 pub fn new(quins: &'a [NQuin]) -> Self {
73 Self {
74 quins,
75 shapes: [ShaclShape {
76 shape_iri: 0,
77 target_class: None,
78 target_node: None,
79 constraints: [0; 32],
80 constraint_count: 0,
81 }; 64],
82 shape_count: 0,
83 constraints: [ShaclConstraint::NodeKind { node_kind: 0 }; 256],
84 constraint_count: 0,
85 }
86 }
87
88 pub fn add_shape(&mut self, shape: ShaclShape) -> Result<u8, String> {
90 if self.shape_count >= 64 {
91 return Err("Shape overflow".to_string());
92 }
93 let idx = self.shape_count;
94 self.shapes[idx as usize] = shape;
95 self.shape_count += 1;
96 Ok(idx)
97 }
98
99 pub fn add_constraint(&mut self, constraint: ShaclConstraint) -> Result<ConstraintId, String> {
101 if self.constraint_count >= 256 {
102 return Err("Constraint overflow".to_string());
103 }
104 let idx = self.constraint_count as ConstraintId;
105 self.constraints[self.constraint_count as usize] = constraint;
106 self.constraint_count += 1;
107 Ok(idx)
108 }
109
110 pub fn validate_node(&self, node: u64, shape: &ShaclShape) -> Result<ValidationResult, String> {
112 let mut violation_count = 0;
113
114 for i in 0..shape.constraint_count as usize {
116 let constraint_id = shape.constraints[i];
117 let constraint = self
118 .constraints
119 .get(constraint_id as usize)
120 .ok_or("Constraint ID out of bounds")?;
121
122 if !self.validate_constraint(node, constraint)? {
123 violation_count += 1;
124 }
125 }
126
127 Ok(ValidationResult {
128 conforms: violation_count == 0,
129 focus_node: node,
130 violation_count,
131 })
132 }
133
134 fn validate_constraint(&self, node: u64, constraint: &ShaclConstraint) -> Result<bool, String> {
136 match constraint {
137 ShaclConstraint::NodeKind { node_kind } => {
138 let actual_kind = self.get_node_kind(node);
140 Ok(actual_kind == *node_kind)
141 }
142 ShaclConstraint::Class { class_iri } => {
143 self.check_class_membership(node, *class_iri)
145 }
146 ShaclConstraint::MinCount { min_count } => {
147 self.check_min_count(node, *min_count)
149 }
150 ShaclConstraint::MaxCount { max_count } => {
151 self.check_max_count(node, *max_count)
153 }
154 ShaclConstraint::Datatype { datatype_iri } => {
155 self.check_datatype(node, *datatype_iri)
157 }
158 ShaclConstraint::Pattern { pattern_regex } => {
159 self.check_pattern(node, *pattern_regex)
161 }
162 ShaclConstraint::Sparql { query } => {
163 self.validate_sparql_constraint(node, query)
165 }
166 ShaclConstraint::Property { .. } => {
167 Ok(true)
169 }
170 }
171 }
172
173 fn get_node_kind(&self, node: u64) -> u8 {
174 if node >= 0x8000_0000_0000_0000 {
176 0 } else if node & 0x7000_0000_0000_0000 != 0 {
178 2 } else {
180 1 }
182 }
183
184 fn check_class_membership(&self, node: u64, class_iri: u64) -> Result<bool, String> {
185 let rdf_type = crate::lexicon::generate_60bit_token(
187 b"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
188 );
189
190 for quin in self.quins {
191 if quin.subject == node && quin.predicate == rdf_type && quin.object == class_iri {
192 return Ok(true);
193 }
194 }
195
196 Ok(false)
197 }
198
199 fn check_min_count(&self, node: u64, min_count: u64) -> Result<bool, String> {
200 let count = self.count_outgoing_triples(node);
201 Ok(count >= min_count)
202 }
203
204 fn check_max_count(&self, node: u64, max_count: u64) -> Result<bool, String> {
205 let count = self.count_outgoing_triples(node);
206 Ok(count <= max_count)
207 }
208
209 fn check_datatype(&self, node: u64, datatype_iri: u64) -> Result<bool, String> {
210 let expected_tag = datatype_iri_to_tag(datatype_iri);
211 for quin in self.quins {
212 if quin.subject != node {
213 continue;
214 }
215 if quin.object >> 63 != 0 {
216 return Ok(false);
217 }
218 let tag = ((quin.object >> 60) & 0b111) as u8;
219 return Ok(tag == expected_tag);
220 }
221 Ok(false)
222 }
223
224 fn check_pattern(&self, node: u64, pattern_regex: u64) -> Result<bool, String> {
225 for quin in self.quins {
226 if quin.subject == node {
227 let payload = quin.object & 0x0FFF_FFFF_FFFF_FFFF;
228 if payload == pattern_regex {
229 return Ok(true);
230 }
231 }
232 }
233 Ok(false)
234 }
235
236 fn validate_sparql_constraint(&self, node: u64, query: &SparqlQuery) -> Result<bool, String> {
237 let mut ctx = SparqlQueryContext::new();
238 let _this_var = ctx.register_variable("$this")?;
239 let plan = QueryPlanner::plan(query, &ctx)?;
240 let executor = QueryExecutor::new(self.quins);
241 let focused = self
242 .quins
243 .iter()
244 .any(|q| q.subject == node || q.object == node);
245 if !focused {
246 return Ok(false);
247 }
248 match query {
249 SparqlQuery::Ask(_) => executor.execute_ask(&plan, &ctx),
250 _ => {
251 let results = executor.execute(&plan, &ctx)?;
252 Ok(!results.is_empty())
253 }
254 }
255 }
256
257 fn count_outgoing_triples(&self, node: u64) -> u64 {
258 let mut count = 0;
259 for quin in self.quins {
260 if quin.subject == node {
261 count += 1;
262 }
263 }
264 count
265 }
266
267 pub fn validate_graph(&self) -> Result<Vec<ValidationResult>, String> {
269 let mut results = Vec::new();
270
271 for shape_idx in 0..self.shape_count as usize {
273 let shape = self.shapes[shape_idx];
274
275 let target_nodes = self.find_target_nodes(&shape)?;
277
278 for node in target_nodes {
279 let result = self.validate_node(node, &shape)?;
280 results.push(result);
281 }
282 }
283
284 Ok(results)
285 }
286
287 fn find_target_nodes(&self, shape: &ShaclShape) -> Result<Vec<u64>, String> {
288 let mut nodes = Vec::new();
289
290 if let Some(target_node) = shape.target_node {
291 nodes.push(target_node);
292 } else if let Some(target_class) = shape.target_class {
293 let rdf_type = crate::lexicon::generate_60bit_token(
295 b"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
296 );
297
298 for quin in self.quins {
299 if quin.predicate == rdf_type && quin.object == target_class {
300 nodes.push(quin.subject);
301 }
302 }
303 }
304
305 Ok(nodes)
306 }
307}
308
309impl<'a> Default for ShaclValidator<'a> {
310 fn default() -> Self {
311 Self::new(&[])
312 }
313}
314
315fn datatype_iri_to_tag(datatype_iri: u64) -> u8 {
316 if datatype_iri == crate::q_hash("xsd:string") {
317 0
318 } else if datatype_iri == crate::q_hash("xsd:integer") {
319 1
320 } else if datatype_iri == crate::q_hash("xsd:decimal")
321 || datatype_iri == crate::q_hash("xsd:double")
322 {
323 2
324 } else if datatype_iri == crate::q_hash("xsd:boolean") {
325 3
326 } else {
327 0
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_shacl_validator_creation() {
337 let quins = vec![];
338 let validator = ShaclValidator::new(&quins);
339 assert_eq!(validator.shape_count, 0);
340 }
341
342 #[test]
343 fn test_add_shape() {
344 let quins = vec![];
345 let mut validator = ShaclValidator::new(&quins);
346
347 let shape = ShaclShape {
348 shape_iri: 1,
349 target_class: None,
350 target_node: None,
351 constraints: [0; 32],
352 constraint_count: 0,
353 };
354
355 let result = validator.add_shape(shape);
356 assert!(result.is_ok());
357 assert_eq!(validator.shape_count, 1);
358 }
359
360 #[test]
361 fn test_add_constraint() {
362 let quins = vec![];
363 let mut validator = ShaclValidator::new(&quins);
364
365 let constraint = ShaclConstraint::NodeKind { node_kind: 1 };
366 let result = validator.add_constraint(constraint);
367 assert!(result.is_ok());
368 assert_eq!(validator.constraint_count, 1);
369 }
370}