从零构建 Rust SQL 引擎:从解析器到向量化执行器的工程实践

在大数据与数据库领域,理解 SQL 引擎的内部工作原理是每个系统程序员的必修课。本文将用 Rust 从零实现一个简化但完整的 SQL 引擎,覆盖 SQL 语法解析、逻辑计划构建、查询优化到向量化执行的全链路,并分享工程实践中的关键设计决策与性能陷阱。

一、为什么从零构建?

现有数据库系统如 PostgreSQL、DuckDB、ClickHouse 各自拥有复杂的优化器与执行器,代码量动辄数十万行。从零构建不是为了替代它们,而是为了:

  • 深入理解查询执行的每一个环节
  • 针对特定场景做极致优化(如嵌入式分析、边缘计算)
  • 学习 Rust 在系统编程中的类型状态模式与零成本抽象实践

二、SQL 解析器:从 Token 到 AST

SQL 解析是引擎的第一步。经典的解析器生成器(如 YACC/Bison)生成的 C 代码缺乏内存安全保证,而 Rust 的 nom 或手写递归下降解析器则是更安全的选择。

词法分析 (Lexer)

词法分析器将原始 SQL 字符串拆分为 Token 流。以下是核心实现:

#[derive(Debug, Clone, PartialEq)]
pub enum Token {
    // 关键字
    Select, From, Where, Order, By, Limit, And, Or, Not,
    Insert, Into, Values, Create, Table, Index,

    // 运算符
    Eq, Ne, Lt, Gt, Le, Ge,
    Plus, Minus, Star, Slash,

    // 字面量
    Ident(String),
    Number(String),
    String(String),

    // 符号
    LParen, RParen, Comma, Semi,
}

pub struct Lexer {
    input: Vec<char>,
    pos: usize,
}

impl Lexer {
    pub fn new(input: &str) -> Self {
        Lexer { input: input.chars().collect(), pos: 0 }
    }

    pub fn next_token(&mut self) -> Result<Token, String> {
        self.skip_whitespace();

        if self.pos >= self.input.len() {
            return Err("Unexpected EOF".into());
        }

        let ch = self.input[self.pos];

        match ch {
            '=' => { self.pos += 1; Ok(Token::Eq) }
            '!' => {
                self.pos += 1;
                if self.peek() == '=' { self.pos += 1; Ok(Token::Ne) }
                else { Err("Expected != after !".into()) }
            }
            '<' => {
                self.pos += 1;
                match self.peek() {
                    '=' => { self.pos += 1; Ok(Token::Le) }
                    '>' => { self.pos += 1; Ok(Token::Ne) }
                    _ => Ok(Token::Lt),
                }
            }
            '>' => { self.pos += 1; if self.peek()=='='{self.pos+=1;Ok(Token::Ge)} else {Ok(Token::Gt)} }
            '+' => { self.pos += 1; Ok(Token::Plus) }
            '-' => { self.pos += 1; Ok(Token::Minus) }
            '*' => { self.pos += 1; Ok(Token::Star) }
            '/' => { self.pos += 1; Ok(Token::Slash) }
            ',' => { self.pos += 1; Ok(Token::Comma) }
            '(' => { self.pos += 1; Ok(Token::LParen) }
            ')' => { self.pos += 1; Ok(Token::RParen) }
            ';' => { self.pos += 1; Ok(Token::Semi) }
            '\'' => self.read_string(),
            c if c.is_ascii_digit() => self.read_number(),
            c if c.is_alphabetic() || c == '_' => self.read_identifier(),
            _ => Err(format!("Unexpected character: {}", ch)),
        }
    }

    fn read_identifier(&mut self) -> Result<Token, String> {
        let start = self.pos;
        while self.pos < self.input.len() && (self.input[self.pos].is_alphanumeric() || self.input[self.pos] == '_') {
            self.pos += 1;
        }
        let ident: String = self.input[start..self.pos].iter().collect();

        let token = match ident.to_uppercase().as_str() {
            "SELECT" => Token::Select,
            "FROM" => Token::From,
            "WHERE" => Token::Where,
            "ORDER" => Token::Order,
            "BY" => Token::By,
            "LIMIT" => Token::Limit,
            "AND" => Token::And,
            "OR" => Token::Or,
            "NOT" => Token::Not,
            "INSERT" => Token::Insert,
            "INTO" => Token::Into,
            "VALUES" => Token::Values,
            _ => Token::Ident(ident),
        };
        Ok(token)
    }

