likes
comments
collection

「Rust 重写 sqlite」SQL模块:CREATE

作者站长头像
站长
· 阅读数 14

「这是我参与11月更文挑战的第 14 天,活动详情查看:2021最后一次更文挑战


SQL模块

到这正式进入sql模块。不过这实际与之前的 meta command 模块没有什么不同,至少在结构上是这样。我们有一个枚举,定义了我们最初计划支持的查询类型。然后是一个带有 fn new() 的 impl block,同样作为构造函数。

然后是一个 fn process_command(),返回 Result<String, SQLRiteError>,如果你还记得,这个函数是由 main.rs 调用的。在这个函数中,神奇的事情开始发生。

你会注意到,在 fn process_command() 的开头,我们使用了 sqlparser-rs crate,它为Rust建立了一个可扩展的SQL Lexer和解析器,并有许多不同的SQL方言,包括SQLite方言,所以我决定暂时使用它们而不是编写一个全新的SQL Lexer。

通过调用 Parser::parse_sql(),我得到了一个 Result<Vec<Statement>, ParserError>,我做了一些基本的检查,并把它传递给一个匹配语句,以确定输入的是哪种类型的SQL语句,或者在这个过程中是否有错误,如果有,我就返回这个错误。返回的语句是 sqlparser::ast::Statement,它是一个所有可能语句结果的枚举,你可以从 sqlparser文档 中看到。

目前,我实际建立的解析器的唯一SQL语句是 CREATE TABLE,对于其余的语句,我们只是识别SQL语句的类型并返回给用户。在与CREATE TABLE匹配的block中,我们调用另一个模块 Parser::create,它包含了CREATE TABLE的所有逻辑。

#[derive(Debug,PartialEq)]
pub enum SQLCommand {
    Insert(String),
    Delete(String),
    Update(String),
    CreateTable(String),
    Select(String),
    Unknown(String),
}

impl SQLCommand {
    pub fn new(command: String) -> SQLCommand {
        let v = command.split(" ").collect::<Vec<&str>>();
        match v[0] {
            "insert" => SQLCommand::Insert(command),
            "update" => SQLCommand::Update(command),
            "delete" => SQLCommand::Delete(command),
            "create" => SQLCommand::CreateTable(command),
            "select" => SQLCommand::Select(command),
            _ => SQLCommand::Unknown(command),
        }
    }
}

// 使用sqlparser-rs执行SQL语句的初始解析
pub fn process_command(query: &str) -> Result<String> {
    let dialect = SQLiteDialect{};
    let message: String;
    let mut ast = Parser::parse_sql(&dialect, &query).map_err(SQLRiteError::from)?;

    if ast.len() > 1 {
        return Err(SQLRiteError::SqlError(ParserError::ParserError(format!(
            "Expected a single query statement, but there are {}",
            ast.len()
        ))));
    }

    let query = ast.pop().unwrap();

    // 最初只实现一些基本的SQL语句
    match query {
        Statement::CreateTable{..} => {
            let result = CreateQuery::new(&query);
            match result {
                Ok(payload) => {
                    println!("Table name: {}", payload.table_name);
                    for col in payload.columns {
                        println!("Column Name: {}, Column Type: {}", col.name, col.datatype);
                    }
                },
                Err(err) => return Err(err),
            }
            message = String::from("CREATE TABLE Statement executed.");
            // TODO: Push table to DB
        },
        Statement::Query(_query) => message = String::from("SELECT Statement executed."),
        Statement::Insert {..} => message = String::from("INSERT Statement executed."),
        Statement::Delete{..} => message = String::from("DELETE Statement executed."),
        _ => {
            return Err(SQLRiteError::NotImplemented(
                "SQL Statement not supported yet.".to_string()))
        }
    };

    Ok(message)
}

这是我们的 sql::parser::create 模块。

这里我们有两个结构类型的定义。第一个是 ParsedColumn:代表表中的一个列,第二个是 CreateQuery:代表一个表。正如你所看到的,CreateQuery有一个名为columns的属性,这是一个ParsedColumns的集合。我们在这个模块上的主要方法,也就是 fn new(),返回一个 Result<CreateTable, SQLRiteError>,然后将其插入到我们的数据库数据结构中,该结构仍有待于在代码中定义。

// Represents Columns in a table
#[derive(PartialEq, Debug)]
pub struct ParsedColumn {
    pub name: String,
    pub datatype: String,
    pub is_pk: bool,
    pub is_nullable: bool,
}

// Represents a SQL Statement CREATE TABLE
#[derive(Debug)]
pub struct CreateQuery {
    pub table_name: String,         // table name
    pub columns: Vec<ParsedColumn>, // columns
}

impl CreateQuery {
    pub fn new(statement: &Statement) -> Result<CreateQuery> {
        match statement {
            Statement::CreateTable {
                name,
                columns,
                constraints: _constraints,
                with_options: _with_options,
                external: _external,
                file_format: _file_format,
                location: _location,
                ..
            } => {
                let table_name = name;
                let mut parsed_columns: Vec<ParsedColumn> = vec![];

                // 遍历 columns
                for col in columns {
                    let name = col.name.to_string();
                    // TODO: 目前只能解析基础类型,没有时间戳和日期
                    let datatype = match &col.data_type {
                        DataType::SmallInt => "int",
                        DataType::Int => "int",
                        DataType::BigInt => "int",
                        DataType::Boolean => "bool",
                        DataType::Text => "string",
                        DataType::Varchar(_bytes) => "string",
                        DataType::Float(_precision) => "float",
                        DataType::Double => "float",
                        DataType::Decimal(_precision1, _precision2) => "float",
                        _ => {
                            println!("not matched on custom type");
                            "invalid"
                        }
                    };

                    let mut is_pk: bool = false;
                    for column_option in &col.options {
                        is_pk = match column_option.option {
                            ColumnOption::Unique { is_primary } => is_primary,
                            _ => false,
                        };
                    }

                    parsed_columns.push(ParsedColumn {
                        name,
                        datatype: datatype.to_string(),
                        is_pk,
                        is_nullable: false,
                    });
                }
                // TODO: 处理表约束,比如主键外键等
                for constraint in _constraints {
                    println!("{:?}", constraint);
                }
                return Ok(CreateQuery {
                    table_name: table_name.to_string(),
                    columns: parsed_columns,
                });
            }

            _ => return Err(SQLRiteError::Internal("Error parsing query".to_string())),
        }
    }
}