1use crate::compiler::ir::{JoinType, QueryBuilderFn};
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5pub enum ChainGroup {
6 Evm,
8 Solana,
10 Trading,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum DimType {
16 String,
17 Int,
18 Float,
19 Decimal,
21 Date,
23 DateTime,
24 Bool,
25}
26
27#[derive(Debug, Clone)]
28pub struct Dimension {
29 pub graphql_name: String,
30 pub column: String,
31 pub dim_type: DimType,
32 pub description: Option<String>,
33}
34
35#[derive(Debug, Clone)]
36pub enum DimensionNode {
37 Leaf(Dimension),
38 Group {
39 graphql_name: String,
40 description: Option<String>,
41 children: Vec<DimensionNode>,
42 },
43 Array {
46 graphql_name: String,
47 description: Option<String>,
48 children: Vec<ArrayFieldDef>,
49 },
50}
51
52#[derive(Debug, Clone)]
54pub struct ArrayFieldDef {
55 pub graphql_name: String,
56 pub column: String,
57 pub field_type: ArrayFieldType,
58 pub description: Option<String>,
59}
60
61#[derive(Debug, Clone)]
63pub enum ArrayFieldType {
64 Scalar(DimType),
65 Union(Vec<UnionVariant>),
66}
67
68#[derive(Debug, Clone)]
70pub struct UnionVariant {
71 pub type_name: String,
73 pub field_name: String,
75 pub source_type: DimType,
77 pub source_type_names: Vec<String>,
80}
81
82#[derive(Debug, Clone)]
86pub struct SelectorDef {
87 pub graphql_name: String,
88 pub column: String,
89 pub dim_type: DimType,
90}
91
92pub fn selector(graphql_name: &str, column: &str, dim_type: DimType) -> SelectorDef {
93 SelectorDef {
94 graphql_name: graphql_name.to_string(),
95 column: column.to_string(),
96 dim_type,
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct MetricDef {
103 pub name: String,
104 pub expression_template: Option<String>,
108 pub return_type: DimType,
109 pub description: Option<String>,
110 pub supports_where: bool,
112}
113
114impl MetricDef {
115 pub fn standard(name: &str) -> Self {
116 Self {
117 name: name.to_string(),
118 expression_template: None,
119 return_type: DimType::Float,
120 description: None,
121 supports_where: true,
122 }
123 }
124
125 pub fn custom(name: &str, expression: &str) -> Self {
126 Self {
127 name: name.to_string(),
128 expression_template: Some(expression.to_string()),
129 return_type: DimType::Float,
130 description: None,
131 supports_where: false,
132 }
133 }
134
135 pub fn with_description(mut self, desc: &str) -> Self {
136 self.description = Some(desc.to_string());
137 self
138 }
139
140 pub fn with_return_type(mut self, rt: DimType) -> Self {
141 self.return_type = rt;
142 self
143 }
144}
145
146pub fn standard_metrics(names: &[&str]) -> Vec<MetricDef> {
148 names.iter().map(|n| MetricDef::standard(n)).collect()
149}
150
151#[derive(Debug, Clone)]
154pub struct TableRoute {
155 pub schema: String,
156 pub table_pattern: String,
157 pub available_columns: Vec<String>,
159 pub priority: u32,
161}
162
163#[derive(Debug, Clone)]
165pub struct JoinDef {
166 pub field_name: String,
168 pub target_cube: String,
170 pub conditions: Vec<(String, String)>,
172 pub description: Option<String>,
173 pub join_type: JoinType,
175}
176
177pub fn join_def(field_name: &str, target_cube: &str, conditions: &[(&str, &str)]) -> JoinDef {
178 JoinDef {
179 field_name: field_name.to_string(),
180 target_cube: target_cube.to_string(),
181 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
182 description: None,
183 join_type: JoinType::Left,
184 }
185}
186
187pub fn join_def_desc(field_name: &str, target_cube: &str, conditions: &[(&str, &str)], desc: &str) -> JoinDef {
188 JoinDef {
189 field_name: field_name.to_string(),
190 target_cube: target_cube.to_string(),
191 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
192 description: Some(desc.to_string()),
193 join_type: JoinType::Left,
194 }
195}
196
197pub fn join_def_typed(
198 field_name: &str, target_cube: &str,
199 conditions: &[(&str, &str)],
200 join_type: JoinType,
201) -> JoinDef {
202 JoinDef {
203 field_name: field_name.to_string(),
204 target_cube: target_cube.to_string(),
205 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
206 description: None,
207 join_type,
208 }
209}
210
211#[derive(Debug, Clone)]
212pub struct CubeDefinition {
213 pub name: String,
214 pub schema: String,
215 pub table_pattern: String,
220 pub chain_column: Option<String>,
224 pub dimensions: Vec<DimensionNode>,
225 pub metrics: Vec<MetricDef>,
226 pub selectors: Vec<SelectorDef>,
227 pub default_filters: Vec<(String, String)>,
228 pub default_limit: u32,
229 pub max_limit: u32,
230 pub use_final: bool,
232 pub description: String,
234 pub joins: Vec<JoinDef>,
236 pub table_routes: Vec<TableRoute>,
239 pub custom_query_builder: Option<QueryBuilderFn>,
242 pub from_subquery: Option<String>,
246 pub chain_groups: Vec<ChainGroup>,
248}
249
250impl CubeDefinition {
251 pub fn table_for_chain(&self, chain: &str) -> String {
252 self.table_pattern.replace("{chain}", chain)
253 }
254
255 pub fn qualified_table(&self, chain: &str) -> String {
256 format!("{}.{}", self.schema, self.table_for_chain(chain))
257 }
258
259 pub fn resolve_table(&self, chain: &str, requested_columns: &[String]) -> (String, String) {
262 if self.table_routes.is_empty() {
263 return (self.schema.clone(), self.table_for_chain(chain));
264 }
265
266 let mut candidates: Vec<&TableRoute> = self.table_routes.iter()
267 .filter(|r| {
268 r.available_columns.is_empty()
269 || (!requested_columns.is_empty()
270 && requested_columns.iter().all(|c| r.available_columns.contains(c)))
271 })
272 .collect();
273
274 candidates.sort_by_key(|r| r.priority);
275
276 if let Some(best) = candidates.first() {
277 (best.schema.clone(), best.table_pattern.replace("{chain}", chain))
278 } else {
279 (self.schema.clone(), self.table_for_chain(chain))
280 }
281 }
282
283 pub fn flat_dimensions(&self) -> Vec<(String, Dimension)> {
284 let mut out = Vec::new();
285 for node in &self.dimensions {
286 collect_leaves(node, "", &mut out);
287 }
288 out
289 }
290
291 pub fn has_metric(&self, name: &str) -> bool {
293 self.metrics.iter().any(|m| m.name == name)
294 }
295
296 pub fn find_metric(&self, name: &str) -> Option<&MetricDef> {
298 self.metrics.iter().find(|m| m.name == name)
299 }
300
301 pub fn array_columns(&self) -> Vec<(String, String)> {
304 let mut out = Vec::new();
305 for node in &self.dimensions {
306 collect_array_columns(node, "", &mut out);
307 }
308 out
309 }
310}
311
312fn collect_array_columns(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, String)>) {
313 match node {
314 DimensionNode::Leaf(_) => {}
315 DimensionNode::Group { graphql_name, children, .. } => {
316 let new_prefix = if prefix.is_empty() {
317 graphql_name.clone()
318 } else {
319 format!("{prefix}_{graphql_name}")
320 };
321 for child in children {
322 collect_array_columns(child, &new_prefix, out);
323 }
324 }
325 DimensionNode::Array { graphql_name, children, .. } => {
326 let arr_prefix = if prefix.is_empty() {
327 graphql_name.clone()
328 } else {
329 format!("{prefix}_{graphql_name}")
330 };
331 for af in children {
332 out.push((
333 format!("{}_{}", arr_prefix, af.graphql_name),
334 af.column.clone(),
335 ));
336 }
337 }
338 }
339}
340
341fn collect_leaves(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, Dimension)>) {
342 match node {
343 DimensionNode::Leaf(dim) => {
344 let path = if prefix.is_empty() {
345 dim.graphql_name.clone()
346 } else {
347 format!("{}_{}", prefix, dim.graphql_name)
348 };
349 out.push((path, dim.clone()));
350 }
351 DimensionNode::Group { graphql_name, children, .. } => {
352 let new_prefix = if prefix.is_empty() {
353 graphql_name.clone()
354 } else {
355 format!("{prefix}_{graphql_name}")
356 };
357 for child in children {
358 collect_leaves(child, &new_prefix, out);
359 }
360 }
361 DimensionNode::Array { .. } => {
362 }
365 }
366}
367
368pub struct CubeBuilder {
373 def: CubeDefinition,
374}
375
376impl CubeBuilder {
377 pub fn new(name: &str) -> Self {
378 Self {
379 def: CubeDefinition {
380 name: name.to_string(),
381 schema: String::new(),
382 table_pattern: String::new(),
383 chain_column: None,
384 dimensions: Vec::new(),
385 metrics: Vec::new(),
386 selectors: Vec::new(),
387 default_filters: Vec::new(),
388 default_limit: 25,
389 max_limit: 10000,
390 use_final: false,
391 description: String::new(),
392 joins: Vec::new(),
393 table_routes: Vec::new(),
394 custom_query_builder: None,
395 from_subquery: None,
396 chain_groups: Vec::new(),
397 },
398 }
399 }
400
401 pub fn schema(mut self, schema: &str) -> Self {
402 self.def.schema = schema.to_string();
403 self
404 }
405
406 pub fn table(mut self, pattern: &str) -> Self {
407 self.def.table_pattern = pattern.to_string();
408 self
409 }
410
411 pub fn chain_column(mut self, column: &str) -> Self {
412 self.def.chain_column = Some(column.to_string());
413 self
414 }
415
416 pub fn dimension(mut self, node: DimensionNode) -> Self {
417 self.def.dimensions.push(node);
418 self
419 }
420
421 pub fn metric(mut self, name: &str) -> Self {
423 self.def.metrics.push(MetricDef::standard(name));
424 self
425 }
426
427 pub fn metrics(mut self, names: &[&str]) -> Self {
429 self.def.metrics.extend(names.iter().map(|s| MetricDef::standard(s)));
430 self
431 }
432
433 pub fn custom_metric(mut self, def: MetricDef) -> Self {
435 self.def.metrics.push(def);
436 self
437 }
438
439 pub fn selector(mut self, sel: SelectorDef) -> Self {
440 self.def.selectors.push(sel);
441 self
442 }
443
444 pub fn default_filter(mut self, column: &str, value: &str) -> Self {
445 self.def.default_filters.push((column.to_string(), value.to_string()));
446 self
447 }
448
449 pub fn default_limit(mut self, limit: u32) -> Self {
450 self.def.default_limit = limit;
451 self
452 }
453
454 pub fn max_limit(mut self, limit: u32) -> Self {
455 self.def.max_limit = limit;
456 self
457 }
458
459 pub fn use_final(mut self, val: bool) -> Self {
460 self.def.use_final = val;
461 self
462 }
463
464 pub fn description(mut self, desc: &str) -> Self {
465 self.def.description = desc.to_string();
466 self
467 }
468
469 pub fn join(mut self, j: JoinDef) -> Self {
470 self.def.joins.push(j);
471 self
472 }
473
474 pub fn joins(mut self, js: Vec<JoinDef>) -> Self {
475 self.def.joins.extend(js);
476 self
477 }
478
479 pub fn table_route(mut self, route: TableRoute) -> Self {
480 self.def.table_routes.push(route);
481 self
482 }
483
484 pub fn custom_query_builder(mut self, builder: QueryBuilderFn) -> Self {
485 self.def.custom_query_builder = Some(builder);
486 self
487 }
488
489 pub fn from_subquery(mut self, subquery_sql: &str) -> Self {
490 self.def.from_subquery = Some(subquery_sql.to_string());
491 self
492 }
493
494 pub fn chain_groups(mut self, groups: Vec<ChainGroup>) -> Self {
495 self.def.chain_groups = groups;
496 self
497 }
498
499 pub fn build(self) -> CubeDefinition {
500 self.def
501 }
502}
503
504pub fn dim(graphql_name: &str, column: &str, dim_type: DimType) -> DimensionNode {
509 DimensionNode::Leaf(Dimension {
510 graphql_name: graphql_name.to_string(),
511 column: column.to_string(),
512 dim_type,
513 description: None,
514 })
515}
516
517pub fn dim_desc(graphql_name: &str, column: &str, dim_type: DimType, desc: &str) -> DimensionNode {
518 DimensionNode::Leaf(Dimension {
519 graphql_name: graphql_name.to_string(),
520 column: column.to_string(),
521 dim_type,
522 description: Some(desc.to_string()),
523 })
524}
525
526pub fn dim_group(graphql_name: &str, children: Vec<DimensionNode>) -> DimensionNode {
527 DimensionNode::Group {
528 graphql_name: graphql_name.to_string(),
529 description: None,
530 children,
531 }
532}
533
534pub fn dim_group_desc(graphql_name: &str, desc: &str, children: Vec<DimensionNode>) -> DimensionNode {
535 DimensionNode::Group {
536 graphql_name: graphql_name.to_string(),
537 description: Some(desc.to_string()),
538 children,
539 }
540}
541
542pub fn dim_array(graphql_name: &str, children: Vec<ArrayFieldDef>) -> DimensionNode {
543 DimensionNode::Array {
544 graphql_name: graphql_name.to_string(),
545 description: None,
546 children,
547 }
548}
549
550pub fn dim_array_desc(graphql_name: &str, desc: &str, children: Vec<ArrayFieldDef>) -> DimensionNode {
551 DimensionNode::Array {
552 graphql_name: graphql_name.to_string(),
553 description: Some(desc.to_string()),
554 children,
555 }
556}
557
558pub fn array_field(graphql_name: &str, column: &str, field_type: ArrayFieldType) -> ArrayFieldDef {
559 ArrayFieldDef {
560 graphql_name: graphql_name.to_string(),
561 column: column.to_string(),
562 field_type,
563 description: None,
564 }
565}
566
567pub fn array_field_desc(graphql_name: &str, column: &str, field_type: ArrayFieldType, desc: &str) -> ArrayFieldDef {
568 ArrayFieldDef {
569 graphql_name: graphql_name.to_string(),
570 column: column.to_string(),
571 field_type,
572 description: Some(desc.to_string()),
573 }
574}
575
576pub fn variant(type_name: &str, field_name: &str, source_type: DimType) -> UnionVariant {
578 UnionVariant {
579 type_name: type_name.to_string(),
580 field_name: field_name.to_string(),
581 source_type,
582 source_type_names: vec![],
583 }
584}
585
586pub fn variant_matching(
590 type_name: &str,
591 field_name: &str,
592 source_type: DimType,
593 source_names: &[&str],
594) -> UnionVariant {
595 UnionVariant {
596 type_name: type_name.to_string(),
597 field_name: field_name.to_string(),
598 source_type,
599 source_type_names: source_names.iter().map(|s| s.to_string()).collect(),
600 }
601}