-
Notifications
You must be signed in to change notification settings - Fork 97
/
Copy pathutils.rs
140 lines (123 loc) · 4.07 KB
/
utils.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use anyhow::Result;
use chrono::{DateTime, Utc};
use clickhouse::Row;
use serde::Deserialize;
use uuid::Uuid;
use crate::{
db::utils::validate_sql_string,
features::{is_feature_enabled, Feature},
};
use super::modifiers::GroupByInterval;
#[derive(Deserialize, Row)]
pub struct TimeBounds {
pub min_time: i64,
pub max_time: i64,
}
pub fn chrono_to_nanoseconds(chrono_dt: DateTime<Utc>) -> i64 {
let timestamp = chrono_dt.timestamp(); // seconds since the Unix epoch
let nanos = chrono_dt.timestamp_subsec_nanos(); // nanoseconds part
// Convert to a total number of nanoseconds since the Unix epoch
let total_nanos = (timestamp as i64) * 1_000_000_000 + (nanos as i64);
total_nanos
}
pub fn nanoseconds_to_chrono(timestamp_nanos: i64) -> DateTime<Utc> {
// Create a DateTime<Utc> object from the timestamp in nanoseconds
DateTime::from_timestamp(
timestamp_nanos / 1_000_000_000, // Convert to seconds
(timestamp_nanos % 1_000_000_000) as u32, // Remaining nanoseconds
)
.unwrap_or_else(|| {
log::error!(
"Failed to create DateTime<Utc> object from timestamp: {}. Defaulting to current time.",
timestamp_nanos
);
Utc::now()
})
}
pub fn group_by_time_absolute_statement(
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
group_by_interval: GroupByInterval,
) -> String {
let ch_round_time = group_by_interval.to_ch_truncate_time();
let ch_interval = group_by_interval.to_interval();
let ch_step = group_by_interval.to_ch_step();
let ch_start_time = start_time.timestamp();
let ch_end_time = end_time.timestamp();
format!(
"GROUP BY
time
ORDER BY
time
WITH FILL
FROM {ch_round_time}(fromUnixTimestamp({ch_start_time}))
TO {ch_round_time}(fromUnixTimestamp({ch_end_time}) + INTERVAL {ch_interval})
STEP {ch_step}"
)
}
pub fn group_by_time_relative_statement(
past_hours: i64,
group_by_interval: GroupByInterval,
) -> String {
let ch_round_time = group_by_interval.to_ch_truncate_time();
let ch_interval = group_by_interval.to_interval();
let ch_step = group_by_interval.to_ch_step();
format!(
"GROUP BY
time
ORDER BY
time
WITH FILL
FROM {ch_round_time}(NOW() - INTERVAL {past_hours} HOUR + INTERVAL {ch_interval})
TO {ch_round_time}(NOW() + INTERVAL {ch_interval})
STEP {ch_step}"
)
}
// Template ID is not included here for events, so that all graphs in the event dashboard
// have the same time bounds. If we want to change that logic, we can optionally add
// template_id to the WHERE clause.
async fn get_time_bounds(
clickhouse: &clickhouse::Client,
project_id: &Uuid,
table_name: &str,
column_name: &str,
) -> Result<TimeBounds> {
if !validate_sql_string(&table_name) {
return Err(anyhow::anyhow!("Invalid table name: {}", table_name));
}
if !validate_sql_string(&column_name) {
return Err(anyhow::anyhow!("Invalid column name: {}", column_name));
}
if !is_feature_enabled(Feature::FullBuild) {
return Ok(TimeBounds {
min_time: chrono_to_nanoseconds(Utc::now() - chrono::Duration::days(1)),
max_time: chrono_to_nanoseconds(Utc::now()),
});
}
let query_string = format!(
"SELECT
MIN({column_name}) AS min_time,
MAX({column_name}) AS max_time
FROM
{table_name}
WHERE project_id = ?",
);
let time_bounds = clickhouse
.query(&query_string)
.bind(project_id)
.fetch_one::<TimeBounds>()
.await?;
Ok(time_bounds)
}
pub async fn get_bounds(
clickhouse: &clickhouse::Client,
project_id: &Uuid,
table_name: &str,
column_name: &str,
) -> Result<(DateTime<Utc>, DateTime<Utc>)> {
let time_bounds = get_time_bounds(clickhouse, project_id, table_name, column_name).await?;
Ok((
nanoseconds_to_chrono(time_bounds.min_time),
nanoseconds_to_chrono(time_bounds.max_time),
))
}