databuild/databuild/event_log/query_engine.rs
Stuart Axelbrooke f4c52cacc3
Some checks failed
/ setup (push) Has been cancelled
Big bump
2025-08-14 22:55:49 -07:00

388 lines
16 KiB
Rust

use super::*;
use super::storage::BELStorage;
use std::sync::Arc;
use std::collections::HashMap;
/// App-layer aggregation that scans storage events
pub struct BELQueryEngine {
storage: Arc<dyn BELStorage>,
}
impl BELQueryEngine {
pub fn new(storage: Arc<dyn BELStorage>) -> Self {
Self { storage }
}
/// Get latest status for a partition by scanning recent events
pub async fn get_latest_partition_status(&self, partition_ref: &str) -> Result<Option<(PartitionStatus, i64)>> {
let filter = EventFilter {
partition_refs: vec![partition_ref.to_string()],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
self.aggregate_partition_status(&events.events)
}
/// Get all build requests that are currently building a partition
pub async fn get_active_builds_for_partition(&self, partition_ref: &str) -> Result<Vec<String>> {
let filter = EventFilter {
partition_refs: vec![partition_ref.to_string()],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
let mut active_builds = Vec::new();
let mut build_states: HashMap<String, BuildRequestStatus> = HashMap::new();
// Process events chronologically to track build states
for event in events.events {
match &event.event_type {
Some(crate::build_event::EventType::BuildRequestEvent(br_event)) => {
if let Ok(status) = BuildRequestStatus::try_from(br_event.status_code) {
build_states.insert(event.build_request_id.clone(), status);
}
}
Some(crate::build_event::EventType::PartitionEvent(p_event)) => {
if let Some(partition_event_ref) = &p_event.partition_ref {
if partition_event_ref.str == partition_ref {
// Check if this partition is actively being built
if let Ok(status) = PartitionStatus::try_from(p_event.status_code) {
if matches!(status, PartitionStatus::PartitionBuilding | PartitionStatus::PartitionAnalyzed) {
// Check if the build request is still active
if let Some(build_status) = build_states.get(&event.build_request_id) {
if matches!(build_status,
BuildRequestStatus::BuildRequestReceived |
BuildRequestStatus::BuildRequestPlanning |
BuildRequestStatus::BuildRequestExecuting |
BuildRequestStatus::BuildRequestAnalysisCompleted
) {
if !active_builds.contains(&event.build_request_id) {
active_builds.push(event.build_request_id.clone());
}
}
}
}
}
}
}
}
_ => {}
}
}
Ok(active_builds)
}
/// Get summary of a build request by aggregating its events
pub async fn get_build_request_summary(&self, build_id: &str) -> Result<BuildRequestSummary> {
let filter = EventFilter {
partition_refs: vec![],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![build_id.to_string()],
};
let events = self.storage.list_events(0, filter).await?;
// If no events found, build doesn't exist
if events.events.is_empty() {
return Err(BuildEventLogError::QueryError(format!("Build request '{}' not found", build_id)));
}
let mut status = BuildRequestStatus::BuildRequestUnknown;
let mut requested_partitions = Vec::new();
let mut created_at = 0i64;
let mut updated_at = 0i64;
for event in events.events {
if event.timestamp > 0 {
if created_at == 0 || event.timestamp < created_at {
created_at = event.timestamp;
}
if event.timestamp > updated_at {
updated_at = event.timestamp;
}
}
if let Some(crate::build_event::EventType::BuildRequestEvent(br_event)) = &event.event_type {
if let Ok(event_status) = BuildRequestStatus::try_from(br_event.status_code) {
status = event_status;
}
if !br_event.requested_partitions.is_empty() {
requested_partitions = br_event.requested_partitions.iter()
.map(|p| p.str.clone())
.collect();
}
}
}
Ok(BuildRequestSummary {
build_request_id: build_id.to_string(),
status,
requested_partitions,
created_at,
updated_at,
})
}
/// List build requests with pagination and filtering
pub async fn list_build_requests(&self, request: BuildsListRequest) -> Result<BuildsListResponse> {
// For now, scan all events and aggregate
let filter = EventFilter {
partition_refs: vec![],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
let mut build_summaries: HashMap<String, BuildRequestSummary> = HashMap::new();
// Aggregate by build request ID
for event in events.events {
if let Some(crate::build_event::EventType::BuildRequestEvent(br_event)) = &event.event_type {
let build_id = &event.build_request_id;
let entry = build_summaries.entry(build_id.clone()).or_insert_with(|| {
BuildRequestSummary {
build_request_id: build_id.clone(),
status: BuildRequestStatus::BuildRequestUnknown,
requested_partitions: Vec::new(),
created_at: event.timestamp,
updated_at: event.timestamp,
}
});
if let Ok(status) = BuildRequestStatus::try_from(br_event.status_code) {
entry.status = status;
}
entry.updated_at = event.timestamp.max(entry.updated_at);
if !br_event.requested_partitions.is_empty() {
entry.requested_partitions = br_event.requested_partitions.iter()
.map(|p| p.str.clone())
.collect();
}
}
}
let mut builds: Vec<_> = build_summaries.into_values().collect();
builds.sort_by(|a, b| b.created_at.cmp(&a.created_at)); // Most recent first
// Apply status filter if provided
if let Some(status_filter) = &request.status_filter {
if let Ok(filter_status) = status_filter.parse::<i32>() {
if let Ok(status) = BuildRequestStatus::try_from(filter_status) {
builds.retain(|b| b.status == status);
}
}
}
let total_count = builds.len() as u32;
let offset = request.offset.unwrap_or(0) as usize;
let limit = request.limit.unwrap_or(50) as usize;
let paginated_builds = builds.into_iter()
.skip(offset)
.take(limit)
.map(|summary| BuildSummary {
build_request_id: summary.build_request_id,
status_code: summary.status as i32,
status_name: summary.status.to_display_string(),
requested_partitions: summary.requested_partitions.into_iter()
.map(|s| PartitionRef { str: s })
.collect(),
total_jobs: 0, // TODO: Implement
completed_jobs: 0, // TODO: Implement
failed_jobs: 0, // TODO: Implement
cancelled_jobs: 0, // TODO: Implement
requested_at: summary.created_at,
started_at: None, // TODO: Implement
completed_at: None, // TODO: Implement
duration_ms: None, // TODO: Implement
cancelled: false, // TODO: Implement
})
.collect();
Ok(BuildsListResponse {
builds: paginated_builds,
total_count,
has_more: (offset + limit) < total_count as usize,
})
}
/// Get activity summary for dashboard
pub async fn get_activity_summary(&self) -> Result<ActivitySummary> {
let builds_response = self.list_build_requests(BuildsListRequest {
limit: Some(5),
offset: Some(0),
status_filter: None,
}).await?;
let active_builds_count = builds_response.builds.iter()
.filter(|b| matches!(
BuildRequestStatus::try_from(b.status_code).unwrap_or(BuildRequestStatus::BuildRequestUnknown),
BuildRequestStatus::BuildRequestReceived |
BuildRequestStatus::BuildRequestPlanning |
BuildRequestStatus::BuildRequestExecuting |
BuildRequestStatus::BuildRequestAnalysisCompleted
))
.count() as u32;
let recent_builds = builds_response.builds.into_iter()
.map(|b| BuildRequestSummary {
build_request_id: b.build_request_id,
status: BuildRequestStatus::try_from(b.status_code).unwrap_or(BuildRequestStatus::BuildRequestUnknown),
requested_partitions: b.requested_partitions.into_iter().map(|p| p.str).collect(),
created_at: b.requested_at,
updated_at: b.completed_at.unwrap_or(b.requested_at),
})
.collect();
// For partitions, we'd need a separate implementation
let recent_partitions = Vec::new(); // TODO: Implement partition listing
Ok(ActivitySummary {
active_builds_count,
recent_builds,
recent_partitions,
total_partitions_count: 0, // TODO: Implement
})
}
/// Helper to aggregate partition status from events
fn aggregate_partition_status(&self, events: &[BuildEvent]) -> Result<Option<(PartitionStatus, i64)>> {
let mut latest_status = None;
let mut latest_timestamp = 0i64;
// Look for the most recent partition event for this partition
for event in events {
if let Some(crate::build_event::EventType::PartitionEvent(p_event)) = &event.event_type {
if event.timestamp >= latest_timestamp {
if let Ok(status) = PartitionStatus::try_from(p_event.status_code) {
latest_status = Some(status);
latest_timestamp = event.timestamp;
}
}
}
}
Ok(latest_status.map(|status| (status, latest_timestamp)))
}
/// Get build request ID that created an available partition
pub async fn get_build_request_for_available_partition(&self, partition_ref: &str) -> Result<Option<String>> {
let filter = EventFilter {
partition_refs: vec![partition_ref.to_string()],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
// Find the most recent PARTITION_AVAILABLE event
let mut latest_available_build_id = None;
let mut latest_timestamp = 0i64;
for event in events.events {
if let Some(crate::build_event::EventType::PartitionEvent(p_event)) = &event.event_type {
if let Some(partition_event_ref) = &p_event.partition_ref {
if partition_event_ref.str == partition_ref {
if let Ok(status) = PartitionStatus::try_from(p_event.status_code) {
if status == PartitionStatus::PartitionAvailable && event.timestamp >= latest_timestamp {
latest_available_build_id = Some(event.build_request_id.clone());
latest_timestamp = event.timestamp;
}
}
}
}
}
}
Ok(latest_available_build_id)
}
/// Append an event to storage
pub async fn append_event(&self, event: BuildEvent) -> Result<i64> {
self.storage.append_event(event).await
}
/// Get all events for a specific partition
pub async fn get_partition_events(&self, partition_ref: &str, _limit: Option<u32>) -> Result<Vec<BuildEvent>> {
let filter = EventFilter {
partition_refs: vec![partition_ref.to_string()],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
Ok(events.events)
}
/// Execute a raw SQL query (for backwards compatibility)
pub async fn execute_query(&self, _query: &str) -> Result<QueryResult> {
// TODO: Implement SQL query execution if needed
// For now, return empty result to avoid compilation errors
Ok(QueryResult {
columns: vec![],
rows: vec![],
})
}
/// Get all events in a timestamp range
pub async fn get_events_in_range(&self, _start: i64, _end: i64) -> Result<Vec<BuildEvent>> {
// TODO: Implement range filtering
// For now, get all events
let filter = EventFilter {
partition_refs: vec![],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
Ok(events.events)
}
/// Get all events for a specific job run
pub async fn get_job_run_events(&self, job_run_id: &str) -> Result<Vec<BuildEvent>> {
let filter = EventFilter {
partition_refs: vec![],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![job_run_id.to_string()],
build_request_ids: vec![],
};
let events = self.storage.list_events(0, filter).await?;
Ok(events.events)
}
/// Get all events for a specific build request
pub async fn get_build_request_events(&self, build_request_id: &str, _limit: Option<u32>) -> Result<Vec<BuildEvent>> {
let filter = EventFilter {
partition_refs: vec![],
partition_patterns: vec![],
job_labels: vec![],
job_run_ids: vec![],
build_request_ids: vec![build_request_id.to_string()],
};
let events = self.storage.list_events(0, filter).await?;
Ok(events.events)
}
}