selene-db-gql 1.3.0

ISO/IEC 39075:2024 GQL parser, planner, optimizer, and executor for selene-db.
Documentation
//! Expression and subquery depth guards for the bind pass.

use crate::{
    ExistsBody, IsCheckKind, MatchClause, PipelineStatement, QueryPipeline, ReturnClause,
    ValueExpr, analyze::error::AnalysisError,
};

use super::ANALYZER_MAX_DEPTH;

pub(super) fn check_expr_depth(expr: &ValueExpr) -> Result<(), AnalysisError> {
    let mut stack = vec![(expr, 1_u32)];
    while let Some((expr, depth)) = stack.pop() {
        if depth > ANALYZER_MAX_DEPTH {
            return Err(AnalysisError::RecursionLimitExceeded { depth });
        }
        let next = depth.saturating_add(1);
        match expr {
            ValueExpr::PropertyAccess { target, .. } => stack.push((target, next)),
            ValueExpr::ListLiteral { items, .. } => {
                stack.extend(items.iter().rev().map(|item| (item, next)));
            }
            ValueExpr::PathConstructor { elements, .. } => {
                stack.extend(elements.iter().rev().map(|element| (element, next)));
            }
            ValueExpr::RecordLiteral { fields, .. } => {
                stack.extend(fields.iter().rev().map(|(_, value)| (value, next)));
            }
            ValueExpr::BinaryOp { lhs, rhs, .. } => {
                stack.push((rhs, next));
                stack.push((lhs, next));
            }
            ValueExpr::UnaryOp { operand, .. } => stack.push((operand, next)),
            ValueExpr::FunctionCall { args, .. } => {
                stack.extend(args.iter().rev().map(|arg| (arg, next)));
            }
            ValueExpr::DurationBetween { start, end, .. } => {
                stack.push((end, next));
                stack.push((start, next));
            }
            ValueExpr::IsCheck { operand, kind, .. } => {
                stack.push((operand, next));
                match kind {
                    IsCheckKind::SourceOf(value) | IsCheckKind::DestinationOf(value) => {
                        stack.push((value, next));
                    }
                    IsCheckKind::Null
                    | IsCheckKind::Directed
                    | IsCheckKind::Labeled(_)
                    | IsCheckKind::TruthValue(_)
                    | IsCheckKind::Typed(_)
                    | IsCheckKind::Normalized(_) => {}
                }
            }
            ValueExpr::InList { operand, list, .. } => {
                stack.extend(list.iter().rev().map(|item| (item, next)));
                stack.push((operand, next));
            }
            ValueExpr::InListExpression { operand, list, .. } => {
                stack.push((list, next));
                stack.push((operand, next));
            }
            ValueExpr::AllDifferent { items, .. } | ValueExpr::Same { items, .. } => {
                stack.extend(items.iter().rev().map(|item| (item, next)));
            }
            ValueExpr::PropertyExists { target, .. } => stack.push((target, next)),
            ValueExpr::Case {
                branches,
                else_branch,
                ..
            } => {
                if let Some(value) = else_branch {
                    stack.push((value, next));
                }
                for (condition, value) in branches.iter().rev() {
                    stack.push((value, next));
                    stack.push((condition, next));
                }
            }
            ValueExpr::Cast { value, .. } | ValueExpr::Normalize { source: value, .. } => {
                stack.push((value, next));
            }
            ValueExpr::Trim {
                character, source, ..
            } => {
                stack.push((source, next));
                if let Some(character) = character {
                    stack.push((character, next));
                }
            }
            ValueExpr::Literal(_)
            | ValueExpr::Variable { .. }
            | ValueExpr::Parameter { .. }
            | ValueExpr::Exists { .. }
            | ValueExpr::ValueSubquery { .. } => {}
        }
    }
    Ok(())
}

pub(super) fn check_subquery_depth(expr: &ValueExpr) -> Result<(), AnalysisError> {
    check_expr_subquery_depth(expr, 1)
}

pub(crate) fn check_query_subquery_depth(
    pipeline: &QueryPipeline,
    depth: u32,
) -> Result<(), AnalysisError> {
    if depth > ANALYZER_MAX_DEPTH {
        return Err(AnalysisError::RecursionLimitExceeded { depth });
    }
    for statement in &pipeline.statements {
        match statement {
            PipelineStatement::Match(clause) => {
                check_match_clause_subquery_depth(clause, depth)?;
            }
            PipelineStatement::Filter(value) => check_expr_subquery_depth(value, depth)?,
            PipelineStatement::Let(bindings) => {
                for binding in bindings {
                    check_expr_subquery_depth(&binding.value, depth)?;
                }
            }
            PipelineStatement::For(statement) => {
                check_expr_subquery_depth(&statement.source, depth)?
            }
            PipelineStatement::Sorting(terms) => {
                for term in terms {
                    check_expr_subquery_depth(&term.expr, depth)?;
                }
            }
            PipelineStatement::Return(clause) => check_return_subquery_depth(clause, depth)?,
            PipelineStatement::With(clause) => {
                for item in &clause.items {
                    check_expr_subquery_depth(&item.expr, depth)?;
                }
                if let Some(group_by) = &clause.group_by {
                    for value in group_by {
                        check_expr_subquery_depth(value, depth)?;
                    }
                }
                if let Some(value) = &clause.having {
                    check_expr_subquery_depth(value, depth)?;
                }
                if let Some(value) = &clause.where_clause {
                    check_expr_subquery_depth(value, depth)?;
                }
            }
            PipelineStatement::Call(call) => {
                for arg in &call.args {
                    check_expr_subquery_depth(arg, depth)?;
                }
            }
            PipelineStatement::CallSubquery(call) => {
                check_query_subquery_depth(&call.body, depth.saturating_add(1))?;
            }
            PipelineStatement::Limit(_) | PipelineStatement::Offset(_) => {}
        }
    }
    Ok(())
}

