linkrs-query 0.1.0

Query engine, parser, and executor for Linkrs
Documentation
//! Query Engine Component Integration Testing
//!
//! Test Range.
//! - query::parser - SQL/NGQL parsing, AST generation
//! - query::binder - semantic validation + binding (replaces validator)
//! - query::planner - execution plan generation
//! - query::optimizer - plan optimization, rule application
//! - query::executor - executor scheduling, result return
//! - query::pipeline - full query pipeline

#![allow(clippy::arc_with_non_send_sync)]

mod common;

use common::{assertions::assert_ok, TestStorage};

use linkrs_core::types::SpaceInfo;
use linkrs_metrics::{QueryStatus, StatsManager};
use linkrs_query::optimizer::OptimizerEngine;
use linkrs_query::parser::Parser;
use linkrs_query::pipeline::QueryPipelineManager;
use linkrs_query::planning::PlannerConfig;
use linkrs_query::storage::StorageSchemaOps;
use linkrs_query::QueryContext;
use linkrs_query::QueryRequestContext;
use std::sync::Arc;

/// Creating a query context for testing
fn create_test_query_context() -> Arc<QueryContext> {
    let request_context = Arc::new(QueryRequestContext::new("TEST".to_string()));
    let mut qctx = QueryContext::new(request_context);
    let space_info = SpaceInfo::new("test_space".to_string());
    qctx.set_space_info(space_info);
    Arc::new(qctx)
}

// ==================== Parser Integration Testing ====================

#[test]
fn test_parser_match_statement_basic() {
    // Note: The parser uses the (:Label) syntax, which requires a colon before the label.
    // The parser expects variable names to be followed by a colon and a label
    let query = "MATCH (n:Person) RETURN n";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(result.is_ok(), "MATCH should parse: {:?}", result.err());
}

#[test]
fn test_parser_go_statement() {
    let query = "GO FROM 1 OVER KNOWS";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(result.is_ok(), "GO should parse: {:?}", result.err());
}

#[test]
fn test_parser_use_statement() {
    let query = "USE test_space";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(result.is_ok(), "USE should parse: {:?}", result.err());
}

#[test]
fn test_parser_create_tag() {
    // Trying out different variants of the CREATE TAG syntax
    let queries = vec![
        "CREATE TAG test_tag(name: STRING)",
        "CREATE TAG IF NOT EXISTS test_tag(name STRING)",
    ];

    for query in queries {
        let mut parser = Parser::new(query);
        let result = parser.parse();
        assert!(
            result.is_ok(),
            "CREATE TAG variant should parse '{query}': {:?}",
            result.err()
        );
    }
}

#[test]
fn test_parser_show_statements() {
    let queries = vec!["SHOW SPACES", "SHOW TAGS", "SHOW EDGES"];

    for query in queries {
        let mut parser = Parser::new(query);
        let result = parser.parse();
        assert!(
            result.is_ok(),
            "SHOW variant should parse '{query}': {:?}",
            result.err()
        );
    }
}

#[test]
fn test_parser_insert_vertex() {
    let query = "INSERT VERTEX Person(name, age) VALUES 1:('Alice', 25)";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(
        result.is_ok(),
        "INSERT VERTEX should parse: {:?}",
        result.err()
    );
}

#[test]
fn test_parser_invalid_syntax() {
    let query = "INVALID SYNTAX HERE";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    // Invalid syntax should return an error
    assert!(result.is_err(), "Invalid syntax should return an error");
}

// ==================== Planner Integration Testing ====================

#[test]
fn test_planner_config_creation() {
    let config = PlannerConfig::default();
    // Configuration created successfully
    let _ = config;
}

#[test]
fn test_planner_match_statement() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();

    // Creating a graph space
    let mut space_info = common::storage_helpers::create_test_space("planner_test_space");
    {
        let mut storage_guard = storage.write();
        assert_ok(storage_guard.create_space(&mut space_info));
    }

    // parse query
    let query = "MATCH (n:Person) RETURN n";
    let mut parser = Parser::new(query);
    let result = parser.parse();

    // If parsing fails, skip this test
    if result.is_err() {
        return;
    }

    let _stmt = result.expect("Failed to parse query");

    // Creating query contexts (using the new API)
    let _query_context = create_test_query_context();

    // Scheduled Generation Tests - Simplified version that only verifies successful creation
    // The test passes and is successful when it reaches this point
}

