qualia_core_db/sparql_library/parsers/
nquads_star.rs1use crate::lexicon::{generate_60bit_token, generate_embedded_triple_id};
2use crate::rdf_star::{RdfStarParseError, RdfStarParser};
3use crate::NQuin;
9
10#[derive(Debug, Clone, Default)]
12pub struct NQuadsLineTerms {
13 pub subject: String,
14 pub predicate: String,
15 pub object: String,
16 pub graph: String,
17 pub outer_predicate: String,
18 pub outer_object: String,
19 pub outer_graph: String,
20}
21
22pub struct NQuadsStarParser {
24 context_hash: u64,
26 last_terms: Option<NQuadsLineTerms>,
27}
28
29impl NQuadsStarParser {
30 pub fn new(context_hash: u64) -> Self {
32 Self {
33 context_hash,
34 last_terms: None,
35 }
36 }
37
38 pub fn session_context(&self) -> u64 {
40 self.context_hash
41 }
42
43 pub fn last_line_terms(&self) -> Option<&NQuadsLineTerms> {
45 self.last_terms.as_ref()
46 }
47
48 fn graph_hash_from_token(&self, graph: &str) -> u64 {
49 if graph.is_empty() {
50 self.context_hash
51 } else {
52 generate_60bit_token(graph.as_bytes())
53 }
54 }
55
56 fn store_regular_terms(
57 &mut self,
58 subject_str: &str,
59 predicate_str: &str,
60 object_str: &str,
61 graph_str: &str,
62 ) {
63 self.last_terms = Some(NQuadsLineTerms {
64 subject: subject_str.to_string(),
65 predicate: predicate_str.to_string(),
66 object: object_str.to_string(),
67 graph: graph_str.to_string(),
68 ..Default::default()
69 });
70 }
71
72 fn store_embedded_terms(
73 &mut self,
74 outer_predicate_str: &str,
75 outer_object_str: &str,
76 outer_graph_str: &str,
77 ) {
78 self.last_terms = Some(NQuadsLineTerms {
79 outer_predicate: outer_predicate_str.to_string(),
80 outer_object: outer_object_str.to_string(),
81 outer_graph: outer_graph_str.to_string(),
82 ..Default::default()
83 });
84 }
85
86 fn parse_line(&mut self, line: &str) -> Result<ParseResult, RdfStarParseError> {
88 let line = line.trim();
89 if line.is_empty() || line.starts_with('#') {
90 return Ok(ParseResult::Comment);
91 }
92
93 if line.starts_with("<<<") {
95 self.parse_embedded_triple_line(line)
96 } else {
97 self.parse_quad_line(line)
98 }
99 }
100
101 fn parse_quad_line(&mut self, line: &str) -> Result<ParseResult, RdfStarParseError> {
103 let parts: Vec<&str> = line.split_whitespace().collect();
105 if parts.len() < 5 {
106 return Err(RdfStarParseError::InvalidSyntax);
107 }
108
109 let subject_str = parts[0];
110 let predicate_str = parts[1];
111 let object_str = parts[2];
112 let graph_str = parts[3];
113
114 let subject = subject_str.trim_start_matches('<').trim_end_matches('>');
116 let predicate = predicate_str.trim_start_matches('<').trim_end_matches('>');
117 let object = object_str.trim_start_matches('<').trim_end_matches('>');
118 let graph = graph_str.trim_start_matches('<').trim_end_matches('>');
119
120 let subject_hash = generate_60bit_token(subject.as_bytes());
121 let predicate_hash = generate_60bit_token(predicate.as_bytes());
122 let object_hash = generate_60bit_token(object.as_bytes());
123 let graph_hash = self.graph_hash_from_token(graph);
124 self.store_regular_terms(subject, predicate, object, graph);
125
126 Ok(ParseResult::RegularQuad {
127 subject: subject_hash,
128 predicate: predicate_hash,
129 object: object_hash,
130 graph: graph_hash,
131 })
132 }
133
134 fn parse_embedded_triple_line(&mut self, line: &str) -> Result<ParseResult, RdfStarParseError> {
136 let end_embedded = line
140 .find(">>>")
141 .ok_or(RdfStarParseError::MalformedEmbeddedTriple)?;
142
143 let embedded_part = &line[3..end_embedded]; let embedded_parts: Vec<&str> = embedded_part.split_whitespace().collect();
148 if embedded_parts.len() < 3 {
149 return Err(RdfStarParseError::MalformedEmbeddedTriple);
150 }
151
152 let subject = embedded_parts[0]
153 .trim_start_matches('<')
154 .trim_end_matches('>');
155 let predicate = embedded_parts[1]
156 .trim_start_matches('<')
157 .trim_end_matches('>');
158 let object = embedded_parts[2]
159 .trim_start_matches('<')
160 .trim_end_matches('>');
161
162 let subject_hash = generate_60bit_token(subject.as_bytes());
163 let predicate_hash = generate_60bit_token(predicate.as_bytes());
164 let object_hash = generate_60bit_token(object.as_bytes());
165
166 let virtual_id = generate_embedded_triple_id(subject_hash, predicate_hash, object_hash);
168
169 let remaining = &line[end_embedded + 3..]; let outer_parts: Vec<&str> = remaining.split_whitespace().collect();
172 if outer_parts.len() < 3 {
173 return Err(RdfStarParseError::MalformedEmbeddedTriple);
174 }
175
176 let outer_predicate = outer_parts[0].trim_start_matches('<').trim_end_matches('>');
177 let outer_object = outer_parts[1].trim_start_matches('<').trim_end_matches('>');
178 let outer_graph = outer_parts[2].trim_start_matches('<').trim_end_matches('>');
179
180 let outer_predicate_hash = generate_60bit_token(outer_predicate.as_bytes());
181 let outer_object_hash = generate_60bit_token(outer_object.as_bytes());
182 let outer_graph_hash = self.graph_hash_from_token(outer_graph);
183 self.store_embedded_terms(outer_predicate, outer_object, outer_graph);
184
185 Ok(ParseResult::EmbeddedQuad {
186 virtual_id,
187 components: [subject_hash, predicate_hash, object_hash],
188 outer_predicate: outer_predicate_hash,
189 outer_object: outer_object_hash,
190 outer_graph: outer_graph_hash,
191 })
192 }
193}
194
195impl RdfStarParser for NQuadsStarParser {
196 fn parse_embedded_triple(
197 &mut self,
198 input: &[u8],
199 ) -> Result<(u64, [u64; 3]), RdfStarParseError> {
200 let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
201
202 match self.parse_line(line)? {
203 ParseResult::EmbeddedQuad {
204 virtual_id,
205 components,
206 ..
207 } => Ok((virtual_id, components)),
208 _ => Err(RdfStarParseError::MalformedEmbeddedTriple),
209 }
210 }
211
212 fn parse_triple(&mut self, input: &[u8]) -> Result<(u64, u64, u64), RdfStarParseError> {
213 let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
214
215 match self.parse_line(line)? {
216 ParseResult::RegularQuad {
217 subject,
218 predicate,
219 object,
220 ..
221 } => Ok((subject, predicate, object)),
222 _ => Err(RdfStarParseError::InvalidSyntax),
223 }
224 }
225
226 fn parse_quad(&mut self, input: &[u8]) -> Result<(u64, u64, u64, u64), RdfStarParseError> {
227 let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
228
229 match self.parse_line(line)? {
230 ParseResult::RegularQuad {
231 subject,
232 predicate,
233 object,
234 graph,
235 ..
236 } => Ok((subject, predicate, object, graph)),
237 ParseResult::EmbeddedQuad {
238 outer_predicate,
239 outer_object,
240 outer_graph,
241 ..
242 } => Ok((0, outer_predicate, outer_object, outer_graph)),
243 _ => Err(RdfStarParseError::InvalidSyntax),
244 }
245 }
246
247 fn supports_quads(&self) -> bool {
248 true
249 }
250
251 fn supports_named_graphs(&self) -> bool {
252 true
253 }
254
255 fn format_name(&self) -> &'static str {
256 "N-Quads-Star"
257 }
258}
259
260enum ParseResult {
262 Comment,
263 RegularQuad {
264 subject: u64,
265 predicate: u64,
266 object: u64,
267 graph: u64,
268 },
269 EmbeddedQuad {
270 virtual_id: u64,
271 components: [u64; 3],
272 outer_predicate: u64,
273 outer_object: u64,
274 outer_graph: u64,
275 },
276}
277
278pub fn parse_nquads_star_into<R: std::io::Read, S: crate::sparql_library::quin_sink::QuinSink>(
280 reader: R,
281 context_hash: u64,
282 sink: &mut S,
283) -> Result<u64, Box<dyn std::error::Error>> {
284 use std::io::BufRead;
285
286 let mut parser = NQuadsStarParser::new(context_hash);
287 let mut count = 0;
288 let buf_reader = BufReader::new(reader);
289
290 for line in buf_reader.lines() {
291 let line = line?;
292 match parser.parse_line(&line)? {
293 ParseResult::Comment => continue,
294 ParseResult::RegularQuad {
295 subject,
296 predicate,
297 object,
298 graph,
299 ..
300 } => {
301 sink.push(NQuin {
302 subject,
303 predicate,
304 object,
305 context: graph, metadata: 0b10 << 61,
307 parity: 0,
308 })?;
309 count += 1;
310 }
311 ParseResult::EmbeddedQuad {
312 virtual_id,
313 components,
314 outer_predicate,
315 outer_object,
316 outer_graph,
317 ..
318 } => {
319 sink.push(NQuin {
320 subject: virtual_id,
321 predicate: outer_predicate,
322 object: outer_object,
323 context: outer_graph,
324 metadata: 0b10 << 61,
325 parity: 0,
326 })?;
327 count += 1;
328
329 sink.push(NQuin {
330 subject: components[0],
331 predicate: components[1],
332 object: components[2],
333 context: outer_graph,
334 metadata: 0b10 << 61,
335 parity: 0,
336 })?;
337 count += 1;
338 }
339 }
340 }
341
342 Ok(count)
343}
344
345pub fn parse_nquads_star_stream<R: std::io::Read>(
347 reader: R,
348 context_hash: u64,
349 sorter: &mut crate::external_sort::ExternalSorter,
350) -> Result<u64, Box<dyn std::error::Error>> {
351 parse_nquads_star_into(reader, context_hash, sorter)
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use crate::rdf_star::RdfStarParser;
358
359 #[test]
360 fn test_nquads_star_parser_creation() {
361 let parser = NQuadsStarParser::new(0);
362 assert_eq!(parser.format_name(), "N-Quads-Star");
363 assert!(parser.supports_quads());
364 assert!(parser.supports_named_graphs());
365 }
366
367 #[test]
368 fn test_parse_regular_quad() {
369 let mut parser = NQuadsStarParser::new(0);
370 let input = b"<http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> <http://example.org/Graph1> .";
371 let result = parser.parse_quad(input);
372 assert!(result.is_ok());
373 let (s, p, o, g) = result.unwrap();
374 assert_ne!(s, 0);
375 assert_ne!(p, 0);
376 assert_ne!(o, 0);
377 assert_ne!(g, 0);
378 }
379
380 #[test]
381 fn test_parse_embedded_quad() {
382 let mut parser = NQuadsStarParser::new(0);
383 let input = b"<<<http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob>>> <http://example.org/saidBy> <http://example.org/Charlie> <http://example.org/Graph1> .";
384 let result = parser.parse_embedded_triple(input);
385 assert!(result.is_ok());
386 let (virtual_id, components) = result.unwrap();
387 assert_ne!(virtual_id, 0);
388 assert_ne!(components[0], 0);
389 assert_ne!(components[1], 0);
390 assert_ne!(components[2], 0);
391 }
392}
393use std::io::BufReader;