Skip to main content

activecube_rs/cube/
definition.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub enum DimType {
3    String,
4    Int,
5    Float,
6    DateTime,
7    Bool,
8}
9
10#[derive(Debug, Clone)]
11pub struct Dimension {
12    pub graphql_name: String,
13    pub column: String,
14    pub dim_type: DimType,
15}
16
17#[derive(Debug, Clone)]
18pub enum DimensionNode {
19    Leaf(Dimension),
20    Group {
21        graphql_name: String,
22        children: Vec<DimensionNode>,
23    },
24}
25
26/// A named selector defines a filterable field on a Cube.
27/// Each selector maps a GraphQL argument name to a column + type,
28/// enabling `eq`, `gt`, `in`, `any` etc.
29#[derive(Debug, Clone)]
30pub struct SelectorDef {
31    pub graphql_name: String,
32    pub column: String,
33    pub dim_type: DimType,
34}
35
36pub fn selector(graphql_name: &str, column: &str, dim_type: DimType) -> SelectorDef {
37    SelectorDef {
38        graphql_name: graphql_name.to_string(),
39        column: column.to_string(),
40        dim_type,
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct CubeDefinition {
46    pub name: String,
47    pub schema: String,
48    /// Table name pattern. Use `{chain}` as placeholder for chain-prefixed tables
49    /// (e.g. `{chain}_trades` → `sol_trades`). For tables without chain prefix
50    /// (e.g. `dex_pool_liquidities`), use the literal table name and set
51    /// `chain_column` instead.
52    pub table_pattern: String,
53    /// If set, the table doesn't use a `{chain}` prefix in its name. Instead,
54    /// the chain is filtered via `WHERE <chain_column> = ?`. Example:
55    /// `dex_pool_liquidities` has a `chain` column rather than `sol_dex_pool_liquidities`.
56    pub chain_column: Option<String>,
57    pub dimensions: Vec<DimensionNode>,
58    pub metrics: Vec<String>,
59    pub selectors: Vec<SelectorDef>,
60    pub default_filters: Vec<(String, String)>,
61    pub default_limit: u32,
62    pub max_limit: u32,
63    /// Append FINAL to FROM clause for ReplacingMergeTree tables in ClickHouse.
64    pub use_final: bool,
65}
66
67impl CubeDefinition {
68    pub fn table_for_chain(&self, chain: &str) -> String {
69        self.table_pattern.replace("{chain}", chain)
70    }
71
72    pub fn qualified_table(&self, chain: &str) -> String {
73        format!("{}.{}", self.schema, self.table_for_chain(chain))
74    }
75
76    pub fn flat_dimensions(&self) -> Vec<(String, Dimension)> {
77        let mut out = Vec::new();
78        for node in &self.dimensions {
79            collect_leaves(node, "", &mut out);
80        }
81        out
82    }
83}
84
85fn collect_leaves(node: &DimensionNode, prefix: &str, out: &mut Vec<(String, Dimension)>) {
86    match node {
87        DimensionNode::Leaf(dim) => {
88            let path = if prefix.is_empty() {
89                dim.graphql_name.clone()
90            } else {
91                format!("{}_{}", prefix, dim.graphql_name)
92            };
93            out.push((path, dim.clone()));
94        }
95        DimensionNode::Group { graphql_name, children } => {
96            let new_prefix = if prefix.is_empty() {
97                graphql_name.clone()
98            } else {
99                format!("{prefix}_{graphql_name}")
100            };
101            for child in children {
102                collect_leaves(child, &new_prefix, out);
103            }
104        }
105    }
106}
107
108// ---------------------------------------------------------------------------
109// CubeBuilder — ergonomic builder pattern for CubeDefinition
110// ---------------------------------------------------------------------------
111
112pub struct CubeBuilder {
113    def: CubeDefinition,
114}
115
116impl CubeBuilder {
117    pub fn new(name: &str) -> Self {
118        Self {
119            def: CubeDefinition {
120                name: name.to_string(),
121                schema: String::new(),
122                table_pattern: String::new(),
123                chain_column: None,
124                dimensions: Vec::new(),
125                metrics: Vec::new(),
126                selectors: Vec::new(),
127                default_filters: Vec::new(),
128                default_limit: 25,
129                max_limit: 10000,
130                use_final: false,
131            },
132        }
133    }
134
135    pub fn schema(mut self, schema: &str) -> Self {
136        self.def.schema = schema.to_string();
137        self
138    }
139
140    pub fn table(mut self, pattern: &str) -> Self {
141        self.def.table_pattern = pattern.to_string();
142        self
143    }
144
145    pub fn chain_column(mut self, column: &str) -> Self {
146        self.def.chain_column = Some(column.to_string());
147        self
148    }
149
150    pub fn dimension(mut self, node: DimensionNode) -> Self {
151        self.def.dimensions.push(node);
152        self
153    }
154
155    pub fn metric(mut self, name: &str) -> Self {
156        self.def.metrics.push(name.to_string());
157        self
158    }
159
160    pub fn metrics(mut self, names: &[&str]) -> Self {
161        self.def.metrics.extend(names.iter().map(|s| s.to_string()));
162        self
163    }
164
165    pub fn selector(mut self, sel: SelectorDef) -> Self {
166        self.def.selectors.push(sel);
167        self
168    }
169
170    pub fn default_filter(mut self, column: &str, value: &str) -> Self {
171        self.def.default_filters.push((column.to_string(), value.to_string()));
172        self
173    }
174
175    pub fn default_limit(mut self, limit: u32) -> Self {
176        self.def.default_limit = limit;
177        self
178    }
179
180    pub fn max_limit(mut self, limit: u32) -> Self {
181        self.def.max_limit = limit;
182        self
183    }
184
185    pub fn use_final(mut self, val: bool) -> Self {
186        self.def.use_final = val;
187        self
188    }
189
190    pub fn build(self) -> CubeDefinition {
191        self.def
192    }
193}
194
195// ---------------------------------------------------------------------------
196// Helper functions for concise dimension/selector construction
197// ---------------------------------------------------------------------------
198
199pub fn dim(graphql_name: &str, column: &str, dim_type: DimType) -> DimensionNode {
200    DimensionNode::Leaf(Dimension {
201        graphql_name: graphql_name.to_string(),
202        column: column.to_string(),
203        dim_type,
204    })
205}
206
207pub fn dim_group(graphql_name: &str, children: Vec<DimensionNode>) -> DimensionNode {
208    DimensionNode::Group {
209        graphql_name: graphql_name.to_string(),
210        children,
211    }
212}