linkrs-query 0.1.0

Query engine, parser, and executor for Linkrs
Documentation
//! Cypher Style CREATE Data Statement Integration Testing
//!
//! Test Range.
//! - CREATE (n:Label {prop: value}) - createnode
//! - CREATE (a)-[:Type {prop: value}]->(b) - createedge
//! - CREATE (a:Label1)-[:Type]->(b:Label2) - createpath
//! - Automatic Schema Inference and Creation

mod common;

use common::TestStorage;

use linkrs_metrics::StatsManager;
use linkrs_query::optimizer::OptimizerEngine;
use linkrs_query::parser::Parser;
use linkrs_query::pipeline::QueryPipelineManager;
use std::sync::Arc;

// ==================== CREATE node test ====================

#[test]
fn test_create_cypher_node_basic() {
    let query = "CREATE (n:Person {name: 'Alice', age: 30})";
    let mut parser = Parser::new(query);

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

    let stmt = result.expect("CREATEstatement: should succeed");
    assert_eq!(stmt.ast.stmt.kind(), "CREATE");
}

#[test]
fn test_create_cypher_node_without_props() {
    let query = "CREATE (n:Person)";
    let mut parser = Parser::new(query);

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

#[test]
fn test_create_cypher_node_multiple_labels() {
    let query = "CREATE (n:Person:Employee {name: 'Alice', department: 'Engineering'})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(
        result.is_ok(),
        "Cypher CREATE nodemulti-label: should succeed: {:?}",
        result.err()
    );
}

#[test]
fn test_create_cypher_node_without_variable() {
    let query = "CREATE (:Person {name: 'Bob'})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(
        result.is_ok(),
        "Cypher CREATE nodewithout variable: should succeed: {:?}",
        result.err()
    );
}

#[test]
fn test_create_cypher_node_complex_props() {
    let query = r#"CREATE (n:Person {
        name: 'Charlie',
        age: 35,
        salary: 50000.50,
        is_active: true,
        created_at: '2024-01-01T00:00:00'
    })"#;
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_cypher_edge_basic() {
    let query = "CREATE (a)-[:KNOWS {since: '2020-01-01', degree: 0.8}]->(b)";
    let mut parser = Parser::new(query);

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

    let stmt = result.expect("CREATEstatement: should succeed");
    assert_eq!(stmt.ast.stmt.kind(), "CREATE");
}