    fn read_number(&mut self) -> Result<Token, String> {
        let start = self.pos;
        while self.pos < self.input.len() && (self.input[self.pos].is_ascii_digit() || self.input[self.pos] == '.') {
            self.pos += 1;
        }
        Ok(Token::Number(self.input[start..self.pos].iter().collect()))
    }

    fn read_string(&mut self) -> Result<Token, String> {
        self.pos += 1; // skip opening quote
        let start = self.pos;
        while self.pos < self.input.len() && self.input[self.pos] != '\'' {
            self.pos += 1;
        }
        let s: String = self.input[start..self.pos].iter().collect();
        self.pos += 1; // skip closing quote
        Ok(Token::String(s))
    }

    fn peek(&self) -> char {
        if self.pos < self.input.len() { self.input[self.pos] } else { '\0' }
    }

    fn skip_whitespace(&mut self) {
        while self.pos < self.input.len() && self.input[self.pos].is_whitespace() {
            self.pos += 1;
        }
    }
}

语法分析 (Parser)

手写递归下降解析器在处理 SQL 这种上下文无关文法时非常高效。以下是 SELECT 语句的 AST 定义:

#[derive(Debug)]
pub enum Expr {
    Column(String),
    Literal(Value),
    BinaryOp {
        left: Box<Expr>,
        op: BinaryOp,
        right: Box<Expr>,
    },
    Aggregate(AggregateFunction, Box<Expr>),
}

#[derive(Debug)]
pub enum BinaryOp {
    Eq, Ne, Lt, Gt, Le, Ge,
    And, Or,
    Add, Sub, Mul, Div,
}

#[derive(Debug)]
pub enum AggregateFunction {
    Count, Sum, Avg, Min, Max,
}

#[derive(Debug)]
pub struct SelectStmt {
    pub projections: Vec<Projection>,
    pub from: String,
    pub selection: Option<WhereClause>,
    pub group_by: Vec<Expr>,
    pub order_by: Vec<OrderBy>,
    pub limit: Option<usize>,
}

#[derive(Debug)]
pub struct Projection {
    pub expr: Expr,
    pub alias: Option<String>,
}

#[derive(Debug)]
pub struct WhereClause {
    pub condition: Expr,
}

#[derive(Debug)]
pub struct OrderBy {
    pub expr: Expr,
    pub ascending: bool,
}

pub struct Parser {
    lexer: Lexer,
    current: Token,
}

impl Parser {
    pub fn new(input: &str) -> Result<Self, String> {
        let mut lexer = Lexer::new(input);
        let current = lexer.next_token()?;
        Ok(Parser { lexer, current })
    }

    fn advance(&mut self) -> Result<(), String> {
        self.current = self.lexer.next_token()?;
        Ok(())
    }

    fn expect(&mut self, token: Token) -> Result<(), String> {
        if std::mem::discriminant(&self.current) == std::mem::discriminant(&token) {
            self.advance()
        } else {
            Err(format!("Expected {:?}, got {:?}", token, self.current))
        }
    }

    pub fn parse_select(&mut self) -> Result<SelectStmt, String> {
        self.expect(Token::Select)?;

        let projections = self.parse_projections()?;
        self.expect(Token::From)?;
        let from = self.parse_identifier()?;

        let selection = if self.current == Token::Where {
            self.advance()?;
            Some(WhereClause { condition: self.parse_expr()? })
        } else {
            None
        };

        let order_by = if self.current == Token::Order {
            self.advance()?;
            self.expect(Token::By)?;
            self.parse_order_by()?
        } else {
            vec![]
        };

        let limit = if self.current == Token::Limit {
            self.advance()?;
            match &self.current {
                Token::Number(n) => {
                    let l = n.parse::<usize>().map_err(|e| e.to_string())?;
                    self.advance()?;
                    Some(l)
                }
                _ => return Err("Expected number after LIMIT".into()),
            }
        } else {
            None
        };

        Ok(SelectStmt {
            projections,
            from,
            selection,
            group_by: vec![],
            order_by,
            limit,
        })
    }

