Rust as Oracle External Procedure


07.05.2021
by Kamil Stawiarski

Inspired by a little chat with Frits Hoogland about the future of programming languages I challenged myself to learn a bit of Rust and show how to create a shared library to send emails and attach it to Oracle as an external procedure (just as I did with GoLang here: https://blog.ora-600.pl/2021/05/04/golang-as-oracle-external-procedure/)

The steps are actually pretty easy after installing the Rust (which is also trivial):

First we have to create a new project with cargo:

[oracle@b3124c94c1c7 ~]$ cargo new rustmail
     Created binary (application) `rustmail` package

This is the default output of Cargo.toml file, which we have to modify a bit:

[oracle@b3124c94c1c7 ~]$ cd rustmail/
[oracle@b3124c94c1c7 rustmail]$ ls
Cargo.toml  src
[oracle@b3124c94c1c7 rustmail]$ cat Cargo.toml
[package]
name = "rustmail"
version = "0.1.0"
authors = ["oracle"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

We will add the following sections to Cargo.toml with dependencies and mark the project to be compiled as shared dynamic library:

[dependencies]
lettre = "0.10.0-beta.4"
libc = "0.2.94"

[lib]
name = "rustmail"
crate-type = ["dylib"]

With those changes in place we can create a file in directory src, called lib.rs – this is the source code of the library with comments to clarify the syntax:

use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};

use libc::c_char; //To use as a C char raw pointer
use std::ffi::CStr; //To to convert from a C char raw pointer (*const c_char)
use std::ffi::CString; //To return C char raw pointer (*mut c_char), based on String value

//Rust should not mangle with function names
//Function has to be declared as external and public
#[no_mangle]
pub extern "C" fn sendemail(c_to: *const c_char, c_subj: *const c_char, c_what: *const c_char ) -> *mut c_char{
//*const c_char is a raw C pointer and it has to handled in unsafe{}
let s_to = unsafe {
	assert!(!c_to.is_null());
	CStr::from_ptr(c_to)
};

let s_subj = unsafe {
	assert!(!c_subj.is_null());
	CStr::from_ptr(c_subj)
};

let s_what = unsafe {
	assert!(!c_what.is_null());
	CStr::from_ptr(c_what)
};

let email = Message::builder()
    .from("someone <someone@ora-600.pl>".parse().unwrap())
    .reply_to("someone < someone@ora-600.pl>".parse().unwrap())
    .to(s_to.to_str().unwrap().parse().unwrap())
    .subject(String::from(s_subj.to_str().unwrap()))
    .body(String::from(s_what.to_str().unwrap()))
    .unwrap();

let creds = Credentials::new("someone@ora-600.pl".to_string(), "SuperSecretPassword".to_string());

// Open a remote connection to a mail server
let mailer = SmtpTransport::relay("mailserver.smtp.com.pl")
    .unwrap()
    .credentials(creds)
    .build();

// Send the email
let mut s_result = String::new();

match mailer.send(&email) {
    Ok(_) => s_result.push_str("Email sent successfully!"),
    Err(e) => s_result = format!("Something went wrong: {}", e.to_string())
}

let c_result = CString::new(s_result).unwrap();
c_result.into_raw()
}

Great, let’s save it and compile our project!

[oracle@b3124c94c1c7 rustmail]$ vim src/lib.rs
[oracle@b3124c94c1c7 rustmail]$ cargo build --release
    Updating crates.io index
   Compiling libc v0.2.94
   Compiling cfg-if v1.0.0
   Compiling autocfg v1.0.1
   Compiling pkg-config v0.3.19
   Compiling cc v1.0.67
   Compiling smallvec v1.6.1
   Compiling bitflags v1.2.1
   Compiling log v0.4.14
   Compiling radium v0.5.3
   Compiling scopeguard v1.1.0
   Compiling tinyvec_macros v0.1.0
   Compiling version_check v0.9.3
   Compiling foreign-types-shared v0.1.1
   Compiling openssl v0.10.34
   Compiling memchr v2.4.0
   Compiling native-tls v0.2.7
   Compiling once_cell v1.7.2
   Compiling funty v1.1.0
   Compiling tap v1.0.1
   Compiling matches v0.1.8
   Compiling wyz v0.2.0
   Compiling openssl-probe v0.1.2
   Compiling match_cfg v0.1.0
   Compiling regex-syntax v0.6.25
   Compiling mime v0.3.16
   Compiling fastrand v1.4.1
   Compiling httpdate v1.0.0
   Compiling base64 v0.13.0
   Compiling quoted_printable v0.4.3
   Compiling instant v0.1.9
   Compiling lock_api v0.4.4
   Compiling tinyvec v1.2.0
   Compiling foreign-types v0.3.2
   Compiling unicode-bidi v0.3.5
   Compiling nom v6.1.2
   Compiling openssl-sys v0.9.63
   Compiling regex v1.5.4
   Compiling unicode-normalization v0.1.17
   Compiling parking_lot_core v0.8.3
   Compiling hostname v0.3.1
   Compiling bitvec v0.19.5
   Compiling parking_lot v0.11.1
   Compiling idna v0.2.3
   Compiling scheduled-thread-pool v0.2.5
   Compiling r2d2 v0.8.9
   Compiling lettre v0.10.0-beta.4
   Compiling rustmail v0.1.0 (/home/oracle/rustmail)
    Finished release [optimized] target(s) in 21.39s

After this you should have a library ready to use:

[oracle@b3124c94c1c7 rustmail]$ file target/release/librustmail.so
target/release/librustmail.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=e54868dcf696e60a89222aab4e5ead32899edc88, not stripped

Now we can move it to appropriate location, and modify extproc.ora to look like this:

[oracle@b3124c94c1c7 rustmail]$ cp target/release/librustmail.so ~/extproc/
[oracle@b3124c94c1c7 rustmail]$ vim $ORACLE_HOME/hs/admin/extproc.ora
[oracle@b3124c94c1c7 rustmail]$ cat !$ | tail -1
cat $ORACLE_HOME/hs/admin/extproc.ora | tail -1
SET EXTPROC_DLLS=ONLY:/home/oracle/extproc/sendemail.so,ONLY:/home/oracle/extproc/librustmail.so

The rest is the same:

SQL> create or replace library lib_rustmail as '/home/oracle/extproc/librustmail.so';
  2  /

Library created.

SQL> create or replace function f_rust_sendmail(p_to varchar2, p_subcject varchar2, p_body varchar2) return varchar2 as
  2  external
  3  name "sendemail"
  4  library lib_rustmail
  5  language C;
  6  /

Function created.

SQL> select f_rust_sendmail('kamil@ora-600.pl', 'jebacpis', 'Beer') from dual;

F_RUST_SENDMAIL('KAMIL@ORA-600.PL','JEBACPIS','DUPA')
--------------------------------------------------------------------------------
Email sent successfully!

That’s it 🙂 I personally believe, that this is extremely powerful feature of Oracle Database. You don’t have to wait for implementing Graal inside a database to use different languages – you can use the full power of modern languages right now and in a very easy way!


Contact us

Database Whisperers sp. z o. o. sp. k.
al. Jerozolimskie 200, 3rd floor, room 342
02-486 Warszawa
NIP: 5272744987
REGON:362524978
+48 508 943 051
+48 661 966 009
info@ora-600.pl

Newsletter Sign up to be updated