#[test]
fn test_create_cypher_edge_without_props() {
    let query = "CREATE (a)-[:FRIEND]->(b)";
    let mut parser = Parser::new(query);

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

#[test]
fn test_create_cypher_edge_bidirectional() {
    let query = "CREATE (a)-[:COLLEAGUE]-(b)";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_cypher_edge_left_to_right() {
    let query = "CREATE (a)<-[:FOLLOWS]-(b)";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_cypher_path_basic() {
    let query = "CREATE (a:Person)-[:KNOWS]->(b:Person)";
    let mut parser = Parser::new(query);

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

#[test]
fn test_create_cypher_path_with_props() {
    let query = "CREATE (a:Person {name: 'Alice'})-[:KNOWS {since: '2020-01-01'}]->(b:Person {name: 'Bob'})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(
        result.is_ok(),
        "Cypher CREATE pathwith properties: should succeed: {:?}",
        result.err()
    );
}

#[test]
fn test_create_cypher_long_path() {
    let query = "CREATE (a:Person)-[:KNOWS]->(b:Person)-[:WORKS_AT]->(c:Company)";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_cypher_multiple_nodes() {
    let query =
        "CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}), (c:Person {name: 'Charlie'})";
    let mut parser = Parser::new(query);

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

#[test]
fn test_create_cypher_mixed_patterns() {
    let query = "CREATE (a:Person {name: 'Alice'}), (a)-[:KNOWS]->(b:Person {name: 'Bob'})";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

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

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

    let create_space = "CREATE SPACE IF NOT EXISTS test_space";
    let _ = pipeline_manager.execute_query(create_space);

    let use_space = "USE test_space";
    let _ = pipeline_manager.execute_query(use_space);

    let query = "CREATE (n:Person {name: 'Alice', age: 30})";
    let result = pipeline_manager.execute_query(query);
    assert_create_data_rejected(result);
}

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

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

    let create_space = "CREATE SPACE IF NOT EXISTS test_space";
    let _ = pipeline_manager.execute_query(create_space);

    let use_space = "USE test_space";
    let _ = pipeline_manager.execute_query(use_space);

    let query = "CREATE (a:Person {name: 'Alice'})-[:KNOWS {since: '2020-01-01'}]->(b:Person {name: 'Bob'})";
    let result = pipeline_manager.execute_query(query);
    assert_create_data_rejected(result);
}

fn assert_create_data_rejected(
    result: Result<linkrs_query::executor::base::ExecutionResult, linkrs_core::error::DBError>,
) {
    match result {
        Ok(linkrs_query::executor::base::ExecutionResult::Error(message)) => {
            assert!(
                message.contains("use INSERT VERTEX / INSERT EDGE"),
                "CREATE data should be rejected explicitly, got: {}",
                message
            );
        }
        Err(error) => {
            assert!(
                format!("{:?}", error).contains("use INSERT VERTEX / INSERT EDGE"),
                "CREATE data should be rejected explicitly, got: {:?}",
                error
            );
        }
        other => panic!("CREATE data should be rejected, got: {:?}", other),
    }
}

#[test]
fn test_create_cypher_invalid_syntax() {
    let query = "CREATE n:Person {name: 'Alice'}";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_cypher_empty_label() {
    let query = "CREATE (n {})";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_cypher_nested_props() {
    let query = "CREATE (n:Person {address: {city: 'Beijing', street: 'Main St'}})";
    let mut parser = Parser::new(query);

    let _result = parser.parse();
}

#[test]
fn test_create_parse_only_string_prop() {
    let query = "CREATE (n:Person {name: 'Alice'})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(result.is_ok(), "String property parsing should succeed");
}

#[test]
fn test_create_parse_only_int_prop() {
    let query = "CREATE (n:Person {age: 30})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(result.is_ok(), "Integer attribute parsing should succeed");
}

#[test]
fn test_create_parse_only_float_prop() {
    let query = "CREATE (n:Person {salary: 50000.50})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(
        result.is_ok(),
        "Floating point attribute parsing should succeed"
    );
}

#[test]
fn test_create_parse_only_bool_prop() {
    let query = "CREATE (n:Person {is_active: true})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(result.is_ok(), "Boolean attribute parsing should succeed");
}

#[test]
fn test_create_parse_only_mixed_props() {
    let query = "CREATE (n:Person {name: 'Alice', age: 30, salary: 50000.50, is_active: true})";
    let mut parser = Parser::new(query);

    let result = parser.parse();
    assert!(
        result.is_ok(),
        "Mixed type attribute parsing should succeed"
    );
}

#[test]
fn test_cypher_vs_ngql_create_node() {
    let cypher_query = "CREATE (n:Person {name: 'Alice', age: 30})";
    let mut cypher_parser = Parser::new(cypher_query);
    let cypher_result = cypher_parser.parse();

    let ngql_query = "INSERT VERTEX Person(name, age) VALUES 1:('Alice', 30)";
    let mut ngql_parser = Parser::new(ngql_query);
    let ngql_result = ngql_parser.parse();

    assert!(
        cypher_result.is_ok(),
        "Cypher syntax should parse successfully"
    );
    assert!(
        ngql_result.is_ok(),
        "The NGQL syntax should parse successfully"
    );
}

#[test]
fn test_cypher_vs_ngql_create_edge() {
    let cypher_query = "CREATE (a)-[:KNOWS {since: '2020-01-01'}]->(b)";
    let mut cypher_parser = Parser::new(cypher_query);
    let cypher_result = cypher_parser.parse();

    let ngql_query = "INSERT EDGE KNOWS(since) VALUES 1 -> 2:('2020-01-01')";
    let mut ngql_parser = Parser::new(ngql_query);
    let _ = ngql_parser.parse();

    assert!(
        cypher_result.is_ok(),
        "Cypher syntax should parse successfully"
    );
}