Better Programming

Advice for programmers.

Follow publication

Accessing Firebird With Diesel and Rust

Luis Fernando Batels
Better Programming
Published in
5 min readSep 20, 2022

We recently launched the rsfbclient-diesel adapter, which enabled the diesel to work with the Firebird database.

This post works as a continuation of my previous post, where I showed how to use Firebird with Rust. Now let’s learn how to improve this usage with an ORM layer.

To facilitate our understanding, we will implement a CRUD CLI tool with the job table from the example database employee.fdb. This database comes with your Firebird installation, but it can also be found in the examples/empbuild folder extracted from the official site.

Starting our project

We need to start including the diesel and rsfbclient-diesel dependencies in our Cargo.toml file:

Now in main.rs we import everything and connect in the employee.fdb database:

Listing rows

To manipulate some table with diesel, we need to declare the fields and the struct of this table. In a new module schema.rs we will declare our job table:

The Job struct will be our record representation in the job table. Because the table use the job_ prefix in some fields, we need to use the #[diesel(column_name = …)]. Below we declare our table schema, providing the types of columns.

Our struct declares the #[derive(Queryable)]. With this derivation, we are able to perform read operations.

Back to the main.rs again. We import the new module and use the diesel load function to get our records list:

Improving our list of records

We have a list, now let’s improve the view and the method we use to call this. To do this, we need to add the tabled and argopt in Cargo.toml file.

We are building a CRUD, let’s move the list operation to a command called list with help of argopt.

In the main.rs create the list function, moving the entire content of current main function and marking it with #[subcmd]:

In order for the command to be available, we need to add the #[cmd_group] derivation on the main function.

When we test, we will see that our project lists all the available commands:

‹dieselexample› cargo run
……
SUBCOMMANDS:
help Print this message or the help of the given subcommand(s)
list List all available jobs

Now we just need to format our list using the tabled. In this project, we will use the schema struct directly with the tabled just to simplify our implementation.

In our Job struct definition, we added the Tabled derive:

In our list command, we changed the way to print the records by calling the tabled:

When we test, we will see our formatted list:

‹dieselexample› cargo run list
+-------+--------------------------+-------------+
| code | title | country |
+-------+--------------------------+-------------+
| CEO | Chief Executive Officer | USA |
+-------+--------------------------+-------------+
| CFO | Chief Financial Officer | USA |
+-------+--------------------------+-------------+
| VP | Vice President | USA |
+-------+--------------------------+-------------+
| Dir | Director | USA |
+-------+--------------------------+-------------+
| Mngr | Manager | USA |

Inserting new records

Before we can insert a new record, we need to finish our columns mapping. Some non-null columns still remain:

With the new columns, we create the add function and place it in the available command list with #[cmd_groud]:

When we test, we will see our new command:

‹dieselexample› cargo run add 
USAGE:
employee add —code <CODE> —title <TITLE> —grade <GRADE> —country <COUNTRY> —min-salary <MIN_SALARY> —max-salary <MAX_SALARY>

Now let’s go to the insert. To enable some struct for insert operations, we need to add the Insertable derive:

With the struct ready, we just need to call the insert_into diesel function:

Let’s test:

‹dieselexample› cargo run add --code TE --title "Tech solver stuffs" --grade 1 --country USA --min-salary 2000 --max-salary 5000 
‹dieselexample› cargo run list
+-------+-------------+---------+-------+------------+------------+
| code | title | country | grade | min_salary | max_salary |
+-------+-------------+---------+-------+------------+------------+
....
| TE | Tech solver | USA | 1 | 2000 | 5000 |
+-------+-------------+---------+-------+------------+------------+

It’s update time

We already have the list and the add operation, still remains the update. We start by creating the update command:

We will use the Option type on the editable fields. With this approach the user only needs to provide what he really wants to update:

<dieselexample› cargo run update —help
USAGE:
employee update [OPTIONS] <CODE>
ARGS:
<CODE> Job code
OPTIONS:
--max-salary <MAX_SALARY> New max salary
--min-salary <MIN_SALARY> New min salary
--title <TITLE> New title

Since our intention is just to update some fields, let’s create a new struct with the AsChangeset derive that will represent these fields:

In our command again, now we just need to call the update diesel function:

Let’s test:

‹dieselexample› cargo run update TE —title=”New job title”
‹dieselexample› cargo run list
+-------+--------------+---------+-------+------------+------------+
| code | title | country | grade | min_salary | max_salary |
+------+---------------+---------+-------+------------+------------+
....
| TE | New job title | USA | 1 | 2000 | 5000 |
+------+---------------+---------+-------+------------+------------+

Removing records

Finally, now let’s delete our records. To make this, we create the remove command:

As expected, the deletion is performed by calling the delete diesel function:

Let’s test:

‹dieselexample› cargo run remove TE
‹dieselexample› cargo run list
+------+---------------+---------+-------+-----------+-------------+
| code | title | country | grade | min_salary | max_salary |
+------+---------------+---------+-------+-----------+-------------+
....
| SRep | Sales Represe | France | 4 | 20000 | 100000 |
+------+---------------+---------+-------+-----------+-------------+

Improving the connection

As you can see, the string connection is hard coded. We can solve this with a simple approach, using environment variables.

To manage environment variables in rust, we use the std:env module:

With the imported module, we just need to get the string connection from the DIESELFDB_CONN env:

Let’s test:

‹dieselexample› export DIESELFDB_CONN=”firebird://SYA:ma@localhost/employee.fdb”
‹dieselexample› cargo run list
+------+---------------+---------+-------+-----------+-------------+
| code | title | country | grade | min_salary | max_salary |
+------+---------------+---------+-------+-----------+-------------+
....
| SRep | Sales Represe | France | 4 | 20000 | 100000 |
+------+---------------+---------+-------+-----------+-------------+

The source code of the example project is available below:

To follow the rsfbclient and rsfbclient_diesel development, you can access our repository: https://github.com/fernandobatels/rsfbclient

Thanks for your attention and see you next time!

Write a response