Skip to main content

fraiseql_db/
introspector.rs

1//! Database introspection trait for querying table metadata.
2
3use fraiseql_error::Result;
4
5use crate::DatabaseType;
6
7/// Kind of database relation (table or view).
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum RelationKind {
10    /// A base table.
11    Table,
12    /// A view.
13    View,
14}
15
16/// Metadata about a database relation (table or view).
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct RelationInfo {
19    /// Schema name (e.g. "public", "main", "dbo").
20    pub schema: String,
21    /// Relation name.
22    pub name:   String,
23    /// Whether the relation is a table or view.
24    pub kind:   RelationKind,
25}
26
27/// Database introspection trait for querying table metadata.
28#[allow(async_fn_in_trait)] // Reason: trait is used with concrete types only, not dyn Trait
29pub trait DatabaseIntrospector: Send + Sync {
30    /// List all fact tables in the database (tables starting with "tf_").
31    ///
32    /// Returns: Vec of table names matching the tf_* pattern
33    async fn list_fact_tables(&self) -> Result<Vec<String>>;
34
35    /// Query column information for a table.
36    ///
37    /// Returns: Vec of (column_name, data_type, is_nullable)
38    async fn get_columns(&self, table_name: &str) -> Result<Vec<(String, String, bool)>>;
39
40    /// Query indexes for a table.
41    ///
42    /// Returns: Vec of column names that have indexes
43    async fn get_indexed_columns(&self, table_name: &str) -> Result<Vec<String>>;
44
45    /// Get database type (for SQL type parsing).
46    fn database_type(&self) -> DatabaseType;
47
48    /// Get sample JSONB data from a column to extract dimension paths.
49    ///
50    /// Returns: Sample JSON value from the column, or None if no data exists.
51    async fn get_sample_jsonb(
52        &self,
53        _table_name: &str,
54        _column_name: &str,
55    ) -> Result<Option<serde_json::Value>> {
56        Ok(None)
57    }
58
59    /// List all relations (tables and views) in the database.
60    ///
61    /// Returns: Vec of relation metadata. Default implementation returns an empty list.
62    async fn list_relations(&self) -> Result<Vec<RelationInfo>> {
63        Ok(Vec::new())
64    }
65
66    /// Get sample JSON rows from a column for schema inference.
67    ///
68    /// Returns: Vec of parsed JSON values from the column. Default returns an empty list.
69    async fn get_sample_json_rows(
70        &self,
71        _table_name: &str,
72        _column_name: &str,
73        _limit: usize,
74    ) -> Result<Vec<serde_json::Value>> {
75        Ok(Vec::new())
76    }
77
78    /// Probe whether a callable SQL **function** named `name` exists — in `schema`
79    /// when given, else resolved against the connection `search_path`.
80    ///
81    /// Used to validate that a mutation's `sql_source` (a *function*, not a
82    /// relation) is backed. Resolved verbatim / case-sensitively, mirroring how the
83    /// runtime and `pg_catalog::resolve_functions` match function names.
84    ///
85    /// `None` ⇒ this connector cannot probe functions (e.g. a non-Postgres
86    /// dialect) and the caller should skip the function-existence check.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if the catalog query fails.
91    async fn function_exists(&self, _schema: Option<&str>, _name: &str) -> Result<Option<bool>> {
92        Ok(None)
93    }
94
95    /// Probe whether a **schema-qualified** relation exists, resolved
96    /// case-sensitively / verbatim — the same way the runtime resolves a qualified
97    /// `sql_source` (`quote_postgres_identifier` + `to_regclass`), so a mixed-case
98    /// relation in an off-`search_path` schema is not falsely reported missing.
99    ///
100    /// `None` ⇒ this connector cannot probe qualified relations directly (e.g. a
101    /// non-Postgres dialect); the caller falls back to the relation-map lookup.
102    ///
103    /// # Errors
104    ///
105    /// Returns an error if the catalog query fails.
106    async fn qualified_relation_exists(&self, _schema: &str, _name: &str) -> Result<Option<bool>> {
107        Ok(None)
108    }
109}