mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
Fallback dashboard aggregate reads on schema mismatch
This commit is contained in:
@@ -175,6 +175,38 @@ fn split_dashboard_hourly_aggregate_range(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dashboard_aggregate_schema_mismatch_message(message: &str) -> bool {
|
||||||
|
let message = message.to_ascii_lowercase();
|
||||||
|
let references_dashboard_aggregate = [
|
||||||
|
"stats_summary",
|
||||||
|
"stats_daily",
|
||||||
|
"stats_user_daily",
|
||||||
|
"stats_daily_model_provider",
|
||||||
|
"stats_user_daily_model_provider",
|
||||||
|
"cutoff_date",
|
||||||
|
"effective_input_tokens",
|
||||||
|
"total_input_context",
|
||||||
|
"response_time_sum_ms",
|
||||||
|
"response_time_samples",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|pattern| message.contains(pattern));
|
||||||
|
if !references_dashboard_aggregate {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.contains("does not exist")
|
||||||
|
|| message.contains("unknown column")
|
||||||
|
|| message.contains("no column named")
|
||||||
|
|| message.contains("error occurred while decoding column")
|
||||||
|
|| message.contains("is not compatible with sql type")
|
||||||
|
|| message.contains("unexpected null")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dashboard_should_fallback_to_raw_on_aggregate_error(err: &DataLayerError) -> bool {
|
||||||
|
dashboard_aggregate_schema_mismatch_message(&err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn absorb_dashboard_summary(
|
fn absorb_dashboard_summary(
|
||||||
target: &mut StoredUsageDashboardSummary,
|
target: &mut StoredUsageDashboardSummary,
|
||||||
part: &StoredUsageDashboardSummary,
|
part: &StoredUsageDashboardSummary,
|
||||||
@@ -4065,7 +4097,20 @@ ORDER BY created_at_unix_secs ASC, group_id ASC, usage_id ASC
|
|||||||
&self,
|
&self,
|
||||||
query: &UsageDashboardSummaryQuery,
|
query: &UsageDashboardSummaryQuery,
|
||||||
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
|
) -> Result<StoredUsageDashboardSummary, DataLayerError> {
|
||||||
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
|
let cutoff_utc = match self.read_stats_daily_cutoff_date().await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
|
||||||
|
return self
|
||||||
|
.summarize_dashboard_usage_raw(
|
||||||
|
query.created_from_unix_secs,
|
||||||
|
query.created_until_unix_secs,
|
||||||
|
query.user_id.as_deref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
let Some(cutoff_utc) = cutoff_utc else {
|
||||||
return self
|
return self
|
||||||
.summarize_dashboard_usage_raw(
|
.summarize_dashboard_usage_raw(
|
||||||
query.created_from_unix_secs,
|
query.created_from_unix_secs,
|
||||||
@@ -4099,13 +4144,26 @@ ORDER BY created_at_unix_secs ASC, group_id ASC, usage_id ASC
|
|||||||
absorb_dashboard_summary(&mut summary, &raw);
|
absorb_dashboard_summary(&mut summary, &raw);
|
||||||
}
|
}
|
||||||
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
|
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
|
||||||
let aggregate = self
|
let aggregate = match self
|
||||||
.summarize_dashboard_usage_from_daily_aggregates(
|
.summarize_dashboard_usage_from_daily_aggregates(
|
||||||
aggregate_start,
|
aggregate_start,
|
||||||
aggregate_end,
|
aggregate_end,
|
||||||
query.user_id.as_deref(),
|
query.user_id.as_deref(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
|
{
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
|
||||||
|
return self
|
||||||
|
.summarize_dashboard_usage_raw(
|
||||||
|
query.created_from_unix_secs,
|
||||||
|
query.created_until_unix_secs,
|
||||||
|
query.user_id.as_deref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
absorb_dashboard_summary(&mut summary, &aggregate);
|
absorb_dashboard_summary(&mut summary, &aggregate);
|
||||||
}
|
}
|
||||||
if let Some((raw_start, raw_end)) = split.raw_trailing {
|
if let Some((raw_start, raw_end)) = split.raw_trailing {
|
||||||
@@ -4300,7 +4358,14 @@ ORDER BY date ASC, total_cost_usd DESC, "usage".model ASC, "usage".provider_name
|
|||||||
return self.list_dashboard_daily_breakdown_raw(query).await;
|
return self.list_dashboard_daily_breakdown_raw(query).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(cutoff_utc) = self.read_stats_daily_cutoff_date().await? else {
|
let cutoff_utc = match self.read_stats_daily_cutoff_date().await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
|
||||||
|
return self.list_dashboard_daily_breakdown_raw(query).await;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
let Some(cutoff_utc) = cutoff_utc else {
|
||||||
return self.list_dashboard_daily_breakdown_raw(query).await;
|
return self.list_dashboard_daily_breakdown_raw(query).await;
|
||||||
};
|
};
|
||||||
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
|
let start_utc = dashboard_unix_secs_to_utc(query.created_from_unix_secs);
|
||||||
@@ -4323,14 +4388,21 @@ ORDER BY date ASC, total_cost_usd DESC, "usage".model ASC, "usage".provider_name
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
|
if let Some((aggregate_start, aggregate_end)) = split.aggregate {
|
||||||
items.extend(
|
let aggregate_rows = match self
|
||||||
self.list_dashboard_daily_breakdown_from_daily_aggregates(
|
.list_dashboard_daily_breakdown_from_daily_aggregates(
|
||||||
aggregate_start,
|
aggregate_start,
|
||||||
aggregate_end,
|
aggregate_end,
|
||||||
query.user_id.as_deref(),
|
query.user_id.as_deref(),
|
||||||
)
|
)
|
||||||
.await?,
|
.await
|
||||||
);
|
{
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err) => {
|
||||||
|
return self.list_dashboard_daily_breakdown_raw(query).await;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
items.extend(aggregate_rows);
|
||||||
}
|
}
|
||||||
if let Some((raw_start, raw_end)) = split.raw_trailing {
|
if let Some((raw_start, raw_end)) = split.raw_trailing {
|
||||||
items.extend(
|
items.extend(
|
||||||
|
|||||||
@@ -500,6 +500,34 @@ fn usage_sql_summarize_total_tokens_by_api_key_ids_supports_daily_aggregates() {
|
|||||||
assert!(source.contains("read_stats_daily_cutoff_date().await?"));
|
assert!(source.contains("read_stats_daily_cutoff_date().await?"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dashboard_aggregate_schema_mismatch_detector_matches_legacy_schema_failures() {
|
||||||
|
assert!(super::dashboard_aggregate_schema_mismatch_message(
|
||||||
|
"postgres error: error occurred while decoding column \"cutoff_date\": \
|
||||||
|
mismatched types; Rust type `chrono::DateTime<Utc>` (as SQL type `TIMESTAMPTZ`) \
|
||||||
|
is not compatible with SQL type `INT8`"
|
||||||
|
));
|
||||||
|
assert!(super::dashboard_aggregate_schema_mismatch_message(
|
||||||
|
"postgres error: db error: ERROR: relation \"stats_daily_model_provider\" does not exist"
|
||||||
|
));
|
||||||
|
assert!(super::dashboard_aggregate_schema_mismatch_message(
|
||||||
|
"postgres error: db error: ERROR: column \"effective_input_tokens\" does not exist"
|
||||||
|
));
|
||||||
|
assert!(!super::dashboard_aggregate_schema_mismatch_message(
|
||||||
|
"postgres error: db error: ERROR: permission denied for relation stats_daily"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dashboard_aggregate_reads_fallback_to_raw_on_schema_mismatch() {
|
||||||
|
let source = include_str!("mod.rs");
|
||||||
|
assert!(source.contains("dashboard_should_fallback_to_raw_on_aggregate_error"));
|
||||||
|
assert!(
|
||||||
|
source.contains("Err(err) if dashboard_should_fallback_to_raw_on_aggregate_error(&err)")
|
||||||
|
);
|
||||||
|
assert!(source.contains("return self.list_dashboard_daily_breakdown_raw(query).await;"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn usage_sql_summarize_usage_totals_by_user_ids_supports_user_summary_aggregates() {
|
fn usage_sql_summarize_usage_totals_by_user_ids_supports_user_summary_aggregates() {
|
||||||
let source = include_str!("mod.rs");
|
let source = include_str!("mod.rs");
|
||||||
|
|||||||
Reference in New Issue
Block a user