Skip to main content

fraiseql_cli/schema/intermediate/
analytics.rs

1//! Fact table/aggregate structs: `IntermediateFactTable`, `IntermediateMeasure`,
2//! `IntermediateDimensions`, `IntermediateDimensionPath`, `IntermediateFilter`,
3//! `IntermediateAggregateQuery`.
4
5use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8
9// =============================================================================
10// Analytics Definitions
11// =============================================================================
12
13/// Fact table definition in intermediate format (Analytics)
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct IntermediateFactTable {
16    /// Name of the fact table
17    pub table_name:               String,
18    /// The type the fact table is read as: its measures, filters and dimension paths are
19    /// fields of that type, gated as a read of it (accepts `type` too).
20    #[serde(default, alias = "type", skip_serializing_if = "Option::is_none")]
21    pub type_name:                Option<String>,
22    /// Measure columns (numeric aggregates)
23    pub measures:                 Vec<IntermediateMeasure>,
24    /// Dimension metadata
25    pub dimensions:               IntermediateDimensions,
26    /// Denormalized filter columns
27    pub denormalized_filters:     Vec<IntermediateFilter>,
28    /// Maps JSONB measure paths to flat SQL column names for pre-aggregated views
29    #[serde(default)]
30    pub native_measures:          HashMap<String, String>,
31    /// Maps deep JSONB dimension paths to flat SQL column names for GROUP BY
32    #[serde(default)]
33    pub native_dimension_mapping: HashMap<String, String>,
34}
35
36/// Measure column definition
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct IntermediateMeasure {
39    /// Measure column name
40    pub name:       String,
41    /// SQL data type of the measure
42    pub sql_type:   String,
43    /// Whether the column can be NULL
44    pub nullable:   bool,
45    /// How the measure aggregates over time (#1459). Omitted: additive.
46    #[serde(
47        default,
48        skip_serializing_if = "fraiseql_core::compiler::fact_table::Additivity::is_additive"
49    )]
50    pub additivity: fraiseql_core::compiler::fact_table::Additivity,
51}
52
53/// Dimensions metadata
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct IntermediateDimensions {
56    /// Dimension name
57    pub name:  String,
58    /// Paths to dimension fields within JSONB
59    pub paths: Vec<IntermediateDimensionPath>,
60}
61
62/// Dimension path within JSONB
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct IntermediateDimensionPath {
65    /// Path name identifier
66    pub name:      String,
67    /// JSON path (accepts both "`json_path`" and "path" for cross-language compat)
68    #[serde(alias = "path")]
69    pub json_path: String,
70    /// Data type (accepts both "`data_type`" and "type" for cross-language compat)
71    #[serde(alias = "type")]
72    pub data_type: String,
73}
74
75/// Denormalized filter column
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct IntermediateFilter {
78    /// Filter column name
79    pub name:      String,
80    /// SQL data type of the filter
81    pub sql_type:  String,
82    /// Whether this column should be indexed
83    pub indexed:   bool,
84    /// For an `LTREE` path column: the `[hierarchies.<name>]` its paths belong to, so
85    /// `descendant_of_id` / `ancestor_of_id` can resolve a node id (#1498).
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub hierarchy: Option<String>,
88}
89
90/// Aggregate query definition (Analytics)
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct IntermediateAggregateQuery {
93    /// Aggregate query name
94    pub name:            String,
95    /// Fact table to aggregate from
96    pub fact_table:      String,
97    /// Automatically generate GROUP BY clauses
98    pub auto_group_by:   bool,
99    /// Automatically generate aggregate functions
100    pub auto_aggregates: bool,
101    /// Optional description
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub description:     Option<String>,
104}