Skip to main content

fraiseql_core/federation/
database_resolver.rs

1//! Database entity resolution for federation.
2//!
3//! Executes actual database queries to resolve entities from local databases,
4//! replacing mock data with real results.
5
6use std::sync::Arc;
7
8use serde_json::Value;
9use tracing::warn;
10
11use crate::{
12    db::traits::DatabaseAdapter,
13    error::Result,
14    federation::{
15        metadata_helpers::find_federation_type,
16        query_builder::construct_where_in_clause,
17        requires_provides_validator::RequiresProvidesRuntimeValidator,
18        selection_parser::FieldSelection,
19        tracing::FederationTraceContext,
20        types::{EntityRepresentation, FederatedType, FederationMetadata},
21    },
22};
23
24/// Resolves federation entities from local databases.
25pub struct DatabaseEntityResolver<A: DatabaseAdapter> {
26    /// Database adapter for executing queries
27    adapter:  Arc<A>,
28    /// Federation metadata
29    metadata: FederationMetadata,
30}
31
32impl<A: DatabaseAdapter> DatabaseEntityResolver<A> {
33    /// Create a new database entity resolver.
34    #[must_use]
35    pub fn new(adapter: Arc<A>, metadata: FederationMetadata) -> Self {
36        Self { adapter, metadata }
37    }
38
39    /// Resolve entities from database.
40    ///
41    /// # Arguments
42    ///
43    /// * `typename` - The entity type name (e.g., "User")
44    /// * `representations` - Entity representations with key field values
45    /// * `selection` - Field selection from GraphQL query
46    ///
47    /// # Returns
48    ///
49    /// Vector of resolved entities (or None for missing entities)
50    ///
51    /// # Errors
52    ///
53    /// Returns error if database query fails
54    pub async fn resolve_entities_from_db(
55        &self,
56        typename: &str,
57        representations: &[EntityRepresentation],
58        selection: &FieldSelection,
59    ) -> Result<Vec<Option<Value>>> {
60        self.resolve_entities_from_db_with_tracing(typename, representations, selection, None)
61            .await
62    }
63
64    /// Resolve entities from database with optional distributed tracing.
65    ///
66    /// # Arguments
67    ///
68    /// * `typename` - The entity type name (e.g., "User")
69    /// * `representations` - Entity representations with key field values
70    /// * `selection` - Field selection from GraphQL query
71    /// * `trace_context` - Optional W3C trace context for span creation
72    ///
73    /// # Returns
74    ///
75    /// Vector of resolved entities (or None for missing entities)
76    ///
77    /// # Errors
78    ///
79    /// Returns error if database query fails
80    pub async fn resolve_entities_from_db_with_tracing(
81        &self,
82        typename: &str,
83        representations: &[EntityRepresentation],
84        selection: &FieldSelection,
85        _trace_context: Option<FederationTraceContext>,
86    ) -> Result<Vec<Option<Value>>> {
87        if representations.is_empty() {
88            return Ok(Vec::new());
89        }
90
91        // Find type definition using metadata helpers
92        let fed_type = find_federation_type(typename, &self.metadata)?;
93
94        // Get table name (simplified: use lowercase type name)
95        let table_name = typename.to_lowercase();
96
97        // Build WHERE IN clause for batch query
98        let where_clause = construct_where_in_clause(typename, representations, &self.metadata)?;
99
100        // Build SELECT list from field selection + always include key fields
101        let mut select_fields = selection.fields.clone();
102        for key in &fed_type.keys {
103            for field in &key.fields {
104                if !select_fields.contains(field) {
105                    select_fields.push(field.clone());
106                }
107            }
108        }
109
110        // Add __typename field
111        if !select_fields.contains(&"__typename".to_string()) {
112            select_fields.push("__typename".to_string());
113        }
114
115        // Execute query
116        let sql = format!(
117            "SELECT {} FROM {} WHERE {}",
118            select_fields.join(", "),
119            table_name,
120            where_clause
121        );
122
123        // Execute the query (using raw query execution)
124        let rows = self.adapter.execute_raw_query(&sql).await?;
125
126        // Project results maintaining order
127        project_results(&rows, representations, fed_type, typename)
128    }
129}
130
131/// Project database results to federation format, maintaining order of representations.
132fn project_results(
133    rows: &[std::collections::HashMap<String, Value>],
134    representations: &[EntityRepresentation],
135    fed_type: &FederatedType,
136    typename: &str,
137) -> Result<Vec<Option<Value>>> {
138    use std::collections::HashMap as StdHashMap;
139
140    // Build a map of key values -> row data for quick lookup
141    // Key is constructed from the key fields of the federation type
142    let mut row_map: StdHashMap<Vec<String>, StdHashMap<String, Value>> = StdHashMap::new();
143
144    for row in rows {
145        // Build key from key fields
146        let key_values: Result<Vec<String>> = fed_type
147            .keys
148            .first()
149            .ok_or_else(|| crate::error::FraiseQLError::Validation {
150                message: format!("Type '{}' has no key fields", typename),
151                path:    None,
152            })?
153            .fields
154            .iter()
155            .map(|field| {
156                row.get(field)
157                    .and_then(|v| v.as_str().map(|s| s.to_string()))
158                    .or_else(|| row.get(field).map(|v| v.to_string()))
159                    .ok_or_else(|| crate::error::FraiseQLError::Validation {
160                        message: format!("Key field '{}' not found in row", field),
161                        path:    None,
162                    })
163            })
164            .collect();
165
166        if let Ok(key) = key_values {
167            row_map.insert(key, row.clone());
168        }
169    }
170
171    // Map representations to results, preserving order
172    let mut results = Vec::new();
173    for rep in representations {
174        // Extract key values from representation
175        let key_values: Vec<String> = fed_type
176            .keys
177            .first()
178            .map(|k| {
179                k.fields
180                    .iter()
181                    .filter_map(|field| {
182                        rep.key_fields.get(field).and_then(|v| {
183                            v.as_str().map(|s| s.to_string()).or_else(|| Some(v.to_string()))
184                        })
185                    })
186                    .collect()
187            })
188            .unwrap_or_default();
189
190        // Look up row in map
191        if let Some(row) = row_map.get(&key_values) {
192            let mut entity = row.clone();
193            entity.insert("__typename".to_string(), Value::String(typename.to_string()));
194
195            // Validate @requires/@provides directives are satisfied
196            if let Err(validation_errors) =
197                RequiresProvidesRuntimeValidator::validate_entity_against_type(
198                    typename, &entity, fed_type,
199                )
200            {
201                // Log validation errors but continue processing
202                for error in validation_errors {
203                    warn!("Federation directive validation error for {}: {}", typename, error);
204                }
205            }
206
207            results.push(Some(Value::Object(serde_json::Map::from_iter(entity))));
208        } else {
209            // Entity not found in database
210            results.push(None);
211        }
212    }
213
214    Ok(results)
215}
216
217#[cfg(test)]
218mod tests {
219    #[test]
220    fn test_database_resolver_creation() {
221        // Test that resolver can be created (mock adapter would be used)
222        // Actual DB tests are in integration tests
223    }
224}