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}
74
75#[derive(Debug, Clone)]
79pub struct SelectorDef {
80 pub graphql_name: String,
81 pub column: String,
82 pub dim_type: DimType,
83}
84
85pub fn selector(graphql_name: &str, column: &str, dim_type: DimType) -> SelectorDef {
86 SelectorDef {
87 graphql_name: graphql_name.to_string(),
88 column: column.to_string(),
89 dim_type,
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct MetricDef {
96 pub name: String,
97 pub expression_template: Option<String>,
101 pub return_type: DimType,
102 pub description: Option<String>,
103 pub supports_where: bool,
105}
106
107impl MetricDef {
108 pub fn standard(name: &str) -> Self {
109 Self {
110 name: name.to_string(),
111 expression_template: None,
112 return_type: DimType::Float,
113 description: None,
114 supports_where: true,
115 }
116 }
117
118 pub fn custom(name: &str, expression: &str) -> Self {
119 Self {
120 name: name.to_string(),
121 expression_template: Some(expression.to_string()),
122 return_type: DimType::Float,
123 description: None,
124 supports_where: false,
125 }
126 }
127
128 pub fn with_description(mut self, desc: &str) -> Self {
129 self.description = Some(desc.to_string());
130 self
131 }
132
133 pub fn with_return_type(mut self, rt: DimType) -> Self {
134 self.return_type = rt;
135 self
136 }
137}
138
139pub fn standard_metrics(names: &[&str]) -> Vec<MetricDef> {
141 names.iter().map(|n| MetricDef::standard(n)).collect()
142}
143
144#[derive(Debug, Clone)]
147pub struct TableRoute {
148 pub schema: String,
149 pub table_pattern: String,
150 pub available_columns: Vec<String>,
152 pub priority: u32,
154}
155
156#[derive(Debug, Clone)]
158pub struct JoinDef {
159 pub field_name: String,
161 pub target_cube: String,
163 pub conditions: Vec<(String, String)>,
165 pub description: Option<String>,
166 pub join_type: JoinType,
168}
169
170pub fn join_def(field_name: &str, target_cube: &str, conditions: &[(&str, &str)]) -> JoinDef {
171 JoinDef {
172 field_name: field_name.to_string(),
173 target_cube: target_cube.to_string(),
174 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
175 description: None,
176 join_type: JoinType::Left,
177 }
178}
179
180pub fn join_def_desc(field_name: &str, target_cube: &str, conditions: &[(&str, &str)], desc: &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: Some(desc.to_string()),
186 join_type: JoinType::Left,
187 }
188}
189
190pub fn join_def_typed(
191 field_name: &str, target_cube: &str,
192 conditions: &[(&str, &str)],
193 join_type: JoinType,
194) -> JoinDef {
195 JoinDef {
196 field_name: field_name.to_string(),
197 target_cube: target_cube.to_string(),
198 conditions: conditions.iter().map(|(l, r)| (l.to_string(), r.to_string())).collect(),
199 description: None,
200 join_type,
201 }
202}
203
204#[derive(Debug, Clone)]
205pub struct CubeDefinition {
206 pub name: String,
207 pub schema: String,
208 pub table_pattern: String,
213 pub chain_column: Option<String>,
217 pub dimensions: Vec<DimensionNode>,
218 pub metrics: Vec<MetricDef>,
219 pub selectors: Vec<SelectorDef>,
220 pub default_filters: Vec<(String, String)>,
221 pub default_limit: u32,
222 pub max_limit: u32,
223 pub use_final: bool,
225 pub description: String,
227 pub joins: Vec<JoinDef>,
229 pub table_routes: Vec<TableRoute>,
232 pub custom_query_builder: Option<QueryBuilderFn>,
235 pub from_subquery: Option<String>,
239 pub chain_groups: Vec<ChainGroup>,
241}
242
243impl CubeDefinition {
244 pub fn table_for_chain(&self, chain: &str) -> String {
245 self.table_pattern.replace("{chain}", chain)
246 }
247
248 pub fn qualified_table(&self, chain: &str) -> String {
249 format!("{}.{}", self.schema, self.table_for_chain(chain))
250 }
251
252 pub fn resolve_table(&self, chain: &str, requested_columns: &[String]) -> (String, String) {
255 if self.table_routes.is_empty() {
256 return (self.schema.clone(), self.table_for_chain(chain));
257 }
258
259 let mut candidates: Vec<&TableRoute> = self.table_routes.iter()
260 .filter(|r| {
261 r.available_columns.is_empty()
262 || (!requested_columns.is_empty()
263 && requested_columns.iter().all(|c| r.available_columns.contains(c)))
264 })
265 .collect();
266
267 candidates.sort_by_key(|r| r.priority);
268
269 if let Some(best) = candidates.first() {
270 (best.schema.clone(), best.table_pattern.replace("{chain}", chain))
271 } else {
272 (self.schema.clone(), self.table_for_chain(chain))
273 }
274 }
275
276 pub fn flat_dimensions(&self) -> Vec<(String, Dimension)> {
277 let mut out = Vec::new();
278 for node in &self.dimensions {
279 collect_leaves(node, "", &mut out);
280 }
281 out
282 }
283
284 pub fn has_metric(&self, name: &str) -> bool {
286 self.metrics.iter().any(|m| m.name == name)
287 }
288
289 pub fn find_metric(&self, name: &str) -> Option<&MetricDef> {
291 self.metrics.iter().find(|m| m.name == name)
292 }
293}
294
295fn collect_leaves(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, Dimension)>) {
296 match node {
297 DimensionNode::Leaf(dim) => {
298 let path = if prefix.is_empty() {
299 dim.graphql_name.clone()
300 } else {
301 format!("{}_{}", prefix, dim.graphql_name)
302 };
303 out.push((path, dim.clone()));
304 }
305 DimensionNode::Group { graphql_name, children, .. } => {
306 let new_prefix = if prefix.is_empty() {
307 graphql_name.clone()
308 } else {
309 format!("{prefix}_{graphql_name}")
310 };
311 for child in children {
312 collect_leaves(child, &new_prefix, out);
313 }
314 }
315 DimensionNode::Array { .. } => {
316 }
319 }
320}
321
322pub struct CubeBuilder {
327 def: CubeDefinition,
328}
329
330impl CubeBuilder {
331 pub fn new(name: &str) -> Self {
332 Self {
333 def: CubeDefinition {
334 name: name.to_string(),
335 schema: String::new(),
336 table_pattern: String::new(),
337 chain_column: None,
338 dimensions: Vec::new(),
339 metrics: Vec::new(),
340 selectors: Vec::new(),
341 default_filters: Vec::new(),
342 default_limit: 25,
343 max_limit: 10000,
344 use_final: false,
345 description: String::new(),
346 joins: Vec::new(),
347 table_routes: Vec::new(),
348 custom_query_builder: None,
349 from_subquery: None,
350 chain_groups: Vec::new(),
351 },
352 }
353 }
354
355 pub fn schema(mut self, schema: &str) -> Self {
356 self.def.schema = schema.to_string();
357 self
358 }
359
360 pub fn table(mut self, pattern: &str) -> Self {
361 self.def.table_pattern = pattern.to_string();
362 self
363 }
364
365 pub fn chain_column(mut self, column: &str) -> Self {
366 self.def.chain_column = Some(column.to_string());
367 self
368 }
369
370 pub fn dimension(mut self, node: DimensionNode) -> Self {
371 self.def.dimensions.push(node);
372 self
373 }
374
375 pub fn metric(mut self, name: &str) -> Self {
377 self.def.metrics.push(MetricDef::standard(name));
378 self
379 }
380
381 pub fn metrics(mut self, names: &[&str]) -> Self {
383 self.def.metrics.extend(names.iter().map(|s| MetricDef::standard(s)));
384 self
385 }
386
387 pub fn custom_metric(mut self, def: MetricDef) -> Self {
389 self.def.metrics.push(def);
390 self
391 }
392
393 pub fn selector(mut self, sel: SelectorDef) -> Self {
394 self.def.selectors.push(sel);
395 self
396 }
397
398 pub fn default_filter(mut self, column: &str, value: &str) -> Self {
399 self.def.default_filters.push((column.to_string(), value.to_string()));
400 self
401 }
402
403 pub fn default_limit(mut self, limit: u32) -> Self {
404 self.def.default_limit = limit;
405 self
406 }
407
408 pub fn max_limit(mut self, limit: u32) -> Self {
409 self.def.max_limit = limit;
410 self
411 }
412
413 pub fn use_final(mut self, val: bool) -> Self {
414 self.def.use_final = val;
415 self
416 }
417
418 pub fn description(mut self, desc: &str) -> Self {
419 self.def.description = desc.to_string();
420 self
421 }
422
423 pub fn join(mut self, j: JoinDef) -> Self {
424 self.def.joins.push(j);
425 self
426 }
427
428 pub fn joins(mut self, js: Vec<JoinDef>) -> Self {
429 self.def.joins.extend(js);
430 self
431 }
432
433 pub fn table_route(mut self, route: TableRoute) -> Self {
434 self.def.table_routes.push(route);
435 self
436 }
437
438 pub fn custom_query_builder(mut self, builder: QueryBuilderFn) -> Self {
439 self.def.custom_query_builder = Some(builder);
440 self
441 }
442
443 pub fn from_subquery(mut self, subquery_sql: &str) -> Self {
444 self.def.from_subquery = Some(subquery_sql.to_string());
445 self
446 }
447
448 pub fn chain_groups(mut self, groups: Vec<ChainGroup>) -> Self {
449 self.def.chain_groups = groups;
450 self
451 }
452
453 pub fn build(self) -> CubeDefinition {
454 self.def
455 }
456}
457
458pub fn dim(graphql_name: &str, column: &str, dim_type: DimType) -> DimensionNode {
463 DimensionNode::Leaf(Dimension {
464 graphql_name: graphql_name.to_string(),
465 column: column.to_string(),
466 dim_type,
467 description: None,
468 })
469}
470
471pub fn dim_desc(graphql_name: &str, column: &str, dim_type: DimType, desc: &str) -> DimensionNode {
472 DimensionNode::Leaf(Dimension {
473 graphql_name: graphql_name.to_string(),
474 column: column.to_string(),
475 dim_type,
476 description: Some(desc.to_string()),
477 })
478}
479
480pub fn dim_group(graphql_name: &str, children: Vec<DimensionNode>) -> DimensionNode {
481 DimensionNode::Group {
482 graphql_name: graphql_name.to_string(),
483 description: None,
484 children,
485 }
486}
487
488pub fn dim_group_desc(graphql_name: &str, desc: &str, children: Vec<DimensionNode>) -> DimensionNode {
489 DimensionNode::Group {
490 graphql_name: graphql_name.to_string(),
491 description: Some(desc.to_string()),
492 children,
493 }
494}
495
496pub fn dim_array(graphql_name: &str, children: Vec<ArrayFieldDef>) -> DimensionNode {
497 DimensionNode::Array {
498 graphql_name: graphql_name.to_string(),
499 description: None,
500 children,
501 }
502}
503
504pub fn dim_array_desc(graphql_name: &str, desc: &str, children: Vec<ArrayFieldDef>) -> DimensionNode {
505 DimensionNode::Array {
506 graphql_name: graphql_name.to_string(),
507 description: Some(desc.to_string()),
508 children,
509 }
510}
511
512pub fn array_field(graphql_name: &str, column: &str, field_type: ArrayFieldType) -> ArrayFieldDef {
513 ArrayFieldDef {
514 graphql_name: graphql_name.to_string(),
515 column: column.to_string(),
516 field_type,
517 description: None,
518 }
519}
520
521pub fn array_field_desc(graphql_name: &str, column: &str, field_type: ArrayFieldType, desc: &str) -> ArrayFieldDef {
522 ArrayFieldDef {
523 graphql_name: graphql_name.to_string(),
524 column: column.to_string(),
525 field_type,
526 description: Some(desc.to_string()),
527 }
528}
529
530pub fn variant(type_name: &str, field_name: &str, source_type: DimType) -> UnionVariant {
531 UnionVariant {
532 type_name: type_name.to_string(),
533 field_name: field_name.to_string(),
534 source_type,
535 }
536}