linkrs-query 0.1.0

Query engine, parser, and executor for Linkrs
Documentation
use std::sync::Arc;

use crate::parser::ast::stmt::{Ast, Stmt};
use crate::parser::core::error::{ParseError, ParseErrorKind};
use crate::parser::parsing::expr_parser::parse_expression_with_context;
use crate::parser::parsing::parse_context::{ParseContext, RecoveryScope};
use crate::parser::parsing::stmt_parser::StmtParser;
use crate::parser::TokenKind;
use linkrs_core::types::expr::contextual::ContextualExpression;
use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;

/// Parser analysis results, including the AST (Statement + Expression Context).
#[derive(Debug, Clone)]
pub struct ParserResult {
    /// Parsed AST (using Arc for shared ownership)
    pub ast: Arc<Ast>,
}

pub struct Parser<'a> {
    ctx: ParseContext<'a>,
    expr_context: Arc<ExpressionAnalysisContext>,
}

impl<'a> Parser<'a> {
    pub fn new(input: &'a str) -> Self {
        let expr_context = Arc::new(ExpressionAnalysisContext::new());
        let mut ctx = ParseContext::new(input);
        ctx.set_expression_context(expr_context.clone());

        Self { ctx, expr_context }
    }

    /// Attach the shared type-alias catalog so unknown type names resolve
    /// through user-defined aliases (`CAST(x AS alias)`, DDL column types).
    /// Defaults to off (previous behavior) when unset.
    pub fn with_type_alias_manager(
        mut self,
        manager: Arc<linkrs_core::metadata::TypeAliasManager>,
    ) -> Self {
        self.ctx.set_type_alias_manager(manager);
        self
    }

    pub fn from_string(input: String) -> Self {
        let expr_context = Arc::new(ExpressionAnalysisContext::new());
        let mut ctx = ParseContext::from_string(input);
        ctx.set_expression_context(expr_context.clone());

        Self { ctx, expr_context }
    }

    pub fn set_compat_mode(&mut self, enabled: bool) {
        self.ctx.set_compat_mode(enabled);
    }

    pub fn parse(&mut self) -> Result<ParserResult, ParseError> {
        let expr_context = Arc::new(ExpressionAnalysisContext::new());
        self.ctx.set_expression_context(expr_context.clone());
        self.expr_context = expr_context;
        self.ctx.reset_recovery_count();

        let stmt = match self.parse_statement() {
            Ok(stmt) => stmt,
            Err(e) => {
                // Recovery may replace the original cause with a generic
                // "Too many parse errors" terminator. Prefer the first recorded
                // error so callers see the actionable diagnostic.
                let recorded = self.ctx.errors();
                if !recorded.is_empty() {
                    let first = &recorded.errors[0];
                    return Err(ParseError {
                        kind: first.kind.clone(),
                        message: Box::from(first.message.as_ref()),
                        position: first.position,
                        offset: first.offset,
                        unexpected_token: first
                            .unexpected_token
                            .as_ref()
                            .map(|t| Box::from(t.as_ref())),
                        expected_tokens: first.expected_tokens.clone(),
                        context: None,
                        hints: first.hints.clone(),
                        recovered: first.recovered,
                    });
                }
                return Err(e);
            }
        };

        // A statement must consume the whole input. An optional trailing
        // semicolon terminator is allowed; any further token is a syntax
        // error, otherwise trailing garbage such as `COMMIT junk` would be
        // silently truncated to `COMMIT` and executed.
        if self.ctx.match_token(TokenKind::Semicolon) {
            // Optional statement terminator consumed.
        }
        if self.ctx.current_token().kind != TokenKind::Eof {
            self.ctx.add_error(ParseError::new(
                ParseErrorKind::UnexpectedToken,
                format!(
                    "Unexpected token after end of statement: {:?}",
                    self.ctx.current_token().kind
                ),
                self.ctx.current_position(),
            ));
        }

        let ast = Ast::new(stmt, self.expr_context.clone());
        Ok(ParserResult { ast: Arc::new(ast) })
    }

    pub fn parse_statement(&mut self) -> Result<Stmt, ParseError> {
        if self.ctx.is_recovery_exhausted() {
            return Err(ParseError::new(
                crate::parser::core::error::ParseErrorKind::SyntaxError,
                "Too many parse errors, aborting".to_string(),
                self.ctx.current_position(),
            ));
        }

        match StmtParser::parse_statement(&mut self.ctx) {
            Ok(stmt) => Ok(stmt),
            Err(e) => {
                if self.ctx.is_recovery_exhausted() {
                    Err(e)
                } else {
                    self.ctx.try_recover(e, RecoveryScope::Statement)?;
                    self.parse_statement()
                }
            }
        }
    }

    pub fn parse_expression_contextual(&mut self) -> Result<ContextualExpression, ParseError> {
        match parse_expression_with_context(&mut self.ctx, self.expr_context.clone()) {
            Ok(expr) => Ok(expr),
            Err(e) => {
                if self.ctx.is_recovery_exhausted() {
                    Err(e)
                } else {
                    self.ctx.try_recover(e, RecoveryScope::Expression)?;
                    self.parse_expression_contextual()
                }
            }
        }
    }

    pub fn expression_context(&self) -> &Arc<ExpressionAnalysisContext> {
        &self.expr_context
    }

    pub fn expression_context_clone(&self) -> Arc<ExpressionAnalysisContext> {
        self.expr_context.clone()
    }

    pub fn has_errors(&self) -> bool {
        self.ctx.has_errors()
    }

    pub fn errors(&self) -> &crate::parser::ParseErrors {
        self.ctx.errors()
    }

    pub fn take_errors(&mut self) -> crate::parser::ParseErrors {
        self.ctx.take_errors()
    }
}