    fn parse_projections(&mut self) -> Result<Vec<Projection>, String> {
        let mut projections = vec![];

        loop {
            let expr = self.parse_expr()?;
            let alias = if self.current.to_string().to_lowercase() == "as" {
                self.advance()?;
                Some(self.parse_identifier()?)
            } else if matches!(&self.current, Token::Ident(_)) {
                Some(self.parse_identifier()?)
            } else {
                None
            };

            projections.push(Projection { expr, alias });

            if self.current == Token::Comma {
                self.advance()?;
            } else {
                break;
            }
        }

        Ok(projections)
    }

    fn parse_expr(&mut self) -> Result<Expr, String> {
        self.parse_or()
    }

    fn parse_or(&mut self) -> Result<Expr, String> {
        let mut left = self.parse_and()?;

        while self.current == Token::Or {
            self.advance()?;
            let right = self.parse_and()?;
            left = Expr::BinaryOp { 
                left: Box::new(left), 
                op: BinaryOp::Or, 
                right: Box::new(right) 
            };
        }

        Ok(left)
    }

    fn parse_and(&mut self) -> Result<Expr, String> {
        // AND has higher precedence than OR
        let mut left = self.parse_not()?;

        while self.current == Token::And {
            self.advance()?;
            let right = self.parse_not()?;
            left = Expr::BinaryOp { 
                left: Box::new(left), 
                op: BinaryOp::And, 
                right: Box::new(right) 
            };
        }

        Ok(left)
    }

    fn parse_order_by(&mut self) -> Result<Vec<OrderBy>, String> {
        let mut ordering = vec![];
        loop {
            let expr = self.parse_expr()?;
            let ascending = if self.current.to_string().to_uppercase() == "DESC" {
                self.advance()?;
                false
            } else if self.current.to_string().to_uppercase() == "ASC" {
                self.advance()?;
                true
            } else {
                true
            };
            ordering.push(OrderBy { expr, ascending });
            if self.current == Token::Comma { self.advance()?; } else { break; }
        }
        Ok(ordering)
    }

    fn parse_identifier(&mut self) -> Result<String, String> {
        match &self.current {
            Token::Ident(name) => {
                let n = name.clone();
                self.advance()?;
                Ok(n)
            }
            _ => Err(format!("Expected identifier, got {:?}", self.current)),
        }
    }
}

三、逻辑计划与查询优化

解析后的 AST 需要转换为逻辑计划,然后经过优化器处理。这是 SQL 引擎中最具工程价值的部分。

逻辑计划节点

#[derive(Debug)]
pub enum LogicalPlan {
    Scan {
        table: String,
        projections: Vec<String>,
        predicate: Option<Expr>,
    },
    Filter {
        input: Box<LogicalPlan>,
        predicate: Expr,
    },
    Project {
        input: Box<LogicalPlan>,
        expressions: Vec<(Expr, Option<String>)>,
    },
    Aggregate {
        input: Box<LogicalPlan>,
        group_exprs: Vec<Expr>,
        aggr_exprs: Vec<Expr>,
    },
    Sort {
        input: Box<LogicalPlan>,
        order_by: Vec<OrderBy>,
    },
    Limit {
        input: Box<LogicalPlan>,
        limit: usize,
    },
}

impl LogicalPlan {
    /// 递归计算该计划的输出列数
    pub fn output_columns(&self) -> usize {
        match self {
            LogicalPlan::Scan { projections, .. } => projections.len(),
            LogicalPlan::Project { expressions, .. } => expressions.len(),
            LogicalPlan::Filter { input, .. } | 
            LogicalPlan::Sort { input, .. } |
            LogicalPlan::Limit { input, .. } => input.output_columns(),
            LogicalPlan::Aggregate { group_exprs, aggr_exprs, .. } => {
                group_exprs.len() + aggr_exprs.len()
            }
        }
    }
}

查询优化器

查询优化的核心是将慢操作变快、将冗余操作减少。以下实现经典的谓词下推与列裁剪:

pub struct Optimizer;

impl Optimizer {
    pub fn optimize(plan: LogicalPlan) -> LogicalPlan {
        let plan = Self::predicate_pushdown(plan);
        let plan = Self::column_pruning(plan);
        let plan = Self::limit_pushdown(plan);
        plan
    }

