1use crate::ast::{Document, Field, OperationDefinition, Selection, SelectionSet, Value};
4use crate::optimizer::QueryOptimizer;
5use crate::schema::{PropertyType, RdfVocabulary};
6use anyhow::{anyhow, Result};
7use std::collections::HashMap;
8use std::fmt::Write;
9use std::sync::Arc;
10
11#[derive(Debug, Clone)]
13pub struct TranslationConfig {
14 pub default_limit: usize,
15 pub max_limit: usize,
16 pub enable_aggregation: bool,
17 pub enable_full_text_search: bool,
18 pub optimize_queries: bool,
19 pub enable_nested_filtering: bool,
20 pub enable_unions: bool,
21 pub enable_sorting: bool,
22 pub max_query_depth: usize,
23}
24
25impl Default for TranslationConfig {
26 fn default() -> Self {
27 Self {
28 default_limit: 100,
29 max_limit: 10000,
30 enable_aggregation: true,
31 enable_full_text_search: false,
32 optimize_queries: true,
33 enable_nested_filtering: true,
34 enable_unions: true,
35 enable_sorting: true,
36 max_query_depth: 10,
37 }
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct QueryContext {
44 pub variables: HashMap<String, Value>,
45 pub operation_name: Option<String>,
46 pub depth: usize,
47 pub fragments: HashMap<String, crate::ast::FragmentDefinition>,
48}
49
50impl QueryContext {
51 pub fn new() -> Self {
52 Self {
53 variables: HashMap::new(),
54 operation_name: None,
55 depth: 0,
56 fragments: HashMap::new(),
57 }
58 }
59}
60
61pub struct RdfGraphQLMapper {
63 namespace_prefixes: HashMap<String, String>,
64 vocabulary: Option<RdfVocabulary>,
65 config: TranslationConfig,
66 optimizer: Option<Arc<QueryOptimizer>>,
67 variable_counter: std::cell::RefCell<usize>,
68}
69
70impl RdfGraphQLMapper {
71 pub fn new() -> Self {
72 let mut namespace_prefixes = HashMap::new();
73
74 namespace_prefixes.insert(
76 "rdf".to_string(),
77 "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
78 );
79 namespace_prefixes.insert(
80 "rdfs".to_string(),
81 "http://www.w3.org/2000/01/rdf-schema#".to_string(),
82 );
83 namespace_prefixes.insert(
84 "owl".to_string(),
85 "http://www.w3.org/2002/07/owl#".to_string(),
86 );
87 namespace_prefixes.insert(
88 "xsd".to_string(),
89 "http://www.w3.org/2001/XMLSchema#".to_string(),
90 );
91 namespace_prefixes.insert("foaf".to_string(), "http://xmlns.com/foaf/0.1/".to_string());
92 namespace_prefixes.insert("schema".to_string(), "http://schema.org/".to_string());
93 namespace_prefixes.insert(
94 "dc".to_string(),
95 "http://purl.org/dc/elements/1.1/".to_string(),
96 );
97 namespace_prefixes.insert(
98 "dbo".to_string(),
99 "http://dbpedia.org/ontology/".to_string(),
100 );
101 namespace_prefixes.insert(
102 "geo".to_string(),
103 "http://www.w3.org/2003/01/geo/wgs84_pos#".to_string(),
104 );
105 namespace_prefixes.insert(
106 "skos".to_string(),
107 "http://www.w3.org/2004/02/skos/core#".to_string(),
108 );
109
110 Self {
111 namespace_prefixes,
112 vocabulary: None,
113 config: TranslationConfig::default(),
114 optimizer: None,
115 variable_counter: std::cell::RefCell::new(0),
116 }
117 }
118
119 pub fn with_vocabulary(mut self, vocabulary: RdfVocabulary) -> Self {
120 self.vocabulary = Some(vocabulary);
121 self
122 }
123
124 pub fn with_config(mut self, config: TranslationConfig) -> Self {
125 self.config = config;
126 self
127 }
128
129 pub fn with_optimizer(mut self, optimizer: Arc<QueryOptimizer>) -> Self {
130 self.optimizer = Some(optimizer);
131 self
132 }
133
134 pub fn add_namespace(&mut self, prefix: String, uri: String) {
135 self.namespace_prefixes.insert(prefix, uri);
136 }
137
138 pub async fn graphql_to_sparql(
140 &self,
141 document: &Document,
142 type_name: &str,
143 context: &QueryContext,
144 ) -> Result<String> {
145 if let Some(ref optimizer) = self.optimizer {
147 let query_plan = optimizer.get_query_plan(document).await?;
148 if self.config.optimize_queries {
149 return Ok(query_plan.sparql_query);
150 }
151 }
152
153 *self.variable_counter.borrow_mut() = 0;
155
156 let mut sparql_builder = SparqlQueryBuilder::new(&self.namespace_prefixes, &self.config);
157
158 let operation = self.extract_operation(document, &context.operation_name)?;
160
161 let query_depth = self.calculate_query_depth(&operation.selection_set);
163 if query_depth > self.config.max_query_depth {
164 return Err(anyhow!(
165 "Query depth {} exceeds maximum allowed depth {}",
166 query_depth,
167 self.config.max_query_depth
168 ));
169 }
170
171 match operation.operation_type {
173 crate::ast::OperationType::Query => {
174 self.translate_query_operation(operation, &mut sparql_builder, context, type_name)
175 }
176 crate::ast::OperationType::Mutation => self.translate_mutation_operation(
177 operation,
178 &mut sparql_builder,
179 context,
180 type_name,
181 ),
182 crate::ast::OperationType::Subscription => {
183 Err(anyhow!("Subscription operations not yet supported"))
184 }
185 }
186 }
187
188 fn extract_operation<'a>(
189 &self,
190 document: &'a Document,
191 operation_name: &Option<String>,
192 ) -> Result<&'a OperationDefinition> {
193 let operations: Vec<_> = document
194 .definitions
195 .iter()
196 .filter_map(|def| match def {
197 crate::ast::Definition::Operation(op) => Some(op),
198 _ => None,
199 })
200 .collect();
201
202 match (operations.len(), operation_name) {
203 (0, _) => Err(anyhow!("No operations found in document")),
204 (1, _) => Ok(operations[0]),
205 (_, Some(name)) => operations
206 .iter()
207 .find(|op| op.name.as_ref() == Some(name))
208 .copied()
209 .ok_or_else(|| anyhow!("Operation '{}' not found", name)),
210 (_, None) => Err(anyhow!(
211 "Multiple operations found but no operation name specified"
212 )),
213 }
214 }
215
216 fn calculate_query_depth(&self, selection_set: &SelectionSet) -> usize {
217 fn calculate_depth_recursive(selection_set: &SelectionSet, current_depth: usize) -> usize {
218 let mut max_depth = current_depth;
219
220 for selection in &selection_set.selections {
221 let depth = match selection {
222 Selection::Field(field) => {
223 if let Some(ref nested_selection) = field.selection_set {
224 calculate_depth_recursive(nested_selection, current_depth + 1)
225 } else {
226 current_depth + 1
227 }
228 }
229 Selection::InlineFragment(fragment) => {
230 calculate_depth_recursive(&fragment.selection_set, current_depth)
231 }
232 Selection::FragmentSpread(_) => current_depth + 1, };
234 max_depth = max_depth.max(depth);
235 }
236 max_depth
237 }
238
239 calculate_depth_recursive(selection_set, 0)
240 }
241
242 fn translate_query_operation(
243 &self,
244 operation: &OperationDefinition,
245 builder: &mut SparqlQueryBuilder,
246 context: &QueryContext,
247 type_name: &str,
248 ) -> Result<String> {
249 if type_name == "Query" {
251 self.translate_root_selection_set(&operation.selection_set, builder, context)?;
252 } else {
253 return Err(anyhow!("Only Query type supported for now"));
254 }
255
256 builder.build()
257 }
258
259 fn translate_mutation_operation(
260 &self,
261 operation: &OperationDefinition,
262 builder: &mut SparqlQueryBuilder,
263 context: &QueryContext,
264 type_name: &str,
265 ) -> Result<String> {
266 if type_name == "Mutation" {
268 self.translate_mutation_selection_set(&operation.selection_set, builder, context)?;
269 } else {
270 return Err(anyhow!("Only Mutation type supported for mutations"));
271 }
272
273 builder.build()
274 }
275
276 fn translate_mutation_selection_set(
277 &self,
278 selection_set: &SelectionSet,
279 builder: &mut SparqlQueryBuilder,
280 context: &QueryContext,
281 ) -> Result<()> {
282 for selection in &selection_set.selections {
283 match selection {
284 Selection::Field(field) => {
285 self.translate_mutation_field(field, builder, context)?;
286 }
287 Selection::InlineFragment(_) | Selection::FragmentSpread(_) => {
288 }
290 }
291 }
292 Ok(())
293 }
294
295 fn translate_mutation_field(
296 &self,
297 field: &Field,
298 builder: &mut SparqlQueryBuilder,
299 _context: &QueryContext,
300 ) -> Result<()> {
301 match field.name.as_str() {
302 "insertTriple" => {
303 let args: Vec<(String, Value)> = field
304 .arguments
305 .iter()
306 .map(|arg| (arg.name.clone(), arg.value.clone()))
307 .collect();
308 let triple = self.extract_triple_from_arguments(&args)?;
309 builder.add_insert_data(&triple);
310 }
311 "insertTriples" => {
312 let args: Vec<(String, Value)> = field
313 .arguments
314 .iter()
315 .map(|arg| (arg.name.clone(), arg.value.clone()))
316 .collect();
317 let triples = self.extract_triples_from_arguments(&args)?;
318 for triple in triples {
319 builder.add_insert_data(&triple);
320 }
321 }
322 "deleteTriple" => {
323 let args: Vec<(String, Value)> = field
324 .arguments
325 .iter()
326 .map(|arg| (arg.name.clone(), arg.value.clone()))
327 .collect();
328 let triple = self.extract_triple_from_arguments(&args)?;
329 builder.add_delete_data(&triple);
330 }
331 "deleteTriples" => {
332 let args: Vec<(String, Value)> = field
333 .arguments
334 .iter()
335 .map(|arg| (arg.name.clone(), arg.value.clone()))
336 .collect();
337 let triples = self.extract_triples_from_arguments(&args)?;
338 for triple in triples {
339 builder.add_delete_data(&triple);
340 }
341 }
342 "updateTriple" => {
343 let args: Vec<(String, Value)> = field
345 .arguments
346 .iter()
347 .map(|arg| (arg.name.clone(), arg.value.clone()))
348 .collect();
349 if let Ok(old_triple) = self.extract_old_triple_from_arguments(&args) {
350 builder.add_delete_data(&old_triple);
351 }
352 if let Ok(new_triple) = self.extract_new_triple_from_arguments(&args) {
353 builder.add_insert_data(&new_triple);
354 }
355 }
356 _ => {
357 return Err(anyhow!("Unsupported mutation operation: {}", field.name));
358 }
359 }
360 Ok(())
361 }
362
363 fn extract_triple_from_arguments(&self, arguments: &[(String, Value)]) -> Result<String> {
364 let mut subject = None;
365 let mut predicate = None;
366 let mut object = None;
367
368 for (arg_name, arg_value) in arguments {
369 match arg_name.as_str() {
370 "subject" => {
371 subject = Some(self.format_rdf_term(arg_value)?);
372 }
373 "predicate" => {
374 predicate = Some(self.format_rdf_term(arg_value)?);
375 }
376 "object" => {
377 object = Some(self.format_rdf_term(arg_value)?);
378 }
379 _ => {}
380 }
381 }
382
383 match (subject, predicate, object) {
384 (Some(s), Some(p), Some(o)) => Ok(format!("{s} {p} {o} .")),
385 _ => Err(anyhow!(
386 "Missing required arguments: subject, predicate, or object"
387 )),
388 }
389 }
390
391 fn extract_triples_from_arguments(&self, arguments: &[(String, Value)]) -> Result<Vec<String>> {
392 for (arg_name, arg_value) in arguments {
393 if arg_name == "triples" {
394 if let Value::ListValue(triple_list) = arg_value {
395 let mut result = Vec::new();
396 for triple_value in triple_list {
397 if let Value::ObjectValue(triple_obj) = triple_value {
398 let triple_args: Vec<(String, Value)> = triple_obj
399 .iter()
400 .map(|(k, v)| (k.clone(), v.clone()))
401 .collect();
402 result.push(self.extract_triple_from_arguments(&triple_args)?);
403 }
404 }
405 return Ok(result);
406 }
407 }
408 }
409 Err(anyhow!("No triples argument found"))
410 }
411
412 fn extract_old_triple_from_arguments(&self, arguments: &[(String, Value)]) -> Result<String> {
413 for (arg_name, arg_value) in arguments {
414 if arg_name == "old" {
415 if let Value::ObjectValue(old_obj) = arg_value {
416 let old_args: Vec<(String, Value)> = old_obj
417 .iter()
418 .map(|(k, v)| (k.clone(), v.clone()))
419 .collect();
420 return self.extract_triple_from_arguments(&old_args);
421 }
422 }
423 }
424 Err(anyhow!("No old triple argument found"))
425 }
426
427 fn extract_new_triple_from_arguments(&self, arguments: &[(String, Value)]) -> Result<String> {
428 for (arg_name, arg_value) in arguments {
429 if arg_name == "new" {
430 if let Value::ObjectValue(new_obj) = arg_value {
431 let new_args: Vec<(String, Value)> = new_obj
432 .iter()
433 .map(|(k, v)| (k.clone(), v.clone()))
434 .collect();
435 return self.extract_triple_from_arguments(&new_args);
436 }
437 }
438 }
439 Err(anyhow!("No new triple argument found"))
440 }
441
442 fn format_rdf_term(&self, value: &Value) -> Result<String> {
443 match value {
444 Value::StringValue(s) => {
445 if s.contains("://") || s.starts_with("http") {
447 Ok(format!("<{s}>"))
448 } else if s.starts_with("_:") {
449 Ok(s.clone())
451 } else {
452 Ok(format!("\"{}\"", s.replace('\"', "\\\"")))
454 }
455 }
456 Value::IntValue(i) => Ok(format!(
457 "\"{i}\"^^<http://www.w3.org/2001/XMLSchema#integer>"
458 )),
459 Value::FloatValue(f) => Ok(format!(
460 "\"{f}\"^^<http://www.w3.org/2001/XMLSchema#decimal>"
461 )),
462 Value::BooleanValue(b) => Ok(format!(
463 "\"{b}\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
464 )),
465 Value::ObjectValue(obj) => {
466 if let (Some(Value::StringValue(value)), Some(Value::StringValue(datatype))) =
468 (obj.get("value"), obj.get("datatype"))
469 {
470 Ok(format!("\"{value}\"^^<{datatype}>"))
471 } else if let (Some(Value::StringValue(value)), Some(Value::StringValue(lang))) =
472 (obj.get("value"), obj.get("language"))
473 {
474 Ok(format!("\"{value}\"@{lang}"))
475 } else {
476 Err(anyhow!("Invalid RDF term object format"))
477 }
478 }
479 _ => Err(anyhow!("Unsupported value type for RDF term")),
480 }
481 }
482
483 fn translate_root_selection_set(
484 &self,
485 selection_set: &SelectionSet,
486 builder: &mut SparqlQueryBuilder,
487 context: &QueryContext,
488 ) -> Result<()> {
489 for selection in &selection_set.selections {
490 match selection {
491 Selection::Field(field) => {
492 self.translate_root_field(field, builder, context)?;
493 }
494 Selection::InlineFragment(fragment) => {
495 self.translate_root_selection_set(&fragment.selection_set, builder, context)?;
497 }
498 Selection::FragmentSpread(fragment_spread) => {
499 if let Some(fragment_def) =
501 context.fragments.get(&fragment_spread.fragment_name)
502 {
503 self.translate_root_selection_set(
504 &fragment_def.selection_set,
505 builder,
506 context,
507 )?;
508 }
509 }
510 }
511 }
512 Ok(())
513 }
514
515 fn translate_root_field(
516 &self,
517 field: &Field,
518 builder: &mut SparqlQueryBuilder,
519 context: &QueryContext,
520 ) -> Result<()> {
521 match field.name.as_str() {
522 "hello" | "version" => {
523 }
525 "triples" => {
526 builder.add_count_query("?s ?p ?o");
527 }
528 "subjects" => {
529 let limit = self
530 .extract_limit_from_field(field)
531 .unwrap_or(self.config.default_limit);
532 builder.add_distinct_select("?s");
533 builder.add_where_pattern("?s ?p ?o");
534 builder.set_limit(limit);
535 }
536 "predicates" => {
537 let limit = self
538 .extract_limit_from_field(field)
539 .unwrap_or(self.config.default_limit);
540 builder.add_distinct_select("?p");
541 builder.add_where_pattern("?s ?p ?o");
542 builder.set_limit(limit);
543 }
544 "objects" => {
545 let limit = self
546 .extract_limit_from_field(field)
547 .unwrap_or(self.config.default_limit);
548 builder.add_distinct_select("?o");
549 builder.add_where_pattern("?s ?p ?o");
550 builder.set_limit(limit);
551 }
552 "sparql" => {
553 }
555 _ => {
556 if let Some(ref vocabulary) = self.vocabulary {
558 self.translate_vocabulary_field(field, builder, context, vocabulary)?;
559 } else {
560 let var_name = format!("?{}", field.name);
562 builder.add_select(&var_name);
563 builder.add_where_pattern(&format!(
564 "?s {} {}",
565 self.field_to_predicate(&field.name),
566 var_name
567 ));
568 }
569 }
570 }
571 Ok(())
572 }
573
574 fn translate_vocabulary_field(
575 &self,
576 field: &Field,
577 builder: &mut SparqlQueryBuilder,
578 context: &QueryContext,
579 vocabulary: &RdfVocabulary,
580 ) -> Result<()> {
581 let field_name_variations = vec![
583 field.name.clone(),
584 self.pluralize(&field.name),
585 self.singularize(&field.name),
586 ];
587
588 for class_uri in vocabulary.classes.keys() {
589 let class_name = self.uri_to_graphql_name(class_uri);
590 let class_name_lower = class_name.to_lowercase();
591
592 for variation in &field_name_variations {
593 if variation.to_lowercase() == class_name_lower
594 || variation.to_lowercase() == self.pluralize(&class_name_lower)
595 {
596 return self
598 .translate_class_query(field, class_uri, builder, context, vocabulary);
599 }
600 }
601 }
602
603 self.translate_property_field(field, builder, context, vocabulary)
605 }
606
607 fn translate_class_query(
608 &self,
609 field: &Field,
610 class_uri: &str,
611 builder: &mut SparqlQueryBuilder,
612 context: &QueryContext,
613 vocabulary: &RdfVocabulary,
614 ) -> Result<()> {
615 let subject_var = "?subject";
616
617 builder.add_where_pattern(&format!("{subject_var} a <{class_uri}>"));
619
620 let limit = self
622 .extract_limit_from_field(field)
623 .unwrap_or(self.config.default_limit);
624 let offset = self.extract_offset_from_field(field).unwrap_or(0);
625
626 if let Some(where_filter) = self.extract_where_from_field(field) {
627 builder.add_filter(&where_filter);
628 }
629
630 if let Some(ref selection_set) = field.selection_set {
632 self.translate_object_selection_set(
633 selection_set,
634 subject_var,
635 builder,
636 context,
637 vocabulary,
638 )?;
639 } else {
640 builder.add_select(subject_var);
642 }
643
644 builder.set_limit(limit);
645 if offset > 0 {
646 builder.set_offset(offset);
647 }
648
649 Ok(())
650 }
651
652 fn translate_object_selection_set(
653 &self,
654 selection_set: &SelectionSet,
655 subject_var: &str,
656 builder: &mut SparqlQueryBuilder,
657 context: &QueryContext,
658 vocabulary: &RdfVocabulary,
659 ) -> Result<()> {
660 for selection in &selection_set.selections {
661 match selection {
662 Selection::Field(field) => {
663 self.translate_object_field(field, subject_var, builder, context, vocabulary)?;
664 }
665 Selection::InlineFragment(fragment) => {
666 self.translate_object_selection_set(
667 &fragment.selection_set,
668 subject_var,
669 builder,
670 context,
671 vocabulary,
672 )?;
673 }
674 Selection::FragmentSpread(fragment_spread) => {
675 if let Some(fragment_def) =
676 context.fragments.get(&fragment_spread.fragment_name)
677 {
678 self.translate_object_selection_set(
679 &fragment_def.selection_set,
680 subject_var,
681 builder,
682 context,
683 vocabulary,
684 )?;
685 }
686 }
687 }
688 }
689 Ok(())
690 }
691
692 fn translate_object_field(
693 &self,
694 field: &Field,
695 subject_var: &str,
696 builder: &mut SparqlQueryBuilder,
697 context: &QueryContext,
698 vocabulary: &RdfVocabulary,
699 ) -> Result<()> {
700 match field.name.as_str() {
701 "id" | "uri" => {
702 builder.add_select(subject_var);
703 }
704 _ => {
705 let property_uri = self.find_property_uri(&field.name, vocabulary);
707 if let Some(prop_uri) = property_uri {
708 let field_var = format!("?{}", field.name);
709 builder.add_select(&field_var);
710
711 if let Some(property) = vocabulary.properties.get(&prop_uri) {
712 match property.property_type {
713 PropertyType::ObjectProperty => {
714 builder.add_where_pattern(&format!(
715 "{subject_var} <{prop_uri}> {field_var}"
716 ));
717
718 if let Some(ref selection_set) = field.selection_set {
720 self.translate_object_selection_set(
721 selection_set,
722 &field_var,
723 builder,
724 context,
725 vocabulary,
726 )?;
727 }
728 }
729 PropertyType::DataProperty | PropertyType::AnnotationProperty => {
730 builder.add_where_pattern(&format!(
731 "{subject_var} <{prop_uri}> {field_var}"
732 ));
733 }
734 }
735 } else {
736 builder
738 .add_where_pattern(&format!("{subject_var} <{prop_uri}> {field_var}"));
739 }
740 } else {
741 let predicate = self.field_to_predicate(&field.name);
743 let field_var = format!("?{}", field.name);
744 builder.add_select(&field_var);
745 builder.add_where_pattern(&format!("{subject_var} {predicate} {field_var}"));
746 }
747 }
748 }
749 Ok(())
750 }
751
752 fn translate_property_field(
753 &self,
754 field: &Field,
755 builder: &mut SparqlQueryBuilder,
756 _context: &QueryContext,
757 _vocabulary: &RdfVocabulary,
758 ) -> Result<()> {
759 let var_name = format!("?{}", field.name);
761 let predicate = self.field_to_predicate(&field.name);
762
763 builder.add_select(&var_name);
764 builder.add_where_pattern(&format!("?s {predicate} {var_name}"));
765
766 Ok(())
767 }
768
769 fn find_property_uri(&self, field_name: &str, vocabulary: &RdfVocabulary) -> Option<String> {
770 for (prop_uri, property) in &vocabulary.properties {
772 if let Some(ref label) = property.label {
773 if label.to_lowercase() == field_name.to_lowercase() {
774 return Some(prop_uri.clone());
775 }
776 }
777
778 let local_name = prop_uri.split(['#', '/']).next_back().unwrap_or("");
780 if local_name.to_lowercase() == field_name.to_lowercase() {
781 return Some(prop_uri.clone());
782 }
783 }
784 None
785 }
786
787 fn uri_to_graphql_name(&self, uri: &str) -> String {
788 if let Some(fragment) = uri.split('#').next_back() {
789 self.to_pascal_case(fragment)
790 } else if let Some(segment) = uri.split('/').next_back() {
791 self.to_pascal_case(segment)
792 } else {
793 "Resource".to_string()
794 }
795 }
796
797 fn to_pascal_case(&self, input: &str) -> String {
798 let mut result = String::new();
799 let mut capitalize_next = true;
800
801 for ch in input.chars() {
802 if ch.is_alphanumeric() {
803 if capitalize_next {
804 result.push(ch.to_uppercase().next().unwrap_or(ch));
805 capitalize_next = false;
806 } else {
807 result.push(ch);
808 }
809 } else {
810 capitalize_next = true;
811 }
812 }
813
814 result
815 }
816
817 fn pluralize(&self, word: &str) -> String {
818 if word.ends_with('s') || word.ends_with("sh") || word.ends_with("ch") {
819 format!("{word}es")
820 } else if let Some(stripped) = word.strip_suffix('y') {
821 format!("{stripped}ies")
822 } else {
823 format!("{word}s")
824 }
825 }
826
827 fn singularize(&self, word: &str) -> String {
828 if let Some(stripped) = word.strip_suffix("ies") {
829 format!("{stripped}y")
830 } else if word.ends_with("es") && (word.ends_with("shes") || word.ends_with("ches")) {
831 word[..word.len() - 2].to_string()
832 } else if word.ends_with('s') && word.len() > 1 {
833 word[..word.len() - 1].to_string()
834 } else {
835 word.to_string()
836 }
837 }
838
839 #[allow(dead_code)]
840 fn build_sparql_from_selection_set(
841 &self,
842 selection_set: &SelectionSet,
843 ) -> Result<(Vec<String>, Vec<String>)> {
844 let mut select_vars = Vec::new();
845 let mut where_patterns = Vec::new();
846
847 for selection in &selection_set.selections {
848 match selection {
849 Selection::Field(field) => {
850 match field.name.as_str() {
851 "hello" | "version" => {
852 }
854 "triples" => {
855 select_vars.push("(COUNT(*) as ?count)".to_string());
857 where_patterns.push("?s ?p ?o".to_string());
858 }
859 "subjects" => {
860 select_vars.push("DISTINCT ?s".to_string());
861 where_patterns.push("?s ?p ?o".to_string());
862
863 if let Some(_limit) = self.extract_limit_from_field(field) {
865 }
867 }
868 "predicates" => {
869 select_vars.push("DISTINCT ?p".to_string());
870 where_patterns.push("?s ?p ?o".to_string());
871 }
872 "objects" => {
873 select_vars.push("DISTINCT ?o".to_string());
874 where_patterns.push("?s ?p ?o".to_string());
875 }
876 "sparql" => {
877 }
879 _ => {
880 let var_name = format!("?{}", field.name);
882 select_vars.push(var_name.clone());
883 where_patterns.push(format!(
884 "?s {} {}",
885 self.field_to_predicate(&field.name),
886 var_name
887 ));
888 }
889 }
890 }
891 _ => {
892 }
894 }
895 }
896
897 Ok((select_vars, where_patterns))
898 }
899
900 fn extract_limit_from_field(&self, field: &Field) -> Option<usize> {
901 for arg in &field.arguments {
902 if arg.name == "limit" {
903 if let Value::IntValue(i) = &arg.value {
904 let limit = (*i as usize).min(self.config.max_limit);
905 return Some(limit);
906 }
907 }
908 }
909 None
910 }
911
912 fn extract_offset_from_field(&self, field: &Field) -> Option<usize> {
913 for arg in &field.arguments {
914 if arg.name == "offset" {
915 if let Value::IntValue(i) = &arg.value {
916 return Some(*i as usize);
917 }
918 }
919 }
920 None
921 }
922
923 fn extract_where_from_field(&self, field: &Field) -> Option<String> {
924 for arg in &field.arguments {
925 if arg.name == "where" {
926 if let Value::StringValue(s) = &arg.value {
927 return Some(s.clone());
928 }
929 }
930 }
931 None
932 }
933
934 fn field_to_predicate(&self, field_name: &str) -> String {
935 match field_name {
937 "name" => "foaf:name".to_string(),
938 "email" => "foaf:mbox".to_string(),
939 "knows" => "foaf:knows".to_string(),
940 "label" => "rdfs:label".to_string(),
941 "comment" => "rdfs:comment".to_string(),
942 _ => format!(":{field_name}"), }
944 }
945
946 pub fn rdf_to_graphql_schema(&self, vocabulary_uri: &str) -> Result<String> {
948 Ok(format!(
950 r#"
951type Query {{
952 hello: String
953 version: String
954 triples: Int
955 subjects(limit: Int = 10): [String!]!
956 predicates(limit: Int = 10): [String!]!
957 objects(limit: Int = 10): [String!]!
958 sparql(query: String!): String
959}}
960
961# Generated from vocabulary: {vocabulary_uri}
962"#
963 ))
964 }
965}
966
967struct SparqlQueryBuilder {
969 prefixes: HashMap<String, String>,
970 select_vars: Vec<String>,
971 where_patterns: Vec<String>,
972 filters: Vec<String>,
973 group_by: Vec<String>,
974 order_by: Vec<String>,
975 limit: Option<usize>,
976 offset: Option<usize>,
977 distinct: bool,
978 config: TranslationConfig,
979 insert_data: Vec<String>,
981 delete_data: Vec<String>,
982 insert_where: Vec<String>,
983 delete_where: Vec<String>,
984 is_update_query: bool,
985}
986
987impl SparqlQueryBuilder {
988 fn new(prefixes: &HashMap<String, String>, config: &TranslationConfig) -> Self {
989 Self {
990 prefixes: prefixes.clone(),
991 select_vars: Vec::new(),
992 where_patterns: Vec::new(),
993 filters: Vec::new(),
994 group_by: Vec::new(),
995 order_by: Vec::new(),
996 limit: None,
997 offset: None,
998 distinct: false,
999 config: config.clone(),
1000 insert_data: Vec::new(),
1001 delete_data: Vec::new(),
1002 insert_where: Vec::new(),
1003 delete_where: Vec::new(),
1004 is_update_query: false,
1005 }
1006 }
1007
1008 fn add_select(&mut self, var: &str) {
1009 if !self.select_vars.contains(&var.to_string()) {
1010 self.select_vars.push(var.to_string());
1011 }
1012 }
1013
1014 fn add_distinct_select(&mut self, var: &str) {
1015 self.distinct = true;
1016 self.add_select(var);
1017 }
1018
1019 fn add_count_query(&mut self, pattern: &str) {
1020 self.select_vars.push("(COUNT(*) as ?count)".to_string());
1021 self.add_where_pattern(pattern);
1022 }
1023
1024 fn add_where_pattern(&mut self, pattern: &str) {
1025 self.where_patterns.push(pattern.to_string());
1026 }
1027
1028 fn add_filter(&mut self, filter: &str) {
1029 self.filters.push(filter.to_string());
1030 }
1031
1032 fn set_limit(&mut self, limit: usize) {
1033 self.limit = Some(limit.min(self.config.max_limit));
1034 }
1035
1036 fn set_offset(&mut self, offset: usize) {
1037 self.offset = Some(offset);
1038 }
1039
1040 fn add_insert_data(&mut self, triple: &str) {
1041 self.is_update_query = true;
1042 self.insert_data.push(triple.to_string());
1043 }
1044
1045 fn add_delete_data(&mut self, triple: &str) {
1046 self.is_update_query = true;
1047 self.delete_data.push(triple.to_string());
1048 }
1049
1050 #[allow(dead_code)]
1051 fn add_insert_where(&mut self, triple: &str, condition: &str) {
1052 self.is_update_query = true;
1053 self.insert_where
1054 .push(format!("{triple} WHERE {{ {condition} }}"));
1055 }
1056
1057 #[allow(dead_code)]
1058 fn add_delete_where(&mut self, triple: &str, condition: &str) {
1059 self.is_update_query = true;
1060 self.delete_where
1061 .push(format!("{triple} WHERE {{ {condition} }}"));
1062 }
1063
1064 fn build(&self) -> Result<String> {
1065 let mut query = String::new();
1066
1067 for (prefix, uri) in &self.prefixes {
1069 writeln!(query, "PREFIX {prefix}: <{uri}>")?;
1070 }
1071
1072 if !query.is_empty() {
1073 writeln!(query)?;
1074 }
1075
1076 if self.is_update_query {
1077 self.build_update_query(&mut query)?;
1079 } else {
1080 self.build_select_query(&mut query)?;
1082 }
1083
1084 Ok(query)
1085 }
1086
1087 fn build_select_query(&self, query: &mut String) -> Result<()> {
1088 if self.select_vars.is_empty() {
1090 write!(query, "SELECT *")?;
1091 } else {
1092 let distinct_clause = if self.distinct { "DISTINCT " } else { "" };
1093 write!(
1094 query,
1095 "SELECT {}{}",
1096 distinct_clause,
1097 self.select_vars.join(" ")
1098 )?;
1099 }
1100
1101 if !self.where_patterns.is_empty() {
1103 writeln!(query)?;
1104 writeln!(query, "WHERE {{")?;
1105 for pattern in &self.where_patterns {
1106 writeln!(query, " {pattern}")?;
1107 }
1108
1109 for filter in &self.filters {
1111 writeln!(query, " FILTER({filter})")?;
1112 }
1113
1114 write!(query, "}}")?
1115 }
1116
1117 if !self.group_by.is_empty() {
1119 writeln!(query)?;
1120 write!(query, "GROUP BY {}", self.group_by.join(" "))?;
1121 }
1122
1123 if !self.order_by.is_empty() {
1125 writeln!(query)?;
1126 write!(query, "ORDER BY {}", self.order_by.join(" "))?;
1127 }
1128
1129 if let Some(limit) = self.limit {
1131 writeln!(query)?;
1132 write!(query, "LIMIT {limit}")?;
1133 }
1134
1135 if let Some(offset) = self.offset {
1136 writeln!(query)?;
1137 write!(query, "OFFSET {offset}")?;
1138 }
1139
1140 Ok(())
1141 }
1142
1143 fn build_update_query(&self, query: &mut String) -> Result<()> {
1144 let mut operations = Vec::new();
1145
1146 if !self.delete_data.is_empty() {
1148 let mut delete_block = String::new();
1149 writeln!(delete_block, "DELETE DATA {{")?;
1150 for triple in &self.delete_data {
1151 writeln!(delete_block, " {triple}")?;
1152 }
1153 write!(delete_block, "}}")?;
1154 operations.push(delete_block);
1155 }
1156
1157 if !self.insert_data.is_empty() {
1159 let mut insert_block = String::new();
1160 writeln!(insert_block, "INSERT DATA {{")?;
1161 for triple in &self.insert_data {
1162 writeln!(insert_block, " {triple}")?;
1163 }
1164 write!(insert_block, "}}")?;
1165 operations.push(insert_block);
1166 }
1167
1168 for delete_where in &self.delete_where {
1170 operations.push(format!("DELETE {delete_where}"));
1171 }
1172
1173 for insert_where in &self.insert_where {
1175 operations.push(format!("INSERT {insert_where}"));
1176 }
1177
1178 write!(query, "{}", operations.join(";\n"))?;
1180
1181 Ok(())
1182 }
1183
1184 #[allow(dead_code)]
1186 fn add_group_by(&mut self, group: &str) {
1187 self.group_by.push(group.to_string());
1188 }
1189
1190 #[allow(dead_code)]
1192 fn add_order_by(&mut self, order: &str) {
1193 self.order_by.push(order.to_string());
1194 }
1195}
1196
1197impl Default for RdfGraphQLMapper {
1198 fn default() -> Self {
1199 Self::new()
1200 }
1201}
1202
1203impl Default for QueryContext {
1204 fn default() -> Self {
1205 Self::new()
1206 }
1207}