Introduction

Rust Oracle TimesTen

This blog shows you how to use Rust with both the Oracle Database and Oracle TimesTen In-Memory Database.

The same Rust source code will work against either Oracle Database 19c or Oracle TimesTen.

 

Rust is a high performance general purpose programming language which excels at memory and thread safety.  There is a high learning curve when learning Rust as the Rust compiler will not allow code which could lead to memory or thread safety issues.  Rust can be used to create diverse things such as WebAssemblies, GraphQL servers and is now an approved technology for Linux kernel device drivers. 

Rust uses

Rust was voted the most beloved programming language for the last six years in StackOverflow surverys beating out Python and JavaScript.  Once you stop fighting the Rust compiler, it will change the way you think about thread and memory safety for the better.

 

This blog covers the following topics:

  • Using the Oracle TimesTen Cache LiveLab
  • Installing Rust 1.64.0 in the LiveLab
  • Installing the rust-oracle SQL driver
  • Connecting to either Oracle Database 19c or TimesTen Cache
  • Simple queries returning resultsets
  • Dropping tables via a PLSQL block
  • Creating tables using SQL
  • Inserting, updating and deleting rows using SQL
  • Committing ACID transactions
  • How Rust programs can work the same with both Oracle Database 19c and TimesTen Cache

 

This blog is not a tutorial on the Rust language, instead shows you how to do the most basic SQL operations using basic Rust code.

Refer to the excellent documentation for more information on the Rust language.

 

Future blogs will cover other database topics for Rust such as:

  • Error handling with the rust-oracle driver
  • Using Connection pools
  • Using PLSQL
  • Optimizing SQL with Rust

 

 

 

Using the Oracle TimesTen Cache LiveLab

TimesTen Cache LiveLab

The TimesTen Cache LiveLab is a VM which runs on Oracle Cloud.  The VM uses Docker containers to run the Oracle Database 19c and TimesTen 22.1 Cache.

 

Docker constainer in LiveLab VM

The Oracle TimesTen Cache LiveLab is here.

You need to have completed Labs 1 to 13 before you can start using Rust with TimesTen.

 

 

 

rust-oracle versions

rust-oracle versions

  • The rust-oracle SQL driver works with both the Oracle Database and Oracle TimesTen
  • The rust-oracle driver is open source and was created by Kubo Takehiro
  • The LiveLab for TimesTen Cache uses Oracle Linux 8.6
  • This blog was tested with Rust 1.64.0

 

 

 

Installing Rust in your LiveLab VM container

LiveLab environment for Rust

To install Rust 1.64.0 in your TimesTen Cache LiveLab VM.

First ssh to your LiveLab VM:

ssh oracle@your_livelab_public_ip_address

 

Then ssh to the tthost1 Docker container in the LiveLabs VM:

ssh tthost1

 

Download and install Rust 1.64.0 in the container

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

 

Rust install - part 1

Rust install - more output

 

Setup the Rust environment

source “$HOME/.cargo/env”

 

This installed Rust 1.64.0 on my LiveLab container.

rustc -V
cargo -V

 

Rustc and cargo versions

The rust compiler [rustc] and package & build manager [cargo] are now ready for you to create a project in Rust.

 

 

Install a C compiler for Linking Rust Executables

sudo yum install gcc

This installed gcc 8.5.0 on the LiveLabs VM’s container.

 

Create a project in Rust

cargo new test1

 

Create a new Rust project

This creates a directory tree with a default config file [Cargo.toml] and a default 'Hello World' program in the src directory.

 

Build the test1 Rust project as a smoke test

Compile and link the default ‘Hello World’ Rust program [test1/src/main.rs] using cargo:

cargo build

 

cargo build test1

 

Run the test1 ‘Hello world’ program

/home/oracle/target/debug/test1

 

If you can see ‘Hello, world!’ on your console then the Rust build system is working correctly.

 

 

Install the rust-oracle driver

Add the following rust-oracle driver dependency to your test1/Cargo.toml config file.

oracle = { version = "0.5", features = ["chrono"] }

 

Your Cago.toml file should now look like this:

Cargo.toml dependency for rust-oracle driver

 

 

A Rust program that works with Oracle 19c and TimesTen 18c

Create a trivial Rust program in your test1 project using the rust-oracle driver that works with both Oracle Database 19c and Oracle TimesTen.

The test1/src/main.rs program does the following:

  • Connects as appuser/appuser@$SVC_NAME
  • Determines the version of the Oracle Instant Client
  • Determines the version of the database server
  • Queries a set of rows via a SQL where clause
  • Disconnects from the database

 

Add the following source code to test1/src/main.rs to replace your ‘Hello World’ program.

use std::env;
use oracle::{Connection, Result, Version};

