reddb-io-server 1.2.0

RedDB server-side engine: storage, runtime, replication, MCP, AI, and the gRPC/HTTP/RedWire/PG-wire dispatchers. Re-exported by the umbrella `reddb` crate.
Documentation
//! Parser for TREE commands and CREATE/DROP TREE.

use super::super::ast::{
    CreateTreeQuery, DropTreeQuery, QueryExpr, TreeCommand, TreeNodeSpec, TreePosition,
};
use super::super::lexer::Token;
use super::error::ParseError;
use super::Parser;
use crate::json::Value as JsonValue;
use crate::storage::schema::Value;

impl<'a> Parser<'a> {
    pub fn parse_create_tree_body(&mut self) -> Result<QueryExpr, ParseError> {
        let if_not_exists = self.match_if_not_exists()?;
        let name = self.expect_ident()?;
        self.expect(Token::In)?;
        let collection = self.expect_ident()?;
        self.expect_tree_ident("ROOT")?;
        let root = self.parse_tree_node_spec(false)?;
        let default_max_children = self.parse_tree_required_max_children()?;

        Ok(QueryExpr::CreateTree(CreateTreeQuery {
            collection,
            name,
            root,
            default_max_children,
            if_not_exists,
        }))
    }

    pub fn parse_drop_tree_body(&mut self) -> Result<QueryExpr, ParseError> {
        let if_exists = self.match_if_exists()?;
        let name = self.expect_ident()?;
        self.expect(Token::In)?;
        let collection = self.expect_ident()?;
        Ok(QueryExpr::DropTree(DropTreeQuery {
            collection,
            name,
            if_exists,
        }))
    }

    pub fn parse_tree_command(&mut self) -> Result<QueryExpr, ParseError> {
        self.expect(Token::Tree)?;

        if self.consume(&Token::Insert)? {
            self.expect(Token::Into)?;
            let (collection, tree_name) = self.parse_tree_target()?;
            self.expect_tree_ident("PARENT")?;
            let parent_id = self.parse_tree_entity_id()?;
            let node = self.parse_tree_node_spec(true)?;
            let position = self.parse_tree_position()?;
            return Ok(QueryExpr::TreeCommand(TreeCommand::Insert {
                collection,
                tree_name,
                parent_id,
                node,
                position,
            }));
        }

        if self.consume_ident_ci("MOVE")? {
            let (collection, tree_name) = self.parse_tree_target()?;
            self.expect(Token::Node)?;
            let node_id = self.parse_tree_entity_id()?;
            self.expect(Token::To)?;
            self.expect_tree_ident("PARENT")?;
            let parent_id = self.parse_tree_entity_id()?;
            let position = self.parse_tree_position()?;
            return Ok(QueryExpr::TreeCommand(TreeCommand::Move {
                collection,
                tree_name,
                node_id,
                parent_id,
                position,
            }));
        }

        if self.consume(&Token::Delete)? {
            let (collection, tree_name) = self.parse_tree_target()?;
            self.expect(Token::Node)?;
            let node_id = self.parse_tree_entity_id()?;
            return Ok(QueryExpr::TreeCommand(TreeCommand::Delete {
                collection,
                tree_name,
                node_id,
            }));
        }

        if self.consume_ident_ci("VALIDATE")? {
            let (collection, tree_name) = self.parse_tree_target()?;
            return Ok(QueryExpr::TreeCommand(TreeCommand::Validate {
                collection,
                tree_name,
            }));
        }

        if self.consume_ident_ci("REBALANCE")? {
            let (collection, tree_name) = self.parse_tree_target()?;
            let dry_run = if self.consume_ident_ci("DRY")? {
                self.expect_tree_ident("RUN")?;
                true
            } else {
                false
            };
            return Ok(QueryExpr::TreeCommand(TreeCommand::Rebalance {
                collection,
                tree_name,
                dry_run,
            }));
        }

        Err(ParseError::expected(
            vec!["INSERT", "MOVE", "DELETE", "VALIDATE", "REBALANCE"],
            self.peek(),
            self.position(),
        ))
    }

    fn parse_tree_target(&mut self) -> Result<(String, String), ParseError> {
        let collection = self.expect_ident()?;
        self.expect(Token::Dot)?;
        let tree_name = self.expect_ident()?;
        Ok((collection, tree_name))
    }

    fn parse_tree_node_spec(
        &mut self,
        allow_max_children: bool,
    ) -> Result<TreeNodeSpec, ParseError> {
        self.expect_tree_ident("LABEL")?;
        let label = self.parse_tree_string_like()?;
        let mut node_type = None;
        let mut properties = Vec::new();
        let mut metadata = Vec::new();
        let mut max_children = None;

        loop {
            if self.consume_ident_ci("TYPE")? {
                node_type = Some(self.parse_tree_string_like()?);
            } else if self.consume(&Token::Properties)? {
                properties = self.parse_tree_object_literal_entries()?;
            } else if self.consume(&Token::Metadata)? {
                metadata = self.parse_tree_object_literal_entries()?;
            } else if allow_max_children
                && (self.consume_ident_ci("MAX_CHILDREN")?
                    || self.consume_ident_ci("MAXCHILDREN")?)
            {
                max_children = Some(self.parse_tree_positive_usize()?);
            } else {
                break;
            }
        }

        Ok(TreeNodeSpec {
            label,
            node_type,
            properties,
            metadata,
            max_children,
        })
    }

