icydb-core 0.180.3

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use crate::db::query::explain::{
    ExplainExecutionMode, ExplainExecutionNodeDescriptor, ExplainExecutionNodeType,
    ExplainPropertyMap,
};

use super::{ExecutionNodeIdentity, ExecutionNodeLayer, collect_execution_node_identities};

fn extract_node_ids_from_canonical_json(json: &str) -> Vec<u64> {
    let mut ids = Vec::new();
    let mut remaining = json;
    let node_id_prefix = "\"node_id\":";

    while let Some(prefix_index) = remaining.find(node_id_prefix) {
        let value_start = prefix_index.saturating_add(node_id_prefix.len());
        let digits_start = value_start;
        let digits_end = remaining[digits_start..]
            .find(|ch: char| !ch.is_ascii_digit())
            .map_or(remaining.len(), |relative| {
                digits_start.saturating_add(relative)
            });
        let parsed = remaining[digits_start..digits_end]
            .parse::<u64>()
            .expect("node_id field must parse as u64");
        ids.push(parsed);
        remaining = &remaining[digits_end..];
    }

    ids
}

#[test]
fn execution_node_identity_from_explain_node_preserves_node_and_layer_contract() {
    let descriptor = ExplainExecutionNodeDescriptor {
        node_type: ExplainExecutionNodeType::IndexPrefixScan,
        execution_mode: ExplainExecutionMode::Materialized,
        access_strategy: None,
        predicate_pushdown: None,
        filter_expr: None,
        residual_filter_expr: None,
        residual_filter_predicate: None,
        projection: None,
        ordering_source: None,
        limit: None,
        cursor: None,
        covering_scan: None,
        rows_expected: None,
        children: Vec::new(),
        node_properties: ExplainPropertyMap::new(),
    };

    let identity = ExecutionNodeIdentity::from_explain_node(7, &descriptor);
    assert_eq!(identity.node_id(), 7);
    assert_eq!(identity.node_type(), "IndexPrefixScan");
    assert_eq!(identity.layer(), ExecutionNodeLayer::Scan);
    assert_eq!(identity.layer().as_str(), "scan");
}

#[test]
fn execution_node_identity_constructor_is_stable() {
    let identity = ExecutionNodeIdentity::new(3, "LimitOffset", ExecutionNodeLayer::Terminal);

    assert_eq!(identity.node_id(), 3);
    assert_eq!(identity.node_type(), "LimitOffset");
    assert_eq!(identity.layer(), ExecutionNodeLayer::Terminal);
    assert_eq!(identity.layer().as_str(), "terminal");
}

