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


REPL

Since we are using SQLite as our model, we might as well try to copy SQLite to make it easier for users. When you run SQlite3, SQLite starts a read-execution-print cycle, also known as REPL.

❯ sqlite3 SQLite version 3.32.3 2020-06-18 14:16:19 Enter ". Help "for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database.sqlite> .databases
main:
sqlite> .dadada
Error: unknown command or invalid arguments:  "dadada". Enter ".help" for help
sqlite> .exit
Copy the code

First, there are some design choices to be made to achieve the above results. Since the focus of the project was on research and building a database, most of the development effort was focused on that, which meant I didn’t want to spend most of my time reinventing the wheel and writing CLI interpreter or REPL logic. So, for this, I decided to leverage a somewhat mature third-party library that had already been developed. But maybe in the future, if there’s some free time and using these third-party libraries does affect the overall performance of the application, I can always come back and replace.

The logic of the REPL is pretty straightforward:

In an infinite loop, print a prompt, get an input line, validate and process that line.

I decided to use Rustyline Crate, which is quite mature, memory efficient, and has solved many of the problems we had to deal with, even from a user experience perspective, such as providing hints in real time and auto-completion, which is a great feature.

So, you can find it on Github before you actually write the code. Here I’ll use a demo snippet to quickly show how rustyline works with a simple example:

First, you need to add this dependency to your cargo. Toml:

[dependencies]  
rustyline = "7.1.0"
Copy the code

In the main. Rs:

use rustyline::error::ReadlineError;
use rustyline::Editor;

fn main() {
    // Create an editor with default configuration options
    let mut repl = Editor::<()>::new();
    // a file with a history command is loaded. If the file does not exist, it creates one
    if repl.load_history(".repl_history").is_err() {
        println!("No previous history.");
    }
    // An infinite loop, stuck here until the user terminates the program
    loop {
	// Ask the user to enter a command. You can add anything you want as a prefix here
        let readline = repl.readline(">> ");

        // readline returns a result. The result is then filtered with a match statement
        match readline {
            Ok(line) => {
                repl.add_history_entry(line.as_str());
                println!("Line: {}", line);
            },
            Err(ReadlineError::Interrupted) => {
                println!("CTRL-C");
                break
            },
            Err(ReadlineError::Eof) => {
                println!("CTRL-D");
                break
            },
            Err(err) => {
                println!("Error: {:? }", err);
                break}}}// Save the command to a file. So far, they've been stored in memory
    repl.save_history(".repl_history").unwrap();
}
Copy the code

So, with the above code, you have a basic REPL program up and running.