If you read our recent article introducing Rust, you may wonder how to use an Oracle database with a Rust app. In this article, we will look at a Rust crate called Sibyl, which does exactly that. We’ll get Sibyl up and running in a sample project and send a few transactions through it to get a feel for the library.

If you haven’t used Rust before, don’t let that hold you back. Check out Hello Rust! and come back here when you’re ready. Rust is a great language with many possible applications, so it’s definitely worth checking out more.

What is Sibyl?

As mentioned above, Sibyl is a Rust crate that supports interactions with Oracle databases in Rust code. As a reminder, a crate is a library or app written in Rust that is packaged up to make distribution and versioning easier for Rust developers.

Sibyl uses the Oracle Call Interface (OCI) to interact with Oracle databases. The Oracle Call Interfaces is distinct from the other OCI—Oracle Cloud Infrastructure. Keep this in mind when reading the docs, as Sibyl is not exclusively meant for Oracle Cloud Infrastructure, though it can be used there. Essentially, Sibyl serves as a Rust wrapper around the Oracle Call Interface to provide any Rust app access to Oracle databases.

Sibyl can also be configured to use blocking (threaded) or nonblocking (asynchronous) calls, depending on your needs. We’ll look at how to pick between these two modes and talk a little about what it might mean for your app.

Set up your environment

Since Sibyl is a library that will run on a Rust app, we need somewhere to run that Rust app. While we could connect to any Oracle Database from a laptop or some other computer, we will use an Oracle Cloud Infrastructure instance for this. There are some things you’ll need to set up in advance, such as a Virtual Cloud Network (VCN) with the right security rules to let traffic in as you’d like. You will also need to install Rust on the machine if you haven’t already. If you want a guide to how to get that going, take a look at the Hello, Rust post, since that will be our starting point for this article. You’ll even have a simple web app written in Rust to start with.

In addition to having all of those things squared away, you’ll also need to install the Oracle instant client. This will allow you to connect to an Oracle database. Fortunately, on an Oracle Linux instance running in OCI, this is pretty easy and only takes two commands:


$ sudo dnf install oracle-release-el8
$ sudo dnf install oracle-instantclient19.10-basic    

Finally, you’ll need to connect to an Oracle database. Following the spirit of our previous post, we will use an Always Free Autonomous Database. In particular, we will create an Autonomous Transaction Processing (ATP) database, but we’ll assume the data is loaded as described in Lab 1 & 2 of our 15-minute Quick Start tutorial for Autonomous Databases. This will allow us to get up and running quickly with some data and not worry about the details that a DBA would typically take care of when starting up a new database instance. You’ll want to get the Wallet for this new database onto your instance where you’ll be running your Rust app and update the sqlnet.ora to configure the directory where you’re storing it correctly. .

Once you have all of that lined up, we can start analyzing the data in our database using Sibyl. Let’s jump into that now.

Calling on Sibyl to access Oracle

To begin with, let’s create an app that is exclusively using Sibyl to print a report of data out to our console. This isn’t particularly special, but it will help us ensure we have things configured properly and show a baseline for what is possible with the library. To get an app going, we can run a simple cargo command:


$ cargo new hello_sibyl

This will create a simple app in a new directory hello_sibyl. Change your working directory to the app root and add Sibyl as a dependency in the Cargo.toml:


[dependencies]
sibyl = { version = "0.6", features = ["blocking"] }    

Notice, we have to specify if we want to use the blocking or non-blocking version of Sibyl right as we declare our dependency on the library. It’s not possible to use both in the same app.

We’ll also need to declare a few environment variables for Sibyl to function correctly. You’ll want to run exports for the following environment variables:


LIBRARY_PATH = "/usr/lib/oracle/19.10/client64/lib/"
TNS_ADMIN = "/home/opc/wallets/Wallet_RustTransactions"
DBNAME = "rusttransactions_low"
DBUSER = "ADMIN"
DBPASS = "SomeSecurePassword"