    /// 谓词下推:将 Filter 尽可能靠近数据源,减少中间数据量
    fn predicate_pushdown(plan: LogicalPlan) -> LogicalPlan {
        match plan {
            LogicalPlan::Filter { input, predicate } => {
                let input = Self::predicate_pushdown(*input);

                match input {
                    // 将 Filter 穿过 Project 下推
                    LogicalPlan::Project { input: sub_input, expressions } => {
                        let pushed_predicate = Self::rewrite_predicate_through_project(
                            &predicate, &expressions
                        );
                        LogicalPlan::Project {
                            input: Box::new(LogicalPlan::Filter {
                                input: sub_input,
                                predicate: pushed_predicate,
                            }),
                            expressions,
                        }
                    }

                    // 将 Filter 与 Scan 合并
                    LogicalPlan::Scan { table, projections, predicate: scan_pred } => {
                        let combined_pred = match scan_pred {
                            Some(existing) => Expr::BinaryOp {
                                left: Box::new(existing),
                                op: BinaryOp::And,
                                right: Box::new(predicate),
                            },
                            None => predicate,
                        };
                        LogicalPlan::Scan {
                            table,
                            projections,
                            predicate: Some(combined_pred),
                        }
                    }

                    _ => LogicalPlan::Filter { input: Box::new(input), predicate },
                }
            }

            LogicalPlan::Limit { input, limit } => {
                let input = Self::predicate_pushdown(*input);
                LogicalPlan::Limit { input: Box::new(input), limit }
            }

            LogicalPlan::Project { input, expressions } => {
                let input = Self::predicate_pushdown(*input);
                LogicalPlan::Project { input: Box::new(input), expressions }
            }

            _ => plan,
        }
    }

    /// 列裁剪:只读取查询中实际需要的列
    fn column_pruning(plan: LogicalPlan) -> LogicalPlan {
        match plan {
            LogicalPlan::Project { input, expressions } => {
                let input = Self::column_pruning(*input);
                let required_cols: Vec<String> = expressions.iter()
                    .flat_map(|(e, _)| collect_column_refs(e))
                    .collect();

                match input {
                    LogicalPlan::Scan { table, projections, predicate } => {
                        let pruned = projections.into_iter()
                            .filter(|p| required_cols.contains(p))
                            .collect();
                        LogicalPlan::Project {
                            input: Box::new(LogicalPlan::Scan {
                                table,
                                projections: pruned,
                                predicate,
                            }),
                            expressions,
                        }
                    }
                    _ => LogicalPlan::Project { input: Box::new(input), expressions },
                }
            }

            LogicalPlan::Filter { input, predicate } => {
                let input = Self::column_pruning(*input);
                let filter_cols = collect_column_refs(&predicate);
                match input {
                    LogicalPlan::Scan { table, projections, predicate: scan_pred } => {
                        let mut all_cols = projections;
                        all_cols.extend(filter_cols);
                        all_cols.sort_unstable();
                        all_cols.dedup();
                        LogicalPlan::Filter {
                            input: Box::new(LogicalPlan::Scan {
                                table,
                                projections: all_cols,
                                predicate: scan_pred,
                            }),
                            predicate,
                        }
                    }
                    _ => LogicalPlan::Filter { input: Box::new(input), predicate },
                }
            }

            _ => plan,
        }
    }

    /// 重写谓词中的列引用,穿透 Projection 层
    fn rewrite_predicate_through_project(
        predicate: &Expr,
        projections: &[(Expr, Option<String>)],
    ) -> Expr {
        match predicate {
            Expr::Column(name) => {
                for (expr, alias) in projections {
                    if let Some(a) = alias {
                        if a == name { return expr.clone(); }
                    }
                    if let Expr::Column(col_name) = expr {
                        if col_name == name {
                            return expr.clone();
                        }
                    }
                }
                predicate.clone()
            }
            Expr::BinaryOp { left, op, right } => {
                Expr::BinaryOp {
                    left: Box::new(Self::rewrite_predicate_through_project(left, projections)),
                    op: op.clone(),
                    right: Box::new(Self::rewrite_predicate_through_project(right, projections)),
                }
            }
            _ => predicate.clone(),
        }
    }