#[test]
fn execution_node_identity_collection_preserves_node_id_order_and_layer_mapping() {
    let descriptor = ExplainExecutionNodeDescriptor {
        node_type: ExplainExecutionNodeType::TopNSeek,
        execution_mode: ExplainExecutionMode::Streaming,
        access_strategy: None,
        predicate_pushdown: None,
        filter_expr: None,
        residual_filter_expr: None,
        residual_filter_predicate: None,
        projection: None,
        ordering_source: None,
        limit: None,
        cursor: None,
        covering_scan: None,
        rows_expected: None,
        children: vec![
            ExplainExecutionNodeDescriptor {
                node_type: ExplainExecutionNodeType::IndexPrefixScan,
                execution_mode: ExplainExecutionMode::Materialized,
                access_strategy: None,
                predicate_pushdown: None,
                filter_expr: None,
                residual_filter_expr: None,
                residual_filter_predicate: None,
                projection: None,
                ordering_source: None,
                limit: None,
                cursor: None,
                covering_scan: None,
                rows_expected: None,
                children: Vec::new(),
                node_properties: ExplainPropertyMap::new(),
            },
            ExplainExecutionNodeDescriptor {
                node_type: ExplainExecutionNodeType::AggregateCount,
                execution_mode: ExplainExecutionMode::Materialized,
                access_strategy: None,
                predicate_pushdown: None,
                filter_expr: None,
                residual_filter_expr: None,
                residual_filter_predicate: None,
                projection: None,
                ordering_source: None,
                limit: None,
                cursor: None,
                covering_scan: None,
                rows_expected: None,
                children: vec![ExplainExecutionNodeDescriptor {
                    node_type: ExplainExecutionNodeType::LimitOffset,
                    execution_mode: ExplainExecutionMode::Materialized,
                    access_strategy: None,
                    predicate_pushdown: None,
                    filter_expr: None,
                    residual_filter_expr: None,
                    residual_filter_predicate: None,
                    projection: None,
                    ordering_source: None,
                    limit: None,
                    cursor: None,
                    covering_scan: None,
                    rows_expected: None,
                    children: Vec::new(),
                    node_properties: ExplainPropertyMap::new(),
                }],
                node_properties: ExplainPropertyMap::new(),
            },
        ],
        node_properties: ExplainPropertyMap::new(),
    };
    let identities = collect_execution_node_identities(&descriptor);
    let actual = identities
        .iter()
        .map(|identity| {
            (
                identity.node_id(),
                identity.node_type().to_string(),
                identity.layer().as_str().to_string(),
            )
        })
        .collect::<Vec<_>>();
    let expected = vec![
        (0_u64, "TopNSeek".to_string(), "pipeline".to_string()),
        (1_u64, "IndexPrefixScan".to_string(), "scan".to_string()),
        (2_u64, "AggregateCount".to_string(), "aggregate".to_string()),
        (3_u64, "LimitOffset".to_string(), "terminal".to_string()),
    ];

    assert_eq!(
        actual, expected,
        "diagnostics identity collection must preserve canonical node ordering and layers",
    );
}

#[test]
fn execution_node_identity_collection_node_ids_match_canonical_explain_json_ids() {
    let descriptor = ExplainExecutionNodeDescriptor {
        node_type: ExplainExecutionNodeType::TopNSeek,
        execution_mode: ExplainExecutionMode::Streaming,
        access_strategy: None,
        predicate_pushdown: None,
        filter_expr: None,
        residual_filter_expr: None,
        residual_filter_predicate: None,
        projection: None,
        ordering_source: None,
        limit: None,
        cursor: None,
        covering_scan: None,
        rows_expected: None,
        children: vec![
            ExplainExecutionNodeDescriptor {
                node_type: ExplainExecutionNodeType::IndexPrefixScan,
                execution_mode: ExplainExecutionMode::Materialized,
                access_strategy: None,
                predicate_pushdown: None,
                filter_expr: None,
                residual_filter_expr: None,
                residual_filter_predicate: None,
                projection: None,
                ordering_source: None,
                limit: None,
                cursor: None,
                covering_scan: None,
                rows_expected: None,
                children: Vec::new(),
                node_properties: ExplainPropertyMap::new(),
            },
            ExplainExecutionNodeDescriptor {
                node_type: ExplainExecutionNodeType::LimitOffset,
                execution_mode: ExplainExecutionMode::Materialized,
                access_strategy: None,
                predicate_pushdown: None,
                filter_expr: None,
                residual_filter_expr: None,
                residual_filter_predicate: None,
                projection: None,
                ordering_source: None,
                limit: None,
                cursor: None,
                covering_scan: None,
                rows_expected: None,
                children: Vec::new(),
                node_properties: ExplainPropertyMap::new(),
            },
        ],
        node_properties: ExplainPropertyMap::new(),
    };
    let identities = collect_execution_node_identities(&descriptor);
    let identity_ids = identities
        .iter()
        .map(|identity| identity.node_id())
        .collect::<Vec<_>>();
    let json_ids = extract_node_ids_from_canonical_json(&descriptor.render_json_canonical());

    assert_eq!(
        identity_ids, json_ids,
        "diagnostics node ids must match canonical explain JSON node ids",
    );
}