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