“This is the 14th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”


SQL module

This is where you enter the SQL module. However, this is actually no different from the previous Meta Command module, at least in terms of structure. We have an enumeration that defines the types of queries we originally planned to support. Then there is an impl block with fn new(), again as a constructor.

Then there is a FN process_command(), which returns Result

. If you recall, this function is called by main.rs. In this function, magical things start to happen.
,>

You’ll notice that at the beginning of FN process_command() we use SQLParser-RS Crate, which builds an extensible SQL Lexer and parser for Rust and has a number of different SQL dialects, including SQLite, So I decided to use them for the time being rather than write a whole new SQL Lexer.

By calling Parser:: parse_SQL (), I get a Result

, ParserError>, I do some basic checks and pass it to a matching Statement to determine what type of SQL Statement was entered, Or if there was an error in the process, if so, I return the error. The return Statement is sqlparser: : ast: : the Statement, it is a Statement results the enumeration of all possible, you can see from sqlparser document.

Currently, the only SQL statement I actually set up for the parser is CREATE TABLE, and for the rest, we just identify the type of SQL statement and return it to the user. In the block matching CREATE TABLE, we call another module Parser:: CREATE, which contains all the logic for 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::<VecThe < &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),
        }
    }
}

// Perform initial parsing of SQL statements using SQLparser-RS
pub fn process_command(query: &str) - >Result<String> {
    let dialect = SQLiteDialect{};
    let message: String;
    let mutast = 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();

    // Initially only some basic SQL statements are implemented
    matchquery { 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)
}
Copy the code

This is our SQL :: Parser :: Create module.

Here we have two definitions of structure types. The first is ParsedColumn, which represents a column in the table, and the second is CreateQuery, which represents a table. As you can see, CreateQuery has a property called Columns, which is a collection of ParsedColumns. Our main method on this module, fn new(), returns a Result

and then inserts it into our database data structure, which has yet to be defined in the code.
,>

// 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> {
        matchstatement { 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![];

                / / traverse the columns
                for col in columns {
                    let name = col.name.to_string();
                    // TODO:Currently, only basic types can be resolved, with no timestamp or date
                    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:Handles table constraints, such as primary and foreign keys
                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())),
        }
    }
}
Copy the code