1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Database introspection trait for querying table metadata.
use fraiseql_error::Result;
use crate::DatabaseType;
/// Kind of database relation (table or view).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RelationKind {
/// A base table.
Table,
/// A view.
View,
}
/// Metadata about a database relation (table or view).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelationInfo {
/// Schema name (e.g. "public", "main", "dbo").
pub schema: String,
/// Relation name.
pub name: String,
/// Whether the relation is a table or view.
pub kind: RelationKind,
}
/// Database introspection trait for querying table metadata.
#[allow(async_fn_in_trait)] // Reason: trait is used with concrete types only, not dyn Trait
pub trait DatabaseIntrospector: Send + Sync {
/// List all fact tables in the database (tables starting with "tf_").
///
/// Returns: Vec of table names matching the tf_* pattern
async fn list_fact_tables(&self) -> Result<Vec<String>>;
/// Query column information for a table.
///
/// Returns: Vec of (column_name, data_type, is_nullable)
async fn get_columns(&self, table_name: &str) -> Result<Vec<(String, String, bool)>>;
/// Query indexes for a table.
///
/// Returns: Vec of column names that have indexes
async fn get_indexed_columns(&self, table_name: &str) -> Result<Vec<String>>;
/// Get database type (for SQL type parsing).
fn database_type(&self) -> DatabaseType;
/// Get sample JSONB data from a column to extract dimension paths.
///
/// Returns: Sample JSON value from the column, or None if no data exists.
async fn get_sample_jsonb(
&self,
_table_name: &str,
_column_name: &str,
) -> Result<Option<serde_json::Value>> {
Ok(None)
}
/// List all relations (tables and views) in the database.
///
/// Returns: Vec of relation metadata. Default implementation returns an empty list.
async fn list_relations(&self) -> Result<Vec<RelationInfo>> {
Ok(Vec::new())
}
/// Get sample JSON rows from a column for schema inference and the L3
/// JSONB-key drift check (#384).
///
/// Returns: Vec of parsed JSON values from the column.
///
/// Deliberately has **no default implementation**: a default returning an
/// empty Vec made "cannot sample" indistinguishable from "table is empty",
/// which silently blinded the L3 drift check for every connector that
/// forgot to override it (the `PostgresIntrospector` did, so the check had
/// never sampled a row). Implementors that genuinely cannot sample must
/// return an error, not fabricate an empty result.
async fn get_sample_json_rows(
&self,
table_name: &str,
column_name: &str,
limit: usize,
) -> Result<Vec<serde_json::Value>>;
/// Probe whether a callable SQL **function** named `name` exists — in `schema`
/// when given, else resolved against the connection `search_path`.
///
/// Used to validate that a mutation's `sql_source` (a *function*, not a
/// relation) is backed. Resolved verbatim / case-sensitively, mirroring how the
/// runtime and `pg_catalog::resolve_functions` match function names.
///
/// `None` ⇒ this connector cannot probe functions (e.g. a non-Postgres
/// dialect) and the caller should skip the function-existence check.
///
/// # Errors
///
/// Returns an error if the catalog query fails.
async fn function_exists(&self, _schema: Option<&str>, _name: &str) -> Result<Option<bool>> {
Ok(None)
}
/// Probe whether a **schema-qualified** relation exists, resolved
/// case-sensitively / verbatim — the same way the runtime resolves a qualified
/// `sql_source` (`quote_postgres_identifier` + `to_regclass`), so a mixed-case
/// relation in an off-`search_path` schema is not falsely reported missing.
///
/// `None` ⇒ this connector cannot probe qualified relations directly (e.g. a
/// non-Postgres dialect); the caller falls back to the relation-map lookup.
///
/// # Errors
///
/// Returns an error if the catalog query fails.
async fn qualified_relation_exists(&self, _schema: &str, _name: &str) -> Result<Option<bool>> {
Ok(None)
}
/// The SQL body of `relation`, when it is a view (#1303).
///
/// Read to answer one question the catalog cannot: does this view already
/// order itself? A view carrying its own `ORDER BY` is the compiler's *own*
/// documented remedy for `order_by = false`, and imposing a default page
/// ordering on top of it replaces the author's order with another one — the
/// single case in which #1303's fix is a regression. Seeing the body is what
/// turns that from a silent break into a compile-time diagnostic.
///
/// `None` ⇒ this connector cannot read view bodies, or `relation` is a table
/// or does not exist. A caller must treat `None` as *unknown*, never as "does
/// not order itself".
///
/// # Errors
///
/// Returns an error if the catalog query fails.
async fn get_view_definition(&self, _relation: &str) -> Result<Option<String>> {
Ok(None)
}
}