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}