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>;