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