metabase_api_rs/api/
client.rs

1//! Metabase client implementation
2
3use crate::api::auth::{AuthManager, Credentials};
4use crate::api::CardListParams;
5use crate::core::error::{Error, Result};
6use crate::core::models::common::{CardId, ExportFormat};
7#[cfg(feature = "query-builder")]
8use crate::core::models::mbql::MbqlQuery;
9use crate::core::models::{
10    Card, Collection, Dashboard, DatabaseMetadata, DatasetQuery, Field, HealthStatus, MetabaseId,
11    NativeQuery, Pagination, QueryResult, SyncResult, User,
12};
13use crate::service::ServiceManager;
14use crate::transport::http_provider_safe::{HttpClientAdapter, HttpProviderSafe};
15use crate::transport::HttpClient;
16use serde_json::{json, Value};
17use std::collections::HashMap;
18use std::sync::Arc;
19
20#[cfg(feature = "cache")]
21use crate::cache::{cache_key, CacheConfig, CacheLayer};
22
23/// The main client for interacting with Metabase API
24#[derive(Clone)]
25pub struct MetabaseClient {
26    pub(super) auth_manager: AuthManager,
27    pub(super) base_url: String,
28    pub(super) service_manager: ServiceManager,
29    #[cfg(feature = "cache")]
30    pub(super) cache: CacheLayer,
31}
32
33impl MetabaseClient {
34    /// Creates a new MetabaseClient instance
35    pub fn new(base_url: impl Into<String>) -> Result<Self> {
36        let base_url = base_url.into();
37
38        // Validate URL
39        if !base_url.starts_with("http://") && !base_url.starts_with("https://") {
40            return Err(Error::Config(
41                "Invalid URL: must start with http:// or https://".to_string(),
42            ));
43        }
44
45        let http_client = HttpClient::new(&base_url)?;
46        let auth_manager = AuthManager::new();
47
48        // Create HttpProviderSafe adapter for ServiceManager
49        let http_provider: Arc<dyn HttpProviderSafe> =
50            Arc::new(HttpClientAdapter::new(http_client));
51        let service_manager = ServiceManager::new(http_provider);
52
53        Ok(Self {
54            auth_manager,
55            base_url,
56            service_manager,
57            #[cfg(feature = "cache")]
58            cache: CacheLayer::new(CacheConfig::default()),
59        })
60    }
61
62    /// Creates a new MetabaseClient with custom cache configuration
63    #[cfg(feature = "cache")]
64    pub fn with_cache(base_url: impl Into<String>, cache_config: CacheConfig) -> Result<Self> {
65        let base_url = base_url.into();
66
67        // Validate URL
68        if !base_url.starts_with("http://") && !base_url.starts_with("https://") {
69            return Err(Error::Config(
70                "Invalid URL: must start with http:// or https://".to_string(),
71            ));
72        }
73
74        let http_client = HttpClient::new(&base_url)?;
75        let auth_manager = AuthManager::new();
76
77        // Create HttpProviderSafe adapter for ServiceManager
78        let http_provider: Arc<dyn HttpProviderSafe> =
79            Arc::new(HttpClientAdapter::new(http_client));
80        let service_manager = ServiceManager::new(http_provider);
81
82        Ok(Self {
83            auth_manager,
84            base_url,
85            service_manager,
86            cache: CacheLayer::new(cache_config),
87        })
88    }
89
90    /// Gets the base URL of the client
91    pub fn base_url(&self) -> &str {
92        &self.base_url
93    }
94
95    /// Checks if the client is authenticated
96    pub fn is_authenticated(&self) -> bool {
97        self.auth_manager.is_authenticated()
98    }
99
100    /// Checks if cache is enabled
101    #[cfg(feature = "cache")]
102    pub fn is_cache_enabled(&self) -> bool {
103        self.cache.is_enabled()
104    }
105
106    /// Sets the cache enabled state
107    #[cfg(feature = "cache")]
108    pub fn set_cache_enabled(&mut self, enabled: bool) {
109        self.cache.set_enabled(enabled);
110    }
111
112    /// Checks if cache is enabled (always false when cache feature is disabled)
113    #[cfg(not(feature = "cache"))]
114    pub fn is_cache_enabled(&self) -> bool {
115        false
116    }
117
118    /// Sets the cache enabled state (no-op when cache feature is disabled)
119    #[cfg(not(feature = "cache"))]
120    pub fn set_cache_enabled(&mut self, _enabled: bool) {
121        // No-op when cache feature is disabled
122    }
123
124    /// Authenticates with the Metabase API
125    pub async fn authenticate(&mut self, credentials: Credentials) -> Result<()> {
126        // Use ServiceManager for authentication
127        let (session_id, user) = self
128            .service_manager
129            .auth_service()
130            .ok_or_else(|| Error::Config("Auth service not available".to_string()))?
131            .authenticate(credentials)
132            .await
133            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
134
135        self.auth_manager.set_session(session_id, user);
136        Ok(())
137    }
138
139    /// Logs out from the Metabase API
140    pub async fn logout(&mut self) -> Result<()> {
141        if !self.is_authenticated() {
142            return Ok(());
143        }
144
145        // Get session ID before clearing
146        let session_id = self.auth_manager.get_session_id();
147
148        if let Some(id) = session_id {
149            // Use ServiceManager for logout
150            self.service_manager
151                .auth_service()
152                .ok_or_else(|| Error::Config("Auth service not available".to_string()))?
153                .logout(&id)
154                .await
155                .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
156        }
157
158        // Clear local session
159        self.auth_manager.clear_session();
160        Ok(())
161    }
162
163    /// Performs a health check on the Metabase API
164    pub async fn health_check(&self) -> Result<HealthStatus> {
165        // Use ServiceManager for health check
166        self.service_manager
167            .auth_service()
168            .ok_or_else(|| Error::Config("Auth service not available".to_string()))?
169            .health_check()
170            .await
171            .map_err(|e| Error::Config(format!("Service error: {}", e)))
172    }
173
174    /// Gets the current authenticated user
175    pub async fn get_current_user(&self) -> Result<User> {
176        if !self.is_authenticated() {
177            return Err(Error::Authentication("Not authenticated".to_string()));
178        }
179
180        let session_id = self
181            .auth_manager
182            .get_session_id()
183            .ok_or_else(|| Error::Authentication("No session available".to_string()))?;
184
185        // Use ServiceManager for getting current user
186        self.service_manager
187            .auth_service()
188            .ok_or_else(|| Error::Config("Auth service not available".to_string()))?
189            .get_current_user(&session_id)
190            .await
191            .map_err(|e| Error::Config(format!("Service error: {}", e)))
192    }
193
194    // ==================== Card Operations ====================
195
196    /// Gets a card by ID
197    pub async fn get_card(&self, id: i64) -> Result<Card> {
198        #[cfg(feature = "cache")]
199        {
200            let cache_key = cache_key("card", id);
201            if let Some(card) = self.cache.get_metadata::<Card>(&cache_key) {
202                return Ok(card);
203            }
204        }
205
206        // Use ServiceManager for layered architecture
207        let card = self
208            .service_manager
209            .card_service()
210            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
211            .get_card(CardId(id as i32))
212            .await
213            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
214
215        #[cfg(feature = "cache")]
216        {
217            let cache_key = cache_key("card", id);
218            let _ = self.cache.set_metadata(cache_key, &card);
219        }
220
221        Ok(card)
222    }
223
224    /// Lists all cards
225    pub async fn list_cards(&self, params: Option<CardListParams>) -> Result<Vec<Card>> {
226        use crate::repository::card::CardFilterParams;
227
228        // Convert CardListParams to CardFilterParams and PaginationParams
229        let filters = params.map(|p| CardFilterParams {
230            f: p.f,
231            model_type: p.model_type,
232            archived: None,
233            collection_id: None,
234        });
235
236        // Use ServiceManager for layered architecture
237        self.service_manager
238            .card_service()
239            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
240            .list_cards(None, filters)
241            .await
242            .map_err(|e| Error::Config(format!("Service error: {}", e)))
243    }
244
245    /// Creates a new card
246    pub async fn create_card(&self, card: Card) -> Result<Card> {
247        if !self.is_authenticated() {
248            return Err(Error::Authentication(
249                "Authentication required to create card".to_string(),
250            ));
251        }
252
253        // Use ServiceManager for layered architecture with validation
254        self.service_manager
255            .card_service()
256            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
257            .create_card(card)
258            .await
259            .map_err(|e| Error::Config(format!("Service error: {}", e)))
260    }
261
262    /// Updates an existing card
263    pub async fn update_card(&self, id: i64, updates: serde_json::Value) -> Result<Card> {
264        if !self.is_authenticated() {
265            return Err(Error::Authentication(
266                "Authentication required to update card".to_string(),
267            ));
268        }
269
270        #[cfg(feature = "cache")]
271        {
272            let cache_key = cache_key("card", id);
273            self.cache.invalidate(&cache_key);
274        }
275
276        // First, get the existing card
277        let service = self
278            .service_manager
279            .card_service()
280            .ok_or_else(|| Error::Config("Card service not available".to_string()))?;
281
282        let mut existing_card = service
283            .get_card(CardId(id as i32))
284            .await
285            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
286
287        // Merge the updates into the existing card
288        if let Some(name) = updates.get("name").and_then(|v| v.as_str()) {
289            existing_card.name = name.to_string();
290        }
291        if let Some(description) = updates.get("description") {
292            existing_card.description = if description.is_null() {
293                None
294            } else {
295                description.as_str().map(|s| s.to_string())
296            };
297        }
298        if let Some(display) = updates.get("display").and_then(|v| v.as_str()) {
299            existing_card.display = display.to_string();
300        }
301        if let Some(dataset_query) = updates.get("dataset_query") {
302            existing_card.dataset_query = Some(dataset_query.clone());
303        }
304        if let Some(visualization_settings) = updates.get("visualization_settings") {
305            existing_card.visualization_settings = visualization_settings.clone();
306        }
307
308        // Use ServiceManager for layered architecture
309        service
310            .update_card(CardId(id as i32), existing_card)
311            .await
312            .map_err(|e| Error::Config(format!("Service error: {}", e)))
313    }
314
315    /// Deletes a card
316    pub async fn delete_card(&self, id: i64) -> Result<()> {
317        if !self.is_authenticated() {
318            return Err(Error::Authentication(
319                "Authentication required to delete card".to_string(),
320            ));
321        }
322
323        #[cfg(feature = "cache")]
324        {
325            let cache_key = cache_key("card", id);
326            self.cache.invalidate(&cache_key);
327        }
328
329        // Use ServiceManager for layered architecture
330        self.service_manager
331            .card_service()
332            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
333            .delete_card(CardId(id as i32))
334            .await
335            .map_err(|e| Error::Config(format!("Service error: {}", e)))
336    }
337
338    // ==================== Collection Operations ====================
339
340    /// Gets a collection by ID
341    pub async fn get_collection(&self, id: MetabaseId) -> Result<Collection> {
342        use crate::core::models::common::CollectionId;
343
344        #[cfg(feature = "cache")]
345        {
346            let cache_key = cache_key("collection", id.0);
347            if let Some(collection) = self.cache.get_metadata::<Collection>(&cache_key) {
348                return Ok(collection);
349            }
350        }
351
352        // Use ServiceManager for layered architecture
353        let collection = self
354            .service_manager
355            .collection_service()
356            .ok_or_else(|| Error::Config("Collection service not available".to_string()))?
357            .get_collection(CollectionId(id.0 as i32))
358            .await
359            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
360
361        #[cfg(feature = "cache")]
362        {
363            let cache_key = cache_key("collection", id.0);
364            let _ = self.cache.set_metadata(cache_key, &collection);
365        }
366
367        Ok(collection)
368    }
369
370    /// Lists all collections
371    pub async fn list_collections(&self) -> Result<Vec<Collection>> {
372        // Use ServiceManager for layered architecture
373        self.service_manager
374            .collection_service()
375            .ok_or_else(|| Error::Config("Collection service not available".to_string()))?
376            .list_collections(None, None)
377            .await
378            .map_err(|e| Error::Config(format!("Service error: {}", e)))
379    }
380
381    /// Creates a new collection
382    pub async fn create_collection(&self, collection: Collection) -> Result<Collection> {
383        if !self.is_authenticated() {
384            return Err(Error::Authentication(
385                "Authentication required to create collection".to_string(),
386            ));
387        }
388
389        // Use ServiceManager for layered architecture with validation
390        self.service_manager
391            .collection_service()
392            .ok_or_else(|| Error::Config("Collection service not available".to_string()))?
393            .create_collection(collection)
394            .await
395            .map_err(|e| Error::Config(format!("Service error: {}", e)))
396    }
397
398    /// Updates an existing collection
399    pub async fn update_collection(
400        &self,
401        id: MetabaseId,
402        updates: serde_json::Value,
403    ) -> Result<Collection> {
404        use crate::core::models::common::CollectionId;
405
406        if !self.is_authenticated() {
407            return Err(Error::Authentication(
408                "Authentication required to update collection".to_string(),
409            ));
410        }
411
412        #[cfg(feature = "cache")]
413        {
414            let cache_key = cache_key("collection", id.0);
415            self.cache.invalidate(&cache_key);
416        }
417
418        // First, get the existing collection
419        let service = self
420            .service_manager
421            .collection_service()
422            .ok_or_else(|| Error::Config("Collection service not available".to_string()))?;
423
424        let mut existing_collection = service
425            .get_collection(CollectionId(id.0 as i32))
426            .await
427            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
428
429        // Merge the updates into the existing collection
430        if let Some(name) = updates.get("name").and_then(|v| v.as_str()) {
431            existing_collection.name = name.to_string();
432        }
433        if let Some(description) = updates.get("description") {
434            existing_collection.description = if description.is_null() {
435                None
436            } else {
437                description.as_str().map(|s| s.to_string())
438            };
439        }
440        if let Some(parent_id) = updates.get("parent_id") {
441            existing_collection.parent_id = if parent_id.is_null() {
442                None
443            } else {
444                parent_id.as_i64().map(|id| id as i32)
445            };
446        }
447        if let Some(color) = updates.get("color") {
448            existing_collection.color = if color.is_null() {
449                None
450            } else {
451                color.as_str().map(|s| s.to_string())
452            };
453        }
454        if let Some(archived) = updates.get("archived").and_then(|v| v.as_bool()) {
455            existing_collection.archived = Some(archived);
456        }
457
458        // Use ServiceManager for layered architecture
459        service
460            .update_collection(CollectionId(id.0 as i32), existing_collection)
461            .await
462            .map_err(|e| Error::Config(format!("Service error: {}", e)))
463    }
464
465    /// Archives a collection (Metabase doesn't delete, only archives)
466    pub async fn archive_collection(&self, id: MetabaseId) -> Result<Collection> {
467        use crate::core::models::common::CollectionId;
468
469        if !self.is_authenticated() {
470            return Err(Error::Authentication(
471                "Authentication required to archive collection".to_string(),
472            ));
473        }
474
475        #[cfg(feature = "cache")]
476        {
477            let cache_key = cache_key("collection", id.0);
478            self.cache.invalidate(&cache_key);
479        }
480
481        // Use ServiceManager for layered architecture
482        self.service_manager
483            .collection_service()
484            .ok_or_else(|| Error::Config("Collection service not available".to_string()))?
485            .archive_collection(CollectionId(id.0 as i32))
486            .await
487            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
488
489        // Get and return the updated collection
490        self.get_collection(id).await
491    }
492
493    // ==================== Dashboard Operations ====================
494
495    /// Gets a dashboard by ID
496    pub async fn get_dashboard(&self, id: MetabaseId) -> Result<Dashboard> {
497        use crate::core::models::common::DashboardId;
498
499        #[cfg(feature = "cache")]
500        {
501            let cache_key = cache_key("dashboard", id.0);
502            if let Some(dashboard) = self.cache.get_metadata::<Dashboard>(&cache_key) {
503                return Ok(dashboard);
504            }
505        }
506
507        // Use ServiceManager for layered architecture
508        let dashboard = self
509            .service_manager
510            .dashboard_service()
511            .ok_or_else(|| Error::Config("Dashboard service not available".to_string()))?
512            .get_dashboard(DashboardId(id.0 as i32))
513            .await
514            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
515
516        #[cfg(feature = "cache")]
517        {
518            let cache_key = cache_key("dashboard", id.0);
519            let _ = self.cache.set_metadata(cache_key, &dashboard);
520        }
521
522        Ok(dashboard)
523    }
524
525    /// Lists all dashboards
526    pub async fn list_dashboards(&self, pagination: Option<Pagination>) -> Result<Vec<Dashboard>> {
527        use crate::repository::traits::PaginationParams;
528
529        // Convert Pagination to PaginationParams if needed
530        let pagination_params = pagination.map(|p| PaginationParams {
531            page: None, // Using offset instead of page
532            limit: Some(p.limit() as u32),
533            offset: Some(p.offset() as u32),
534        });
535
536        // Use ServiceManager for layered architecture
537        self.service_manager
538            .dashboard_service()
539            .ok_or_else(|| Error::Config("Dashboard service not available".to_string()))?
540            .list_dashboards(pagination_params, None)
541            .await
542            .map_err(|e| Error::Config(format!("Service error: {}", e)))
543    }
544
545    /// Creates a new dashboard
546    pub async fn create_dashboard(&self, dashboard: Dashboard) -> Result<Dashboard> {
547        if !self.is_authenticated() {
548            return Err(Error::Authentication(
549                "Authentication required to create dashboard".to_string(),
550            ));
551        }
552
553        // Use ServiceManager for layered architecture with validation
554        self.service_manager
555            .dashboard_service()
556            .ok_or_else(|| Error::Config("Dashboard service not available".to_string()))?
557            .create_dashboard(dashboard)
558            .await
559            .map_err(|e| Error::Config(format!("Service error: {}", e)))
560    }
561
562    /// Updates an existing dashboard
563    pub async fn update_dashboard(
564        &self,
565        id: MetabaseId,
566        updates: serde_json::Value,
567    ) -> Result<Dashboard> {
568        use crate::core::models::common::DashboardId;
569
570        if !self.is_authenticated() {
571            return Err(Error::Authentication(
572                "Authentication required to update dashboard".to_string(),
573            ));
574        }
575
576        #[cfg(feature = "cache")]
577        {
578            let cache_key = cache_key("dashboard", id.0);
579            self.cache.invalidate(&cache_key);
580        }
581
582        // Convert JSON updates to Dashboard struct
583        let dashboard: Dashboard = serde_json::from_value(updates)
584            .map_err(|e| Error::Validation(format!("Invalid dashboard update: {}", e)))?;
585
586        // Use ServiceManager for layered architecture with validation
587        self.service_manager
588            .dashboard_service()
589            .ok_or_else(|| Error::Config("Dashboard service not available".to_string()))?
590            .update_dashboard(DashboardId(id.0 as i32), dashboard)
591            .await
592            .map_err(|e| Error::Config(format!("Service error: {}", e)))
593    }
594
595    /// Deletes a dashboard
596    pub async fn delete_dashboard(&self, id: MetabaseId) -> Result<()> {
597        use crate::core::models::common::DashboardId;
598
599        if !self.is_authenticated() {
600            return Err(Error::Authentication(
601                "Authentication required to delete dashboard".to_string(),
602            ));
603        }
604
605        #[cfg(feature = "cache")]
606        {
607            let cache_key = cache_key("dashboard", id.0);
608            self.cache.invalidate(&cache_key);
609        }
610
611        // Use ServiceManager for layered architecture
612        self.service_manager
613            .dashboard_service()
614            .ok_or_else(|| Error::Config("Dashboard service not available".to_string()))?
615            .delete_dashboard(DashboardId(id.0 as i32))
616            .await
617            .map_err(|e| Error::Config(format!("Service error: {}", e)))
618    }
619
620    // ==================== Query Operations ====================
621
622    /// Executes a dataset query
623    pub async fn execute_query(&self, query: DatasetQuery) -> Result<QueryResult> {
624        if !self.is_authenticated() {
625            return Err(Error::Authentication(
626                "Authentication required to execute query".to_string(),
627            ));
628        }
629
630        // Use ServiceManager for layered architecture
631        self.service_manager
632            .query_service()
633            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
634            .execute_dataset_query(query)
635            .await
636            .map_err(|e| Error::Config(format!("Service error: {}", e)))
637    }
638
639    /// Executes a native SQL query
640    pub async fn execute_native_query(
641        &self,
642        database: MetabaseId,
643        native_query: NativeQuery,
644    ) -> Result<QueryResult> {
645        if !self.is_authenticated() {
646            return Err(Error::Authentication(
647                "Authentication required to execute native query".to_string(),
648            ));
649        }
650
651        // Use ServiceManager for layered architecture
652        self.service_manager
653            .query_service()
654            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
655            .execute_native_query(database.0 as i32, native_query)
656            .await
657            .map_err(|e| Error::Config(format!("Service error: {}", e)))
658    }
659
660    // ==================== Extended Card Operations ====================
661
662    /// Execute a card's query with optional parameters
663    pub async fn execute_card_query(
664        &self,
665        card_id: i64,
666        parameters: Option<Value>,
667    ) -> Result<QueryResult> {
668        if !self.is_authenticated() {
669            return Err(Error::Authentication(
670                "Authentication required to execute card query".to_string(),
671            ));
672        }
673
674        // Use ServiceManager for layered architecture
675        self.service_manager
676            .card_service()
677            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
678            .execute_card_query(CardId(card_id as i32), parameters)
679            .await
680            .map_err(|e| Error::Config(format!("Service error: {}", e)))
681    }
682
683    /// Export card query results in specified format
684    pub async fn export_card_query(
685        &self,
686        card_id: i64,
687        format: ExportFormat,
688        parameters: Option<Value>,
689    ) -> Result<Vec<u8>> {
690        if !self.is_authenticated() {
691            return Err(Error::Authentication(
692                "Authentication required to export card query".to_string(),
693            ));
694        }
695
696        // Use ServiceManager for layered architecture
697        self.service_manager
698            .card_service()
699            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
700            .export_card_query(CardId(card_id as i32), format, parameters)
701            .await
702            .map_err(|e| Error::Config(format!("Service error: {}", e)))
703    }
704
705    /// Execute a pivot query for a card
706    pub async fn execute_card_pivot_query(
707        &self,
708        card_id: i64,
709        parameters: Option<Value>,
710    ) -> Result<QueryResult> {
711        if !self.is_authenticated() {
712            return Err(Error::Authentication(
713                "Authentication required to execute pivot query".to_string(),
714            ));
715        }
716
717        // Use ServiceManager for layered architecture
718        self.service_manager
719            .card_service()
720            .ok_or_else(|| Error::Config("Card service not available".to_string()))?
721            .execute_card_pivot_query(CardId(card_id as i32), parameters)
722            .await
723            .map_err(|e| Error::Config(format!("Service error: {}", e)))
724    }
725
726    // ==================== Database Metadata Operations ====================
727
728    /// Get database metadata including tables and fields
729    pub async fn get_database_metadata(&self, database_id: MetabaseId) -> Result<DatabaseMetadata> {
730        #[cfg(feature = "cache")]
731        {
732            let cache_key = cache_key("database_metadata", database_id.0);
733            if let Some(metadata) = self.cache.get_metadata::<DatabaseMetadata>(&cache_key) {
734                return Ok(metadata);
735            }
736        }
737
738        // Use ServiceManager for layered architecture
739        let metadata = self
740            .service_manager
741            .database_service()
742            .ok_or_else(|| Error::Config("Database service not available".to_string()))?
743            .get_database_metadata(database_id)
744            .await
745            .map_err(|e| Error::Config(format!("Service error: {}", e)))?;
746
747        #[cfg(feature = "cache")]
748        {
749            let cache_key = cache_key("database_metadata", database_id.0);
750            let _ = self.cache.set_metadata(cache_key, &metadata);
751        }
752
753        Ok(metadata)
754    }
755
756    /// Sync database schema
757    pub async fn sync_database_schema(&self, database_id: MetabaseId) -> Result<SyncResult> {
758        if !self.is_authenticated() {
759            return Err(Error::Authentication(
760                "Authentication required to sync database schema".to_string(),
761            ));
762        }
763
764        #[cfg(feature = "cache")]
765        {
766            let cache_key = cache_key("database_metadata", database_id.0);
767            self.cache.invalidate(&cache_key);
768        }
769
770        // Use ServiceManager for layered architecture
771        self.service_manager
772            .database_service()
773            .ok_or_else(|| Error::Config("Database service not available".to_string()))?
774            .sync_database_schema(database_id)
775            .await
776            .map_err(|e| Error::Config(format!("Service error: {}", e)))
777    }
778
779    /// Get all fields for a database
780    pub async fn get_database_fields(&self, database_id: MetabaseId) -> Result<Vec<Field>> {
781        // Use ServiceManager for layered architecture
782        self.service_manager
783            .database_service()
784            .ok_or_else(|| Error::Config("Database service not available".to_string()))?
785            .get_database_fields(database_id)
786            .await
787            .map_err(|e| Error::Config(format!("Service error: {}", e)))
788    }
789
790    /// Get all schemas for a database
791    pub async fn get_database_schemas(&self, database_id: MetabaseId) -> Result<Vec<String>> {
792        // Use ServiceManager for layered architecture
793        self.service_manager
794            .database_service()
795            .ok_or_else(|| Error::Config("Database service not available".to_string()))?
796            .get_database_schemas(database_id)
797            .await
798            .map_err(|e| Error::Config(format!("Service error: {}", e)))
799    }
800
801    // ==================== Dataset Operations ====================
802
803    /// Execute a dataset query with advanced options
804    pub async fn execute_dataset_query(&self, query: Value) -> Result<QueryResult> {
805        if !self.is_authenticated() {
806            return Err(Error::Authentication(
807                "Authentication required to execute dataset query".to_string(),
808            ));
809        }
810
811        // Use ServiceManager for layered architecture
812        self.service_manager
813            .query_service()
814            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
815            .execute_raw_query(query)
816            .await
817            .map_err(|e| Error::Config(format!("Service error: {}", e)))
818    }
819
820    /// Execute a native query through the dataset endpoint
821    pub async fn execute_dataset_native(&self, query: Value) -> Result<QueryResult> {
822        if !self.is_authenticated() {
823            return Err(Error::Authentication(
824                "Authentication required to execute native dataset query".to_string(),
825            ));
826        }
827
828        // Use ServiceManager for layered architecture
829        self.service_manager
830            .query_service()
831            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
832            .execute_raw_query(query)
833            .await
834            .map_err(|e| Error::Config(format!("Service error: {}", e)))
835    }
836
837    /// Execute a pivot dataset query
838    pub async fn execute_dataset_pivot(&self, query: Value) -> Result<QueryResult> {
839        if !self.is_authenticated() {
840            return Err(Error::Authentication(
841                "Authentication required to execute pivot dataset query".to_string(),
842            ));
843        }
844
845        // Use ServiceManager for layered architecture
846        self.service_manager
847            .query_service()
848            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
849            .execute_pivot_query(query)
850            .await
851            .map_err(|e| Error::Config(format!("Service error: {}", e)))
852    }
853
854    /// Export dataset query results
855    pub async fn export_dataset(&self, format: ExportFormat, query: Value) -> Result<Vec<u8>> {
856        if !self.is_authenticated() {
857            return Err(Error::Authentication(
858                "Authentication required to export dataset".to_string(),
859            ));
860        }
861
862        // Use ServiceManager for layered architecture
863        self.service_manager
864            .query_service()
865            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
866            .export_query(format.as_str(), query)
867            .await
868            .map_err(|e| Error::Config(format!("Service error: {}", e)))
869    }
870
871    // ==================== MBQL Query Operations ====================
872
873    /// Execute an MBQL query
874    #[cfg(feature = "query-builder")]
875    pub async fn execute_mbql_query(
876        &self,
877        database_id: MetabaseId,
878        query: MbqlQuery,
879    ) -> Result<QueryResult> {
880        if !self.is_authenticated() {
881            return Err(Error::Authentication(
882                "Authentication required to execute MBQL query".to_string(),
883            ));
884        }
885
886        let dataset_query = query.to_dataset_query(database_id);
887
888        // Use ServiceManager for layered architecture
889        self.service_manager
890            .query_service()
891            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
892            .execute_dataset_query(dataset_query)
893            .await
894            .map_err(|e| Error::Config(format!("Service error: {}", e)))
895    }
896
897    /// Export MBQL query results in specified format
898    #[cfg(feature = "query-builder")]
899    pub async fn export_mbql_query(
900        &self,
901        database_id: MetabaseId,
902        query: MbqlQuery,
903        format: ExportFormat,
904    ) -> Result<Vec<u8>> {
905        if !self.is_authenticated() {
906            return Err(Error::Authentication(
907                "Authentication required to export MBQL query".to_string(),
908            ));
909        }
910
911        let dataset_query = query.to_dataset_query(database_id);
912
913        // Use ServiceManager for layered architecture
914        // Convert dataset query to JSON Value for export
915        let query_value = serde_json::to_value(&dataset_query)
916            .map_err(|e| Error::Serialization(e.to_string()))?;
917
918        self.service_manager
919            .query_service()
920            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
921            .export_query(format.as_str(), query_value)
922            .await
923            .map_err(|e| Error::Config(format!("Service error: {}", e)))
924    }
925
926    // ==================== SQL Convenience Methods ====================
927
928    /// Execute a simple SQL query
929    pub async fn execute_sql(&self, database_id: MetabaseId, sql: &str) -> Result<QueryResult> {
930        let native_query = NativeQuery::new(sql);
931        self.execute_native_query(database_id, native_query).await
932    }
933
934    /// Execute a parameterized SQL query
935    pub async fn execute_sql_with_params(
936        &self,
937        database_id: MetabaseId,
938        sql: &str,
939        params: HashMap<String, Value>,
940    ) -> Result<QueryResult> {
941        let mut native_query = NativeQuery::new(sql);
942        for (name, value) in params {
943            native_query = native_query.with_param(&name, value);
944        }
945        self.execute_native_query(database_id, native_query).await
946    }
947
948    /// Export SQL query results in specified format
949    pub async fn export_sql_query(
950        &self,
951        database_id: MetabaseId,
952        sql: &str,
953        format: ExportFormat,
954    ) -> Result<Vec<u8>> {
955        if !self.is_authenticated() {
956            return Err(Error::Authentication(
957                "Authentication required to export SQL query".to_string(),
958            ));
959        }
960
961        let native_query = NativeQuery::new(sql);
962        let request = json!({
963            "database": database_id.0,
964            "type": "native",
965            "native": {
966                "query": native_query.query,
967                "template-tags": native_query.template_tags
968            }
969        });
970
971        // Use ServiceManager for layered architecture
972        self.service_manager
973            .query_service()
974            .ok_or_else(|| Error::Config("Query service not available".to_string()))?
975            .export_query(format.as_str(), request)
976            .await
977            .map_err(|e| Error::Config(format!("Service error: {}", e)))
978    }
979}