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!