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:

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

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

1[oracle@b3124c94c1c7 ~]$ cd rustmail/
2[oracle@b3124c94c1c7 rustmail]$ ls
3Cargo.toml  src
4[oracle@b3124c94c1c7 rustmail]$ cat Cargo.toml
5[package]
6name = "rustmail"
7version = "0.1.0"
8authors = ["oracle"]
9edition = "2018"
10 
11# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 
13[dependencies]

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

1[dependencies]
2lettre = "0.10.0-beta.4"
3libc = "0.2.94"
4 
5[lib]
6name = "rustmail"
7crate-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:

1use lettre::transport::smtp::authentication::Credentials;
2use lettre::{Message, SmtpTransport, Transport};
3 
4use libc::c_char; //To use as a C char raw pointer
5use std::ffi::CStr; //To to convert from a C char raw pointer (*const c_char)
6use std::ffi::CString; //To return C char raw pointer (*mut c_char), based on String value
7 
8//Rust should not mangle with function names
9//Function has to be declared as external and public
10#[no_mangle]
11pub extern "C" fn sendemail(c_to: *const c_char, c_subj: *const c_char, c_what: *const c_char ) -> *mut c_char{
12//*const c_char is a raw C pointer and it has to handled in unsafe{}
13let s_to = unsafe {
14    assert!(!c_to.is_null());
15    CStr::from_ptr(c_to)
16};
17 
18let s_subj = unsafe {
19    assert!(!c_subj.is_null());
20    CStr::from_ptr(c_subj)
21};
22 
23let s_what = unsafe {
24    assert!(!c_what.is_null());
25    CStr::from_ptr(c_what)
26};
27 
28let email = Message::builder()
29    .from("someone <someone@ora-600.pl>".parse().unwrap())
30    .reply_to("someone < someone@ora-600.pl>".parse().unwrap())
31    .to(s_to.to_str().unwrap().parse().unwrap())
32    .subject(String::from(s_subj.to_str().unwrap()))
33    .body(String::from(s_what.to_str().unwrap()))
34    .unwrap();
35 
36let creds = Credentials::new("someone@ora-600.pl".to_string(), "SuperSecretPassword".to_string());
37 
38// Open a remote connection to a mail server
39let mailer = SmtpTransport::relay("mailserver.smtp.com.pl")
40    .unwrap()
41    .credentials(creds)
42    .build();
43 
44// Send the email
45let mut s_result = String::new();
46 
47match mailer.send(&email) {
48    Ok(_) => s_result.push_str("Email sent successfully!"),
49    Err(e) => s_result = format!("Something went wrong: {}", e.to_string())
50}
51 
52let c_result = CString::new(s_result).unwrap();
53c_result.into_raw()
54}

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

1[oracle@b3124c94c1c7 rustmail]$ vim src/lib.rs
2[oracle@b3124c94c1c7 rustmail]$ cargo build --release
3    Updating crates.io index
4   Compiling libc v0.2.94
5   Compiling cfg-if v1.0.0
6   Compiling autocfg v1.0.1
7   Compiling pkg-config v0.3.19
8   Compiling cc v1.0.67
9   Compiling smallvec v1.6.1
10   Compiling bitflags v1.2.1
11   Compiling log v0.4.14
12   Compiling radium v0.5.3
13   Compiling scopeguard v1.1.0
14   Compiling tinyvec_macros v0.1.0
15   Compiling version_check v0.9.3
16   Compiling foreign-types-shared v0.1.1
17   Compiling openssl v0.10.34
18   Compiling memchr v2.4.0
19   Compiling native-tls v0.2.7
20   Compiling once_cell v1.7.2
21   Compiling funty v1.1.0
22   Compiling tap v1.0.1
23   Compiling matches v0.1.8
24   Compiling wyz v0.2.0
25   Compiling openssl-probe v0.1.2
26   Compiling match_cfg v0.1.0
27   Compiling regex-syntax v0.6.25
28   Compiling mime v0.3.16
29   Compiling fastrand v1.4.1
30   Compiling httpdate v1.0.0
31   Compiling base64 v0.13.0
32   Compiling quoted_printable v0.4.3
33   Compiling instant v0.1.9
34   Compiling lock_api v0.4.4
35   Compiling tinyvec v1.2.0
36   Compiling foreign-types v0.3.2
37   Compiling unicode-bidi v0.3.5
38   Compiling nom v6.1.2
39   Compiling openssl-sys v0.9.63
40   Compiling regex v1.5.4
41   Compiling unicode-normalization v0.1.17
42   Compiling parking_lot_core v0.8.3
43   Compiling hostname v0.3.1
44   Compiling bitvec v0.19.5
45   Compiling parking_lot v0.11.1
46   Compiling idna v0.2.3
47   Compiling scheduled-thread-pool v0.2.5
48   Compiling r2d2 v0.8.9
49   Compiling lettre v0.10.0-beta.4
50   Compiling rustmail v0.1.0 (/home/oracle/rustmail)
51    Finished release [optimized] target(s) in 21.39s

After this you should have a library ready to use:

1[oracle@b3124c94c1c7 rustmail]$ file target/release/librustmail.so
2target/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:

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

The rest is the same:

1SQL> create or replace library lib_rustmail as '/home/oracle/extproc/librustmail.so';
2  2  /
3 
4Library created.
5 
6SQL> create or replace function f_rust_sendmail(p_to varchar2, p_subcject varchar2, p_body varchar2) return varchar2 as
7  2  external
8  name "sendemail"
9  4  library lib_rustmail
10  5  language C;
11  6  /
12 
13Function created.
14 
15SQL> select f_rust_sendmail('kamil@ora-600.pl', 'jebacpis', 'Beer') from dual;
16 
17F_RUST_SENDMAIL('KAMIL@ORA-600.PL','JEBACPIS','DUPA')
18--------------------------------------------------------------------------------
19Email 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