1use crate::compiler::ir::{JoinType, QueryBuilderFn};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum DimType {
5 String,
6 Int,
7 Float,
8 DateTime,
9 Bool,
10}
11
12#[derive(Debug, Clone)]
13pub struct Dimension {
14 pub graphql_name: String,
15 pub column: String,
16 pub dim_type: DimType,
17 pub description: Option<String>,
18}
19
20#[derive(Debug, Clone)]
21pub enum DimensionNode {
22 Leaf(Dimension),
23 Group {
24 graphql_name: String,
25 description: Option<String>,
26 children: Vec<DimensionNode>,
27 },
28}
29
30#[derive(Debug, Clone)]
34pub struct SelectorDef {
35 pub graphql_name: String,
36 pub column: String,
37 pub dim_type: DimType,
38}
39
40pub fn selector(graphql_name: &str, column: &str, dim_type: DimType) -> SelectorDef {
41 SelectorDef {
42 graphql_name: graphql_name.to_string(),
43 column: column.to_string(),
44 dim_type,
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct MetricDef {
51 pub name: String,
52 pub expression_template: Option<String>,
56 pub return_type: DimType,
57 pub description: Option<String>,
58 pub supports_where: bool,
60}
61
62impl MetricDef {
63 pub fn standard(name: &str) -> Self {
64 Self {
65 name: name.to_string(),
66 expression_template: None,
67 return_type: DimType::Float,
68 description: None,
69 supports_where: true,
70 }
71 }
72
73 pub fn custom(name: &str, expression: &str) -> Self {
74 Self {
75 name: name.to_string(),
76 expression_template: Some(expression.to_string()),
77 return_type: DimType::Float,
78 description: None,
79 supports_where: false,
80 }
81 }
82
83 pub fn with_description(mut self, desc: &str) -> Self {
84 self.description = Some(desc.to_string());
85 self
86 }
87
88 pub fn with_return_type(mut self, rt: DimType) -> Self {
89 self.return_type = rt;
90 self
91 }
92}
93
94pub fn standard_metrics(names: &[&str]) -> Vec<MetricDef> {
96 names.iter().map(|n| MetricDef::standard(n)).collect()
97}
98
99#[derive(Debug, Clone)]
102pub struct TableRoute {
103 pub schema: String,
104 pub table_pattern: String,
105 pub available_columns: Vec<String>,
107 pub priority: u32,
109}
110
111#[derive(Debug, Clone)]
113pub struct JoinDef {
114 pub field_name: String,
116 pub target_cube: String,
118 pub conditions: Vec<(String, String)>,
120 pub description: Option<String>,
121 pub join_type: JoinType,
123}
124
125pub fn join_def(field_name: &str, target_cube: &str, conditions: &[(&str, &str)]) -> JoinDef {
126 JoinDef {
127 field_name: field_name.to_string(),
128 target_cube: target_cube.to_string(),
129 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
130 description: None,
131 join_type: JoinType::Left,
132 }
133}
134
135pub fn join_def_desc(field_name: &str, target_cube: &str, conditions: &[(&str, &str)], desc: &str) -> JoinDef {
136 JoinDef {
137 field_name: field_name.to_string(),
138 target_cube: target_cube.to_string(),
139 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
140 description: Some(desc.to_string()),
141 join_type: JoinType::Left,
142 }
143}
144
145pub fn join_def_typed(
146 field_name: &str, target_cube: &str,
147 conditions: &[(&str, &str)],
148 join_type: JoinType,
149) -> JoinDef {
150 JoinDef {
151 field_name: field_name.to_string(),
152 target_cube: target_cube.to_string(),
153 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
154 description: None,
155 join_type,
156 }
157}
158
159#[derive(Debug, Clone)]
160pub struct CubeDefinition {
161 pub name: String,
162 pub schema: String,
163 pub table_pattern: String,
168 pub chain_column: Option<String>,
172 pub dimensions: Vec<DimensionNode>,
173 pub metrics: Vec<MetricDef>,
174 pub selectors: Vec<SelectorDef>,
175 pub default_filters: Vec<(String, String)>,
176 pub default_limit: u32,
177 pub max_limit: u32,
178 pub use_final: bool,
180 pub description: String,
182 pub joins: Vec<JoinDef>,
184 pub table_routes: Vec<TableRoute>,
187 pub custom_query_builder: Option<QueryBuilderFn>,
190 pub from_subquery: Option<String>,
194}
195
196impl CubeDefinition {
197 pub fn table_for_chain(&self, chain: &str) -> String {
198 self.table_pattern.replace("{chain}", chain)
199 }
200
201 pub fn qualified_table(&self, chain: &str) -> String {
202 format!("{}.{}", self.schema, self.table_for_chain(chain))
203 }
204
205 pub fn resolve_table(&self, chain: &str, requested_columns: &[String]) -> (String, String) {
208 if self.table_routes.is_empty() {
209 return (self.schema.clone(), self.table_for_chain(chain));
210 }
211
212 let mut candidates: Vec<&TableRoute> = self.table_routes.iter()
213 .filter(|r| {
214 r.available_columns.is_empty()
215 || requested_columns.iter().all(|c| r.available_columns.contains(c))
216 })
217 .collect();
218
219 candidates.sort_by_key(|r| r.priority);
220
221 if let Some(best) = candidates.first() {
222 (best.schema.clone(), best.table_pattern.replace("{chain}", chain))
223 } else {
224 (self.schema.clone(), self.table_for_chain(chain))
225 }
226 }
227
228 pub fn flat_dimensions(&self) -> Vec<(String, Dimension)> {
229 let mut out = Vec::new();
230 for node in &self.dimensions {
231 collect_leaves(node, "", &mut out);
232 }
233 out
234 }
235
236 pub fn has_metric(&self, name: &str) -> bool {
238 self.metrics.iter().any(|m| m.name == name)
239 }
240
241 pub fn find_metric(&self, name: &str) -> Option<&MetricDef> {
243 self.metrics.iter().find(|m| m.name == name)
244 }
245}
246
247fn collect_leaves(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, Dimension)>) {
248 match node {
249 DimensionNode::Leaf(dim) => {
250 let path = if prefix.is_empty() {
251 dim.graphql_name.clone()
252 } else {
253 format!("{}_{}", prefix, dim.graphql_name)
254 };
255 out.push((path, dim.clone()));
256 }
257 DimensionNode::Group { graphql_name, children, .. } => {
258 let new_prefix = if prefix.is_empty() {
259 graphql_name.clone()
260 } else {
261 format!("{prefix}_{graphql_name}")
262 };
263 for child in children {
264 collect_leaves(child, &new_prefix, out);
265 }
266 }
267 }
268}
269
270pub struct CubeBuilder {
275 def: CubeDefinition,
276}
277
278impl CubeBuilder {
279 pub fn new(name: &str) -> Self {
280 Self {
281 def: CubeDefinition {
282 name: name.to_string(),
283 schema: String::new(),
284 table_pattern: String::new(),
285 chain_column: None,
286 dimensions: Vec::new(),
287 metrics: Vec::new(),
288 selectors: Vec::new(),
289 default_filters: Vec::new(),
290 default_limit: 25,
291 max_limit: 10000,
292 use_final: false,
293 description: String::new(),
294 joins: Vec::new(),
295 table_routes: Vec::new(),
296 custom_query_builder: None,
297 from_subquery: None,
298 },
299 }
300 }
301
302 pub fn schema(mut self, schema: &str) -> Self {
303 self.def.schema = schema.to_string();
304 self
305 }
306
307 pub fn table(mut self, pattern: &str) -> Self {
308 self.def.table_pattern = pattern.to_string();
309 self
310 }
311
312 pub fn chain_column(mut self, column: &str) -> Self {
313 self.def.chain_column = Some(column.to_string());
314 self
315 }
316
317 pub fn dimension(mut self, node: DimensionNode) -> Self {
318 self.def.dimensions.push(node);
319 self
320 }
321
322 pub fn metric(mut self, name: &str) -> Self {
324 self.def.metrics.push(MetricDef::standard(name));
325 self
326 }
327
328 pub fn metrics(mut self, names: &[&str]) -> Self {
330 self.def.metrics.extend(names.iter().map(|s| MetricDef::standard(s)));
331 self
332 }
333
334 pub fn custom_metric(mut self, def: MetricDef) -> Self {
336 self.def.metrics.push(def);
337 self
338 }
339
340 pub fn selector(mut self, sel: SelectorDef) -> Self {
341 self.def.selectors.push(sel);
342 self
343 }
344
345 pub fn default_filter(mut self, column: &str, value: &str) -> Self {
346 self.def.default_filters.push((column.to_string(), value.to_string()));
347 self
348 }
349
350 pub fn default_limit(mut self, limit: u32) -> Self {
351 self.def.default_limit = limit;
352 self
353 }
354
355 pub fn max_limit(mut self, limit: u32) -> Self {
356 self.def.max_limit = limit;
357 self
358 }
359
360 pub fn use_final(mut self, val: bool) -> Self {
361 self.def.use_final = val;
362 self
363 }
364
365 pub fn description(mut self, desc: &str) -> Self {
366 self.def.description = desc.to_string();
367 self
368 }
369
370 pub fn join(mut self, j: JoinDef) -> Self {
371 self.def.joins.push(j);
372 self
373 }
374
375 pub fn joins(mut self, js: Vec<JoinDef>) -> Self {
376 self.def.joins.extend(js);
377 self
378 }
379
380 pub fn table_route(mut self, route: TableRoute) -> Self {
381 self.def.table_routes.push(route);
382 self
383 }
384
385 pub fn custom_query_builder(mut self, builder: QueryBuilderFn) -> Self {
386 self.def.custom_query_builder = Some(builder);
387 self
388 }
389
390 pub fn from_subquery(mut self, subquery_sql: &str) -> Self {
391 self.def.from_subquery = Some(subquery_sql.to_string());
392 self
393 }
394
395 pub fn build(self) -> CubeDefinition {
396 self.def
397 }
398}
399
400pub fn dim(graphql_name: &str, column: &str, dim_type: DimType) -> DimensionNode {
405 DimensionNode::Leaf(Dimension {
406 graphql_name: graphql_name.to_string(),
407 column: column.to_string(),
408 dim_type,
409 description: None,
410 })
411}
412
413pub fn dim_desc(graphql_name: &str, column: &str, dim_type: DimType, desc: &str) -> DimensionNode {
414 DimensionNode::Leaf(Dimension {
415 graphql_name: graphql_name.to_string(),
416 column: column.to_string(),
417 dim_type,
418 description: Some(desc.to_string()),
419 })
420}
421
422pub fn dim_group(graphql_name: &str, children: Vec<DimensionNode>) -> DimensionNode {
423 DimensionNode::Group {
424 graphql_name: graphql_name.to_string(),
425 description: None,
426 children,
427 }
428}
429
430pub fn dim_group_desc(graphql_name: &str, desc: &str, children: Vec<DimensionNode>) -> DimensionNode {
431 DimensionNode::Group {
432 graphql_name: graphql_name.to_string(),
433 description: Some(desc.to_string()),
434 children,
435 }
436}