fn main() -> Result<()> {

    let svc = match env::var_os(“SVC_NAME”) {
        Some(v) => v.into_string().unwrap(),
        None => panic!(“$SVC_NAME is not set”)
    };
    println!(“\nService Name is {}”, svc);

    let client_ver = Version::client()?;
    println!(“\nOracle Client Version: {}”, client_ver);

    let conn = Connection::connect(“appuser”, “appuser”, svc)?;
    let (server_ver, banner) = conn.server_version()?;

    println!(“\nDatabase Server Version: {}”, server_ver);
    println!(“\nServer Banner: {}\n”, banner);

    let sql = “select * from vpn_users where vpn_id = 0 and vpn_nb < 5 and rownum < 3 order by 2”;
    let mut stmt = conn.statement(sql).build()?;
    let rows = stmt.query(&[])?;

    // Get the column names
    for info in rows.column_info() {
       print!(“{} “, info.name())
    }
    println!(“”);

    // Display the resultset
    for row_result in rows {
        // print column values
        for (idx, val) in row_result?.sql_values().iter().enumerate() {
            if idx != 0 {
                print!(“,”);
            }
            print!(“{}”, val);
        }
        println!();
    }
    conn.close()?;

    println!(“\nBye”);
    Ok(())
}

 

Now build your new rust-oracle demo source code to create an executable

cargo build

build 1

build 2

The cargo build did the following:

  • Detected the dependency on the rust-oracle driver in the Cargo.toml config file
  • Downloaded this driver and its dependencies
  • Compiled all of those source code files
  • Used the gcc Linker to create an executable Rust program test1/target/debug/test1

 

Now run the test1 executable

/home/oracle/test1/target/debug/test1

Oops. Set the Linux environment variable SVC_NAME to define the TNS service name to use

export SVC_NAME=ORCLPDB1
../target/debug/test1

 

The output will look like this:

test1 on Oracle

I will explain why this worked in the next few sections …

The TimesTen Cache LiveLab already has the appuser schema and the VPN_USERS tables with data in it.

 

 

 

Use Oracle Net Service names to connect

There are multiple ways to connect with the rust-oracle driver. 

The TimesTen Cache LiveLab configures and uses $TNS_ADMIN/tnsnames.ora to enable connection to either the Oracle Database 19c or Oracle TimesTen.

ORCLPDB1 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = dbhost)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = ORCLPDB1)
    )
  )

SAMPLEDB =
  (DESCRIPTION =
    (CONNECT_DATA =
      (SERVER = timesten_direct)
      (SERVICE_NAME = sampledb)
    )
  )

SAMPLEDBCS =
  (DESCRIPTION =
    (CONNECT_DATA =
      (SERVER = timesten_client)
      (SERVICE_NAME = sampledbcs)
    )
  )

 

This tnsnames.ora file provides three services to connect to:

TNS service names

  • TimesTen client/server connections use TCP/IP sockets [and optionally TLS]. The TimesTen client can be on a different machine than the TimesTen server
  • TimesTen direct linked connections use shared memory
    • Direct linked connections are about 10x faster than TCP sockets
    • The application and TimesTen MUST to be on the same machine.
  • The TimesTen service name [direct linked or client/server] in tnsnames.or MUST point to a working TimesTen DSN in sys.odbc.ini

 

 

 

Choose which service name to connect to

The test1 Rust program uses an environment variable [$SVC_NAME] to define the service name to connect to.

You need to define the value of $SVC_NAME to choose which of the three services to connect to.

 

To set the SVC_NAME in the Linux bash shell to point ot Oracle Database 19c:

export SVC_NAME=ORCLPDB1

 

To set the SVC_NAME in the Linux bash shell to point to TimesTen with a direct linked connection:

export SVC_NAME=sampledb

 

To set the SVC_NAME in the Linux bash shell to point to a TimesTen client/server connection:

export SVC_NAME=sampledbcs

 

 

 

Use Rust to query Oracle Database 19c or TimesTen

Run test1 against Oracle Database 19c:

export SVC_NAME=ORCLPDB1
cd /home/oracle/test1/target/debug
./test1

 

The output from test1/target/debug/test1 when connected to Oracle Database 19c:

test1 run on Oracle 19c

 

The TimesTen Cache LiveLab uses:

  • Oracle Instant Client 19c which ships with TimesTen 22.1.  This is why the Client version output is 19.14
  • Oracle Database 19c which is why the Server version output is 19.3
  • Pre-loaded data which exists in the APPUSER.VPN_USERS table

 

Run test1/target/debug/test1 against TimesTen Cache:

export SVC_NAME=sampledb
./test1

 

The output of test1/target/debug/test1 when connected to Oracle TimesTen Cache:

test1 run against TimesTen cache

 

The TimesTen Cache LiveLab uses:

  • Oracle Instant Client 19c which ships with TimesTen 22.1.  This is why the Client version output is 19.14
  • TimesTen 22.1 which is why the Server version output is 22.1.1.3
  • Pre-loaded data which exists in the APPUSER.VPN_USERS table

 

