selene-db-gql 1.3.0

ISO/IEC 39075:2024 GQL parser, planner, optimizer, and executor for selene-db.
Documentation
//! Mutation-pipeline bind handling.

use crate::{
    DeleteStatement, ElementKind, MutationPipeline, MutationStatement, MutationTerminator,
    RemoveItem, SetItem,
    analyze::{
        binding::BindingUseKind,
        error::{AnalysisError, ConditionClause},
        write_set::WriteKind,
    },
};

use super::{BindContext, expr, pattern, query};

pub(crate) fn bind_mutation_pipeline(
    ctx: &mut BindContext,
    pipeline: &MutationPipeline,
) -> Result<(), AnalysisError> {
    for (statement_index, statement) in pipeline.statements.iter().enumerate() {
        bind_mutation_statement(ctx, statement_index, statement)?;
    }
    if let Some(terminator) = &pipeline.terminator {
        match terminator {
            MutationTerminator::Return(clause) => query::bind_return_clause(ctx, clause)?,
            MutationTerminator::Finish(_) => {}
        }
    }
    Ok(())
}

fn bind_mutation_statement(
    ctx: &mut BindContext,
    statement_index: usize,
    statement: &MutationStatement,
) -> Result<(), AnalysisError> {
    match statement {
        MutationStatement::Match(clause) => pattern::bind_match_clause(ctx, clause),
        MutationStatement::Filter(value) => {
            expr::bind_condition(ctx, value, ConditionClause::Filter)?;
            Ok(())
        }
        MutationStatement::Insert(insert) => {
            for graph_pattern in &insert.patterns {
                pattern::bind_insert_graph_pattern(ctx, statement_index, graph_pattern)?;
            }
            Ok(())
        }
        MutationStatement::Set(items) => bind_set_items(ctx, statement_index, items),
        MutationStatement::Remove(items) => bind_remove_items(ctx, statement_index, items),
        MutationStatement::Delete(statement) => bind_delete(ctx, statement_index, statement),
    }
}

fn bind_set_items(
    ctx: &mut BindContext,
    statement_index: usize,
    items: &[SetItem],
) -> Result<(), AnalysisError> {
    for item in items {
        match item {
            SetItem::Property {
                target,
                key,
                value,
                span,
            } => {
                let target = ctx.resolve(target.clone(), *span, BindingUseKind::SetTarget)?;
                expr::bind_value_expr(ctx, value)?;
                let element = ctx.element_kind(target);
                ctx.record_write(
                    statement_index,
                    *span,
                    WriteKind::SetProperty {
                        target,
                        element,
                        key: key.clone(),
                        value_span: value.span(),
                    },
                );
            }
            SetItem::PropertyMerge {
                target,
                properties,
                span,
            } => {
                let target = ctx.resolve(target.clone(), *span, BindingUseKind::SetTarget)?;
                let element = ctx.element_kind(target);
                for (_, value) in properties {
                    expr::bind_value_expr(ctx, value)?;
                }
                for (key, value) in properties {
                    ctx.record_write(
                        statement_index,
                        *span,
                        WriteKind::SetProperty {
                            target,
                            element,
                            key: key.clone(),
                            value_span: value.span(),
                        },
                    );
                }
            }
            SetItem::Label {
                target,
                label,
                span,
            } => {
                let target = ctx.resolve(target.clone(), *span, BindingUseKind::SetTarget)?;
                let element = ctx.element_kind(target);
                ctx.record_write(
                    statement_index,
                    *span,
                    WriteKind::SetLabel {
                        target,
                        element,
                        label: label.clone(),
                    },
                );
            }
        }
    }
    Ok(())
}

fn bind_remove_items(
    ctx: &mut BindContext,
    statement_index: usize,
    items: &[RemoveItem],
) -> Result<(), AnalysisError> {
    for item in items {
        match item {
            RemoveItem::Property { target, key, span } => {
                let target = ctx.resolve(target.clone(), *span, BindingUseKind::RemoveTarget)?;
                let element = ctx.element_kind(target);
                ctx.record_write(
                    statement_index,
                    *span,
                    WriteKind::RemoveProperty {
                        target,
                        element,
                        key: key.clone(),
                    },
                );
            }
            RemoveItem::Label {
                target,
                label,
                span,
            } => {
                let target = ctx.resolve(target.clone(), *span, BindingUseKind::RemoveTarget)?;
                let element = ctx.element_kind(target);
                if matches!(element, ElementKind::Edge) {
                    return Err(AnalysisError::NotImplemented {
                        message: "REMOVE label on edge is not supported; edge labels are immutable"
                            .to_owned(),
                        span: *span,
                        hint: None,
                    });
                }
                ctx.record_write(
                    statement_index,
                    *span,
                    WriteKind::RemoveLabel {
                        target,
                        element,
                        label: label.clone(),
                    },
                );
            }
        }
    }
    Ok(())
}

fn bind_delete(
    ctx: &mut BindContext,
    statement_index: usize,
    statement: &DeleteStatement,
) -> Result<(), AnalysisError> {
    for item in &statement.items {
        let target = ctx.resolve(item.clone(), statement.span, BindingUseKind::DeleteTarget)?;
        let element = ctx.element_kind(target);
        if matches!(element, ElementKind::Path | ElementKind::Alias) {
            return Err(AnalysisError::InvalidReference {
                message: "DELETE target must be a node or edge binding".to_owned(),
                span: statement.span,
            });
        }
        ctx.record_write(
            statement_index,
            statement.span,
            WriteKind::DeleteTarget {
                target,
                element,
                mode: statement.mode,
            },
        );
    }
    Ok(())
}