    /// LIMIT 下推:减少上游数据处理量
    fn limit_pushdown(plan: LogicalPlan) -> LogicalPlan {
        // LIMIT 下推到 Scan 层或 Sort 层
        // 实际实现需要考虑是否有 ORDER BY 等情况
        plan
    }
}

fn collect_column_refs(expr: &Expr) -> Vec<String> {
    let mut cols = Vec::new();
    collect_columns(expr, &mut cols);
    cols
}

fn collect_columns(expr: &Expr, cols: &mut Vec<String>) {
    match expr {
        Expr::Column(name) => cols.push(name.clone()),
        Expr::BinaryOp { left, right, .. } => {
            collect_columns(left, cols);
            collect_columns(right, cols);
        }
        Expr::Aggregate(_, inner) => collect_columns(inner, cols),
        Expr::Literal(_) => {}
    }
}

四、向量化执行引擎

现代分析型数据库普遍采用向量化执行(也称批处理执行),相比传统的 Volcano 模型(一次处理一行),向量化执行一次处理一批数据(通常 1024 行),能充分利用 CPU 流水线和 SIMD 指令。

列式内存布局

use std::sync::Arc;

/// 类型安全的值枚举
#[derive(Debug, Clone)]
pub enum Value {
    Null,
    Boolean(bool),
    Int64(i64),
    Float64(f64),
    String(String),
}

/// 列式批处理:一个批次包含多列数据
#[derive(Debug)]
pub struct RecordBatch {
    pub columns: Vec<Column>,
    pub row_count: usize,
}

#[derive(Debug)]
pub struct Column {
    pub name: String,
    pub data: ColumnData,
}

#[derive(Debug)]
pub enum ColumnData {
    Int64(Vec<i64>),
    Float64(Vec<f64>),
    String<Vec<String>),
    Boolean(Vec<bool>),
}

impl RecordBatch {
    pub fn new(columns: Vec<Column>) -> Self {
        let row_count = columns.first().map(|c| c.len()).unwrap_or(0);
        RecordBatch { columns, row_count }
    }

    pub fn column(&self, idx: usize) -> Option<&Column> {
        self.columns.get(idx)
    }
}

impl Column {
    pub fn len(&self) -> usize {
        match &self.data {
            ColumnData::Int64(v) => v.len(),
            ColumnData::Float64(v) => v.len(),
            ColumnData::String(v) => v.len(),
            ColumnData::Boolean(v) => v.len(),
        }
    }

    pub fn is_empty(&self) -> bool { self.len() == 0 }
}

向量化表达式求值

/// 向量化表达式求值器
pub struct ExpressionEvaluator<'a> {
    batch: &'a RecordBatch,
}

impl<'a> ExpressionEvaluator<'a> {
    pub fn new(batch: &'a RecordBatch) -> Self {
        ExpressionEvaluator { batch }
    }

    /// 向量化求值:对整个批次执行表达式,返回布尔列
    pub fn evaluate(&self, expr: &Expr) -> Result<ColumnData, String> {
        match expr {
            Expr::BinaryOp { left, op, right } => {
                self.evaluate_binary_op(left, op, right)
            }
            Expr::Column(name) => {
                self.get_column(name)
            }
            Expr::Literal(val) => {
                self.scalar_to_column(val)
            }
            _ => Err("Unsupported expression in evaluator".into()),
        }
    }

    fn evaluate_binary_op(
        &self,
        left: &Expr,
        op: &BinaryOp,
        right: &Expr,
    ) -> Result<ColumnData, String> {
        let left_eval = self.evaluate(left)?;
        let right_eval = self.evaluate(right)?;

        match (left_eval, right_eval) {
            (ColumnData::Int64(a), ColumnData::Int64(b)) => {
                let result: Vec<bool> = a.iter().zip(b.iter())
                    .map(|(x, y)| Self::compare_int(x, op, y))
                    .collect();
                Ok(ColumnData::Boolean(result))
            }
            (ColumnData::Float64(a), ColumnData::Float64(b)) => {
                let result: Vec<bool> = a.iter().zip(b.iter())
                    .map(|(x, y)| Self::compare_float(x, op, y))
                    .collect();
                Ok(ColumnData::Boolean(result))
            }
            (ColumnData::Int64(a), ColumnData::Float64(b)) => {
                let a: Vec<f64> = a.iter().map(|x| *x as f64).collect();
                let result: Vec<bool> = a.iter().zip(b.iter())
                    .map(|(x, y)| Self::compare_float(x, op, y))
                    .collect();
                Ok(ColumnData::Boolean(result))
            }
            _ => Err("Type mismatch in binary operation".into()),
        }
    }

