use crate::bridge::envelope::{ErrorCode, Response};
use crate::data::executor::core_loop::CoreLoop;
use crate::data::executor::handlers::accum::GroupState;
use crate::data::executor::task::ExecutionTask;
use nodedb_physical::physical_plan::{AggregateSpec, GroupKeySpec};
use nodedb_types::Value;
pub(in crate::data::executor) const AGG_STATE_FIELD: &str = "__agg_state";
pub(in crate::data::executor) struct PartialAggregateStateParams<'a> {
pub task: &'a ExecutionTask,
pub tid: u64,
pub collection: &'a str,
pub input: Option<&'a nodedb_physical::physical_plan::PhysicalPlan>,
pub group_by: &'a [GroupKeySpec],
pub aggregates: &'a [AggregateSpec],
pub filters: &'a [u8],
}
impl CoreLoop {
pub(in crate::data::executor) fn execute_partial_aggregate_state(
&mut self,
params: PartialAggregateStateParams<'_>,
) -> Response {
let PartialAggregateStateParams {
task,
tid,
collection,
input,
group_by,
aggregates,
filters,
} = params;
let docs = if let Some(sub_plan) = input {
let sub_response = self.execute_plan(task, sub_plan);
crate::data::executor::response_codec::decode_response_to_docs(&sub_response)
.unwrap_or_default()
} else {
let scan_limit = self.query_tuning.aggregate_scan_cap;
match self.scan_collection(
task.request.database_id.as_u64(),
tid,
collection,
scan_limit,
) {
Ok(d) => d,
Err(e) => {
return self.response_error(
task,
ErrorCode::Internal {
detail: e.to_string(),
},
);
}
}
};
let (groups, _sub) =
match self.accumulate_groups(super::streaming::accumulate::AccumulateGroupsParams {
docs: &docs,
group_by,
aggregates,
filters,
sub_group_by: &[],
sub_aggregates: &[],
}) {
Ok(g) => g,
Err(e) => {
return self.response_error(
task,
ErrorCode::Internal {
detail: e.to_string(),
},
);
}
};
let rows = match Self::partial_state_rows(groups, group_by) {
Ok(r) => r,
Err(e) => {
return self.response_error(
task,
ErrorCode::Internal {
detail: e.to_string(),
},
);
}
};
match crate::data::executor::response_codec::encode_value_vec(&rows) {
Ok(payload) => self.response_with_payload(task, payload),
Err(e) => self.response_error(
task,
ErrorCode::Internal {
detail: e.to_string(),
},
),
}
}
fn partial_state_rows(
groups: std::collections::HashMap<String, GroupState>,
group_by: &[GroupKeySpec],
) -> crate::Result<Vec<Value>> {
let mut rows: Vec<Value> = Vec::with_capacity(groups.len());
for (group_key, state) in groups {
let mut map: std::collections::HashMap<String, Value> =
std::collections::HashMap::with_capacity(group_by.len() + 1);
if !group_by.is_empty() {
let parts: Vec<serde_json::Value> =
sonic_rs::from_str(&group_key).map_err(|e| crate::Error::Codec {
detail: format!("partial-state group key decode: {e}"),
})?;
let mut part_idx = 0usize;
for spec in group_by {
if spec.field.is_none() && spec.expr.is_none() {
continue;
}
let jv = parts
.get(part_idx)
.cloned()
.unwrap_or(serde_json::Value::Null);
map.insert(spec.output_name.clone(), Value::from(jv));
part_idx += 1;
}
}
let state_bytes = sonic_rs::to_vec(&state).map_err(|e| crate::Error::Codec {
detail: format!("partial-state serialize: {e}"),
})?;
map.insert(AGG_STATE_FIELD.to_string(), Value::Bytes(state_bytes));
rows.push(Value::Object(map));
}
Ok(rows)
}
}