use crate::chkerr;
use crate::connection::Conn;
use crate::oci_attr::data_type::{AttrValue, DataType};
use crate::oci_attr::mode::{ReadMode, WriteMode};
use crate::oci_attr::{self, OciAttr, SqlFnCode};
use crate::private;
use crate::sql_type::FromSql;
use crate::sql_type::OracleType;
use crate::sql_type::RefCursor;
use crate::sql_type::ToSql;
#[cfg(doc)]
use crate::sql_type::{Blob, Clob, Nclob};
use crate::sql_value::BufferRowIndex;
use crate::to_rust_str;
use crate::AssertSend;
use crate::Connection;
use crate::Context;
use crate::DpiStmt;
use crate::Error;
use crate::OdpiStr;
use crate::Result;
use crate::ResultSet;
use crate::Row;
use crate::RowValue;
use crate::SqlValue;
use odpic_sys::*;
use std::borrow::ToOwned;
use std::fmt;
#[cfg(doc)]
use std::io::Read;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use std::ptr;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
const SQLFNCODE_CREATE_TYPE: u16 = 77;
const SQLFNCODE_ALTER_TYPE: u16 = 80;
const SQLFNCODE_DROP_TYPE: u16 = 78;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LobBindType {
Locator,
Bytes,
}
#[derive(Clone, Debug)]
pub struct QueryParams {
pub fetch_array_size: u32,
pub prefetch_rows: Option<u32>,
pub lob_bind_type: LobBindType,
}
impl QueryParams {
pub fn new() -> QueryParams {
QueryParams {
fetch_array_size: DPI_DEFAULT_FETCH_ARRAY_SIZE,
prefetch_rows: None,
lob_bind_type: LobBindType::Bytes,
}
}
}
pub struct StatementBuilder<'conn, 'sql> {
conn: &'conn Connection,
sql: &'sql str,
query_params: QueryParams,
scrollable: bool,
tag: String,
exclude_from_cache: bool,
}
impl<'conn, 'sql> StatementBuilder<'conn, 'sql> {
pub(crate) fn new(conn: &'conn Connection, sql: &'sql str) -> StatementBuilder<'conn, 'sql> {
StatementBuilder {
conn,
sql,
query_params: QueryParams::new(),
scrollable: false,
tag: "".into(),
exclude_from_cache: false,
}
}
pub fn fetch_array_size(&mut self, size: u32) -> &mut StatementBuilder<'conn, 'sql> {
self.query_params.fetch_array_size = size;
self
}
pub fn prefetch_rows(&mut self, size: u32) -> &mut StatementBuilder<'conn, 'sql> {
self.query_params.prefetch_rows = Some(size);
self
}
pub fn lob_locator(&mut self) -> &mut StatementBuilder<'conn, 'sql> {
self.query_params.lob_bind_type = LobBindType::Locator;
self
}
pub fn tag<T>(&mut self, tag_name: T) -> &mut StatementBuilder<'conn, 'sql>
where
T: Into<String>,
{
self.tag = tag_name.into();
self
}
pub fn exclude_from_cache(&mut self) -> &mut StatementBuilder<'conn, 'sql> {
self.exclude_from_cache = true;
self
}
pub fn build(&self) -> Result<Statement> {
Statement::new(self)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum StatementType {
Select,
Insert,
Update,
Delete,
Merge,
Create,
Alter,
Drop,
Begin,
Declare,
Commit,
Rollback,
ExplainPlan,
Call,
Unknown,
}
impl StatementType {
pub(crate) fn from_enum(num: dpiStatementType) -> StatementType {
match num as u32 {
DPI_STMT_TYPE_SELECT => StatementType::Select,
DPI_STMT_TYPE_INSERT => StatementType::Insert,
DPI_STMT_TYPE_UPDATE => StatementType::Update,
DPI_STMT_TYPE_DELETE => StatementType::Delete,
DPI_STMT_TYPE_MERGE => StatementType::Merge,
DPI_STMT_TYPE_CREATE => StatementType::Create,
DPI_STMT_TYPE_ALTER => StatementType::Alter,
DPI_STMT_TYPE_DROP => StatementType::Drop,
DPI_STMT_TYPE_BEGIN => StatementType::Begin,
DPI_STMT_TYPE_DECLARE => StatementType::Declare,
DPI_STMT_TYPE_COMMIT => StatementType::Commit,
DPI_STMT_TYPE_ROLLBACK => StatementType::Rollback,
DPI_STMT_TYPE_EXPLAIN_PLAN => StatementType::ExplainPlan,
DPI_STMT_TYPE_CALL => StatementType::Call,
_ => StatementType::Unknown,
}
}
}
impl fmt::Display for StatementType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StatementType::Select => write!(f, "select"),
StatementType::Insert => write!(f, "insert"),
StatementType::Update => write!(f, "update"),
StatementType::Delete => write!(f, "delete"),
StatementType::Merge => write!(f, "merge"),
StatementType::Create => write!(f, "create"),
StatementType::Alter => write!(f, "alter"),
StatementType::Drop => write!(f, "drop"),
StatementType::Begin => write!(f, "PL/SQL(begin)"),
StatementType::Declare => write!(f, "PL/SQL(declare)"),
StatementType::Commit => write!(f, "commit"),
StatementType::Rollback => write!(f, "rollback"),
StatementType::ExplainPlan => write!(f, "explain plan"),
StatementType::Call => write!(f, "call"),
StatementType::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug)]
pub(crate) struct Stmt {
pub(crate) conn: Conn,
pub(crate) handle: DpiStmt,
pub(crate) row: Option<Row>,
shared_buffer_row_index: Arc<AtomicU32>,
last_buffer_row_index: u32,
more_rows: bool,
pub(crate) query_params: QueryParams,
tag: String,
}
impl Stmt {
pub(crate) fn new(conn: Conn, handle: DpiStmt, query_params: QueryParams, tag: String) -> Stmt {
Stmt {
conn,
handle,
row: None,
shared_buffer_row_index: Arc::new(AtomicU32::new(0)),
last_buffer_row_index: 0,
more_rows: false,
query_params,
tag,
}
}
pub(crate) fn ctxt(&self) -> &Context {
self.conn.ctxt()
}
pub(crate) fn conn(&self) -> &Conn {
&self.conn
}
pub(crate) fn handle(&self) -> *mut dpiStmt {
self.handle.raw
}
fn close(&mut self) -> Result<()> {
let tag = OdpiStr::new(&self.tag);
chkerr!(self.ctxt(), dpiStmt_close(self.handle(), tag.ptr, tag.len));
Ok(())
}
pub(crate) fn init_row(&mut self, num_cols: usize) -> Result<()> {
self.shared_buffer_row_index.store(0, Ordering::Relaxed);
self.last_buffer_row_index = 0;
self.more_rows = true;
if self.row.is_some() {
return Ok(());
}
let mut column_info = Vec::with_capacity(num_cols);
let mut column_values = Vec::with_capacity(num_cols);
for i in 0..num_cols {
let info = ColumnInfo::new(self, i)?;
let val = SqlValue::for_column(
self.conn.clone(),
self.query_params.clone(),
self.shared_buffer_row_index.clone(),
info.oracle_type(),
self.handle(),
(i + 1) as u32,
)?;
column_info.push(info);
column_values.push(val);
}
self.row = Some(Row::new(column_info, column_values)?);
Ok(())
}
fn try_next(&mut self) -> Result<Option<&Row>> {
let index = self.shared_buffer_row_index.load(Ordering::Relaxed);
let last_index = self.last_buffer_row_index;
if index + 1 < last_index {
self.shared_buffer_row_index
.store(index + 1, Ordering::Relaxed);
Ok(Some(self.row.as_ref().unwrap()))
} else if self.more_rows && self.fetch_rows()? {
Ok(Some(self.row.as_ref().unwrap()))
} else {
Ok(None)
}
}
pub fn next(&mut self) -> Option<Result<&Row>> {
self.try_next().transpose()
}
pub fn fetch_rows(&mut self) -> Result<bool> {
let handle = self.handle();
let row = self.row.as_mut().unwrap();
for i in 0..(row.column_info.len()) {
if row.column_values[i].fetch_array_buffer_shared_count()? > 1 {
let oratype = row.column_info[i].oracle_type();
row.column_values[i] = SqlValue::for_column(
self.conn.clone(),
self.query_params.clone(),
self.shared_buffer_row_index.clone(),
oratype,
handle,
(i + 1) as u32,
)?;
}
}
let mut new_index = 0;
let mut num_rows = 0;
let mut more_rows = 0;
chkerr!(
self.ctxt(),
dpiStmt_fetchRows(
handle,
self.query_params.fetch_array_size,
&mut new_index,
&mut num_rows,
&mut more_rows
)
);
self.shared_buffer_row_index
.store(new_index, Ordering::Relaxed);
self.last_buffer_row_index = new_index + num_rows;
self.more_rows = more_rows != 0;
Ok(num_rows != 0)
}
pub fn row_count(&self) -> Result<u64> {
let mut count = 0;
chkerr!(self.ctxt(), dpiStmt_getRowCount(self.handle(), &mut count));
Ok(count)
}
}
impl AssertSend for Stmt {}
impl Drop for Stmt {
fn drop(&mut self) {
let _ = self.close();
}
}
#[derive(Debug)]
pub struct Statement {
pub(crate) stmt: Stmt,
statement_type: StatementType,
is_returning: bool,
bind_count: usize,
bind_names: Vec<String>,
bind_values: Vec<SqlValue<'static>>,
}
impl Statement {
fn new(builder: &StatementBuilder<'_, '_>) -> Result<Statement> {
let conn = builder.conn;
let sql = OdpiStr::new(builder.sql);
let tag = OdpiStr::new(&builder.tag);
let mut handle = DpiStmt::null();
chkerr!(
conn.ctxt(),
dpiConn_prepareStmt(
conn.handle(),
i32::from(builder.scrollable),
sql.ptr,
sql.len,
tag.ptr,
tag.len,
&mut handle.raw
)
);
let mut info = MaybeUninit::uninit();
chkerr!(conn.ctxt(), dpiStmt_getInfo(handle.raw, info.as_mut_ptr()));
let info = unsafe { info.assume_init() };
let mut num = 0;
chkerr!(conn.ctxt(), dpiStmt_getBindCount(handle.raw, &mut num));
let bind_count = num as usize;
let mut bind_names = Vec::with_capacity(bind_count);
let mut bind_values = Vec::with_capacity(bind_count);
if bind_count > 0 {
let mut names: Vec<*const c_char> = vec![ptr::null_mut(); bind_count];
let mut lengths = vec![0; bind_count];
chkerr!(
conn.ctxt(),
dpiStmt_getBindNames(
handle.raw,
&mut num,
names.as_mut_ptr(),
lengths.as_mut_ptr()
)
);
bind_names = Vec::with_capacity(num as usize);
for i in 0..(num as usize) {
bind_names.push(to_rust_str(names[i], lengths[i]));
bind_values.push(SqlValue::for_bind(
conn.conn.clone(),
builder.query_params.clone(),
1,
));
}
};
let tag = if builder.exclude_from_cache {
chkerr!(conn.ctxt(), dpiStmt_deleteFromCache(handle.raw));
String::new()
} else {
builder.tag.clone()
};
Ok(Statement {
stmt: Stmt::new(conn.conn.clone(), handle, builder.query_params.clone(), tag),
statement_type: StatementType::from_enum(info.statementType),
is_returning: info.isReturning != 0,
bind_count,
bind_names,
bind_values,
})
}
pub fn close(&mut self) -> Result<()> {
self.stmt.close()
}
pub(crate) fn ctxt(&self) -> &Context {
self.conn().ctxt()
}
pub(crate) fn conn(&self) -> &Conn {
&self.stmt.conn
}
pub(crate) fn handle(&self) -> *mut dpiStmt {
self.stmt.handle.raw
}
pub fn query(&mut self, params: &[&dyn ToSql]) -> Result<ResultSet<Row>> {
self.exec(params, true, "query")?;
Ok(ResultSet::<Row>::new(&mut self.stmt))
}
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<ResultSet<Row>> {
self.exec_named(params, true, "query_named")?;
Ok(ResultSet::<Row>::new(&mut self.stmt))
}
pub fn query_as<T>(&mut self, params: &[&dyn ToSql]) -> Result<ResultSet<T>>
where
T: RowValue,
{
self.exec(params, true, "query_as")?;
Ok(ResultSet::new(&mut self.stmt))
}
pub fn into_result_set<T>(mut self, params: &[&dyn ToSql]) -> Result<ResultSet<'static, T>>
where
T: RowValue,
{
self.exec(params, true, "into_result_set")?;
Ok(ResultSet::from_stmt(self.stmt))
}
pub fn query_as_named<T>(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<ResultSet<T>>
where
T: RowValue,
{
self.exec_named(params, true, "query_as_named")?;
Ok(ResultSet::new(&mut self.stmt))
}
pub fn into_result_set_named<T>(
mut self,
params: &[(&str, &dyn ToSql)],
) -> Result<ResultSet<'static, T>>
where
T: RowValue,
{
self.exec_named(params, true, "into_result_set_named")?;
Ok(ResultSet::from_stmt(self.stmt))
}
pub fn query_row(&mut self, params: &[&dyn ToSql]) -> Result<Row> {
let mut rows = self.query(params)?;
rows.next().unwrap_or(Err(Error::no_data_found()))
}
pub fn query_row_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Row> {
let mut rows = self.query_named(params)?;
rows.next().unwrap_or(Err(Error::no_data_found()))
}
pub fn query_row_as<T>(&mut self, params: &[&dyn ToSql]) -> Result<T>
where
T: RowValue,
{
let mut rows = self.query_as::<T>(params)?;
rows.next().unwrap_or(Err(Error::no_data_found()))
}
pub fn query_row_as_named<T>(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<T>
where
T: RowValue,
{
let mut rows = self.query_as_named::<T>(params)?;
rows.next().unwrap_or(Err(Error::no_data_found()))
}
pub fn execute(&mut self, params: &[&dyn ToSql]) -> Result<()> {
self.exec(params, false, "execute")
}
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<()> {
self.exec_named(params, false, "execute_named")
}
fn check_stmt_type(&self, must_be_query: bool, method_name: &str) -> Result<()> {
if must_be_query {
if self.statement_type == StatementType::Select {
Ok(())
} else {
Err(Error::invalid_operation(format!(
"could not use the `{}` method for non-select statements",
method_name
)))
}
} else if self.statement_type != StatementType::Select {
Ok(())
} else {
Err(Error::invalid_operation(format!(
"could not use the `{}` method for select statements",
method_name
)))
}
}
pub(crate) fn exec(
&mut self,
params: &[&dyn ToSql],
must_be_query: bool,
method_name: &str,
) -> Result<()> {
self.check_stmt_type(must_be_query, method_name)?;
for (i, param) in params.iter().enumerate() {
self.bind(i + 1, *param)?;
}
self.exec_common()
}
pub(crate) fn exec_named(
&mut self,
params: &[(&str, &dyn ToSql)],
must_be_query: bool,
method_name: &str,
) -> Result<()> {
self.check_stmt_type(must_be_query, method_name)?;
for param in params {
self.bind(param.0, param.1)?;
}
self.exec_common()
}
fn exec_common(&mut self) -> Result<()> {
let mut num_query_columns = 0;
let mut exec_mode = DPI_MODE_EXEC_DEFAULT;
if self.conn().autocommit() {
exec_mode |= DPI_MODE_EXEC_COMMIT_ON_SUCCESS;
}
chkerr!(
self.ctxt(),
dpiStmt_setFetchArraySize(self.handle(), self.stmt.query_params.fetch_array_size)
);
if let Some(prefetch_rows) = self.stmt.query_params.prefetch_rows {
chkerr!(
self.ctxt(),
dpiStmt_setPrefetchRows(self.handle(), prefetch_rows)
);
}
chkerr!(
self.ctxt(),
dpiStmt_execute(self.handle(), exec_mode, &mut num_query_columns)
);
self.ctxt().set_warning();
if self.is_ddl() {
let fncode = self.oci_attr::<SqlFnCode>()?;
match fncode {
SQLFNCODE_CREATE_TYPE | SQLFNCODE_ALTER_TYPE | SQLFNCODE_DROP_TYPE => {
self.conn().clear_object_type_cache()?
}
_ => (),
}
}
if self.statement_type == StatementType::Select {
self.stmt.init_row(num_query_columns as usize)?;
}
if self.is_returning {
for val in self.bind_values.iter_mut() {
val.fix_internal_data()?;
}
}
Ok(())
}
pub fn bind_count(&self) -> usize {
self.bind_count
}
pub fn bind_names(&self) -> Vec<&str> {
self.bind_names.iter().map(|name| name.as_str()).collect()
}
pub fn bind<I>(&mut self, bindidx: I, value: &dyn ToSql) -> Result<()>
where
I: BindIndex,
{
let pos = bindidx.idx(self)?;
let conn = Connection::from_conn(self.conn().clone());
if self.bind_values[pos].init_handle(&value.oratype(&conn)?)? {
chkerr!(
self.ctxt(),
bindidx.bind(self.handle(), self.bind_values[pos].handle()?)
);
}
self.bind_values[pos].set(value)
}
pub fn bind_value<I, T>(&self, bindidx: I) -> Result<T>
where
I: BindIndex,
T: FromSql,
{
let pos = bindidx.idx(self)?;
self.bind_values[pos].get()
}
pub fn returned_values<I, T>(&self, bindidx: I) -> Result<Vec<T>>
where
I: BindIndex,
T: FromSql,
{
let mut rows = 0;
chkerr!(self.ctxt(), dpiStmt_getRowCount(self.handle(), &mut rows));
if rows == 0 {
return Ok(vec![]);
}
let mut sqlval = self.bind_values[bindidx.idx(self)?].clone_with_narrow_lifetime()?;
if rows > sqlval.array_size as u64 {
rows = sqlval.array_size as u64;
}
let mut vec = Vec::with_capacity(rows as usize);
for i in 0..rows {
sqlval.buffer_row_index = BufferRowIndex::Owned(i as u32);
vec.push(sqlval.get()?);
}
Ok(vec)
}
pub fn row_count(&self) -> Result<u64> {
self.stmt.row_count()
}
pub fn implicit_result(&self) -> Result<Option<RefCursor>> {
let mut handle = DpiStmt::null();
chkerr!(
self.ctxt(),
dpiStmt_getImplicitResult(self.handle(), &mut handle.raw)
);
if handle.is_null() {
Ok(None)
} else {
let cursor = RefCursor::from_handle(
self.stmt.conn.clone(),
handle,
self.stmt.query_params.clone(),
)?;
Ok(Some(cursor))
}
}
pub fn statement_type(&self) -> StatementType {
self.statement_type
}
pub fn is_query(&self) -> bool {
self.statement_type == StatementType::Select
}
pub fn is_plsql(&self) -> bool {
matches!(
self.statement_type,
StatementType::Begin | StatementType::Declare | StatementType::Call
)
}
pub fn is_ddl(&self) -> bool {
matches!(
self.statement_type,
StatementType::Create | StatementType::Drop | StatementType::Alter
)
}
pub fn is_dml(&self) -> bool {
matches!(
self.statement_type,
StatementType::Insert
| StatementType::Update
| StatementType::Delete
| StatementType::Merge
)
}
pub fn is_returning(&self) -> bool {
self.is_returning
}
pub fn last_row_id(&self) -> Result<Option<String>> {
let mut rowid = ptr::null_mut();
chkerr!(self.ctxt(), dpiStmt_getLastRowid(self.handle(), &mut rowid));
if rowid.is_null() {
Ok(None)
} else {
let mut ptr = ptr::null();
let mut len = 0;
chkerr!(
self.ctxt(),
dpiRowid_getStringValue(rowid, &mut ptr, &mut len)
);
Ok(Some(to_rust_str(ptr, len)))
}
}
pub fn oci_attr<T>(&self) -> Result<<<T::DataType as DataType>::Type as ToOwned>::Owned>
where
T: OciAttr<HandleType = oci_attr::handle::Stmt>,
T::Mode: ReadMode,
{
let attr_value = AttrValue::from_stmt(self, <T>::ATTR_NUM);
unsafe { <T::DataType>::get(attr_value) }
}
pub fn set_oci_attr<T>(&mut self, value: &<T::DataType as DataType>::Type) -> Result<()>
where
T: OciAttr<HandleType = oci_attr::handle::Stmt>,
T::Mode: WriteMode,
{
let mut attr_value = AttrValue::from_stmt(self, <T>::ATTR_NUM);
unsafe { <T::DataType>::set(&mut attr_value, value) }
}
}
impl AssertSend for Statement {}
#[derive(Debug, Clone)]
pub struct ColumnInfo {
name: String,
oracle_type: OracleType,
nullable: bool,
}
impl ColumnInfo {
fn new(stmt: &Stmt, idx: usize) -> Result<ColumnInfo> {
let mut info = MaybeUninit::uninit();
chkerr!(
stmt.ctxt(),
dpiStmt_getQueryInfo(stmt.handle(), (idx + 1) as u32, info.as_mut_ptr())
);
let info = unsafe { info.assume_init() };
Ok(ColumnInfo {
name: to_rust_str(info.name, info.nameLength),
oracle_type: OracleType::from_type_info(stmt.conn(), &info.typeInfo)?,
nullable: info.nullOk != 0,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn oracle_type(&self) -> &OracleType {
&self.oracle_type
}
pub fn nullable(&self) -> bool {
self.nullable
}
}
impl fmt::Display for ColumnInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.nullable {
write!(f, "{} {}", self.name, self.oracle_type)
} else {
write!(f, "{} {} NOT NULL", self.name, self.oracle_type)
}
}
}
pub trait BindIndex: private::Sealed {
#[doc(hidden)]
fn idx(&self, stmt: &Statement) -> Result<usize>;
#[doc(hidden)]
unsafe fn bind(&self, stmt_handle: *mut dpiStmt, var_handle: *mut dpiVar) -> i32;
}
impl BindIndex for usize {
fn idx(&self, stmt: &Statement) -> Result<usize> {
let num = stmt.bind_count();
if 0 < num && 1 <= *self && *self <= num {
Ok(*self - 1)
} else {
Err(Error::invalid_bind_index(*self))
}
}
unsafe fn bind(&self, stmt_handle: *mut dpiStmt, var_handle: *mut dpiVar) -> i32 {
dpiStmt_bindByPos(stmt_handle, *self as u32, var_handle)
}
}
impl BindIndex for &str {
fn idx(&self, stmt: &Statement) -> Result<usize> {
let bindname = self.to_uppercase();
stmt.bind_names()
.iter()
.position(|&name| name == bindname)
.ok_or_else(|| Error::invalid_bind_name(*self))
}
unsafe fn bind(&self, stmt_handle: *mut dpiStmt, var_handle: *mut dpiVar) -> i32 {
let s = OdpiStr::new(self);
dpiStmt_bindByName(stmt_handle, s.ptr, s.len, var_handle)
}
}
pub trait ColumnIndex: private::Sealed {
#[doc(hidden)]
fn idx(&self, column_info: &[ColumnInfo]) -> Result<usize>;
}
impl ColumnIndex for usize {
fn idx(&self, column_info: &[ColumnInfo]) -> Result<usize> {
let ncols = column_info.len();
if *self < ncols {
Ok(*self)
} else {
Err(Error::invalid_column_index(*self))
}
}
}
impl ColumnIndex for &str {
fn idx(&self, column_info: &[ColumnInfo]) -> Result<usize> {
for (idx, info) in column_info.iter().enumerate() {
if info.name.as_str().eq_ignore_ascii_case(self) {
return Ok(idx);
}
}
Err(Error::invalid_column_name(*self))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util;
#[test]
fn column_info() -> Result<()> {
let conn = test_util::connect()?;
let mut stmt = conn.statement("select * from TestDataTypes").build()?;
let rows = stmt.query(&[])?;
let colinfo = rows.column_info();
assert_eq!(colinfo[0].name(), "STRINGCOL");
assert_eq!(colinfo[0].oracle_type(), &OracleType::Varchar2(100));
assert_eq!(colinfo[1].name(), "UNICODECOL");
assert_eq!(colinfo[1].oracle_type(), &OracleType::NVarchar2(100));
assert_eq!(colinfo[2].name(), "FIXEDCHARCOL");
assert_eq!(colinfo[2].oracle_type(), &OracleType::Char(100));
assert_eq!(colinfo[3].name(), "FIXEDUNICODECOL");
assert_eq!(colinfo[3].oracle_type(), &OracleType::NChar(100));
assert_eq!(colinfo[4].name(), "RAWCOL");
assert_eq!(colinfo[4].oracle_type(), &OracleType::Raw(30));
assert_eq!(colinfo[5].name(), "FLOATCOL");
assert_eq!(colinfo[5].oracle_type(), &OracleType::Float(126));
assert_eq!(colinfo[6].name(), "DOUBLEPRECCOL");
assert_eq!(colinfo[6].oracle_type(), &OracleType::Float(126));
assert_eq!(colinfo[7].name(), "INTCOL");
assert_eq!(colinfo[7].oracle_type(), &OracleType::Number(9, 0));
assert_eq!(colinfo[8].name(), "NUMBERCOL");
assert_eq!(colinfo[8].oracle_type(), &OracleType::Number(9, 2));
assert_eq!(colinfo[9].name(), "DATECOL");
assert_eq!(colinfo[9].oracle_type(), &OracleType::Date);
assert_eq!(colinfo[10].name(), "TIMESTAMPCOL");
assert_eq!(colinfo[10].oracle_type(), &OracleType::Timestamp(6));
assert_eq!(colinfo[11].name(), "TIMESTAMPTZCOL");
assert_eq!(colinfo[11].oracle_type(), &OracleType::TimestampTZ(6));
assert_eq!(colinfo[12].name(), "TIMESTAMPLTZCOL");
assert_eq!(colinfo[12].oracle_type(), &OracleType::TimestampLTZ(6));
assert_eq!(colinfo[13].name(), "INTERVALDSCOL");
assert_eq!(colinfo[13].oracle_type(), &OracleType::IntervalDS(2, 6));
assert_eq!(colinfo[14].name(), "INTERVALYMCOL");
assert_eq!(colinfo[14].oracle_type(), &OracleType::IntervalYM(2));
assert_eq!(colinfo[15].name(), "BINARYFLTCOL");
assert_eq!(colinfo[15].oracle_type(), &OracleType::BinaryFloat);
assert_eq!(colinfo[16].name(), "BINARYDOUBLECOL");
assert_eq!(colinfo[16].oracle_type(), &OracleType::BinaryDouble);
assert_eq!(colinfo[17].name(), "CLOBCOL");
assert_eq!(colinfo[17].oracle_type(), &OracleType::CLOB);
assert_eq!(colinfo[18].name(), "NCLOBCOL");
assert_eq!(colinfo[18].oracle_type(), &OracleType::NCLOB);
assert_eq!(colinfo[19].name(), "BLOBCOL");
assert_eq!(colinfo[19].oracle_type(), &OracleType::BLOB);
assert_eq!(colinfo[20].name(), "BFILECOL");
assert_eq!(colinfo[20].oracle_type(), &OracleType::BFILE);
assert_eq!(colinfo[21].name(), "LONGCOL");
assert_eq!(colinfo[21].oracle_type(), &OracleType::Long);
assert_eq!(colinfo[22].name(), "UNCONSTRAINEDCOL");
assert_eq!(colinfo[22].oracle_type(), &OracleType::Number(0, -127));
assert_eq!(colinfo[23].name(), "SIGNEDINTCOL");
assert_eq!(colinfo[23].oracle_type(), &OracleType::Number(38, 0));
assert_eq!(colinfo[24].name(), "SUBOBJECTCOL");
assert_eq!(
colinfo[24].oracle_type().to_string(),
OracleType::Object(conn.object_type("UDT_SUBOBJECT")?).to_string()
);
assert_eq!(colinfo.len(), 25);
let mut stmt = conn.statement("select * from TestLongRaws").build()?;
let rows = stmt.query(&[])?;
let colinfo = rows.column_info();
assert_eq!(colinfo[0].name(), "INTCOL");
assert_eq!(colinfo[0].oracle_type(), &OracleType::Number(9, 0));
assert_eq!(colinfo[1].name(), "LONGRAWCOL");
assert_eq!(colinfo[1].oracle_type(), &OracleType::LongRaw);
assert_eq!(colinfo.len(), 2);
let mut stmt = conn.statement("select * from TestXml").build()?;
let rows = stmt.query(&[])?;
let colinfo = rows.column_info();
assert_eq!(colinfo[0].name(), "INTCOL");
assert_eq!(colinfo[0].oracle_type(), &OracleType::Number(9, 0));
assert_eq!(colinfo[1].name(), "XMLCOL");
assert_eq!(colinfo[1].oracle_type(), &OracleType::Xml);
assert_eq!(colinfo.len(), 2);
Ok(())
}
#[test]
fn fetch_rows_to_vec() -> Result<()> {
let conn = test_util::connect()?;
let mut stmt = conn
.statement("select IntCol from TestStrings order by IntCol")
.fetch_array_size(3)
.build()?;
let mut rows = Vec::new();
for row_result in stmt.query(&[])? {
rows.push(row_result?);
}
for (index, row) in rows.iter().enumerate() {
let int_col: usize = row.get(0)?;
assert_eq!(int_col, index + 1);
}
Ok(())
}
}