diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 32344cc..a390c9a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,9 +5,9 @@ [package] name = "rust_odbc_test" -version = "0.1.0" +version = "0.2.0" edition = "2024" [dependencies] -anyhow = "1.0.102" -odbc-api = "24.0.0" +anyhow = "1.0" +odbc-api = "28.1" diff --git a/rust/src/main_parameter_markers.rs b/rust/src/main_parameter_markers.rs new file mode 100644 index 0000000..6241d8a --- /dev/null +++ b/rust/src/main_parameter_markers.rs @@ -0,0 +1,265 @@ +/* +Copyright Dr. Gerd Anders. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// https://docs.rs/odbc-api/latest/odbc_api/struct.Connection.html + +// Readme: Rename this file to main.rs - save already exisitng main.rs before if desired - and compile it + +// For option 2: +use anyhow::{Context, Result}; + +use odbc_api::{Cursor, Environment, ConnectionOptions, IntoParameter}; + +fn main() -> Result<(), Box> { + let env = Environment::new()?; + + // Requirement: Db2 instance is started + + // Option 1: Values in plain text: + // const DB2_DSN: &str = "dsn_db2samples"; + // const DB2_USER: &str = "db2luw1"; + // const DB2_PWD: &str = "db2lUw1"; + + // let conn = env.connect(&DB2_DSN, &DB2_USER, &DB2_PWD, ConnectionOptions::default())?; + + // Option 2: Values taken from environmnt variables + // in bash: + // export DB2_DSN="dsn_db2samples"; + // export DB2_USER="db2luw1"; + // export DB2_PWD="db2lUw1"; + // + let db2_dsn = std::env::var("DB2_DSN").context("DB2_DSN environment variable not set")?; + let db2_user = std::env::var("DB2_USER").context("DB2_USER environment variable not set")?; + let db2_pwd = std::env::var("DB2_PWD").context("DB2_PWD environment variable not set")?; + + let conn = env.connect(&db2_dsn, &db2_user, &db2_pwd, ConnectionOptions::default())?; + + // The SELECT queries below do not use any parameters + let query_params = (); + let timeout_sec: Option = None; + let mut row_cnt: u32 = 0; + + println!(""); + + // SELECT sample + + println!("SELECT sample"); + + println!(""); + + let select_stmt = "SELECT empno, firstnme, COALESCE(midinit, '-') AS midinit, lastname, edlevel FROM employee ORDER BY empno"; + + println!("| {:>8} | {:<12} | {:<8} | {:<15} | {:<7} |", "empno", "firstnme", "midinit", "lastname", "edlevel"); + println!("|----------|--------------|----------|-----------------|---------|"); + + let mut cursor = conn.execute(select_stmt, query_params, timeout_sec)?.expect("Assume select statement creates cursor"); + while let Some(mut row) = cursor.next_row()? { + let mut buf = Vec::::new(); + row.get_text(1, &mut buf)?; + let empno = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(2, &mut buf)?; + let firstnme = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(3, &mut buf)?; + let midinit = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(4, &mut buf)?; + let lastname = String::from_utf8(buf).unwrap(); + + let mut edlevel: u8 = 0; + row.get_data(5, &mut edlevel)?; + + println!("| {empno:>8} | {firstnme:<12} | {midinit:<8} | {lastname:<15} | {edlevel:>7} |"); + + row_cnt += 1; + } + + println!("\n {} record(s) selected.", row_cnt); + + // INSERT sample + + println!("\n\nINSERT sample"); + + let insert_stmt = "INSERT INTO employee (empno, firstnme, midinit, lastname, workdept, phoneno, hiredate, job, edlevel, sex, birthdate, salary, bonus, comm) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + let ip0 = "000000".into_parameter(); + let ip1 = "THOMAS".into_parameter(); + let ip2 = "J".into_parameter(); + let ip3 = "WATSON".into_parameter(); + let ip4 = "A00".into_parameter(); + let ip5 = "1234".into_parameter(); + let ip6 = "05/01/1914".into_parameter(); + let ip7 = "FOUNDER".into_parameter(); + let ip8: i16 = 20; + let ip9 = "M".into_parameter(); + let ip10 = "02/17/1874".into_parameter(); + let ip11: f64 = 250000.0; + let ip12: f64 = 2500.0; + let ip13: f64 = 8750.0; + let insert_params = (&ip0, &ip1, &ip2, &ip3, &ip4, &ip5, &ip6, &ip7, &ip8, &ip9, &ip10, &ip11, &ip12, &ip13); + + match conn.execute(insert_stmt, insert_params, timeout_sec) { + Err(e) => println!("INSERT stmt: {}", e), + Ok(None) => println!("INSERT stmt: Ok(None))"), + Ok(Some(_)) => println!("INSERT stmt: Ok(some(_))"), + } + + // INSERT validation + + let select_stmt = "SELECT empno, firstnme, COALESCE(midinit, '-') AS midinit, lastname, edlevel FROM employee WHERE empno = ?"; + let sp0 = "000000".into_parameter(); + let select_params = (&sp0,); + + println!("After INSERT:\n"); + println!("| {:>8} | {:<12} | {:<8} | {:<15} | {:<7} |", "empno", "firstnme", "midinit", "lastname", "edlevel"); + println!("|----------|--------------|----------|-----------------|---------|"); + + row_cnt = 0; + + let mut cursor = conn.execute(select_stmt, select_params, timeout_sec)?.expect("Assume select statement creates cursor"); + while let Some(mut row) = cursor.next_row()? { + let mut buf = Vec::::new(); + row.get_text(1, &mut buf)?; + let empno = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(2, &mut buf)?; + let firstnme = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(3, &mut buf)?; + let midinit = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(4, &mut buf)?; + let lastname = String::from_utf8(buf).unwrap(); + + let mut edlevel: u8 = 0; + row.get_data(5, &mut edlevel)?; + + println!("| {empno:>8} | {firstnme:<12} | {midinit:<8} | {lastname:<15} | {edlevel:>7} |"); + + row_cnt += 1; + } + + println!("\n {} record(s) selected.", row_cnt); + + // UPDATE sample + + println!("\n\nUPDATE sample"); + + let update_stmt = "UPDATE employee SET empno = ? WHERE empno = ?"; + + let up0 = "000001".into_parameter(); + let up1 = "000000".into_parameter(); + let update_params = (&up0, &up1); + + match conn.execute(update_stmt, update_params, timeout_sec) { + Err(e) => println!("UPDATE stmt: {}", e), + Ok(None) => println!("UPDATE stmt: Ok(None))"), + Ok(Some(_)) => println!("UPDATE stmt: Ok(some(_))"), + } + + // UPDATE validation + + let select_stmt = "SELECT empno, firstnme, COALESCE(midinit, '-') AS midinit, lastname, edlevel FROM employee WHERE empno IN ('000000', '000001') ORDER BY empno"; + + row_cnt = 0; + + println!("After UPDATE:\n"); + println!("| {:>8} | {:<12} | {:<8} | {:<15} | {:<7} |", "empno", "firstnme", "midinit", "lastname", "edlevel"); + println!("|----------|--------------|----------|-----------------|---------|"); + + let mut cursor = conn.execute(select_stmt, query_params, timeout_sec)?.expect("Assume select statement creates cursor"); + while let Some(mut row) = cursor.next_row()? { + let mut buf = Vec::::new(); + row.get_text(1, &mut buf)?; + let empno = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(2, &mut buf)?; + let firstnme = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(3, &mut buf)?; + let midinit = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(4, &mut buf)?; + let lastname = String::from_utf8(buf).unwrap(); + + let mut edlevel: u8 = 0; + row.get_data(5, &mut edlevel)?; + + println!("| {empno:>8} | {firstnme:<12} | {midinit:<8} | {lastname:<15} | {edlevel:>7} |"); + + row_cnt += 1; + } + + println!("\n {} record(s) selected.", row_cnt); + + // DELETE sample + + println!("\n\nDELETE sample"); + + let delete_stmt = "DELETE FROM employee WHERE empno <= ?"; + + let dp0 = "000001".into_parameter(); + let delete_params = (&dp0,); + + match conn.execute(delete_stmt, delete_params, timeout_sec) { + Err(e) => println!("DELETE stmt: {}", e), + Ok(None) => println!("DELETE stmt: Ok(None))"), + Ok(Some(_)) => println!("DELETE stmt: Ok(some(_))"), + } + + // DELETE validation (should not output any records after DELETE) + + let select_stmt = "SELECT empno, firstnme, COALESCE(midinit, '-') AS midinit, lastname, edlevel FROM employee WHERE empno IN ('000000', '000001') ORDER BY empno"; + + println!("After DELETE:\n"); + println!("| {:>8} | {:<12} | {:<8} | {:<15} | {:<7} |", "empno", "firstnme", "midinit", "lastname", "edlevel"); + println!("|----------|--------------|----------|-----------------|---------|"); + + row_cnt = 0; + + let mut cursor = conn.execute(select_stmt, query_params, timeout_sec)?.expect("Assume select statement creates cursor"); + while let Some(mut row) = cursor.next_row()? { + let mut buf = Vec::::new(); + row.get_text(1, &mut buf)?; + let empno = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(2, &mut buf)?; + let firstnme = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(3, &mut buf)?; + let midinit = String::from_utf8(buf).unwrap(); + + let mut buf = Vec::::new(); + row.get_text(4, &mut buf)?; + let lastname = String::from_utf8(buf).unwrap(); + + let mut edlevel: u8 = 0; + row.get_data(5, &mut edlevel)?; + + println!("| {empno:>8} | {firstnme:<12} | {midinit:<8} | {lastname:<15} | {edlevel:>7} |"); + + row_cnt += 1; + } + + println!("\n {} record(s) selected.", row_cnt); + + println!("\n"); + + Ok(()) +} diff --git a/rust/src/main_sp_udf.rs b/rust/src/main_sp_udf.rs new file mode 100644 index 0000000..0249983 --- /dev/null +++ b/rust/src/main_sp_udf.rs @@ -0,0 +1,128 @@ +/* +Copyright Dr. Gerd Anders. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// https://docs.rs/odbc-api/latest/odbc_api/struct.Connection.html +// https://docs.rs/odbc-api/latest/odbc_api/parameter/index.html + +// Readme: Rename this file to main.rs - save already exisitng main.rs before if desired - and compile it + +// For option 2: +use anyhow::{Context, Result}; + +// When Using InOut (see e. g. second URL above for a sample): +// use odbc_api::{Environment, ConnectionOptions, Out, InOut, Nullable}; +use odbc_api::{Cursor, Environment, ConnectionOptions, Out, Nullable}; +use odbc_api::parameter::VarCharArray; + +fn main() -> Result<(), Box> { + let env = Environment::new()?; + + // Requirement: Db2 instance is started + + // Option 1: Values in plain text: + // const DB2_DSN: &str = "dsn_db2samples"; + // const DB2_USER: &str = "db2luw1"; + // const DB2_PWD: &str = "db2lUw1"; + + // let conn = env.connect(&DB2_DSN, &DB2_USER, &DB2_PWD, ConnectionOptions::default())?; + + // Option 2: Values taken from environmnt variables + // in bash: + // export DB2_DSN="dsn_db2samples"; + // export DB2_USER="db2luw1"; + // export DB2_PWD="db2lUw1"; + // + let db2_dsn = std::env::var("DB2_DSN").context("DB2_DSN environment variable not set")?; + let db2_user = std::env::var("DB2_USER").context("DB2_USER environment variable not set")?; + let db2_pwd = std::env::var("DB2_PWD").context("DB2_PWD environment variable not set")?; + + let conn = env.connect(&db2_dsn, &db2_user, &db2_pwd, ConnectionOptions::default())?; + + // Sample for CALLing a Stored Procedure (SP) + + println!(""); + + println!("Sample CALLing a SP: CALL DBMS_UTILITY.DB_VERSION(?, ?)"); + + println!(""); + + let mut rc = Nullable::::null(); + let mut version = VarCharArray::<64>::NULL; + let mut compatibility = VarCharArray::<64>::NULL; + + // on Db2 command line, the SP below would return output like this: + // CALL DBMS_UTILITY.DB_VERSION(?, ?) + // + // Value of output parameters + // -------------------------- + // Parameter Name : VERSION + // Parameter Value : DB2 v12.1.4.0 + + // Parameter Name : COMPATIBILITY + // Parameter Value : DB2 v12.1.4.0 + + // Return Status = 0 + + conn.execute("{? = CALL DBMS_UTILITY.DB_VERSION(?, ?)}", + (Out(&mut rc), Out(&mut version), Out(&mut compatibility)), + None)?; + + let version_str = version.as_bytes().map(|b| String::from_utf8_lossy(b).into_owned()).unwrap_or_default(); + let compat_str = compatibility.as_bytes().map(|b| String::from_utf8_lossy(b).into_owned()).unwrap_or_default(); + + println!("Return code : {:?}", rc.into_opt()); + println!("VERSION : {}", version_str); + println!("COMPATIBILITY: {}", compat_str); + + println!("\n"); + + // Sample for executing a User-Defined Function (UDF) + + // for parameter marker in SELECT statement below: + let my_ibm_dbs = VarCharArray::<3>::new(b"db2"); + + // on Db2 command line, the UDF below would return output like this: + // db2 "VALUES UPPER('db2')" + // + // 1 + // --- + // DB2 + // + // 1 record(s) selected. + + let timeout_sec: Option = None; + let mut row_cnt: u32 = 0; + + println!(""); + + // SELECT sample + + println!("SELECT sample using a UDF: SELECT UPPER(?) FROM SYSIBM.SYSDUMMY1"); + + println!(""); + + let select_udf_stmt = "SELECT UPPER(?) FROM SYSIBM.SYSDUMMY1"; + // Equivalant for SQL statement above: + // let values_udf_stmt = "VALUES UPPER(?)"; + + let mut cursor = conn.execute(select_udf_stmt, &my_ibm_dbs, timeout_sec)?.expect("Assume select statement creates cursor"); + + while let Some(mut row) = cursor.next_row()? { + let mut buf = Vec::::new(); + row.get_text(1, &mut buf)?; + let upper_case = String::from_utf8(buf).unwrap(); + + println!("{}", upper_case); + + row_cnt += 1; + } + + println!("\n {} record(s) selected.", row_cnt); + + println!("\n"); + + Ok(()) +}