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}
46
47/// Dimensions metadata
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct IntermediateDimensions {
50    /// Dimension name
51    pub name:  String,
52    /// Paths to dimension fields within JSONB
53    pub paths: Vec<IntermediateDimensionPath>,
54}
55
56/// Dimension path within JSONB
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct IntermediateDimensionPath {
59    /// Path name identifier
60    pub name:      String,
61    /// JSON path (accepts both "`json_path`" and "path" for cross-language compat)
62    #[serde(alias = "path")]
63    pub json_path: String,
64    /// Data type (accepts both "`data_type`" and "type" for cross-language compat)
65    #[serde(alias = "type")]
66    pub data_type: String,
67}
68
69/// Denormalized filter column
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct IntermediateFilter {
72    /// Filter column name
73    pub name:      String,
74    /// SQL data type of the filter
75    pub sql_type:  String,
76    /// Whether this column should be indexed
77    pub indexed:   bool,
78    /// For an `LTREE` path column: the `[hierarchies.<name>]` its paths belong to, so
79    /// `descendant_of_id` / `ancestor_of_id` can resolve a node id (#1498).
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub hierarchy: Option<String>,
82}
83
84/// Aggregate query definition (Analytics)
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86pub struct IntermediateAggregateQuery {
87    /// Aggregate query name
88    pub name:            String,
89    /// Fact table to aggregate from
90    pub fact_table:      String,
91    /// Automatically generate GROUP BY clauses
92    pub auto_group_by:   bool,
93    /// Automatically generate aggregate functions
94    pub auto_aggregates: bool,
95    /// Optional description
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub description:     Option<String>,
98}