icydb-cli 0.148.13

Developer CLI tools for IcyDB
use icydb::db::sql::SqlQueryResult;
use serde_json::Value;

use crate::shell::perf::ShellPerfAttribution;

pub(crate) fn parse_perf_result(
    value: &Value,
) -> Result<(SqlQueryResult, ShellPerfAttribution), String> {
    let result_value = value
        .get("result")
        .ok_or_else(|| "perf result missing result payload".to_string())?;
    let mut result_value = result_value.clone();

    normalize_grouped_next_cursor_json(&mut result_value);

    let result =
        serde_json::from_value::<SqlQueryResult>(result_value).map_err(|err| err.to_string())?;

    Ok((
        result,
        ShellPerfAttribution {
            total: parse_perf_u64(value, "instructions")?,
            planner: parse_perf_u64(value, "planner_instructions")?,
            store: parse_perf_u64_or_default(value, "store_instructions")?,
            executor: parse_perf_u64(value, "executor_instructions")?,
            pure_covering_decode: parse_perf_u64_or_default(
                value,
                "pure_covering_decode_instructions",
            )?,
            pure_covering_row_assembly: parse_perf_u64_or_default(
                value,
                "pure_covering_row_assembly_instructions",
            )?,
            decode: parse_perf_u64_or_default(value, "decode_instructions")?,
            compiler: parse_perf_u64(value, "compiler_instructions")?,
        },
    ))
}

pub(crate) fn normalize_grouped_next_cursor_json(value: &mut Value) {
    let Some(grouped) = value.get_mut("Grouped") else {
        return;
    };
    let Some(grouped_object) = grouped.as_object_mut() else {
        return;
    };
    let Some(next_cursor) = grouped_object.get_mut("next_cursor") else {
        return;
    };

    match next_cursor {
        Value::Array(values) if values.is_empty() => *next_cursor = Value::Null,
        Value::Array(values) if values.len() == 1 => {
            *next_cursor = values.pop().unwrap_or(Value::Null);
        }
        _ => {}
    }
}

fn parse_perf_u64(value: &Value, field: &str) -> Result<u64, String> {
    let field_value = value
        .get(field)
        .ok_or_else(|| format!("perf result missing {field}"))?;

    match field_value {
        Value::Number(number) => number
            .as_u64()
            .ok_or_else(|| format!("perf field {field} is not a u64")),
        Value::String(text) => text
            .parse()
            .map_err(|_| format!("perf field {field} is not a u64")),
        _ => Err(format!("perf field {field} has unsupported type")),
    }
}

fn parse_perf_u64_or_default(value: &Value, field: &str) -> Result<u64, String> {
    if value.get(field).is_none() {
        return Ok(0);
    }

    parse_perf_u64(value, field)
}

pub(crate) fn find_result_payload(value: &Value) -> Option<&Value> {
    if matches!(value, Value::Object(map) if map.contains_key("Ok") || map.contains_key("Err")) {
        return Some(value);
    }

    if let Some(result) = value.get("result") {
        return Some(result);
    }

    match value {
        Value::Array(items) => items.iter().find_map(find_result_payload),
        Value::Object(map) => map.values().find_map(find_result_payload),
        _ => None,
    }
}