    fn compare_int(a: &i64, op: &BinaryOp, b: &i64) -> bool {
        match op {
            BinaryOp::Eq => a == b,
            BinaryOp::Ne => a != b,
            BinaryOp::Lt => a < b,
            BinaryOp::Gt => a > b,
            BinaryOp::Le => a <= b,
            BinaryOp::Ge => a >= b,
            _ => false,
        }
    }

    fn compare_float(a: &f64, op: &BinaryOp, b: &f64) -> bool {
        match op {
            BinaryOp::Eq => (a - b).abs() < f64::EPSILON,
            BinaryOp::Ne => (a - b).abs() >= f64::EPSILON,
            BinaryOp::Lt => a < b,
            BinaryOp::Gt => a > b,
            BinaryOp::Le => a <= b,
            BinaryOp::Ge => a >= b,
            _ => false,
        }
    }

    fn get_column(&self, name: &str) -> Result<ColumnData, String> {
        self.batch.columns.iter()
            .find(|c| c.name == name)
            .map(|c| c.data.clone())
            .ok_or_else(|| format!("Column '{}' not found", name))
    }

    fn scalar_to_column(&self, val: &Value) -> Result<ColumnData, String> {
        let n = self.batch.row_count;
        match val {
            Value::Int64(v) => Ok(ColumnData::Int64(vec![*v; n])),
            Value::Float64(v) => Ok(ColumnData::Float64(vec![*v; n])),
            _ => Err("Unsupported literal type".into()),
        }
    }
}

执行器与流水线

/// 执行器 trait - 定义节点执行接口
pub trait Executor: Send {
    /// 执行并返回一批结果
    fn execute(&mut self) -> Result<Option<RecordBatch>, String>;

    /// 子节点
    fn children(&self) -> &[Box<dyn Executor>];
}

/// 全表扫描执行器
pub struct ScanExecutor {
    table: String,
    projections: Vec<String>,
    predicate: Option<Expr>,
    position: usize,
}

impl ScanExecutor {
    pub fn new(table: String, projections: Vec<String>, predicate: Option<Expr>) -> Self {
        ScanExecutor { table, projections, predicate, position: 0 }
    }
}

impl Executor for ScanExecutor {
    fn execute(&mut self) -> Result<Option<RecordBatch>, String> {
        // 模拟从存储读取数据
        let batch = self.read_next_batch()?;
        Ok(Some(batch))
    }

    fn children(&self) -> &[Box<dyn Executor>] { &[] }
}

impl ScanExecutor {
    fn read_next_batch(&mut self) -> Result<RecordBatch, String> {
        // 实际实现中会异步读取消下一页数据
        let row_count = 1024;
        let columns: Vec<Column> = self.projections.iter()
            .map(|name| Column {
                name: name.clone(),
                data: ColumnData::Int64((0..row_count).map(|i| i as i64).collect()),
            })
            .collect();
        self.position += row_count;
        Ok(RecordBatch::new(columns))
    }
}

/// 过滤执行器
pub struct FilterExecutor {
    input: Box<dyn Executor>,
    predicate: Expr,
}

impl Executor for FilterExecutor {
    fn execute(&mut self) -> Result<Option<RecordBatch>, String> {
        let batch = match self.input.execute()? {
            Some(b) => b,
            None => return Ok(None),
        };

        let evaluator = ExpressionEvaluator::new(&batch);
        let mask = evaluator.evaluate(&self.predicate)?;

        let filtered = apply_mask(&batch, &mask)?;
        Ok(Some(filtered))
    }

    fn children(&self) -> &[Box<dyn Executor>] { std::slice::from_ref(&self.input) }
}

/// 投影执行器
pub struct ProjectExecutor {
    input: Box<dyn Executor>,
    expressions: Vec<(Expr, Option<String>)>,
}

