Introduction

In this article, we will explore how to access MySQL from Rust. We will learn how to use all MySQL DML commands like select, insert, update, and delete in Rust.

Create an application package.

Create a new application.

cargo new temp-project
[package]
name = "temp_project"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Build your project.

cargo build

Add these dependencies to the Cargo.toml file.

[dependencies]
mysql = "*"
chrono = "0.4"

We require the chrono crate to work with date and time columns.

Get Started

In the main.rs file, import the namespaces.

use mysql::prelude::*;
use mysql::*;
use chrono::prelude::*; //For date and time

MySQL connection in Rust programming language

Insert this code into the main() function. The URL of your connection may differ.

fn main() {
    let url = "mysql://root:root@localhost:3306/mcn"; //"mysql://UserName:Password@localhost:3306/DatabaseName"
    let pool = Pool::new(url).unwrap();
    let mut conn = pool.get_conn().unwrap();
}

Run the code at this point to make sure you can open a connection.

cargo run

Create a table

In this article, we will use a Stock table with the schema shown below

The Structure

fn show_stock(cn:&mut PooledConn)
    {
        let qr = format!("create table Stock
        ItemId INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
        ItemName VARCHAR(128) NOT NULL,categoryId int,
        PRIMARY KEY(ItemId),FOREIGN KEY (categoryId) REFERENCES category
        (categoryId)");
        }

How to insert data in the stock table using Rust?

The params macro simplifies the supply of named parameter values. Drop in exec drop() indicates that no result data is returned. This is sufficient for inserting, updating, and deleting SQL.
If you have a lot of inserts, compiling the SQL as a prepared statement will save you time.

fn insert_stock(cn:&mut PooledConn)
{
    let mut itemname = String::new();
    println!("Enter the name of the item:");
    std::io::stdin().read_line(&mut itemname).unwrap();

    let mut qt = String::new();
    println!("Enter the quantity:");
    std::io::stdin().read_line(&mut qt).unwrap();
    let quantity:i64 = qt.trim().parse().expect("enter valid quantity");

    let mut cid = String::new();
    println!("Enter the category id:");
    std::io::stdin().read_line(&mut cid).unwrap();
    let categoryid:i64 = cid.trim().parse().expect("enter valid category id");

    let query="INSERT INTO stock (ItemName,quantity,categoryId) values (:itemname, :quantity, :categoryid)";
    let params=params!{"itemname"=>itemname, "quantity"=>quantity,"categoryid"=>categoryid,};
    cn.exec_drop(query,params).unwrap();
}

How to select data from the MySQL database?

Here I am applying an inner join between two tables to fetch the data from the database. Append this code to the main() function.

fn show_stock(cn:&mut PooledConn)
    {
    let qr = format!("select ItemId, ItemName, stock.categoryId, categoryName, quantity from stock inner join category on stock.categoryId = category.categoryId");
        let res:Vec<(i64,String,i64, String, i64)> = cn.query(qr).unwrap();
    for r in res {
        println!("Item ID:{} | Item Name:{} | Category Id:{} | Category Name:{} | Quantity:{}", r.0, r.1, r.2,r.3, r.4);
    }
    }

Perform a Streamed Query

Streamed queries read the result data row by row. This is useful if you need to read large amounts of data for exporting or other purposes. The entire data set is never kept in memory.
Append this code to the main() function.

conn.query_iter("select ItemId, ItemName from stock")
.unwrap()
.for_each(|row| {
let r:(i32, String) = from_row(row.unwrap());
println!("{}, {:?}", r.0, r.1);
});

The row is of the type MySQL common::row::Row in this case. This type transports data in the form of bytes at the most basic level. The form row() function converts bytes into more user-friendly formats such as i32 and String. The converted data is returned as a tuple with the items in the same order as the query columns.

Gather Query Results

The query result can be collected in a vector. The vector's items are all tuples.

let res:Vec<(i64,String,i64, String, i64)> = cn.query(qr).unwrap();
    
    for r in res {
        println!("Item ID:{} | Item Name:{} | Category Id:{} | Category Name:{} | Quantity:{}", r.0, r.1, r.2,r.3, r.4);
    }

We don't have to do anything because the query() function converts low-level bytes to our desired data types. We had to specify the tuple's data type explicitly. Otherwise, the compiler has no way of knowing.

Convert the outcome to structured data

Working with tuples is not a bad thing. However, for a real-world application, you might want to define a struct to model each row in the result. We'll do it right now.
Add a struct.

struct Employee{
    employee_id:i64,
    employee_fname:String,
    employee_lname:String,
    employee_mail:String,
    password:String,
    user_type:i64
 }
 fn show_all(cn:&mut PooledConn)
   {
    let res:Vec<(i64, String, String,String,String,String,i64,String,i64,i64)> = cn.query("select * from employee")
 .unwrap();
 for r in res {
 println!("Employee ID:{} |First Name:{} |Last Name:{} |Email:{} |Mobile No:{} |Address:{} |Salary:{} |Password:{} |Department ID:{} |User Type ID:{}", r.0, r.1, r.2, r.3, r.4,r.5,r.6,r.7,r.8,r.9);
    }
   }

The cool thing is that we didn't have to specify the data type of the tuple. The compiler deduced it from the data types of the fields of the Product.

Delete and Update

These are comparable to insert.

let stmt = conn.prep("update stock set ItemName=:itemname,quantity=:quantity  where ItemId=:ItemId")
.unwrap();
conn.exec_drop(&stmt, params! {
"ItemId" => 6,
"itemname" => "P1111",
"quantity" => 5,
}).unwrap();
   

Delete the data on the stock table

let stmt = conn.prep("delete from stock where ItemId=:ItemId").unwrap();
conn.exec_drop(&stmt, params! {
"ItemId" => 6,
}).unwrap();

Summary

We learned how to insert, update, and delete in this section. This should cover the most common data types used in database programming. I love how it converts data to any type I am working with Rust. Here we used i32, String, and NaieveDate.