fn check_match_clause_subquery_depth(
    clause: &MatchClause,
    depth: u32,
) -> Result<(), AnalysisError> {
    for pattern in &clause.patterns {
        for element in &pattern.elements {
            match element {
                crate::PatternElement::Node(node) => {
                    for (_, value) in &node.properties {
                        check_expr_subquery_depth(value, depth)?;
                    }
                    if let Some(value) = &node.inline_where {
                        check_expr_subquery_depth(value, depth)?;
                    }
                }
                crate::PatternElement::Edge(edge) => {
                    for (_, value) in &edge.properties {
                        check_expr_subquery_depth(value, depth)?;
                    }
                    if let Some(value) = &edge.inline_where {
                        check_expr_subquery_depth(value, depth)?;
                    }
                }
            }
        }
    }
    if let Some(value) = &clause.where_clause {
        check_expr_subquery_depth(value, depth)?;
    }
    Ok(())
}

fn check_return_subquery_depth(clause: &ReturnClause, depth: u32) -> Result<(), AnalysisError> {
    for item in &clause.items {
        check_expr_subquery_depth(&item.expr, depth)?;
    }
    if let Some(group_by) = &clause.group_by {
        for value in group_by {
            check_expr_subquery_depth(value, depth)?;
        }
    }
    if let Some(value) = &clause.having {
        check_expr_subquery_depth(value, depth)?;
    }
    Ok(())
}

fn check_expr_subquery_depth(expr: &ValueExpr, depth: u32) -> Result<(), AnalysisError> {
    let mut stack = vec![(expr, depth)];
    while let Some((expr, depth)) = stack.pop() {
        match expr {
            ValueExpr::PropertyAccess { target, .. } => stack.push((target, depth)),
            ValueExpr::ListLiteral { items, .. }
            | ValueExpr::PathConstructor {
                elements: items, ..
            }
            | ValueExpr::AllDifferent { items, .. }
            | ValueExpr::Same { items, .. } => {
                stack.extend(items.iter().rev().map(|item| (item, depth)));
            }
            ValueExpr::RecordLiteral { fields, .. } => {
                stack.extend(fields.iter().rev().map(|(_, value)| (value, depth)));
            }
            ValueExpr::BinaryOp { lhs, rhs, .. } => {
                stack.push((rhs, depth));
                stack.push((lhs, depth));
            }
            ValueExpr::UnaryOp { operand, .. }
            | ValueExpr::PropertyExists {
                target: operand, ..
            } => stack.push((operand, depth)),
            ValueExpr::FunctionCall { args, .. } | ValueExpr::InList { list: args, .. } => {
                stack.extend(args.iter().rev().map(|arg| (arg, depth)));
                if let ValueExpr::InList { operand, .. } = expr {
                    stack.push((operand, depth));
                }
            }
            ValueExpr::DurationBetween { start, end, .. } => {
                stack.push((end, depth));
                stack.push((start, depth));
            }
            ValueExpr::InListExpression { operand, list, .. } => {
                stack.push((list, depth));
                stack.push((operand, depth));
            }
            ValueExpr::IsCheck { operand, kind, .. } => {
                stack.push((operand, depth));
                match kind {
                    IsCheckKind::SourceOf(value) | IsCheckKind::DestinationOf(value) => {
                        stack.push((value, depth));
                    }
                    IsCheckKind::Null
                    | IsCheckKind::Directed
                    | IsCheckKind::Labeled(_)
                    | IsCheckKind::TruthValue(_)
                    | IsCheckKind::Typed(_)
                    | IsCheckKind::Normalized(_) => {}
                }
            }
            ValueExpr::Case {
                branches,
                else_branch,
                ..
            } => {
                if let Some(value) = else_branch {
                    stack.push((value, depth));
                }
                for (condition, value) in branches.iter().rev() {
                    stack.push((value, depth));
                    stack.push((condition, depth));
                }
            }
            ValueExpr::ValueSubquery { body, .. } => {
                check_query_subquery_depth(body, depth.saturating_add(1))?;
            }
            ValueExpr::Exists { body, .. } => match body {
                ExistsBody::Match(pattern) => {
                    check_match_clause_subquery_depth(pattern, depth.saturating_add(1))?;
                }
                ExistsBody::Query(pipeline) => {
                    check_query_subquery_depth(pipeline, depth.saturating_add(1))?;
                }
            },
            ValueExpr::Cast { value, .. } => stack.push((value, depth)),
            ValueExpr::Normalize { source, .. } => stack.push((source, depth)),
            ValueExpr::Trim {
                character, source, ..
            } => {
                stack.push((source, depth));
                if let Some(character) = character {
                    stack.push((character, depth));
                }
            }
            ValueExpr::Literal(_) | ValueExpr::Variable { .. } | ValueExpr::Parameter { .. } => {}
        }
    }
    Ok(())
}