These environment variables will load whenever we run our app. Note that your database names may differ, and hopefully, you’re using a more secure password than I did here. Also, the DBNAME variable shows us an interesting feature of the Autonomous Transaction Processing database. We can append _low, _medium, _high, _tp, and _tp_urgent to add increasing levels of priority to our connection. Since there’s nothing special about what we’re doing, I chose to go for the lowest setting. However, you could choose any others, as these names are all defined in the wallet generated by your ATP instance.

Now let’s write some Rust code! In src/main.rs, we can put the following code:


use sibyl;

fn main() -> Result<(),Box<dyn std::error::Error>> {
   let oracle = sibyl::env()?;        let dbname = std::env::var("DBNAME").expect("database name"); let dbuser = std::env::var("DBUSER").expect("user name"); let dbpass = std::env::var("DBPASS").expect("password"); let session = oracle.connect(&dbname, &dbuser, &dbpass)?; let stmt = session.prepare(" select name, round(sum(actual_price),0) as sales from custsales c, genre g where c.genre_id = g.genre_id group by name order by sales desc fetch first :top_sales rows only ")?; let rows = stmt.query(5)?; while let Some(row) = rows.next()? { let name : &str = row.get(0)?; let sales : u32 = row.get(1)?; println!("{:10}: {:>5}", name, sales); } Ok(()) }

This little app will run one of the reports from the startup guide we used to load the data into our database. Specifically, you’ll see that it builds up a query on the custsales and genre tables and then generates a small report that gets printed to the standard output when it runs. If everything is configured correctly, then you’ll see something like this:


  [opc@rustybox-instance hello_sibyl]$ cargo run
  Compiling hello_sibyl v0.1.0 (/home/opc/hello_sibyl)
   Finished dev [unoptimized + debuginfo] target(s) in 0.59s
    Running `target/debug/hello_sibyl`
Drama     : 789734
Action    : 663613
Adventure : 397374
Comedy    : 354187
Sci-Fi    : 323543 

With only several lines of Rust code, we’ve just made a connection with our new Oracle database and run an arbitrary query over it. If we had more complex needs, you should be able to see how much this can come in handy.

Making Blocking and Nonblocking calls

You may have noticed that our code is running via the blocking feature of Sibyl. This indicates the mode by which OCI is being used too. Sibyl can also be configured to run with nonblocking calls to the database. However, when choosing this method,, you will be required to choose a corresponding asynchronous library. Sibyl provides multiple options for this, including the actix crate, developed by the same team that created the actix-web crate. If you wanted to add Sibyl to an Actix web app, you wouldn’t even need to put another dependency in place to use nonblocking OCI calls. Your dependencies would look like this in your Cargo.toml:


[dependencies]
actix-web = "4"
sibyl = { version = "0.6", features = ["nonblocking", "actix"] }    

You also wouldn’t need to make particularly drastic changes to your code to run it in the nonblocking mode:


use sibyl;

fn main() -> Result<(),Box<dyn std::error::Error>> {
   sibyl::block_on(async {       let oracle = sibyl::env()?; let dbname = std::env::var("DBNAME").expect("database name"); let dbuser = std::env::var("DBUSER").expect("user name"); let dbpass = std::env::var("DBPASS").expect("password"); let session = oracle.connect(&dbname, &dbuser, &dbpass).await?; let stmt = session.prepare(" select name, round(sum(actual_price),0) as sales from custsales c, genre g where c.genre_id = g.genre_id group by name order by sales desc fetch first :top_sales rows only ").await?; let rows = stmt.query(5).await?; while let Some(row) = rows.next().await? { let name : &str = row.get(0)?; let sales : u32 = row.get(1)?; println!("{:10}: {:>5}", name, sales); } Ok(()) }) }

Notice that it’s now running in a Sibyl block with an async call and several uses of the await syntax. Basically, it’s just normal asynchronous Rust code. This code isn’t using Actix Web, but it could easily be modified to be used in a web app, which is one common use case for async database calls.

Conclusion

Hopefully, you can see the value in using a powerful database driver like Sibyl paired with the power of Oracle Cloud Infrastructure. If you have been interested in trying Rust with an Oracle database, don’t hesitate! Try Sibyl!