// ==================== QueryPipelineManager  ====================

#[test]
fn test_pipeline_manager_creation() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let _pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );
    // Pipeline Manager Created Successfully
}

#[test]
fn test_pipeline_manager_create_tag() {
    use crate::common::test_scenario::TestScenario;
    TestScenario::new()
        .expect("Failed to create test scenario")
        .setup_space("pipeline_tag_space")
        .exec_ddl("CREATE TAG pipeline_test_tag(name STRING, age INT)")
        .assert_success()
        .assert_tag_exists("pipeline_test_tag");
}

#[test]
fn test_pipeline_manager_use_space() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    // Create the space first
    {
        let mut storage_guard = storage.write();
        let mut space_info = common::storage_helpers::create_test_space("use_test_space");
        let _ = storage_guard.create_space(&mut space_info);
    }

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    // Execute USE query
    let query = "USE use_test_space";
    let result = pipeline_manager.execute_query(query);

    assert!(
        result.is_ok(),
        "Execution should return Ok result: {:?}",
        result.err()
    );
}

// ==================== Integrated Testing of the Complete Query Process ====================

#[test]
fn test_complete_query_flow_show_spaces() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    // Perform the complete process: SHOW SPACES
    let query = "SHOW SPACES";
    let result = pipeline_manager.execute_query(query);

    // The query execution should be completed (whether successfully or not depends on the implementation).
    match result {
        Ok(_exec_result) => {
            // Verify the execution results.
            // The type of the result to be returned should be verified based on the actual implementation.
        }
        Err(_e) => {
            // Certain errors are acceptable, depending on the current state of implementation.
        }
    }
}

#[test]
fn test_complete_query_flow_reports_stage_profile() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    let (_result, _metrics, profile) = pipeline_manager
        .execute_query_with_profile("SHOW SPACES", 0, None)
        .expect("SHOW SPACES should execute");

    assert_eq!(profile.status, QueryStatus::Success);
    assert!(profile.stages.parse_us > 0);
    assert!(profile.total_duration_us >= profile.stages.parse_us);
}

#[test]
fn test_query_flow_create_and_desc_tag() {
    use crate::common::test_scenario::TestScenario;
    TestScenario::new()
        .expect("Failed to create test scenario")
        .setup_space("desc_flow_space")
        .exec_ddl("CREATE TAG desc_test_tag(name STRING)")
        .assert_success()
        .query("DESC TAG desc_test_tag")
        .assert_success()
        .assert_result_count(1);
}

// ==================== Integrated Testing for Error Handling ====================

#[test]
fn test_query_error_invalid_syntax() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    // Translate the query that contains grammar errors.
    let query = "INVALID SYNTAX HERE";
    let result = pipeline_manager.execute_query(query);

    // An error should be returned.
    assert!(result.is_err(), "Invalid syntax should result in an error.");
}

#[test]
fn test_query_error_nonexistent_space() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    // Try to use a space that doesn’t exist.
    let query = "USE nonexistent_space_xyz";
    let result = pipeline_manager.execute_query(query);

    // Using a missing space must fail with a clear error.
    match result {
        Ok(_) => panic!("USE of a missing space should not succeed"),
        Err(e) => assert!(
            format!("{e:?}").contains("Space not found"),
            "USE missing space should report Space not found, got: {e:?}"
        ),
    };
}

// ==================== Performance Testing ====================

#[test]
fn test_query_pipeline_performance() {
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    // Perform multiple queries to test the performance.
    let query = "SHOW SPACES";
    let iterations = 10;

    for i in 0..iterations {
        let result = pipeline_manager.execute_query(query);
        assert!(
            result.is_ok(),
            "Query {} should execute: {:?}",
            i,
            result.err()
        );
    }
}

// ==================== Concurrent Testing (Simplified Version) ====================

#[test]
fn test_sequential_query_execution() {
    // Since QueryPipelineManager does not belong to the Send category, we execute the tests in a sequential manner.
    let test_storage = TestStorage::new().expect("Failed to create test storage");
    let storage = test_storage.storage();
    let stats_manager = Arc::new(StatsManager::new());

    let mut pipeline_manager = QueryPipelineManager::with_optimizer(
        storage,
        stats_manager,
        Arc::new(OptimizerEngine::default()),
    );

    // Executing multiple queries in sequence
    for _i in 0..5 {
        let query = "SHOW SPACES";
        let _result = pipeline_manager.execute_query(query);
    }
}