Apart from the Database server version, the output is the same for both Oracle Database 19c and TimesTen Cache:

  • The Database server Version output is supposed to be different for Oracle and TimesTen
  • The output is the same for both TimesTen Direct Linked and Client/Server connections

 

 

 

A Rust application to modify Oracle Database 19c or TimesTen

The test2 program does the following:

  • Connects as appuser/appuser@$SVC_NAME
  • Determines the version of the database server
  • Drops table T via a PLSQL block
  • Creates table T via SQL
  • Inserts three rows into table T
  • Updates all of the rows in table T
  • Deletes a row by primary key in table T
  • Commits the transaction
  • Queries the rows in table T after the inserts, updates and deletes
  • Disconnects

 

Create a new Rust project called test2 and configure the Cargo.toml config file [like you did for project test1]. Make sure the Cargo.toml is for test2 and not test1 🙂

cd
cargo new test2
cd test2
cp ../test1/Cargo.toml .

 

Create proejct test2

 

Replace test2/src/main.rs with the following source code:

use std::env;
use oracle::{Connection, Result, Version};

fn main() -> Result<()> {

    let svc = match env::var_os(“SVC_NAME”) {
        Some(v) => v.into_string().unwrap(),
        None => panic!(“$SVC_NAME is not set”)
    };
    println!(“\nService Name is {}”, svc);

    let client_ver = Version::client()?;
    println!(“\nOracle Client Version: {}”, client_ver);

    let conn = Connection::connect(“appuser”, “appuser”, svc)?;
    let (server_ver, banner) = conn.server_version()?;

    println!(“\nDatabase Server Version: {}”, server_ver);
    println!(“\nServer Banner: {}”, banner);

    let plsql = “
      begin
        execute immediate ‘drop table t’;
      exception when others
        then null;
      end;”;

    let table_t = “
      create table t (
        id     number not null primary key,
        addr   varchar(64),
        price  number(6,2)
      )”;

    // Unconditionally drop table T
    conn.execute(plsql, &[])?;

    // Create table T
    conn.execute(table_t, &[])?;

    // Data to insert
    let data = [
      (1, “Seasame Street”, 42.69),
      (2, “500 Oracle Parkway”, 10.00),
      (3, “3rd Ave”, 1049.27),
    ];

    let inssql = “insert into t(id, addr, price) values (:id, :addr, :price)”;

    // Prepare the statement
    let mut insstmt = conn.statement(inssql).build()?;

    // insert rows using positional parameters
    for d in &data {
      insstmt.execute(&[&d.0, &d.1, &d.2])?;
    }

    // Display the resultset
    println!(“\nRows in table T after the inserts”);
    display_rows( &conn )?;

    // Update some rows
    conn.execute(“update t set addr = ‘Where the streets have no name'”, &[])?;
    println!(“\nRows in table T after the updates”);
    display_rows( &conn )?;

    // Delete a row
    conn.execute(“delete from t where id = 3”, &[])?;
    println!(“\nRows in table T after the delete”);
    display_rows( &conn )?;

    conn.commit()?;
    conn.close()?;

    println!(“\nBye”);
    Ok(())
}


fn display_rows (conn: &Connection) -> Result<()> {

  let sql = “select * from t”;
  let mut stmt = conn.statement(sql).build()?;
  let rows = stmt.query(&[])?;

  for row_result in rows {
    for (idx, val) in row_result?.sql_values().iter().enumerate() {
      if idx != 0 {
        print!(“,”);
      }
      print!(“{}”, val);
    }
    println!();
  }
  Ok(())
}

 

Build the test2 executable

cargo build

 

Run the test2/target/debug/test2 executable against Oracle Database 19c:

export SVC_NAME=ORCLPDB1
cd /home/oracle/test2/target/debug
./test2

 

The output of test2/target/debug/test2 when connected to Oracle Database 19c:

Output from test2 run against Oracle

 

 

Run test2/target/debug/test2 against TimesTen Cache:

export SVC_NAME=SAMPLEDB
./test2

 

The output of test2/target/debug/test2 when connected to TimesTen Cache:

cache test2

 

Apart from the Server version, the output is the same for both Oracle Database 19c and TimesTen Cache:

  • The Server version output is supposed to be different for Oracle and TimesTen
  • The output is the same for both TimesTen Direct Linked and Client/Server connections

 

 

 

Summary

  • The LiveLab for Oracle TimesTen Cache runs on Oracle Cloud
  • Use the rust-oracle SQL driver to talk to Oracle databases
  • The same Rust program can be used with Oracle 19c or TimesTen Cache [without re-compiling or re-linking]
  • Connect to the database via a service name in tnsnames.ora

 

 

Learn more SQL Language APIs

 

 

Learn more about TimesTen XE:

 

 

More TimesTen XE Blogs

 

 

Disclaimer: These are my personal thoughts and do not represent Oracle’s official viewpoint in any way, shape, or form.