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