use crate::{Result, oci::{self, *}, types::{date, number, raw, varchar}};
use libc::c_void;
use std::{collections::HashMap, ptr};
pub(crate) const DEFAULT_LONG_BUFFER_SIZE: u32 = 32768;
#[derive(Debug, PartialEq)]
pub enum ColumnType {
Unknown,
Char,
NChar,
Varchar,
NVarchar,
Clob,
NClob,
Long,
Raw,
LongRaw,
Blob,
Number,
BinaryFloat,
BinaryDouble,
Date,
Timestamp,
TimestampWithTimeZone,
TimestampWithLocalTimeZone,
IntervalYearToMonth,
IntervalDayToSecond,
RowID,
Cursor,
}
impl std::fmt::Display for ColumnType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ColumnType::Unknown => write!(f, "UNKNOWN"),
ColumnType::Char => write!(f, "CHAR"),
ColumnType::NChar => write!(f, "NCHAR"),
ColumnType::Varchar => write!(f, "VARCHAR2"),
ColumnType::NVarchar => write!(f, "NVARCHAR2"),
ColumnType::Clob => write!(f, "CLOB"),
ColumnType::NClob => write!(f, "NCLOB"),
ColumnType::Long => write!(f, "LONG"),
ColumnType::Raw => write!(f, "RAW"),
ColumnType::LongRaw => write!(f, "LONG RAW"),
ColumnType::Blob => write!(f, "BLOB"),
ColumnType::Number => write!(f, "NUMBER"),
ColumnType::BinaryFloat => write!(f, "BINARY_FLOAT"),
ColumnType::BinaryDouble => write!(f, "BINARY_DOUBLE"),
ColumnType::Date => write!(f, "DATE"),
ColumnType::Timestamp => write!(f, "TIMESTAMP"),
ColumnType::TimestampWithTimeZone => write!(f, "TIMESTAMP WITH TIME ZONE"),
ColumnType::TimestampWithLocalTimeZone => write!(f, "TIMESTAMP WITH LOCAL TIME ZONE"),
ColumnType::IntervalYearToMonth => write!(f, "INTERVAL YEAR TO MONTH"),
ColumnType::IntervalDayToSecond => write!(f, "INTERVAL DAY TO SECOND"),
ColumnType::RowID => write!(f, "ROWID"),
ColumnType::Cursor => write!(f, "SYS_REFCURSOR"),
}
}
}
pub struct ColumnInfo<'a> {
desc: Ptr<OCIParam>,
err: &'a OCIError,
}
impl<'a> ColumnInfo<'a> {
pub(crate) fn new(desc: Ptr<OCIParam>, err: &'a OCIError) -> Self {
Self { desc, err }
}
fn get_attr<T: attr::AttrGet>(&self, attr: u32) -> Result<T> {
attr::get(attr, OCI_DTYPE_PARAM, self.desc.as_ref(), self.err)
}
pub fn is_visible(&self) -> Result<bool> {
let invisible: u8 = self.get_attr(OCI_ATTR_INVISIBLE_COL)?;
Ok(invisible == 0)
}
pub fn is_null(&self) -> Result<bool> {
let is_null: u8 = self.get_attr(OCI_ATTR_IS_NULL)?;
Ok(is_null != 0)
}
pub fn is_identity(&self) -> Result<bool> {
let col_props: u8 = self.get_attr(OCI_ATTR_COL_PROPERTIES)?;
Ok(col_props & OCI_ATTR_COL_PROPERTY_IS_IDENTITY != 0)
}
pub fn is_generated_always(&self) -> Result<bool> {
let col_props: u8 = self.get_attr(OCI_ATTR_COL_PROPERTIES)?;
Ok(col_props & OCI_ATTR_COL_PROPERTY_IS_GEN_ALWAYS != 0)
}
pub fn is_generated_on_null(&self) -> Result<bool> {
let col_props: u8 = self.get_attr(OCI_ATTR_COL_PROPERTIES)?;
Ok(col_props & OCI_ATTR_COL_PROPERTY_IS_GEN_BY_DEF_ON_NULL != 0)
}
pub fn name(&self) -> Result<&str> {
self.get_attr::<&str>(OCI_ATTR_NAME)
}
pub fn size(&self) -> Result<usize> {
let size = self.get_attr::<u16>(OCI_ATTR_DATA_SIZE)? as usize;
Ok(size)
}
pub fn char_size(&self) -> Result<usize> {
let size = self.get_attr::<u16>(OCI_ATTR_CHAR_SIZE)? as usize;
Ok(size)
}
pub fn precision(&self) -> Result<i16> {
self.get_attr::<i16>(OCI_ATTR_PRECISION)
}
pub fn scale(&self) -> Result<i8> {
self.get_attr::<i8>(OCI_ATTR_SCALE)
}
pub fn data_type(&self) -> Result<ColumnType> {
let col_type = match self.get_attr::<u16>(OCI_ATTR_DATA_TYPE)? {
SQLT_RDD => ColumnType::RowID,
SQLT_CHR => match self.get_attr::<u8>(OCI_ATTR_CHARSET_FORM)? {
SQLCS_NCHAR => ColumnType::NVarchar,
_ => ColumnType::Varchar,
},
SQLT_AFC => match self.get_attr::<u8>(OCI_ATTR_CHARSET_FORM)? {
SQLCS_NCHAR => ColumnType::NChar,
_ => ColumnType::Char,
},
SQLT_CLOB => match self.get_attr::<u8>(OCI_ATTR_CHARSET_FORM)? {
SQLCS_NCHAR => ColumnType::NClob,
_ => ColumnType::Clob,
},
SQLT_LNG => ColumnType::Long,
SQLT_BIN => ColumnType::Raw,
SQLT_LBI => ColumnType::LongRaw,
SQLT_BLOB => ColumnType::Blob,
SQLT_NUM => ColumnType::Number,
SQLT_DAT => ColumnType::Date,
SQLT_TIMESTAMP => ColumnType::Timestamp,
SQLT_TIMESTAMP_TZ => ColumnType::TimestampWithTimeZone,
SQLT_TIMESTAMP_LTZ => ColumnType::TimestampWithLocalTimeZone,
SQLT_INTERVAL_YM => ColumnType::IntervalYearToMonth,
SQLT_INTERVAL_DS => ColumnType::IntervalDayToSecond,
SQLT_IBFLOAT => ColumnType::BinaryFloat,
SQLT_IBDOUBLE => ColumnType::BinaryDouble,
SQLT_RSET => ColumnType::Cursor,
_ => ColumnType::Unknown,
};
Ok(col_type)
}
pub fn type_name(&self) -> Result<&str> {
self.get_attr::<&str>(OCI_ATTR_TYPE_NAME)
}
pub fn schema_name(&self) -> Result<&str> {
self.get_attr::<&str>(OCI_ATTR_SCHEMA_NAME)
}
}
pub(crate) enum ColumnBuffer {
Text(Ptr<OCIString>),
CLOB(Descriptor<OCICLobLocator>),
Binary(Ptr<OCIRaw>),
BLOB(Descriptor<OCIBLobLocator>),
BFile(Descriptor<OCIBFileLocator>),
Number(Box<OCINumber>),
Date(OCIDate),
Timestamp(Descriptor<OCITimestamp>),
TimestampTZ(Descriptor<OCITimestampTZ>),
TimestampLTZ(Descriptor<OCITimestampLTZ>),
IntervalYM(Descriptor<OCIIntervalYearToMonth>),
IntervalDS(Descriptor<OCIIntervalDayToSecond>),
Float(f32),
Double(f64),
Rowid(Descriptor<OCIRowid>),
Cursor(Handle<OCIStmt>),
}
impl ColumnBuffer {
fn new(data_type: u16, data_size: u32, env: &impl AsRef<OCIEnv>, err: &impl AsRef<OCIError>) -> Result<Self> {
let val = match data_type {
SQLT_DAT => ColumnBuffer::Date(date::new()),
SQLT_TIMESTAMP => ColumnBuffer::Timestamp(Descriptor::<OCITimestamp>::new(env)?),
SQLT_TIMESTAMP_TZ => ColumnBuffer::TimestampTZ(Descriptor::<OCITimestampTZ>::new(env)?),
SQLT_TIMESTAMP_LTZ => {
ColumnBuffer::TimestampLTZ(Descriptor::<OCITimestampLTZ>::new(env)?)
}
SQLT_INTERVAL_YM => {
ColumnBuffer::IntervalYM(Descriptor::<OCIIntervalYearToMonth>::new(env)?)
}
SQLT_INTERVAL_DS => {
ColumnBuffer::IntervalDS(Descriptor::<OCIIntervalDayToSecond>::new(env)?)
}
SQLT_NUM => ColumnBuffer::Number(Box::new(number::new())),
SQLT_IBFLOAT => ColumnBuffer::Float(0f32),
SQLT_IBDOUBLE => ColumnBuffer::Double(0f64),
SQLT_BIN | SQLT_LBI => ColumnBuffer::Binary(raw::new(data_size, env.as_ref(), err.as_ref())?),
SQLT_CLOB => ColumnBuffer::CLOB(Descriptor::<OCICLobLocator>::new(env)?),
SQLT_BLOB => ColumnBuffer::BLOB(Descriptor::<OCIBLobLocator>::new(env)?),
SQLT_BFILE => ColumnBuffer::BFile(Descriptor::<OCIBFileLocator>::new(env)?),
SQLT_RDD => ColumnBuffer::Rowid(Descriptor::<OCIRowid>::new(env)?),
SQLT_RSET => ColumnBuffer::Cursor(Handle::<OCIStmt>::new(env)?),
_ => ColumnBuffer::Text(varchar::new(data_size, env.as_ref(), err.as_ref())?),
};
Ok(val)
}
fn drop(&mut self, env: &OCIEnv, err: &OCIError) {
match self {
ColumnBuffer::Text(oci_str_ptr) => {
varchar::free(oci_str_ptr, env, err);
}
ColumnBuffer::Binary(oci_raw_ptr) => {
raw::free(oci_raw_ptr, env, err);
}
_ => {}
}
}
fn get_output_buffer_def(&mut self, col_size: usize) -> (u16, *mut c_void, usize) {
use std::mem::size_of;
match self {
ColumnBuffer::Text(oci_str_ptr) => (SQLT_LVC, oci_str_ptr.get() as *mut c_void, col_size + size_of::<u32>()),
ColumnBuffer::Binary(oci_raw_ptr) => (SQLT_LVB, oci_raw_ptr.get() as *mut c_void, col_size + size_of::<u32>()),
ColumnBuffer::Number(oci_num_box) => (SQLT_VNU, oci_num_box.as_mut() as *mut OCINumber as *mut c_void, size_of::<OCINumber>()),
ColumnBuffer::Date(oci_date) => (SQLT_ODT, oci_date as *mut OCIDate as *mut c_void, size_of::<OCIDate>()),
ColumnBuffer::Timestamp(ts) => (SQLT_TIMESTAMP, ts.as_ptr() as *mut c_void, size_of::<*mut OCIDateTime>()),
ColumnBuffer::TimestampTZ(ts) => (SQLT_TIMESTAMP_TZ, ts.as_ptr() as *mut c_void, size_of::<*mut OCIDateTime>()),
ColumnBuffer::TimestampLTZ(ts) => (SQLT_TIMESTAMP_LTZ, ts.as_ptr() as *mut c_void, size_of::<*mut OCIDateTime>()),
ColumnBuffer::IntervalYM(int) => (SQLT_INTERVAL_YM, int.as_ptr() as *mut c_void, size_of::<*mut OCIInterval>()),
ColumnBuffer::IntervalDS(int) => (SQLT_INTERVAL_DS, int.as_ptr() as *mut c_void, size_of::<*mut OCIInterval>()),
ColumnBuffer::Float(val) => (SQLT_BFLOAT, val as *mut f32 as *mut c_void, size_of::<f32>()),
ColumnBuffer::Double(val) => (SQLT_BDOUBLE, val as *mut f64 as *mut c_void, size_of::<f64>()),
ColumnBuffer::CLOB(lob) => (SQLT_CLOB, lob.as_ptr() as *mut c_void, size_of::<*mut OCILobLocator>()),
ColumnBuffer::BLOB(lob) => (SQLT_BLOB, lob.as_ptr() as *mut c_void, size_of::<*mut OCILobLocator>()),
ColumnBuffer::BFile(lob) => (SQLT_BFILE, lob.as_ptr() as *mut c_void, size_of::<*mut OCILobLocator>()),
ColumnBuffer::Rowid(rowid) => (SQLT_RDD, rowid.as_ptr() as *mut c_void, size_of::<*mut OCIRowid>()),
ColumnBuffer::Cursor(handle) => (SQLT_RSET, handle.as_ptr() as *mut c_void, 0),
}
}
}
#[allow(dead_code)]
pub struct Column {
buf: ColumnBuffer,
inf: Descriptor<OCIParam>,
def: Ptr<OCIDefine>,
len: u32,
ind: i16
}
impl Column {
fn new(buf: ColumnBuffer, inf: Descriptor<OCIParam>) -> Self {
Self {
buf,
inf,
def: Ptr::<OCIDefine>::null(),
len: 0,
ind: 0
}
}
pub(crate) fn is_null(&self) -> bool {
self.ind == OCI_IND_NULL
}
pub(crate) fn data(&mut self) -> &mut ColumnBuffer {
&mut self.buf
}
pub(crate) fn name(&self, err: &OCIError) -> Result<&str> {
self.inf.get_attr(OCI_ATTR_NAME, err)
}
}
pub struct Columns {
names: HashMap<&'static str, usize>,
cols: Vec<Column>,
env: Ptr<OCIEnv>,
err: Ptr<OCIError>,
#[cfg(feature="nonblocking")]
lob_col_present: bool,
}
impl Drop for Columns {
fn drop(&mut self) {
for col in self.cols.iter_mut() {
col.buf.drop(&self.env, &self.err);
}
}
}
impl Columns {
pub(crate) fn new(stmt: Ptr<OCIStmt>, env: Ptr<OCIEnv>, err: Ptr<OCIError>, max_long_fetch_size: u32) -> Result<Self> {
let num_columns : u32 = attr::get(OCI_ATTR_PARAM_COUNT, OCI_HTYPE_STMT, stmt.as_ref(), err.as_ref())?;
let num_columns = num_columns as usize;
let mut names = HashMap::with_capacity(num_columns);
let mut cols = Vec::with_capacity(num_columns);
#[cfg(feature="nonblocking")]
let mut lob_col_present = false;
let utf8_factor = std::env::var("ORACLE_UTF8_CONV_FACTOR").ok().and_then(|val| val.parse::<u32>().ok()).unwrap_or(1);
for i in 0..num_columns {
let col_info = param::get((i + 1) as u32, OCI_HTYPE_STMT, stmt.as_ref(), err.as_ref())?;
let data_type = col_info.get_attr::<u16>(OCI_ATTR_DATA_TYPE, err.as_ref())?;
let data_size = match data_type {
SQLT_LNG | SQLT_LBI => max_long_fetch_size,
_ => col_info.get_attr::<u16>(OCI_ATTR_DATA_SIZE, err.as_ref())? as u32 * utf8_factor,
};
let nchar_form = col_info.get_attr::<u8>(OCI_ATTR_CHARSET_FORM, err.as_ref())? == SQLCS_NCHAR;
#[cfg(feature="nonblocking")]
if !lob_col_present {
lob_col_present = match data_type {
SQLT_CLOB | SQLT_BLOB => true,
_ => false,
};
}
cols.push(Column::new(ColumnBuffer::new(data_type, data_size, &env, &err)?, col_info));
let (output_type, output_buff_ptr, output_buff_size) = cols[i].buf.get_output_buffer_def(data_size as usize);
oci::define_by_pos(
stmt.as_ref(), cols[i].def.as_mut_ptr(), err.as_ref(),
(i + 1) as u32,
output_buff_ptr, output_buff_size as i64, output_type,
&mut cols[i].ind,
&mut cols[i].len,
ptr::null_mut::<u16>(),
OCI_DEFAULT
)?;
if nchar_form {
attr::set(OCI_ATTR_CHARSET_FORM, SQLCS_NCHAR, OCI_HTYPE_DEFINE, cols[i].def.as_ref(), err.as_ref())?;
}
let name : &str = cols[i].inf.get_attr(OCI_ATTR_NAME, err.as_ref())?;
names.insert(name, i);
}
#[cfg(feature="nonblocking")]
let res = Self { names, cols, env, err, lob_col_present };
#[cfg(not(feature="nonblocking"))]
let res = Self { names, cols, env, err };
Ok(res)
}
pub(crate) fn col_index(&self, name: &str) -> Option<usize> {
self.names.get(name).map(|ix| *ix)
}
pub(crate) fn col(&self, index: usize) -> Option<&Column> {
self.cols.get(index)
}
pub(crate) fn col_mut(&mut self, index: usize) -> Option<&mut Column> {
self.cols.get_mut(index)
}
pub(crate) fn is_null(&self, index: usize) -> bool {
self.col(index).map_or(true, |col| col.is_null())
}
pub(crate) fn column_param<'a>(&'a self, index: usize) -> Option<Ptr<OCIParam>> {
self.col(index).map(|col| col.inf.get_ptr())
}
#[cfg(feature="nonblocking")]
pub(crate) fn has_lob_col(&self) -> bool {
self.lob_col_present
}
}