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