1use std::collections::HashMap;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct UpdateParseError {
17 pub message: String,
19 pub position: usize,
21 pub line: Option<usize>,
23 pub column: Option<usize>,
25}
26
27impl UpdateParseError {
28 pub fn new(message: impl Into<String>, position: usize) -> Self {
30 Self {
31 message: message.into(),
32 position,
33 line: None,
34 column: None,
35 }
36 }
37
38 pub fn with_location(mut self, line: usize, column: usize) -> Self {
40 self.line = Some(line);
41 self.column = Some(column);
42 self
43 }
44}
45
46impl fmt::Display for UpdateParseError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match (self.line, self.column) {
49 (Some(ln), Some(col)) => {
50 write!(f, "update parse error at {}:{}: {}", ln, col, self.message)
51 }
52 _ => write!(
53 f,
54 "update parse error at byte {}: {}",
55 self.position, self.message
56 ),
57 }
58 }
59}
60
61impl std::error::Error for UpdateParseError {}
62
63fn line_col(source: &str, byte_offset: usize) -> (usize, usize) {
65 let prefix = &source[..byte_offset.min(source.len())];
66 let line = prefix.chars().filter(|&c| c == '\n').count() + 1;
67 let last_newline = prefix.rfind('\n').map(|p| p + 1).unwrap_or(0);
68 let col = byte_offset.saturating_sub(last_newline) + 1;
69 (line, col)
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct TriplePattern {
79 pub subject: String,
80 pub predicate: String,
81 pub object: String,
82}
83
84impl TriplePattern {
85 pub fn new(
86 subject: impl Into<String>,
87 predicate: impl Into<String>,
88 object: impl Into<String>,
89 ) -> Self {
90 Self {
91 subject: subject.into(),
92 predicate: predicate.into(),
93 object: object.into(),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum GraphTarget {
101 Graph(String),
103 Default,
105 Named,
107 All,
109}
110
111impl fmt::Display for GraphTarget {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 GraphTarget::Graph(iri) => write!(f, "GRAPH <{}>", iri),
115 GraphTarget::Default => write!(f, "DEFAULT"),
116 GraphTarget::Named => write!(f, "NAMED"),
117 GraphTarget::All => write!(f, "ALL"),
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub enum UpdateOperation {
125 InsertData {
127 triples: Vec<TriplePattern>,
129 graph: Option<String>,
131 },
132 DeleteData {
134 triples: Vec<TriplePattern>,
136 graph: Option<String>,
138 },
139 DeleteInsertWhere {
141 delete_triples: Vec<TriplePattern>,
142 insert_triples: Vec<TriplePattern>,
143 where_triples: Vec<TriplePattern>,
144 graph: Option<String>,
145 },
146 Load {
148 source_uri: String,
149 target_graph: Option<String>,
150 silent: bool,
151 },
152 Clear { target: GraphTarget, silent: bool },
154 Drop { target: GraphTarget, silent: bool },
156 CreateGraph { graph_iri: String, silent: bool },
158 Copy {
160 source: GraphTarget,
161 destination: GraphTarget,
162 silent: bool,
163 },
164 Move {
166 source: GraphTarget,
167 destination: GraphTarget,
168 silent: bool,
169 },
170 Add {
172 source: GraphTarget,
173 destination: GraphTarget,
174 silent: bool,
175 },
176}
177
178impl UpdateOperation {
179 pub fn kind_label(&self) -> &'static str {
181 match self {
182 UpdateOperation::InsertData { .. } => "INSERT DATA",
183 UpdateOperation::DeleteData { .. } => "DELETE DATA",
184 UpdateOperation::DeleteInsertWhere { .. } => "DELETE/INSERT WHERE",
185 UpdateOperation::Load { .. } => "LOAD",
186 UpdateOperation::Clear { .. } => "CLEAR",
187 UpdateOperation::Drop { .. } => "DROP",
188 UpdateOperation::CreateGraph { .. } => "CREATE GRAPH",
189 UpdateOperation::Copy { .. } => "COPY",
190 UpdateOperation::Move { .. } => "MOVE",
191 UpdateOperation::Add { .. } => "ADD",
192 }
193 }
194}
195
196#[derive(Debug, Clone, PartialEq)]
198pub struct UpdateRequest {
199 pub prefixes: HashMap<String, String>,
201 pub operations: Vec<UpdateOperation>,
203}
204
205pub struct UpdateParser {
211 prefixes: HashMap<String, String>,
213}
214
215impl Default for UpdateParser {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl UpdateParser {
222 pub fn new() -> Self {
224 Self {
225 prefixes: HashMap::new(),
226 }
227 }
228
229 pub fn with_prefixes(prefixes: HashMap<String, String>) -> Self {
231 Self { prefixes }
232 }
233
234 pub fn parse(&mut self, input: &str) -> Result<UpdateRequest, UpdateParseError> {
236 let mut pos = 0usize;
237 let mut operations = Vec::new();
238
239 pos = self.skip_ws(input, pos);
241 pos = self.parse_prologue(input, pos)?;
242
243 loop {
245 pos = self.skip_ws(input, pos);
246 if pos >= input.len() {
247 break;
248 }
249
250 let op = self.parse_operation(input, &mut pos)?;
251 operations.push(op);
252
253 pos = self.skip_ws(input, pos);
254 if pos < input.len() && input.as_bytes().get(pos) == Some(&b';') {
255 pos += 1; }
257 }
258
259 if operations.is_empty() {
260 let (ln, col) = line_col(input, pos);
261 return Err(UpdateParseError::new("empty update request", pos).with_location(ln, col));
262 }
263
264 Ok(UpdateRequest {
265 prefixes: self.prefixes.clone(),
266 operations,
267 })
268 }
269
270 fn parse_prologue(&mut self, input: &str, mut pos: usize) -> Result<usize, UpdateParseError> {
273 loop {
274 pos = self.skip_ws(input, pos);
275 if self.match_keyword(input, pos, "PREFIX") {
276 pos = self.consume_keyword(input, pos, "PREFIX")?;
277 pos = self.skip_ws(input, pos);
278
279 let (prefix, new_pos) = self.read_prefix_label(input, pos)?;
280 pos = self.skip_ws(input, new_pos);
281
282 let (iri, new_pos) = self.read_iri_ref(input, pos)?;
283 pos = new_pos;
284
285 self.prefixes.insert(prefix, iri);
286 } else if self.match_keyword(input, pos, "BASE") {
287 pos = self.consume_keyword(input, pos, "BASE")?;
288 pos = self.skip_ws(input, pos);
289 let (_, new_pos) = self.read_iri_ref(input, pos)?;
291 pos = new_pos;
292 } else {
293 break;
294 }
295 }
296 Ok(pos)
297 }
298
299 fn parse_operation(
302 &self,
303 input: &str,
304 pos: &mut usize,
305 ) -> Result<UpdateOperation, UpdateParseError> {
306 *pos = self.skip_ws(input, *pos);
307 if *pos >= input.len() {
308 let (ln, col) = line_col(input, *pos);
309 return Err(
310 UpdateParseError::new("unexpected end of input", *pos).with_location(ln, col)
311 );
312 }
313
314 if self.match_keyword(input, *pos, "INSERT") {
316 self.parse_insert(input, pos)
317 } else if self.match_keyword(input, *pos, "DELETE") {
318 self.parse_delete(input, pos)
319 } else if self.match_keyword(input, *pos, "LOAD") {
320 self.parse_load(input, pos)
321 } else if self.match_keyword(input, *pos, "CLEAR") {
322 self.parse_clear(input, pos)
323 } else if self.match_keyword(input, *pos, "DROP") {
324 self.parse_drop(input, pos)
325 } else if self.match_keyword(input, *pos, "CREATE") {
326 self.parse_create(input, pos)
327 } else if self.match_keyword(input, *pos, "COPY") {
328 self.parse_copy(input, pos)
329 } else if self.match_keyword(input, *pos, "MOVE") {
330 self.parse_move(input, pos)
331 } else if self.match_keyword(input, *pos, "ADD") {
332 self.parse_add(input, pos)
333 } else {
334 let (ln, col) = line_col(input, *pos);
335 let snippet: String = input[*pos..].chars().take(20).collect();
336 Err(UpdateParseError::new(
337 format!("expected update keyword, found: '{}'", snippet),
338 *pos,
339 )
340 .with_location(ln, col))
341 }
342 }
343
344 fn parse_insert(
347 &self,
348 input: &str,
349 pos: &mut usize,
350 ) -> Result<UpdateOperation, UpdateParseError> {
351 *pos = self.consume_keyword(input, *pos, "INSERT")?;
352 *pos = self.skip_ws(input, *pos);
353
354 if self.match_keyword(input, *pos, "DATA") {
355 *pos = self.consume_keyword(input, *pos, "DATA")?;
356 *pos = self.skip_ws(input, *pos);
357
358 let (graph, triples) = self.parse_quad_data(input, pos)?;
359 Ok(UpdateOperation::InsertData { triples, graph })
360 } else {
361 let (ln, col) = line_col(input, *pos);
363 Err(UpdateParseError::new(
364 "expected DATA after INSERT (standalone INSERT without DELETE is not supported here; use DELETE/INSERT WHERE)",
365 *pos,
366 )
367 .with_location(ln, col))
368 }
369 }
370
371 fn parse_delete(
374 &self,
375 input: &str,
376 pos: &mut usize,
377 ) -> Result<UpdateOperation, UpdateParseError> {
378 *pos = self.consume_keyword(input, *pos, "DELETE")?;
379 *pos = self.skip_ws(input, *pos);
380
381 if self.match_keyword(input, *pos, "DATA") {
382 *pos = self.consume_keyword(input, *pos, "DATA")?;
383 *pos = self.skip_ws(input, *pos);
384
385 let (graph, triples) = self.parse_quad_data(input, pos)?;
386 Ok(UpdateOperation::DeleteData { triples, graph })
387 } else if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'{') {
388 let delete_triples = self.parse_brace_block(input, pos)?;
390 *pos = self.skip_ws(input, *pos);
391
392 let mut insert_triples = Vec::new();
393 if self.match_keyword(input, *pos, "INSERT") {
394 *pos = self.consume_keyword(input, *pos, "INSERT")?;
395 *pos = self.skip_ws(input, *pos);
396 insert_triples = self.parse_brace_block(input, pos)?;
397 *pos = self.skip_ws(input, *pos);
398 }
399
400 *pos = self.consume_keyword(input, *pos, "WHERE")?;
401 *pos = self.skip_ws(input, *pos);
402 let where_triples = self.parse_brace_block(input, pos)?;
403
404 Ok(UpdateOperation::DeleteInsertWhere {
405 delete_triples,
406 insert_triples,
407 where_triples,
408 graph: None,
409 })
410 } else {
411 let (ln, col) = line_col(input, *pos);
412 Err(
413 UpdateParseError::new("expected DATA or '{' after DELETE", *pos)
414 .with_location(ln, col),
415 )
416 }
417 }
418
419 fn parse_load(
422 &self,
423 input: &str,
424 pos: &mut usize,
425 ) -> Result<UpdateOperation, UpdateParseError> {
426 *pos = self.consume_keyword(input, *pos, "LOAD")?;
427 *pos = self.skip_ws(input, *pos);
428
429 let silent = self.try_consume_keyword(input, pos, "SILENT");
430 *pos = self.skip_ws(input, *pos);
431
432 let (source_uri, new_pos) = self.read_iri_ref(input, *pos)?;
433 *pos = new_pos;
434 *pos = self.skip_ws(input, *pos);
435
436 let target_graph = if self.match_keyword(input, *pos, "INTO") {
437 *pos = self.consume_keyword(input, *pos, "INTO")?;
438 *pos = self.skip_ws(input, *pos);
439 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
440 *pos = self.skip_ws(input, *pos);
441 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
442 *pos = new_pos;
443 Some(iri)
444 } else {
445 None
446 };
447
448 Ok(UpdateOperation::Load {
449 source_uri,
450 target_graph,
451 silent,
452 })
453 }
454
455 fn parse_clear(
458 &self,
459 input: &str,
460 pos: &mut usize,
461 ) -> Result<UpdateOperation, UpdateParseError> {
462 *pos = self.consume_keyword(input, *pos, "CLEAR")?;
463 *pos = self.skip_ws(input, *pos);
464 let silent = self.try_consume_keyword(input, pos, "SILENT");
465 *pos = self.skip_ws(input, *pos);
466 let target = self.parse_graph_ref_all(input, pos)?;
467 Ok(UpdateOperation::Clear { target, silent })
468 }
469
470 fn parse_drop(
471 &self,
472 input: &str,
473 pos: &mut usize,
474 ) -> Result<UpdateOperation, UpdateParseError> {
475 *pos = self.consume_keyword(input, *pos, "DROP")?;
476 *pos = self.skip_ws(input, *pos);
477 let silent = self.try_consume_keyword(input, pos, "SILENT");
478 *pos = self.skip_ws(input, *pos);
479 let target = self.parse_graph_ref_all(input, pos)?;
480 Ok(UpdateOperation::Drop { target, silent })
481 }
482
483 fn parse_create(
486 &self,
487 input: &str,
488 pos: &mut usize,
489 ) -> Result<UpdateOperation, UpdateParseError> {
490 *pos = self.consume_keyword(input, *pos, "CREATE")?;
491 *pos = self.skip_ws(input, *pos);
492 let silent = self.try_consume_keyword(input, pos, "SILENT");
493 *pos = self.skip_ws(input, *pos);
494 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
495 *pos = self.skip_ws(input, *pos);
496 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
497 *pos = new_pos;
498 Ok(UpdateOperation::CreateGraph {
499 graph_iri: iri,
500 silent,
501 })
502 }
503
504 fn parse_copy(
507 &self,
508 input: &str,
509 pos: &mut usize,
510 ) -> Result<UpdateOperation, UpdateParseError> {
511 *pos = self.consume_keyword(input, *pos, "COPY")?;
512 *pos = self.skip_ws(input, *pos);
513 let silent = self.try_consume_keyword(input, pos, "SILENT");
514 *pos = self.skip_ws(input, *pos);
515 let source = self.parse_graph_or_default(input, pos)?;
516 *pos = self.skip_ws(input, *pos);
517 *pos = self.consume_keyword(input, *pos, "TO")?;
518 *pos = self.skip_ws(input, *pos);
519 let destination = self.parse_graph_or_default(input, pos)?;
520 Ok(UpdateOperation::Copy {
521 source,
522 destination,
523 silent,
524 })
525 }
526
527 fn parse_move(
528 &self,
529 input: &str,
530 pos: &mut usize,
531 ) -> Result<UpdateOperation, UpdateParseError> {
532 *pos = self.consume_keyword(input, *pos, "MOVE")?;
533 *pos = self.skip_ws(input, *pos);
534 let silent = self.try_consume_keyword(input, pos, "SILENT");
535 *pos = self.skip_ws(input, *pos);
536 let source = self.parse_graph_or_default(input, pos)?;
537 *pos = self.skip_ws(input, *pos);
538 *pos = self.consume_keyword(input, *pos, "TO")?;
539 *pos = self.skip_ws(input, *pos);
540 let destination = self.parse_graph_or_default(input, pos)?;
541 Ok(UpdateOperation::Move {
542 source,
543 destination,
544 silent,
545 })
546 }
547
548 fn parse_add(&self, input: &str, pos: &mut usize) -> Result<UpdateOperation, UpdateParseError> {
549 *pos = self.consume_keyword(input, *pos, "ADD")?;
550 *pos = self.skip_ws(input, *pos);
551 let silent = self.try_consume_keyword(input, pos, "SILENT");
552 *pos = self.skip_ws(input, *pos);
553 let source = self.parse_graph_or_default(input, pos)?;
554 *pos = self.skip_ws(input, *pos);
555 *pos = self.consume_keyword(input, *pos, "TO")?;
556 *pos = self.skip_ws(input, *pos);
557 let destination = self.parse_graph_or_default(input, pos)?;
558 Ok(UpdateOperation::Add {
559 source,
560 destination,
561 silent,
562 })
563 }
564
565 fn parse_graph_ref_all(
568 &self,
569 input: &str,
570 pos: &mut usize,
571 ) -> Result<GraphTarget, UpdateParseError> {
572 *pos = self.skip_ws(input, *pos);
573 if self.match_keyword(input, *pos, "ALL") {
574 *pos = self.consume_keyword(input, *pos, "ALL")?;
575 Ok(GraphTarget::All)
576 } else if self.match_keyword(input, *pos, "DEFAULT") {
577 *pos = self.consume_keyword(input, *pos, "DEFAULT")?;
578 Ok(GraphTarget::Default)
579 } else if self.match_keyword(input, *pos, "NAMED") {
580 *pos = self.consume_keyword(input, *pos, "NAMED")?;
581 Ok(GraphTarget::Named)
582 } else if self.match_keyword(input, *pos, "GRAPH") {
583 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
584 *pos = self.skip_ws(input, *pos);
585 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
586 *pos = new_pos;
587 Ok(GraphTarget::Graph(iri))
588 } else {
589 let (ln, col) = line_col(input, *pos);
590 Err(
591 UpdateParseError::new("expected GRAPH, DEFAULT, NAMED, or ALL", *pos)
592 .with_location(ln, col),
593 )
594 }
595 }
596
597 fn parse_graph_or_default(
598 &self,
599 input: &str,
600 pos: &mut usize,
601 ) -> Result<GraphTarget, UpdateParseError> {
602 *pos = self.skip_ws(input, *pos);
603 if self.match_keyword(input, *pos, "DEFAULT") {
604 *pos = self.consume_keyword(input, *pos, "DEFAULT")?;
605 Ok(GraphTarget::Default)
606 } else if self.match_keyword(input, *pos, "GRAPH") {
607 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
608 *pos = self.skip_ws(input, *pos);
609 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
610 *pos = new_pos;
611 Ok(GraphTarget::Graph(iri))
612 } else {
613 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'<') {
615 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
616 *pos = new_pos;
617 Ok(GraphTarget::Graph(iri))
618 } else {
619 let (ln, col) = line_col(input, *pos);
620 Err(
621 UpdateParseError::new("expected DEFAULT, GRAPH <iri>, or <iri>", *pos)
622 .with_location(ln, col),
623 )
624 }
625 }
626 }
627
628 fn parse_quad_data(
637 &self,
638 input: &str,
639 pos: &mut usize,
640 ) -> Result<(Option<String>, Vec<TriplePattern>), UpdateParseError> {
641 *pos = self.skip_ws(input, *pos);
642
643 if *pos >= input.len() || input.as_bytes().get(*pos) != Some(&b'{') {
645 let (ln, col) = line_col(input, *pos);
646 return Err(
647 UpdateParseError::new("expected '{' to open quad data block", *pos)
648 .with_location(ln, col),
649 );
650 }
651 *pos += 1; *pos = self.skip_ws(input, *pos);
654
655 if self.match_keyword(input, *pos, "GRAPH") {
657 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
658 *pos = self.skip_ws(input, *pos);
659 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
660 *pos = new_pos;
661 *pos = self.skip_ws(input, *pos);
662
663 let triples = self.parse_brace_block(input, pos)?;
665
666 *pos = self.skip_ws(input, *pos);
668 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
669 *pos += 1;
670 }
671
672 *pos = self.skip_ws(input, *pos);
674 if *pos >= input.len() || input.as_bytes().get(*pos) != Some(&b'}') {
675 let (ln, col) = line_col(input, *pos);
676 return Err(
677 UpdateParseError::new("expected '}' to close quad data block", *pos)
678 .with_location(ln, col),
679 );
680 }
681 *pos += 1;
682
683 Ok((Some(iri), triples))
684 } else {
685 let mut triples = Vec::new();
687 loop {
688 *pos = self.skip_ws(input, *pos);
689 if *pos >= input.len() {
690 let (ln, col) = line_col(input, *pos);
691 return Err(UpdateParseError::new(
692 "unexpected end of input, expected '}'",
693 *pos,
694 )
695 .with_location(ln, col));
696 }
697 if input.as_bytes().get(*pos) == Some(&b'}') {
698 *pos += 1;
699 break;
700 }
701
702 let triple = self.parse_triple_pattern(input, pos)?;
703 triples.push(triple);
704
705 *pos = self.skip_ws(input, *pos);
706 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
708 *pos += 1;
709 }
710 }
711
712 Ok((None, triples))
713 }
714 }
715
716 fn parse_brace_block(
718 &self,
719 input: &str,
720 pos: &mut usize,
721 ) -> Result<Vec<TriplePattern>, UpdateParseError> {
722 *pos = self.skip_ws(input, *pos);
723 if *pos >= input.len() || input.as_bytes().get(*pos) != Some(&b'{') {
724 let (ln, col) = line_col(input, *pos);
725 return Err(UpdateParseError::new("expected '{'", *pos).with_location(ln, col));
726 }
727 *pos += 1; let mut triples = Vec::new();
730 loop {
731 *pos = self.skip_ws(input, *pos);
732 if *pos >= input.len() {
733 let (ln, col) = line_col(input, *pos);
734 return Err(
735 UpdateParseError::new("unexpected end of input, expected '}'", *pos)
736 .with_location(ln, col),
737 );
738 }
739 if input.as_bytes().get(*pos) == Some(&b'}') {
740 *pos += 1;
741 break;
742 }
743
744 let triple = self.parse_triple_pattern(input, pos)?;
745 triples.push(triple);
746
747 *pos = self.skip_ws(input, *pos);
748 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
750 *pos += 1;
751 }
752 }
753
754 Ok(triples)
755 }
756
757 fn parse_triple_pattern(
759 &self,
760 input: &str,
761 pos: &mut usize,
762 ) -> Result<TriplePattern, UpdateParseError> {
763 *pos = self.skip_ws(input, *pos);
764 let subject = self.read_term(input, pos)?;
765 *pos = self.skip_ws(input, *pos);
766 let predicate = self.read_term(input, pos)?;
767 *pos = self.skip_ws(input, *pos);
768 let object = self.read_term(input, pos)?;
769 Ok(TriplePattern::new(subject, predicate, object))
770 }
771
772 fn read_term(&self, input: &str, pos: &mut usize) -> Result<String, UpdateParseError> {
776 *pos = self.skip_ws(input, *pos);
777 if *pos >= input.len() {
778 let (ln, col) = line_col(input, *pos);
779 return Err(
780 UpdateParseError::new("unexpected end of input while reading term", *pos)
781 .with_location(ln, col),
782 );
783 }
784
785 let ch = input.as_bytes()[*pos];
786
787 if ch == b'<' {
789 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
790 *pos = new_pos;
791 return Ok(format!("<{}>", iri));
792 }
793
794 if ch == b'?' || ch == b'$' {
796 let start = *pos;
797 *pos += 1; while *pos < input.len() && is_name_char(input.as_bytes()[*pos]) {
799 *pos += 1;
800 }
801 return Ok(input[start..*pos].to_string());
802 }
803
804 if ch == b'"' || ch == b'\'' {
806 return self.read_literal(input, pos);
807 }
808
809 if ch == b'_' && input.as_bytes().get(*pos + 1) == Some(&b':') {
811 let start = *pos;
812 *pos += 2;
813 while *pos < input.len() && is_name_char(input.as_bytes()[*pos]) {
814 *pos += 1;
815 }
816 return Ok(input[start..*pos].to_string());
817 }
818
819 if ch == b'a'
821 && (*pos + 1 >= input.len()
822 || !is_name_char(input.as_bytes().get(*pos + 1).copied().unwrap_or(b' ')))
823 {
824 *pos += 1;
825 return Ok("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>".to_string());
826 }
827
828 let start = *pos;
830 while *pos < input.len() && is_name_char(input.as_bytes()[*pos]) {
831 *pos += 1;
832 }
833 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b':') {
834 let prefix = &input[start..*pos];
835 *pos += 1; let local_start = *pos;
837 while *pos < input.len() && is_pname_local_char(input.as_bytes()[*pos]) {
838 *pos += 1;
839 }
840 let local = &input[local_start..*pos];
841
842 if let Some(ns) = self.prefixes.get(prefix) {
844 return Ok(format!("<{}{}>", ns, local));
845 }
846 return Ok(format!("{}:{}", prefix, local));
847 }
848
849 *pos = start; if ch.is_ascii_digit() || ch == b'+' || ch == b'-' {
852 return self.read_numeric_literal(input, pos);
853 }
854
855 if self.match_keyword(input, *pos, "true") {
857 *pos += 4;
858 return Ok("\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>".to_string());
859 }
860 if self.match_keyword(input, *pos, "false") {
861 *pos += 5;
862 return Ok("\"false\"^^<http://www.w3.org/2001/XMLSchema#boolean>".to_string());
863 }
864
865 let (ln, col) = line_col(input, *pos);
866 let snippet: String = input[*pos..].chars().take(20).collect();
867 Err(
868 UpdateParseError::new(format!("unexpected token: '{}'", snippet), *pos)
869 .with_location(ln, col),
870 )
871 }
872
873 fn read_literal(&self, input: &str, pos: &mut usize) -> Result<String, UpdateParseError> {
875 let quote = input.as_bytes()[*pos];
876 let start = *pos;
877 *pos += 1; let mut value = String::new();
879
880 while *pos < input.len() {
881 let ch = input.as_bytes()[*pos];
882 if ch == b'\\' && *pos + 1 < input.len() {
883 let esc = input.as_bytes()[*pos + 1];
884 let escaped = match esc {
885 b'n' => '\n',
886 b't' => '\t',
887 b'\\' => '\\',
888 b'"' => '"',
889 b'\'' => '\'',
890 _ => {
891 *pos += 2;
892 continue;
893 }
894 };
895 value.push(escaped);
896 *pos += 2;
897 } else if ch == quote {
898 *pos += 1; if *pos < input.len()
901 && input.as_bytes().get(*pos) == Some(&b'^')
902 && input.as_bytes().get(*pos + 1) == Some(&b'^')
903 {
904 *pos += 2;
905 if input.as_bytes().get(*pos) == Some(&b'<') {
906 let (dt, new_pos) = self.read_iri_ref(input, *pos)?;
907 *pos = new_pos;
908 return Ok(format!("\"{}\"^^<{}>", value, dt));
909 }
910 }
911 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'@') {
912 *pos += 1;
913 let lang_start = *pos;
914 while *pos < input.len()
915 && (input.as_bytes()[*pos].is_ascii_alphanumeric()
916 || input.as_bytes()[*pos] == b'-')
917 {
918 *pos += 1;
919 }
920 let lang = &input[lang_start..*pos];
921 return Ok(format!("\"{}\"@{}", value, lang));
922 }
923 return Ok(format!("\"{}\"", value));
924 } else {
925 value.push(ch as char);
926 *pos += 1;
927 }
928 }
929
930 let (ln, col) = line_col(input, start);
931 Err(UpdateParseError::new("unterminated string literal", start).with_location(ln, col))
932 }
933
934 fn read_numeric_literal(
936 &self,
937 input: &str,
938 pos: &mut usize,
939 ) -> Result<String, UpdateParseError> {
940 let start = *pos;
941 if *pos < input.len() && (input.as_bytes()[*pos] == b'+' || input.as_bytes()[*pos] == b'-')
943 {
944 *pos += 1;
945 }
946 while *pos < input.len() && input.as_bytes()[*pos].is_ascii_digit() {
948 *pos += 1;
949 }
950 let mut is_decimal = false;
952 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
953 is_decimal = true;
954 *pos += 1;
955 while *pos < input.len() && input.as_bytes()[*pos].is_ascii_digit() {
956 *pos += 1;
957 }
958 }
959 if *pos < input.len() && (input.as_bytes()[*pos] == b'e' || input.as_bytes()[*pos] == b'E')
961 {
962 is_decimal = true;
963 *pos += 1;
964 if *pos < input.len()
965 && (input.as_bytes()[*pos] == b'+' || input.as_bytes()[*pos] == b'-')
966 {
967 *pos += 1;
968 }
969 while *pos < input.len() && input.as_bytes()[*pos].is_ascii_digit() {
970 *pos += 1;
971 }
972 }
973
974 if *pos == start {
975 let (ln, col) = line_col(input, *pos);
976 return Err(
977 UpdateParseError::new("expected numeric literal", *pos).with_location(ln, col)
978 );
979 }
980
981 let text = &input[start..*pos];
982 if is_decimal {
983 Ok(format!(
984 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#double>",
985 text
986 ))
987 } else {
988 Ok(format!(
989 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
990 text
991 ))
992 }
993 }
994
995 fn read_iri_ref(&self, input: &str, pos: usize) -> Result<(String, usize), UpdateParseError> {
997 if pos >= input.len() || input.as_bytes().get(pos) != Some(&b'<') {
998 let (ln, col) = line_col(input, pos);
999 return Err(
1000 UpdateParseError::new("expected '<' to start IRI reference", pos)
1001 .with_location(ln, col),
1002 );
1003 }
1004 let start = pos + 1;
1005 let mut end = start;
1006 while end < input.len() && input.as_bytes()[end] != b'>' {
1007 end += 1;
1008 }
1009 if end >= input.len() {
1010 let (ln, col) = line_col(input, pos);
1011 return Err(
1012 UpdateParseError::new("unterminated IRI reference", pos).with_location(ln, col)
1013 );
1014 }
1015 let iri = input[start..end].to_string();
1016 Ok((iri, end + 1))
1017 }
1018
1019 fn read_prefix_label(
1021 &self,
1022 input: &str,
1023 pos: usize,
1024 ) -> Result<(String, usize), UpdateParseError> {
1025 let start = pos;
1026 let mut p = pos;
1027 while p < input.len() && input.as_bytes()[p] != b':' {
1028 p += 1;
1029 }
1030 if p >= input.len() {
1031 let (ln, col) = line_col(input, pos);
1032 return Err(
1033 UpdateParseError::new("expected ':' in prefix declaration", pos)
1034 .with_location(ln, col),
1035 );
1036 }
1037 let prefix = input[start..p].trim().to_string();
1038 Ok((prefix, p + 1)) }
1040
1041 fn skip_ws(&self, input: &str, mut pos: usize) -> usize {
1044 let bytes = input.as_bytes();
1045 while pos < bytes.len() {
1046 if bytes[pos].is_ascii_whitespace() {
1047 pos += 1;
1048 } else if bytes[pos] == b'#' {
1049 while pos < bytes.len() && bytes[pos] != b'\n' {
1051 pos += 1;
1052 }
1053 } else {
1054 break;
1055 }
1056 }
1057 pos
1058 }
1059
1060 fn match_keyword(&self, input: &str, pos: usize, kw: &str) -> bool {
1061 let end = pos + kw.len();
1062 if end > input.len() {
1063 return false;
1064 }
1065 if !input[pos..end].eq_ignore_ascii_case(kw) {
1066 return false;
1067 }
1068 end >= input.len() || !is_name_char(input.as_bytes()[end])
1070 }
1071
1072 fn consume_keyword(
1073 &self,
1074 input: &str,
1075 pos: usize,
1076 kw: &str,
1077 ) -> Result<usize, UpdateParseError> {
1078 if !self.match_keyword(input, pos, kw) {
1079 let (ln, col) = line_col(input, pos);
1080 let snippet: String = input[pos..].chars().take(20).collect();
1081 return Err(UpdateParseError::new(
1082 format!("expected keyword '{}', found: '{}'", kw, snippet),
1083 pos,
1084 )
1085 .with_location(ln, col));
1086 }
1087 Ok(pos + kw.len())
1088 }
1089
1090 fn try_consume_keyword(&self, input: &str, pos: &mut usize, kw: &str) -> bool {
1092 if self.match_keyword(input, *pos, kw) {
1093 *pos += kw.len();
1094 true
1095 } else {
1096 false
1097 }
1098 }
1099}
1100
1101fn is_name_char(b: u8) -> bool {
1104 b.is_ascii_alphanumeric() || b == b'_' || b == b'-'
1105}
1106
1107fn is_pname_local_char(b: u8) -> bool {
1108 b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.'
1109}
1110
1111pub fn parse_update(input: &str) -> Result<UpdateRequest, UpdateParseError> {
1117 let mut parser = UpdateParser::new();
1118 parser.parse(input)
1119}
1120
1121pub fn parse_update_with_prefixes(
1123 input: &str,
1124 prefixes: HashMap<String, String>,
1125) -> Result<UpdateRequest, UpdateParseError> {
1126 let mut parser = UpdateParser::with_prefixes(prefixes);
1127 parser.parse(input)
1128}
1129
1130#[cfg(test)]
1135mod tests {
1136 use super::*;
1137
1138 #[test]
1141 fn test_insert_data_single_triple() {
1142 let input = r#"INSERT DATA { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o> }"#;
1143 let req = parse_update(input).expect("should parse");
1144 assert_eq!(req.operations.len(), 1);
1145 match &req.operations[0] {
1146 UpdateOperation::InsertData { triples, graph } => {
1147 assert_eq!(triples.len(), 1);
1148 assert_eq!(triples[0].subject, "<http://ex.org/s>");
1149 assert_eq!(triples[0].predicate, "<http://ex.org/p>");
1150 assert_eq!(triples[0].object, "<http://ex.org/o>");
1151 assert!(graph.is_none());
1152 }
1153 other => panic!("expected InsertData, got {:?}", other),
1154 }
1155 }
1156
1157 #[test]
1158 fn test_insert_data_multiple_triples() {
1159 let input = r#"INSERT DATA {
1160 <http://ex.org/s1> <http://ex.org/p1> <http://ex.org/o1> .
1161 <http://ex.org/s2> <http://ex.org/p2> "hello"
1162 }"#;
1163 let req = parse_update(input).expect("should parse");
1164 assert_eq!(req.operations.len(), 1);
1165 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1166 assert_eq!(triples.len(), 2);
1167 assert_eq!(triples[1].object, "\"hello\"");
1168 }
1169 }
1170
1171 #[test]
1172 fn test_insert_data_with_graph() {
1173 let input = r#"INSERT DATA { GRAPH <http://ex.org/g> { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o> } }"#;
1174 let req = parse_update(input).expect("should parse");
1175 if let UpdateOperation::InsertData { graph, triples } = &req.operations[0] {
1176 assert_eq!(graph.as_deref(), Some("http://ex.org/g"));
1177 assert_eq!(triples.len(), 1);
1178 }
1179 }
1180
1181 #[test]
1182 fn test_insert_data_with_prefix() {
1183 let input = r#"PREFIX ex: <http://ex.org/>
1184 INSERT DATA { ex:s ex:p ex:o }"#;
1185 let req = parse_update(input).expect("should parse");
1186 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1187 assert_eq!(triples[0].subject, "<http://ex.org/s>");
1188 assert_eq!(triples[0].predicate, "<http://ex.org/p>");
1189 assert_eq!(triples[0].object, "<http://ex.org/o>");
1190 }
1191 }
1192
1193 #[test]
1194 fn test_insert_data_with_literal_datatype() {
1195 let input = r#"INSERT DATA { <http://ex.org/s> <http://ex.org/p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> }"#;
1196 let req = parse_update(input).expect("should parse");
1197 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1198 assert!(triples[0].object.contains("integer"));
1199 }
1200 }
1201
1202 #[test]
1203 fn test_insert_data_with_lang_tag() {
1204 let input = r#"INSERT DATA { <http://ex.org/s> <http://ex.org/p> "bonjour"@fr }"#;
1205 let req = parse_update(input).expect("should parse");
1206 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1207 assert_eq!(triples[0].object, "\"bonjour\"@fr");
1208 }
1209 }
1210
1211 #[test]
1214 fn test_delete_data_single_triple() {
1215 let input = r#"DELETE DATA { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o> }"#;
1216 let req = parse_update(input).expect("should parse");
1217 assert_eq!(req.operations.len(), 1);
1218 match &req.operations[0] {
1219 UpdateOperation::DeleteData { triples, graph } => {
1220 assert_eq!(triples.len(), 1);
1221 assert!(graph.is_none());
1222 }
1223 other => panic!("expected DeleteData, got {:?}", other),
1224 }
1225 }
1226
1227 #[test]
1228 fn test_delete_data_with_graph() {
1229 let input = r#"DELETE DATA { GRAPH <http://ex.org/g> { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o> } }"#;
1230 let req = parse_update(input).expect("should parse");
1231 if let UpdateOperation::DeleteData { graph, .. } = &req.operations[0] {
1232 assert_eq!(graph.as_deref(), Some("http://ex.org/g"));
1233 }
1234 }
1235
1236 #[test]
1237 fn test_delete_data_multiple_triples() {
1238 let input = r#"DELETE DATA {
1239 <http://ex.org/s1> <http://ex.org/p1> <http://ex.org/o1> .
1240 <http://ex.org/s2> <http://ex.org/p2> <http://ex.org/o2>
1241 }"#;
1242 let req = parse_update(input).expect("should parse");
1243 if let UpdateOperation::DeleteData { triples, .. } = &req.operations[0] {
1244 assert_eq!(triples.len(), 2);
1245 }
1246 }
1247
1248 #[test]
1251 fn test_delete_insert_where() {
1252 let input = r#"DELETE { ?s <http://ex.org/old> ?o }
1253 INSERT { ?s <http://ex.org/new> ?o }
1254 WHERE { ?s <http://ex.org/old> ?o }"#;
1255 let req = parse_update(input).expect("should parse");
1256 match &req.operations[0] {
1257 UpdateOperation::DeleteInsertWhere {
1258 delete_triples,
1259 insert_triples,
1260 where_triples,
1261 ..
1262 } => {
1263 assert_eq!(delete_triples.len(), 1);
1264 assert_eq!(insert_triples.len(), 1);
1265 assert_eq!(where_triples.len(), 1);
1266 assert_eq!(delete_triples[0].predicate, "<http://ex.org/old>");
1267 assert_eq!(insert_triples[0].predicate, "<http://ex.org/new>");
1268 }
1269 other => panic!("expected DeleteInsertWhere, got {:?}", other),
1270 }
1271 }
1272
1273 #[test]
1274 fn test_delete_where_only() {
1275 let input = r#"DELETE { ?s <http://ex.org/p> ?o }
1276 WHERE { ?s <http://ex.org/p> ?o }"#;
1277 let req = parse_update(input).expect("should parse");
1278 if let UpdateOperation::DeleteInsertWhere { insert_triples, .. } = &req.operations[0] {
1279 assert!(insert_triples.is_empty());
1280 }
1281 }
1282
1283 #[test]
1284 fn test_delete_insert_where_with_variables() {
1285 let input = r#"DELETE { ?person <http://ex.org/age> ?old }
1286 INSERT { ?person <http://ex.org/age> ?new }
1287 WHERE { ?person <http://ex.org/age> ?old }"#;
1288 let req = parse_update(input).expect("should parse");
1289 if let UpdateOperation::DeleteInsertWhere {
1290 delete_triples,
1291 insert_triples,
1292 ..
1293 } = &req.operations[0]
1294 {
1295 assert_eq!(delete_triples[0].subject, "?person");
1296 assert_eq!(insert_triples[0].object, "?new");
1297 }
1298 }
1299
1300 #[test]
1303 fn test_load_basic() {
1304 let input = r#"LOAD <http://example.org/data.ttl>"#;
1305 let req = parse_update(input).expect("should parse");
1306 match &req.operations[0] {
1307 UpdateOperation::Load {
1308 source_uri,
1309 target_graph,
1310 silent,
1311 } => {
1312 assert_eq!(source_uri, "http://example.org/data.ttl");
1313 assert!(target_graph.is_none());
1314 assert!(!silent);
1315 }
1316 other => panic!("expected Load, got {:?}", other),
1317 }
1318 }
1319
1320 #[test]
1321 fn test_load_into_graph() {
1322 let input = r#"LOAD <http://example.org/data.ttl> INTO GRAPH <http://example.org/g>"#;
1323 let req = parse_update(input).expect("should parse");
1324 if let UpdateOperation::Load { target_graph, .. } = &req.operations[0] {
1325 assert_eq!(target_graph.as_deref(), Some("http://example.org/g"));
1326 }
1327 }
1328
1329 #[test]
1330 fn test_load_silent() {
1331 let input = r#"LOAD SILENT <http://example.org/data.ttl>"#;
1332 let req = parse_update(input).expect("should parse");
1333 if let UpdateOperation::Load { silent, .. } = &req.operations[0] {
1334 assert!(*silent);
1335 }
1336 }
1337
1338 #[test]
1339 fn test_load_silent_into_graph() {
1340 let input =
1341 r#"LOAD SILENT <http://example.org/data.ttl> INTO GRAPH <http://example.org/g>"#;
1342 let req = parse_update(input).expect("should parse");
1343 if let UpdateOperation::Load {
1344 silent,
1345 target_graph,
1346 ..
1347 } = &req.operations[0]
1348 {
1349 assert!(*silent);
1350 assert_eq!(target_graph.as_deref(), Some("http://example.org/g"));
1351 }
1352 }
1353
1354 #[test]
1357 fn test_clear_all() {
1358 let input = "CLEAR ALL";
1359 let req = parse_update(input).expect("should parse");
1360 match &req.operations[0] {
1361 UpdateOperation::Clear { target, silent } => {
1362 assert_eq!(*target, GraphTarget::All);
1363 assert!(!silent);
1364 }
1365 other => panic!("expected Clear, got {:?}", other),
1366 }
1367 }
1368
1369 #[test]
1370 fn test_clear_default() {
1371 let input = "CLEAR DEFAULT";
1372 let req = parse_update(input).expect("should parse");
1373 if let UpdateOperation::Clear { target, .. } = &req.operations[0] {
1374 assert_eq!(*target, GraphTarget::Default);
1375 }
1376 }
1377
1378 #[test]
1379 fn test_clear_named() {
1380 let input = "CLEAR NAMED";
1381 let req = parse_update(input).expect("should parse");
1382 if let UpdateOperation::Clear { target, .. } = &req.operations[0] {
1383 assert_eq!(*target, GraphTarget::Named);
1384 }
1385 }
1386
1387 #[test]
1388 fn test_clear_graph() {
1389 let input = "CLEAR GRAPH <http://example.org/g>";
1390 let req = parse_update(input).expect("should parse");
1391 if let UpdateOperation::Clear { target, .. } = &req.operations[0] {
1392 assert_eq!(
1393 *target,
1394 GraphTarget::Graph("http://example.org/g".to_string())
1395 );
1396 }
1397 }
1398
1399 #[test]
1400 fn test_clear_silent() {
1401 let input = "CLEAR SILENT ALL";
1402 let req = parse_update(input).expect("should parse");
1403 if let UpdateOperation::Clear { silent, .. } = &req.operations[0] {
1404 assert!(*silent);
1405 }
1406 }
1407
1408 #[test]
1411 fn test_drop_all() {
1412 let input = "DROP ALL";
1413 let req = parse_update(input).expect("should parse");
1414 match &req.operations[0] {
1415 UpdateOperation::Drop { target, silent } => {
1416 assert_eq!(*target, GraphTarget::All);
1417 assert!(!silent);
1418 }
1419 other => panic!("expected Drop, got {:?}", other),
1420 }
1421 }
1422
1423 #[test]
1424 fn test_drop_graph() {
1425 let input = "DROP GRAPH <http://ex.org/g>";
1426 let req = parse_update(input).expect("should parse");
1427 if let UpdateOperation::Drop { target, .. } = &req.operations[0] {
1428 assert_eq!(*target, GraphTarget::Graph("http://ex.org/g".to_string()));
1429 }
1430 }
1431
1432 #[test]
1433 fn test_drop_silent() {
1434 let input = "DROP SILENT DEFAULT";
1435 let req = parse_update(input).expect("should parse");
1436 if let UpdateOperation::Drop { target, silent } = &req.operations[0] {
1437 assert_eq!(*target, GraphTarget::Default);
1438 assert!(*silent);
1439 }
1440 }
1441
1442 #[test]
1445 fn test_create_graph() {
1446 let input = "CREATE GRAPH <http://example.org/new-graph>";
1447 let req = parse_update(input).expect("should parse");
1448 match &req.operations[0] {
1449 UpdateOperation::CreateGraph { graph_iri, silent } => {
1450 assert_eq!(graph_iri, "http://example.org/new-graph");
1451 assert!(!silent);
1452 }
1453 other => panic!("expected CreateGraph, got {:?}", other),
1454 }
1455 }
1456
1457 #[test]
1458 fn test_create_graph_silent() {
1459 let input = "CREATE SILENT GRAPH <http://example.org/g>";
1460 let req = parse_update(input).expect("should parse");
1461 if let UpdateOperation::CreateGraph { silent, .. } = &req.operations[0] {
1462 assert!(*silent);
1463 }
1464 }
1465
1466 #[test]
1469 fn test_copy_default_to_graph() {
1470 let input = "COPY DEFAULT TO GRAPH <http://ex.org/backup>";
1471 let req = parse_update(input).expect("should parse");
1472 match &req.operations[0] {
1473 UpdateOperation::Copy {
1474 source,
1475 destination,
1476 silent,
1477 } => {
1478 assert_eq!(*source, GraphTarget::Default);
1479 assert_eq!(
1480 *destination,
1481 GraphTarget::Graph("http://ex.org/backup".to_string())
1482 );
1483 assert!(!silent);
1484 }
1485 other => panic!("expected Copy, got {:?}", other),
1486 }
1487 }
1488
1489 #[test]
1490 fn test_copy_graph_to_default() {
1491 let input = "COPY GRAPH <http://ex.org/src> TO DEFAULT";
1492 let req = parse_update(input).expect("should parse");
1493 if let UpdateOperation::Copy {
1494 source,
1495 destination,
1496 ..
1497 } = &req.operations[0]
1498 {
1499 assert_eq!(*source, GraphTarget::Graph("http://ex.org/src".to_string()));
1500 assert_eq!(*destination, GraphTarget::Default);
1501 }
1502 }
1503
1504 #[test]
1505 fn test_copy_silent() {
1506 let input = "COPY SILENT DEFAULT TO GRAPH <http://ex.org/dst>";
1507 let req = parse_update(input).expect("should parse");
1508 if let UpdateOperation::Copy { silent, .. } = &req.operations[0] {
1509 assert!(*silent);
1510 }
1511 }
1512
1513 #[test]
1516 fn test_move_graph_to_graph() {
1517 let input = "MOVE GRAPH <http://ex.org/a> TO GRAPH <http://ex.org/b>";
1518 let req = parse_update(input).expect("should parse");
1519 match &req.operations[0] {
1520 UpdateOperation::Move {
1521 source,
1522 destination,
1523 silent,
1524 } => {
1525 assert_eq!(*source, GraphTarget::Graph("http://ex.org/a".to_string()));
1526 assert_eq!(
1527 *destination,
1528 GraphTarget::Graph("http://ex.org/b".to_string())
1529 );
1530 assert!(!silent);
1531 }
1532 other => panic!("expected Move, got {:?}", other),
1533 }
1534 }
1535
1536 #[test]
1537 fn test_move_silent() {
1538 let input = "MOVE SILENT GRAPH <http://ex.org/a> TO DEFAULT";
1539 let req = parse_update(input).expect("should parse");
1540 if let UpdateOperation::Move { silent, .. } = &req.operations[0] {
1541 assert!(*silent);
1542 }
1543 }
1544
1545 #[test]
1548 fn test_add_default_to_graph() {
1549 let input = "ADD DEFAULT TO GRAPH <http://ex.org/combined>";
1550 let req = parse_update(input).expect("should parse");
1551 match &req.operations[0] {
1552 UpdateOperation::Add {
1553 source,
1554 destination,
1555 silent,
1556 } => {
1557 assert_eq!(*source, GraphTarget::Default);
1558 assert_eq!(
1559 *destination,
1560 GraphTarget::Graph("http://ex.org/combined".to_string())
1561 );
1562 assert!(!silent);
1563 }
1564 other => panic!("expected Add, got {:?}", other),
1565 }
1566 }
1567
1568 #[test]
1569 fn test_add_silent() {
1570 let input = "ADD SILENT GRAPH <http://ex.org/src> TO DEFAULT";
1571 let req = parse_update(input).expect("should parse");
1572 if let UpdateOperation::Add { silent, .. } = &req.operations[0] {
1573 assert!(*silent);
1574 }
1575 }
1576
1577 #[test]
1580 fn test_multiple_operations_semicolon_separated() {
1581 let input = r#"INSERT DATA { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o> } ;
1582 CLEAR ALL"#;
1583 let req = parse_update(input).expect("should parse");
1584 assert_eq!(req.operations.len(), 2);
1585 assert_eq!(req.operations[0].kind_label(), "INSERT DATA");
1586 assert_eq!(req.operations[1].kind_label(), "CLEAR");
1587 }
1588
1589 #[test]
1590 fn test_three_operations() {
1591 let input = r#"
1592 CREATE GRAPH <http://ex.org/g> ;
1593 LOAD <http://ex.org/data.ttl> INTO GRAPH <http://ex.org/g> ;
1594 DROP GRAPH <http://ex.org/old>
1595 "#;
1596 let req = parse_update(input).expect("should parse");
1597 assert_eq!(req.operations.len(), 3);
1598 assert_eq!(req.operations[0].kind_label(), "CREATE GRAPH");
1599 assert_eq!(req.operations[1].kind_label(), "LOAD");
1600 assert_eq!(req.operations[2].kind_label(), "DROP");
1601 }
1602
1603 #[test]
1606 fn test_multiple_prefixes() {
1607 let input = r#"
1608 PREFIX ex: <http://example.org/>
1609 PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1610 INSERT DATA { ex:alice foaf:name "Alice" }
1611 "#;
1612 let req = parse_update(input).expect("should parse");
1613 assert_eq!(req.prefixes.len(), 2);
1614 assert_eq!(
1615 req.prefixes.get("ex"),
1616 Some(&"http://example.org/".to_string())
1617 );
1618 assert_eq!(
1619 req.prefixes.get("foaf"),
1620 Some(&"http://xmlns.com/foaf/0.1/".to_string())
1621 );
1622 }
1623
1624 #[test]
1625 fn test_prefix_expansion_in_triples() {
1626 let input = r#"
1627 PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1628 INSERT DATA { <http://ex.org/alice> foaf:knows <http://ex.org/bob> }
1629 "#;
1630 let req = parse_update(input).expect("should parse");
1631 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1632 assert_eq!(triples[0].predicate, "<http://xmlns.com/foaf/0.1/knows>");
1633 }
1634 }
1635
1636 #[test]
1639 fn test_error_empty_input() {
1640 let result = parse_update("");
1641 assert!(result.is_err());
1642 let err = result.expect_err("should be error");
1643 assert!(err.message.contains("empty"));
1644 }
1645
1646 #[test]
1647 fn test_error_unknown_keyword() {
1648 let result = parse_update("FROBNICATE ALL");
1649 assert!(result.is_err());
1650 let err = result.expect_err("should be error");
1651 assert!(err.message.contains("expected update keyword"));
1652 assert!(err.position == 0);
1653 }
1654
1655 #[test]
1656 fn test_error_missing_brace() {
1657 let result =
1658 parse_update("INSERT DATA <http://ex.org/s> <http://ex.org/p> <http://ex.org/o>");
1659 assert!(result.is_err());
1660 }
1661
1662 #[test]
1663 fn test_error_unterminated_brace() {
1664 let result =
1665 parse_update("INSERT DATA { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o>");
1666 assert!(result.is_err());
1667 let err = result.expect_err("should be error");
1668 assert!(err.message.contains("'}'"));
1669 }
1670
1671 #[test]
1672 fn test_error_position_tracking() {
1673 let result = parse_update("CLEAR BADTARGET");
1674 assert!(result.is_err());
1675 let err = result.expect_err("should be error");
1676 assert!(err.position > 0);
1677 assert!(err.line.is_some());
1678 assert!(err.column.is_some());
1679 }
1680
1681 #[test]
1682 fn test_error_unterminated_iri() {
1683 let result = parse_update("LOAD <http://example.org/unterminated");
1684 assert!(result.is_err());
1685 let err = result.expect_err("should be error");
1686 assert!(err.message.contains("unterminated"));
1687 }
1688
1689 #[test]
1692 fn test_rdf_type_shorthand() {
1693 let input = r#"INSERT DATA { <http://ex.org/alice> a <http://ex.org/Person> }"#;
1694 let req = parse_update(input).expect("should parse");
1695 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1696 assert_eq!(
1697 triples[0].predicate,
1698 "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
1699 );
1700 }
1701 }
1702
1703 #[test]
1704 fn test_blank_node() {
1705 let input = r#"INSERT DATA { _:b1 <http://ex.org/p> <http://ex.org/o> }"#;
1706 let req = parse_update(input).expect("should parse");
1707 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1708 assert_eq!(triples[0].subject, "_:b1");
1709 }
1710 }
1711
1712 #[test]
1713 fn test_numeric_literal_integer() {
1714 let input = r#"INSERT DATA { <http://ex.org/s> <http://ex.org/age> 42 }"#;
1715 let req = parse_update(input).expect("should parse");
1716 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1717 assert!(triples[0].object.contains("42"));
1718 assert!(triples[0].object.contains("integer"));
1719 }
1720 }
1721
1722 #[test]
1723 fn test_numeric_literal_decimal() {
1724 let input = r#"INSERT DATA { <http://ex.org/s> <http://ex.org/weight> 3.14 }"#;
1725 let req = parse_update(input).expect("should parse");
1726 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1727 assert!(triples[0].object.contains("3.14"));
1728 }
1729 }
1730
1731 #[test]
1734 fn test_graph_target_display() {
1735 assert_eq!(GraphTarget::Default.to_string(), "DEFAULT");
1736 assert_eq!(GraphTarget::Named.to_string(), "NAMED");
1737 assert_eq!(GraphTarget::All.to_string(), "ALL");
1738 assert_eq!(
1739 GraphTarget::Graph("http://ex.org/g".to_string()).to_string(),
1740 "GRAPH <http://ex.org/g>"
1741 );
1742 }
1743
1744 #[test]
1747 fn test_error_display_with_location() {
1748 let err = UpdateParseError::new("bad token", 10).with_location(2, 5);
1749 let msg = err.to_string();
1750 assert!(msg.contains("2:5"));
1751 assert!(msg.contains("bad token"));
1752 }
1753
1754 #[test]
1755 fn test_error_display_without_location() {
1756 let err = UpdateParseError::new("bad token", 10);
1757 let msg = err.to_string();
1758 assert!(msg.contains("byte 10"));
1759 assert!(msg.contains("bad token"));
1760 }
1761
1762 #[test]
1765 fn test_kind_labels() {
1766 assert_eq!(
1767 UpdateOperation::InsertData {
1768 triples: vec![],
1769 graph: None
1770 }
1771 .kind_label(),
1772 "INSERT DATA"
1773 );
1774 assert_eq!(
1775 UpdateOperation::DeleteData {
1776 triples: vec![],
1777 graph: None
1778 }
1779 .kind_label(),
1780 "DELETE DATA"
1781 );
1782 assert_eq!(
1783 UpdateOperation::Load {
1784 source_uri: String::new(),
1785 target_graph: None,
1786 silent: false
1787 }
1788 .kind_label(),
1789 "LOAD"
1790 );
1791 assert_eq!(
1792 UpdateOperation::Clear {
1793 target: GraphTarget::All,
1794 silent: false
1795 }
1796 .kind_label(),
1797 "CLEAR"
1798 );
1799 assert_eq!(
1800 UpdateOperation::Drop {
1801 target: GraphTarget::All,
1802 silent: false
1803 }
1804 .kind_label(),
1805 "DROP"
1806 );
1807 assert_eq!(
1808 UpdateOperation::CreateGraph {
1809 graph_iri: String::new(),
1810 silent: false
1811 }
1812 .kind_label(),
1813 "CREATE GRAPH"
1814 );
1815 assert_eq!(
1816 UpdateOperation::Copy {
1817 source: GraphTarget::Default,
1818 destination: GraphTarget::Default,
1819 silent: false
1820 }
1821 .kind_label(),
1822 "COPY"
1823 );
1824 assert_eq!(
1825 UpdateOperation::Move {
1826 source: GraphTarget::Default,
1827 destination: GraphTarget::Default,
1828 silent: false
1829 }
1830 .kind_label(),
1831 "MOVE"
1832 );
1833 assert_eq!(
1834 UpdateOperation::Add {
1835 source: GraphTarget::Default,
1836 destination: GraphTarget::Default,
1837 silent: false
1838 }
1839 .kind_label(),
1840 "ADD"
1841 );
1842 }
1843
1844 #[test]
1847 fn test_comments_are_skipped() {
1848 let input = r#"
1849 # This is a comment
1850 INSERT DATA {
1851 # Another comment
1852 <http://ex.org/s> <http://ex.org/p> <http://ex.org/o>
1853 }
1854 "#;
1855 let req = parse_update(input).expect("should parse");
1856 assert_eq!(req.operations.len(), 1);
1857 }
1858
1859 #[test]
1862 fn test_with_prefixes_constructor() {
1863 let mut prefixes = HashMap::new();
1864 prefixes.insert("ex".to_string(), "http://example.org/".to_string());
1865
1866 let input = "INSERT DATA { ex:s ex:p ex:o }";
1867 let result = parse_update_with_prefixes(input, prefixes);
1868 let req = result.expect("should parse");
1869 if let UpdateOperation::InsertData { triples, .. } = &req.operations[0] {
1870 assert_eq!(triples[0].subject, "<http://example.org/s>");
1871 }
1872 }
1873
1874 #[test]
1877 fn test_triple_pattern_new() {
1878 let tp = TriplePattern::new("s", "p", "o");
1879 assert_eq!(tp.subject, "s");
1880 assert_eq!(tp.predicate, "p");
1881 assert_eq!(tp.object, "o");
1882 }
1883
1884 #[test]
1887 fn test_keywords_case_insensitive() {
1888 let input = "clear all";
1889 let req = parse_update(input).expect("should parse");
1890 assert_eq!(req.operations[0].kind_label(), "CLEAR");
1891 }
1892
1893 #[test]
1894 fn test_mixed_case_keywords() {
1895 let input = "Insert Data { <http://ex.org/s> <http://ex.org/p> <http://ex.org/o> }";
1896 let req = parse_update(input).expect("should parse");
1897 assert_eq!(req.operations[0].kind_label(), "INSERT DATA");
1898 }
1899
1900 #[test]
1903 fn test_line_col_first_line() {
1904 let (ln, col) = line_col("hello world", 6);
1905 assert_eq!(ln, 1);
1906 assert_eq!(col, 7);
1907 }
1908
1909 #[test]
1910 fn test_line_col_second_line() {
1911 let (ln, col) = line_col("hello\nworld", 6);
1912 assert_eq!(ln, 2);
1913 assert_eq!(col, 1);
1914 }
1915
1916 #[test]
1917 fn test_line_col_empty() {
1918 let (ln, col) = line_col("", 0);
1919 assert_eq!(ln, 1);
1920 assert_eq!(col, 1);
1921 }
1922
1923 #[test]
1926 fn test_parser_default_trait() {
1927 let parser = UpdateParser::default();
1928 assert!(parser.prefixes.is_empty());
1929 }
1930}