impl Executor for ProjectExecutor {
    fn execute(&mut self) -> Result<Option<RecordBatch>, String> {
        let batch = match self.input.execute()? {
            Some(b) => b,
            None => return Ok(None),
        };

        let evaluator = ExpressionEvaluator::new(&batch);
        let mut new_columns = Vec::with_capacity(self.expressions.len());

        for (expr, alias) in &self.expressions {
            let result = evaluator.evaluate(expr)?;
            let name = alias.clone().unwrap_or_else(|| format!("{:?}", expr));
            new_columns.push(Column { name, data: result });
        }

        Ok(Some(RecordBatch::new(new_columns)))
    }

    fn children(&self) -> &[Box<dyn Executor>] { std::slice::from_ref(&self.input) }
}

/// 应用布尔掩码过滤行
fn apply_mask(batch: &RecordBatch, mask: &ColumnData) -> Result<RecordBatch, String> {
    let bools = match mask {
        ColumnData::Boolean(v) => v,
        _ => return Err("Mask must be boolean".into()),
    };

    let mut new_columns = Vec::with_capacity(batch.columns.len());

    for col in &batch.columns {
        let filtered = match &col.data {
            ColumnData::Int64(data) => {
                let v: Vec<i64> = data.iter().zip(bools.iter())
                    .filter(|(_, m)| **m)
                    .map(|(v, _)| *v)
                    .collect();
                ColumnData::Int64(v)
            }
            ColumnData::Float64(data) => {
                let v: Vec<f64> = data.iter().zip(bools.iter())
                    .filter(|(_, m)| **m)
                    .map(|(v, _)| *v)
                    .collect();
                ColumnData::Float64(v)
            }
            ColumnData::String(data) => {
                let v: Vec<String> = data.iter().zip(bools.iter())
                    .filter(|(_, m)| **m)
                    .map(|(v, _)| v.clone())
                    .collect();
                ColumnData::String(v)
            }
            ColumnData::Boolean(data) => {
                let v: Vec<bool> = data.iter().zip(bools.iter())
                    .filter(|(_, m)| **m)
                    .map(|(v, _)| *v)
                    .collect();
                ColumnData::Boolean(v)
            }
        };
        new_columns.push(Column { name: col.name.clone(), data: filtered });
    }

    Ok(RecordBatch::new(new_columns))
}

五、完整查询执行流程

将以上组件串联起来,构成完整的查询执行流程:

/// SQL 引擎主结构
pub struct SqlEngine {
    catalog: Catalog,
}

#[derive(Debug, Default)]
pub struct Catalog {
    tables: HashMap<String, TableMeta>,
}

#[derive(Debug)]
pub struct TableMeta {
    pub name: String,
    pub columns: Vec<ColumnMeta>,
}

#[derive(Debug)]
pub struct ColumnMeta {
    pub name: String,
    pub data_type: DataType,
}

#[derive(Debug)]
pub enum DataType {
    Int64, Float64, String, Boolean,
}

impl SqlEngine {
    pub fn new() -> Self {
        SqlEngine { catalog: Catalog::default() }
    }

    pub fn register_table(&mut self, name: &str, columns: Vec<(&str, DataType)>) {
        let meta = TableMeta {
            name: name.to_string(),
            columns: columns.iter().map(|(n, t)| ColumnMeta {
                name: n.to_string(),
                data_type: t.clone(),
            }).collect(),
        };
        self.catalog.tables.insert(name.to_string(), meta);
    }

    /// 执行 SQL 语句
    pub fn execute(&self, sql: &str) -> Result<Vec<RecordBatch>, String> {
        // 1. 解析 SQL
        let mut parser = Parser::new(sql)?;
        let ast = parser.parse_select()?;

        // 2. 构建逻辑计划
        let plan = self.build_logical_plan(&ast)?;

        // 3. 优化逻辑计划
        let optimized = Optimizer::optimize(plan);

        // 4. 构建物理计划
        let physical = self.build_physical_plan(&optimized);

        // 5. 执行物理计划
        let mut results = Vec::new();
        loop {
            match physical.execute()? {
                Some(batch) => results.push(batch),
                None => break,
            }
        }

        Ok(results)
    }

