Skip to main content

fraiseql_cli/schema/intermediate/
operations.rs

1//! Query/mutation structs: `IntermediateQuery`, `IntermediateMutation`,
2//! `IntermediateArgument`, `IntermediateAutoParams`, `IntermediateQueryDefaults`.
3
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7use super::types::IntermediateDeprecation;
8
9/// SQL source dispatch configuration in intermediate format.
10///
11/// Specifies that the query's SQL source should be resolved dynamically
12/// based on an enum argument value, either via an explicit mapping or a
13/// template string expanded at compile time.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct IntermediateSqlSourceDispatch {
16    /// The argument name used for dispatch (e.g., "timeInterval").
17    pub argument: String,
18
19    /// Explicit enum-value-to-table mapping.
20    /// Empty when `template` is used (compiler expands the template).
21    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
22    pub mapping: IndexMap<String, String>,
23
24    /// Template string with a `{placeholder}` for the enum value.
25    /// E.g., `"tf_orders_{time_interval}"`.
26    /// Mutually exclusive with a non-empty `mapping` (the compiler rejects both).
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub template: Option<String>,
29}
30
31/// Argument definition in intermediate format
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct IntermediateArgument {
34    /// Argument name
35    pub name: String,
36
37    /// Argument type name
38    ///
39    /// **Language-agnostic**: Uses "type", not "`arg_type`"
40    #[serde(rename = "type")]
41    pub arg_type: String,
42
43    /// Is argument optional?
44    pub nullable: bool,
45
46    /// Default value (JSON)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub default: Option<serde_json::Value>,
49
50    /// Deprecation info (from @deprecated directive)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub deprecated: Option<IntermediateDeprecation>,
53}
54
55/// Query definition in intermediate format
56#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
57pub struct IntermediateQuery {
58    /// Query name (e.g., "users")
59    pub name: String,
60
61    /// Return type name (e.g., "User")
62    pub return_type: String,
63
64    /// Returns a list?
65    #[serde(default)]
66    pub returns_list: bool,
67
68    /// Result is nullable?
69    #[serde(default)]
70    pub nullable: bool,
71
72    /// Query arguments
73    #[serde(default)]
74    pub arguments: Vec<IntermediateArgument>,
75
76    /// Query description (from docstring)
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub description: Option<String>,
79
80    /// SQL source (table/view name)
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub sql_source: Option<String>,
83
84    /// Dynamic SQL source dispatch. Mutually exclusive with `sql_source`.
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub sql_source_dispatch: Option<IntermediateSqlSourceDispatch>,
87
88    /// Auto-generated parameters config
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub auto_params: Option<IntermediateAutoParams>,
91
92    /// Deprecation info (from @deprecated directive)
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub deprecated: Option<IntermediateDeprecation>,
95
96    /// JSONB column name for extracting data (e.g., "data")
97    /// Used for tv_* (denormalized JSONB tables) pattern
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub jsonb_column: Option<String>,
100
101    /// Whether this is a Relay connection query.
102    /// When true, the compiler wraps results in `{ edges: [{ node, cursor }], pageInfo }`
103    /// and generates `first`/`after`/`last`/`before` arguments instead of `limit`/`offset`.
104    /// Requires `returns_list = true` and `sql_source` to be set.
105    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
106    pub relay: bool,
107
108    /// Server-injected parameters: SQL column name → source expression (e.g. `"jwt:org_id"`).
109    /// Not exposed as GraphQL arguments.
110    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
111    pub inject: IndexMap<String, String>,
112
113    /// Per-query result cache TTL in seconds. Overrides the global cache TTL for this query.
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub cache_ttl_seconds: Option<u64>,
116
117    /// Additional database views this query reads beyond the primary `sql_source`.
118    ///
119    /// Used for correct cache invalidation when a query JOINs or reads multiple views.
120    /// Each entry is validated as a safe SQL identifier at schema compile time.
121    #[serde(default, skip_serializing_if = "Vec::is_empty")]
122    pub additional_views: Vec<String>,
123
124    /// Role required to execute this query and see it in introspection.
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub requires_role: Option<String>,
127
128    /// Relay cursor column type: `"uuid"` for UUID PKs, `"int64"` (or absent) for bigint PKs.
129    /// Only meaningful when `relay = true`.
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub relay_cursor_type: Option<String>,
132}
133
134/// Mutation definition in intermediate format
135#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
136pub struct IntermediateMutation {
137    /// Mutation name (e.g., "createUser")
138    pub name: String,
139
140    /// Return type name (e.g., "User")
141    pub return_type: String,
142
143    /// Returns a list?
144    #[serde(default)]
145    pub returns_list: bool,
146
147    /// Result is nullable?
148    #[serde(default)]
149    pub nullable: bool,
150
151    /// Mutation arguments
152    #[serde(default)]
153    pub arguments: Vec<IntermediateArgument>,
154
155    /// Mutation description (from docstring)
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub description: Option<String>,
158
159    /// SQL source (function name)
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub sql_source: Option<String>,
162
163    /// Operation type (CREATE, UPDATE, DELETE, CUSTOM)
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub operation: Option<String>,
166
167    /// Deprecation info (from @deprecated directive)
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub deprecated: Option<IntermediateDeprecation>,
170
171    /// Server-injected parameters: SQL parameter name → source expression (e.g. `"jwt:org_id"`).
172    /// Not exposed as GraphQL arguments.
173    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
174    pub inject: IndexMap<String, String>,
175
176    /// Fact tables whose version counter should be bumped after this mutation succeeds.
177    ///
178    /// Used for correct invalidation of analytic/aggregate cache entries.
179    #[serde(default, skip_serializing_if = "Vec::is_empty")]
180    pub invalidates_fact_tables: Vec<String>,
181
182    /// View names whose cached query results should be invalidated after this
183    /// mutation succeeds.
184    #[serde(default, skip_serializing_if = "Vec::is_empty")]
185    pub invalidates_views: Vec<String>,
186}
187
188/// Auto-params configuration in intermediate format.
189///
190/// Each field is `Option<bool>`: `None` means "not specified — inherit from
191/// `[query_defaults]`"; `Some(v)` means explicitly set by the authoring-language decorator.
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct IntermediateAutoParams {
194    /// Enable automatic limit parameter (None = inherit from query_defaults)
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub limit:        Option<bool>,
197    /// Enable automatic offset parameter (None = inherit from query_defaults)
198    #[serde(default, skip_serializing_if = "Option::is_none")]
199    pub offset:       Option<bool>,
200    /// Enable automatic where clause parameter (None = inherit from query_defaults)
201    #[serde(rename = "where", default, skip_serializing_if = "Option::is_none")]
202    pub where_clause: Option<bool>,
203    /// Enable automatic order_by parameter (None = inherit from query_defaults)
204    #[serde(default, skip_serializing_if = "Option::is_none")]
205    pub order_by:     Option<bool>,
206}
207
208/// Global auto-param defaults for list queries (injected from TOML by the merger).
209///
210/// Never present in `schema.json` — set only at compile time via `[query_defaults]`
211/// in `fraiseql.toml`.
212///
213/// The `Default` implementation returns all-`true`, matching the historical behaviour
214/// when no `[query_defaults]` section is present in TOML.
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216pub struct IntermediateQueryDefaults {
217    /// Default for `where` parameter
218    pub where_clause: bool,
219    /// Default for `order_by` parameter
220    pub order_by:     bool,
221    /// Default for `limit` parameter
222    pub limit:        bool,
223    /// Default for `offset` parameter
224    pub offset:       bool,
225}
226
227impl Default for IntermediateQueryDefaults {
228    fn default() -> Self {
229        Self {
230            where_clause: true,
231            order_by:     true,
232            limit:        true,
233            offset:       true,
234        }
235    }
236}