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