Skip to main content

fraiseql_db/traits/
adapter_types.rs

1//! Supporting types for the `DatabaseAdapter` trait family.
2//!
3//! Extracted from the main `traits` module to keep the trait definition file
4//! focused on method signatures.
5
6use std::sync::Arc;
7
8use super::DatabaseAdapter;
9use crate::{
10    types::{
11        DatabaseType, JsonbValue,
12        sql_hints::{OrderByClause, SqlProjectionHint},
13    },
14    where_clause::WhereClause,
15};
16
17/// Result from a relay pagination query, containing rows and an optional total count.
18#[derive(Debug, Clone)]
19#[non_exhaustive]
20pub struct RelayPageResult {
21    /// The page of JSONB rows (already trimmed to the requested page size).
22    pub rows:        Vec<JsonbValue>,
23    /// Total count of matching rows (only populated when requested via `include_total_count`).
24    pub total_count: Option<u64>,
25}
26
27impl RelayPageResult {
28    /// Creates a new `RelayPageResult`.
29    #[must_use]
30    pub const fn new(rows: Vec<JsonbValue>, total_count: Option<u64>) -> Self {
31        Self { rows, total_count }
32    }
33
34    /// Returns a reference to the page of JSONB rows.
35    #[must_use]
36    pub fn rows(&self) -> &[JsonbValue] {
37        &self.rows
38    }
39
40    /// Consumes the result and returns the rows.
41    #[must_use]
42    pub fn into_rows(self) -> Vec<JsonbValue> {
43        self.rows
44    }
45
46    /// Returns the total count of matching rows, if requested.
47    #[must_use]
48    pub const fn total_count(&self) -> Option<u64> {
49        self.total_count
50    }
51}
52
53/// Database capabilities and feature support.
54///
55/// Describes what features a database backend supports, allowing the runtime
56/// to adapt behavior based on database limitations.
57#[derive(Debug, Clone, Copy)]
58pub struct DatabaseCapabilities {
59    /// Database type.
60    pub database_type: DatabaseType,
61
62    /// Supports locale-specific collations.
63    pub supports_locale_collation: bool,
64
65    /// Requires custom collation registration.
66    pub requires_custom_collation: bool,
67
68    /// Recommended collation provider.
69    pub recommended_collation: Option<&'static str>,
70}
71
72impl DatabaseCapabilities {
73    /// Create capabilities from database type.
74    #[must_use]
75    pub const fn from_database_type(db_type: DatabaseType) -> Self {
76        match db_type {
77            DatabaseType::PostgreSQL => Self {
78                database_type:             db_type,
79                supports_locale_collation: true,
80                requires_custom_collation: false,
81                recommended_collation:     Some("icu"),
82            },
83            DatabaseType::MySQL => Self {
84                database_type:             db_type,
85                supports_locale_collation: false,
86                requires_custom_collation: false,
87                recommended_collation:     Some("utf8mb4_unicode_ci"),
88            },
89            DatabaseType::SQLite => Self {
90                database_type:             db_type,
91                supports_locale_collation: false,
92                requires_custom_collation: true,
93                recommended_collation:     Some("NOCASE"),
94            },
95            DatabaseType::SQLServer => Self {
96                database_type:             db_type,
97                supports_locale_collation: true,
98                requires_custom_collation: false,
99                recommended_collation:     Some("Latin1_General_100_CI_AI_SC_UTF8"),
100            },
101        }
102    }
103
104    /// Get collation strategy description.
105    #[must_use]
106    pub const fn collation_strategy(&self) -> &'static str {
107        match self.database_type {
108            DatabaseType::PostgreSQL => "ICU collations (locale-specific)",
109            DatabaseType::MySQL => "UTF8MB4 collations (general)",
110            DatabaseType::SQLite => "NOCASE (limited)",
111            DatabaseType::SQLServer => "Language-specific collations",
112        }
113    }
114}
115
116/// Strategy used by an adapter for executing mutations.
117///
118/// Adapters that use stored database functions (PostgreSQL, MySQL, SQL Server) use
119/// `FunctionCall`. Adapters that generate INSERT/UPDATE/DELETE SQL directly (SQLite)
120/// use `DirectSql`.
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122#[non_exhaustive]
123pub enum MutationStrategy {
124    /// Mutations execute via stored database functions (`SELECT * FROM fn_create_user($1, $2)`).
125    FunctionCall,
126    /// Mutations execute via direct SQL (`INSERT INTO ... RETURNING *`).
127    DirectSql,
128}
129
130/// The kind of direct mutation operation.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[non_exhaustive]
133pub enum DirectMutationOp {
134    /// `INSERT INTO ... RETURNING *`
135    Insert,
136    /// `UPDATE ... SET ... WHERE pk = ? RETURNING *`
137    Update,
138    /// `DELETE FROM ... WHERE pk = ? RETURNING *`
139    Delete,
140}
141
142/// Context for a direct SQL mutation (used by `DirectSql` strategy adapters).
143///
144/// All field references are borrowed from the caller to avoid allocation.
145#[derive(Debug)]
146pub struct DirectMutationContext<'a> {
147    /// The mutation operation to perform.
148    pub operation:      DirectMutationOp,
149    /// Target table name (e.g., `"users"`).
150    pub table:          &'a str,
151    /// Client-supplied column names (in bind order).
152    pub columns:        &'a [String],
153    /// All bind values: client values first, then injected values.
154    pub values:         &'a [serde_json::Value],
155    /// Server-injected column names (e.g., RLS tenant columns), appended after client columns.
156    pub inject_columns: &'a [String],
157    /// GraphQL return type name (e.g., `"User"`), used in the mutation response envelope.
158    pub return_type:    &'a str,
159}
160
161/// A typed cursor value for keyset (relay) pagination.
162///
163/// The cursor type is determined at compile time by `QueryDefinition::relay_cursor_type`
164/// and used at runtime to choose the correct SQL comparison and cursor
165/// encoding/decoding path.
166#[derive(Debug, Clone, PartialEq, Eq)]
167#[non_exhaustive]
168pub enum CursorValue {
169    /// BIGINT primary key cursor (default, backward-compatible).
170    Int64(i64),
171    /// UUID cursor — bound as text and cast to `uuid` in SQL.
172    Uuid(String),
173}
174
175/// Parameters for an `execute_with_projection_arc` call (F043).
176///
177/// Consolidates the six positional parameters of the projection-execution path
178/// into a single borrowed struct so adapters and callers cannot reorder them
179/// by mistake. All fields borrow from the caller; the struct is constructed
180/// per-request on the stack and discarded after the call.
181///
182/// # Field ordering
183///
184/// The field order mirrors a SQL `SELECT … FROM view WHERE … ORDER BY … LIMIT
185/// … OFFSET …` clause, top-to-bottom, so that reading the struct mirrors the
186/// query it produces.
187///
188/// Intentionally **not** `#[non_exhaustive]`: the struct is the *call shape*
189/// of the trait method and any field addition is a breaking trait change
190/// regardless. Callers construct it with a struct literal so that omitting a
191/// field is a hard compile error.
192#[derive(Debug, Clone, Copy)]
193pub struct ProjectionRequest<'a> {
194    /// View or table name (e.g. `"v_user"`).
195    pub view:         &'a str,
196    /// Projection hint (`SELECT` shape). `None` falls back to `SELECT data`.
197    pub projection:   Option<&'a SqlProjectionHint>,
198    /// WHERE clause AST. `None` means no filter.
199    pub where_clause: Option<&'a WhereClause>,
200    /// ORDER BY clauses. Empty slice (or `None`) means unordered.
201    pub order_by:     Option<&'a [OrderByClause]>,
202    /// Row limit. `None` means no limit.
203    pub limit:        Option<u32>,
204    /// Row offset. `None` means no offset.
205    pub offset:       Option<u32>,
206}
207
208impl<'a> ProjectionRequest<'a> {
209    /// Construct a `ProjectionRequest` from a view name with no filters,
210    /// pagination or projection. Useful for tests and simple table scans.
211    #[must_use]
212    pub const fn new(view: &'a str) -> Self {
213        Self {
214            view,
215            projection: None,
216            where_clause: None,
217            order_by: None,
218            limit: None,
219            offset: None,
220        }
221    }
222}
223
224/// Type alias for boxed dynamic database adapters.
225///
226/// Used to store database adapters without generic type parameters in collections
227/// or struct fields. The adapter type is determined at runtime.
228///
229/// # Example
230///
231/// ```ignore
232/// let adapter: BoxDatabaseAdapter = Box::new(postgres_adapter);
233/// ```
234pub type BoxDatabaseAdapter = Box<dyn DatabaseAdapter>;
235
236/// Type alias for arc-wrapped dynamic database adapters.
237///
238/// Used for thread-safe, reference-counted storage of adapters in shared state.
239///
240/// # Example
241///
242/// ```ignore
243/// let adapter: ArcDatabaseAdapter = Arc::new(postgres_adapter);
244/// ```
245pub type ArcDatabaseAdapter = Arc<dyn DatabaseAdapter>;