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!