    fn build_logical_plan(&self, ast: &SelectStmt) -> Result<LogicalPlan, String> {
        let scan = LogicalPlan::Scan {
            table: ast.from.clone(),
            projections: self.infer_projections(&ast.projections),
            predicate: ast.selection.as_ref().map(|w| w.condition.clone()),
        };

        let filter = match &ast.selection {
            Some(where_clause) => {
                LogicalPlan::Filter {
                    input: Box::new(scan),
                    predicate: where_clause.condition.clone(),
                }
            }
            None => scan,
        };

        let project = LogicalPlan::Project {
            input: Box::new(filter),
            expressions: ast.projections.iter()
                .map(|p| (p.expr.clone(), p.alias.clone()))
                .collect(),
        };

        let plan = match &ast.order_by.is_empty() {
            false => LogicalPlan::Sort { input: Box::new(project), order_by: ast.order_by.clone() },
            true => project,
        };

        Ok(plan)
    }

    fn build_physical_plan(&self, plan: &LogicalPlan) -> Box<dyn Executor> {
        match plan {
            LogicalPlan::Scan { table, projections, predicate } => {
                Box::new(ScanExecutor::new(table.clone(), projections.clone(), predicate.clone()))
            }
            LogicalPlan::Filter { input, predicate } => {
                let child = self.build_physical_plan(input);
                Box::new(FilterExecutor { input: child, predicate: predicate.clone() })
            }
            LogicalPlan::Project { input, expressions } => {
                let child = self.build_physical_plan(input);
                Box::new(ProjectExecutor { input: child, expressions: expressions.clone() })
            }
            _ => todo!("Implement more physical operators"),
        }
    }

    fn infer_projections(&self, projections: &[Projection]) -> Vec<String> {
        projections.iter()
            .map(|p| match &p.expr {
                Expr::Column(name) => name.clone(),
                _ => format!("{:?}", p.expr),
            })
            .collect()
    }
}

六、工程实践中的性能陷阱与优化

1. 内存分配优化

向量化执行中,每次 batch 都会创建大量临时 Vec。mimalloc 或 jemalloc 比系统默认分配器更优:

use mimalloc::MiMalloc;

#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

2. 零拷贝列引用

使用 Arc<[T]> 替代 Vec 实现列的共享引用,避免不必要的克隆:

pub struct ColumnView {
    pub name: String,
    pub data: Arc<[Value]>,
}

3. 分支预测友好的表达式求值

对谓词求值,按选择性排序条件。高选择性(能过滤更多数据)的谓词先求值:

fn sort_predicates_by_selectivity(predicates: &mut [(Expr, f64)]) {
    // 按选择性从高到低排序(选择性 = 通过率,值越小过滤越多)
    predicates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
}

4. 编译时常量折叠

在解析阶段就将常量表达式求值,避免运行时重复计算:

fn fold_constants(expr: Expr) -> Expr {
    match expr {
        Expr::BinaryOp { left, op, right } => {
            let left = fold_constants(*left);
            let right = fold_constants(*right);

            if let (Expr::Literal(a), Expr::Literal(b)) = (&left, &right) {
                if let Some(result) = eval_literal_op(a, &op, b) {
                    return Expr::Literal(result);
                }
            }

            Expr::BinaryOp { left: Box::new(left), op, right: Box::new(right) }
        }
        _ => expr,
    }
}

七、总结

从零构建 SQL 引擎是理解数据库系统运作的最佳实践。通过本文的实现,我们覆盖了:

  • 词法与语法解析:用 nom 或手写递归下降实现 Token 流处理
  • 逻辑计划与优化器:谓词下推、列裁剪等经典优化规则
  • 向量化执行引擎:列式内存布局、批处理求值、流水线执行
  • 性能工程:内存分配器选择、零拷贝、分支预测友好设计

工程实践中,一个生产级 SQL 引擎还需要事务支持、WAL 日志、并发控制、分布式执行等复杂机制。但核心的解析-优化-执行三阶段架构是相通的。用 Rust 来实现不仅能保证内存安全,其 trait 系统和枚举类型还能让阶段间的数据流更加类型安全,许多逻辑错误在编译期就能被拦截。

最终代码可参考 DuckDB 的极简版本 Limbo(原 libSQL),它用 Rust 实现了 SQLite 的完整语义,是学习 SQL 引擎实现的绝佳素材。

参考资料

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部