    fn parse_tree_position(&mut self) -> Result<TreePosition, ParseError> {
        if !(self.consume_ident_ci("POSITION")?) {
            return Ok(TreePosition::Last);
        }

        if self.consume(&Token::First)? {
            return Ok(TreePosition::First);
        }
        if self.consume(&Token::Last)? {
            return Ok(TreePosition::Last);
        }

        Ok(TreePosition::Index(self.parse_tree_positive_usize()?))
    }

    fn parse_tree_required_max_children(&mut self) -> Result<usize, ParseError> {
        if !(self.consume_ident_ci("MAX_CHILDREN")? || self.consume_ident_ci("MAXCHILDREN")?) {
            return Err(ParseError::expected(
                vec!["MAX_CHILDREN"],
                self.peek(),
                self.position(),
            ));
        }
        self.parse_tree_positive_usize()
    }

    fn parse_tree_entity_id(&mut self) -> Result<u64, ParseError> {
        match self.peek().clone() {
            Token::Integer(value) if value > 0 => {
                self.advance()?;
                Ok(value as u64)
            }
            Token::String(value) => {
                self.advance()?;
                parse_tree_entity_id_text(&value).ok_or_else(|| {
                    ParseError::new(
                        // F-05: `value` is caller-controlled string-literal
                        // bytes. Render via `{:?}` so embedded CR/LF/NUL/
                        // quotes are escaped before the message reaches the
                        // downstream JSON / audit / log / gRPC sinks.
                        format!("invalid tree entity id {value:?}"),
                        self.position(),
                    )
                })
            }
            other => Err(ParseError::expected(
                vec!["entity id"],
                &other,
                self.position(),
            )),
        }
    }

    fn parse_tree_positive_usize(&mut self) -> Result<usize, ParseError> {
        let value = self.parse_integer()?;
        if value <= 0 {
            return Err(ParseError::new(
                "expected a positive integer".to_string(),
                self.position(),
            ));
        }
        Ok(value as usize)
    }

    fn parse_tree_string_like(&mut self) -> Result<String, ParseError> {
        match self.peek().clone() {
            Token::String(value) => {
                self.advance()?;
                Ok(value)
            }
            _ => self.expect_ident_or_keyword(),
        }
    }

    fn expect_tree_ident(&mut self, expected: &str) -> Result<(), ParseError> {
        if self.consume_ident_ci(expected)? {
            return Ok(());
        }
        Err(ParseError::expected(
            vec![expected],
            self.peek(),
            self.position(),
        ))
    }

    fn parse_tree_object_literal_entries(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
        let literal = self.parse_literal_value()?;
        let Value::Json(bytes) = literal else {
            return Err(ParseError::new(
                "expected object literal".to_string(),
                self.position(),
            ));
        };
        let decoded = crate::json::from_slice::<JsonValue>(&bytes).map_err(|err| {
            ParseError::new(
                // F-05: serde's parse error string can echo a user fragment.
                // Render via `{:?}` so embedded control bytes / quotes are
                // escaped before the message reaches downstream sinks.
                format!("failed to decode object literal: {:?}", err.to_string()),
                self.position(),
            )
        })?;
        let JsonValue::Object(object) = decoded else {
            return Err(ParseError::new(
                "expected object literal".to_string(),
                self.position(),
            ));
        };
        object
            .into_iter()
            .map(|(key, value)| Ok((key, tree_json_value_to_storage_value(&value)?)))
            .collect()
    }
}

fn parse_tree_entity_id_text(value: &str) -> Option<u64> {
    value
        .strip_prefix('e')
        .unwrap_or(value)
        .parse::<u64>()
        .ok()
        .filter(|value| *value > 0)
}

fn tree_json_value_to_storage_value(value: &JsonValue) -> Result<Value, ParseError> {
    Ok(match value {
        JsonValue::Null => Value::Null,
        JsonValue::Bool(value) => Value::Boolean(*value),
        JsonValue::Number(value) => {
            if value.fract().abs() < f64::EPSILON {
                Value::Integer(*value as i64)
            } else {
                Value::Float(*value)
            }
        }
        JsonValue::String(value) => Value::text(value.clone()),
        JsonValue::Array(_) | JsonValue::Object(_) => {
            Value::Json(crate::json::to_vec(value).map_err(|err| {
                ParseError::new(
                    // F-05: defensively escape encoder error in case it
                    // echoes a user fragment.
                    format!("failed to encode nested JSON value: {:?}", err.to_string()),
                    crate::storage::query::lexer::Position::default(),
                )
            })?)
        }
    })
}